mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-12 18:36:49 +00:00
Compare commits
281 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc9f61ecf8 | ||
|
|
07d4528276 | ||
|
|
4ace780431 | ||
|
|
51df8de64f | ||
|
|
d4a5829858 | ||
|
|
ff5fe66994 | ||
|
|
37e50a68e4 | ||
|
|
a72c662b97 | ||
|
|
ff2db0c5f8 | ||
|
|
e9c60a33b9 | ||
|
|
8e059d3c69 | ||
|
|
806ba94a2e | ||
|
|
4bec583bff | ||
|
|
90256f9c28 | ||
|
|
eb284c1742 | ||
|
|
b614e17f48 | ||
|
|
5259eaae5f | ||
|
|
b0ad0fccfa | ||
|
|
2a83ad8689 | ||
|
|
c127110192 | ||
|
|
0470725112 | ||
|
|
0623bab084 | ||
|
|
4a0df684c7 | ||
|
|
7dccb5ba90 | ||
|
|
5a4be24df0 | ||
|
|
5cb5967977 | ||
|
|
aeb8d30dae | ||
|
|
1822fd97a6 | ||
|
|
4ff3e9fe10 | ||
|
|
a63994440d | ||
|
|
f6d0de1454 | ||
|
|
9b0e27a9d1 | ||
|
|
415d88f302 | ||
|
|
f9c98ea9a1 | ||
|
|
369a5a8a2f | ||
|
|
62341c6381 | ||
|
|
fa1becb791 | ||
|
|
50a1704994 | ||
|
|
302538c370 | ||
|
|
0f80b6ce12 | ||
|
|
1df8fc903e | ||
|
|
9a2dcb89af | ||
|
|
1661591f6c | ||
|
|
6dde720039 | ||
|
|
a54b725d6d | ||
|
|
265e76e8ca | ||
|
|
1516069888 | ||
|
|
cd263b761c | ||
|
|
c952ad16ad | ||
|
|
ca09c58f26 | ||
|
|
e876a25d59 | ||
|
|
2a780151be | ||
|
|
9d2009d2af | ||
|
|
f887533dda | ||
|
|
bef3f154d6 | ||
|
|
6fb775d5ed | ||
|
|
b5a8563725 | ||
|
|
1a9ade6de9 | ||
|
|
48df853ff5 | ||
|
|
2137a2addb | ||
|
|
e6a7239716 | ||
|
|
25bed92f2e | ||
|
|
909cd407dd | ||
|
|
f2d25748b1 | ||
|
|
6396e6e970 | ||
|
|
e52f813dae | ||
|
|
9ab2eec30c | ||
|
|
f96b37dae3 | ||
|
|
5b68b2f707 | ||
|
|
ff634bd870 | ||
|
|
7d21d88dc9 | ||
|
|
31b62d6296 | ||
|
|
5e68604f11 | ||
|
|
c4edd3a683 | ||
|
|
574781e901 | ||
|
|
8ea4f933da | ||
|
|
1e9820f291 | ||
|
|
34ed86cf2d | ||
|
|
2dfcedad56 | ||
|
|
706168f2f0 | ||
|
|
3ecb5c0c75 | ||
|
|
249a3c4e18 | ||
|
|
2b8ebba233 | ||
|
|
758394464b | ||
|
|
d3c624ae10 | ||
|
|
163c65849e | ||
|
|
99ff714913 | ||
|
|
c2f302450f | ||
|
|
9186bcd455 | ||
|
|
3d074b530f | ||
|
|
20783db373 | ||
|
|
d5b37a5171 | ||
|
|
b9ddb39edc | ||
|
|
3efe2f878d | ||
|
|
064fee79b3 | ||
|
|
d13f182801 | ||
|
|
ee34935223 | ||
|
|
0b7ce0d6db | ||
|
|
9dd02044a5 | ||
|
|
d8a7190f43 | ||
|
|
e5e0fcc887 | ||
|
|
53496ff9eb | ||
|
|
6635a89af7 | ||
|
|
c1248857b8 | ||
|
|
8c95399292 | ||
|
|
14c1abb861 | ||
|
|
e2d61f5e89 | ||
|
|
5a1ef04c67 | ||
|
|
e9e73ef0e4 | ||
|
|
be844253eb | ||
|
|
001e3688da | ||
|
|
b4ffd4dcc2 | ||
|
|
e5a9606449 | ||
|
|
9eabbb8dab | ||
|
|
1217811402 | ||
|
|
d4f2fcb98a | ||
|
|
0b2417849c | ||
|
|
3e8369c8f5 | ||
|
|
cbaedcec92 | ||
|
|
bf2c1a0c1d | ||
|
|
0284eebf35 | ||
|
|
e4ba515109 | ||
|
|
af1f6f6c6d | ||
|
|
b0e8ee42c1 | ||
|
|
86aeb6a3c7 | ||
|
|
5d83414e9b | ||
|
|
9820d7de38 | ||
|
|
910eca810a | ||
|
|
6feaddf747 | ||
|
|
10f6fb91f6 | ||
|
|
c7bd4b94b8 | ||
|
|
dd223a21f2 | ||
|
|
549366ff15 | ||
|
|
e92f60fb1c | ||
|
|
92b05c76a3 | ||
|
|
fa6fa0be64 | ||
|
|
752efac7cf | ||
|
|
2b1f1f748b | ||
|
|
798cd9e778 | ||
|
|
2f5b0a5afb | ||
|
|
de2d4a5870 | ||
|
|
b3728568a8 | ||
|
|
6ba98b4b2c | ||
|
|
d3d6921a0f | ||
|
|
68393f1444 | ||
|
|
229e6fa0a3 | ||
|
|
8a069787d1 | ||
|
|
29ed38a6cc | ||
|
|
4c7d3a491a | ||
|
|
b0f90cf43c | ||
|
|
02ec0634b3 | ||
|
|
64d21969f0 | ||
|
|
0995adeb59 | ||
|
|
073a3fe2b4 | ||
|
|
c07df6a5a6 | ||
|
|
a7d109833d | ||
|
|
0364a14073 | ||
|
|
ddc8d33356 | ||
|
|
cd81dfaead | ||
|
|
25073ada84 | ||
|
|
15b6f9dd5e | ||
|
|
0377e02dc1 | ||
|
|
f679708a4e | ||
|
|
0f748a3333 | ||
|
|
bdedaa06d8 | ||
|
|
6a41d9be87 | ||
|
|
4aa6c673ff | ||
|
|
aa3c1aa0fe | ||
|
|
31bac9641f | ||
|
|
60bdde6349 | ||
|
|
5faf33fabe | ||
|
|
f3b1a5dd35 | ||
|
|
07ccbf7f98 | ||
|
|
a3af6617a9 | ||
|
|
9f7fb654c8 | ||
|
|
b4a6686eac | ||
|
|
ac13ff114c | ||
|
|
af9187947f | ||
|
|
b7f7038934 | ||
|
|
f0fdbf7cf4 | ||
|
|
74ab31c0e6 | ||
|
|
6c054e6078 | ||
|
|
18974c68e1 | ||
|
|
78ab852141 | ||
|
|
4497173213 | ||
|
|
4d74d246e8 | ||
|
|
cc51537e93 | ||
|
|
07aececdcf | ||
|
|
c3fbf9cdf9 | ||
|
|
180598c603 | ||
|
|
69a5527e24 | ||
|
|
e4a6b3880f | ||
|
|
96ce4deb45 | ||
|
|
a3c179bd3f | ||
|
|
a57504ba1f | ||
|
|
25451eed6b | ||
|
|
50765563f8 | ||
|
|
2ae75ce13e | ||
|
|
18aa16650d | ||
|
|
314ed50e0f | ||
|
|
7346a49982 | ||
|
|
5182286218 | ||
|
|
a267848394 | ||
|
|
104bd31f9b | ||
|
|
8bc9985edb | ||
|
|
6dfc927730 | ||
|
|
3771e26eba | ||
|
|
fd21d35aee | ||
|
|
1417b924d2 | ||
|
|
2912c1b87d | ||
|
|
e746a0db7d | ||
|
|
84a2886003 | ||
|
|
c668677b8a | ||
|
|
2490cbfccb | ||
|
|
20a46a791c | ||
|
|
0e4fe08ac4 | ||
|
|
cf0f716534 | ||
|
|
b70cfa5968 | ||
|
|
f77beb50ff | ||
|
|
a941280982 | ||
|
|
9d40ee49e6 | ||
|
|
6abbdecb97 | ||
|
|
660ce92c27 | ||
|
|
c5bcef6ae4 | ||
|
|
d26a0b6b73 | ||
|
|
9400516b56 | ||
|
|
6ebfb02d0e | ||
|
|
11a77043eb | ||
|
|
58b43c7bc9 | ||
|
|
be41271038 | ||
|
|
04e13e1136 | ||
|
|
8868b7eb12 | ||
|
|
c85d00fcb8 | ||
|
|
2c6dab9c85 | ||
|
|
c45194473b | ||
|
|
3c0e6eeee2 | ||
|
|
ca5a5bf301 | ||
|
|
8c7a7348f4 | ||
|
|
3c2b6f4ed1 | ||
|
|
ec6f6056a8 | ||
|
|
c464b21d76 | ||
|
|
c83d249147 | ||
|
|
aae5ae9f1e | ||
|
|
888e0e0bb4 | ||
|
|
a0b0fb05a6 | ||
|
|
a11562e4dd | ||
|
|
2d872c25bf | ||
|
|
986837d601 | ||
|
|
353d6fbc5f | ||
|
|
0a88738faa | ||
|
|
4d6af57f79 | ||
|
|
110fd3ecdf | ||
|
|
1cb065f829 | ||
|
|
1cfa499b0e | ||
|
|
3fc0d4a666 | ||
|
|
de6c96d138 | ||
|
|
ffb8edef0b | ||
|
|
7a8b5e89be | ||
|
|
90690cd238 | ||
|
|
90c621ac07 | ||
|
|
2d5eccbf97 | ||
|
|
7e6259da31 | ||
|
|
11974689ef | ||
|
|
5739a82c19 | ||
|
|
3d6fd10e59 | ||
|
|
81482683cf | ||
|
|
57f1742f24 | ||
|
|
3b467dbae8 | ||
|
|
306390440a | ||
|
|
267131626e | ||
|
|
02099ebdae | ||
|
|
d153e58933 | ||
|
|
c2a59a374c | ||
|
|
2a1011921b | ||
|
|
df27ee0d3e | ||
|
|
c2851015ce | ||
|
|
d0c246c345 | ||
|
|
6aed9ba549 | ||
|
|
4f2bc33ec3 | ||
|
|
0f3404ca27 | ||
|
|
4398ea12b8 |
@@ -49,7 +49,6 @@ Metrics/LineLength:
|
||||
- app/controllers/spree/admin/orders_controller_decorator.rb
|
||||
- app/controllers/spree/admin/payments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/payment_methods_controller_decorator.rb
|
||||
- app/controllers/spree/admin/products_controller_decorator.rb
|
||||
- app/controllers/spree/admin/reports_controller_decorator.rb
|
||||
- app/controllers/spree/api/products_controller_decorator.rb
|
||||
- app/controllers/spree/credit_cards_controller.rb
|
||||
@@ -123,14 +122,11 @@ Metrics/LineLength:
|
||||
- app/serializers/api/admin/subscription_serializer.rb
|
||||
- app/serializers/api/admin/tag_rule_serializer.rb
|
||||
- app/serializers/api/admin/variant_override_serializer.rb
|
||||
- app/serializers/api/admin/variant_serializer.rb
|
||||
- app/services/cart_service.rb
|
||||
- app/services/default_stock_location.rb
|
||||
- app/services/embedded_page_service.rb
|
||||
- app/services/line_item_syncer.rb
|
||||
- app/services/order_cycle_form.rb
|
||||
- app/services/order_factory.rb
|
||||
- app/services/order_syncer.rb
|
||||
- app/services/subscriptions_count.rb
|
||||
- app/services/variants_stock_levels.rb
|
||||
- app/views/json/_groups.rabl
|
||||
@@ -157,7 +153,6 @@ Metrics/LineLength:
|
||||
- lib/open_food_network/permalink_generator.rb
|
||||
- lib/open_food_network/products_cache.rb
|
||||
- lib/open_food_network/products_renderer.rb
|
||||
- lib/open_food_network/proxy_order_syncer.rb
|
||||
- lib/open_food_network/reports/bulk_coop_allocation_report.rb
|
||||
- lib/open_food_network/reports/line_items.rb
|
||||
- lib/open_food_network/sales_tax_report.rb
|
||||
@@ -344,10 +339,8 @@ Metrics/LineLength:
|
||||
- spec/models/tag_rule/filter_shipping_methods_spec.rb
|
||||
- spec/models/variant_override_spec.rb
|
||||
- spec/performance/orders_controller_spec.rb
|
||||
- spec/performance/proxy_order_syncer_spec.rb
|
||||
- spec/performance/shop_controller_spec.rb
|
||||
- spec/requests/checkout/failed_checkout_spec.rb
|
||||
- spec/requests/checkout/paypal_spec.rb
|
||||
- spec/requests/checkout/stripe_connect_spec.rb
|
||||
- spec/requests/embedded_shopfronts_headers_spec.rb
|
||||
- spec/requests/shop_spec.rb
|
||||
@@ -443,7 +436,6 @@ Metrics/AbcSize:
|
||||
- app/models/spree/order_decorator.rb
|
||||
- app/models/spree/payment_decorator.rb
|
||||
- app/models/spree/product_decorator.rb
|
||||
- app/models/spree/product_set.rb
|
||||
- app/models/spree/taxon_decorator.rb
|
||||
- app/serializers/api/admin/enterprise_serializer.rb
|
||||
- app/serializers/api/product_serializer.rb
|
||||
@@ -565,6 +557,7 @@ Metrics/CyclomaticComplexity:
|
||||
- app/helpers/checkout_helper.rb
|
||||
- app/helpers/i18n_helper.rb
|
||||
- app/helpers/order_cycles_helper.rb
|
||||
- app/helpers/spree/admin/navigation_helper_decorator.rb
|
||||
- app/models/enterprise.rb
|
||||
- app/models/enterprise_relationship.rb
|
||||
- app/models/product_import/entry_processor.rb
|
||||
@@ -572,7 +565,6 @@ Metrics/CyclomaticComplexity:
|
||||
- app/models/spree/ability_decorator.rb
|
||||
- app/models/spree/payment_decorator.rb
|
||||
- app/models/spree/product_decorator.rb
|
||||
- app/models/spree/product_set.rb
|
||||
- app/models/variant_override_set.rb
|
||||
- app/services/cart_service.rb
|
||||
- lib/discourse/single_sign_on.rb
|
||||
@@ -600,7 +592,6 @@ Metrics/PerceivedComplexity:
|
||||
- app/models/spree/ability_decorator.rb
|
||||
- app/models/spree/order_decorator.rb
|
||||
- app/models/spree/product_decorator.rb
|
||||
- app/models/spree/product_set.rb
|
||||
- lib/discourse/single_sign_on.rb
|
||||
- lib/open_food_network/bulk_coop_report.rb
|
||||
- lib/open_food_network/enterprise_issue_validator.rb
|
||||
@@ -656,7 +647,6 @@ Metrics/MethodLength:
|
||||
- app/models/spree/payment_decorator.rb
|
||||
- app/models/spree/payment_method_decorator.rb
|
||||
- app/models/spree/product_decorator.rb
|
||||
- app/models/spree/product_set.rb
|
||||
- app/serializers/api/admin/order_cycle_serializer.rb
|
||||
- app/serializers/api/cached_enterprise_serializer.rb
|
||||
- app/services/order_cycle_form.rb
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config --exclude-limit 1400`
|
||||
# on 2019-05-28 16:29:07 +0100 using RuboCop version 0.57.2.
|
||||
# on 2019-07-23 14:09:18 +0100 using RuboCop version 0.57.2.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
@@ -32,15 +32,6 @@ Layout/EndAlignment:
|
||||
Layout/IndentHash:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
# Offense count: 7
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented, indented_relative_to_receiver
|
||||
Layout/MultilineMethodCallIndentation:
|
||||
Exclude:
|
||||
- 'app/models/spree/line_item_decorator.rb'
|
||||
- 'app/models/spree/product_decorator.rb'
|
||||
|
||||
# Offense count: 4
|
||||
Lint/AmbiguousOperator:
|
||||
Exclude:
|
||||
@@ -55,7 +46,7 @@ Lint/DuplicateMethods:
|
||||
- 'lib/discourse/single_sign_on.rb'
|
||||
- 'lib/open_food_network/subscription_summary.rb'
|
||||
|
||||
# Offense count: 15
|
||||
# Offense count: 8
|
||||
Lint/IneffectiveAccessModifier:
|
||||
Exclude:
|
||||
- 'app/models/column_preference.rb'
|
||||
@@ -79,7 +70,13 @@ Lint/UnderscorePrefixedVariableName:
|
||||
Exclude:
|
||||
- 'spec/support/cancan_helper.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
Lint/UnneededCopDisableDirective:
|
||||
Exclude:
|
||||
- 'app/models/product_import/entry_validator.rb'
|
||||
|
||||
# Offense count: 5
|
||||
# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods.
|
||||
Lint/UselessAccessModifier:
|
||||
Exclude:
|
||||
@@ -89,28 +86,47 @@ Lint/UselessAccessModifier:
|
||||
- 'lib/open_food_network/reports/bulk_coop_report.rb'
|
||||
- 'spec/lib/open_food_network/reports/report_spec.rb'
|
||||
|
||||
# Offense count: 91
|
||||
# Offense count: 8
|
||||
# Configuration parameters: CheckForMethodsWithNoSideEffects.
|
||||
Lint/Void:
|
||||
Exclude:
|
||||
- 'app/serializers/api/enterprise_serializer.rb'
|
||||
- 'spec/features/admin/bulk_product_update_spec.rb'
|
||||
- 'spec/features/admin/enterprise_groups_spec.rb'
|
||||
- 'spec/features/admin/enterprises/index_spec.rb'
|
||||
- 'spec/features/admin/enterprises_spec.rb'
|
||||
- 'spec/features/admin/order_cycles_spec.rb'
|
||||
- 'spec/features/admin/payment_method_spec.rb'
|
||||
- 'spec/features/admin/products_spec.rb'
|
||||
- 'spec/features/admin/variant_overrides_spec.rb'
|
||||
- 'spec/features/admin/variants_spec.rb'
|
||||
- 'spec/features/consumer/shopping/checkout_spec.rb'
|
||||
- 'spec/features/consumer/shopping/shopping_spec.rb'
|
||||
- 'spec/features/consumer/shopping/variant_overrides_spec.rb'
|
||||
|
||||
# Offense count: 109
|
||||
# Offense count: 15
|
||||
Metrics/AbcSize:
|
||||
Max: 36
|
||||
|
||||
# Offense count: 13
|
||||
# Configuration parameters: CountComments, ExcludedMethods.
|
||||
Metrics/BlockLength:
|
||||
Max: 774
|
||||
Max: 115
|
||||
|
||||
# Offense count: 2
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/ClassLength:
|
||||
Max: 169
|
||||
|
||||
# Offense count: 1
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 8
|
||||
|
||||
# Offense count: 8
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/MethodLength:
|
||||
Max: 31
|
||||
|
||||
# Offense count: 2
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/ModuleLength:
|
||||
Max: 208
|
||||
|
||||
# Offense count: 2
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 11
|
||||
|
||||
# Offense count: 7
|
||||
Naming/AccessorMethodName:
|
||||
@@ -160,7 +176,7 @@ Naming/PredicateName:
|
||||
- 'lib/open_food_network/packing_report.rb'
|
||||
- 'lib/tasks/data.rake'
|
||||
|
||||
# Offense count: 12
|
||||
# Offense count: 11
|
||||
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
||||
# AllowedNames: io, id, to, by, on, in, at
|
||||
Naming/UncommunicativeMethodParamName:
|
||||
@@ -288,13 +304,12 @@ Style/CaseEquality:
|
||||
- 'app/helpers/angular_form_helper.rb'
|
||||
- 'spec/models/spree/payment_spec.rb'
|
||||
|
||||
# Offense count: 79
|
||||
# Offense count: 78
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AutoCorrect, EnforcedStyle.
|
||||
# SupportedStyles: nested, compact
|
||||
Style/ClassAndModuleChildren:
|
||||
Exclude:
|
||||
- 'app/controllers/spree/store_controller_decorator.rb'
|
||||
- 'app/helpers/angular_form_helper.rb'
|
||||
- 'app/models/calculator/flat_percent_per_item.rb'
|
||||
- 'app/models/spree/concerns/payment_method_distributors.rb'
|
||||
@@ -379,11 +394,27 @@ Style/CommentedKeyword:
|
||||
Exclude:
|
||||
- 'app/controllers/application_controller.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
|
||||
# SupportedStyles: assign_to_condition, assign_inside_condition
|
||||
Style/ConditionalAssignment:
|
||||
Exclude:
|
||||
- 'app/controllers/spree/api/products_controller.rb'
|
||||
- 'app/controllers/spree/api/taxons_controller.rb'
|
||||
- 'app/controllers/spree/api/variants_controller.rb'
|
||||
|
||||
# Offense count: 2
|
||||
Style/DateTime:
|
||||
Exclude:
|
||||
- 'lib/open_food_network/users_and_enterprises_report.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
Style/EachWithObject:
|
||||
Exclude:
|
||||
- 'app/controllers/spree/api/base_controller.rb'
|
||||
|
||||
# Offense count: 5
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: annotated, template, unannotated
|
||||
@@ -393,7 +424,7 @@ Style/FormatStringToken:
|
||||
- 'lib/open_food_network/sales_tax_report.rb'
|
||||
- 'spec/models/enterprise_spec.rb'
|
||||
|
||||
# Offense count: 69
|
||||
# Offense count: 68
|
||||
# Configuration parameters: MinBodyLength.
|
||||
Style/GuardClause:
|
||||
Exclude:
|
||||
@@ -410,7 +441,9 @@ Style/GuardClause:
|
||||
- 'app/controllers/spree/admin/products_controller_decorator.rb'
|
||||
- 'app/controllers/spree/admin/resource_controller_decorator.rb'
|
||||
- 'app/controllers/spree/admin/variants_controller_decorator.rb'
|
||||
- 'app/controllers/spree/orders_controller_decorator.rb'
|
||||
- 'app/controllers/spree/api/base_controller.rb'
|
||||
- 'app/controllers/spree/checkout_controller.rb'
|
||||
- 'app/controllers/spree/orders_controller.rb'
|
||||
- 'app/controllers/spree/paypal_controller_decorator.rb'
|
||||
- 'app/jobs/products_cache_integrity_checker_job.rb'
|
||||
- 'app/models/enterprise.rb'
|
||||
@@ -434,12 +467,23 @@ Style/GuardClause:
|
||||
- 'spec/support/request/distribution_helper.rb'
|
||||
- 'spec/support/request/shop_workflow.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# Offense count: 6
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols.
|
||||
# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys
|
||||
Style/HashSyntax:
|
||||
Exclude:
|
||||
- 'app/controllers/spree/api/base_controller.rb'
|
||||
- 'app/controllers/spree/checkout_controller.rb'
|
||||
- 'app/controllers/spree/orders_controller.rb'
|
||||
|
||||
# Offense count: 4
|
||||
Style/IfInsideElse:
|
||||
Exclude:
|
||||
- 'app/controllers/admin/column_preferences_controller.rb'
|
||||
- 'app/controllers/admin/variant_overrides_controller.rb'
|
||||
- 'app/controllers/spree/admin/products_controller_decorator.rb'
|
||||
- 'app/controllers/spree/api/taxons_controller.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Style/MissingRespondToMissing:
|
||||
@@ -492,9 +536,10 @@ Style/RegexpLiteral:
|
||||
- 'spec/mailers/subscription_mailer_spec.rb'
|
||||
- 'spec/models/content_configuration_spec.rb'
|
||||
|
||||
# Offense count: 243
|
||||
# Offense count: 244
|
||||
Style/Send:
|
||||
Exclude:
|
||||
- 'app/controllers/spree/checkout_controller.rb'
|
||||
- 'app/models/spree/shipping_method_decorator.rb'
|
||||
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
|
||||
- 'spec/controllers/checkout_controller_spec.rb'
|
||||
@@ -541,3 +586,9 @@ Style/Send:
|
||||
Style/StructInheritance:
|
||||
Exclude:
|
||||
- 'lib/open_food_network/enterprise_fee_applicator.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
Style/UnlessElse:
|
||||
Exclude:
|
||||
- 'app/controllers/spree/api/variants_controller.rb'
|
||||
|
||||
@@ -42,6 +42,6 @@ $ docker-compose up
|
||||
```
|
||||
|
||||
This command will setup the database and seed it with sample data. The default admin user is 'ofn@example.com' with 'ofn123' password.
|
||||
Check the app in the browser at `http:://localhost:3000`.
|
||||
Check the app in the browser at `http://localhost:3000`.
|
||||
|
||||
You will then get the trace of the containers in the terminal. You can stop the containers using Ctrl-C in the terminal.
|
||||
|
||||
10
Gemfile
10
Gemfile
@@ -37,7 +37,7 @@ gem 'stripe'
|
||||
gem 'activemerchant', '~> 1.78'
|
||||
|
||||
gem 'devise', '~> 2.2.5'
|
||||
gem 'devise-encryptable', '0.1.2'
|
||||
gem 'devise-encryptable', '0.2.0'
|
||||
gem 'jwt', '~> 2.2'
|
||||
gem 'oauth2', '~> 1.4.1' # Used for Stripe Connect
|
||||
|
||||
@@ -49,6 +49,10 @@ gem 'delayed_job_web'
|
||||
# When merged, revert to upstream gem
|
||||
gem 'simple_form', github: 'RohanM/simple_form'
|
||||
|
||||
# Spree's default pagination gem (locked to the current version used by Spree)
|
||||
# We use it's methods in OFN code as well, so this is a direct dependency
|
||||
gem 'kaminari', '~> 0.14.1'
|
||||
|
||||
gem 'andand'
|
||||
gem 'angularjs-rails', '1.5.5'
|
||||
gem 'aws-sdk'
|
||||
@@ -119,6 +123,10 @@ gem 'jquery-rails', '3.0.4'
|
||||
|
||||
gem 'ofn-qz', github: 'openfoodfoundation/ofn-qz', ref: '60da2ae4c44cbb4c8d602f59fb5fff8d0f21db3c'
|
||||
|
||||
group :production, :staging do
|
||||
gem 'ddtrace'
|
||||
end
|
||||
|
||||
group :test, :development do
|
||||
# Pretty printed test output
|
||||
gem 'atomic'
|
||||
|
||||
31
Gemfile.lock
31
Gemfile.lock
@@ -223,16 +223,18 @@ GEM
|
||||
activerecord (>= 3.2.0, < 5.0)
|
||||
fog (~> 1.0)
|
||||
rails (>= 3.2.0, < 5.0)
|
||||
ddtrace (0.26.0)
|
||||
msgpack
|
||||
debugger-linecache (1.2.0)
|
||||
deface (1.0.2)
|
||||
colorize (>= 0.5.8)
|
||||
nokogiri (~> 1.6.0)
|
||||
polyglot
|
||||
rails (>= 3.1)
|
||||
delayed_job (4.1.5)
|
||||
activesupport (>= 3.0, < 5.3)
|
||||
delayed_job_active_record (4.1.3)
|
||||
activerecord (>= 3.0, < 5.3)
|
||||
delayed_job (4.1.8)
|
||||
activesupport (>= 3.0, < 6.1)
|
||||
delayed_job_active_record (4.1.4)
|
||||
activerecord (>= 3.0, < 6.1)
|
||||
delayed_job (>= 3.0, < 5)
|
||||
delayed_job_web (1.4.3)
|
||||
activerecord (> 3.0.0)
|
||||
@@ -244,7 +246,7 @@ GEM
|
||||
orm_adapter (~> 0.1)
|
||||
railties (~> 3.1)
|
||||
warden (~> 1.2.1)
|
||||
devise-encryptable (0.1.2)
|
||||
devise-encryptable (0.2.0)
|
||||
devise (>= 2.1.0)
|
||||
diff-lcs (1.3)
|
||||
diffy (3.3.0)
|
||||
@@ -454,7 +456,7 @@ GEM
|
||||
rspec (>= 2.99.0, < 4.0)
|
||||
haml (4.0.7)
|
||||
tilt
|
||||
hashdiff (0.4.0)
|
||||
hashdiff (1.0.0)
|
||||
highline (1.6.18)
|
||||
hike (1.2.3)
|
||||
http_parser.rb (0.6.0)
|
||||
@@ -483,7 +485,7 @@ GEM
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
kgio (2.11.2)
|
||||
knapsack (1.17.2)
|
||||
knapsack (1.18.0)
|
||||
rake
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
@@ -505,11 +507,12 @@ GEM
|
||||
railties (>= 3.1)
|
||||
money (5.1.1)
|
||||
i18n (~> 0.6.0)
|
||||
msgpack (1.3.1)
|
||||
multi_json (1.13.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
nenv (0.3.0)
|
||||
net-http-persistent (3.0.1)
|
||||
net-http-persistent (3.1.0)
|
||||
connection_pool (~> 2.2)
|
||||
newrelic_rpm (3.18.1.330)
|
||||
nokogiri (1.6.8.1)
|
||||
@@ -555,7 +558,7 @@ GEM
|
||||
pry-byebug (3.4.3)
|
||||
byebug (>= 9.0, < 9.1)
|
||||
pry (~> 0.10)
|
||||
public_suffix (3.0.3)
|
||||
public_suffix (3.1.1)
|
||||
rabl (0.8.4)
|
||||
activesupport (>= 2.3.14)
|
||||
rack (1.4.7)
|
||||
@@ -591,7 +594,7 @@ GEM
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
raindrops (0.19.0)
|
||||
rake (12.3.2)
|
||||
rake (12.3.3)
|
||||
ransack (0.7.2)
|
||||
actionpack (~> 3.0)
|
||||
activerecord (~> 3.0)
|
||||
@@ -698,7 +701,7 @@ GEM
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
state_machine (1.2.0)
|
||||
stringex (1.5.1)
|
||||
stripe (4.19.0)
|
||||
stripe (4.24.0)
|
||||
faraday (~> 0.13)
|
||||
net-http-persistent (~> 3.0)
|
||||
therubyracer (0.12.0)
|
||||
@@ -736,7 +739,7 @@ GEM
|
||||
nokogiri (~> 1.6)
|
||||
rubyzip (~> 1.0)
|
||||
selenium-webdriver (~> 3.0)
|
||||
webmock (3.6.0)
|
||||
webmock (3.6.2)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
@@ -775,12 +778,13 @@ DEPENDENCIES
|
||||
dalli
|
||||
database_cleaner (= 0.7.1)
|
||||
db2fog
|
||||
ddtrace
|
||||
debugger-linecache
|
||||
deface (= 1.0.2)
|
||||
delayed_job_active_record
|
||||
delayed_job_web
|
||||
devise (~> 2.2.5)
|
||||
devise-encryptable (= 0.1.2)
|
||||
devise-encryptable (= 0.2.0)
|
||||
diffy
|
||||
eventmachine (>= 1.2.3)
|
||||
factory_bot_rails
|
||||
@@ -804,6 +808,7 @@ DEPENDENCIES
|
||||
jquery-rails (= 3.0.4)
|
||||
json_spec (~> 1.1.4)
|
||||
jwt (~> 2.2)
|
||||
kaminari (~> 0.14.1)
|
||||
knapsack
|
||||
letter_opener (>= 1.4.1)
|
||||
listen (= 3.0.8)
|
||||
|
||||
@@ -1,267 +1,287 @@
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, $window, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories) ->
|
||||
$scope.loading = true
|
||||
$scope.loadingAllPages = true
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories, RequestMonitor) ->
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.StatusMessage = StatusMessage
|
||||
$scope.columns = Columns.columns
|
||||
|
||||
$scope.columns = Columns.columns
|
||||
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
||||
|
||||
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
||||
$scope.RequestMonitor = RequestMonitor
|
||||
$scope.pagination = BulkProducts.pagination
|
||||
$scope.per_page_options = [
|
||||
{id: 15, name: t('js.admin.orders.index.per_page', results: 15)},
|
||||
{id: 50, name: t('js.admin.orders.index.per_page', results: 50)},
|
||||
{id: 100, name: t('js.admin.orders.index.per_page', results: 100)}
|
||||
]
|
||||
|
||||
$scope.filterableColumns = [
|
||||
{ name: t("label_producers"), db_column: "producer_name" },
|
||||
{ name: t("name"), db_column: "name" }
|
||||
]
|
||||
$scope.filterableColumns = [
|
||||
{ name: t("label_producers"), db_column: "producer_name" },
|
||||
{ name: t("name"), db_column: "name" }
|
||||
]
|
||||
|
||||
$scope.filterTypes = [
|
||||
{ name: t("equals"), predicate: "eq" },
|
||||
{ name: t("contains"), predicate: "cont" }
|
||||
]
|
||||
$scope.filterTypes = [
|
||||
{ name: t("equals"), predicate: "eq" },
|
||||
{ name: t("contains"), predicate: "cont" }
|
||||
]
|
||||
|
||||
$scope.optionTabs =
|
||||
filters: { title: t("filter_products"), visible: false }
|
||||
$scope.optionTabs =
|
||||
filters: { title: t("filter_products"), visible: false }
|
||||
|
||||
$scope.producers = producers
|
||||
$scope.taxons = Taxons.all
|
||||
$scope.tax_categories = tax_categories
|
||||
$scope.producerFilter = ""
|
||||
$scope.categoryFilter = ""
|
||||
$scope.importDateFilter = ""
|
||||
$scope.page = 1
|
||||
$scope.per_page = 15
|
||||
$scope.products = BulkProducts.products
|
||||
$scope.query = ""
|
||||
$scope.DisplayProperties = DisplayProperties
|
||||
|
||||
$scope.initialise = ->
|
||||
SpreeApiAuth.authorise()
|
||||
.then ->
|
||||
$scope.spree_api_key_ok = true
|
||||
$scope.fetchProducts()
|
||||
.catch (message) ->
|
||||
$scope.api_error_msg = message
|
||||
|
||||
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter, per_page]', ->
|
||||
$scope.page = 1 # Reset page when changing filters for new search
|
||||
|
||||
$scope.changePage = (newPage) ->
|
||||
$scope.page = newPage
|
||||
$scope.fetchProducts()
|
||||
|
||||
$scope.fetchProducts = ->
|
||||
removeClearedValues()
|
||||
params = {
|
||||
'q[name_cont]': $scope.query,
|
||||
'q[supplier_id_eq]': $scope.producerFilter,
|
||||
'q[primary_taxon_id_eq]': $scope.categoryFilter,
|
||||
import_date: $scope.importDateFilter,
|
||||
page: $scope.page,
|
||||
per_page: $scope.per_page
|
||||
}
|
||||
RequestMonitor.load(BulkProducts.fetch(params).$promise).then ->
|
||||
$scope.resetProducts()
|
||||
|
||||
removeClearedValues = ->
|
||||
delete $scope.producerFilter if $scope.producerFilter == "0"
|
||||
delete $scope.categoryFilter if $scope.categoryFilter == "0"
|
||||
delete $scope.importDateFilter if $scope.importDateFilter == "0"
|
||||
|
||||
$timeout ->
|
||||
if $scope.showLatestImport
|
||||
$scope.importDateFilter = $scope.importDates[1].id
|
||||
|
||||
$scope.resetProducts = ->
|
||||
DirtyProducts.clear()
|
||||
StatusMessage.clear()
|
||||
|
||||
$scope.updateOnHand = (product) ->
|
||||
on_demand_variants = []
|
||||
if product.variants
|
||||
on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
|
||||
|
||||
unless product.on_demand || on_demand_variants.length > 0
|
||||
product.on_hand = $scope.onHand(product)
|
||||
|
||||
|
||||
$scope.producers = producers
|
||||
$scope.taxons = Taxons.all
|
||||
$scope.tax_categories = tax_categories
|
||||
$scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers
|
||||
$scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons
|
||||
$scope.onHand = (product) ->
|
||||
onHand = 0
|
||||
if product.hasOwnProperty("variants") and product.variants instanceof Object
|
||||
for id, variant of product.variants
|
||||
onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
|
||||
else
|
||||
onHand = "error"
|
||||
onHand
|
||||
|
||||
$scope.shiftTab = (tab) ->
|
||||
$scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined
|
||||
tab.visible = !tab.visible
|
||||
$scope.visibleTab = tab
|
||||
|
||||
$scope.resetSelectFilters = ->
|
||||
$scope.query = ""
|
||||
$scope.producerFilter = "0"
|
||||
$scope.categoryFilter = "0"
|
||||
$scope.importDateFilter = "0"
|
||||
$scope.products = BulkProducts.products
|
||||
$scope.filteredProducts = []
|
||||
$scope.currentFilters = []
|
||||
$scope.limit = 15
|
||||
$scope.query = ""
|
||||
$scope.DisplayProperties = DisplayProperties
|
||||
|
||||
$scope.initialise = ->
|
||||
SpreeApiAuth.authorise()
|
||||
.then ->
|
||||
$scope.spree_api_key_ok = true
|
||||
$scope.fetchProducts()
|
||||
.catch (message) ->
|
||||
$scope.api_error_msg = message
|
||||
|
||||
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter]', ->
|
||||
$scope.limit = 15 # Reset limit whenever searching
|
||||
|
||||
$scope.fetchProducts = ->
|
||||
$scope.loading = true
|
||||
$scope.loadingAllPages = true
|
||||
BulkProducts.fetch($scope.currentFilters, ->
|
||||
$scope.loadingAllPages = false
|
||||
).then ->
|
||||
$scope.resetProducts()
|
||||
$scope.loading = false
|
||||
|
||||
$timeout ->
|
||||
if $scope.showLatestImport
|
||||
$scope.importDateFilter = $scope.importDates[1].id
|
||||
|
||||
$scope.resetProducts = ->
|
||||
DirtyProducts.clear()
|
||||
StatusMessage.clear()
|
||||
|
||||
$scope.updateOnHand = (product) ->
|
||||
on_demand_variants = []
|
||||
if product.variants
|
||||
on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
|
||||
|
||||
unless product.on_demand || on_demand_variants.length > 0
|
||||
product.on_hand = $scope.onHand(product)
|
||||
$scope.editWarn = (product, variant) ->
|
||||
if (DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
|
||||
window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||
|
||||
|
||||
$scope.onHand = (product) ->
|
||||
onHand = 0
|
||||
if product.hasOwnProperty("variants") and product.variants instanceof Object
|
||||
for id, variant of product.variants
|
||||
onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
|
||||
else
|
||||
onHand = "error"
|
||||
onHand
|
||||
$scope.toggleShowAllVariants = ->
|
||||
showVariants = !DisplayProperties.showVariants 0
|
||||
$scope.products.forEach (product) ->
|
||||
DisplayProperties.setShowVariants product.id, showVariants
|
||||
DisplayProperties.setShowVariants 0, showVariants
|
||||
|
||||
$scope.shiftTab = (tab) ->
|
||||
$scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined
|
||||
tab.visible = !tab.visible
|
||||
$scope.visibleTab = tab
|
||||
|
||||
$scope.resetSelectFilters = ->
|
||||
$scope.query = ""
|
||||
$scope.producerFilter = "0"
|
||||
$scope.categoryFilter = "0"
|
||||
$scope.importDateFilter = "0"
|
||||
|
||||
$scope.editWarn = (product, variant) ->
|
||||
if (DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
|
||||
window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||
$scope.addVariant = (product) ->
|
||||
product.variants.push
|
||||
id: $scope.nextVariantId()
|
||||
unit_value: null
|
||||
unit_description: null
|
||||
on_demand: false
|
||||
display_as: null
|
||||
display_name: null
|
||||
on_hand: null
|
||||
price: null
|
||||
DisplayProperties.setShowVariants product.id, true
|
||||
|
||||
|
||||
$scope.toggleShowAllVariants = ->
|
||||
showVariants = !DisplayProperties.showVariants 0
|
||||
$scope.filteredProducts.forEach (product) ->
|
||||
DisplayProperties.setShowVariants product.id, showVariants
|
||||
DisplayProperties.setShowVariants 0, showVariants
|
||||
$scope.nextVariantId = ->
|
||||
$scope.variantIdCounter = 0 unless $scope.variantIdCounter?
|
||||
$scope.variantIdCounter -= 1
|
||||
$scope.variantIdCounter
|
||||
|
||||
$scope.addVariant = (product) ->
|
||||
product.variants.push
|
||||
id: $scope.nextVariantId()
|
||||
unit_value: null
|
||||
unit_description: null
|
||||
on_demand: false
|
||||
display_as: null
|
||||
display_name: null
|
||||
on_hand: null
|
||||
price: null
|
||||
DisplayProperties.setShowVariants product.id, true
|
||||
|
||||
|
||||
$scope.nextVariantId = ->
|
||||
$scope.variantIdCounter = 0 unless $scope.variantIdCounter?
|
||||
$scope.variantIdCounter -= 1
|
||||
$scope.variantIdCounter
|
||||
|
||||
$scope.deleteProduct = (product) ->
|
||||
if confirm("Are you sure?")
|
||||
$http(
|
||||
method: "DELETE"
|
||||
url: "/api/products/" + product.id + "/soft_delete"
|
||||
).success (data) ->
|
||||
$scope.products.splice $scope.products.indexOf(product), 1
|
||||
DirtyProducts.deleteProduct product.id
|
||||
$scope.displayDirtyProducts()
|
||||
|
||||
|
||||
$scope.deleteVariant = (product, variant) ->
|
||||
if product.variants.length > 1
|
||||
if !$scope.variantSaved(variant)
|
||||
$scope.removeVariant(product, variant)
|
||||
else
|
||||
if confirm(t("are_you_sure"))
|
||||
$http(
|
||||
method: "DELETE"
|
||||
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete"
|
||||
).success (data) ->
|
||||
$scope.removeVariant(product, variant)
|
||||
else
|
||||
alert(t("delete_product_variant"))
|
||||
|
||||
$scope.removeVariant = (product, variant) ->
|
||||
product.variants.splice product.variants.indexOf(variant), 1
|
||||
DirtyProducts.deleteVariant product.id, variant.id
|
||||
$scope.displayDirtyProducts()
|
||||
|
||||
|
||||
$scope.cloneProduct = (product) ->
|
||||
BulkProducts.cloneProduct product
|
||||
|
||||
$scope.hasVariants = (product) ->
|
||||
product.variants.length > 0
|
||||
|
||||
|
||||
$scope.hasUnit = (product) ->
|
||||
product.variant_unit_with_scale?
|
||||
|
||||
|
||||
$scope.variantSaved = (variant) ->
|
||||
variant.hasOwnProperty('id') && variant.id > 0
|
||||
|
||||
|
||||
$scope.hasOnDemandVariants = (product) ->
|
||||
(variant for id, variant of product.variants when variant.on_demand).length > 0
|
||||
|
||||
|
||||
$scope.submitProducts = ->
|
||||
# Pack pack $scope.products, so they will match the list returned from the server,
|
||||
# then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
|
||||
$scope.packProduct product for id, product of $scope.products
|
||||
$scope.packProduct product for id, product of DirtyProducts.all()
|
||||
|
||||
productsToSubmit = filterSubmitProducts(DirtyProducts.all())
|
||||
if productsToSubmit.length > 0
|
||||
$scope.updateProducts productsToSubmit # Don't submit an empty list
|
||||
else
|
||||
StatusMessage.display 'alert', t("products_change")
|
||||
|
||||
|
||||
$scope.updateProducts = (productsToSubmit) ->
|
||||
$scope.displayUpdating()
|
||||
$scope.deleteProduct = (product) ->
|
||||
if confirm("Are you sure?")
|
||||
$http(
|
||||
method: "POST"
|
||||
url: "/admin/products/bulk_update"
|
||||
data:
|
||||
products: productsToSubmit
|
||||
filters: $scope.currentFilters
|
||||
).success((data) ->
|
||||
DirtyProducts.clear()
|
||||
BulkProducts.updateVariantLists(data.products || [])
|
||||
$timeout -> $scope.displaySuccess()
|
||||
).error (data, status) ->
|
||||
if status == 400 && data.errors? && data.errors.length > 0
|
||||
errors = error + "\n" for error in data.errors
|
||||
alert t("products_update_error") + "\n" + errors
|
||||
$scope.displayFailure t("products_update_error")
|
||||
else
|
||||
$scope.displayFailure t("products_update_error_data") + status
|
||||
method: "DELETE"
|
||||
url: "/api/products/" + product.id + "/soft_delete"
|
||||
).success (data) ->
|
||||
$scope.products.splice $scope.products.indexOf(product), 1
|
||||
DirtyProducts.deleteProduct product.id
|
||||
$scope.displayDirtyProducts()
|
||||
|
||||
$scope.cancel = (destination) ->
|
||||
$window.location = destination
|
||||
|
||||
$scope.packProduct = (product) ->
|
||||
if product.variant_unit_with_scale
|
||||
match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
|
||||
if match
|
||||
product.variant_unit = match[1]
|
||||
product.variant_unit_scale = parseFloat(match[2])
|
||||
else
|
||||
product.variant_unit = product.variant_unit_with_scale
|
||||
product.variant_unit_scale = null
|
||||
$scope.deleteVariant = (product, variant) ->
|
||||
if product.variants.length > 1
|
||||
if !$scope.variantSaved(variant)
|
||||
$scope.removeVariant(product, variant)
|
||||
else
|
||||
product.variant_unit = product.variant_unit_scale = null
|
||||
if confirm(t("are_you_sure"))
|
||||
$http(
|
||||
method: "DELETE"
|
||||
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete"
|
||||
).success (data) ->
|
||||
$scope.removeVariant(product, variant)
|
||||
else
|
||||
alert(t("delete_product_variant"))
|
||||
|
||||
$scope.packVariant product, product.master if product.master
|
||||
|
||||
if product.variants
|
||||
for id, variant of product.variants
|
||||
$scope.packVariant product, variant
|
||||
$scope.removeVariant = (product, variant) ->
|
||||
product.variants.splice product.variants.indexOf(variant), 1
|
||||
DirtyProducts.deleteVariant product.id, variant.id
|
||||
$scope.displayDirtyProducts()
|
||||
|
||||
|
||||
$scope.packVariant = (product, variant) ->
|
||||
if variant.hasOwnProperty("unit_value_with_description")
|
||||
match = variant.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/)
|
||||
if match
|
||||
product = BulkProducts.find product.id
|
||||
variant.unit_value = parseFloat(match[1])
|
||||
variant.unit_value = null if isNaN(variant.unit_value)
|
||||
variant.unit_value *= product.variant_unit_scale if variant.unit_value && product.variant_unit_scale
|
||||
variant.unit_description = match[3]
|
||||
$scope.cloneProduct = (product) ->
|
||||
BulkProducts.cloneProduct product
|
||||
|
||||
$scope.incrementLimit = ->
|
||||
if $scope.limit < $scope.products.length
|
||||
$scope.limit = $scope.limit + 5
|
||||
$scope.hasVariants = (product) ->
|
||||
product.variants.length > 0
|
||||
|
||||
|
||||
$scope.displayUpdating = ->
|
||||
StatusMessage.display 'progress', t("saving")
|
||||
$scope.hasUnit = (product) ->
|
||||
product.variant_unit_with_scale?
|
||||
|
||||
|
||||
$scope.displaySuccess = ->
|
||||
StatusMessage.display 'success',t("products_changes_saved")
|
||||
$scope.bulk_product_form.$setPristine()
|
||||
$scope.variantSaved = (variant) ->
|
||||
variant.hasOwnProperty('id') && variant.id > 0
|
||||
|
||||
|
||||
$scope.displayFailure = (failMessage) ->
|
||||
StatusMessage.display 'failure', t("products_update_error_msg") + "#{failMessage}"
|
||||
$scope.hasOnDemandVariants = (product) ->
|
||||
(variant for id, variant of product.variants when variant.on_demand).length > 0
|
||||
|
||||
|
||||
$scope.displayDirtyProducts = ->
|
||||
count = DirtyProducts.count()
|
||||
switch count
|
||||
when 0 then StatusMessage.clear()
|
||||
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
|
||||
else StatusMessage.display 'notice', t("products_unsaved", n: count)
|
||||
$scope.submitProducts = ->
|
||||
# Pack pack $scope.products, so they will match the list returned from the server,
|
||||
# then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
|
||||
$scope.packProduct product for id, product of $scope.products
|
||||
$scope.packProduct product for id, product of DirtyProducts.all()
|
||||
|
||||
productsToSubmit = filterSubmitProducts(DirtyProducts.all())
|
||||
if productsToSubmit.length > 0
|
||||
$scope.updateProducts productsToSubmit # Don't submit an empty list
|
||||
else
|
||||
StatusMessage.display 'alert', t("products_change")
|
||||
|
||||
|
||||
$scope.updateProducts = (productsToSubmit) ->
|
||||
$scope.displayUpdating()
|
||||
$http(
|
||||
method: "POST"
|
||||
url: "/admin/products/bulk_update"
|
||||
data:
|
||||
products: productsToSubmit
|
||||
filters:
|
||||
'q[name_cont]': $scope.query
|
||||
'q[supplier_id_eq]': $scope.producerFilter
|
||||
'q[primary_taxon_id_eq]': $scope.categoryFilter
|
||||
import_date: $scope.importDateFilter
|
||||
page: $scope.page
|
||||
per_page: $scope.per_page
|
||||
).success((data) ->
|
||||
DirtyProducts.clear()
|
||||
BulkProducts.updateVariantLists(data.products || [])
|
||||
$timeout -> $scope.displaySuccess()
|
||||
).error (data, status) ->
|
||||
if status == 400 && data.errors? && data.errors.length > 0
|
||||
errors = error + "\n" for error in data.errors
|
||||
alert t("products_update_error") + "\n" + errors
|
||||
$scope.displayFailure t("products_update_error")
|
||||
else
|
||||
$scope.displayFailure t("products_update_error_data") + status
|
||||
|
||||
$scope.cancel = (destination) ->
|
||||
$window.location = destination
|
||||
|
||||
$scope.packProduct = (product) ->
|
||||
if product.variant_unit_with_scale
|
||||
match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
|
||||
if match
|
||||
product.variant_unit = match[1]
|
||||
product.variant_unit_scale = parseFloat(match[2])
|
||||
else
|
||||
product.variant_unit = product.variant_unit_with_scale
|
||||
product.variant_unit_scale = null
|
||||
else
|
||||
product.variant_unit = product.variant_unit_scale = null
|
||||
|
||||
$scope.packVariant product, product.master if product.master
|
||||
|
||||
if product.variants
|
||||
for id, variant of product.variants
|
||||
$scope.packVariant product, variant
|
||||
|
||||
|
||||
$scope.packVariant = (product, variant) ->
|
||||
if variant.hasOwnProperty("unit_value_with_description")
|
||||
match = variant.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/)
|
||||
if match
|
||||
product = BulkProducts.find product.id
|
||||
variant.unit_value = parseFloat(match[1])
|
||||
variant.unit_value = null if isNaN(variant.unit_value)
|
||||
variant.unit_value *= product.variant_unit_scale if variant.unit_value && product.variant_unit_scale
|
||||
variant.unit_description = match[3]
|
||||
|
||||
$scope.incrementLimit = ->
|
||||
if $scope.limit < $scope.products.length
|
||||
$scope.limit = $scope.limit + 5
|
||||
|
||||
|
||||
$scope.displayUpdating = ->
|
||||
StatusMessage.display 'progress', t("saving")
|
||||
|
||||
|
||||
$scope.displaySuccess = ->
|
||||
StatusMessage.display 'success',t("products_changes_saved")
|
||||
$scope.bulk_product_form.$setPristine()
|
||||
|
||||
|
||||
$scope.displayFailure = (failMessage) ->
|
||||
StatusMessage.display 'failure', t("products_update_error_msg") + "#{failMessage}"
|
||||
|
||||
|
||||
$scope.displayDirtyProducts = ->
|
||||
count = DirtyProducts.count()
|
||||
switch count
|
||||
when 0 then StatusMessage.clear()
|
||||
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
|
||||
else StatusMessage.display 'notice', t("products_unsaved", n: count)
|
||||
|
||||
|
||||
filterSubmitProducts = (productsToFilter) ->
|
||||
|
||||
@@ -23,7 +23,7 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor,
|
||||
|
||||
$scope.fetchResults = (page=1) ->
|
||||
$scope.resetSelected()
|
||||
Orders.index({
|
||||
params = {
|
||||
'q[completed_at_lt]': $scope['q']['completed_at_lt'],
|
||||
'q[completed_at_gt]': $scope['q']['completed_at_gt'],
|
||||
'q[state_eq]': $scope['q']['state_eq'],
|
||||
@@ -39,7 +39,8 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor,
|
||||
'q[s]': $scope.sorting || 'completed_at desc',
|
||||
per_page: $scope.per_page,
|
||||
page: page
|
||||
})
|
||||
}
|
||||
RequestMonitor.load(Orders.index(params).$promise)
|
||||
|
||||
$scope.resetSelected = ->
|
||||
$scope.selected_orders.length = 0
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
angular.module("admin.products").directive "setOnDemand", ->
|
||||
link: (scope, element, attr) ->
|
||||
onHand = element.context.querySelector("#variant_on_hand")
|
||||
onDemand = element.context.querySelector("#variant_on_demand")
|
||||
|
||||
disableOnHandIfOnDemand = ->
|
||||
if onDemand.checked
|
||||
onHand.disabled = 'disabled'
|
||||
onHand.dataStock = onHand.value
|
||||
onHand.value = t('admin.products.variants.infinity')
|
||||
|
||||
disableOnHandIfOnDemand()
|
||||
|
||||
onDemand.addEventListener 'change', (event) ->
|
||||
disableOnHandIfOnDemand()
|
||||
|
||||
if !onDemand.checked
|
||||
onHand.removeAttribute('disabled')
|
||||
onHand.value = onHand.dataStock
|
||||
@@ -0,0 +1,6 @@
|
||||
angular.module("admin.resources").factory 'ProductResource', ($resource) ->
|
||||
$resource('/admin/product/:id/:action.json', {}, {
|
||||
'index':
|
||||
url: '/api/products/bulk_products.json'
|
||||
method: 'GET'
|
||||
})
|
||||
@@ -1,15 +1,13 @@
|
||||
angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher, $http) ->
|
||||
angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetcher, $http) ->
|
||||
new class BulkProducts
|
||||
products: []
|
||||
pagination: {}
|
||||
|
||||
fetch: (filters, onComplete) ->
|
||||
queryString = filters.reduce (qs,f) ->
|
||||
return qs + "q[#{f.property.db_column}_#{f.predicate.predicate}]=#{f.value};"
|
||||
, ""
|
||||
|
||||
url = "/api/products/bulk_products?page=::page::;per_page=20;#{queryString}"
|
||||
processData = (data) => @addProducts data.products
|
||||
PagedFetcher.fetch url, processData, onComplete
|
||||
fetch: (params) ->
|
||||
ProductResource.index params, (data) =>
|
||||
@products.length = 0
|
||||
@addProducts data.products
|
||||
angular.extend(@pagination, data.pagination)
|
||||
|
||||
cloneProduct: (product) ->
|
||||
$http.post("/api/products/" + product.id + "/clone").success (data) =>
|
||||
|
||||
@@ -27,8 +27,6 @@ angular.module("admin.utils").directive "variantAutocomplete", ($timeout) ->
|
||||
window.variants = data # this is how spree auto complete JS code picks up variants
|
||||
results: data
|
||||
formatResult: (variant) ->
|
||||
if variant["images"][0] != undefined && variant["images"][0].image != undefined
|
||||
variant.image = variant.images[0].image.mini_url
|
||||
variantTemplate variant: variant
|
||||
formatSelection: (variant) ->
|
||||
element.parent().children(".options_placeholder").html variant.options_text
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
Darkswarm.directive "cart", ->
|
||||
# Toggles visibility of the "cart" popover
|
||||
restrict: 'A'
|
||||
link: (scope, elem, attr)->
|
||||
scope.open = false
|
||||
elem.bind 'click', ->
|
||||
scope.$apply ->
|
||||
scope.open = !scope.open
|
||||
@@ -0,0 +1,19 @@
|
||||
Darkswarm.directive "cartToggle", ($document) ->
|
||||
# Toggles visibility of the "cart" popover
|
||||
restrict: 'A'
|
||||
link: (scope, elem, attr)->
|
||||
scope.open = false
|
||||
|
||||
$document.bind 'click', (event) ->
|
||||
cart_button = elem[0]
|
||||
element_and_parents = [event.target, event.target.parentElement, event.target.parentElement.parentElement]
|
||||
cart_button_clicked = (element_and_parents.indexOf(cart_button) != -1)
|
||||
|
||||
if cart_button_clicked
|
||||
scope.$apply ->
|
||||
scope.open = !scope.open
|
||||
else
|
||||
scope.$apply ->
|
||||
scope.open = false
|
||||
|
||||
return
|
||||
@@ -1,10 +1,15 @@
|
||||
@API_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:SS Z"
|
||||
|
||||
Darkswarm.filter "date_in_words", ->
|
||||
(date) ->
|
||||
moment(date).fromNow()
|
||||
(date, dateFormat) ->
|
||||
dateFormat ?= @API_DATETIME_FORMAT
|
||||
moment(date, dateFormat).fromNow()
|
||||
|
||||
Darkswarm.filter "sensible_timeframe", (date_in_wordsFilter)->
|
||||
(date) ->
|
||||
if moment().add(2, 'days') < moment(date)
|
||||
(date, dateFormat) ->
|
||||
dateFormat ?= @API_DATETIME_FORMAT
|
||||
|
||||
if moment().add(2, 'days') < moment(date, dateFormat)
|
||||
t 'orders_open'
|
||||
else
|
||||
t('closing') + date_in_wordsFilter(date)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.add-line-item {
|
||||
fieldset {
|
||||
.vertical-align-top {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding-top: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
.select2-container {
|
||||
.select2-choice {
|
||||
.select2-search-choice-close {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
.select2-arrow {
|
||||
width: 22px;
|
||||
|
||||
@@ -35,5 +35,6 @@ $med-grey: #666;
|
||||
$med-drk-grey: #444;
|
||||
$dark-grey: #333;
|
||||
$light-grey: #ddd;
|
||||
$light-grey-transparency: rgba(0, 0, 0, .1);
|
||||
$black: #000;
|
||||
$white: #fff;
|
||||
|
||||
@@ -42,7 +42,7 @@ nav.top-bar {
|
||||
}
|
||||
|
||||
.top-bar-section {
|
||||
border-bottom: 1px solid $ofn-grey;
|
||||
border-bottom: 1px solid $light-grey-transparency;
|
||||
|
||||
a.icon {
|
||||
&:hover {
|
||||
@@ -170,9 +170,10 @@ nav.top-bar {
|
||||
|
||||
.tab-bar {
|
||||
background-color: white;
|
||||
border-bottom: 1px solid $light-grey-transparency;
|
||||
height: 2.8em;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 2.8em;
|
||||
z-index: 1;
|
||||
|
||||
.cart-span {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@import "mixins";
|
||||
@import "branding";
|
||||
@import "compass/css3/user-interface";
|
||||
@import 'variables';
|
||||
|
||||
.cart {
|
||||
@include user-select(none);
|
||||
@@ -15,8 +16,8 @@
|
||||
|
||||
.joyride-tip-guide {
|
||||
display: block;
|
||||
right: 10px;
|
||||
top: 55px;
|
||||
right: 0;
|
||||
top: $topbar-height;
|
||||
width: 480px;
|
||||
|
||||
@media screen and (min-width: 641px) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require 'open_food_network/permissions'
|
||||
require 'open_food_network/proxy_order_syncer'
|
||||
|
||||
module Admin
|
||||
class SubscriptionsController < ResourceController
|
||||
@@ -32,21 +33,11 @@ module Admin
|
||||
end
|
||||
|
||||
def create
|
||||
form = SubscriptionForm.new(@subscription, params[:subscription])
|
||||
if form.save
|
||||
render_as_json @subscription
|
||||
else
|
||||
render json: { errors: form.json_errors }, status: :unprocessable_entity
|
||||
end
|
||||
save_form_and_render(false)
|
||||
end
|
||||
|
||||
def update
|
||||
form = SubscriptionForm.new(@subscription, params[:subscription])
|
||||
if form.save
|
||||
render_as_json @subscription, order_update_issues: form.order_update_issues
|
||||
else
|
||||
render json: { errors: form.json_errors }, status: :unprocessable_entity
|
||||
end
|
||||
save_form_and_render
|
||||
end
|
||||
|
||||
def cancel
|
||||
@@ -67,12 +58,26 @@ module Admin
|
||||
end
|
||||
|
||||
def unpause
|
||||
@subscription.update_attributes(paused_at: nil)
|
||||
render_as_json @subscription
|
||||
params[:subscription][:paused_at] = nil
|
||||
save_form_and_render
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def save_form_and_render(render_issues = true)
|
||||
form = SubscriptionForm.new(@subscription, params[:subscription])
|
||||
unless form.save
|
||||
render json: { errors: form.json_errors }, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
if render_issues
|
||||
render_as_json @subscription, order_update_issues: form.order_update_issues
|
||||
else
|
||||
render_as_json @subscription
|
||||
end
|
||||
end
|
||||
|
||||
def permissions
|
||||
return @permissions unless @permissions.nil?
|
||||
@permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||
|
||||
@@ -9,8 +9,8 @@ module Admin
|
||||
def map_by_tag
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
serialiser = ActiveModel::ArraySerializer.new(collection)
|
||||
render json: serialiser.to_json
|
||||
serializer = ActiveModel::ArraySerializer.new(collection)
|
||||
render json: serializer.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Api
|
||||
class ProductImagesController < Spree::Api::BaseController
|
||||
class ProductImagesController < BaseController
|
||||
respond_to :json
|
||||
|
||||
def update_product_image
|
||||
@@ -8,11 +8,11 @@ module Api
|
||||
|
||||
if @product.images.first.nil?
|
||||
@image = Spree::Image.create(attachment: params[:file], viewable_id: @product.master.id, viewable_type: 'Spree::Variant')
|
||||
respond_with(@image, status: 201)
|
||||
render json: @image, serializer: ImageSerializer, status: :created
|
||||
else
|
||||
@image = @product.images.first
|
||||
@image.update_attributes(attachment: params[:file])
|
||||
respond_with(@image, status: 200)
|
||||
render json: @image, serializer: ImageSerializer, status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
143
app/controllers/api/products_controller.rb
Normal file
143
app/controllers/api/products_controller.rb
Normal file
@@ -0,0 +1,143 @@
|
||||
require 'open_food_network/permissions'
|
||||
|
||||
module Api
|
||||
class ProductsController < Api::BaseController
|
||||
respond_to :json
|
||||
DEFAULT_PAGE = 1
|
||||
DEFAULT_PER_PAGE = 15
|
||||
|
||||
skip_authorization_check only: [:show, :bulk_products, :overridable]
|
||||
|
||||
def show
|
||||
@product = find_product(params[:id])
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
authorize! :create, Spree::Product
|
||||
params[:product][:available_on] ||= Time.zone.now
|
||||
@product = Spree::Product.new(params[:product])
|
||||
begin
|
||||
if @product.save
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: 201
|
||||
else
|
||||
invalid_resource!(@product)
|
||||
end
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
@product.permalink = nil
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize! :update, Spree::Product
|
||||
@product = find_product(params[:id])
|
||||
if @product.update_attributes(params[:product])
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: 200
|
||||
else
|
||||
invalid_resource!(@product)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize! :delete, Spree::Product
|
||||
@product = find_product(params[:id])
|
||||
@product.update_attribute(:deleted_at, Time.zone.now)
|
||||
@product.variants_including_master.update_all(deleted_at: Time.zone.now)
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: 204
|
||||
end
|
||||
|
||||
# TODO: This should be named 'managed'. Is the action above used? Maybe we should remove it.
|
||||
def bulk_products
|
||||
product_query = OpenFoodNetwork::Permissions.new(current_api_user).
|
||||
editable_products.merge(product_scope)
|
||||
|
||||
if params[:import_date].present?
|
||||
product_query = product_query.imported_on(params[:import_date]).group_by_products_id
|
||||
end
|
||||
|
||||
@products = product_query.order('created_at DESC').
|
||||
ransack(params[:q]).result.
|
||||
page(params[:page] || DEFAULT_PAGE).per(params[:per_page] || DEFAULT_PER_PAGE)
|
||||
|
||||
render_paged_products @products
|
||||
end
|
||||
|
||||
def overridable
|
||||
producers = OpenFoodNetwork::Permissions.new(current_api_user).
|
||||
variant_override_producers.by_name
|
||||
|
||||
@products = paged_products_for_producers producers
|
||||
|
||||
render_paged_products @products
|
||||
end
|
||||
|
||||
def soft_delete
|
||||
authorize! :delete, Spree::Product
|
||||
@product = find_product(params[:product_id])
|
||||
authorize! :delete, @product
|
||||
@product.destroy
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: 204
|
||||
end
|
||||
|
||||
# POST /api/products/:product_id/clone
|
||||
#
|
||||
def clone
|
||||
authorize! :create, Spree::Product
|
||||
original_product = find_product(params[:product_id])
|
||||
authorize! :update, original_product
|
||||
|
||||
@product = original_product.duplicate
|
||||
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: 201
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Copied and modified from SpreeApi::BaseController to allow
|
||||
# enterprise users to access inactive products
|
||||
def product_scope
|
||||
# This line modified
|
||||
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present?
|
||||
scope = Spree::Product
|
||||
if params[:show_deleted]
|
||||
scope = scope.with_deleted
|
||||
end
|
||||
else
|
||||
scope = Spree::Product.active
|
||||
end
|
||||
|
||||
scope.includes(:master)
|
||||
end
|
||||
|
||||
def paged_products_for_producers(producers)
|
||||
Spree::Product.scoped.
|
||||
merge(product_scope).
|
||||
where(supplier_id: producers).
|
||||
by_producer.by_name.
|
||||
ransack(params[:q]).result.
|
||||
page(params[:page]).per(params[:per_page])
|
||||
end
|
||||
|
||||
def render_paged_products(products)
|
||||
serializer = ActiveModel::ArraySerializer.new(
|
||||
products,
|
||||
each_serializer: ::Api::Admin::ProductSerializer
|
||||
)
|
||||
|
||||
render text: {
|
||||
products: serializer,
|
||||
pagination: pagination_data(products)
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def pagination_data(results)
|
||||
{
|
||||
results: results.total_count,
|
||||
pages: results.num_pages,
|
||||
page: (params[:page] || DEFAULT_PAGE).to_i,
|
||||
per_page: (params[:per_page] || DEFAULT_PER_PAGE).to_i
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
79
app/controllers/api/variants_controller.rb
Normal file
79
app/controllers/api/variants_controller.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
module Api
|
||||
class VariantsController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check only: [:index, :show]
|
||||
before_filter :product
|
||||
|
||||
def index
|
||||
@variants = scope.includes(:option_values).ransack(params[:q]).result
|
||||
render json: @variants, each_serializer: Api::VariantSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
@variant = scope.includes(:option_values).find(params[:id])
|
||||
render json: @variant, serializer: Api::VariantSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
authorize! :create, Spree::Variant
|
||||
@variant = scope.new(params[:variant])
|
||||
if @variant.save
|
||||
render json: @variant, serializer: Api::VariantSerializer, status: 201
|
||||
else
|
||||
invalid_resource!(@variant)
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize! :update, Spree::Variant
|
||||
@variant = scope.find(params[:id])
|
||||
if @variant.update_attributes(params[:variant])
|
||||
render json: @variant, serializer: Api::VariantSerializer, status: 200
|
||||
else
|
||||
invalid_resource!(@product)
|
||||
end
|
||||
end
|
||||
|
||||
def soft_delete
|
||||
@variant = scope.find(params[:variant_id])
|
||||
authorize! :delete, @variant
|
||||
|
||||
VariantDeleter.new.delete(@variant)
|
||||
render json: @variant, serializer: Api::VariantSerializer, status: 204
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize! :delete, Spree::Variant
|
||||
@variant = scope.find(params[:id])
|
||||
@variant.destroy
|
||||
render json: @variant, serializer: Api::VariantSerializer, status: 204
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def product
|
||||
@product ||= Spree::Product.find_by_permalink(params[:product_id]) if params[:product_id]
|
||||
end
|
||||
|
||||
def scope
|
||||
if @product
|
||||
unless current_api_user.has_spree_role?("admin") || params[:show_deleted]
|
||||
variants = @product.variants_including_master
|
||||
else
|
||||
variants = @product.variants_including_master.with_deleted
|
||||
end
|
||||
else
|
||||
variants = Spree::Variant.scoped
|
||||
if current_api_user.has_spree_role?("admin")
|
||||
unless params[:show_deleted]
|
||||
variants = Spree::Variant.active
|
||||
end
|
||||
else
|
||||
variants = variants.active
|
||||
end
|
||||
end
|
||||
variants
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -27,12 +27,12 @@ class ShopController < BaseController
|
||||
if oc = OrderCycle.with_distributor(@distributor).active.find_by_id(params[:order_cycle_id])
|
||||
current_order(true).set_order_cycle! oc
|
||||
@current_order_cycle = oc
|
||||
render partial: "json/order_cycle"
|
||||
render json: @current_order_cycle, serializer: Api::OrderCycleSerializer
|
||||
else
|
||||
render status: :not_found, json: ""
|
||||
end
|
||||
else
|
||||
render partial: "json/order_cycle"
|
||||
render json: current_order_cycle, serializer: Api::OrderCycleSerializer
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ Spree::Admin::BaseController.class_eval do
|
||||
|
||||
def active_distributors_not_ready_for_checkout
|
||||
ocs = OrderCycle.managed_by(spree_current_user).active
|
||||
distributors = ocs.map(&:distributors).flatten.uniq
|
||||
distributors = ocs.includes(:distributors).map(&:distributors).flatten.uniq
|
||||
Enterprise.where('enterprises.id IN (?)', distributors).not_ready_for_checkout
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require 'open_food_network/spree_api_key_loader'
|
||||
require 'open_food_network/referer_parser'
|
||||
require 'open_food_network/permissions'
|
||||
|
||||
Spree::Admin::ProductsController.class_eval do
|
||||
include OpenFoodNetwork::SpreeApiKeyLoader
|
||||
@@ -48,19 +49,13 @@ Spree::Admin::ProductsController.class_eval do
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
collection_hash = Hash[params[:products].each_with_index.map { |p, i| [i, p] }]
|
||||
product_set = Spree::ProductSet.new(collection_attributes: collection_hash)
|
||||
|
||||
params[:filters] ||= {}
|
||||
bulk_index_query = params[:filters].reduce("") do |string, filter|
|
||||
"#{string}q[#{filter[:property][:db_column]}_#{filter[:predicate][:predicate]}]=#{filter[:value]};"
|
||||
end
|
||||
product_set = product_set_from_params(params)
|
||||
|
||||
# Ensure we're authorised to update all products
|
||||
product_set.collection.each { |p| authorize! :update, p }
|
||||
|
||||
if product_set.save
|
||||
redirect_to "/api/products/bulk_products?page=1;per_page=500;#{bulk_index_query}"
|
||||
redirect_to main_app.bulk_products_api_products_path( bulk_index_query(params) )
|
||||
else
|
||||
if product_set.errors.present?
|
||||
render json: { errors: product_set.errors }, status: :bad_request
|
||||
@@ -73,7 +68,8 @@ Spree::Admin::ProductsController.class_eval do
|
||||
protected
|
||||
|
||||
def collection
|
||||
# This method is copied directly from the spree product controller, except where we narrow the search below with the managed_by search to support
|
||||
# This method is copied directly from the spree product controller
|
||||
# except where we narrow the search below with the managed_by search to support
|
||||
# enterprise users.
|
||||
# TODO: There has to be a better way!!!
|
||||
return @collection if @collection.present?
|
||||
@@ -108,14 +104,32 @@ Spree::Admin::ProductsController.class_eval do
|
||||
|
||||
private
|
||||
|
||||
def product_set_from_params(params)
|
||||
collection_hash = Hash[params[:products].each_with_index.map { |p, i| [i, p] }]
|
||||
Spree::ProductSet.new(collection_attributes: collection_hash)
|
||||
end
|
||||
|
||||
def bulk_index_query(params)
|
||||
params[:filters].to_h.merge(page: params[:page], per_page: params[:per_page])
|
||||
end
|
||||
|
||||
def load_form_data
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).managed_product_enterprises.is_primary_producer.by_name
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
managed_product_enterprises.is_primary_producer.by_name
|
||||
@taxons = Spree::Taxon.order(:name)
|
||||
@import_dates = product_import_dates.uniq.to_json
|
||||
end
|
||||
|
||||
def product_import_dates
|
||||
import_dates = Spree::Variant.
|
||||
options = [{ id: '0', name: '' }]
|
||||
product_import_dates_query.collect(&:import_date).
|
||||
map { |i| options.push(id: i.to_date, name: i.to_date.to_formatted_s(:long)) }
|
||||
|
||||
options
|
||||
end
|
||||
|
||||
def product_import_dates_query
|
||||
Spree::Variant.
|
||||
select('DISTINCT spree_variants.import_date').
|
||||
joins(:product).
|
||||
where('spree_products.supplier_id IN (?)', editable_enterprises.collect(&:id)).
|
||||
@@ -123,18 +137,14 @@ Spree::Admin::ProductsController.class_eval do
|
||||
where(spree_variants: { is_master: false }).
|
||||
where(spree_variants: { deleted_at: nil }).
|
||||
order('spree_variants.import_date DESC')
|
||||
|
||||
options = [{ id: '0', name: '' }]
|
||||
import_dates.collect(&:import_date).map { |i| options.push(id: i.to_date, name: i.to_date.to_formatted_s(:long)) }
|
||||
|
||||
options
|
||||
end
|
||||
|
||||
def strip_new_properties
|
||||
unless spree_current_user.admin? || params[:product][:product_properties_attributes].nil?
|
||||
names = Spree::Property.pluck(:name)
|
||||
params[:product][:product_properties_attributes].each do |key, property|
|
||||
params[:product][:product_properties_attributes].delete key unless names.include? property[:property_name]
|
||||
return if spree_current_user.admin? || params[:product][:product_properties_attributes].nil?
|
||||
names = Spree::Property.pluck(:name)
|
||||
params[:product][:product_properties_attributes].each do |key, property|
|
||||
unless names.include? property[:property_name]
|
||||
params[:product][:product_properties_attributes].delete key
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -149,12 +159,32 @@ Spree::Admin::ProductsController.class_eval do
|
||||
end
|
||||
|
||||
def set_stock_levels(product, on_hand, on_demand)
|
||||
variant = product.master
|
||||
if product.variants.any?
|
||||
variant = product.variants.first
|
||||
variant = product_variant(product)
|
||||
|
||||
begin
|
||||
variant.on_demand = on_demand if on_demand.present?
|
||||
variant.on_hand = on_hand.to_i if on_hand.present?
|
||||
rescue StandardError => error
|
||||
notify_bugsnag(error, product, variant)
|
||||
raise error
|
||||
end
|
||||
end
|
||||
|
||||
def notify_bugsnag(error, product, variant)
|
||||
Bugsnag.notify(error) do |report|
|
||||
report.add_tab(:product, product.attributes)
|
||||
report.add_tab(:product_error, product.errors.first) unless product.valid?
|
||||
report.add_tab(:variant, variant.attributes)
|
||||
report.add_tab(:variant_error, variant.errors.first) unless variant.valid?
|
||||
end
|
||||
end
|
||||
|
||||
def product_variant(product)
|
||||
if product.variants.any?
|
||||
product.variants.first
|
||||
else
|
||||
product.master
|
||||
end
|
||||
variant.on_demand = on_demand if on_demand.present?
|
||||
variant.on_hand = on_hand.to_i if on_hand.present?
|
||||
end
|
||||
|
||||
def set_product_master_variant_price_to_zero
|
||||
|
||||
@@ -18,6 +18,7 @@ Spree::Admin::VariantsController.class_eval do
|
||||
def search
|
||||
scoper = OpenFoodNetwork::ScopeVariantsForSearch.new(params)
|
||||
@variants = scoper.search
|
||||
render json: @variants, each_serializer: Api::Admin::VariantSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
||||
130
app/controllers/spree/api/base_controller.rb
Normal file
130
app/controllers/spree/api/base_controller.rb
Normal file
@@ -0,0 +1,130 @@
|
||||
require_dependency 'spree/api/controller_setup'
|
||||
|
||||
module Spree
|
||||
module Api
|
||||
class BaseController < ActionController::Metal
|
||||
include Spree::Api::ControllerSetup
|
||||
include Spree::Core::ControllerHelpers::SSL
|
||||
include ::ActionController::Head
|
||||
|
||||
self.responder = Spree::Api::Responders::AppResponder
|
||||
|
||||
respond_to :json
|
||||
|
||||
attr_accessor :current_api_user
|
||||
|
||||
before_filter :set_content_type
|
||||
before_filter :check_for_user_or_api_key, :if => :requires_authentication?
|
||||
before_filter :authenticate_user
|
||||
after_filter :set_jsonp_format
|
||||
|
||||
rescue_from Exception, :with => :error_during_processing
|
||||
rescue_from CanCan::AccessDenied, :with => :unauthorized
|
||||
rescue_from ActiveRecord::RecordNotFound, :with => :not_found
|
||||
|
||||
helper Spree::Api::ApiHelpers
|
||||
|
||||
ssl_allowed
|
||||
|
||||
def set_jsonp_format
|
||||
if params[:callback] && request.get?
|
||||
self.response_body = "#{params[:callback]}(#{response_body})"
|
||||
headers["Content-Type"] = 'application/javascript'
|
||||
end
|
||||
end
|
||||
|
||||
def map_nested_attributes_keys(klass, attributes)
|
||||
nested_keys = klass.nested_attributes_options.keys
|
||||
attributes.inject({}) do |h, (k, v)|
|
||||
key = nested_keys.include?(k.to_sym) ? "#{k}_attributes" : k
|
||||
h[key] = v
|
||||
h
|
||||
end.with_indifferent_access
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_content_type
|
||||
content_type = case params[:format]
|
||||
when "json"
|
||||
"application/json"
|
||||
when "xml"
|
||||
"text/xml"
|
||||
end
|
||||
headers["Content-Type"] = content_type
|
||||
end
|
||||
|
||||
def check_for_user_or_api_key
|
||||
# User is already authenticated with Spree, make request this way instead.
|
||||
return true if @current_api_user = try_spree_current_user ||
|
||||
!requires_authentication?
|
||||
|
||||
return if api_key.present?
|
||||
render("spree/api/errors/must_specify_api_key", status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def authenticate_user
|
||||
return if @current_api_user
|
||||
|
||||
if requires_authentication? || api_key.present?
|
||||
unless @current_api_user = Spree.user_class.find_by_spree_api_key(api_key.to_s)
|
||||
render("spree/api/errors/invalid_api_key", status: :unauthorized) && return
|
||||
end
|
||||
else
|
||||
# An anonymous user
|
||||
@current_api_user = Spree.user_class.new
|
||||
end
|
||||
end
|
||||
|
||||
def unauthorized
|
||||
render("spree/api/errors/unauthorized", status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def error_during_processing(exception)
|
||||
render(text: { exception: exception.message }.to_json,
|
||||
status: :unprocessable_entity) && return
|
||||
end
|
||||
|
||||
def requires_authentication?
|
||||
true
|
||||
end
|
||||
|
||||
def not_found
|
||||
render("spree/api/errors/not_found", status: :not_found) && return
|
||||
end
|
||||
|
||||
def current_ability
|
||||
Spree::Ability.new(current_api_user)
|
||||
end
|
||||
|
||||
def invalid_resource!(resource)
|
||||
@resource = resource
|
||||
render "spree/api/errors/invalid_resource", status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def api_key
|
||||
request.headers["X-Spree-Token"] || params[:token]
|
||||
end
|
||||
helper_method :api_key
|
||||
|
||||
def find_product(id)
|
||||
product_scope.find_by_permalink!(id.to_s)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
product_scope.find(id)
|
||||
end
|
||||
|
||||
def product_scope
|
||||
if current_api_user.has_spree_role?("admin")
|
||||
scope = Product
|
||||
if params[:show_deleted]
|
||||
scope = scope.with_deleted
|
||||
end
|
||||
else
|
||||
scope = Product.active
|
||||
end
|
||||
|
||||
scope.includes(:master)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
Spree::Api::LineItemsController.class_eval do
|
||||
around_filter :apply_enterprise_fees_with_lock, only: :update
|
||||
|
||||
private
|
||||
|
||||
def apply_enterprise_fees_with_lock
|
||||
authorize! :read, order
|
||||
order.with_lock do
|
||||
yield
|
||||
order.update_distribution_charge!
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,86 +0,0 @@
|
||||
require 'open_food_network/permissions'
|
||||
|
||||
Spree::Api::ProductsController.class_eval do
|
||||
def managed
|
||||
authorize! :admin, Spree::Product
|
||||
authorize! :read, Spree::Product
|
||||
|
||||
@products = product_scope.ransack(params[:q]).result.managed_by(current_api_user).page(params[:page]).per(params[:per_page])
|
||||
respond_with(@products, default_template: :index)
|
||||
end
|
||||
|
||||
# TODO: This should be named 'managed'. Is the action above used? Maybe we should remove it.
|
||||
def bulk_products
|
||||
@products = OpenFoodNetwork::Permissions.new(current_api_user).editable_products.
|
||||
merge(product_scope).
|
||||
order('created_at DESC').
|
||||
ransack(params[:q]).result.
|
||||
page(params[:page]).per(params[:per_page])
|
||||
|
||||
render_paged_products @products
|
||||
end
|
||||
|
||||
def overridable
|
||||
producers = OpenFoodNetwork::Permissions.new(current_api_user).
|
||||
variant_override_producers.by_name
|
||||
|
||||
@products = paged_products_for_producers producers
|
||||
|
||||
render_paged_products @products
|
||||
end
|
||||
|
||||
def soft_delete
|
||||
authorize! :delete, Spree::Product
|
||||
@product = find_product(params[:product_id])
|
||||
authorize! :delete, @product
|
||||
@product.destroy
|
||||
respond_with(@product, status: 204)
|
||||
end
|
||||
|
||||
# POST /api/products/:product_id/clone
|
||||
#
|
||||
def clone
|
||||
authorize! :create, Spree::Product
|
||||
original_product = find_product(params[:product_id])
|
||||
authorize! :update, original_product
|
||||
|
||||
@product = original_product.duplicate
|
||||
|
||||
respond_with(@product, status: 201, default_template: :show)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Copied and modified from Spree::Api::BaseController to allow
|
||||
# enterprise users to access inactive products
|
||||
def product_scope
|
||||
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present? # This line modified
|
||||
scope = Spree::Product
|
||||
if params[:show_deleted]
|
||||
scope = scope.with_deleted
|
||||
end
|
||||
else
|
||||
scope = Spree::Product.active
|
||||
end
|
||||
|
||||
scope.includes(:master)
|
||||
end
|
||||
|
||||
def paged_products_for_producers(producers)
|
||||
Spree::Product.scoped.
|
||||
merge(product_scope).
|
||||
where(supplier_id: producers).
|
||||
by_producer.by_name.
|
||||
ransack(params[:q]).result.
|
||||
page(params[:page]).per(params[:per_page])
|
||||
end
|
||||
|
||||
def render_paged_products(products)
|
||||
serializer = ActiveModel::ArraySerializer.new(
|
||||
products,
|
||||
each_serializer: Api::Admin::ProductSerializer
|
||||
)
|
||||
|
||||
render text: { products: serializer, pages: products.num_pages }.to_json
|
||||
end
|
||||
end
|
||||
108
app/controllers/spree/api/shipments_controller.rb
Normal file
108
app/controllers/spree/api/shipments_controller.rb
Normal file
@@ -0,0 +1,108 @@
|
||||
require 'open_food_network/scope_variant_to_hub'
|
||||
|
||||
module Spree
|
||||
module Api
|
||||
class ShipmentsController < Spree::Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
before_filter :find_order
|
||||
before_filter :find_and_update_shipment, only: [:ship, :ready, :add, :remove]
|
||||
|
||||
def create
|
||||
variant = scoped_variant(params[:variant_id])
|
||||
quantity = params[:quantity].to_i
|
||||
@shipment = get_or_create_shipment(params[:stock_location_id])
|
||||
|
||||
@order.contents.add(variant, quantity, nil, @shipment)
|
||||
|
||||
@shipment.refresh_rates
|
||||
@shipment.save!
|
||||
|
||||
respond_with(@shipment.reload, default_template: :show)
|
||||
end
|
||||
|
||||
def update
|
||||
authorize! :read, Shipment
|
||||
@shipment = @order.shipments.find_by_number!(params[:id])
|
||||
params[:shipment] ||= []
|
||||
unlock = params[:shipment].delete(:unlock)
|
||||
|
||||
if unlock == 'yes'
|
||||
@shipment.adjustment.open
|
||||
end
|
||||
|
||||
@shipment.update_attributes(params[:shipment])
|
||||
|
||||
if unlock == 'yes'
|
||||
@shipment.adjustment.close
|
||||
end
|
||||
|
||||
@shipment.reload
|
||||
respond_with(@shipment, default_template: :show)
|
||||
end
|
||||
|
||||
def ready
|
||||
authorize! :read, Shipment
|
||||
unless @shipment.ready?
|
||||
if @shipment.can_ready?
|
||||
@shipment.ready!
|
||||
else
|
||||
render "spree/api/shipments/cannot_ready_shipment", status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
end
|
||||
respond_with(@shipment, default_template: :show)
|
||||
end
|
||||
|
||||
def ship
|
||||
authorize! :read, Shipment
|
||||
unless @shipment.shipped?
|
||||
@shipment.ship!
|
||||
end
|
||||
respond_with(@shipment, default_template: :show)
|
||||
end
|
||||
|
||||
def add
|
||||
variant = scoped_variant(params[:variant_id])
|
||||
quantity = params[:quantity].to_i
|
||||
|
||||
@order.contents.add(variant, quantity, nil, @shipment)
|
||||
|
||||
respond_with(@shipment, default_template: :show)
|
||||
end
|
||||
|
||||
def remove
|
||||
variant = scoped_variant(params[:variant_id])
|
||||
quantity = params[:quantity].to_i
|
||||
|
||||
@order.contents.remove(variant, quantity, @shipment)
|
||||
@shipment.reload if @shipment.persisted?
|
||||
|
||||
respond_with(@shipment, default_template: :show)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_order
|
||||
@order = Spree::Order.find_by_number!(params[:order_id])
|
||||
authorize! :read, @order
|
||||
end
|
||||
|
||||
def find_and_update_shipment
|
||||
@shipment = @order.shipments.find_by_number!(params[:id])
|
||||
@shipment.update_attributes(params[:shipment])
|
||||
@shipment.reload
|
||||
end
|
||||
|
||||
def scoped_variant(variant_id)
|
||||
variant = Spree::Variant.find(variant_id)
|
||||
OpenFoodNetwork::ScopeVariantToHub.new(@order.distributor).scope(variant)
|
||||
variant
|
||||
end
|
||||
|
||||
def get_or_create_shipment(stock_location_id)
|
||||
@order.shipment || @order.shipments.create(stock_location_id: stock_location_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,47 +0,0 @@
|
||||
require 'open_food_network/scope_variant_to_hub'
|
||||
|
||||
Spree::Api::ShipmentsController.class_eval do
|
||||
def create
|
||||
variant = scoped_variant(params[:variant_id])
|
||||
quantity = params[:quantity].to_i
|
||||
@shipment = get_or_create_shipment(params[:stock_location_id])
|
||||
|
||||
@order.contents.add(variant, quantity, nil, @shipment)
|
||||
|
||||
@shipment.refresh_rates
|
||||
@shipment.save!
|
||||
|
||||
respond_with(@shipment.reload, default_template: :show)
|
||||
end
|
||||
|
||||
def add
|
||||
variant = scoped_variant(params[:variant_id])
|
||||
quantity = params[:quantity].to_i
|
||||
|
||||
@order.contents.add(variant, quantity, nil, @shipment)
|
||||
|
||||
respond_with(@shipment, default_template: :show)
|
||||
end
|
||||
|
||||
def remove
|
||||
variant = scoped_variant(params[:variant_id])
|
||||
quantity = params[:quantity].to_i
|
||||
|
||||
@order.contents.remove(variant, quantity, @shipment)
|
||||
@shipment.reload if @shipment.persisted?
|
||||
|
||||
respond_with(@shipment, default_template: :show)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scoped_variant(variant_id)
|
||||
variant = Spree::Variant.find(variant_id)
|
||||
OpenFoodNetwork::ScopeVariantToHub.new(@order.distributor).scope(variant)
|
||||
variant
|
||||
end
|
||||
|
||||
def get_or_create_shipment(stock_location_id)
|
||||
@order.shipment || @order.shipments.create(stock_location_id: stock_location_id)
|
||||
end
|
||||
end
|
||||
75
app/controllers/spree/api/taxons_controller.rb
Normal file
75
app/controllers/spree/api/taxons_controller.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
module Spree
|
||||
module Api
|
||||
class TaxonsController < Spree::Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
if taxonomy
|
||||
@taxons = taxonomy.root.children
|
||||
else
|
||||
if params[:ids]
|
||||
@taxons = Taxon.where(id: params[:ids].split(","))
|
||||
else
|
||||
@taxons = Taxon.ransack(params[:q]).result
|
||||
end
|
||||
end
|
||||
respond_with(@taxons)
|
||||
end
|
||||
|
||||
def show
|
||||
@taxon = taxon
|
||||
respond_with(@taxon)
|
||||
end
|
||||
|
||||
def jstree
|
||||
show
|
||||
end
|
||||
|
||||
def create
|
||||
authorize! :create, Taxon
|
||||
@taxon = Taxon.new(params[:taxon])
|
||||
@taxon.taxonomy_id = params[:taxonomy_id]
|
||||
taxonomy = Taxonomy.find_by_id(params[:taxonomy_id])
|
||||
|
||||
if taxonomy.nil?
|
||||
@taxon.errors[:taxonomy_id] = I18n.t(:invalid_taxonomy_id, scope: 'spree.api')
|
||||
invalid_resource!(@taxon) && return
|
||||
end
|
||||
|
||||
@taxon.parent_id = taxonomy.root.id unless params[:taxon][:parent_id]
|
||||
|
||||
if @taxon.save
|
||||
respond_with(@taxon, status: 201, default_template: :show)
|
||||
else
|
||||
invalid_resource!(@taxon)
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize! :update, Taxon
|
||||
if taxon.update_attributes(params[:taxon])
|
||||
respond_with(taxon, status: 200, default_template: :show)
|
||||
else
|
||||
invalid_resource!(taxon)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize! :delete, Taxon
|
||||
taxon.destroy
|
||||
respond_with(taxon, status: 204)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def taxonomy
|
||||
return if params[:taxonomy_id].blank?
|
||||
@taxonomy ||= Taxonomy.find(params[:taxonomy_id])
|
||||
end
|
||||
|
||||
def taxon
|
||||
@taxon ||= taxonomy.taxons.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
Spree::Api::VariantsController.class_eval do
|
||||
def soft_delete
|
||||
@variant = scope.find(params[:variant_id])
|
||||
authorize! :delete, @variant
|
||||
|
||||
VariantDeleter.new.delete(@variant)
|
||||
respond_with @variant, status: 204
|
||||
end
|
||||
end
|
||||
@@ -29,7 +29,7 @@ module Spree
|
||||
|
||||
def load_order
|
||||
@order = current_order
|
||||
redirect_to main_app.cart_path && return unless @order
|
||||
redirect_to(main_app.cart_path) && return unless @order
|
||||
|
||||
if params[:state]
|
||||
redirect_to checkout_state_path(@order.state) if @order.can_go_to_state?(params[:state])
|
||||
|
||||
@@ -61,6 +61,12 @@ module InjectionHelper
|
||||
inject_json_ams "currentOrder", current_order, Api::CurrentOrderSerializer, current_distributor: current_distributor, current_order_cycle: current_order_cycle
|
||||
end
|
||||
|
||||
def inject_current_order_cycle
|
||||
serializer = Api::OrderCycleSerializer.new(current_order_cycle)
|
||||
json = serializer.object.present? ? serializer.to_json : "{}"
|
||||
render partial: "json/injection_ams", locals: { name: "orderCycleData", json: json }
|
||||
end
|
||||
|
||||
def inject_available_shipping_methods
|
||||
inject_json_ams "shippingMethods", available_shipping_methods,
|
||||
Api::ShippingMethodSerializer, current_order: current_order
|
||||
|
||||
@@ -18,6 +18,7 @@ module Spree
|
||||
klass = Spree::Order if klass == :bulk_order_management
|
||||
klass = EnterpriseGroup if klass == :group
|
||||
klass = VariantOverride if klass == :Inventory
|
||||
klass = ProductImport::ProductImporter if klass == :import
|
||||
klass = Spree::Admin::ReportsController if klass == :report
|
||||
klass
|
||||
end
|
||||
|
||||
120
app/helpers/spree/api/api_helpers.rb
Normal file
120
app/helpers/spree/api/api_helpers.rb
Normal file
@@ -0,0 +1,120 @@
|
||||
module Spree
|
||||
module Api
|
||||
module ApiHelpers
|
||||
def required_fields_for(model)
|
||||
required_fields = model._validators.select do |_field, validations|
|
||||
validations.any? { |v| v.is_a?(ActiveModel::Validations::PresenceValidator) }
|
||||
end.map(&:first) # get fields that are invalid
|
||||
# Permalinks presence is validated, but are really automatically generated
|
||||
# Therefore we shouldn't tell API clients that they MUST send one through
|
||||
required_fields.map!(&:to_s).delete("permalink")
|
||||
required_fields
|
||||
end
|
||||
|
||||
def product_attributes
|
||||
[:id, :name, :description, :price, :available_on, :permalink, :meta_description,
|
||||
:meta_keywords, :shipping_category_id, :taxon_ids]
|
||||
end
|
||||
|
||||
def product_property_attributes
|
||||
[:id, :product_id, :property_id, :value, :property_name]
|
||||
end
|
||||
|
||||
def variant_attributes
|
||||
[:id, :name, :sku, :price, :weight, :height, :width, :depth,
|
||||
:is_master, :cost_price, :permalink]
|
||||
end
|
||||
|
||||
def image_attributes
|
||||
[:id, :position, :attachment_content_type, :attachment_file_name,
|
||||
:type, :attachment_updated_at, :attachment_width, :attachment_height, :alt]
|
||||
end
|
||||
|
||||
def option_value_attributes
|
||||
[:id, :name, :presentation, :option_type_name, :option_type_id]
|
||||
end
|
||||
|
||||
def order_attributes
|
||||
[:id, :number, :item_total, :total, :state, :adjustment_total, :user_id,
|
||||
:created_at, :updated_at, :completed_at, :payment_total,
|
||||
:shipment_state, :payment_state, :email, :special_instructions, :token]
|
||||
end
|
||||
|
||||
def line_item_attributes
|
||||
[:id, :quantity, :price, :variant_id]
|
||||
end
|
||||
|
||||
def option_type_attributes
|
||||
[:id, :name, :presentation, :position]
|
||||
end
|
||||
|
||||
def payment_attributes
|
||||
[:id, :source_type, :source_id, :amount, :payment_method_id,
|
||||
:response_code, :state, :avs_response, :created_at, :updated_at]
|
||||
end
|
||||
|
||||
def payment_method_attributes
|
||||
[:id, :name, :description]
|
||||
end
|
||||
|
||||
def shipment_attributes
|
||||
[:id, :tracking, :number, :cost, :shipped_at, :state]
|
||||
end
|
||||
|
||||
def taxonomy_attributes
|
||||
[:id, :name]
|
||||
end
|
||||
|
||||
def taxon_attributes
|
||||
[:id, :name, :pretty_name, :permalink, :position, :parent_id, :taxonomy_id]
|
||||
end
|
||||
|
||||
def inventory_unit_attributes
|
||||
[:id, :lock_version, :state, :variant_id, :shipment_id, :return_authorization_id]
|
||||
end
|
||||
|
||||
def return_authorization_attributes
|
||||
[:id, :number, :state, :amount, :order_id, :reason, :created_at, :updated_at]
|
||||
end
|
||||
|
||||
def country_attributes
|
||||
[:id, :iso_name, :iso, :iso3, :name, :numcode]
|
||||
end
|
||||
|
||||
def state_attributes
|
||||
[:id, :name, :abbr, :country_id]
|
||||
end
|
||||
|
||||
def adjustment_attributes
|
||||
[:id, :source_type, :source_id, :adjustable_type, :adjustable_id, :originator_type,
|
||||
:originator_id, :amount, :label, :mandatory, :locked, :eligible, :created_at, :updated_at]
|
||||
end
|
||||
|
||||
def creditcard_attributes
|
||||
[:id, :month, :year, :cc_type, :last_digits, :first_name, :last_name,
|
||||
:gateway_customer_profile_id, :gateway_payment_profile_id]
|
||||
end
|
||||
|
||||
def user_attributes
|
||||
[:id, :email, :created_at, :updated_at]
|
||||
end
|
||||
|
||||
def property_attributes
|
||||
[:id, :name, :presentation]
|
||||
end
|
||||
|
||||
def stock_location_attributes
|
||||
[:id, :name, :address1, :address2, :city,
|
||||
:state_id, :state_name, :country_id, :zipcode, :phone, :active]
|
||||
end
|
||||
|
||||
def stock_movement_attributes
|
||||
[:id, :quantity, :stock_item_id]
|
||||
end
|
||||
|
||||
def stock_item_attributes
|
||||
[:id, :count_on_hand, :backorderable, :lock_version, :stock_location_id, :variant_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -59,7 +59,7 @@ class SubscriptionPlacementJob
|
||||
end
|
||||
|
||||
def move_to_completion(order)
|
||||
until order.completed? do order.next! end
|
||||
AdvanceOrderService.new(order).call!
|
||||
end
|
||||
|
||||
def unavailable_stock_lines_for(order)
|
||||
|
||||
@@ -90,7 +90,6 @@ class Enterprise < ActiveRecord::Base
|
||||
validates :permalink, uniqueness: true, presence: true
|
||||
validate :shopfront_taxons
|
||||
validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? }
|
||||
validates :description, length: { maximum: 255 }
|
||||
|
||||
before_validation :initialize_permalink, if: lambda { permalink.nil? }
|
||||
before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? && !owner_id.nil? }
|
||||
@@ -464,9 +463,11 @@ class Enterprise < ActiveRecord::Base
|
||||
self.permalink = Enterprise.find_available_permalink(name)
|
||||
end
|
||||
|
||||
# Touch distributors without them touching their distributors.
|
||||
# We avoid an infinite loop and don't need to touch the whole distributor tree.
|
||||
def touch_distributors
|
||||
Enterprise.distributing_products(supplied_products.select(:id)).
|
||||
where('enterprises.id != ?', id).
|
||||
find_each(&:touch)
|
||||
update_all(updated_at: Time.zone.now)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Tells whether a particular feature is enabled or not
|
||||
class FeatureFlags
|
||||
# Constructor
|
||||
#
|
||||
# @param user [User]
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
# Checks whether product import is enabled for the specified user
|
||||
#
|
||||
# @return [Boolean]
|
||||
def product_import_enabled?
|
||||
user.superadmin?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :user
|
||||
end
|
||||
@@ -4,6 +4,12 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def check_price
|
||||
if currency.nil?
|
||||
self.currency = Spree::Config[:currency]
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
variant.andand.refresh_products_cache
|
||||
end
|
||||
|
||||
@@ -56,6 +56,12 @@ Spree::Product.class_eval do
|
||||
ON (o_order_cycles.id = o_exchanges.order_cycle_id)")
|
||||
}
|
||||
|
||||
scope :imported_on, lambda { |import_date|
|
||||
import_date = Time.zone.parse import_date if import_date.is_a? String
|
||||
import_date = import_date.to_date
|
||||
joins(:variants).merge(Spree::Variant.where(import_date: import_date.beginning_of_day..import_date.end_of_day))
|
||||
}
|
||||
|
||||
scope :with_order_cycles_inner, -> {
|
||||
joins(variants_including_master: { exchanges: :order_cycle })
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ class Spree::ProductSet < ModelSet
|
||||
# variant.update_attributes( { price: xx.x } )
|
||||
#
|
||||
def update_attributes(attributes)
|
||||
if attributes[:taxon_ids].present?
|
||||
attributes[:taxon_ids] = attributes[:taxon_ids].split(',')
|
||||
end
|
||||
split_taxon_ids!(attributes)
|
||||
|
||||
found_model = @collection.find do |model|
|
||||
model.id.to_s == attributes[:id].to_s && model.persisted?
|
||||
@@ -28,12 +26,20 @@ class Spree::ProductSet < ModelSet
|
||||
if found_model.nil?
|
||||
@klass.new(attributes).save unless @reject_if.andand.call(attributes)
|
||||
else
|
||||
update_product_only_attributes(found_model, attributes) &&
|
||||
update_product_variants(found_model, attributes) &&
|
||||
update_product_master(found_model, attributes)
|
||||
update_product(found_model, attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def split_taxon_ids!(attributes)
|
||||
attributes[:taxon_ids] = attributes[:taxon_ids].split(',') if attributes[:taxon_ids].present?
|
||||
end
|
||||
|
||||
def update_product(found_model, attributes)
|
||||
update_product_only_attributes(found_model, attributes) &&
|
||||
update_product_variants(found_model, attributes) &&
|
||||
update_product_master(found_model, attributes)
|
||||
end
|
||||
|
||||
def update_product_only_attributes(product, attributes)
|
||||
variant_related_attrs = [:id, :variants_attributes, :master_attributes]
|
||||
product_related_attrs = attributes.except(*variant_related_attrs)
|
||||
@@ -90,8 +96,23 @@ class Spree::ProductSet < ModelSet
|
||||
|
||||
variant = product.variants.create(variant_attributes)
|
||||
|
||||
variant.on_demand = on_demand if on_demand.present?
|
||||
variant.on_hand = on_hand.to_i if on_hand.present?
|
||||
begin
|
||||
variant.on_demand = on_demand if on_demand.present?
|
||||
variant.on_hand = on_hand.to_i if on_hand.present?
|
||||
rescue StandardError => error
|
||||
notify_bugsnag(error, product, variant, variant_attributes)
|
||||
raise error
|
||||
end
|
||||
end
|
||||
|
||||
def notify_bugsnag(error, product, variant, variant_attributes)
|
||||
Bugsnag.notify(error) do |report|
|
||||
report.add_tab(:product, product.attributes)
|
||||
report.add_tab(:product_error, product.errors.first) unless product.valid?
|
||||
report.add_tab(:variant_attributes, variant_attributes)
|
||||
report.add_tab(:variant, variant.attributes)
|
||||
report.add_tab(:variant_error, variant.errors.first) unless variant.valid?
|
||||
end
|
||||
end
|
||||
|
||||
def collection_attributes=(attributes)
|
||||
|
||||
10
app/models/spree/stock_movement_decorator.rb
Normal file
10
app/models/spree/stock_movement_decorator.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
Spree::StockMovement.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
return if stock_item.variant.blank?
|
||||
OpenFoodNetwork::ProductsCache.variant_changed stock_item.variant
|
||||
end
|
||||
end
|
||||
@@ -136,6 +136,16 @@ module Spree
|
||||
has_spree_role?('admin')
|
||||
end
|
||||
|
||||
def generate_spree_api_key!
|
||||
self.spree_api_key = SecureRandom.hex(24)
|
||||
save!
|
||||
end
|
||||
|
||||
def clear_spree_api_key!
|
||||
self.spree_api_key = nil
|
||||
save!
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def password_required?
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
/replace 'code[erb-loud]:contains("form_for @payment")'
|
||||
= form_for @payment, :url => admin_order_payments_path(@order), :html => {"ng-app" => "admin.payments", "ng-controller" => "PaymentCtrl"} do |f|
|
||||
@@ -1,3 +0,0 @@
|
||||
/ insert_before "fieldset.no-border-top"
|
||||
|
||||
%save-bar{ persist: "true" }
|
||||
@@ -1,2 +0,0 @@
|
||||
/replace 'code[erb-loud]:contains("button @order.cart?")'
|
||||
= button_tag t(:update), type: 'button', "ng-click" => "submitPayment()"
|
||||
@@ -1,6 +0,0 @@
|
||||
/ replace_contents "td.property_name"
|
||||
|
||||
- if spree_current_user.admin?
|
||||
= f.text_field :property_name, :class => 'autocomplete'
|
||||
- else
|
||||
= f.select :property_name, @properties, { :include_blank => true }, { class: 'select2 fullwidth' }
|
||||
@@ -1,26 +0,0 @@
|
||||
/ insert_after 'table.index.sortable'
|
||||
|
||||
=f.check_box :inherits_properties
|
||||
=f.label :inherits_properties, t(".inherits_properties_checkbox_hint", supplier: @product.supplier.name)
|
||||
%br
|
||||
%br
|
||||
|
||||
#inherited_properties
|
||||
%table.index
|
||||
%thead
|
||||
%tr{"data-hook" => "producer_properties_header"}
|
||||
%th= t('admin.products.properties.inherited_property')
|
||||
%th= t('admin.description')
|
||||
%th.actions
|
||||
%tbody#producer_properties{"data-hook" => ""}
|
||||
- @product.supplier.producer_properties.each do |producer_property|
|
||||
%tr
|
||||
%td= producer_property.property.presentation
|
||||
%td= producer_property.value
|
||||
%td.actions
|
||||
|
||||
:coffee
|
||||
$(document).ready ->
|
||||
$("#inherited_properties").toggle $("input#product_inherits_properties").is(':checked')
|
||||
$("input#product_inherits_properties").change ->
|
||||
$("#inherited_properties").toggle $(this).is(':checked')
|
||||
@@ -1,5 +0,0 @@
|
||||
/ replace "tr[data-hook='product_properties_header']"
|
||||
%tr{"data-hook" => "product_properties_header"}
|
||||
%th= t('admin.products.properties.property_name')
|
||||
%th= t('admin.description')
|
||||
%th.actions
|
||||
@@ -1,2 +0,0 @@
|
||||
add_to_attributes '[data-hook="admin_variant_form_fields"]'
|
||||
attributes 'ng-app' => 'admin.products'
|
||||
@@ -1,25 +0,0 @@
|
||||
/ insert_top "[data-hook='admin_variant_form_fields']"
|
||||
|
||||
.field
|
||||
= f.label :display_name, t(:display_name)
|
||||
= f.text_field :display_name, class: "fullwidth"
|
||||
.field
|
||||
= f.label :display_as, t(:display_as)
|
||||
= f.text_field :display_as, class: "fullwidth"
|
||||
|
||||
- if product_has_variant_unit_option_type?(@product)
|
||||
- if @product.variant_unit != 'items'
|
||||
.field{"data-hook" => "unit_value", 'ng-controller' => 'variantUnitsCtrl'}
|
||||
= f.label :unit_value, "#{t('admin.'+@product.variant_unit)} ({{unitName(#{@product.variant_unit_scale}, '#{@product.variant_unit}')}})"
|
||||
= hidden_field_tag 'product_variant_unit_scale', @product.variant_unit_scale
|
||||
= text_field_tag :unit_value_human, nil, {class: "fullwidth", 'ng-model' => 'unit_value_human', 'ng-change' => 'updateValue()'}
|
||||
= f.text_field :unit_value, {hidden: true, 'ng-value' => 'unit_value'}
|
||||
|
||||
.field{"data-hook" => "unit_description"}
|
||||
= f.label :unit_description, t(:spree_admin_unit_description)
|
||||
= f.text_field :unit_description, class: "fullwidth", placeholder: t('admin.products.unit_name_placeholder')
|
||||
|
||||
:javascript
|
||||
angular.element(document.getElementById("new_variant")).ready(function() {
|
||||
angular.bootstrap(document.getElementById("new_variant"), ['admin.products']);
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
/ insert_bottom "[data-hook='admin_variant_form_fields']"
|
||||
|
||||
- if Spree::Config[:track_inventory_levels]
|
||||
.field.checkbox
|
||||
%label
|
||||
= f.check_box :on_demand
|
||||
= t(:on_demand)
|
||||
.field
|
||||
= f.label :on_hand, t(:on_hand)
|
||||
.fullwidth
|
||||
= f.text_field :on_hand
|
||||
@@ -1,10 +0,0 @@
|
||||
/ replace "[data-hook='presentation']"
|
||||
|
||||
- unless variant_unit_option_type?(option_type)
|
||||
.field{"data-hook" => "presentation"}
|
||||
= label :new_variant, option_type.presentation
|
||||
- if @variant.new_record?
|
||||
= select(:new_variant, option_type.presentation, option_type.option_values.collect {|ov| [ ov.presentation, ov.id ] }, {}, {:class => 'select2 fullwidth'})
|
||||
- else
|
||||
- if opt = @variant.option_values.detect {|o| o.option_type == option_type }.try(:presentation)
|
||||
= text_field(:new_variant, option_type.presentation, :value => opt, :disabled => 'disabled', :class => 'fullwidth')
|
||||
@@ -1,33 +0,0 @@
|
||||
/ insert_bottom "[data-hook='admin_variant_form_fields']"
|
||||
:javascript
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
var on_demand = $('input#variant_on_demand');
|
||||
var on_hand = $('input#variant_on_hand');
|
||||
|
||||
disableOnHandIfOnDemand = function() {
|
||||
on_demand_checked = on_demand.attr('checked')
|
||||
if ( on_demand_checked == undefined )
|
||||
on_demand_checked = false;
|
||||
|
||||
on_hand.attr('disabled', on_demand_checked);
|
||||
if(on_demand_checked) {
|
||||
on_hand.attr('data-stock', on_hand.val());
|
||||
on_hand.val(t('admin.products.variants.infinity'));
|
||||
}
|
||||
}
|
||||
|
||||
disableOnHandIfOnDemand();
|
||||
|
||||
on_demand.change(function(){
|
||||
disableOnHandIfOnDemand();
|
||||
if(!this.checked) {
|
||||
if(on_hand.attr('data-stock') !== undefined) {
|
||||
on_hand.val(on_hand.attr('data-stock'));
|
||||
} else {
|
||||
on_hand.val("0");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
/ insert_bottom "[data-hook='on_demand']"
|
||||
%div{'ofn-with-tip' => t('admin.products.variants.to_order_tip')}
|
||||
%a= t('admin.whats_this')
|
||||
@@ -1,14 +0,0 @@
|
||||
/ replace "[data-hook='admin_variant_form_additional_fields']"
|
||||
|
||||
.right.six.columns.omega.label-block{"data-hook" => "admin_variant_form_additional_fields"}
|
||||
- if @product.variant_unit != 'weight'
|
||||
.field{"data-hook" => 'weight'}
|
||||
= f.label 'weight', t('weight')+' (kg)'
|
||||
- value = number_with_precision(@variant.weight, :precision => 2)
|
||||
= f.text_field 'weight', :value => value, :class => 'fullwidth'
|
||||
|
||||
- [:height, :width, :depth].each do |field|
|
||||
.field{"data-hook" => field}
|
||||
= f.label field, t(field)
|
||||
- value = number_with_precision(@variant.send(field), :precision => 2)
|
||||
= f.text_field field, :value => value, :class => 'fullwidth'
|
||||
@@ -1,3 +0,0 @@
|
||||
/ replace "code[erb-loud]:contains('variant.options_text')"
|
||||
|
||||
= variant.full_name
|
||||
@@ -1,15 +1,49 @@
|
||||
class Api::Admin::VariantSerializer < ActiveModel::Serializer
|
||||
attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display, :sku
|
||||
attributes :on_hand, :price, :import_date
|
||||
attributes :id, :name, :producer_name, :image, :sku, :import_date
|
||||
attributes :options_text, :unit_value, :unit_description, :unit_to_display
|
||||
attributes :display_as, :display_name, :name_to_display
|
||||
attributes :price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name
|
||||
|
||||
has_many :variant_overrides
|
||||
|
||||
def name
|
||||
if object.full_name.present?
|
||||
"#{object.name} - #{object.full_name}"
|
||||
else
|
||||
object.name
|
||||
end
|
||||
end
|
||||
|
||||
def on_hand
|
||||
return 0 if object.on_hand.nil?
|
||||
object.on_hand
|
||||
end
|
||||
|
||||
def price
|
||||
# Decimals are passed to json as strings, we need to run parseFloat.toFixed(2) on the client side.
|
||||
# Decimals are passed to json as strings, we need to run parseFloat.toFixed(2) on the client.
|
||||
object.price.nil? ? 0.to_f : object.price
|
||||
end
|
||||
|
||||
def producer_name
|
||||
object.product.supplier.name
|
||||
end
|
||||
|
||||
def image
|
||||
return if object.product.images.empty?
|
||||
object.product.images.first.mini_url
|
||||
end
|
||||
|
||||
def in_stock
|
||||
object.in_stock?
|
||||
end
|
||||
|
||||
def stock_location_id
|
||||
return if object.stock_items.empty?
|
||||
object.stock_items.first.stock_location.id
|
||||
end
|
||||
|
||||
def stock_location_name
|
||||
return if object.stock_items.empty?
|
||||
object.stock_items.first.stock_location.name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
class Api::ImageSerializer < ActiveModel::Serializer
|
||||
attributes :id, :alt, :small_url, :large_url
|
||||
attributes :id, :alt, :thumb_url, :small_url, :image_url, :large_url
|
||||
|
||||
def thumb_url
|
||||
object.attachment.url(:mini, false)
|
||||
end
|
||||
|
||||
def small_url
|
||||
object.attachment.url(:small, false)
|
||||
end
|
||||
|
||||
def image_url
|
||||
object.attachment.url(:product, false)
|
||||
end
|
||||
|
||||
def large_url
|
||||
object.attachment.url(:large, false)
|
||||
end
|
||||
|
||||
13
app/serializers/api/order_cycle_serializer.rb
Normal file
13
app/serializers/api/order_cycle_serializer.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Api
|
||||
class OrderCycleSerializer < ActiveModel::Serializer
|
||||
attributes :order_cycle_id, :orders_close_at
|
||||
|
||||
def order_cycle_id
|
||||
object.id
|
||||
end
|
||||
|
||||
def orders_close_at
|
||||
object.orders_close_at.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,8 @@
|
||||
class Api::VariantSerializer < ActiveModel::Serializer
|
||||
attributes :id, :is_master, :on_hand, :name_to_display, :unit_to_display, :unit_value
|
||||
attributes :options_text, :on_demand, :price, :fees, :price_with_fees, :product_name
|
||||
attributes :id, :is_master, :product_name, :sku
|
||||
attributes :options_text, :unit_value, :unit_description, :unit_to_display
|
||||
attributes :display_as, :display_name, :name_to_display
|
||||
attributes :price, :on_demand, :on_hand, :fees, :price_with_fees
|
||||
attributes :tag_list
|
||||
|
||||
delegate :price, to: :object
|
||||
|
||||
@@ -22,20 +22,36 @@ class LineItemSyncer
|
||||
def update_item_quantities(order)
|
||||
changed_subscription_line_items.each do |sli|
|
||||
line_item = order.line_items.find_by_variant_id(sli.variant_id)
|
||||
next if update_quantity(line_item, sli)
|
||||
product_name = "#{line_item.product.name} - #{line_item.full_name}"
|
||||
order_update_issues.add(order, product_name)
|
||||
|
||||
if line_item.blank?
|
||||
order_update_issues.add(order, sli.variant.product_and_full_name)
|
||||
next
|
||||
end
|
||||
|
||||
unless update_quantity(line_item, sli)
|
||||
add_order_update_issue(order, line_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_new_items(order)
|
||||
new_subscription_line_items.each do |sli|
|
||||
order.line_items.create(variant_id: sli.variant_id, quantity: sli.quantity, skip_stock_check: true)
|
||||
new_line_item = order.line_items.create(variant_id: sli.variant_id,
|
||||
quantity: sli.quantity,
|
||||
skip_stock_check: skip_stock_check?(order))
|
||||
next if skip_stock_check?(order) || new_line_item.sufficient_stock?
|
||||
|
||||
order.line_items.delete(new_line_item)
|
||||
add_order_update_issue(order, new_line_item)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_obsolete_items(order)
|
||||
order.line_items.where(variant_id: subscription_line_items.select(&:marked_for_destruction?).map(&:variant_id)).destroy_all
|
||||
order.line_items.
|
||||
where(variant_id: subscription_line_items.
|
||||
select(&:marked_for_destruction?).
|
||||
map(&:variant_id)).
|
||||
destroy_all
|
||||
end
|
||||
|
||||
def changed_subscription_line_items
|
||||
@@ -48,8 +64,27 @@ class LineItemSyncer
|
||||
|
||||
def update_quantity(line_item, sli)
|
||||
if line_item.quantity == sli.quantity_was
|
||||
return line_item.update_attributes(quantity: sli.quantity, skip_stock_check: true)
|
||||
return line_item.update_attributes(quantity: sli.quantity,
|
||||
skip_stock_check: skip_stock_check?(line_item.order))
|
||||
end
|
||||
line_item.quantity == sli.quantity
|
||||
end
|
||||
|
||||
def skip_stock_check?(order)
|
||||
!order.complete?
|
||||
end
|
||||
|
||||
def add_order_update_issue(order, line_item)
|
||||
issue_description = "#{line_item.product.name} - #{line_item.variant.full_name}"
|
||||
issue_description << " - #{stock_issue_description(line_item)}" if line_item.insufficient_stock?
|
||||
order_update_issues.add(order, issue_description)
|
||||
end
|
||||
|
||||
def stock_issue_description(line_item)
|
||||
if line_item.variant.in_stock?
|
||||
I18n.t("admin.subscriptions.stock.insufficient_stock")
|
||||
else
|
||||
I18n.t("admin.subscriptions.stock.out_of_stock")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Responsible for ensuring that any updates to a Subscription are propagated to any
|
||||
# orders belonging to that Subscription which have been instantiated
|
||||
|
||||
class OrderSyncer
|
||||
attr_reader :order_update_issues
|
||||
|
||||
@@ -11,8 +10,9 @@ class OrderSyncer
|
||||
end
|
||||
|
||||
def sync!
|
||||
future_and_undated_orders.all? do |order|
|
||||
order.assign_attributes(customer_id: customer_id, email: customer.andand.email, distributor_id: shop_id)
|
||||
orders_in_order_cycles_not_closed.all? do |order|
|
||||
order.assign_attributes(customer_id: customer_id, email: customer.andand.email,
|
||||
distributor_id: shop_id)
|
||||
update_associations_for(order)
|
||||
line_item_syncer.sync!(order)
|
||||
order.save
|
||||
@@ -25,7 +25,8 @@ class OrderSyncer
|
||||
|
||||
delegate :orders, :bill_address, :ship_address, :subscription_line_items, to: :subscription
|
||||
delegate :shop_id, :customer, :customer_id, to: :subscription
|
||||
delegate :shipping_method, :shipping_method_id, :payment_method, :payment_method_id, to: :subscription
|
||||
delegate :shipping_method, :shipping_method_id,
|
||||
:payment_method, :payment_method_id, to: :subscription
|
||||
delegate :shipping_method_id_changed?, :shipping_method_id_was, to: :subscription
|
||||
delegate :payment_method_id_changed?, :payment_method_id_was, to: :subscription
|
||||
|
||||
@@ -36,9 +37,10 @@ class OrderSyncer
|
||||
update_payment_for(order) if payment_method_id_changed?
|
||||
end
|
||||
|
||||
def future_and_undated_orders
|
||||
return @future_and_undated_orders unless @future_and_undated_orders.nil?
|
||||
@future_and_undated_orders = orders.joins(:order_cycle).merge(OrderCycle.not_closed).readonly(false)
|
||||
def orders_in_order_cycles_not_closed
|
||||
return @orders_in_order_cycles_not_closed unless @orders_in_order_cycles_not_closed.nil?
|
||||
@orders_in_order_cycles_not_closed = orders.joins(:order_cycle).
|
||||
merge(OrderCycle.not_closed).readonly(false)
|
||||
end
|
||||
|
||||
def update_bill_address_for(order)
|
||||
@@ -49,7 +51,8 @@ class OrderSyncer
|
||||
end
|
||||
|
||||
def update_payment_for(order)
|
||||
payment = order.payments.with_state('checkout').where(payment_method_id: payment_method_id_was).last
|
||||
payment = order.payments.
|
||||
with_state('checkout').where(payment_method_id: payment_method_id_was).last
|
||||
if payment
|
||||
payment.andand.void_transaction!
|
||||
order.payments.create(payment_method_id: payment_method_id, amount: order.reload.total)
|
||||
|
||||
@@ -5,4 +5,11 @@
|
||||
|
||||
= render 'upload_sidebar'
|
||||
|
||||
%h5
|
||||
= t('.notice')
|
||||
%br
|
||||
%p
|
||||
= t('.beta_notice')
|
||||
%br
|
||||
|
||||
= render 'upload_form'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#add-line-item
|
||||
#add-line-item.add-line-item
|
||||
%fieldset
|
||||
%legend{ align: 'center'}= t(:products)
|
||||
%table.no-borders.layout
|
||||
@@ -6,15 +6,15 @@
|
||||
%col{ width: '20%' }
|
||||
%col{ width: '20%' }
|
||||
%tr
|
||||
%td{ style: "vertical-align:top" }
|
||||
%td.vertical-align-top
|
||||
.field
|
||||
= label_tag :add_variant_id, t(:name_or_sku)
|
||||
= label_tag :add_variant_id, t('.name_or_sku')
|
||||
%input#add_variant_id.variant_autocomplete.fullwidth{ type: 'number', ng: { model: 'newItem.variant_id' } }
|
||||
%td{ style: "vertical-align:top" }
|
||||
%td.vertical-align-top
|
||||
.field
|
||||
= label_tag :add_quantity, t(:qty)
|
||||
= label_tag :add_quantity, t('.quantity')
|
||||
%input#add_quantity.fullwidth{ type: 'number', min: 1, ng: { model: 'newItem.quantity' } }
|
||||
%td{ style: "vertical-align:top" }
|
||||
%td
|
||||
.actions
|
||||
%a.icon-plus.button.fullwidth{ href: 'javascript:void(0)', method: :post, ng: { click: 'addSubscriptionLineItem()' } }
|
||||
= t(:add)
|
||||
= t('.add')
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
object @enterprise
|
||||
|
||||
attributes :id, :name
|
||||
@@ -1,5 +0,0 @@
|
||||
object @image
|
||||
attributes(*image_attributes)
|
||||
attributes :viewable_type, :viewable_id
|
||||
node( :thumb_url ) { @product.images.first.attachment.url(:mini) }
|
||||
node( :image_url ) { @product.images.first.attachment.url(:product) }
|
||||
@@ -1,3 +0,0 @@
|
||||
object current_order_cycle
|
||||
attributes :orders_close_at
|
||||
attribute id: :order_cycle_id
|
||||
@@ -1,5 +1,5 @@
|
||||
%span.cart-span{"ng-controller" => "CartCtrl", "ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"}
|
||||
%a#cart.icon{cart: true}
|
||||
%a#cart.icon{"cart-toggle" => true}
|
||||
%span
|
||||
= t '.cart'
|
||||
%span.count
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
- content_for :scripts do
|
||||
:javascript
|
||||
angular.module('Darkswarm').value('orderCycleData', #{render "json/order_cycle"})
|
||||
- content_for :injection_data do
|
||||
= inject_current_order_cycle
|
||||
|
||||
%ordercycle{"ng-controller" => "OrderCycleCtrl"}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.per-page{'ng-show' => '!RequestMonitor.loading && orders.length > 0'}
|
||||
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'ng-model' => 'per_page', 'ng-change' => 'fetchResults()'}
|
||||
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'min-search' => 999, 'ng-model' => 'per_page', 'ng-change' => 'fetchResults()'}
|
||||
|
||||
%span.per-page-feedback
|
||||
{{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<%= admin_inject_json "admin.payments", "currentOrderNumber", @order.number %>
|
||||
<%= admin_inject_json_ams_array "admin.payments", "paymentMethods", @payment_methods, Api::PaymentMethodSerializer, current_order: @order %>
|
||||
|
||||
<div data-hook="admin_payment_form_fields" class="row">
|
||||
<div class="alpha three columns">
|
||||
<div class="field">
|
||||
<%= f.label :amount, t(:amount) %>
|
||||
<%= f.text_field :amount, :value => @order.outstanding_balance, :class => 'fullwidth', "watch-value-as" => 'form_data.amount' %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="omega nine columns">
|
||||
<div class="field">
|
||||
<label><%= t(:payment_method) %></label>
|
||||
<ul>
|
||||
<% @payment_methods.each do |method| %>
|
||||
<li>
|
||||
<label data-hook="payment_method_field">
|
||||
<%= radio_button_tag 'payment[payment_method_id]', method.id, method == @payment_method, { class: "payment_methods_radios", "ng-model" => 'form_data.payment_method' } %>
|
||||
<%= t(method.name, :scope => :payment_methods, :default => method.name) %>
|
||||
</label>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<div class="payment-method-settings">
|
||||
<% @payment_methods.each do |method| %>
|
||||
<div class="payment-methods" id="payment_method_<%= method.id %>">
|
||||
<% if method.source_required? %>
|
||||
<br />
|
||||
<%= render :partial => "spree/admin/payments/source_forms/#{method.method_type}", :locals => { :payment_method => method } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
23
app/views/spree/admin/payments/_form.html.haml
Normal file
23
app/views/spree/admin/payments/_form.html.haml
Normal file
@@ -0,0 +1,23 @@
|
||||
= admin_inject_json "admin.payments", "currentOrderNumber", @order.number
|
||||
= admin_inject_json_ams_array "admin.payments", "paymentMethods", @payment_methods, Api::PaymentMethodSerializer, current_order: @order
|
||||
|
||||
.row
|
||||
.alpha.three.columns
|
||||
.field
|
||||
= f.label :amount, t(:amount)
|
||||
= f.text_field :amount, value: @order.outstanding_balance, class: 'fullwidth', "watch-value-as" => 'form_data.amount'
|
||||
.omega.nine.columns
|
||||
.field
|
||||
%label= t(:payment_method)
|
||||
%ul
|
||||
- @payment_methods.each do |method|
|
||||
%li
|
||||
%label
|
||||
= radio_button_tag 'payment[payment_method_id]', method.id, method == @payment_method, { class: "payment_methods_radios", "ng-model" => 'form_data.payment_method' }
|
||||
= t(method.name, scope: :payment_methods, default: method.name)
|
||||
.payment-method-settings
|
||||
- @payment_methods.each do |method|
|
||||
.payment-methods{id: "payment_method_#{method.id}"}
|
||||
- if method.source_required?
|
||||
%br/
|
||||
= render partial: "spree/admin/payments/source_forms/#{method.method_type}", locals: { payment_method: method }
|
||||
19
app/views/spree/admin/payments/_list.html.haml
Normal file
19
app/views/spree/admin/payments/_list.html.haml
Normal file
@@ -0,0 +1,19 @@
|
||||
%table.index
|
||||
%thead
|
||||
%tr
|
||||
%th= "#{Spree.t('date')}/#{Spree.t('time')}"
|
||||
%th= Spree.t(:amount)
|
||||
%th= Spree.t(:payment_method)
|
||||
%th= Spree.t(:payment_state)
|
||||
%th.actions
|
||||
%tbody
|
||||
- payments.each do |payment|
|
||||
%tr{class: "#{cycle('odd', 'even')}"}
|
||||
%td= pretty_time(payment.created_at)
|
||||
%td.align-center= payment.display_amount.to_html
|
||||
%td.align-center= link_to payment_method_name(payment), spree.admin_order_payment_path(@order, payment)
|
||||
%td.align-center
|
||||
%span{class: "state #{payment.state}"}= Spree.t(payment.state, scope: :payment_states, default: payment.state.capitalize)
|
||||
%td.actions
|
||||
- payment.actions.each do |action|
|
||||
= link_to_with_icon "icon-#{action}", Spree.t(action), fire_admin_order_payment_path(@order, payment, e: action), method: :put, no_text: true, data: {action: action}
|
||||
22
app/views/spree/admin/payments/index.html.haml
Normal file
22
app/views/spree/admin/payments/index.html.haml
Normal file
@@ -0,0 +1,22 @@
|
||||
= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Payments' }
|
||||
|
||||
- content_for :page_actions do
|
||||
- if @order.outstanding_balance?
|
||||
%li#new_payment_section
|
||||
= button_link_to Spree.t(:new_payment), new_admin_order_payment_url(@order), icon: 'icon-plus'
|
||||
%li= button_link_to Spree.t(:back_to_orders_list), admin_orders_path, icon: 'icon-arrow-left'
|
||||
|
||||
- content_for :page_title do
|
||||
%i.icon-arrow-right
|
||||
= Spree.t(:payments)
|
||||
|
||||
- if @order.outstanding_balance?
|
||||
%h5.outstanding-balance
|
||||
= @order.outstanding_balance < 0 ? Spree.t(:credit_owed) : Spree.t(:balance_due)
|
||||
\:
|
||||
%strong= @order.display_outstanding_balance
|
||||
|
||||
- if @payments.any?
|
||||
= render partial: 'list', locals: { payments: @payments }
|
||||
- else
|
||||
.alpha.twelve.columns.no-objects-found= Spree.t(:order_has_no_payments)
|
||||
22
app/views/spree/admin/payments/new.html.haml
Normal file
22
app/views/spree/admin/payments/new.html.haml
Normal file
@@ -0,0 +1,22 @@
|
||||
= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Payments' }
|
||||
|
||||
- content_for :page_title do
|
||||
%i.icon-arrow-right
|
||||
= Spree.t(:new_payment)
|
||||
|
||||
- content_for :page_actions do
|
||||
%li= button_link_to Spree.t(:back_to_payments_list), spree.admin_order_payments_url(@order), icon: 'icon-arrow-left'
|
||||
|
||||
- if @payment_methods.any?
|
||||
= render partial: 'spree/shared/error_messages', locals: { target: @payment }
|
||||
|
||||
= form_for @payment, url: admin_order_payments_path(@order), html: {"ng-app" => "admin.payments", "ng-controller" => "PaymentCtrl"} do |f|
|
||||
%save-bar{ persist: "true" }
|
||||
%fieldset.no-border-top
|
||||
= render partial: 'form', locals: { f: f }
|
||||
.filter-actions.actions
|
||||
= button_tag t(:update), type: 'button', "ng-click" => "submitPayment()"
|
||||
|
||||
- else
|
||||
= Spree.t(:cannot_create_payment_without_payment_methods)
|
||||
= link_to Spree.t(:please_define_payment_methods), admin_payment_methods_url
|
||||
18
app/views/spree/admin/payments/show.html.haml
Normal file
18
app/views/spree/admin/payments/show.html.haml
Normal file
@@ -0,0 +1,18 @@
|
||||
= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Payments' }
|
||||
|
||||
- content_for :page_title do
|
||||
%i.icon-arrow-right
|
||||
= Spree.t("activerecord.models.#{@payment.class.to_s.underscore}.one")
|
||||
%i.icon-arrow-right
|
||||
= payment_method_name(@payment)
|
||||
|
||||
- content_for :page_actions do
|
||||
%li= button_link_to Spree.t(:back_to_payments_list), spree.admin_order_payments_url(@order), icon: 'icon-arrow-left'
|
||||
|
||||
= render partial: "spree/admin/payments/source_views/#{@payment.payment_method.method_type}", locals: { payment: @payment.source.is_a?(Spree::Payment) ? @payment.source : @payment }
|
||||
|
||||
.align-center
|
||||
%h5
|
||||
= label_tag nil, Spree.t(:amount)
|
||||
\:
|
||||
%span.green= @payment.amount
|
||||
@@ -1,58 +0,0 @@
|
||||
<fieldset class="no-border-bottom">
|
||||
<div class="field" data-hook="previous_cards">
|
||||
<% @previous_cards.each do |card| %>
|
||||
<label><%= radio_button_tag :card, card.id, card == @previous_cards.first %> <%= card.display_number %><br /></label>
|
||||
<% end %>
|
||||
<label><%= radio_button_tag :card, 'new', @previous_cards.none?, { class: "card_new" } %> <%= t(:use_new_cc) %></label>
|
||||
</div>
|
||||
|
||||
<div class="card_form" data-hook>
|
||||
<% param_prefix = "payment_source[#{payment_method.id}]" %>
|
||||
|
||||
<div class="clear"></div>
|
||||
|
||||
<div class="alpha four columns">
|
||||
<div data-hook="card_number">
|
||||
<div class="field">
|
||||
<%= hidden_field_tag "#{param_prefix}[cc_type]", '', {class: 'ccType'} %>
|
||||
<%= label_tag "card_number#{payment_method.id}", raw(t(:card_number) + content_tag(:span, ' *', :class => 'required')) %>
|
||||
<%= text_field_tag "#{param_prefix}[number]", '', :class => 'required fullwidth cardNumber', :id => "card_number#{payment_method.id}", :maxlength => 19 %>
|
||||
<span id="card_type" style="display:none;">
|
||||
( <span id="looks_like" ><%= t(:card_type_is) %> <span id="type"></span></span>
|
||||
<span id="unrecognized"><%= t(:unrecognized_card_type) %></span>
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alpha four columns">
|
||||
<div data-hook="card_name">
|
||||
<div class="field">
|
||||
<%= label_tag "card_name#{payment_method.id}", raw(t(:name) + content_tag(:span, ' *', :class => 'required')) %>
|
||||
<%= text_field_tag "#{param_prefix}[name]", '', id: "card_name#{payment_method.id}", class: 'required fullwidth', maxlength: 19 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three columns">
|
||||
<div data-hook="card_expiration" class="field">
|
||||
<%= label_tag "card_expiry#{payment_method.id}", raw(t(:expiration) + content_tag(:span, ' *', :class => 'required')) %><br>
|
||||
<%= text_field_tag "#{param_prefix}[expiry]", '', id: "card_expiry#{payment_method.id}", class: "required cardExpiry", placeholder: "MM / YY" %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="omega two columns">
|
||||
<div data-hook="card_code" class="field">
|
||||
<%= label_tag "card_code#{payment_method.id}", raw(t(:card_code) + content_tag(:span, ' *', :class => "required")) %>
|
||||
<%= text_field_tag "#{param_prefix}[verification_value]", '', id: "card_code#{payment_method.id}", class: 'required fullwidth cardCode', size: 5 %>
|
||||
<a href="/content/cvv" class="info cvvLink" target="_blank">
|
||||
(<%= t(:what_is_this) %>)
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nine columns">
|
||||
<%= image_tag 'credit_cards/credit_card.gif', :class => 'credit-card-image' %>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -0,0 +1,45 @@
|
||||
%fieldset.no-border-bottom
|
||||
.field
|
||||
- @previous_cards.each do |card|
|
||||
%label
|
||||
= radio_button_tag :card, card.id, card == @previous_cards.first
|
||||
= card.display_number
|
||||
%br/
|
||||
%label
|
||||
= radio_button_tag :card, 'new', @previous_cards.none?, { class: "card_new" }
|
||||
= t(:use_new_cc)
|
||||
.card_form
|
||||
- param_prefix = "payment_source[#{payment_method.id}]"
|
||||
.clear
|
||||
.alpha.four.columns
|
||||
%div
|
||||
.field
|
||||
= hidden_field_tag "#{param_prefix}[cc_type]", '', {class: 'ccType'}
|
||||
= label_tag "card_number#{payment_method.id}", raw(t(:card_number) + content_tag(:span, ' *', class: 'required'))
|
||||
= text_field_tag "#{param_prefix}[number]", '', class: 'required fullwidth cardNumber', id: "card_number#{payment_method.id}", maxlength: 19
|
||||
%span#card_type{style: "display:none;"}
|
||||
(
|
||||
%span#looks_like
|
||||
= t(:card_type_is)
|
||||
%span#type
|
||||
%span#unrecognized= t(:unrecognized_card_type)
|
||||
)
|
||||
.alpha.four.columns
|
||||
%div
|
||||
.field
|
||||
= label_tag "card_name#{payment_method.id}", raw(t(:name) + content_tag(:span, ' *', class: 'required'))
|
||||
= text_field_tag "#{param_prefix}[name]", '', id: "card_name#{payment_method.id}", class: 'required fullwidth', maxlength: 19
|
||||
.three.columns
|
||||
.field
|
||||
= label_tag "card_expiry#{payment_method.id}", raw(t(:expiration) + content_tag(:span, ' *', class: 'required'))
|
||||
%br/
|
||||
= text_field_tag "#{param_prefix}[expiry]", '', id: "card_expiry#{payment_method.id}", class: "required cardExpiry", placeholder: "MM / YY"
|
||||
.omega.two.columns
|
||||
.field
|
||||
= label_tag "card_code#{payment_method.id}", raw(t(:card_code) + content_tag(:span, ' *', class: "required"))
|
||||
= text_field_tag "#{param_prefix}[verification_value]", '', id: "card_code#{payment_method.id}", class: 'required fullwidth cardCode', size: 5
|
||||
%a.info.cvvLink{href: "/content/cvv", target: "_blank"}
|
||||
(#{t(:what_is_this)})
|
||||
.nine.columns
|
||||
= image_tag 'credit_cards/credit_card.gif', class: 'credit-card-image'
|
||||
.clear
|
||||
@@ -0,0 +1,33 @@
|
||||
%fieldset
|
||||
%legend{:align => "center"}= Spree.t(:credit_card)
|
||||
.row
|
||||
.alpha.six.columns
|
||||
%dl
|
||||
%dt
|
||||
= Spree.t(:card_number)
|
||||
\:
|
||||
%dd= payment.source.display_number
|
||||
%dt
|
||||
= Spree.t(:expiration)
|
||||
\:
|
||||
%dd
|
||||
= payment.source.month
|
||||
\/
|
||||
= payment.source.year
|
||||
%dt
|
||||
= Spree.t(:card_code)
|
||||
\:
|
||||
%dd= payment.source.verification_value
|
||||
.omega.six.columns
|
||||
%dl
|
||||
%dt
|
||||
= t(:maestro_or_solo_cards)
|
||||
\:
|
||||
%dd= payment.source.issue_number
|
||||
%dt
|
||||
= Spree.t(:start_date)
|
||||
\:
|
||||
%dd
|
||||
= payment.source.start_month
|
||||
\/
|
||||
= payment.source.start_year
|
||||
@@ -0,0 +1,14 @@
|
||||
%tr.product_property.fields{"data-hook" => "product_property", id: "spree_#{dom_id(f.object)}"}
|
||||
%td.no-border
|
||||
%span.handle
|
||||
= f.hidden_field :id
|
||||
%td.property_name
|
||||
- if spree_current_user.admin?
|
||||
= f.text_field :property_name, class: 'autocomplete'
|
||||
- else
|
||||
= f.select :property_name, @properties, { include_blank: true }, { class: 'select2 fullwidth' }
|
||||
%td.value
|
||||
= f.text_field :value, class: 'autocomplete'
|
||||
%td.actions
|
||||
- if f.object.persisted?
|
||||
= link_to_delete f.object, no_text: true
|
||||
71
app/views/spree/admin/product_properties/index.html.haml
Normal file
71
app/views/spree/admin/product_properties/index.html.haml
Normal file
@@ -0,0 +1,71 @@
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
= render partial: 'spree/admin/shared/product_tabs', locals: { current: 'Product Properties' }
|
||||
|
||||
= render partial: 'spree/shared/error_messages', locals: { target: @product }
|
||||
|
||||
- content_for :page_actions do
|
||||
%ul.tollbar.inline-menu
|
||||
%li
|
||||
= link_to_add_fields Spree.t(:add_product_properties), 'tbody#product_properties', class: 'icon-plus button'
|
||||
%li
|
||||
%span#new_ptype_link
|
||||
= link_to Spree.t(:select_from_prototype), available_admin_prototypes_url, remote: true, 'data-update' => 'prototypes', class: 'button icon-copy'
|
||||
|
||||
= form_for @product, url: admin_product_url(@product), method: :put do |f|
|
||||
%fieldset.no-border-top
|
||||
.add_product_properties
|
||||
#prototypes
|
||||
= image_tag 'select2-spinner.gif', plugin: 'spree', style: 'display:none;', id: 'busy_indicator'
|
||||
|
||||
%table.index.sortable{"data-sortable-link" => update_positions_admin_product_product_properties_url}
|
||||
%thead
|
||||
%tr
|
||||
%th.no-border
|
||||
%th= t('admin.products.properties.property_name')
|
||||
%th= t('admin.description')
|
||||
%th.actions
|
||||
|
||||
%tbody#product_properties
|
||||
= f.fields_for :product_properties do |pp_form|
|
||||
= render partial: 'product_property_fields', locals: { f: pp_form }
|
||||
|
||||
= f.check_box :inherits_properties
|
||||
= f.label :inherits_properties, t(".inherits_properties_checkbox_hint", supplier: @product.supplier.name)
|
||||
%br
|
||||
%br
|
||||
|
||||
#inherited_properties
|
||||
%table.index
|
||||
%thead
|
||||
%tr
|
||||
%th= t('admin.products.properties.inherited_property')
|
||||
%th= t('admin.description')
|
||||
%th.actions
|
||||
%tbody#producer_properties
|
||||
- @product.supplier.producer_properties.each do |producer_property|
|
||||
%tr
|
||||
%td= producer_property.property.presentation
|
||||
%td= producer_property.value
|
||||
%td.actions
|
||||
|
||||
|
||||
= render partial: 'spree/admin/shared/edit_resource_links'
|
||||
|
||||
= hidden_field_tag 'clear_product_properties', 'true'
|
||||
|
||||
:coffee
|
||||
$(document).ready ->
|
||||
$("#inherited_properties").toggle $("input#product_inherits_properties").is(':checked')
|
||||
$("input#product_inherits_properties").change ->
|
||||
$("#inherited_properties").toggle $(this).is(':checked')
|
||||
|
||||
:javascript
|
||||
var properties = #{raw(@properties.to_json)};
|
||||
$("#product_properties input.autocomplete").live("keydown", function(){
|
||||
already_auto_completed = $(this).is('ac_input');
|
||||
if (!already_auto_completed) {
|
||||
$(this).autocomplete({source: properties});
|
||||
$(this).focus();
|
||||
}
|
||||
});
|
||||
@@ -4,7 +4,9 @@
|
||||
%div{ ng: { app: 'ofn.admin', controller: 'AdminProductEditCtrl', init: 'initialise()' } }
|
||||
|
||||
= render 'spree/admin/products/index/filters'
|
||||
%hr.divider.sixteen.columns.alpha.omega
|
||||
= render 'spree/admin/products/index/actions'
|
||||
= render 'spree/admin/products/index/indicators'
|
||||
= render 'spree/admin/products/index/products'
|
||||
|
||||
%div{'ng-show' => "!RequestMonitor.loading && products.length > 0" }
|
||||
= render partial: 'admin/shared/angular_pagination'
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
.controls.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0' }
|
||||
.thirteen.columns
|
||||
%columns-dropdown{ action: "#{controller_name}_#{action_name}" }
|
||||
.controls.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' }
|
||||
.ten.columns.alpha.index-controls
|
||||
.per-page{'ng-show' => '!RequestMonitor.loading && products.length > 0'}
|
||||
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'min-search' => 999, 'ng-model' => 'per_page', 'ng-change' => 'fetchProducts()'}
|
||||
|
||||
%span.per-page-feedback
|
||||
{{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }}
|
||||
{{ 'spree.admin.orders.index.viewing' | t:{start: ((pagination.page -1) * pagination.per_page) +1, end: ((pagination.page -1) * pagination.per_page) + products.length} }}
|
||||
|
||||
.six.columns.omega
|
||||
%columns-dropdown{ action: "#{controller_name}_#{action_name}" }
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
.filters.sixteen.columns.alpha.omega
|
||||
.quick_search.three.columns.alpha
|
||||
%label{ for: 'quick_filter' }
|
||||
%br
|
||||
%input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search') }
|
||||
.one.columns
|
||||
.filter_select.three.columns
|
||||
%label{ for: 'producer_filter' }= t 'producer'
|
||||
%br
|
||||
%select.fullwidth{ id: 'producer_filter', 'ofn-select2-min-search' => 5, ng: {model: 'producerFilter', options: 'producer.id as producer.name for producer in filterProducers'} }
|
||||
.filter_select.three.columns
|
||||
%label{ for: 'category_filter' }= t 'category'
|
||||
%br
|
||||
%select.fullwidth{ id: 'category_filter', 'ofn-select2-min-search' => 5, ng: {model: 'categoryFilter', options: 'taxon.id as taxon.name for taxon in filterTaxons'} }
|
||||
- if FeatureFlags.new(@current_user).product_import_enabled?
|
||||
%fieldset
|
||||
%legend{align: 'center'}= t(:search)
|
||||
|
||||
.filters.sixteen.columns.alpha.omega
|
||||
.quick_search.three.columns.alpha
|
||||
%label{ for: 'quick_filter' }
|
||||
%br
|
||||
%input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search') }
|
||||
.one.columns
|
||||
.filter_select.three.columns
|
||||
%label{ for: 'producer_filter' }= t 'producer'
|
||||
%br
|
||||
%select.fullwidth{ id: 'producer_filter', 'ofn-select2-min-search' => 5, ng: {model: 'producerFilter', options: 'producer.id as producer.name for producer in producers'} }
|
||||
.filter_select.three.columns
|
||||
%label{ for: 'category_filter' }= t 'category'
|
||||
%br
|
||||
%select.fullwidth{ id: 'category_filter', 'ofn-select2-min-search' => 5, ng: {model: 'categoryFilter', options: 'taxon.id as taxon.name for taxon in taxons'} }
|
||||
.filter_select.three.columns
|
||||
%label{ for: 'import_filter' } Import Date
|
||||
%br
|
||||
%select.fullwidth{ id: 'import_date_filter', 'ofn-select2-min-search' => 5, ng: {model: 'importDateFilter', init: "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}"}}
|
||||
%option{value: "{{date.id}}", ng: {repeat: "date in importDates track by date.id" }}
|
||||
%option{value: "{{date.id}}", ng: {repeat: "date in importDates" }}
|
||||
{{date.name}}
|
||||
|
||||
.filter_clear.three.columns.omega
|
||||
%label{ for: 'clear_all_filters' }
|
||||
%br
|
||||
%input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" }
|
||||
.filter_clear.three.columns.omega
|
||||
%label{ for: 'clear_all_filters' }
|
||||
%br
|
||||
%input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" }
|
||||
|
||||
.clearfix
|
||||
|
||||
.actions.filter-actions
|
||||
%div
|
||||
%a.button.icon-search{'ng-click' => 'fetchProducts()'}
|
||||
= t(:filter_results)
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
%div{ 'ng-show' => '!spree_api_key_ok' }
|
||||
{{ api_error_msg }}
|
||||
|
||||
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'loading' }
|
||||
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
|
||||
%br
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
%h1= t('.title')
|
||||
|
||||
%div.sixteen.columns.alpha{ 'ng-show' => '!loadingAllPages && filteredProducts.length == 0 && query.length==0' }
|
||||
%div.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && query.length==0' }
|
||||
%h1#no_results= t('.no_products')
|
||||
|
||||
%div.sixteen.columns.alpha{ 'ng-show' => '!loadingAllPages && filteredProducts.length == 0 && query.length!=0' }
|
||||
%div.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && query.length!=0' }
|
||||
%h1#no_results
|
||||
= t('.no_results')
|
||||
'
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || filteredProducts.length == 0' }
|
||||
%div.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' }
|
||||
%form{ name: 'bulk_product_form' }
|
||||
%save-bar{ dirty: "bulk_product_form.$dirty", persist: "false" }
|
||||
%input.red{ type: "button", value: t(:save_changes), ng: { click: "submitProducts()", disabled: "!bulk_product_form.$dirty" } }
|
||||
%input{ type: "button", value: t(:close), 'ng-click' => "cancel('#{admin_products_path}')" }
|
||||
|
||||
%table.index#listing_products.bulk{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1" }
|
||||
%table.index#listing_products.bulk
|
||||
|
||||
= render 'spree/admin/products/index/products_head'
|
||||
|
||||
%tbody{ 'ng-repeat' => 'product in filteredProducts = ( products | filter:query | producer: producerFilter | category: categoryFilter | importDate: importDateFilter | limitTo:limit )', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
|
||||
%tbody{ 'ng-repeat' => 'product in products', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
|
||||
|
||||
= render 'spree/admin/products/index/products_product'
|
||||
= render 'spree/admin/products/index/products_variant'
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" }
|
||||
%div.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0', style: "margin-bottom: 10px" }
|
||||
%div.four.columns.alpha
|
||||
%input.four.columns.alpha{ :type => 'button', :value => t(:save_changes), 'ng-click' => 'submitProducts()'}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.form-buttons.filter-actions.actions
|
||||
= button Spree.t('actions.update'), 'icon-refresh'
|
||||
%span.or= Spree.t(:or)
|
||||
= button_link_to Spree.t('actions.cancel'), collection_url, icon: 'icon-remove'
|
||||
6
app/views/spree/admin/shared/_product_sub_menu.html.haml
Normal file
6
app/views/spree/admin/shared/_product_sub_menu.html.haml
Normal file
@@ -0,0 +1,6 @@
|
||||
- content_for :sub_menu do
|
||||
%ul#sub_nav.inline-menu
|
||||
= tab :products, match_path: '/products'
|
||||
= tab :option_types, match_path: '/option_types'
|
||||
= tab :properties
|
||||
= tab :prototypes
|
||||
@@ -3,11 +3,6 @@
|
||||
:variants_search => spree.admin_search_variants_path(:format => 'json'),
|
||||
:taxons_search => spree.api_taxons_path(:format => 'json'),
|
||||
:user_search => spree.admin_search_users_path(:format => 'json'),
|
||||
:product_search => spree.api_products_path(:format => 'json'),
|
||||
:option_type_search => spree.api_option_types_path(:format => 'json'),
|
||||
:states_search => spree.api_states_path(:format => 'json'),
|
||||
:orders_api => spree.api_orders_path(:format => 'json'),
|
||||
:stock_locations_api => spree.api_stock_locations_path(:format => 'json'),
|
||||
:variants_api => spree.api_variants_path(:format => 'json')
|
||||
:orders_api => spree.api_orders_path(:format => 'json')
|
||||
}.to_json %>;
|
||||
</script>
|
||||
|
||||
@@ -16,16 +16,13 @@
|
||||
</ul>
|
||||
<ul class='variant-data'>
|
||||
<li class='variant-sku'><strong><%= t('admin.sku') %>:</strong> {{variant.sku}}</li>
|
||||
<li class='variant-on_hand'><strong><%= t('on_hand') %>:</strong> {{variant.on_hand}}</li>
|
||||
{{#if variant.on_demand }}
|
||||
<li class='variant-on_demand'><strong><%= t('on_demand') %></strong></li>
|
||||
{{ else }}
|
||||
<li class='variant-on_hand'><strong><%= t('on_hand') %>:</strong> {{variant.on_hand}}</li>
|
||||
{{/if}}
|
||||
<li class='variant-options_text'><strong><%= t('.unit') %>:</strong> {{variant.options_text}}</li>
|
||||
</ul>
|
||||
|
||||
{{#if variant.option_values}}
|
||||
<ul class='variant-options'>
|
||||
{{#each variant.option_values}}
|
||||
<li><strong>{{this.option_type.presentation}}:</strong> {{this.presentation}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
@@ -45,7 +42,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
{{#if variant.[in_stock?]}}
|
||||
{{#if variant.in_stock}}
|
||||
{{#if variant.on_demand}}
|
||||
<td><%= Spree.t(:on_demand) %></td>
|
||||
{{else}}
|
||||
|
||||
67
app/views/spree/admin/variants/_form.html.haml
Normal file
67
app/views/spree/admin/variants/_form.html.haml
Normal file
@@ -0,0 +1,67 @@
|
||||
.label-block.left.six.columns.alpha{'ng-app' => 'admin.products'}
|
||||
.field
|
||||
= f.label :display_name, t(:display_name)
|
||||
= f.text_field :display_name, class: "fullwidth"
|
||||
.field
|
||||
= f.label :display_as, t(:display_as)
|
||||
= f.text_field :display_as, class: "fullwidth"
|
||||
|
||||
- if product_has_variant_unit_option_type?(@product)
|
||||
- if @product.variant_unit != 'items'
|
||||
.field{'ng-controller' => 'variantUnitsCtrl'}
|
||||
= f.label :unit_value, "#{t('admin.'+@product.variant_unit)} ({{unitName(#{@product.variant_unit_scale}, '#{@product.variant_unit}')}})"
|
||||
= hidden_field_tag 'product_variant_unit_scale', @product.variant_unit_scale
|
||||
= text_field_tag :unit_value_human, nil, {class: "fullwidth", 'ng-model' => 'unit_value_human', 'ng-change' => 'updateValue()'}
|
||||
= f.text_field :unit_value, {hidden: true, 'ng-value' => 'unit_value'}
|
||||
|
||||
.field
|
||||
= f.label :unit_description, t(:spree_admin_unit_description)
|
||||
= f.text_field :unit_description, class: "fullwidth", placeholder: t('admin.products.unit_name_placeholder')
|
||||
|
||||
%div
|
||||
- @product.option_types.each do |option_type|
|
||||
- unless variant_unit_option_type?(option_type)
|
||||
.field
|
||||
= label :new_variant, option_type.presentation
|
||||
- if @variant.new_record?
|
||||
= select(:new_variant, option_type.presentation, option_type.option_values.collect {|ov| [ ov.presentation, ov.id ] }, {}, {class: 'select2 fullwidth'})
|
||||
- else
|
||||
- if opt = @variant.option_values.detect {|o| o.option_type == option_type }.try(:presentation)
|
||||
= text_field(:new_variant, option_type.presentation, value: opt, disabled: 'disabled', class: 'fullwidth')
|
||||
.field
|
||||
= f.label :sku, Spree.t(:sku)
|
||||
= f.text_field :sku, class: 'fullwidth'
|
||||
.field
|
||||
= f.label :price, Spree.t(:price)
|
||||
= f.text_field :price, value: number_to_currency(@variant.price, unit: ''), class: 'fullwidth'
|
||||
.field
|
||||
= f.label :cost_price, Spree.t(:cost_price)
|
||||
= f.text_field :cost_price, value: number_to_currency(@variant.cost_price, unit: ''), class: 'fullwidth'
|
||||
|
||||
- if Spree::Config[:track_inventory_levels]
|
||||
%div{ 'set-on-demand' => '' }
|
||||
.field.checkbox
|
||||
%label
|
||||
= f.check_box :on_demand
|
||||
= t(:on_demand)
|
||||
%div{'ofn-with-tip' => t('admin.products.variants.to_order_tip')}
|
||||
%a= t('admin.whats_this')
|
||||
.field
|
||||
= f.label :on_hand, t(:on_hand)
|
||||
.fullwidth
|
||||
= f.text_field :on_hand
|
||||
|
||||
.right.six.columns.omega.label-block
|
||||
- if @product.variant_unit != 'weight'
|
||||
.field
|
||||
= f.label 'weight', t('weight')+' (kg)'
|
||||
- value = number_with_precision(@variant.weight, precision: 2)
|
||||
= f.text_field 'weight', value: value, class: 'fullwidth'
|
||||
|
||||
- [:height, :width, :depth].each do |field|
|
||||
.field
|
||||
= f.label field, t(field)
|
||||
- value = number_with_precision(@variant.send(field), precision: 2)
|
||||
= f.text_field field, value: value, class: 'fullwidth'
|
||||
|
||||
.clear
|
||||
11
app/views/spree/admin/variants/edit.html.haml
Normal file
11
app/views/spree/admin/variants/edit.html.haml
Normal file
@@ -0,0 +1,11 @@
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
= render partial: 'spree/admin/shared/product_tabs', locals: { current: 'Variants' }
|
||||
|
||||
= render partial: 'spree/shared/error_messages', locals: { target: @variant }
|
||||
|
||||
= form_for [:admin, @product, @variant] do |f|
|
||||
%fieldset.no-border-top
|
||||
%div
|
||||
= render partial: 'form', locals: { f: f }
|
||||
= render partial: 'spree/admin/shared/edit_resource_links'
|
||||
54
app/views/spree/admin/variants/index.html.haml
Normal file
54
app/views/spree/admin/variants/index.html.haml
Normal file
@@ -0,0 +1,54 @@
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
= render partial: 'spree/admin/shared/product_tabs', locals: {current: 'Variants'}
|
||||
|
||||
#new_variant
|
||||
- if @variants.any?
|
||||
%table.index.sortable{"data-sortable-link" => update_positions_admin_product_variants_path(@product)}
|
||||
%colgroup
|
||||
%col{style: "width: 5%"}/
|
||||
%col{style: "width: 25%"}/
|
||||
%col{style: "width: 20%"}/
|
||||
%col{style: "width: 20%"}/
|
||||
%col{style: "width: 15%"}/
|
||||
%col{style: "width: 15%"}/
|
||||
%thead
|
||||
%tr
|
||||
%th{colspan: "2"}= Spree.t(:options)
|
||||
%th= Spree.t(:price)
|
||||
%th= Spree.t(:sku)
|
||||
%th.actions
|
||||
%tbody
|
||||
- @variants.each do |variant|
|
||||
%tr{id: spree_dom_id(variant), class: cycle('odd', 'even'), style: "#{"color:red;" if variant.deleted? }" }
|
||||
%td.no-border
|
||||
%span.handle
|
||||
%td= variant.full_name
|
||||
%td.align-center= variant.display_price.to_html
|
||||
%td.align-center= variant.sku
|
||||
%td.actions
|
||||
= link_to_edit(variant, no_text: true) unless variant.deleted?
|
||||
= link_to_delete(variant, no_text: true) unless variant.deleted?
|
||||
- unless @product.has_variants?
|
||||
%tr
|
||||
%td{colspan: "5"}= Spree.t(:none)
|
||||
|
||||
- else
|
||||
.alpha.twelve.columns.no-objects-found
|
||||
= Spree.t(:no_results)
|
||||
\.
|
||||
|
||||
- if @product.empty_option_values?
|
||||
%p.first_add_option_types.no-objects-found
|
||||
= Spree.t(:to_add_variants_you_must_first_define)
|
||||
= link_to Spree.t(:option_types), admin_product_url(@product)
|
||||
= Spree.t(:and)
|
||||
= link_to Spree.t(:option_values), admin_option_types_url
|
||||
|
||||
- else
|
||||
- content_for :page_actions do
|
||||
%ul.inline-menu
|
||||
%li#new_var_link
|
||||
= link_to_with_icon('icon-plus', Spree.t(:new_variant), new_admin_product_variant_url(@product), remote: true, 'data-update' => 'new_variant', class: 'button')
|
||||
|
||||
%li= link_to_with_icon('icon-filter', @deleted.blank? ? Spree.t(:show_deleted) : Spree.t(:show_active), admin_product_variants_url(@product, deleted: @deleted.blank? ? "on" : "off"), class: 'button')
|
||||
7
app/views/spree/admin/variants/new.html.haml
Normal file
7
app/views/spree/admin/variants/new.html.haml
Normal file
@@ -0,0 +1,7 @@
|
||||
= render partial: 'spree/shared/error_messages', locals: { target: @variant }
|
||||
|
||||
= form_for [:admin, @product, @variant] do |f|
|
||||
%fieldset{'data-hook' => "admin_variant_new_form"}
|
||||
%legend{align: "center"}= Spree.t(:new_variant)
|
||||
= render partial: 'form', locals: { f: f }
|
||||
= render partial: 'spree/admin/shared/new_resource_links'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user