mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-15 19:06:50 +00:00
Compare commits
228 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24fe7e8878 | ||
|
|
eb94c5a5bd | ||
|
|
4ef61b642e | ||
|
|
23f4faf192 | ||
|
|
becd57f7a4 | ||
|
|
2e4b224d48 | ||
|
|
2b210bd096 | ||
|
|
91306d5ce4 | ||
|
|
e20e19f963 | ||
|
|
15e56b21ae | ||
|
|
629db3ae4d | ||
|
|
d8e6d98912 | ||
|
|
63eb0980eb | ||
|
|
06ead827d8 | ||
|
|
6dd4a866e5 | ||
|
|
04c962432a | ||
|
|
ffceff3f0a | ||
|
|
4658a53aeb | ||
|
|
3bc834435d | ||
|
|
9111ff1a38 | ||
|
|
b0eac1ecaa | ||
|
|
2e31f234d6 | ||
|
|
e29a81cacc | ||
|
|
a672af1a79 | ||
|
|
f282ff805d | ||
|
|
2f7dc9a578 | ||
|
|
a9829ba5d9 | ||
|
|
8532fa16cd | ||
|
|
c3f01be580 | ||
|
|
e23045b19e | ||
|
|
5e1dea61a8 | ||
|
|
cedf1b26f2 | ||
|
|
47a93568dc | ||
|
|
62471bf2ab | ||
|
|
cdf5bcb7eb | ||
|
|
7414047b92 | ||
|
|
4ef5dfe430 | ||
|
|
e58a1d080f | ||
|
|
357a88fa22 | ||
|
|
f71045b3f2 | ||
|
|
dcdd3f2444 | ||
|
|
6820919552 | ||
|
|
4a4173bdc0 | ||
|
|
9a7e782102 | ||
|
|
9fa892346e | ||
|
|
7341912390 | ||
|
|
d0c797b797 | ||
|
|
cb2e17d7dc | ||
|
|
147654df41 | ||
|
|
14cf168e3b | ||
|
|
c3ee7b7c64 | ||
|
|
a6414b6dbe | ||
|
|
eafffa2c23 | ||
|
|
82a4753eec | ||
|
|
b92e858448 | ||
|
|
4c41c84cc1 | ||
|
|
e53f733966 | ||
|
|
2a8809e6e8 | ||
|
|
31a54e49c5 | ||
|
|
b5ba2acb21 | ||
|
|
0fabde8849 | ||
|
|
e97a16cb40 | ||
|
|
57d87a8042 | ||
|
|
0ca87580e8 | ||
|
|
cf712e9478 | ||
|
|
59e0f3d9f4 | ||
|
|
fc5aff8c79 | ||
|
|
dd717fe8ac | ||
|
|
6341c5fd80 | ||
|
|
47ac6c1491 | ||
|
|
6afda141a1 | ||
|
|
5bb2614f9d | ||
|
|
b3c968856b | ||
|
|
b0a7497f2a | ||
|
|
f959e632ea | ||
|
|
f9cf826f1c | ||
|
|
ececbce596 | ||
|
|
1b7ac1a252 | ||
|
|
d31b24786a | ||
|
|
374bf04118 | ||
|
|
3aff7f62e3 | ||
|
|
fc5e346a06 | ||
|
|
29bbf2fa74 | ||
|
|
64c66ddedc | ||
|
|
e0e2c32d9f | ||
|
|
003341ef7a | ||
|
|
94f8ea2f93 | ||
|
|
e6cd33ee57 | ||
|
|
eb64112b22 | ||
|
|
3e14b62b46 | ||
|
|
3244650932 | ||
|
|
b6753a2593 | ||
|
|
1b119805b4 | ||
|
|
edde7689a9 | ||
|
|
8060977786 | ||
|
|
837a345958 | ||
|
|
7c5e511fde | ||
|
|
d18a06a0f7 | ||
|
|
d23b4fd307 | ||
|
|
2cb4c6bec2 | ||
|
|
924e816a5b | ||
|
|
109da43905 | ||
|
|
33ca6a2096 | ||
|
|
e7b780f963 | ||
|
|
13cba3d244 | ||
|
|
ce45e7cf71 | ||
|
|
ba5a56db14 | ||
|
|
276dcf4a3b | ||
|
|
dcfb1aec6d | ||
|
|
fde2aac366 | ||
|
|
63138aef30 | ||
|
|
7612415991 | ||
|
|
53d901b41b | ||
|
|
bc859cf9f7 | ||
|
|
af48cac140 | ||
|
|
59bb956677 | ||
|
|
8a544f3ab3 | ||
|
|
55b3f4d54f | ||
|
|
452ab3a842 | ||
|
|
a049e7a433 | ||
|
|
97063bf47e | ||
|
|
e64d573337 | ||
|
|
7858a26e5e | ||
|
|
4922c05bcf | ||
|
|
d06b7b8606 | ||
|
|
72b47fbceb | ||
|
|
da7b0966be | ||
|
|
445eb9f287 | ||
|
|
5b4dd57380 | ||
|
|
adb61e48c5 | ||
|
|
838ffdbf00 | ||
|
|
fe8beb5448 | ||
|
|
18ee5254f9 | ||
|
|
40f8cf660c | ||
|
|
2c70db7952 | ||
|
|
1a38a4e1a7 | ||
|
|
15d106bb0a | ||
|
|
c8be2fb89a | ||
|
|
b1f8f91011 | ||
|
|
e08606a310 | ||
|
|
2230d83729 | ||
|
|
d38da02113 | ||
|
|
0e268a171f | ||
|
|
73c4eedd06 | ||
|
|
8a220742f5 | ||
|
|
9fa604db0d | ||
|
|
6083d91d3e | ||
|
|
9bdb396b86 | ||
|
|
ad42b1b485 | ||
|
|
78170bc709 | ||
|
|
957b398a54 | ||
|
|
c3d25bf163 | ||
|
|
ce2a164c66 | ||
|
|
b898ce1ae1 | ||
|
|
409681b7af | ||
|
|
9317549103 | ||
|
|
b22ad244f9 | ||
|
|
9c17a91215 | ||
|
|
6a1c541479 | ||
|
|
6c64261868 | ||
|
|
f437d0f8a0 | ||
|
|
79d6d7cc9e | ||
|
|
e200ece280 | ||
|
|
30bf9257ab | ||
|
|
03e229da08 | ||
|
|
28473c9087 | ||
|
|
09c8819e5a | ||
|
|
f7c047b798 | ||
|
|
b6da0e2092 | ||
|
|
95963c5732 | ||
|
|
aba1b5b67a | ||
|
|
fe58121c7f | ||
|
|
5c4a2c2790 | ||
|
|
a07281910b | ||
|
|
72f9da3ac4 | ||
|
|
375b4648dc | ||
|
|
9af0a39305 | ||
|
|
6817231f29 | ||
|
|
7c7f9551d6 | ||
|
|
d568b45d4a | ||
|
|
b76a6d15a3 | ||
|
|
940423acfc | ||
|
|
6a57aa3b29 | ||
|
|
ca78e9d0e2 | ||
|
|
e705e88007 | ||
|
|
1fbb9fa3df | ||
|
|
71f00f9283 | ||
|
|
7d33a237d0 | ||
|
|
857cacb74b | ||
|
|
fbfe663ebc | ||
|
|
83b90f3167 | ||
|
|
14fd9a121e | ||
|
|
904e89e325 | ||
|
|
2e98b0b5c1 | ||
|
|
523faa670d | ||
|
|
e5e9325499 | ||
|
|
2e4f8003b6 | ||
|
|
60d9edb185 | ||
|
|
0e62dc04bd | ||
|
|
e2940eb9ff | ||
|
|
1c1f066884 | ||
|
|
d5cf355a11 | ||
|
|
d2eee1dafd | ||
|
|
540b26105f | ||
|
|
45b5e838b7 | ||
|
|
da837ff100 | ||
|
|
da2598282b | ||
|
|
0308f1465d | ||
|
|
a6e4893287 | ||
|
|
26769b4150 | ||
|
|
84745e4ccb | ||
|
|
aac7a5e559 | ||
|
|
0a2941ed96 | ||
|
|
05ccd1ecbf | ||
|
|
f8a4f00d52 | ||
|
|
29377bbff9 | ||
|
|
f68d0c2a0f | ||
|
|
3901c49af9 | ||
|
|
ae0ceb61a1 | ||
|
|
fb1c825fbc | ||
|
|
e36b0249b9 | ||
|
|
34fa2d7ad6 | ||
|
|
3aefea9f04 | ||
|
|
15231a9128 | ||
|
|
25e3f72934 | ||
|
|
523d819575 | ||
|
|
bc0a1d9bae | ||
|
|
a53dc3a8c1 |
@@ -33,7 +33,6 @@ Layout/LineLength:
|
||||
- app/controllers/admin/inventory_items_controller.rb
|
||||
- app/controllers/admin/manager_invitations_controller.rb
|
||||
- app/controllers/admin/product_import_controller.rb
|
||||
- app/controllers/admin/proxy_orders_controller.rb
|
||||
- app/controllers/admin/schedules_controller.rb
|
||||
- app/controllers/admin/subscriptions_controller.rb
|
||||
- app/controllers/admin/variant_overrides_controller.rb
|
||||
@@ -98,7 +97,6 @@ Layout/LineLength:
|
||||
- app/services/embedded_page_service.rb
|
||||
- app/services/order_cycle_form.rb
|
||||
- app/services/order_factory.rb
|
||||
- app/services/subscriptions_count.rb
|
||||
- app/services/variants_stock_levels.rb
|
||||
- engines/web/app/helpers/web/cookies_policy_helper.rb
|
||||
- lib/discourse/single_sign_on.rb
|
||||
@@ -239,10 +237,7 @@ Layout/LineLength:
|
||||
- spec/lib/open_food_network/packing_report_spec.rb
|
||||
- spec/lib/open_food_network/permissions_spec.rb
|
||||
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
|
||||
- spec/lib/open_food_network/proxy_order_syncer_spec.rb
|
||||
- spec/lib/open_food_network/scope_variant_to_hub_spec.rb
|
||||
- spec/lib/open_food_network/subscription_payment_updater_spec.rb
|
||||
- spec/lib/open_food_network/subscription_summarizer_spec.rb
|
||||
- spec/lib/open_food_network/tag_rule_applicator_spec.rb
|
||||
- spec/lib/open_food_network/users_and_enterprises_report_spec.rb
|
||||
- spec/lib/open_food_network/xero_invoices_report_spec.rb
|
||||
@@ -317,10 +312,6 @@ Layout/LineLength:
|
||||
- spec/services/permissions/order_spec.rb
|
||||
- spec/services/product_tag_rules_filterer_spec.rb
|
||||
- spec/services/products_renderer_spec.rb
|
||||
- spec/services/subscription_estimator_spec.rb
|
||||
- spec/services/subscription_form_spec.rb
|
||||
- spec/services/subscription_validator_spec.rb
|
||||
- spec/services/subscription_variants_service_spec.rb
|
||||
- spec/spec_helper.rb
|
||||
- spec/support/cancan_helper.rb
|
||||
- spec/support/delayed_job_helper.rb
|
||||
@@ -386,7 +377,6 @@ Metrics/AbcSize:
|
||||
- app/helpers/spree/admin/base_helper.rb
|
||||
- app/helpers/spree/admin/zones_helper.rb
|
||||
- app/helpers/spree/orders_helper.rb
|
||||
- app/mailers/producer_mailer.rb
|
||||
- app/models/calculator/flat_percent_per_item.rb
|
||||
- app/models/column_preference.rb
|
||||
- app/models/enterprise.rb
|
||||
@@ -411,7 +401,7 @@ Metrics/AbcSize:
|
||||
- app/services/cart_service.rb
|
||||
- app/services/create_order_cycle.rb
|
||||
- app/services/order_syncer.rb
|
||||
- app/services/subscription_validator.rb
|
||||
- engines/order_management/app/services/order_management/subscriptions/validator.rb
|
||||
- lib/active_merchant/billing/gateways/stripe_decorator.rb
|
||||
- lib/active_merchant/billing/gateways/stripe_payment_intents.rb
|
||||
- lib/discourse/single_sign_on.rb
|
||||
@@ -583,7 +573,6 @@ Metrics/MethodLength:
|
||||
- app/helpers/spree/admin/navigation_helper.rb
|
||||
- app/helpers/spree/admin/base_helper.rb
|
||||
- app/jobs/subscription_placement_job.rb
|
||||
- app/mailers/producer_mailer.rb
|
||||
- app/models/column_preference.rb
|
||||
- app/models/enterprise.rb
|
||||
- app/models/enterprise_relationship.rb
|
||||
@@ -686,6 +675,12 @@ Metrics/ModuleLength:
|
||||
- app/helpers/injection_helper.rb
|
||||
- app/helpers/spree/admin/navigation_helper.rb
|
||||
- app/helpers/spree/admin/base_helper.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/form_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/validator_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/variants_list_spec.rb
|
||||
- lib/open_food_network/column_preference_defaults.rb
|
||||
- spec/controllers/admin/enterprises_controller_spec.rb
|
||||
- spec/controllers/admin/order_cycles_controller_spec.rb
|
||||
@@ -701,9 +696,7 @@ Metrics/ModuleLength:
|
||||
- spec/lib/open_food_network/order_grouper_spec.rb
|
||||
- spec/lib/open_food_network/permissions_spec.rb
|
||||
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
|
||||
- spec/lib/open_food_network/proxy_order_syncer_spec.rb
|
||||
- spec/lib/open_food_network/scope_variant_to_hub_spec.rb
|
||||
- spec/lib/open_food_network/subscription_payment_updater_spec.rb
|
||||
- spec/lib/open_food_network/tag_rule_applicator_spec.rb
|
||||
- spec/lib/open_food_network/users_and_enterprises_report_spec.rb
|
||||
- spec/models/spree/ability_spec.rb
|
||||
|
||||
@@ -71,7 +71,6 @@ Lint/DuplicateHashKey:
|
||||
Lint/DuplicateMethods:
|
||||
Exclude:
|
||||
- 'lib/discourse/single_sign_on.rb'
|
||||
- 'lib/open_food_network/subscription_summary.rb'
|
||||
|
||||
# Offense count: 10
|
||||
Lint/IneffectiveAccessModifier:
|
||||
@@ -159,7 +158,7 @@ Naming/MethodParameterName:
|
||||
Exclude:
|
||||
- 'app/helpers/spree/admin/base_helper_decorator.rb'
|
||||
- 'app/helpers/spree/base_helper_decorator.rb'
|
||||
- 'app/services/subscription_validator.rb'
|
||||
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
|
||||
- 'lib/open_food_network/reports/bulk_coop_report.rb'
|
||||
- 'lib/open_food_network/xero_invoices_report.rb'
|
||||
- 'spec/lib/open_food_network/reports/report_spec.rb'
|
||||
@@ -174,7 +173,6 @@ Naming/MethodParameterName:
|
||||
Naming/PredicateName:
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'app/mailers/producer_mailer.rb'
|
||||
- 'app/models/enterprise.rb'
|
||||
- 'app/models/enterprise_relationship.rb'
|
||||
- 'app/models/order_cycle.rb'
|
||||
@@ -631,7 +629,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'app/jobs/subscription_placement_job.rb'
|
||||
- 'app/jobs/welcome_enterprise_job.rb'
|
||||
- 'app/mailers/enterprise_mailer.rb'
|
||||
- 'app/mailers/producer_mailer.rb'
|
||||
- 'app/mailers/spree/base_mailer_decorator.rb'
|
||||
- 'app/mailers/spree/order_mailer_decorator.rb'
|
||||
- 'app/mailers/spree/user_mailer.rb'
|
||||
@@ -849,11 +846,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'app/services/reset_order_service.rb'
|
||||
- 'app/services/restart_checkout.rb'
|
||||
- 'app/services/search_orders.rb'
|
||||
- 'app/services/subscription_estimator.rb'
|
||||
- 'app/services/subscription_form.rb'
|
||||
- 'app/services/subscription_validator.rb'
|
||||
- 'app/services/subscription_variants_service.rb'
|
||||
- 'app/services/subscriptions_count.rb'
|
||||
- 'app/services/tax_rate_finder.rb'
|
||||
- 'app/services/upload_sanitizer.rb'
|
||||
- 'app/services/variant_deleter.rb'
|
||||
@@ -861,7 +853,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'app/validators/date_time_string_validator.rb'
|
||||
- 'app/validators/distributors_validator.rb'
|
||||
- 'app/validators/integer_array_validator.rb'
|
||||
- 'app/views/spree/admin/taxons/search.rabl'
|
||||
- 'config.ru'
|
||||
- 'engines/order_management/app/controllers/order_management/application_controller.rb'
|
||||
- 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb'
|
||||
@@ -892,6 +883,7 @@ Style/FrozenStringLiteralComment:
|
||||
- 'engines/order_management/lib/order_management/engine.rb'
|
||||
- 'engines/order_management/lib/order_management/version.rb'
|
||||
- 'engines/order_management/order_management.gemspec'
|
||||
- 'engines/order_management/spec/performance/order_management/subscriptions/proxy_order_syncer_spec.rb'
|
||||
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/authorizer_spec.rb'
|
||||
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/parameters_spec.rb'
|
||||
- 'engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/permissions_spec.rb'
|
||||
@@ -949,7 +941,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'lib/open_food_network/products_and_inventory_report.rb'
|
||||
- 'lib/open_food_network/products_and_inventory_report_base.rb'
|
||||
- 'lib/open_food_network/property_merge.rb'
|
||||
- 'lib/open_food_network/proxy_order_syncer.rb'
|
||||
- 'lib/open_food_network/rack_request_blocker.rb'
|
||||
- 'lib/open_food_network/referer_parser.rb'
|
||||
- 'lib/open_food_network/reports/bulk_coop_allocation_report.rb'
|
||||
@@ -966,8 +957,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'lib/open_food_network/scope_variants_for_search.rb'
|
||||
- 'lib/open_food_network/spree_api_key_loader.rb'
|
||||
- 'lib/open_food_network/subscription_payment_updater.rb'
|
||||
- 'lib/open_food_network/subscription_summarizer.rb'
|
||||
- 'lib/open_food_network/subscription_summary.rb'
|
||||
- 'lib/open_food_network/tag_rule_applicator.rb'
|
||||
- 'lib/open_food_network/user_balance_calculator.rb'
|
||||
- 'lib/open_food_network/users_and_enterprises_report.rb'
|
||||
@@ -1209,7 +1198,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'spec/lib/open_food_network/permissions_spec.rb'
|
||||
- 'spec/lib/open_food_network/products_and_inventory_report_spec.rb'
|
||||
- 'spec/lib/open_food_network/property_merge_spec.rb'
|
||||
- 'spec/lib/open_food_network/proxy_order_syncer_spec.rb'
|
||||
- 'spec/lib/open_food_network/referer_parser_spec.rb'
|
||||
- 'spec/lib/open_food_network/reports/report_spec.rb'
|
||||
- 'spec/lib/open_food_network/reports/row_spec.rb'
|
||||
@@ -1218,8 +1206,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'spec/lib/open_food_network/scope_variant_to_hub_spec.rb'
|
||||
- 'spec/lib/open_food_network/scope_variants_to_search_spec.rb'
|
||||
- 'spec/lib/open_food_network/subscription_payment_updater_spec.rb'
|
||||
- 'spec/lib/open_food_network/subscription_summarizer_spec.rb'
|
||||
- 'spec/lib/open_food_network/subscription_summary_spec.rb'
|
||||
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
|
||||
- 'spec/lib/open_food_network/user_balance_calculator_spec.rb'
|
||||
- 'spec/lib/open_food_network/users_and_enterprises_report_spec.rb'
|
||||
@@ -1304,7 +1290,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'spec/models/variant_override_spec.rb'
|
||||
- 'spec/performance/injection_helper_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'
|
||||
@@ -1355,11 +1340,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'spec/services/reset_order_service_spec.rb'
|
||||
- 'spec/services/restart_checkout_spec.rb'
|
||||
- 'spec/services/search_orders_spec.rb'
|
||||
- 'spec/services/subscription_estimator_spec.rb'
|
||||
- 'spec/services/subscription_form_spec.rb'
|
||||
- 'spec/services/subscription_validator_spec.rb'
|
||||
- 'spec/services/subscription_variants_service_spec.rb'
|
||||
- 'spec/services/subscriptions_count_spec.rb'
|
||||
- 'spec/services/tax_rate_finder_spec.rb'
|
||||
- 'spec/services/upload_sanitizer_spec.rb'
|
||||
- 'spec/services/variants_stock_levels_spec.rb'
|
||||
@@ -1557,7 +1537,6 @@ Style/Send:
|
||||
- 'spec/lib/open_food_network/products_and_inventory_report_spec.rb'
|
||||
- 'spec/lib/open_food_network/sales_tax_report_spec.rb'
|
||||
- 'spec/lib/open_food_network/subscription_payment_updater_spec.rb'
|
||||
- 'spec/lib/open_food_network/subscription_summarizer_spec.rb'
|
||||
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
|
||||
- 'spec/lib/open_food_network/xero_invoices_report_spec.rb'
|
||||
- 'spec/lib/stripe/webhook_handler_spec.rb'
|
||||
|
||||
11
DOCKER.md
11
DOCKER.md
@@ -35,13 +35,20 @@ Download the Docker images and build the containers:
|
||||
$ docker-compose build
|
||||
```
|
||||
|
||||
Run the app with all the required containers:
|
||||
Setup the database and seed it with sample data:
|
||||
```sh
|
||||
$ docker-compose run web bundle exec rake db:reset
|
||||
$ docker-compose run web bundle exec rake db:test:prepare
|
||||
$ docker-compose run web bundle exec rake ofn:sample_data
|
||||
```
|
||||
|
||||
Finally, run the app with all the required containers:
|
||||
|
||||
```sh
|
||||
$ 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.
|
||||
The default admin user is 'ofn@example.com' with 'ofn123' password.
|
||||
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.
|
||||
|
||||
@@ -12,7 +12,7 @@ ENV BUNDLE_PATH /bundles
|
||||
WORKDIR /usr/src/app
|
||||
COPY .ruby-version .
|
||||
|
||||
# Rbenv & Ruby part
|
||||
# Install Rbenv & Ruby
|
||||
RUN git clone https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
|
||||
git clone https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
|
||||
${RBENV_ROOT}/plugins/ruby-build/install.sh && \
|
||||
@@ -21,7 +21,7 @@ RUN git clone https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
|
||||
rbenv global $(cat .ruby-version) && \
|
||||
gem install bundler --version=1.17.2
|
||||
|
||||
# Postgres
|
||||
# Install Postgres
|
||||
RUN sh -c "echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > /etc/apt/sources.list.d/pgdg.list" && \
|
||||
wget --quiet -O - https://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | apt-key add - && \
|
||||
apt-get update && \
|
||||
@@ -38,4 +38,6 @@ RUN wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.z
|
||||
unzip chromedriver_linux64.zip -d /usr/bin && \
|
||||
chmod u+x /usr/bin/chromedriver
|
||||
|
||||
# Copy code and install app dependencies
|
||||
COPY . /usr/src/app/
|
||||
RUN bundle install
|
||||
|
||||
@@ -118,7 +118,7 @@ $ createdb open_food_network_test --owner=ofn
|
||||
If these commands succeed, you should be able to [continue the setup process](#get-it-running).
|
||||
|
||||
[developer-wiki]: https://github.com/openfoodfoundation/openfoodnetwork/wiki
|
||||
[sierra]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup%3A-macOS-%28Sierra%2C-HighSierra-and-Mojave%29
|
||||
[sierra]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup%3A-macOS-%28Sierra%2C-HighSierra%2C-Mojave-and-Catalina%29
|
||||
[el-capitan]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-OS-X-(El-Capitan)
|
||||
[ubuntu]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-Ubuntu
|
||||
[wiki]: https://github.com/openfoodfoundation/openfoodnetwork/wiki
|
||||
|
||||
7
Gemfile
7
Gemfile
@@ -45,10 +45,6 @@ gem 'daemons'
|
||||
gem 'delayed_job_active_record'
|
||||
gem 'delayed_job_web'
|
||||
|
||||
# Fix bug in simple_form preventing collection_check_boxes usage within form_for block
|
||||
# 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'
|
||||
@@ -59,7 +55,6 @@ gem 'aws-sdk'
|
||||
gem 'bugsnag'
|
||||
gem 'db2fog'
|
||||
gem 'haml'
|
||||
gem 'rabl'
|
||||
gem 'redcarpet'
|
||||
gem 'sass', "~> 3.3"
|
||||
gem 'sass-rails', '~> 3.2.3'
|
||||
@@ -112,7 +107,6 @@ gem 'momentjs-rails'
|
||||
gem 'turbo-sprockets-rails3'
|
||||
|
||||
gem "foundation-rails"
|
||||
gem 'foundation_rails_helper', github: 'willrjmarshall/foundation_rails_helper', branch: "rails3"
|
||||
|
||||
gem 'jquery-migrate-rails'
|
||||
gem 'jquery-rails', '3.1.5'
|
||||
@@ -123,6 +117,7 @@ gem 'ofn-qz', github: 'openfoodfoundation/ofn-qz', ref: '60da2ae4c44cbb4c8d602f5
|
||||
|
||||
group :production, :staging do
|
||||
gem 'ddtrace'
|
||||
gem 'unicorn-worker-killer'
|
||||
end
|
||||
|
||||
group :test, :development do
|
||||
|
||||
53
Gemfile.lock
53
Gemfile.lock
@@ -1,11 +1,3 @@
|
||||
GIT
|
||||
remote: https://github.com/RohanM/simple_form.git
|
||||
revision: 45f08a213b40f3d4bda5f5398db841137587160a
|
||||
specs:
|
||||
simple_form (2.0.2)
|
||||
actionpack (~> 3.0)
|
||||
activemodel (~> 3.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/jeremydurham/custom-err-msg.git
|
||||
revision: 3a8ec9dddc7a5b0aab7c69a6060596de300c68f4
|
||||
@@ -65,16 +57,6 @@ GIT
|
||||
rails-i18n
|
||||
spree_core (>= 1.1)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/willrjmarshall/foundation_rails_helper.git
|
||||
revision: 4d5d53fdc4b1fb71e66524d298c5c635de82cfbb
|
||||
branch: rails3
|
||||
specs:
|
||||
foundation_rails_helper (0.4)
|
||||
actionpack (>= 3.0)
|
||||
activemodel (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
|
||||
PATH
|
||||
remote: engines/catalog
|
||||
specs:
|
||||
@@ -173,7 +155,7 @@ GEM
|
||||
xpath (>= 2.0, < 4.0)
|
||||
childprocess (3.0.0)
|
||||
chronic (0.10.2)
|
||||
chunky_png (1.3.10)
|
||||
chunky_png (1.3.11)
|
||||
climate_control (0.2.0)
|
||||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
@@ -200,7 +182,7 @@ GEM
|
||||
sass (>= 3.3.0, < 3.5)
|
||||
compass-import-once (1.0.5)
|
||||
sass (>= 3.2, < 3.5)
|
||||
compass-rails (3.1.0)
|
||||
compass-rails (4.0.0)
|
||||
compass (~> 1.0.0)
|
||||
sass-rails (< 5.1)
|
||||
sprockets (< 4.0)
|
||||
@@ -216,7 +198,7 @@ GEM
|
||||
activerecord (>= 3.2.0, < 5.0)
|
||||
fog (~> 1.0)
|
||||
rails (>= 3.2.0, < 5.0)
|
||||
ddtrace (0.33.1)
|
||||
ddtrace (0.34.2)
|
||||
msgpack
|
||||
debugger-linecache (1.2.0)
|
||||
deface (1.0.2)
|
||||
@@ -257,7 +239,7 @@ GEM
|
||||
faraday (1.0.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffaker (1.22.1)
|
||||
ffi (1.11.3)
|
||||
ffi (1.12.2)
|
||||
figaro (1.1.1)
|
||||
thor (~> 0.14)
|
||||
fission (0.5.0)
|
||||
@@ -421,6 +403,8 @@ GEM
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
geocoder (1.1.8)
|
||||
get_process_mem (0.2.5)
|
||||
ffi (~> 1.0)
|
||||
gmaps4rails (1.5.6)
|
||||
haml (4.0.7)
|
||||
tilt
|
||||
@@ -485,7 +469,7 @@ GEM
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
oj (3.10.5)
|
||||
oj (3.10.6)
|
||||
orm_adapter (0.5.0)
|
||||
paper_trail (5.2.3)
|
||||
activerecord (>= 3.0, < 6.0)
|
||||
@@ -499,7 +483,7 @@ GEM
|
||||
parallel (1.19.1)
|
||||
paranoia (1.3.4)
|
||||
activerecord (~> 3.1)
|
||||
parser (2.7.0.5)
|
||||
parser (2.7.1.0)
|
||||
ast (~> 2.4.0)
|
||||
paypal-sdk-core (0.2.10)
|
||||
multi_json (~> 1.0)
|
||||
@@ -518,8 +502,6 @@ GEM
|
||||
byebug (>= 9.0, < 9.1)
|
||||
pry (~> 0.10)
|
||||
public_suffix (4.0.3)
|
||||
rabl (0.8.4)
|
||||
activesupport (>= 2.3.14)
|
||||
rack (1.4.7)
|
||||
rack-cache (1.11.0)
|
||||
rack (>= 0.4)
|
||||
@@ -559,8 +541,8 @@ GEM
|
||||
activerecord (~> 3.0)
|
||||
polyamorous (~> 0.5.0)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.9.10)
|
||||
ffi (>= 0.5.0, < 2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rbvmomi (1.13.0)
|
||||
builder (~> 3.0)
|
||||
json (>= 1.8)
|
||||
@@ -604,15 +586,15 @@ GEM
|
||||
rspec-retry (0.6.2)
|
||||
rspec-core (> 3.3)
|
||||
rspec-support (3.9.2)
|
||||
rubocop (0.80.1)
|
||||
rubocop (0.81.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.0.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
rexml
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
rubocop-rails (2.5.0)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-rails (2.5.2)
|
||||
activesupport
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
@@ -671,13 +653,16 @@ GEM
|
||||
tzinfo (0.3.56)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unicode-display_width (1.6.1)
|
||||
unicode-display_width (1.7.0)
|
||||
unicorn (5.5.4)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
unicorn-rails (2.2.1)
|
||||
rack
|
||||
unicorn
|
||||
unicorn-worker-killer (0.4.4)
|
||||
get_process_mem (~> 0)
|
||||
unicorn (>= 4, < 6)
|
||||
uuidtools (2.1.5)
|
||||
warden (1.2.7)
|
||||
rack (>= 1.0)
|
||||
@@ -740,7 +725,6 @@ DEPENDENCIES
|
||||
foreigner
|
||||
foundation-icons-sass-rails
|
||||
foundation-rails
|
||||
foundation_rails_helper!
|
||||
fuubar (~> 2.5.0)
|
||||
geocoder
|
||||
gmaps4rails
|
||||
@@ -768,7 +752,6 @@ DEPENDENCIES
|
||||
paperclip (~> 3.4.1)
|
||||
pg (~> 0.21.0)
|
||||
pry-byebug (>= 3.4.3)
|
||||
rabl
|
||||
rack-mini-profiler (< 3.0.0)
|
||||
rack-rewrite
|
||||
rack-ssl
|
||||
@@ -787,7 +770,6 @@ DEPENDENCIES
|
||||
select2-rails (~> 3.4.7)
|
||||
selenium-webdriver
|
||||
shoulda-matchers
|
||||
simple_form!
|
||||
simplecov
|
||||
spinjs-rails
|
||||
spree_core!
|
||||
@@ -803,6 +785,7 @@ DEPENDENCIES
|
||||
uglifier (>= 1.0.3)
|
||||
unicorn
|
||||
unicorn-rails
|
||||
unicorn-worker-killer
|
||||
web!
|
||||
webdrivers
|
||||
webmock
|
||||
|
||||
@@ -2,19 +2,24 @@ angular.module("admin.indexUtils").factory "PagedFetcher", (dataFetcher) ->
|
||||
new class PagedFetcher
|
||||
# Given a URL like http://example.com/foo?page=::page::&per_page=20
|
||||
# And the response includes an attribute pages with the number of pages to fetch
|
||||
# Fetch each page async, and call the processData callback with the resulting data
|
||||
fetch: (url, processData, onLastPageComplete) ->
|
||||
dataFetcher(@urlForPage(url, 1)).then (data) =>
|
||||
processData data
|
||||
# Fetch each page async, and call the pageCallback callback with the resulting data
|
||||
# Developer note: this class should not be re-used!
|
||||
page: 1
|
||||
last_page: 1
|
||||
|
||||
if data.pages > 1
|
||||
for page in [2..data.pages]
|
||||
lastPromise = dataFetcher(@urlForPage(url, page)).then (data) ->
|
||||
processData data
|
||||
onLastPageComplete && lastPromise.then onLastPageComplete
|
||||
return
|
||||
else
|
||||
onLastPageComplete && onLastPageComplete()
|
||||
fetch: (url, pageCallback) ->
|
||||
@fetchPages(url, @page, pageCallback)
|
||||
|
||||
urlForPage: (url, page) ->
|
||||
url.replace("::page::", page)
|
||||
|
||||
fetchPages: (url, page, pageCallback) ->
|
||||
dataFetcher(@urlForPage(url, page)).then (data) =>
|
||||
@page++
|
||||
@last_page = data.pages
|
||||
|
||||
pageCallback(data) if pageCallback
|
||||
|
||||
if @page <= @last_page
|
||||
@fetchPages(url, @page, pageCallback)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, SortOptions, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, VariantUnitManager, RequestMonitor) ->
|
||||
$scope.initialized = false
|
||||
$scope.RequestMonitor = RequestMonitor
|
||||
$scope.filteredLineItems = []
|
||||
$scope.line_items = LineItems.all
|
||||
$scope.confirmDelete = true
|
||||
$scope.startDate = moment().startOf('day').subtract(7, 'days').format('YYYY-MM-DD')
|
||||
$scope.endDate = moment().startOf('day').format('YYYY-MM-DD')
|
||||
@@ -15,50 +15,77 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.confirmRefresh = ->
|
||||
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
|
||||
|
||||
$scope.resetFilters = ->
|
||||
$scope.distributorFilter = ''
|
||||
$scope.supplierFilter = ''
|
||||
$scope.orderCycleFilter = ''
|
||||
$scope.quickSearch = ''
|
||||
|
||||
$scope.resetSelectFilters = ->
|
||||
$scope.distributorFilter = 0
|
||||
$scope.supplierFilter = 0
|
||||
$scope.orderCycleFilter = 0
|
||||
$scope.quickSearch = ""
|
||||
$scope.resetFilters()
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.refreshData = ->
|
||||
unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == 0
|
||||
$scope.startDate = moment(OrderCycles.byID[$scope.orderCycleFilter].orders_open_at).format('YYYY-MM-DD')
|
||||
$scope.endDate = moment(OrderCycles.byID[$scope.orderCycleFilter].orders_close_at).startOf('day').format('YYYY-MM-DD')
|
||||
unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == ''
|
||||
$scope.setOrderCycleDateRange()
|
||||
|
||||
formatted_start_date = moment($scope.startDate).format()
|
||||
formatted_end_date = moment($scope.endDate).add(1,'day').format()
|
||||
$scope.formattedStartDate = moment($scope.startDate).format()
|
||||
$scope.formattedEndDate = moment($scope.endDate).add(1,'day').format()
|
||||
|
||||
return unless moment($scope.formattedStartDate).isValid() and moment($scope.formattedEndDate).isValid()
|
||||
|
||||
$scope.loadOrders()
|
||||
$scope.loadLineItems()
|
||||
|
||||
unless $scope.initialized
|
||||
$scope.loadAssociatedData()
|
||||
|
||||
$scope.dereferenceLoadedData()
|
||||
|
||||
$scope.setOrderCycleDateRange = ->
|
||||
start_date = OrderCycles.byID[$scope.orderCycleFilter].orders_open_at
|
||||
end_date = OrderCycles.byID[$scope.orderCycleFilter].orders_close_at
|
||||
format = "YYYY-MM-DD HH:mm:ss Z"
|
||||
$scope.startDate = moment(start_date, format).format('YYYY-MM-DD')
|
||||
$scope.endDate = moment(end_date, format).startOf('day').format('YYYY-MM-DD')
|
||||
|
||||
$scope.loadOrders = ->
|
||||
RequestMonitor.load $scope.orders = Orders.index(
|
||||
"q[state_not_eq]": "canceled",
|
||||
"q[completed_at_not_null]": "true",
|
||||
"q[completed_at_gteq]": formatted_start_date,
|
||||
"q[completed_at_lt]": formatted_end_date
|
||||
"q[distributor_id_eq]": $scope.distributorFilter,
|
||||
"q[order_cycle_id_eq]": $scope.orderCycleFilter,
|
||||
"q[completed_at_gteq]": $scope.formattedStartDate,
|
||||
"q[completed_at_lt]": $scope.formattedEndDate
|
||||
)
|
||||
|
||||
RequestMonitor.load $scope.lineItems = LineItems.index(
|
||||
"q[order][state_not_eq]": "canceled",
|
||||
"q[order][completed_at_not_null]": "true",
|
||||
"q[order][completed_at_gteq]": formatted_start_date,
|
||||
"q[order][completed_at_lt]": formatted_end_date
|
||||
$scope.loadLineItems = ->
|
||||
RequestMonitor.load LineItems.index(
|
||||
"q[order_state_not_eq]": "canceled",
|
||||
"q[order_completed_at_not_null]": "true",
|
||||
"q[order_distributor_id_eq]": $scope.distributorFilter,
|
||||
"q[variant_product_supplier_id_eq]": $scope.supplierFilter,
|
||||
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
|
||||
"q[order_completed_at_gteq]": $scope.formattedStartDate,
|
||||
"q[order_completed_at_lt]": $scope.formattedEndDate
|
||||
)
|
||||
|
||||
unless $scope.initialized
|
||||
RequestMonitor.load $scope.distributors = Enterprises.index(action: "visible", ams_prefix: "basic", "q[sells_in][]": ["own", "any"])
|
||||
RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{moment().subtract(90,'days').format()}")
|
||||
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
|
||||
$scope.loadAssociatedData = ->
|
||||
RequestMonitor.load $scope.distributors = Enterprises.index(action: "visible", ams_prefix: "basic", "q[sells_in][]": ["own", "any"])
|
||||
RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{moment().subtract(90,'days').format()}")
|
||||
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
|
||||
|
||||
RequestMonitor.load $q.all([$scope.orders.$promise, $scope.distributors.$promise, $scope.orderCycles.$promise, $scope.suppliers.$promise, $scope.lineItems.$promise]).then ->
|
||||
$scope.dereferenceLoadedData = ->
|
||||
RequestMonitor.load $q.all([$scope.orders.$promise, $scope.distributors.$promise, $scope.orderCycles.$promise, $scope.suppliers.$promise, $scope.line_items.$promise]).then ->
|
||||
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID
|
||||
Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.byID
|
||||
Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.byID
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "supplier", Enterprises.byID
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
|
||||
$scope.bulk_order_form.$setPristine()
|
||||
StatusMessage.clear()
|
||||
|
||||
unless $scope.initialized
|
||||
$scope.initialized = true
|
||||
$timeout ->
|
||||
$scope.resetSelectFilters()
|
||||
|
||||
$scope.$watch 'bulk_order_form.$dirty', (newVal, oldVal) ->
|
||||
if newVal == true
|
||||
@@ -77,13 +104,12 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
|
||||
$scope.deleteLineItem = (lineItem) ->
|
||||
if ($scope.confirmDelete && confirm(t "are_you_sure")) || !$scope.confirmDelete
|
||||
LineItems.delete lineItem, =>
|
||||
$scope.lineItems.splice $scope.lineItems.indexOf(lineItem), 1
|
||||
LineItems.delete lineItem
|
||||
|
||||
$scope.deleteLineItems = (lineItems) ->
|
||||
$scope.deleteLineItems = (lineItemsToDelete) ->
|
||||
existingState = $scope.confirmDelete
|
||||
$scope.confirmDelete = false
|
||||
$scope.deleteLineItem lineItem for lineItem in lineItems when lineItem.checked
|
||||
$scope.deleteLineItem lineItem for lineItem in lineItemsToDelete when lineItem.checked
|
||||
$scope.confirmDelete = existingState
|
||||
|
||||
$scope.allBoxesChecked = ->
|
||||
@@ -154,4 +180,5 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
lineItem.final_weight_volume = LineItems.pristineByID[lineItem.id].final_weight_volume * lineItem.quantity / LineItems.pristineByID[lineItem.id].quantity
|
||||
$scope.weightAdjustedPrice(lineItem)
|
||||
|
||||
$scope.resetFilters()
|
||||
$scope.refreshData()
|
||||
|
||||
@@ -14,9 +14,10 @@ angular.module("admin.payments").factory 'AdminStripeElements', ($rootScope, Sta
|
||||
@stripe.createToken(@card, cardData).then (response) =>
|
||||
if(response.error)
|
||||
StatusMessage.display 'error', response.error.message
|
||||
console.error(JSON.stringify(response.error))
|
||||
else
|
||||
secrets.token = response.token.id
|
||||
secrets.cc_type = @mapCC(response.token.card.brand)
|
||||
secrets.cc_type = @mapTokenApiCardBrand(response.token.card.brand)
|
||||
secrets.card = response.token.card
|
||||
submit()
|
||||
|
||||
@@ -29,15 +30,16 @@ angular.module("admin.payments").factory 'AdminStripeElements', ($rootScope, Sta
|
||||
@stripe.createPaymentMethod({ type: 'card', card: @card }, @card, cardData).then (response) =>
|
||||
if(response.error)
|
||||
StatusMessage.display 'error', response.error.message
|
||||
console.error(JSON.stringify(response.error))
|
||||
else
|
||||
secrets.token = response.paymentMethod.id
|
||||
secrets.cc_type = response.paymentMethod.card.brand
|
||||
secrets.cc_type = @mapPaymentMethodsApiCardBrand(response.paymentMethod.card.brand)
|
||||
secrets.card = response.paymentMethod.card
|
||||
submit()
|
||||
|
||||
# Maps the brand returned by Stripe to that required by activemerchant
|
||||
mapCC: (ccType) ->
|
||||
switch ccType
|
||||
# Maps the brand returned by Stripe's tokenAPI to that required by activemerchant
|
||||
mapTokenApiCardBrand: (cardBrand) ->
|
||||
switch cardBrand
|
||||
when 'MasterCard' then return 'master'
|
||||
when 'Visa' then return 'visa'
|
||||
when 'American Express' then return 'american_express'
|
||||
@@ -45,6 +47,14 @@ angular.module("admin.payments").factory 'AdminStripeElements', ($rootScope, Sta
|
||||
when 'JCB' then return 'jcb'
|
||||
when 'Diners Club' then return 'diners_club'
|
||||
|
||||
# Maps the brand returned by Stripe's paymentMethodsAPI to that required by activemerchant
|
||||
mapPaymentMethodsApiCardBrand: (cardBrand) ->
|
||||
switch cardBrand
|
||||
when 'mastercard' then return 'master'
|
||||
when 'amex' then return 'american_express'
|
||||
when 'diners' then return 'diners_club'
|
||||
else return cardBrand # a few brands are equal, for example, visa
|
||||
|
||||
# It doesn't matter if any of these are nil, all are optional.
|
||||
makeCardData: (secrets) ->
|
||||
{'name': secrets.name,
|
||||
|
||||
@@ -2,7 +2,6 @@ angular.module("admin.resources").factory 'LineItemResource', ($resource) ->
|
||||
$resource('/admin/bulk_line_items/:id.json', {}, {
|
||||
'index':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
'update':
|
||||
method: 'PUT'
|
||||
transformRequest: (data, headersGetter) =>
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
angular.module("admin.resources").factory 'LineItems', ($q, LineItemResource) ->
|
||||
new class LineItems
|
||||
all: []
|
||||
byID: {}
|
||||
pristineByID: {}
|
||||
pagination: {}
|
||||
|
||||
index: (params={}, callback=null) ->
|
||||
LineItemResource.index params, (data) =>
|
||||
request = LineItemResource.index params, (data) =>
|
||||
@load(data)
|
||||
(callback || angular.noop)(data)
|
||||
@all.$promise = request.$promise
|
||||
@all
|
||||
|
||||
resetData: ->
|
||||
@all.length = 0
|
||||
@byID = {}
|
||||
@pristineByID = {}
|
||||
|
||||
load: (lineItems) ->
|
||||
load: (data) ->
|
||||
angular.extend(@pagination, data.pagination)
|
||||
@resetData()
|
||||
for lineItem in lineItems
|
||||
for lineItem in data.line_items
|
||||
@all.push lineItem
|
||||
@byID[lineItem.id] = lineItem
|
||||
@pristineByID[lineItem.id] = angular.copy(lineItem)
|
||||
|
||||
@@ -25,8 +32,9 @@ angular.module("admin.resources").factory 'LineItems', ($q, LineItemResource) ->
|
||||
|
||||
save: (lineItem) ->
|
||||
deferred = $q.defer()
|
||||
lineItemResource = new LineItemResource(lineItem)
|
||||
lineItem.errors = {}
|
||||
lineItem.$update({id: lineItem.id})
|
||||
lineItemResource.$update({id: lineItem.id})
|
||||
.then( (data) =>
|
||||
@pristineByID[lineItem.id] = angular.copy(lineItem)
|
||||
deferred.resolve(data)
|
||||
@@ -54,8 +62,10 @@ angular.module("admin.resources").factory 'LineItems', ($q, LineItemResource) ->
|
||||
|
||||
delete: (lineItem, callback=null) ->
|
||||
deferred = $q.defer()
|
||||
lineItem.$delete({id: lineItem.id})
|
||||
lineItemResource = new LineItemResource(lineItem)
|
||||
lineItemResource.$delete({id: lineItem.id})
|
||||
.then( (data) =>
|
||||
@all.splice(@all.indexOf(lineItem),1)
|
||||
delete @byID[lineItem.id]
|
||||
delete @pristineByID[lineItem.id]
|
||||
(callback || angular.noop)(data)
|
||||
|
||||
@@ -43,12 +43,11 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
|
||||
|
||||
$scope.fetchProducts = ->
|
||||
url = "/api/products/overridable?page=::page::;per_page=100"
|
||||
PagedFetcher.fetch url, (data) => $scope.addProducts data.products
|
||||
PagedFetcher.fetch url, $scope.addProducts
|
||||
|
||||
|
||||
$scope.addProducts = (products) ->
|
||||
$scope.products = $scope.products.concat products
|
||||
VariantOverrides.ensureDataFor hubs, products
|
||||
$scope.addProducts = (data) ->
|
||||
$scope.products = $scope.products.concat data.products
|
||||
VariantOverrides.ensureDataFor hubs, data.products
|
||||
|
||||
$scope.displayDirty = ->
|
||||
if DirtyVariantOverrides.count() > 0
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#= require angular-animate
|
||||
#= require angular-resource
|
||||
#= require lodash.underscore.js
|
||||
# bluebird.js is a dependency of angular-google-maps.js 2.0.0
|
||||
#= require bluebird.js
|
||||
#= require angular-scroll.min.js
|
||||
#= require angular-google-maps.min.js
|
||||
#= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js
|
||||
|
||||
@@ -9,8 +9,13 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, $location
|
||||
$scope.show_closed = false
|
||||
$scope.filtersActive = false
|
||||
$scope.distanceMatchesShown = false
|
||||
$scope.closed_shops_loading = false
|
||||
$scope.closed_shops_loaded = false
|
||||
|
||||
$scope.$watch "query", (query)->
|
||||
$scope.resetSearch(query)
|
||||
|
||||
$scope.resetSearch = (query) ->
|
||||
Enterprises.flagMatching query
|
||||
Search.search query
|
||||
$rootScope.$broadcast 'enterprisesChanged'
|
||||
@@ -19,6 +24,7 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, $location
|
||||
$timeout ->
|
||||
Enterprises.calculateDistance query, $scope.firstNameMatch()
|
||||
$rootScope.$broadcast 'enterprisesChanged'
|
||||
$scope.closed_shops_loading = false
|
||||
|
||||
$timeout ->
|
||||
if $location.search()['show_closed']?
|
||||
@@ -73,6 +79,12 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, $location
|
||||
undefined
|
||||
|
||||
$scope.showClosedShops = ->
|
||||
unless $scope.closed_shops_loaded
|
||||
$scope.closed_shops_loading = true
|
||||
$scope.closed_shops_loaded = true
|
||||
Enterprises.loadClosedEnterprises().then ->
|
||||
$scope.resetSearch($scope.query)
|
||||
|
||||
$scope.show_closed = true
|
||||
$location.search('show_closed', '1')
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Darkswarm.controller "HubNodeCtrl", ($scope, HashNavigation, CurrentHub, $http,
|
||||
$scope.shopfront_loading = true
|
||||
$scope.toggle_tab(event)
|
||||
|
||||
$http.get("/api/enterprises/" + $scope.hub.id + "/shopfront")
|
||||
$http.get("/api/shops/" + $scope.hub.id)
|
||||
.success (data) ->
|
||||
$scope.shopfront_loading = false
|
||||
$scope.hub = data
|
||||
|
||||
@@ -24,7 +24,7 @@ Darkswarm.controller "ProducerNodeCtrl", ($scope, HashNavigation, $anchorScroll,
|
||||
$scope.shopfront_loading = true
|
||||
$scope.toggle_tab(event)
|
||||
|
||||
$http.get("/api/enterprises/" + $scope.producer.id + "/shopfront")
|
||||
$http.get("/api/shops/" + $scope.producer.id)
|
||||
.success (data) ->
|
||||
$scope.shopfront_loading = false
|
||||
$scope.producer = data
|
||||
|
||||
@@ -7,7 +7,7 @@ window.Darkswarm = angular.module("Darkswarm", [
|
||||
'templates',
|
||||
'ngSanitize',
|
||||
'ngAnimate',
|
||||
'google-maps',
|
||||
'uiGmapgoogle-maps',
|
||||
'duScroll',
|
||||
'angularFileUpload',
|
||||
'angularSlideables'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Darkswarm.directive 'mapOsmTiles', ($timeout) ->
|
||||
restrict: 'E'
|
||||
require: '^googleMap'
|
||||
require: '^uiGmapGoogleMap'
|
||||
scope: {}
|
||||
link: (scope, elem, attrs, ctrl) ->
|
||||
$timeout =>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Darkswarm.directive 'mapSearch', ($timeout, Search) ->
|
||||
# Install a basic search field in a map
|
||||
restrict: 'E'
|
||||
require: ['^googleMap', 'ngModel']
|
||||
require: ['^uiGmapGoogleMap', 'ngModel']
|
||||
replace: true
|
||||
template: '<input id="pac-input" ng-model="query" placeholder="' + t('location_placeholder') + '"></input>'
|
||||
scope: {}
|
||||
|
||||
@@ -14,15 +14,28 @@ Darkswarm.factory 'Checkout', ($injector, CurrentOrder, ShippingMethods, StripeE
|
||||
|
||||
submit: =>
|
||||
Loading.message = t 'submitting_order'
|
||||
$http.put('/checkout.json', {order: @preprocess()}).success (data, status)=>
|
||||
Navigation.go data.path
|
||||
.error (response, status)=>
|
||||
if response.path
|
||||
Navigation.go response.path
|
||||
else
|
||||
Loading.clear()
|
||||
@errors = response.errors
|
||||
RailsFlashLoader.loadFlash(response.flash)
|
||||
$http.put('/checkout.json', {order: @preprocess()})
|
||||
.then (response) =>
|
||||
Navigation.go response.data.path
|
||||
.catch (response) =>
|
||||
try
|
||||
@handle_checkout_error_response(response)
|
||||
catch error
|
||||
@loadFlash(error: t("checkout.failed")) # inform the user about the unexpected error
|
||||
throw error # generate a BugsnagJS alert
|
||||
|
||||
handle_checkout_error_response: (response) =>
|
||||
if response.data.path
|
||||
Navigation.go response.data.path
|
||||
else
|
||||
throw response unless response.data.flash
|
||||
|
||||
@errors = response.data.errors
|
||||
@loadFlash(response.data.flash)
|
||||
|
||||
loadFlash: (flash) =>
|
||||
Loading.clear()
|
||||
RailsFlashLoader.loadFlash(flash)
|
||||
|
||||
# Rails wants our Spree::Address data to be provided with _attributes
|
||||
preprocess: ->
|
||||
|
||||
@@ -5,7 +5,7 @@ Darkswarm.factory "EnterpriseModal", ($modal, $rootScope, $http)->
|
||||
scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise
|
||||
scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1
|
||||
|
||||
$http.get("/api/enterprises/" + enterprise.id + "/shopfront").success (data) ->
|
||||
$http.get("/api/shops/" + enterprise.id).success (data) ->
|
||||
scope.enterprise = data
|
||||
$modal.open(templateUrl: "enterprise_modal.html", scope: scope)
|
||||
.error (data) ->
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, Matcher, Geo, $rootScope) ->
|
||||
Darkswarm.factory 'Enterprises', (enterprises, ShopsResource, CurrentHub, Taxons, Dereferencer, Matcher, Geo, $rootScope) ->
|
||||
new class Enterprises
|
||||
enterprises: []
|
||||
enterprises_by_id: {}
|
||||
|
||||
constructor: ->
|
||||
# Populate Enterprises.enterprises from json in page.
|
||||
@enterprises = enterprises
|
||||
@initEnterprises(enterprises)
|
||||
|
||||
initEnterprises: (enterprises) ->
|
||||
# Map enterprises to id/object pairs for lookup.
|
||||
for enterprise in enterprises
|
||||
@enterprises.push enterprise
|
||||
@enterprises_by_id[enterprise.id] = enterprise
|
||||
|
||||
# Replace enterprise and taxons ids with actual objects.
|
||||
@dereferenceEnterprises()
|
||||
@dereferenceEnterprises(enterprises)
|
||||
|
||||
@producers = @enterprises.filter (enterprise)->
|
||||
enterprise.category in ["producer_hub", "producer_shop", "producer"]
|
||||
@hubs = @enterprises.filter (enterprise)->
|
||||
enterprise.category in ["hub", "hub_profile", "producer_hub", "producer_shop"]
|
||||
|
||||
dereferenceEnterprises: ->
|
||||
dereferenceEnterprises: (enteprises) ->
|
||||
if CurrentHub.hub?.id
|
||||
CurrentHub.hub = @enterprises_by_id[CurrentHub.hub.id]
|
||||
for enterprise in @enterprises
|
||||
for enterprise in enterprises
|
||||
@dereferenceEnterprise enterprise
|
||||
|
||||
dereferenceEnterprise: (enterprise) ->
|
||||
@@ -42,6 +45,12 @@ Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer,
|
||||
for enterprise in new_enterprises
|
||||
@enterprises_by_id[enterprise.id] = enterprise
|
||||
|
||||
loadClosedEnterprises: ->
|
||||
request = ShopsResource.closed_shops {}, (data) =>
|
||||
@initEnterprises(data)
|
||||
|
||||
request.$promise
|
||||
|
||||
flagMatching: (query) ->
|
||||
for enterprise in @enterprises
|
||||
enterprise.matches_name_query = if query? && query.length > 0
|
||||
@@ -50,7 +59,7 @@ Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer,
|
||||
false
|
||||
|
||||
calculateDistance: (query, firstMatching) ->
|
||||
if query?.length > 0
|
||||
if query?.length > 0 and Geo.OK
|
||||
if firstMatching?
|
||||
@setDistanceFrom firstMatching
|
||||
else
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Darkswarm.service "Geo", ->
|
||||
new class Geo
|
||||
OK: google.maps.GeocoderStatus.OK
|
||||
OK: google?.maps?.GeocoderStatus?.OK
|
||||
|
||||
# Usage:
|
||||
# Geo.geocode address, (results, status) ->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Darkswarm.factory 'OrderCycle', ($resource, orderCycleData) ->
|
||||
class OrderCycle
|
||||
@order_cycle = orderCycleData # Object or {} due to RABL
|
||||
@order_cycle = orderCycleData # Object or {}
|
||||
@push_order_cycle: (callback) ->
|
||||
new $resource("/shop/order_cycle").save {order_cycle_id: @order_cycle.order_cycle_id}, (order_data)->
|
||||
OrderCycle.order_cycle.orders_close_at = order_data.orders_close_at
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Darkswarm.factory 'ShopsResource', ($resource) ->
|
||||
$resource('/api/shops/:id.json', {}, {
|
||||
'closed_shops':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/shops/closed_shops.json'
|
||||
})
|
||||
@@ -15,9 +15,11 @@ Darkswarm.factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) ->
|
||||
if(response.error)
|
||||
Loading.clear()
|
||||
RailsFlashLoader.loadFlash({error: t("error") + ": #{response.error.message}"})
|
||||
@triggerAngularDigest()
|
||||
console.error(JSON.stringify(response.error))
|
||||
else
|
||||
secrets.token = response.token.id
|
||||
secrets.cc_type = @mapCC(response.token.card.brand)
|
||||
secrets.cc_type = @mapTokenApiCardBrand(response.token.card.brand)
|
||||
secrets.card = response.token.card
|
||||
submit()
|
||||
|
||||
@@ -32,15 +34,21 @@ Darkswarm.factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) ->
|
||||
if(response.error)
|
||||
Loading.clear()
|
||||
RailsFlashLoader.loadFlash({error: t("error") + ": #{response.error.message}"})
|
||||
@triggerAngularDigest()
|
||||
console.error(JSON.stringify(response.error))
|
||||
else
|
||||
secrets.token = response.paymentMethod.id
|
||||
secrets.cc_type = response.paymentMethod.card.brand
|
||||
secrets.cc_type = @mapPaymentMethodsApiCardBrand(response.paymentMethod.card.brand)
|
||||
secrets.card = response.paymentMethod.card
|
||||
submit()
|
||||
|
||||
# Maps the brand returned by Stripe to that required by activemerchant
|
||||
mapCC: (ccType) ->
|
||||
switch ccType
|
||||
triggerAngularDigest: ->
|
||||
# $evalAsync is improved way of triggering a digest without calling $apply
|
||||
$rootScope.$evalAsync()
|
||||
|
||||
# Maps the brand returned by Stripe's tokenAPI to that required by activemerchant
|
||||
mapTokenApiCardBrand: (cardBrand) ->
|
||||
switch cardBrand
|
||||
when 'MasterCard' then return 'master'
|
||||
when 'Visa' then return 'visa'
|
||||
when 'American Express' then return 'american_express'
|
||||
@@ -48,6 +56,14 @@ Darkswarm.factory 'StripeElements', ($rootScope, Loading, RailsFlashLoader) ->
|
||||
when 'JCB' then return 'jcb'
|
||||
when 'Diners Club' then return 'diners_club'
|
||||
|
||||
# Maps the brand returned by Stripe's paymentMethodsAPI to that required by activemerchant
|
||||
mapPaymentMethodsApiCardBrand: (cardBrand) ->
|
||||
switch cardBrand
|
||||
when 'mastercard' then return 'master'
|
||||
when 'amex' then return 'american_express'
|
||||
when 'diners' then return 'diners_club'
|
||||
else return cardBrand # a few brands are equal, for example, visa
|
||||
|
||||
# It doesn't matter if any of these are nil, all are optional.
|
||||
makeCardData: (secrets) ->
|
||||
{'name': secrets.name,
|
||||
|
||||
@@ -109,9 +109,4 @@ checkout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #c82020;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,5 +14,10 @@
|
||||
|
||||
.more-controls {
|
||||
text-align: center;
|
||||
|
||||
.spinner {
|
||||
height: 2.25em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,17 @@ module Admin
|
||||
#
|
||||
def index
|
||||
order_params = params[:q].andand.delete :order
|
||||
orders = order_permissions.editable_orders.ransack(order_params).result
|
||||
|
||||
order_permissions = ::Permissions::Order.new(spree_current_user)
|
||||
orders = order_permissions.
|
||||
editable_orders.ransack(order_params).result
|
||||
|
||||
line_items = order_permissions.
|
||||
@line_items = order_permissions.
|
||||
editable_line_items.where(order_id: orders).
|
||||
includes(variant: { option_values: :option_type }).
|
||||
ransack(params[:q]).result.
|
||||
reorder('spree_line_items.order_id ASC, spree_line_items.id ASC')
|
||||
|
||||
render_as_json line_items
|
||||
@line_items = @line_items.page(page).per(params[:per_page]) if using_pagination?
|
||||
|
||||
render json: { line_items: serialized_line_items, pagination: pagination_data }
|
||||
end
|
||||
|
||||
# PUT /admin/bulk_line_items/:id.json
|
||||
@@ -65,6 +64,12 @@ module Admin
|
||||
Api::Admin::LineItemSerializer
|
||||
end
|
||||
|
||||
def serialized_line_items
|
||||
ActiveModel::ArraySerializer.new(
|
||||
@line_items, each_serializer: serializer(nil)
|
||||
)
|
||||
end
|
||||
|
||||
def authorize_update!
|
||||
authorize! :update, order
|
||||
authorize! :read, order
|
||||
@@ -73,5 +78,28 @@ module Admin
|
||||
def order
|
||||
@line_item.order
|
||||
end
|
||||
|
||||
def order_permissions
|
||||
::Permissions::Order.new(spree_current_user)
|
||||
end
|
||||
|
||||
def using_pagination?
|
||||
params[:per_page]
|
||||
end
|
||||
|
||||
def pagination_data
|
||||
return unless using_pagination?
|
||||
|
||||
{
|
||||
results: @line_items.total_count,
|
||||
pages: @line_items.num_pages,
|
||||
page: page.to_i,
|
||||
per_page: params[:per_page].to_i
|
||||
}
|
||||
end
|
||||
|
||||
def page
|
||||
params[:page] || 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,8 +17,9 @@ module Admin
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: params[:enterprise_id]))
|
||||
render_as_json @collection, tag_rule_mapping: tag_rule_mapping
|
||||
render_as_json @collection,
|
||||
tag_rule_mapping: tag_rule_mapping,
|
||||
customer_tags: customer_tags_by_id
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -64,8 +65,13 @@ module Admin
|
||||
def collection
|
||||
return Customer.where("1=0") unless json_request? && params[:enterprise_id].present?
|
||||
|
||||
enterprise = Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id])
|
||||
Customer.of(enterprise)
|
||||
Customer.of(managed_enterprise_id).
|
||||
includes(:bill_address, :ship_address, user: :credit_cards)
|
||||
end
|
||||
|
||||
def managed_enterprise_id
|
||||
@managed_enterprise_id ||= Enterprise.managed_by(spree_current_user).
|
||||
select('enterprises.id').find_by_id(params[:enterprise_id])
|
||||
end
|
||||
|
||||
def load_managed_shops
|
||||
@@ -80,5 +86,28 @@ module Admin
|
||||
def ams_prefix_whitelist
|
||||
[:subscription]
|
||||
end
|
||||
|
||||
def tag_rule_mapping
|
||||
TagRule.mapping_for(Enterprise.where(id: managed_enterprise_id))
|
||||
end
|
||||
|
||||
# Fetches tags for all customers of the enterprise and returns a hash indexed by customer_id
|
||||
def customer_tags_by_id
|
||||
customer_tags = ::ActsAsTaggableOn::Tag.
|
||||
joins(:taggings).
|
||||
includes(:taggings).
|
||||
where(taggings:
|
||||
{ taggable_type: 'Customer',
|
||||
taggable_id: Customer.of(managed_enterprise_id),
|
||||
context: 'tags' })
|
||||
|
||||
customer_tags.each_with_object({}) do |tag, indexed_hash|
|
||||
tag.taggings.each do |tagging|
|
||||
customer_id = tagging.taggable_id
|
||||
indexed_hash[customer_id] ||= []
|
||||
indexed_hash[customer_id] << tag.name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,7 +16,7 @@ module Admin
|
||||
render_as_json @collection,
|
||||
ams_prefix: params[:ams_prefix],
|
||||
current_user: spree_current_user,
|
||||
subscriptions_count: SubscriptionsCount.new(@collection)
|
||||
subscriptions_count: OrderManagement::Subscriptions::Count.new(@collection)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -74,7 +74,7 @@ module Admin
|
||||
render_as_json @order_cycles,
|
||||
ams_prefix: 'index',
|
||||
current_user: spree_current_user,
|
||||
subscriptions_count: SubscriptionsCount.new(@collection)
|
||||
subscriptions_count: OrderManagement::Subscriptions::Count.new(@collection)
|
||||
else
|
||||
order_cycle = order_cycle_set.collection.find{ |oc| oc.errors.present? }
|
||||
render json: { errors: order_cycle.errors.full_messages }, status: :unprocessable_entity
|
||||
|
||||
@@ -9,25 +9,19 @@ module Admin
|
||||
|
||||
def cancel
|
||||
if @proxy_order.cancel
|
||||
respond_with(@proxy_order) do |format|
|
||||
format.json { render_as_json @proxy_order }
|
||||
end
|
||||
render_as_json @proxy_order
|
||||
else
|
||||
respond_with(@proxy_order) do |format|
|
||||
format.json { render json: { errors: [t('admin.proxy_orders.cancel.could_not_cancel_the_order')] }, status: :unprocessable_entity }
|
||||
end
|
||||
render json: { errors: [t('admin.proxy_orders.cancel.could_not_cancel_the_order')] },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def resume
|
||||
if @proxy_order.resume
|
||||
respond_with(@proxy_order) do |format|
|
||||
format.json { render_as_json @proxy_order }
|
||||
end
|
||||
render_as_json @proxy_order
|
||||
else
|
||||
respond_with(@proxy_order) do |format|
|
||||
format.json { render json: { errors: [t('admin.proxy_orders.resume.could_not_resume_the_order')] }, status: :unprocessable_entity }
|
||||
end
|
||||
render json: { errors: [t('admin.proxy_orders.resume.could_not_resume_the_order')] },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
require 'open_food_network/permissions'
|
||||
require 'open_food_network/proxy_order_syncer'
|
||||
require 'order_management/subscriptions/proxy_order_syncer'
|
||||
|
||||
module Admin
|
||||
class SchedulesController < ResourceController
|
||||
@@ -81,7 +81,7 @@ module Admin
|
||||
return unless removed_ids.any? || new_ids.any?
|
||||
|
||||
subscriptions = Subscription.where(schedule_id: @schedule)
|
||||
syncer = OpenFoodNetwork::ProxyOrderSyncer.new(subscriptions)
|
||||
syncer = OrderManagement::Subscriptions::ProxyOrderSyncer.new(subscriptions)
|
||||
syncer.sync!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@ module Admin
|
||||
end
|
||||
|
||||
def variant_if_eligible(variant_id)
|
||||
SubscriptionVariantsService.eligible_variants(@shop).find_by_id(variant_id)
|
||||
OrderManagement::Subscriptions::VariantsList.eligible_variants(@shop).find_by_id(variant_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
require 'open_food_network/permissions'
|
||||
require 'open_food_network/proxy_order_syncer'
|
||||
|
||||
module Admin
|
||||
class SubscriptionsController < ResourceController
|
||||
@@ -65,7 +64,7 @@ module Admin
|
||||
private
|
||||
|
||||
def save_form_and_render(render_issues = true)
|
||||
form = SubscriptionForm.new(@subscription, params[:subscription])
|
||||
form = OrderManagement::Subscriptions::Form.new(@subscription, params[:subscription])
|
||||
unless form.save
|
||||
render json: { errors: form.json_errors }, status: :unprocessable_entity
|
||||
return
|
||||
|
||||
@@ -73,8 +73,10 @@ module Admin
|
||||
end
|
||||
|
||||
def collection
|
||||
@variant_overrides = VariantOverride.includes(:variant).for_hubs(params[:hub_id] || @hubs)
|
||||
@variant_overrides.select { |vo| vo.variant.present? }
|
||||
@variant_overrides = VariantOverride.
|
||||
includes(variant: :product).
|
||||
for_hubs(params[:hub_id] || @hubs).
|
||||
select { |vo| vo.variant.present? }
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
|
||||
@@ -5,7 +5,6 @@ module Api
|
||||
before_filter :override_sells, only: [:create, :update]
|
||||
before_filter :override_visible, only: [:create, :update]
|
||||
respond_to :json
|
||||
skip_authorization_check only: [:shopfront]
|
||||
|
||||
def create
|
||||
authorize! :create, Enterprise
|
||||
@@ -42,12 +41,6 @@ module Api
|
||||
end
|
||||
end
|
||||
|
||||
def shopfront
|
||||
enterprise = Enterprise.find_by_id(params[:id])
|
||||
|
||||
render text: Api::EnterpriseShopfrontSerializer.new(enterprise).to_json, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def override_owner
|
||||
|
||||
@@ -69,12 +69,12 @@ module Api
|
||||
end
|
||||
|
||||
def overridable
|
||||
producers = OpenFoodNetwork::Permissions.new(current_api_user).
|
||||
variant_override_producers.by_name
|
||||
producer_ids = OpenFoodNetwork::Permissions.new(current_api_user).
|
||||
variant_override_producers.by_name.select('enterprises.id')
|
||||
|
||||
@products = paged_products_for_producers producers
|
||||
@products = paged_products_for_producers producer_ids
|
||||
|
||||
render_paged_products @products
|
||||
render_paged_products @products, ::Api::Admin::ProductSimpleSerializer
|
||||
end
|
||||
|
||||
# POST /api/products/:product_id/clone
|
||||
@@ -118,19 +118,20 @@ module Api
|
||||
]
|
||||
end
|
||||
|
||||
def paged_products_for_producers(producers)
|
||||
def paged_products_for_producers(producer_ids)
|
||||
Spree::Product.scoped.
|
||||
merge(product_scope).
|
||||
where(supplier_id: producers).
|
||||
includes(variants: [:product, :default_price, :stock_items]).
|
||||
where(supplier_id: producer_ids).
|
||||
by_producer.by_name.
|
||||
ransack(params[:q]).result.
|
||||
page(params[:page]).per(params[:per_page])
|
||||
end
|
||||
|
||||
def render_paged_products(products)
|
||||
def render_paged_products(products, product_serializer = ::Api::Admin::ProductSerializer)
|
||||
serializer = ActiveModel::ArraySerializer.new(
|
||||
products,
|
||||
each_serializer: ::Api::Admin::ProductSerializer
|
||||
each_serializer: product_serializer
|
||||
)
|
||||
|
||||
render text: {
|
||||
|
||||
27
app/controllers/api/shops_controller.rb
Normal file
27
app/controllers/api/shops_controller.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
class ShopsController < BaseController
|
||||
respond_to :json
|
||||
skip_authorization_check only: [:show, :closed_shops]
|
||||
|
||||
def show
|
||||
enterprise = Enterprise.find_by_id(params[:id])
|
||||
|
||||
render text: Api::EnterpriseShopfrontSerializer.new(enterprise).to_json, status: :ok
|
||||
end
|
||||
|
||||
def closed_shops
|
||||
@active_distributor_ids = []
|
||||
@earliest_closing_times = []
|
||||
|
||||
serialized_closed_shops = ActiveModel::ArraySerializer.new(
|
||||
ShopsListService.new.closed_shops,
|
||||
each_serializer: Api::EnterpriseSerializer,
|
||||
data: OpenFoodNetwork::EnterpriseInjectionData.new
|
||||
)
|
||||
|
||||
render json: serialized_closed_shops
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -29,17 +29,19 @@ class BaseController < ApplicationController
|
||||
return
|
||||
end
|
||||
|
||||
@order_cycles = OrderCycle.with_distributor(@distributor).active
|
||||
.order(@distributor.preferred_shopfront_order_cycle_order)
|
||||
@order_cycles = Shop::OrderCyclesList.new(@distributor, current_customer).call
|
||||
|
||||
applicator = OpenFoodNetwork::TagRuleApplicator.new(@distributor,
|
||||
"FilterOrderCycles",
|
||||
current_customer.andand.tag_list)
|
||||
applicator.filter!(@order_cycles)
|
||||
set_order_cycle
|
||||
end
|
||||
|
||||
# And default to the only order cycle if there's only the one
|
||||
if @order_cycles.count == 1
|
||||
current_order(true).set_order_cycle! @order_cycles.first
|
||||
end
|
||||
# Default to the only order cycle if there's only one
|
||||
#
|
||||
# Here we need to use @order_cycles.size not @order_cycles.count
|
||||
# because OrderCyclesList returns a modified ActiveRecord::Relation
|
||||
# and these modifications are not seen if it is reloaded with count
|
||||
def set_order_cycle
|
||||
return if @order_cycles.size != 1
|
||||
|
||||
current_order(true).set_order_cycle! @order_cycles.first
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,9 +53,8 @@ class CheckoutController < Spree::StoreController
|
||||
rescue Spree::Core::GatewayError => e
|
||||
rescue_from_spree_gateway_error(e)
|
||||
rescue StandardError => e
|
||||
Bugsnag.notify(e)
|
||||
flash[:error] = I18n.t("checkout.failed")
|
||||
update_failed
|
||||
update_failed(e)
|
||||
end
|
||||
|
||||
# Clears the cached order. Required for #current_order to return a new order
|
||||
@@ -165,7 +164,7 @@ class CheckoutController < Spree::StoreController
|
||||
checkout_succeeded
|
||||
redirect_to(order_path(@order)) && return
|
||||
else
|
||||
flash[:error] = order_workflow_error
|
||||
flash[:error] = order_error
|
||||
checkout_failed
|
||||
end
|
||||
end
|
||||
@@ -180,7 +179,6 @@ class CheckoutController < Spree::StoreController
|
||||
|
||||
next if advance_order_state(@order)
|
||||
|
||||
flash[:error] = order_workflow_error
|
||||
return update_failed
|
||||
end
|
||||
|
||||
@@ -205,7 +203,7 @@ class CheckoutController < Spree::StoreController
|
||||
false
|
||||
end
|
||||
|
||||
def order_workflow_error
|
||||
def order_error
|
||||
if @order.errors.present?
|
||||
@order.errors.full_messages.to_sentence
|
||||
else
|
||||
@@ -218,7 +216,7 @@ class CheckoutController < Spree::StoreController
|
||||
checkout_succeeded
|
||||
update_succeeded_response
|
||||
else
|
||||
update_failed
|
||||
update_failed(RuntimeError.new("Order not complete after the checkout workflow"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -244,7 +242,10 @@ class CheckoutController < Spree::StoreController
|
||||
end
|
||||
end
|
||||
|
||||
def update_failed
|
||||
def update_failed(error = RuntimeError.new(order_error))
|
||||
Bugsnag.notify(error)
|
||||
|
||||
flash[:error] = order_error if flash.empty?
|
||||
checkout_failed
|
||||
update_failed_response
|
||||
end
|
||||
|
||||
@@ -96,8 +96,8 @@ class EnterprisesController < BaseController
|
||||
end
|
||||
|
||||
def reset_order_cycle(order, distributor)
|
||||
order_cycle_options = OrderCycle.active.with_distributor(distributor)
|
||||
order.order_cycle = order_cycle_options.first if order_cycle_options.count == 1
|
||||
order_cycles = Shop::OrderCyclesList.new(distributor, current_customer).call
|
||||
order.order_cycle = order_cycles.first if order_cycles.size == 1
|
||||
end
|
||||
|
||||
def shop_order_cycles
|
||||
|
||||
@@ -5,12 +5,23 @@ class HomeController < BaseController
|
||||
|
||||
def index
|
||||
if ContentConfig.home_show_stats
|
||||
@num_distributors = Enterprise.is_distributor.activated.visible.count
|
||||
@num_producers = Enterprise.is_primary_producer.activated.visible.count
|
||||
@num_users = Spree::Order.complete.count('DISTINCT user_id')
|
||||
@num_orders = Spree::Order.complete.count
|
||||
@num_distributors = cached_count('distributors', Enterprise.is_distributor.activated.visible)
|
||||
@num_producers = cached_count('producers', Enterprise.is_primary_producer.activated.visible)
|
||||
@num_orders = cached_count('orders', Spree::Order.complete)
|
||||
@num_users = cached_count(
|
||||
'users', Spree::Order.complete.select('DISTINCT spree_orders.user_id')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def sell; end
|
||||
|
||||
private
|
||||
|
||||
# Cache the value of the query count for 24 hours
|
||||
def cached_count(key, query)
|
||||
Rails.cache.fetch("home_stats_count_#{key}", expires_in: 1.day, race_condition_ttl: 10) do
|
||||
query.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,13 +4,6 @@ class ShopsController < BaseController
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
def index
|
||||
@enterprises = Enterprise
|
||||
.activated
|
||||
.visible
|
||||
.is_distributor
|
||||
.includes(address: [:state, :country])
|
||||
.includes(:properties)
|
||||
.includes(supplied_products: :properties)
|
||||
.all
|
||||
@enterprises = ShopsListService.new.open_shops
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,14 +3,6 @@ module Spree
|
||||
class TaxonsController < Spree::Admin::BaseController
|
||||
respond_to :html, :json, :js
|
||||
|
||||
def search
|
||||
@taxons = if params[:ids]
|
||||
Spree::Taxon.where(id: params[:ids].split(','))
|
||||
else
|
||||
Spree::Taxon.limit(20).search(name_cont: params[:q]).result
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.build(params[:taxon])
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
module ApplicationHelper
|
||||
include FoundationRailsHelper::FlashHelper
|
||||
|
||||
def feature?(feature)
|
||||
OpenFoodNetwork::FeatureToggle.enabled? feature
|
||||
end
|
||||
|
||||
@@ -47,17 +47,6 @@ module OrderCyclesHelper
|
||||
end
|
||||
end
|
||||
|
||||
def order_cycle_options
|
||||
@order_cycles.
|
||||
with_distributor(current_distributor).
|
||||
map { |oc| [order_cycle_close_to_s(oc.orders_close_at), oc.id] }
|
||||
end
|
||||
|
||||
def order_cycle_close_to_s(orders_close_at)
|
||||
"%s (%s)" % [orders_close_at.strftime("#{orders_close_at.day.ordinalize} %b"),
|
||||
distance_of_time_in_words_to_now(orders_close_at)]
|
||||
end
|
||||
|
||||
def active_order_cycle_for_distributor?(_distributor)
|
||||
OrderCycle.active.with_distributor(@distributor).present?
|
||||
end
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
require 'open_food_network/subscription_payment_updater'
|
||||
require 'open_food_network/subscription_summarizer'
|
||||
require 'order_management/subscriptions/summarizer'
|
||||
|
||||
# Confirms orders of unconfirmed proxy orders in recently closed Order Cycles
|
||||
class SubscriptionConfirmJob
|
||||
def perform
|
||||
ids = proxy_orders.pluck(:id)
|
||||
proxy_orders.update_all(confirmed_at: Time.zone.now)
|
||||
ProxyOrder.where(id: ids).each do |proxy_order|
|
||||
Rails.logger.info "Confirming Order for Proxy Order #{proxy_order.id}"
|
||||
@order = proxy_order.order
|
||||
process!
|
||||
end
|
||||
|
||||
send_confirmation_summary_emails
|
||||
confirm_proxy_orders!
|
||||
end
|
||||
|
||||
private
|
||||
@@ -20,10 +12,26 @@ class SubscriptionConfirmJob
|
||||
delegate :record_and_log_error, :send_confirmation_summary_emails, to: :summarizer
|
||||
|
||||
def summarizer
|
||||
@summarizer ||= OpenFoodNetwork::SubscriptionSummarizer.new
|
||||
@summarizer ||= OrderManagement::Subscriptions::Summarizer.new
|
||||
end
|
||||
|
||||
def proxy_orders
|
||||
def confirm_proxy_orders!
|
||||
# Fetch all unconfirmed proxy orders
|
||||
unconfirmed_proxy_orders_ids = unconfirmed_proxy_orders.pluck(:id)
|
||||
|
||||
# Mark these proxy orders as confirmed
|
||||
unconfirmed_proxy_orders.update_all(confirmed_at: Time.zone.now)
|
||||
|
||||
# Confirm these proxy orders
|
||||
ProxyOrder.where(id: unconfirmed_proxy_orders_ids).each do |proxy_order|
|
||||
Rails.logger.info "Confirming Order for Proxy Order #{proxy_order.id}"
|
||||
confirm_order!(proxy_order.order)
|
||||
end
|
||||
|
||||
send_confirmation_summary_emails
|
||||
end
|
||||
|
||||
def unconfirmed_proxy_orders
|
||||
ProxyOrder.not_canceled.where('confirmed_at IS NULL AND placed_at IS NOT NULL')
|
||||
.joins(:order_cycle).merge(recently_closed_order_cycles)
|
||||
.joins(:order).merge(Spree::Order.complete.not_state('canceled'))
|
||||
@@ -33,30 +41,55 @@ class SubscriptionConfirmJob
|
||||
OrderCycle.closed.where('order_cycles.orders_close_at BETWEEN (?) AND (?) OR order_cycles.updated_at BETWEEN (?) AND (?)', 1.hour.ago, Time.zone.now, 1.hour.ago, Time.zone.now)
|
||||
end
|
||||
|
||||
def process!
|
||||
record_order(@order)
|
||||
update_payment! if @order.payment_required?
|
||||
return send_failed_payment_email if @order.errors.present?
|
||||
# It sets up payments, processes payments and sends confirmation emails
|
||||
def confirm_order!(order)
|
||||
record_order(order)
|
||||
|
||||
@order.process_payments! if @order.payment_required?
|
||||
return send_failed_payment_email if @order.errors.present?
|
||||
|
||||
send_confirm_email
|
||||
if process_payment!(order)
|
||||
send_confirmation_email(order)
|
||||
else
|
||||
send_failed_payment_email(order)
|
||||
end
|
||||
end
|
||||
|
||||
def update_payment!
|
||||
OpenFoodNetwork::SubscriptionPaymentUpdater.new(@order).update!
|
||||
def process_payment!(order)
|
||||
return false if order.errors.present?
|
||||
return true unless order.payment_required?
|
||||
|
||||
setup_payment!(order)
|
||||
return false if order.errors.any?
|
||||
|
||||
authorize_payment!(order)
|
||||
return false if order.errors.any?
|
||||
|
||||
order.process_payments!
|
||||
return false if order.errors.any?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def send_confirm_email
|
||||
@order.update!
|
||||
record_success(@order)
|
||||
SubscriptionMailer.confirmation_email(@order).deliver
|
||||
def setup_payment!(order)
|
||||
OrderManagement::Subscriptions::PaymentSetup.new(order).call!
|
||||
return if order.errors.any?
|
||||
|
||||
OrderManagement::Subscriptions::StripePaymentSetup.new(order).call!
|
||||
end
|
||||
|
||||
def send_failed_payment_email
|
||||
@order.update!
|
||||
record_and_log_error(:failed_payment, @order)
|
||||
SubscriptionMailer.failed_payment_email(@order).deliver
|
||||
def authorize_payment!(order)
|
||||
return if order.subscription.payment_method.class != Spree::Gateway::StripeSCA
|
||||
|
||||
OrderManagement::Subscriptions::StripeScaPaymentAuthorize.new(order).call!
|
||||
end
|
||||
|
||||
def send_confirmation_email(order)
|
||||
order.update!
|
||||
record_success(order)
|
||||
SubscriptionMailer.confirmation_email(order).deliver
|
||||
end
|
||||
|
||||
def send_failed_payment_email(order)
|
||||
order.update!
|
||||
record_and_log_error(:failed_payment, order)
|
||||
SubscriptionMailer.failed_payment_email(order).deliver
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
require 'open_food_network/subscription_summarizer'
|
||||
require 'order_management/subscriptions/summarizer'
|
||||
|
||||
class SubscriptionPlacementJob
|
||||
def perform
|
||||
@@ -17,7 +17,7 @@ class SubscriptionPlacementJob
|
||||
delegate :record_and_log_error, :send_placement_summary_emails, to: :summarizer
|
||||
|
||||
def summarizer
|
||||
@summarizer ||= OpenFoodNetwork::SubscriptionSummarizer.new
|
||||
@summarizer ||= OrderManagement::Subscriptions::Summarizer.new
|
||||
end
|
||||
|
||||
def proxy_orders
|
||||
|
||||
@@ -1,41 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProducerMailer < Spree::BaseMailer
|
||||
include I18nHelper
|
||||
|
||||
def order_cycle_report(producer, order_cycle)
|
||||
@producer = producer
|
||||
@coordinator = order_cycle.coordinator
|
||||
@order_cycle = order_cycle
|
||||
line_items = line_items_from(@order_cycle, @producer)
|
||||
@grouped_line_items = line_items.group_by(&:product_and_full_name)
|
||||
@receival_instructions = @order_cycle.receival_instructions_for @producer
|
||||
@total = total_from_line_items(line_items)
|
||||
@tax_total = tax_total_from_line_items(line_items)
|
||||
|
||||
I18n.with_locale valid_locale(@producer.owner) do
|
||||
order_cycle_subject = I18n.t('producer_mailer.order_cycle.subject', producer: producer.name)
|
||||
subject = "[#{Spree::Config.site_name}] #{order_cycle_subject}"
|
||||
with_unscoped_products_and_variants do
|
||||
load_data
|
||||
|
||||
return unless has_orders?(order_cycle, producer)
|
||||
I18n.with_locale(owner_locale) do
|
||||
return unless orders?(order_cycle, producer)
|
||||
|
||||
mail(
|
||||
to: @producer.contact.email,
|
||||
from: from_address,
|
||||
subject: subject,
|
||||
reply_to: @coordinator.contact.email,
|
||||
cc: @coordinator.contact.email
|
||||
)
|
||||
mail(
|
||||
to: @producer.contact.email,
|
||||
from: from_address,
|
||||
subject: subject,
|
||||
reply_to: @coordinator.contact.email,
|
||||
cc: @coordinator.contact.email
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def has_orders?(order_cycle, producer)
|
||||
def owner_locale
|
||||
valid_locale(@producer.owner)
|
||||
end
|
||||
|
||||
def load_data
|
||||
@coordinator = @order_cycle.coordinator
|
||||
|
||||
line_items = line_items_from(@order_cycle, @producer)
|
||||
|
||||
@grouped_line_items = line_items.group_by(&:product_and_full_name)
|
||||
@receival_instructions = @order_cycle.receival_instructions_for(@producer)
|
||||
@total = total_from_line_items(line_items)
|
||||
@tax_total = tax_total_from_line_items(line_items)
|
||||
end
|
||||
|
||||
def subject
|
||||
order_cycle_subject = I18n.t('producer_mailer.order_cycle.subject', producer: @producer.name)
|
||||
"[#{Spree::Config.site_name}] #{order_cycle_subject}"
|
||||
end
|
||||
|
||||
def orders?(order_cycle, producer)
|
||||
line_items_from(order_cycle, producer).any?
|
||||
end
|
||||
|
||||
def line_items_from(order_cycle, producer)
|
||||
Spree::LineItem.
|
||||
includes(variant: { option_values: :option_type }).
|
||||
@line_items ||= Spree::LineItem.
|
||||
includes(:option_values, variant: [:product, { option_values: :option_type }]).
|
||||
from_order_cycle(order_cycle).
|
||||
sorted_by_name_and_unit_value.
|
||||
merge(Spree::Product.in_supplier(producer)).
|
||||
@@ -49,4 +66,22 @@ class ProducerMailer < Spree::BaseMailer
|
||||
def tax_total_from_line_items(line_items)
|
||||
Spree::Money.new line_items.sum(&:included_tax)
|
||||
end
|
||||
|
||||
# This hack makes ActiveRecord skip the default_scope (deleted_at IS NULL)
|
||||
# when eager loading associations. Further details:
|
||||
# https://github.com/rails/rails/issues/11036
|
||||
def with_unscoped_products_and_variants
|
||||
variant_default_scopes = Spree::Variant.default_scopes
|
||||
product_default_scopes = Spree::Product.default_scopes
|
||||
|
||||
Spree::Variant.default_scopes = []
|
||||
Spree::Product.default_scopes = []
|
||||
|
||||
return_value = yield
|
||||
|
||||
Spree::Variant.default_scopes = variant_default_scopes
|
||||
Spree::Product.default_scopes = product_default_scopes
|
||||
|
||||
return_value
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ class OrderCycle < ActiveRecord::Base
|
||||
has_many :distributors, source: :receiver, through: :cached_outgoing_exchanges, uniq: true
|
||||
|
||||
has_and_belongs_to_many :schedules, join_table: 'order_cycle_schedules'
|
||||
has_paper_trail meta: { custom_data: :schedule_ids }
|
||||
has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } }
|
||||
|
||||
attr_accessor :incoming_exchanges, :outgoing_exchanges
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Schedule < ActiveRecord::Base
|
||||
has_and_belongs_to_many :order_cycles, join_table: 'order_cycle_schedules'
|
||||
has_paper_trail meta: { custom_data: :order_cycle_ids }
|
||||
has_paper_trail meta: { custom_data: proc { |schedule| schedule.order_cycle_ids.to_s } }
|
||||
|
||||
has_many :coordinators, uniq: true, through: :order_cycles
|
||||
|
||||
|
||||
36
app/models/spree/stock/quantifier.rb
Normal file
36
app/models/spree/stock/quantifier.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Stock
|
||||
class Quantifier
|
||||
attr_reader :stock_items
|
||||
|
||||
def initialize(variant)
|
||||
@variant = variant
|
||||
@stock_items = fetch_stock_items
|
||||
end
|
||||
|
||||
def total_on_hand
|
||||
stock_items.sum(&:count_on_hand)
|
||||
end
|
||||
|
||||
def backorderable?
|
||||
stock_items.any?(&:backorderable)
|
||||
end
|
||||
|
||||
def can_supply?(required)
|
||||
total_on_hand >= required || backorderable?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_stock_items
|
||||
# Don't re-fetch associated stock items from the DB if we've already eager-loaded them
|
||||
return @variant.stock_items.to_a if @variant.stock_items.loaded?
|
||||
|
||||
Spree::StockItem.joins(:stock_location).
|
||||
where(:variant_id => @variant, Spree::StockLocation.table_name => { active: true })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -125,7 +125,12 @@ module Spree
|
||||
end
|
||||
|
||||
def default_card
|
||||
credit_cards.where(is_default: true).first
|
||||
# Don't re-fetch associated cards from the DB if they're already eager-loaded
|
||||
if credit_cards.loaded?
|
||||
credit_cards.to_a.find(&:is_default)
|
||||
else
|
||||
credit_cards.where(is_default: true).first
|
||||
end
|
||||
end
|
||||
|
||||
# Checks whether the specified user is a superadmin, with full control of the
|
||||
|
||||
@@ -15,8 +15,10 @@ class VariantOverrideSet < ModelSet
|
||||
tag_list.empty?
|
||||
end
|
||||
|
||||
# Override of ModelSet method to allow us to check presence of a tag_list (which is not an attribute)
|
||||
# This method will delete VariantOverrides that have no values (see deletable? above)
|
||||
# If the user sets all values to nil in the UI the VO will be deleted from the DB
|
||||
def collection_to_delete
|
||||
# Override of ModelSet method to allow us to check presence of a tag_list (which is not an attribute)
|
||||
deleted = []
|
||||
collection.delete_if { |e| deleted << e if @delete_if.andand.call(e.attributes, e.tag_list) }
|
||||
deleted
|
||||
|
||||
@@ -6,7 +6,7 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
|
||||
has_one :bill_address, serializer: Api::AddressSerializer
|
||||
|
||||
def tag_list
|
||||
object.tag_list.join(",")
|
||||
customer_tag_list.join(",")
|
||||
end
|
||||
|
||||
def name
|
||||
@@ -14,7 +14,7 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def tags
|
||||
object.tag_list.map do |tag|
|
||||
customer_tag_list.map do |tag|
|
||||
tag_rule_map = options[:tag_rule_mapping].andand[tag]
|
||||
tag_rule_map || { text: tag, rules: nil }
|
||||
end
|
||||
@@ -25,4 +25,12 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
|
||||
|
||||
object.user.default_card.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def customer_tag_list
|
||||
return object.tag_list unless options[:customer_tags]
|
||||
|
||||
options[:customer_tags].andand[object.id] || []
|
||||
end
|
||||
end
|
||||
|
||||
15
app/serializers/api/admin/product_simple_serializer.rb
Normal file
15
app/serializers/api/admin/product_simple_serializer.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module Admin
|
||||
class ProductSimpleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :producer_id
|
||||
|
||||
has_many :variants, key: :variants, serializer: Api::Admin::VariantSimpleSerializer
|
||||
|
||||
def producer_id
|
||||
object.supplier_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,9 +13,9 @@ module Api
|
||||
end
|
||||
|
||||
def in_open_and_upcoming_order_cycles
|
||||
SubscriptionVariantsService.in_open_and_upcoming_order_cycles?(option_or_assigned_shop,
|
||||
option_or_assigned_schedule,
|
||||
object.variant)
|
||||
OrderManagement::Subscriptions::VariantsList.in_open_and_upcoming_order_cycles?(option_or_assigned_shop,
|
||||
option_or_assigned_schedule,
|
||||
object.variant)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
32
app/serializers/api/admin/variant_simple_serializer.rb
Normal file
32
app/serializers/api/admin/variant_simple_serializer.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module Admin
|
||||
class VariantSimpleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :import_date,
|
||||
:options_text, :unit_value, :unit_description, :unit_to_display,
|
||||
:display_as, :display_name, :name_to_display,
|
||||
:price, :on_demand, :on_hand
|
||||
|
||||
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
|
||||
object.price.nil? ? 0.to_f : object.price
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -73,12 +73,16 @@ module Api
|
||||
|
||||
# This results in 3 queries per enterprise
|
||||
def distributed_properties
|
||||
return [] unless active
|
||||
|
||||
(distributed_product_properties + distributed_producer_properties).uniq do |property_object|
|
||||
property_object.property.presentation
|
||||
end
|
||||
end
|
||||
|
||||
def distributed_product_properties
|
||||
return [] unless active
|
||||
|
||||
properties = Spree::Property
|
||||
.joins(products: { variants: { exchanges: :order_cycle } })
|
||||
.merge(Exchange.outgoing)
|
||||
@@ -91,6 +95,8 @@ module Api
|
||||
end
|
||||
|
||||
def distributed_producer_properties
|
||||
return [] unless active
|
||||
|
||||
properties = Spree::Property
|
||||
.joins(
|
||||
producer_properties: {
|
||||
|
||||
@@ -12,11 +12,8 @@ module Checkout
|
||||
def path
|
||||
return unless stripe_payment_method?
|
||||
|
||||
payment = @order.pending_payments.last
|
||||
return unless payment&.checkout?
|
||||
|
||||
payment.authorize!
|
||||
raise unless payment.pending?
|
||||
payment = OrderManagement::Subscriptions::StripeScaPaymentAuthorize.new(@order).call!
|
||||
raise if @order.errors.any?
|
||||
|
||||
field_with_url(payment) if url?(field_with_url(payment))
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'open_food_network/permissions'
|
||||
require 'open_food_network/proxy_order_syncer'
|
||||
require 'open_food_network/order_cycle_form_applicator'
|
||||
require 'order_management/subscriptions/proxy_order_syncer'
|
||||
|
||||
class OrderCycleForm
|
||||
def initialize(order_cycle, params, user)
|
||||
@@ -58,7 +58,7 @@ class OrderCycleForm
|
||||
return unless schedule_ids?
|
||||
return unless schedule_sync_required?
|
||||
|
||||
OpenFoodNetwork::ProxyOrderSyncer.new(subscriptions_to_sync).sync!
|
||||
OrderManagement::Subscriptions::ProxyOrderSyncer.new(subscriptions_to_sync).sync!
|
||||
end
|
||||
|
||||
def schedule_sync_required?
|
||||
|
||||
@@ -40,7 +40,7 @@ class ProductsRenderer
|
||||
end
|
||||
|
||||
def product_scoper
|
||||
OpenFoodNetwork::ScopeProductToHub.new(distributor)
|
||||
@product_scoper ||= OpenFoodNetwork::ScopeProductToHub.new(distributor)
|
||||
end
|
||||
|
||||
def enterprise_fee_calculator
|
||||
|
||||
31
app/services/shop/order_cycles_list.rb
Normal file
31
app/services/shop/order_cycles_list.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Lists available order cycles for a given customer in a given distributor
|
||||
module Shop
|
||||
class OrderCyclesList
|
||||
def initialize(distributor, customer)
|
||||
@distributor = distributor
|
||||
@customer = customer
|
||||
end
|
||||
|
||||
def call
|
||||
order_cycles = OrderCycle.with_distributor(@distributor).active
|
||||
.order(@distributor.preferred_shopfront_order_cycle_order)
|
||||
|
||||
apply_tag_rules!(order_cycles)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# order_cycles is a ActiveRecord::Relation that is modified with reject in the TagRuleApplicator
|
||||
# If this relation is reloaded (for example by calling count on it), the modifications are lost
|
||||
def apply_tag_rules!(order_cycles)
|
||||
applicator = OpenFoodNetwork::TagRuleApplicator.new(@distributor,
|
||||
"FilterOrderCycles",
|
||||
@customer.andand.tag_list)
|
||||
applicator.filter!(order_cycles)
|
||||
|
||||
order_cycles
|
||||
end
|
||||
end
|
||||
end
|
||||
23
app/services/shops_list_service.rb
Normal file
23
app/services/shops_list_service.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ShopsListService
|
||||
def open_shops
|
||||
shops_list.ready_for_checkout.all
|
||||
end
|
||||
|
||||
def closed_shops
|
||||
shops_list.not_ready_for_checkout.all
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def shops_list
|
||||
Enterprise
|
||||
.activated
|
||||
.visible
|
||||
.is_distributor
|
||||
.includes(address: [:state, :country])
|
||||
.includes(:properties)
|
||||
.includes(supplied_products: :properties)
|
||||
end
|
||||
end
|
||||
@@ -1,63 +0,0 @@
|
||||
require 'open_food_network/scope_variant_to_hub'
|
||||
|
||||
# Responsible for estimating prices and fees for subscriptions
|
||||
# Used by SubscriptionForm as part of the create/update process
|
||||
# The values calculated here are intended to be persisted in the db
|
||||
|
||||
class SubscriptionEstimator
|
||||
def initialize(subscription)
|
||||
@subscription = subscription
|
||||
end
|
||||
|
||||
def estimate!
|
||||
assign_price_estimates
|
||||
assign_fee_estimates
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :subscription
|
||||
|
||||
delegate :subscription_line_items, :shipping_method, :payment_method, :shop, to: :subscription
|
||||
|
||||
def assign_price_estimates
|
||||
subscription_line_items.each do |item|
|
||||
item.price_estimate =
|
||||
price_estimate_for(item.variant, item.price_estimate_was)
|
||||
end
|
||||
end
|
||||
|
||||
def price_estimate_for(variant, fallback)
|
||||
return fallback unless fee_calculator && variant
|
||||
|
||||
scoper.scope(variant)
|
||||
fees = fee_calculator.indexed_fees_for(variant)
|
||||
(variant.price + fees).to_d
|
||||
end
|
||||
|
||||
def fee_calculator
|
||||
return @fee_calculator unless @fee_calculator.nil?
|
||||
|
||||
next_oc = subscription.schedule.andand.current_or_next_order_cycle
|
||||
return nil unless shop && next_oc
|
||||
|
||||
@fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(shop, next_oc)
|
||||
end
|
||||
|
||||
def scoper
|
||||
OpenFoodNetwork::ScopeVariantToHub.new(shop)
|
||||
end
|
||||
|
||||
def assign_fee_estimates
|
||||
subscription.shipping_fee_estimate = shipping_fee_estimate
|
||||
subscription.payment_fee_estimate = payment_fee_estimate
|
||||
end
|
||||
|
||||
def shipping_fee_estimate
|
||||
shipping_method.calculator.compute(subscription)
|
||||
end
|
||||
|
||||
def payment_fee_estimate
|
||||
payment_method.calculator.compute(subscription)
|
||||
end
|
||||
end
|
||||
@@ -1,34 +0,0 @@
|
||||
require 'open_food_network/proxy_order_syncer'
|
||||
|
||||
class SubscriptionForm
|
||||
attr_accessor :subscription, :params, :order_update_issues, :validator, :order_syncer, :estimator
|
||||
|
||||
delegate :json_errors, :valid?, to: :validator
|
||||
delegate :order_update_issues, to: :order_syncer
|
||||
|
||||
def initialize(subscription, params = {})
|
||||
@subscription = subscription
|
||||
@params = params
|
||||
@estimator = SubscriptionEstimator.new(subscription)
|
||||
@validator = SubscriptionValidator.new(subscription)
|
||||
@order_syncer = OrderSyncer.new(subscription)
|
||||
end
|
||||
|
||||
def save
|
||||
subscription.assign_attributes(params)
|
||||
return false unless valid?
|
||||
|
||||
subscription.transaction do
|
||||
estimator.estimate!
|
||||
proxy_order_syncer.sync!
|
||||
order_syncer.sync!
|
||||
subscription.save!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def proxy_order_syncer
|
||||
OpenFoodNetwork::ProxyOrderSyncer.new(subscription)
|
||||
end
|
||||
end
|
||||
@@ -1,127 +0,0 @@
|
||||
# Encapsulation of all of the validation logic required for subscriptions
|
||||
# Public interface consists of #valid? method provided by ActiveModel::Validations
|
||||
# and #json_errors which compiles a serializable hash of errors
|
||||
|
||||
class SubscriptionValidator
|
||||
include ActiveModel::Naming
|
||||
include ActiveModel::Conversion
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_reader :subscription
|
||||
|
||||
validates :shop, :customer, :schedule, :shipping_method, :payment_method, presence: true
|
||||
validates :bill_address, :ship_address, :begins_at, presence: true
|
||||
validate :shipping_method_allowed?
|
||||
validate :payment_method_allowed?
|
||||
validate :payment_method_type_allowed?
|
||||
validate :ends_at_after_begins_at?
|
||||
validate :customer_allowed?
|
||||
validate :schedule_allowed?
|
||||
validate :credit_card_ok?
|
||||
validate :subscription_line_items_present?
|
||||
validate :requested_variants_available?
|
||||
|
||||
delegate :shop, :customer, :schedule, :shipping_method, :payment_method, to: :subscription
|
||||
delegate :bill_address, :ship_address, :begins_at, :ends_at, to: :subscription
|
||||
delegate :subscription_line_items, to: :subscription
|
||||
|
||||
def initialize(subscription)
|
||||
@subscription = subscription
|
||||
end
|
||||
|
||||
def json_errors
|
||||
errors.messages.each_with_object({}) do |(k, v), errors|
|
||||
errors[k] = v.map { |msg| build_msg_from(k, msg) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def shipping_method_allowed?
|
||||
return unless shipping_method
|
||||
return if shipping_method.distributors.include?(shop)
|
||||
|
||||
errors.add(:shipping_method, :not_available_to_shop, shop: shop.name)
|
||||
end
|
||||
|
||||
def payment_method_allowed?
|
||||
return unless payment_method
|
||||
return if payment_method.distributors.include?(shop)
|
||||
|
||||
errors.add(:payment_method, :not_available_to_shop, shop: shop.name)
|
||||
end
|
||||
|
||||
def payment_method_type_allowed?
|
||||
return unless payment_method
|
||||
return if Subscription::ALLOWED_PAYMENT_METHOD_TYPES.include? payment_method.type
|
||||
|
||||
errors.add(:payment_method, :invalid_type)
|
||||
end
|
||||
|
||||
def ends_at_after_begins_at?
|
||||
# Only validates ends_at if it is present
|
||||
return if begins_at.blank? || ends_at.blank?
|
||||
return if ends_at > begins_at
|
||||
|
||||
errors.add(:ends_at, :after_begins_at)
|
||||
end
|
||||
|
||||
def customer_allowed?
|
||||
return unless customer
|
||||
return if customer.enterprise == shop
|
||||
|
||||
errors.add(:customer, :does_not_belong_to_shop, shop: shop.name)
|
||||
end
|
||||
|
||||
def schedule_allowed?
|
||||
return unless schedule
|
||||
return if schedule.coordinators.include?(shop)
|
||||
|
||||
errors.add(:schedule, :not_coordinated_by_shop, shop: shop.name)
|
||||
end
|
||||
|
||||
def credit_card_ok?
|
||||
return unless customer && payment_method
|
||||
return unless stripe_payment_method?(payment_method)
|
||||
return errors.add(:payment_method, :charges_not_allowed) unless customer.allow_charges
|
||||
return if customer.user.andand.default_card.present?
|
||||
|
||||
errors.add(:payment_method, :no_default_card)
|
||||
end
|
||||
|
||||
def stripe_payment_method?(payment_method)
|
||||
payment_method.type == "Spree::Gateway::StripeConnect" ||
|
||||
payment_method.type == "Spree::Gateway::StripeSCA"
|
||||
end
|
||||
|
||||
def subscription_line_items_present?
|
||||
return if subscription_line_items.reject(&:marked_for_destruction?).any?
|
||||
|
||||
errors.add(:subscription_line_items, :at_least_one_product)
|
||||
end
|
||||
|
||||
def requested_variants_available?
|
||||
subscription_line_items.each { |sli| verify_availability_of(sli.variant) }
|
||||
end
|
||||
|
||||
def verify_availability_of(variant)
|
||||
return if available_variant_ids.include? variant.id
|
||||
|
||||
name = "#{variant.product.name} - #{variant.full_name}"
|
||||
errors.add(:subscription_line_items, :not_available, name: name)
|
||||
end
|
||||
|
||||
def available_variant_ids
|
||||
return @available_variant_ids if @available_variant_ids.present?
|
||||
|
||||
subscription_variant_ids = subscription_line_items.map(&:variant_id)
|
||||
@available_variant_ids = SubscriptionVariantsService.eligible_variants(shop)
|
||||
.where(id: subscription_variant_ids).pluck(:id)
|
||||
end
|
||||
|
||||
def build_msg_from(k, msg)
|
||||
return msg[1..-1] if msg.starts_with?("^")
|
||||
|
||||
errors.full_message(k, msg)
|
||||
end
|
||||
end
|
||||
@@ -1,39 +0,0 @@
|
||||
class SubscriptionVariantsService
|
||||
# Includes the following variants:
|
||||
# - Variants of permitted producers
|
||||
# - Variants of hub
|
||||
# - Variants that are in outgoing exchanges where the hub is receiver
|
||||
def self.eligible_variants(distributor)
|
||||
variant_conditions = ["spree_products.supplier_id IN (?)", permitted_producer_ids(distributor)]
|
||||
exchange_variant_ids = outgoing_exchange_variant_ids(distributor)
|
||||
if exchange_variant_ids.present?
|
||||
variant_conditions[0] << " OR spree_variants.id IN (?)"
|
||||
variant_conditions << exchange_variant_ids
|
||||
end
|
||||
|
||||
Spree::Variant.joins(:product).where(is_master: false).where(*variant_conditions)
|
||||
end
|
||||
|
||||
def self.in_open_and_upcoming_order_cycles?(distributor, schedule, variant)
|
||||
scope = ExchangeVariant.joins(exchange: { order_cycle: :schedules })
|
||||
.where(variant_id: variant, exchanges: { incoming: false, receiver_id: distributor })
|
||||
.merge(OrderCycle.not_closed)
|
||||
scope = scope.where(schedules: { id: schedule })
|
||||
scope.any?
|
||||
end
|
||||
|
||||
def self.permitted_producer_ids(distributor)
|
||||
other_permitted_producer_ids = EnterpriseRelationship.joins(:parent)
|
||||
.permitting(distributor.id).with_permission(:add_to_order_cycle)
|
||||
.merge(Enterprise.is_primary_producer)
|
||||
.pluck(:parent_id)
|
||||
|
||||
other_permitted_producer_ids | [distributor.id]
|
||||
end
|
||||
|
||||
def self.outgoing_exchange_variant_ids(distributor)
|
||||
ExchangeVariant.select("DISTINCT exchange_variants.variant_id").joins(:exchange)
|
||||
.where(exchanges: { incoming: false, receiver_id: distributor.id })
|
||||
.pluck(:variant_id)
|
||||
end
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
class SubscriptionsCount
|
||||
def initialize(order_cycles)
|
||||
@order_cycles = order_cycles
|
||||
end
|
||||
|
||||
def for(order_cycle_id)
|
||||
active[order_cycle_id] || 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :order_cycles
|
||||
|
||||
def active
|
||||
return @active unless @active.nil?
|
||||
return @active = [] if order_cycles.blank?
|
||||
|
||||
@active ||= ProxyOrder.not_canceled.group(:order_cycle_id).where(order_cycle_id: order_cycles).count
|
||||
end
|
||||
end
|
||||
36
app/services/variant_overrides_indexed.rb
Normal file
36
app/services/variant_overrides_indexed.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Produces mappings of variant overrides by distributor id and variant id
|
||||
# The primary use case for data structured in this way is for injection into
|
||||
# the initializer of the OpenFoodNetwork::ScopeVariantToHub class
|
||||
|
||||
class VariantOverridesIndexed
|
||||
def initialize(variant_ids, distributor_ids)
|
||||
@variant_ids = variant_ids
|
||||
@distributor_ids = distributor_ids
|
||||
end
|
||||
|
||||
def indexed
|
||||
scoped_variant_overrides.each_with_object(hash_of_hashes) do |variant_override, indexed|
|
||||
indexed[variant_override.hub_id][variant_override.variant] = variant_override
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :variant_ids, :distributor_ids
|
||||
|
||||
def scoped_variant_overrides
|
||||
VariantOverride
|
||||
.joins(:variant)
|
||||
.preload(:variant)
|
||||
.where(
|
||||
hub_id: distributor_ids,
|
||||
variant_id: variant_ids,
|
||||
)
|
||||
end
|
||||
|
||||
def hash_of_hashes
|
||||
Hash.new { |hash, key| hash[key] = {} }
|
||||
end
|
||||
end
|
||||
@@ -1,45 +1,49 @@
|
||||
# Report the stock levels of:
|
||||
# - all variants in the order
|
||||
# - all requested variant ids
|
||||
require 'open_food_network/scope_variant_to_hub'
|
||||
|
||||
class VariantsStockLevels
|
||||
def call(order, requested_variant_ids)
|
||||
variant_stock_levels = variant_stock_levels(order.line_items)
|
||||
|
||||
# Variants are not scoped here and so the stock levels reported are incorrect
|
||||
# See cart_controller_spec for more details and #3222
|
||||
order_variant_ids = variant_stock_levels.keys
|
||||
missing_variant_ids = requested_variant_ids - order_variant_ids
|
||||
missing_variant_ids.each do |variant_id|
|
||||
variant = Spree::Variant.find(variant_id)
|
||||
variant_stock_levels[variant_id] = { quantity: 0, max_quantity: 0, on_hand: variant.on_hand, on_demand: variant.on_demand }
|
||||
end
|
||||
missing_variants = Spree::Variant.includes(:stock_items).
|
||||
where(id: (requested_variant_ids - order_variant_ids))
|
||||
|
||||
# The code above is most probably dead code, this bugsnag notification will confirm it
|
||||
notify_bugsnag(order, requested_variant_ids, order_variant_ids) if missing_variant_ids.present?
|
||||
missing_variants.each do |missing_variant|
|
||||
variant = scoped_variant(order.distributor, missing_variant)
|
||||
variant_stock_levels[variant.id] =
|
||||
{ quantity: 0, max_quantity: 0, on_hand: variant.on_hand, on_demand: variant.on_demand }
|
||||
end
|
||||
|
||||
variant_stock_levels
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify_bugsnag(order, requested_variant_ids, order_variant_ids)
|
||||
error_msg = "VariantsStockLevels.call with variants in the request that are not in the order"
|
||||
Bugsnag.notify(RuntimeError.new(error_msg),
|
||||
requested_variant_ids: requested_variant_ids.as_json,
|
||||
order_variant_ids: order_variant_ids.as_json,
|
||||
order: order.as_json,
|
||||
line_items: order.line_items.as_json)
|
||||
end
|
||||
|
||||
def variant_stock_levels(line_items)
|
||||
Hash[
|
||||
line_items.map do |line_item|
|
||||
[line_item.variant.id,
|
||||
variant = scoped_variant(line_item.order.distributor, line_item.variant)
|
||||
|
||||
[variant.id,
|
||||
{ quantity: line_item.quantity,
|
||||
max_quantity: line_item.max_quantity,
|
||||
on_hand: line_item.variant.on_hand,
|
||||
on_demand: line_item.variant.on_demand }]
|
||||
on_hand: variant.on_hand,
|
||||
on_demand: variant.on_demand }]
|
||||
end
|
||||
]
|
||||
end
|
||||
|
||||
def scoped_variant(distributor, variant)
|
||||
return variant if distributor.blank?
|
||||
|
||||
scoper(distributor).scope(variant)
|
||||
variant
|
||||
end
|
||||
|
||||
def scoper(distributor)
|
||||
@scoper ||= OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
%td
|
||||
%ul
|
||||
%li{"ng-repeat" => "permission in enterprise_relationship.permissions"}
|
||||
to {{ EnterpriseRelationships.permission_presentation(permission.name) }}
|
||||
= t 'admin_enterprise_relationships_to'
|
||||
{{ EnterpriseRelationships.permission_presentation(permission.name) }}
|
||||
%td.actions
|
||||
%a.delete-enterprise-relationship.icon-trash.no-text{'ng-click' => 'delete(enterprise_relationship)'}
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
%div{"ng-repeat" => "permission in EnterpriseRelationships.all_permissions"}
|
||||
%label
|
||||
%input{type: "checkbox", "ng-model" => "permissions[permission]"}
|
||||
to {{ EnterpriseRelationships.permission_presentation(permission) }}
|
||||
= t 'admin_enterprise_relationships_to'
|
||||
{{ EnterpriseRelationships.permission_presentation(permission) }}
|
||||
%td.actions
|
||||
%input{type: "button", value: t(:admin_enterprise_relationships_button_create), "ng-click" => "create()"}
|
||||
.errors {{ EnterpriseRelationships.create_errors }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
= inject_available_payment_methods
|
||||
= inject_saved_credit_cards
|
||||
|
||||
= f_form_for current_order,
|
||||
= form_for current_order,
|
||||
html: {name: "checkout",
|
||||
id: "checkout_form",
|
||||
novalidate: true,
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
|
||||
.row
|
||||
.small-12.columns
|
||||
= f.text_area :special_instructions, label: t(:checkout_instructions), size: "60x4", "ng-model" => "order.special_instructions"
|
||||
%label{ for: 'order_special_instructions'}= t(:checkout_instructions)
|
||||
= f.text_area :special_instructions, size: "60x4", "ng-model" => "order.special_instructions"
|
||||
|
||||
.row
|
||||
.small-12.columns.text-right
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
select: "select(\'map\')"}
|
||||
.map-container
|
||||
%map{"ng-if" => "(isActive(\'/map\') && (mapShowed = true)) || mapShowed"}
|
||||
%google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
|
||||
%ui-gmap-google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
|
||||
%map-osm-tiles
|
||||
%map-search
|
||||
%markers{models: "mapMarkers", fit: "true",
|
||||
%ui-gmap-markers{models: "mapMarkers", fit: "true",
|
||||
coords: "'self'", icon: "'icon'", click: "'reveal'"}
|
||||
|
||||
%tab{heading: t(:groups_about),
|
||||
|
||||
10
app/views/layouts/_bugsnag_js.html.haml
Normal file
10
app/views/layouts/_bugsnag_js.html.haml
Normal file
@@ -0,0 +1,10 @@
|
||||
- bugsnag_js_key = ENV['BUGSNAG_JS_KEY'] || ENV['BUGSNAG_API_KEY']
|
||||
- if bugsnag_js_key.present?
|
||||
%script{src: "//d2wy8f7a9ursnm.cloudfront.net/v6/bugsnag.min.js"}
|
||||
:javascript
|
||||
window.bugsnagClient = bugsnag({
|
||||
apiKey: "#{bugsnag_js_key}",
|
||||
beforeSend: function (report) {
|
||||
report.app.releaseStage = "#{Rails.env}"
|
||||
}
|
||||
});
|
||||
@@ -37,6 +37,7 @@
|
||||
#footer
|
||||
%loading
|
||||
|
||||
= render "layouts/bugsnag_js"
|
||||
%script{:src => "https://js.stripe.com/v3/", :type => "text/javascript"}
|
||||
%script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry#{ ENV['GOOGLE_MAPS_API_KEY'] ? '&key=' + ENV['GOOGLE_MAPS_API_KEY'] : ''} "}
|
||||
= javascript_include_tag "darkswarm/all"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
.map-container{"fill-vertical" => true}
|
||||
%map{"ng-controller" => "MapCtrl"}
|
||||
%google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
|
||||
%ui-gmap-google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
|
||||
%map-osm-tiles
|
||||
%map-search
|
||||
%markers{models: "OfnMap.enterprises", fit: "true",
|
||||
%ui-gmap-markers{models: "OfnMap.enterprises", fit: "true",
|
||||
coords: "'self'", icon: "'icon'", click: "'reveal'"}
|
||||
|
||||
.map-footer
|
||||
|
||||
@@ -26,8 +26,11 @@
|
||||
%a{href: "", "ng-click" => "showDistanceMatches()"}
|
||||
= t :hubs_distance_filter, location: "{{ nameMatchesFiltered[0].name }}"
|
||||
.more-controls
|
||||
%a.button{href: "", ng: {click: "showClosedShops()", show: "!show_closed"}}
|
||||
= t '.show_closed_shops'
|
||||
%a.button{href: "", ng: {click: "hideClosedShops()", show: "show_closed"}}
|
||||
= t '.hide_closed_shops'
|
||||
%img.spinner.text-center{ng: {show: "closed_shops_loading"}, src: "/assets/spinning-circles.svg" }
|
||||
%span{ng: {if: "!show_closed", cloak: true}}
|
||||
%a.button{href: "", ng: {click: "showClosedShops()"}}
|
||||
= t '.show_closed_shops'
|
||||
%span{ng: {if: "show_closed", cloak: true}}
|
||||
%a.button{href: "", ng: {click: "hideClosedShops()"}}
|
||||
= t '.hide_closed_shops'
|
||||
%a.button{href: main_app.map_path}= t '.show_on_map'
|
||||
|
||||
@@ -20,28 +20,28 @@
|
||||
%label{ :for => 'start_date_filter' }
|
||||
= t("admin.start_date")
|
||||
%br
|
||||
%input{ :class => "two columns alpha", :type => "text", :id => 'start_date_filter', 'ng-model' => 'startDate', 'datepicker' => "startDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' }
|
||||
%input{ :class => "two columns alpha", :type => "text", :id => 'start_date_filter', 'ng-model' => 'startDate', 'datepicker' => "startDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()', 'ng-model-options' => '{ debounce: 1000 }' }
|
||||
.date_filter{ :class => "two columns" }
|
||||
%label{ :for => 'end_date_filter' }
|
||||
= t("admin.end_date")
|
||||
%br
|
||||
%input{ :class => "two columns alpha", :type => "text", :id => 'end_date_filter', 'ng-model' => 'endDate', 'datepicker' => "endDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' }
|
||||
%input{ :class => "two columns alpha", :type => "text", :id => 'end_date_filter', 'ng-model' => 'endDate', 'datepicker' => "endDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()', 'ng-model-options' => '{ debounce: 1000 }' }
|
||||
.one.column
|
||||
.filter_select{ :class => "three columns" }
|
||||
%label{ :for => 'supplier_filter' }
|
||||
= t("admin.producer")
|
||||
%br
|
||||
%input#supplier_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'suppliers', blank: "{ id: 0, name: '#{t(:all)}' }", ng: { model: 'supplierFilter' } }
|
||||
%input#supplier_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'suppliers', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'supplierFilter', change: 'refreshData()' } }
|
||||
.filter_select{ :class => "three columns" }
|
||||
%label{ :for => 'distributor_filter' }
|
||||
= t("admin.shop")
|
||||
%br
|
||||
%input#distributor_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'distributors', blank: "{ id: 0, name: '#{t(:all)}' }", ng: { model: 'distributorFilter' } }
|
||||
%input#distributor_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'distributors', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'distributorFilter', change: 'refreshData()' } }
|
||||
.filter_select{ :class => "three columns" }
|
||||
%label{ :for => 'order_cycle_filter' }
|
||||
= t("admin.order_cycle")
|
||||
%br
|
||||
%input#order_cycle_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'orderCycles', blank: "{ id: 0, name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'orderCycleFilter', change: 'refreshData()' } }
|
||||
%input#order_cycle_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'orderCycles', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'orderCycleFilter', change: 'refreshData()' } }
|
||||
.filter_clear{ :class => "two columns omega" }
|
||||
%label{ :for => 'clear_all_filters' }
|
||||
%br
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
%hr.divider.sixteen.columns.alpha.omega
|
||||
|
||||
.controls.sixteen.columns.alpha.omega{ ng: { hide: 'RequestMonitor.loading || lineItems.length == 0' } }
|
||||
.controls.sixteen.columns.alpha.omega{ ng: { hide: 'RequestMonitor.loading || line_items.length == 0' } }
|
||||
%div.three.columns.alpha
|
||||
%input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
|
||||
= render 'admin/shared/bulk_actions_dropdown'
|
||||
@@ -157,7 +157,7 @@
|
||||
= t("admin.orders.bulk_management.ask")
|
||||
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
|
||||
|
||||
%tr.line_item{ ng: { repeat: "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:sorting.predicate:sorting.reverse )", 'class-even' => "'even'", 'class-odd' => "'odd'", attr: { id: "li_{{line_item.id}}" } } }
|
||||
%tr.line_item{ ng: { repeat: "line_item in filteredLineItems = ( line_items | filter:quickSearch | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:sorting.predicate:sorting.reverse )", 'class-even' => "'even'", 'class-odd' => "'odd'", attr: { id: "li_{{line_item.id}}" } } }
|
||||
%td.bulk
|
||||
%input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked', 'ignore-dirty' => true }
|
||||
%td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }}
|
||||
@@ -175,7 +175,7 @@
|
||||
%span.error{ ng: { bind: 'line_item.errors.quantity' } }
|
||||
%td.max{ 'ng-show' => 'columns.max.visible' } {{ line_item.max_quantity }}
|
||||
%td.final_weight_volume{ 'ng-show' => 'columns.final_weight_volume.visible' }
|
||||
%input.show-dirty{ :type => 'number', :name => 'final_weight_volume', :id => 'final_weight_volume', ng: { model: "line_item.final_weight_volume", readonly: "unitValueLessThanZero(line_item)", change: "weightAdjustedPrice(line_item)", required: "true", class: '{"update-error": line_item.errors.final_weight_volume}' }, min: 0, 'ng-pattern' => '/[1-9]+/' }
|
||||
%input.show-dirty{ type: 'number', step: 'any', :name => 'final_weight_volume', :id => 'final_weight_volume', ng: { model: "line_item.final_weight_volume", readonly: "unitValueLessThanZero(line_item)", change: "weightAdjustedPrice(line_item)", required: "true", class: '{"update-error": line_item.errors.final_weight_volume}' }, min: 0, 'ng-pattern' => '/[0-9]*[.]?[0-9]+/' }
|
||||
%span.error{ ng: { bind: 'line_item.errors.final_weight_volume' } }
|
||||
%td.price{ 'ng-show' => 'columns.price.visible' }
|
||||
%input.show-dirty{ :type => 'text', :name => 'price', :id => 'price', :ng => { value: 'line_item.price * line_item.quantity | currency:""', readonly: "true", class: '{"update-error": line_item.errors.price}' } }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
= @payment_method
|
||||
- case @payment_method
|
||||
- when Spree::Gateway::StripeConnect
|
||||
= render 'stripe_connect'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.three.columns.omega{ "ng-if" => "product.variant_unit_with_scale != 'items'" }
|
||||
= f.field_container :display_as do
|
||||
= f.label :product_display_as, t('.display_as')
|
||||
%span.required *
|
||||
%input#product_display_as.fullwidth{name: "product[display_as]", placeholder: "{{ placeholder_text }}", type: "text"}
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
= sanitize(@product.description)
|
||||
= f.error_message_on :description
|
||||
|
||||
= f.field_container :taxons do
|
||||
= f.label :taxon_ids, t(:taxons)
|
||||
%br
|
||||
= f.hidden_field :taxon_ids, :value => @product.taxon_ids.join(',')
|
||||
|
||||
.right.four.columns.omega
|
||||
.variant_units_form{ 'ng-app' => 'admin.products', 'ng-controller' => 'editUnitsCtrl' }
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
.three.columns.omega{ 'ng-show' => "product.variant_unit_with_scale == 'items'" }
|
||||
= f.field_container :unit_name do
|
||||
= f.label :product_variant_unit_name, t(".unit_name")
|
||||
%span.required *
|
||||
%input.fullwidth{ id: 'product_variant_unit_name','ng-model' => 'product.variant_unit_name', :name => 'product[variant_unit_name]', :placeholder => t('admin.products.unit_name_placeholder'), :type => 'text' }
|
||||
.twelve.columns.alpha
|
||||
.six.columns.alpha
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
%link{:href => "//fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600&subset=latin,cyrillic,greek,vietnamese", :rel => "stylesheet", :type => "text/css"}
|
||||
|
||||
= stylesheet_link_tag 'admin/all'
|
||||
= render "layouts/bugsnag_js"
|
||||
= javascript_include_tag 'admin/all'
|
||||
|
||||
= render "spree/admin/shared/translations"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
object false
|
||||
child(@taxons => :taxons) do
|
||||
attributes :name, :pretty_name, :id
|
||||
end
|
||||
@@ -13,7 +13,8 @@
|
||||
%tr.order-row
|
||||
%td.order1
|
||||
%a{"ng-href" => "{{::order.path}}", "ng-bind" => "::order.number"}
|
||||
%td.order2{"ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
|
||||
%td.order2
|
||||
%a{"ng-href" => "{{::Orders.shopsByID[order.shop_id].hash}}#{main_app.shop_path}", "ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
|
||||
%td.order3.show-for-large-up{"ng-bind" => "::order.changes_allowed_until"}
|
||||
%td.order4.show-for-large-up{"ng-bind" => "::order.item_count"}
|
||||
%td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","ng-bind" => "::order.total | localizeCurrency"}
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
%tr.order-row
|
||||
%td.order1
|
||||
%a{"ng-href" => "{{::order.path}}", "ng-bind" => "::order.number"}
|
||||
%td.order2{"ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
|
||||
%td.order2
|
||||
%a{"ng-href" => "{{::Orders.shopsByID[order.shop_id].hash}}#{main_app.shop_path}", "ng-bind" => "::Orders.shopsByID[order.shop_id].name"}
|
||||
%td.order3.show-for-large-up{"ng-bind" => "::order.completed_at"}
|
||||
%td.order4.show-for-large-up{"ng-bind" => "::order.item_count"}
|
||||
%td.order5.text-right{"ng-class" => "{'debit': order.payment_state != 'paid', 'credit': order.payment_state == 'paid'}","ng-bind" => "::order.total | localizeCurrency"}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= f_form_for @spree_user, :as => :spree_user, :url => spree.spree_user_password_path, :method => :put do |f|
|
||||
= form_for @spree_user, :as => :spree_user, :url => spree.spree_user_password_path, :method => :put do |f|
|
||||
= render :partial => 'spree/shared/error_messages', :locals => { :target => @spree_user }
|
||||
%fieldset
|
||||
.row
|
||||
@@ -6,9 +6,11 @@
|
||||
%legend= t(:change_my_password)
|
||||
.row
|
||||
.small-12.medium-6.large-4.columns.medium-centered.large-centered
|
||||
%label{ for: 'spree_user_password'}= t(:password)
|
||||
= f.password_field :password
|
||||
.row
|
||||
.small-12.medium-6.large-4.columns.medium-centered.large-centered
|
||||
%label{ for: 'spree_user_password_confirmation'}= t(:password_confirmation)
|
||||
= f.password_field :password_confirmation
|
||||
= f.hidden_field :reset_password_token
|
||||
.row
|
||||
|
||||
13
config.ru
13
config.ru
@@ -1,4 +1,17 @@
|
||||
# This file is used by Rack-based servers to start the application.
|
||||
|
||||
if ENV.fetch('KILL_UNICORNS', false) && ['production', 'staging'].include?(ENV['RAILS_ENV'])
|
||||
# Gracefully restart individual unicorn workers if they have:
|
||||
# - performed between 25000 and 30000 requests
|
||||
# - grown in memory usage to between 700 and 850 MB
|
||||
require 'unicorn/worker_killer'
|
||||
use Unicorn::WorkerKiller::MaxRequests,
|
||||
ENV.fetch('UWK_REQS_MIN', 25_000).to_i,
|
||||
ENV.fetch('UWK_REQS_MAX', 30_000).to_i
|
||||
use Unicorn::WorkerKiller::Oom,
|
||||
( ENV.fetch('UWK_MEM_MIN', 700).to_i * (1024**2) ),
|
||||
( ENV.fetch('UWK_MEM_MAX', 850).to_i * (1024**2) )
|
||||
end
|
||||
|
||||
require ::File.expand_path('../config/environment', __FILE__)
|
||||
run Openfoodnetwork::Application
|
||||
|
||||
@@ -37,6 +37,9 @@ SMTP_PASSWORD: 'f00d'
|
||||
# MAILS_FROM: hello@example.com
|
||||
# MAIL_BCC: manager@example.com
|
||||
|
||||
# Javascript error reporting via Bugsnag.
|
||||
#BUGSNAG_JS_KEY: ""
|
||||
|
||||
# SingleSignOn login for Discourse
|
||||
#
|
||||
# DISCOURSE_SSO_SECRET should be a random string. It must be the same as provided to your Discourse instance.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defaults: &defaults
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
pool: 5
|
||||
pool: <%= ENV.fetch('OFN_DB_POOL', 5) %>
|
||||
host: <%= ENV.fetch('OFN_DB_HOST', 'localhost') %>
|
||||
username: <%= ENV.fetch('OFN_DB_USERNAME', 'ofn') %>
|
||||
password: <%= ENV.fetch('OFN_DB_PASSWORD', 'f00d') %>
|
||||
|
||||
@@ -4,5 +4,6 @@ if ENV['DATADOG_RAILS_APM']
|
||||
c.use :delayed_job, service_name: 'delayed_job'
|
||||
c.use :dalli, service_name: 'memcached'
|
||||
c.analytics_enabled = true
|
||||
c.runtime_metrics_enabled = true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
# Use this setup block to configure all options available in SimpleForm.
|
||||
SimpleForm.setup do |config|
|
||||
# Wrappers are used by the form builder to generate a
|
||||
# complete input. You can remove any component from the
|
||||
# wrapper, change the order or even add your own to the
|
||||
# stack. The options given below are used to wrap the
|
||||
# whole input.
|
||||
config.wrappers :default, :class => :input,
|
||||
:hint_class => :field_with_hint, :error_class => :field_with_errors do |b|
|
||||
## Extensions enabled by default
|
||||
# Any of these extensions can be disabled for a
|
||||
# given input by passing: `f.input EXTENSION_NAME => false`.
|
||||
# You can make any of these extensions optional by
|
||||
# renaming `b.use` to `b.optional`.
|
||||
|
||||
# Determines whether to use HTML5 (:email, :url, ...)
|
||||
# and required attributes
|
||||
b.use :html5
|
||||
|
||||
# Calculates placeholders automatically from I18n
|
||||
# You can also pass a string as f.input :placeholder => "Placeholder"
|
||||
b.use :placeholder
|
||||
|
||||
## Optional extensions
|
||||
# They are disabled unless you pass `f.input EXTENSION_NAME => :lookup`
|
||||
# to the input. If so, they will retrieve the values from the model
|
||||
# if any exists. If you want to enable the lookup for any of those
|
||||
# extensions by default, you can change `b.optional` to `b.use`.
|
||||
|
||||
# Calculates maxlength from length validations for string inputs
|
||||
b.optional :maxlength
|
||||
|
||||
# Calculates pattern from format validations for string inputs
|
||||
b.optional :pattern
|
||||
|
||||
# Calculates min and max from length validations for numeric inputs
|
||||
b.optional :min_max
|
||||
|
||||
# Calculates readonly automatically from readonly attributes
|
||||
b.optional :readonly
|
||||
|
||||
## Inputs
|
||||
b.use :label_input
|
||||
b.use :hint, :wrap_with => { :tag => :span, :class => :hint }
|
||||
b.use :error, :wrap_with => { :tag => :span, :class => :error }
|
||||
end
|
||||
|
||||
# The default wrapper to be used by the FormBuilder.
|
||||
config.default_wrapper = :default
|
||||
|
||||
# Define the way to render check boxes / radio buttons with labels.
|
||||
# Defaults to :nested for bootstrap config.
|
||||
# :inline => input + label
|
||||
# :nested => label > input
|
||||
config.boolean_style = :nested
|
||||
|
||||
# Default class for buttons
|
||||
config.button_class = 'btn'
|
||||
|
||||
# Method used to tidy up errors. Specify any Rails Array method.
|
||||
# :first lists the first message for each field.
|
||||
# Use :to_sentence to list all errors for each field.
|
||||
# config.error_method = :first
|
||||
|
||||
# Default tag used for error notification helper.
|
||||
config.error_notification_tag = :div
|
||||
|
||||
# CSS class to add for error notification helper.
|
||||
config.error_notification_class = 'alert alert-error'
|
||||
|
||||
# ID to add for error notification helper.
|
||||
# config.error_notification_id = nil
|
||||
|
||||
# Series of attempts to detect a default label method for collection.
|
||||
# config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
|
||||
|
||||
# Series of attempts to detect a default value method for collection.
|
||||
# config.collection_value_methods = [ :id, :to_s ]
|
||||
|
||||
# You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
|
||||
# config.collection_wrapper_tag = nil
|
||||
|
||||
# You can define the class to use on all collection wrappers. Defaulting to none.
|
||||
# config.collection_wrapper_class = nil
|
||||
|
||||
# You can wrap each item in a collection of radio/check boxes with a tag,
|
||||
# defaulting to :span. Please note that when using :boolean_style = :nested,
|
||||
# SimpleForm will force this option to be a label.
|
||||
# config.item_wrapper_tag = :span
|
||||
|
||||
# You can define a class to use in all item wrappers. Defaulting to none.
|
||||
# config.item_wrapper_class = nil
|
||||
|
||||
# How the label text should be generated altogether with the required text.
|
||||
# config.label_text = lambda { |label, required| "#{required} #{label}" }
|
||||
|
||||
# You can define the class to use on all labels. Default is nil.
|
||||
config.label_class = 'control-label'
|
||||
|
||||
# You can define the class to use on all forms. Default is simple_form.
|
||||
# config.form_class = :simple_form
|
||||
|
||||
# You can define which elements should obtain additional classes
|
||||
# config.generate_additional_classes_for = [:wrapper, :label, :input]
|
||||
|
||||
# Whether attributes are required by default (or not). Default is true.
|
||||
# config.required_by_default = true
|
||||
|
||||
# Tell browsers whether to use default HTML5 validations (novalidate option).
|
||||
# Default is enabled.
|
||||
config.browser_validations = false
|
||||
|
||||
# Collection of methods to detect if a file type was given.
|
||||
# config.file_methods = [ :mounted_as, :file?, :public_filename ]
|
||||
|
||||
# Custom mappings for input types. This should be a hash containing a regexp
|
||||
# to match as key, and the input type that will be used when the field name
|
||||
# matches the regexp as value.
|
||||
# config.input_mappings = { /count/ => :integer }
|
||||
|
||||
# Default priority for time_zone inputs.
|
||||
# config.time_zone_priority = nil
|
||||
|
||||
# Default priority for country inputs.
|
||||
# config.country_priority = nil
|
||||
|
||||
# Default size for text inputs.
|
||||
# config.default_input_size = 50
|
||||
|
||||
# When false, do not use translations for labels.
|
||||
# config.translate_labels = true
|
||||
|
||||
# Automatically discover new inputs in Rails' autoload path.
|
||||
# config.inputs_discovery = true
|
||||
|
||||
# Cache SimpleForm inputs discovery
|
||||
# config.cache_discovery = !Rails.env.development?
|
||||
end
|
||||
@@ -19,6 +19,8 @@ ar:
|
||||
shipping_category_id: "نوع الشحن"
|
||||
variant_unit: "وحدة النوع"
|
||||
variant_unit_name: "اسم وحدة النوع"
|
||||
spree/credit_card:
|
||||
base: "بطاقة ائتمان"
|
||||
order_cycle:
|
||||
orders_close_at: تاريخ الاغلاق
|
||||
errors:
|
||||
@@ -53,7 +55,7 @@ ar:
|
||||
messages:
|
||||
inclusion: "غير مدرجة في القائمة"
|
||||
models:
|
||||
subscription_validator:
|
||||
order_management/subscriptions/validator:
|
||||
attributes:
|
||||
subscription_line_items:
|
||||
at_least_one_product: "^ الرجاء إضافة منتج واحد على الأقل"
|
||||
@@ -860,6 +862,11 @@ ar:
|
||||
cancel: "إلغاء"
|
||||
back_to_list: "العودة للقائمة"
|
||||
outgoing:
|
||||
outgoing: "الصادر"
|
||||
distributor: "الموزع"
|
||||
products: "منتجات"
|
||||
tags: "الاوسمة"
|
||||
fees: "رسوم"
|
||||
previous: "السابق"
|
||||
save: "حفظ"
|
||||
save_and_back_to_list: "حفظ والعودة إلى القائمة"
|
||||
|
||||
@@ -19,6 +19,8 @@ ca:
|
||||
shipping_category_id: "Categoria d'enviament"
|
||||
variant_unit: "Unitat de la variant"
|
||||
variant_unit_name: "Nom de la unitat de la variant"
|
||||
spree/credit_card:
|
||||
base: "Targeta de crèdit"
|
||||
order_cycle:
|
||||
orders_close_at: Data de tancament
|
||||
errors:
|
||||
@@ -29,6 +31,10 @@ ca:
|
||||
taken: "Ja hi ha un compte per a aquest correu electrònic. Si us plau, inicia sessió o restableix la contrasenya."
|
||||
spree/order:
|
||||
no_card: No hi ha targetes de crèdit autoritzades disponibles per carregar
|
||||
spree/credit_card:
|
||||
attributes:
|
||||
base:
|
||||
card_expired: "Ha expirat"
|
||||
order_cycle:
|
||||
attributes:
|
||||
orders_close_at:
|
||||
@@ -53,7 +59,7 @@ ca:
|
||||
messages:
|
||||
inclusion: "no està inclòs a la llista"
|
||||
models:
|
||||
subscription_validator:
|
||||
order_management/subscriptions/validator:
|
||||
attributes:
|
||||
subscription_line_items:
|
||||
at_least_one_product: "^Afegiu com a mínim un producte"
|
||||
@@ -246,6 +252,8 @@ ca:
|
||||
notes: Notes
|
||||
error: Error
|
||||
processing_payment: "S'està processant el pagament..."
|
||||
no_pending_payments: "No hi ha pagaments pendents"
|
||||
invalid_payment_state: "Estat de pagament no vàlid"
|
||||
filter_results: Aplicar filtre
|
||||
quantity: Quantitat
|
||||
pick_up: Recollida
|
||||
@@ -862,6 +870,12 @@ ca:
|
||||
cancel: "Cancel·lar"
|
||||
back_to_list: "Tornar a la llista"
|
||||
outgoing:
|
||||
outgoing: "Sortint"
|
||||
distributor: "Distribuïdora"
|
||||
products: "Productes "
|
||||
tags: "Etiquetes"
|
||||
delivery_details: "Detalls d'enviament"
|
||||
fees: "Comissions"
|
||||
previous: "Anterior"
|
||||
save: "Desa"
|
||||
save_and_back_to_list: "Desa i torna a la llista"
|
||||
@@ -1442,13 +1456,13 @@ ca:
|
||||
email_payment_summary: Resum del pagament
|
||||
email_payment_method: "Pagament a través de:"
|
||||
email_so_placement_intro_html: "Tens una nova comanda amb <strong> %{distributor} </ strong>"
|
||||
email_so_placement_details_html: "Aquests són els detalls de la comanda de <strong> %{distributor} </ strong>:"
|
||||
email_so_placement_details_html: "Aquests són els detalls de la comanda de <strong>%{distributor}</strong> :"
|
||||
email_so_placement_changes: "Malauradament, no tots els productes que has demanat estaven disponibles. Les quantitats originals que has sol·licitat apareixen ratllades a sota."
|
||||
email_so_payment_success_intro_html: "S'ha processat un pagament automàtic per a la vostra comanda des de <strong> %{distributor} </ strong>."
|
||||
email_so_placement_explainer_html: "Aquesta comanda s'ha creat automàticament per tu."
|
||||
email_so_edit_true_html: "Potd <a href='%{order_url}'> fer canvis </ a> fins que les comandes es tanquin el %{orders_close_at}."
|
||||
email_so_edit_true_html: "Pots <a href='%{order_url}'>fer canvis</a> fins que les comandes es tanquin el %{orders_close_at}."
|
||||
email_so_edit_false_html: "Pots <a href='%{order_url}'> veure detalls d'aquesta comanda </a> en qualsevol moment."
|
||||
email_so_contact_distributor_html: "Si tens alguna pregunta pots contactar amb <strong> %{distributor} </ strong> a través d'%{email}."
|
||||
email_so_contact_distributor_html: "Si tens alguna pregunta pots contactar amb <strong>%{distributor}</strong> a través d'%{email}."
|
||||
email_so_contact_distributor_to_change_order_html: "Aquesta comanda s'ha creat automàticament per a vostè. Podeu fer canvis fins que les comandes es tanquin a %{orders_close_at} contactant a <strong> %{distributor} </ strong> a través d'%{email}."
|
||||
email_so_confirmation_intro_html: "La teva comanda amb <strong> %{distributor} </ strong> ja està confirmada"
|
||||
email_so_confirmation_explainer_html: "Vas realitzar aquesta comanda automàticament i ara s'ha finalitzat."
|
||||
@@ -1487,6 +1501,7 @@ ca:
|
||||
shopping_oc_closed_description: "Si us plau espera fins que s'obri el pròxim cicle (o posa't en contacte amb nosaltres directament per veure si podem acceptar alguna comanda fora de temps)"
|
||||
shopping_oc_last_closed: "L'últim cicle va tancar fa %{distance_of_time} "
|
||||
shopping_oc_next_open: "El següent cicle s'obre en %{distance_of_time}"
|
||||
shopping_oc_select: "Selecciona ..."
|
||||
shopping_tabs_home: "Inici"
|
||||
shopping_tabs_shop: "Botiga"
|
||||
shopping_tabs_about: "Sobre"
|
||||
@@ -1860,6 +1875,7 @@ ca:
|
||||
headline: "Acabat!"
|
||||
thanks: "Gràcies per omplir els detalls de%{enterprise}."
|
||||
login: "Pots canviar o actualitzar la teva organització en qualsevol moment accedint a Katuma i anant a Admin."
|
||||
action: "Vés al panell de control de l'organització"
|
||||
back: "Enrere"
|
||||
continue: "Continua"
|
||||
action_or: "O"
|
||||
@@ -2629,6 +2645,12 @@ ca:
|
||||
tub:
|
||||
one: "tina"
|
||||
other: "cubells"
|
||||
punnet:
|
||||
one: "puntet"
|
||||
other: "capses"
|
||||
packet:
|
||||
one: "paquet"
|
||||
other: "paquets"
|
||||
item:
|
||||
one: "article"
|
||||
other: "articles"
|
||||
@@ -3015,6 +3037,8 @@ ca:
|
||||
tax_invoice: "FACTURA D'IMPOSTOS"
|
||||
code: "Codi"
|
||||
from: "De"
|
||||
to: "Facturar a"
|
||||
shipping: "Enviament"
|
||||
form:
|
||||
distribution_fields:
|
||||
title: "Distribució"
|
||||
|
||||
@@ -19,6 +19,8 @@ de_DE:
|
||||
shipping_category_id: "Versandkategorie"
|
||||
variant_unit: "Varianteneinheit"
|
||||
variant_unit_name: "Name der Varianteneinheit"
|
||||
spree/credit_card:
|
||||
base: "Kreditkarte"
|
||||
order_cycle:
|
||||
orders_close_at: Schlussdatum
|
||||
errors:
|
||||
@@ -29,6 +31,10 @@ de_DE:
|
||||
taken: "Es gibt bereits ein Konto für diese E-Mail-Adresse. Bitte versuchen Sie sich einzuloggen oder setzen Sie Ihr Passwort zurück."
|
||||
spree/order:
|
||||
no_card: Es sind keine belastbaren Karten verfügbar.
|
||||
spree/credit_card:
|
||||
attributes:
|
||||
base:
|
||||
card_expired: "abgelaufen"
|
||||
order_cycle:
|
||||
attributes:
|
||||
orders_close_at:
|
||||
@@ -36,7 +42,7 @@ de_DE:
|
||||
variant_override:
|
||||
count_on_hand:
|
||||
using_producer_stock_settings_but_count_on_hand_set: "muss leer sein, da die Einstellungen des Produzentenbestands verwendet werden"
|
||||
on_demand_but_count_on_hand_set: "muss bei Bedarf leer sein"
|
||||
on_demand_but_count_on_hand_set: "muss leer sein falls Produktion auf Nachfrage"
|
||||
limited_stock_but_no_count_on_hand: "muss angegeben werden, da nur begrenzte Lagerbestände erforderlich sind"
|
||||
activemodel:
|
||||
attributes:
|
||||
@@ -46,12 +52,14 @@ de_DE:
|
||||
distributor_ids: "Hubs"
|
||||
producer_ids: "Erzeuger"
|
||||
order_cycle_ids: "Bestellrunden"
|
||||
enterprise_fee_ids: "Gebühren Namen"
|
||||
enterprise_fee_ids: "Gebührennamen"
|
||||
shipping_method_ids: "Lieferart"
|
||||
payment_method_ids: "Zahlungsarten"
|
||||
errors:
|
||||
messages:
|
||||
inclusion: "ist in der Liste nicht enthalten"
|
||||
models:
|
||||
subscription_validator:
|
||||
order_management/subscriptions/validator:
|
||||
attributes:
|
||||
subscription_line_items:
|
||||
at_least_one_product: "^ Bitte fügen Sie mindestens ein Produkt hinzu"
|
||||
@@ -244,6 +252,8 @@ de_DE:
|
||||
notes: Anmerkungen
|
||||
error: Fehler
|
||||
processing_payment: "Bezahlung wird verarbeitet..."
|
||||
no_pending_payments: "Keine ausstehenden Zahlungen"
|
||||
invalid_payment_state: "Ungültiger Zahlungsstatus"
|
||||
filter_results: Ergebnisse filtern
|
||||
quantity: Menge
|
||||
pick_up: Abholen
|
||||
@@ -700,6 +710,11 @@ de_DE:
|
||||
enable_subscriptions_false: "deaktiviert"
|
||||
enable_subscriptions_true: "aktiviert"
|
||||
shopfront_message: "Laden-Nachricht"
|
||||
shopfront_message_placeholder: >
|
||||
Eine optionale Nachricht, um Kunden willkommen zu heißen und zu erklären,
|
||||
wie Sie bei Ihnen einkaufen können. Wenn hier Text eingegeben wird,
|
||||
wird dieser in einem Home-Tab in Ihrem Shop angezeigt, wenn Kunden zum
|
||||
ersten Mal ihren Shop besuchen.
|
||||
shopfront_message_link_tooltip: "Link einfügen / bearbeiten"
|
||||
shopfront_message_link_prompt: "Bitte geben Sie eine einzufügende URL ein"
|
||||
shopfront_closed_message: "Laden Geschlossen Nachricht"
|
||||
@@ -854,6 +869,12 @@ de_DE:
|
||||
cancel: "Abbrechen"
|
||||
back_to_list: "Zurück zur Liste"
|
||||
outgoing:
|
||||
outgoing: "Ausgehend"
|
||||
distributor: "Verteiler"
|
||||
products: "Produkte"
|
||||
tags: "Stichwörter"
|
||||
delivery_details: "Lieferdetails"
|
||||
fees: "Gebühren"
|
||||
previous: "Bisherige"
|
||||
save: "Speichern"
|
||||
save_and_back_to_list: "Speichern und zurück zur Liste"
|
||||
@@ -1100,10 +1121,13 @@ de_DE:
|
||||
destroy_attachment_does_not_exist: "Logo existiert nicht"
|
||||
enterprise_promo_image:
|
||||
destroy_attachment_does_not_exist: "Webebild existiert nicht"
|
||||
orders:
|
||||
failed_to_update: "Bestellung konnte nicht aktualisiert werden"
|
||||
checkout:
|
||||
already_ordered:
|
||||
cart: "Warenkorb"
|
||||
message_html: "Sie haben bereits eine Bestellung für diesen Bestellzyklus. Überprüfen Sie den %{cart}, um die Artikel zu sehen, die Sie zuvor bestellt haben. Sie können Artikel auch stornieren, solange der Bestellzyklus geöffnet ist."
|
||||
failed: "Die Bestellung ist fehlgeschlagen. Bitte geben Sie uns Bescheid, damit wir Ihre Bestellung trotzdem bearbeiten können."
|
||||
shops:
|
||||
hubs:
|
||||
show_closed_shops: "Geschlossene Läden anzeigen"
|
||||
@@ -1121,7 +1145,7 @@ de_DE:
|
||||
checkout: "Zur Kasse"
|
||||
already_ordered_products: "Bereits in diesem Bestellzyklus bestellt"
|
||||
register_call:
|
||||
selling_on_ofn: "Interesse am Open Food Network?"
|
||||
selling_on_ofn: "Sie möchten selbst im Open Food Network verkaufen?"
|
||||
register: "Hier anmelden"
|
||||
footer:
|
||||
footer_secure: "Sicher und vertrauenswürdig."
|
||||
@@ -1274,6 +1298,7 @@ de_DE:
|
||||
saving_credit_card: Kreditkarte speichern ...
|
||||
card_has_been_removed: "Ihre Karte wurde entfernt (Nummer: %{number})"
|
||||
card_could_not_be_removed: Die Karte konnte nicht entfernt werden
|
||||
invalid_credit_card: "Ungültige Kreditkarte"
|
||||
ie_warning_headline: "Ihr Browser ist veraltet :-("
|
||||
ie_warning_text: "Für das beste Open-Food-Network-Erlebnis empfehlen wir dringend, Ihren Browser zu aktualisieren:"
|
||||
ie_warning_chrome: Chrome herunterladen
|
||||
@@ -1361,7 +1386,7 @@ de_DE:
|
||||
checkout_default_bill_address: "Als Standard-Rechnungsadresse speichern"
|
||||
checkout_shipping: Versandinformation
|
||||
checkout_default_ship_address: "Als Standardversandadresse speichern"
|
||||
checkout_method_free: ??
|
||||
checkout_method_free: kostenlos
|
||||
checkout_address_same: Lieferadresse wie Rechnungsadresse?
|
||||
checkout_ready_for: "Bereit am:"
|
||||
checkout_instructions: "Kommentare oder spezielle Anweisungen?"
|
||||
@@ -1475,6 +1500,7 @@ de_DE:
|
||||
shopping_oc_closed_description: "Bitte warten Sie, bis der nächste Zyklus beginnt (oder kontaktieren Sie uns direkt, um zu sehen, ob wir verspätete Bestellungen annehmen können)"
|
||||
shopping_oc_last_closed: "Der letzte Zyklus wurde vor %{distance_of_time} geschlossen"
|
||||
shopping_oc_next_open: "Der nächste Zyklus wird in %{distance_of_time} geöffnet"
|
||||
shopping_oc_select: "Wählen..."
|
||||
shopping_tabs_home: "Startseite"
|
||||
shopping_tabs_shop: "Laden"
|
||||
shopping_tabs_about: "Über Uns"
|
||||
@@ -1758,13 +1784,13 @@ de_DE:
|
||||
address1_field_placeholder: "z.B. 123 Cranberry-Laufwerk"
|
||||
address1_field_error: "Bitte geben Sie eine Adresse an"
|
||||
address2_field: "Anschrift Zeile 2:"
|
||||
suburb_field: "Vorort:"
|
||||
suburb_field: "Ort:"
|
||||
suburb_field_placeholder: "z.B. Northcote"
|
||||
suburb_field_error: "Bitte geben Sie einen Vorort ein"
|
||||
postcode_field: "Postleitzahl:"
|
||||
postcode_field_placeholder: "z.B. 3070"
|
||||
postcode_field_error: "Postleitzahl erforderlich"
|
||||
state_field: "Zustand:"
|
||||
state_field: "Bundesland:"
|
||||
state_field_error: "Staat erforderlich"
|
||||
country_field: "Land:"
|
||||
country_field_error: "Bitte wähle ein Land"
|
||||
@@ -1783,8 +1809,8 @@ de_DE:
|
||||
yes_producer: "Ja, ich bin ein Produzent."
|
||||
no_producer: "Nein, ich bin kein Produzent"
|
||||
producer_field_error: "Bitte wählen Sie: Sind Sie ein Produzent?"
|
||||
yes_producer_help: "Hersteller machen leckere Dinge zu essen und / oder zu trinken. Du bist ein Produzent, wenn du ihn anbaust, erziehst ihn, braue ihn, backe ihn, gähre ihn, melke ihn oder forme ihn."
|
||||
no_producer_help: "Wenn Sie kein Produzent sind, sind Sie wahrscheinlich jemand, der Lebensmittel verkauft und verteilt. Sie könnten ein Hub, Coop, Einkaufsgruppe, Einzelhändler, Großhändler oder andere sein."
|
||||
yes_producer_help: "Die Produzenten machen leckere Sachen zum Essen und / oder Trinken. Sie sind ein Produzent, wenn Sie anbauen, brauen, backen, fermentieren, melken oder sonst wie Lebenmittel produzieren."
|
||||
no_producer_help: "Wenn Sie kein Produzent sind, sind Sie wahrscheinlich jemand, der Lebensmittel verkauft und verteilt. Sie könnten ein Foodhub, eine Coop, eine Einkaufsgruppe, Einzelhändler, ein Hofladen, Großhändler oder vergleichbares sein."
|
||||
create_profile: "Profil erstellen"
|
||||
about:
|
||||
title: "Über Uns"
|
||||
@@ -1848,6 +1874,7 @@ de_DE:
|
||||
headline: "Fertig!"
|
||||
thanks: "Vielen Dank, dass Sie die Details für %{enterprise} ausgefüllt haben."
|
||||
login: "Sie können Ihr Unternehmen jederzeit ändern oder aktualisieren, indem Sie sich bei Open Food Network anmelden und zum Administrator wechseln."
|
||||
action: "Gehen Sie zum Enterprise Dashboard"
|
||||
back: "Zurück"
|
||||
continue: "Fortsetzen"
|
||||
action_or: "ODER"
|
||||
@@ -1933,6 +1960,7 @@ de_DE:
|
||||
tax_category: "Steuerkategorie"
|
||||
calculator: "Rechner"
|
||||
calculator_values: "Rechnerwerte"
|
||||
calculator_settings_warning: "Wenn Sie den Gebühren-Typ ändern, müssen Sie zuerst speichern, bevor Sie die Gebühren-Einstellungen bearbeiten können"
|
||||
flat_percent_per_item: "Flache Prozent (pro Artikel)"
|
||||
flat_rate_per_item: "Pauschale (pro Stück)"
|
||||
flat_rate_per_order: "Pauschalpreis pro Bestellung)"
|
||||
@@ -2263,6 +2291,7 @@ de_DE:
|
||||
enterprise_register_success_notice: "Herzliche Glückwünsche! Registrierung für %{enterprise} ist abgeschlossen!"
|
||||
enterprise_bulk_update_success_notice: "Unternehmen wurden erfolgreich aktualisiert"
|
||||
enterprise_bulk_update_error: 'Update fehlgeschlagen'
|
||||
enterprise_shop_show_error: "Der gesuchte Shop existiert nicht oder ist auf OFN inaktiv. Bitte schauen Sie nach anderen Shops!"
|
||||
order_cycles_create_notice: 'Ihr Bestellzyklus wurde erstellt.'
|
||||
order_cycles_update_notice: 'Ihr Bestellzyklus wurde aktualisiert.'
|
||||
order_cycles_bulk_update_notice: 'Bestellzyklen wurden aktualisiert.'
|
||||
@@ -2417,6 +2446,12 @@ de_DE:
|
||||
severity: Schwere
|
||||
description: Beschreibung
|
||||
resolve: Entschlossenheit
|
||||
exchange_products:
|
||||
load_more_variants: "Weitere Varianten laden"
|
||||
load_all_variants: "Alle Varianten laden"
|
||||
select_all_variants: "Wählen Sie alle %{total_number_of_variants}-Varianten aus"
|
||||
variants_loaded: "%{num_of_variants_loaded} von %{total_number_of_variants} Varianten geladen"
|
||||
loading_variants: "lade Varianten"
|
||||
tag_rules:
|
||||
shipping_method_tagged_top: "Versandarten markiert"
|
||||
shipping_method_tagged_bottom: "sind:"
|
||||
@@ -2499,6 +2534,7 @@ de_DE:
|
||||
customer_placeholder: "Kunde@beispiel.org"
|
||||
valid_email_error: "Bitte geben Sie eine gültige E-Mail-Adresse ein"
|
||||
subscriptions:
|
||||
error_saving: "Fehler beim Speichern des Abonnements"
|
||||
new:
|
||||
please_select_a_shop: "Bitte wählen Sie einen Laden"
|
||||
insufficient_stock: "Nicht genügend Lagerbestand verfügbar, nur noch %{on_hand} verfügbar"
|
||||
@@ -2575,9 +2611,78 @@ de_DE:
|
||||
have_an_account: "Hast du schon ein Konto?"
|
||||
action_login: "Jetzt einloggen."
|
||||
inflections:
|
||||
each:
|
||||
one: "jeder"
|
||||
other: "pro Stück"
|
||||
bunch:
|
||||
one: "Bündel"
|
||||
other: "Bündel"
|
||||
pack:
|
||||
one: "Pack"
|
||||
other: "Packungen"
|
||||
box:
|
||||
one: "Box"
|
||||
other: "Kisten"
|
||||
bottle:
|
||||
one: "Flasche"
|
||||
other: "Flaschen"
|
||||
jar:
|
||||
one: "Krug"
|
||||
other: "Gläser"
|
||||
head:
|
||||
one: "Kopf"
|
||||
other: "Köpfe"
|
||||
bag:
|
||||
one: "Tasche"
|
||||
other: "Beutel"
|
||||
loaf:
|
||||
one: "Laib"
|
||||
other: "Laibe"
|
||||
single:
|
||||
one: "Einzeln"
|
||||
other: "Einzel"
|
||||
tub:
|
||||
one: "Wanne"
|
||||
other: "Wannen"
|
||||
punnet:
|
||||
one: "Körbchen"
|
||||
other: "Körbchen"
|
||||
packet:
|
||||
one: "Paket"
|
||||
other: "Pakete"
|
||||
item:
|
||||
one: "Artikel"
|
||||
other: "Artikel"
|
||||
dozen:
|
||||
one: "Dutzend"
|
||||
other: "Dutzende"
|
||||
unit:
|
||||
one: "Einheit"
|
||||
other: "Einheiten"
|
||||
serve:
|
||||
one: "Portion"
|
||||
other: "Portionen"
|
||||
tray:
|
||||
one: "Schale"
|
||||
other: "Schalen"
|
||||
piece:
|
||||
one: "Stück"
|
||||
other: "Stücke"
|
||||
pot:
|
||||
one: "Topf"
|
||||
other: "Töpfe"
|
||||
bundle:
|
||||
one: "bündeln"
|
||||
other: "Bündel"
|
||||
flask:
|
||||
one: "Flasche"
|
||||
other: "Flaschen"
|
||||
basket:
|
||||
one: "Korb"
|
||||
other: "Körbe"
|
||||
sack:
|
||||
one: "Sack"
|
||||
other: "Säcke"
|
||||
producers:
|
||||
signup:
|
||||
start_free_profile: "Beginnen Sie mit einem kostenlosen Profil und erweitern Sie es, wenn Sie fertig sind!"
|
||||
@@ -2643,6 +2748,7 @@ de_DE:
|
||||
status: "Status"
|
||||
new: "Neu"
|
||||
start: "Start"
|
||||
end: "Ende"
|
||||
stop: "Halt"
|
||||
first: "Zuerst"
|
||||
previous: "Bisherige"
|
||||
@@ -2819,6 +2925,8 @@ de_DE:
|
||||
zipcode: Postleitzahl
|
||||
weight: Gewicht (pro kg)
|
||||
error_user_destroy_with_orders: "Benutzer mit abgeschlossenen Bestellungen dürfen nicht gelöscht werden"
|
||||
cannot_create_payment_without_payment_methods: "Sie können keine Zahlung für eine Bestellung erstellen, ohne dass Zahlungsmethoden definiert sind."
|
||||
please_define_payment_methods: "Bitte definieren Sie zunächst die Zahlungsmethoden."
|
||||
options: "Optionen"
|
||||
actions:
|
||||
update: "Aktualisieren"
|
||||
@@ -2905,6 +3013,7 @@ de_DE:
|
||||
capture: "Erfassung"
|
||||
ship: "Liefern"
|
||||
edit: "Bearbeiten"
|
||||
order_not_updated: "Die Bestellung konnte nicht aktualisiert werden"
|
||||
note: "Hinweis"
|
||||
first: "Zuerst"
|
||||
last: "Letzte"
|
||||
@@ -2927,6 +3036,8 @@ de_DE:
|
||||
tax_invoice: "Steuerrechnung"
|
||||
code: "Code"
|
||||
from: "Von"
|
||||
to: "Rechnungsempfänger"
|
||||
shipping: "Versand"
|
||||
form:
|
||||
distribution_fields:
|
||||
title: "Verteilung"
|
||||
@@ -3124,6 +3235,12 @@ de_DE:
|
||||
used_saved_card: "Verwende eine gespeicherte Karte:"
|
||||
or_enter_new_card: "Oder geben Sie Details für eine neue Karte ein:"
|
||||
remember_this_card: Erinnerst du dich an diese Karte?
|
||||
stripe_sca:
|
||||
choose_one: Wähle Sie eine Option
|
||||
enter_new_card: Geben Sie Details für eine neue Karte ein
|
||||
used_saved_card: "Verwenden Sie eine gespeicherte Karte:"
|
||||
or_enter_new_card: "Oder geben Sie Details für eine neue Karte ein:"
|
||||
remember_this_card: Diese Karte speichern?
|
||||
date_picker:
|
||||
format: '% Y-% m-%d'
|
||||
js_format: 'JJ-MM-TT'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user