mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-18 19:36:48 +00:00
Compare commits
221 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eafffa2c23 | ||
|
|
82a4753eec | ||
|
|
b92e858448 | ||
|
|
b5ba2acb21 | ||
|
|
e97a16cb40 | ||
|
|
57d87a8042 | ||
|
|
0ca87580e8 | ||
|
|
59e0f3d9f4 | ||
|
|
fc5aff8c79 | ||
|
|
dd717fe8ac | ||
|
|
6341c5fd80 | ||
|
|
47ac6c1491 | ||
|
|
6afda141a1 | ||
|
|
5bb2614f9d | ||
|
|
b3c968856b | ||
|
|
b0a7497f2a | ||
|
|
f959e632ea | ||
|
|
f9cf826f1c | ||
|
|
ececbce596 | ||
|
|
1b7ac1a252 | ||
|
|
d31b24786a | ||
|
|
374bf04118 | ||
|
|
3aff7f62e3 | ||
|
|
fc5e346a06 | ||
|
|
29bbf2fa74 | ||
|
|
64c66ddedc | ||
|
|
e0e2c32d9f | ||
|
|
003341ef7a | ||
|
|
94f8ea2f93 | ||
|
|
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 | ||
|
|
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 | ||
|
|
d254df7ccc | ||
|
|
8caf10f634 | ||
|
|
2e98b0b5c1 | ||
|
|
2966dd9536 | ||
|
|
21e1c0ed0b | ||
|
|
48b99d02b9 | ||
|
|
f17a2eeaea | ||
|
|
18419d0276 | ||
|
|
bd19d8b0bd | ||
|
|
4bcd665379 | ||
|
|
e63dbcfa89 | ||
|
|
c3283adcf5 | ||
|
|
b2ed69831b | ||
|
|
7daba62f43 | ||
|
|
a08020490d | ||
|
|
eb4d970bc7 | ||
|
|
7a3549209f | ||
|
|
52ebd1b402 | ||
|
|
a3a26f704f | ||
|
|
cff8f6dd96 | ||
|
|
81537d92cf | ||
|
|
91e88bd028 | ||
|
|
f5e254a105 | ||
|
|
b41b5d0395 | ||
|
|
296d2e5edb | ||
|
|
ac0a62e962 | ||
|
|
523faa670d | ||
|
|
2c5db8935b | ||
|
|
e5e9325499 | ||
|
|
2e4f8003b6 | ||
|
|
24c8f38111 | ||
|
|
b801bffcd9 | ||
|
|
60d9edb185 | ||
|
|
434b68b019 | ||
|
|
9af4bb9757 | ||
|
|
d847560d7c | ||
|
|
87fae15434 | ||
|
|
e27f7a4301 | ||
|
|
bddfa95eb5 | ||
|
|
ef0fb18fda | ||
|
|
87ee4bbebc | ||
|
|
f5567e556b | ||
|
|
54c3c73ed2 | ||
|
|
8dfdc9bc15 | ||
|
|
36aa52736a | ||
|
|
ac38b2735c | ||
|
|
8b93c5ab56 | ||
|
|
434f98fb46 | ||
|
|
c82c54873c | ||
|
|
de180d32bf | ||
|
|
5b481c19cc | ||
|
|
7110f9e6ee | ||
|
|
63d748b2a4 | ||
|
|
310906c7da | ||
|
|
b81843921b | ||
|
|
dd0e135a4d | ||
|
|
0f2c5d379a | ||
|
|
a29f263041 | ||
|
|
0b878dd0a2 | ||
|
|
45c204017f | ||
|
|
fa98a8ea17 | ||
|
|
7582df2771 | ||
|
|
0e62dc04bd | ||
|
|
e2940eb9ff | ||
|
|
1c1f066884 | ||
|
|
d5cf355a11 | ||
|
|
d2eee1dafd | ||
|
|
540b26105f | ||
|
|
c788f1ae57 | ||
|
|
7baa875a91 | ||
|
|
3de887e1d8 | ||
|
|
45b5e838b7 | ||
|
|
05ccd1ecbf | ||
|
|
f8a4f00d52 | ||
|
|
29377bbff9 | ||
|
|
f68d0c2a0f | ||
|
|
3901c49af9 | ||
|
|
ae0ceb61a1 | ||
|
|
fb1c825fbc | ||
|
|
e36b0249b9 | ||
|
|
34fa2d7ad6 | ||
|
|
3aefea9f04 | ||
|
|
15231a9128 | ||
|
|
25e3f72934 | ||
|
|
523d819575 | ||
|
|
bc0a1d9bae | ||
|
|
a53dc3a8c1 | ||
|
|
1382bb3c6b |
@@ -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
|
||||
@@ -411,7 +402,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
|
||||
@@ -686,6 +677,13 @@ 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/payment_setup_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 +699,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'
|
||||
@@ -849,11 +848,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'
|
||||
@@ -892,6 +886,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 +944,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 +960,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 +1201,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 +1209,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 +1293,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 +1343,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 +1540,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
|
||||
|
||||
6
Gemfile
6
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'
|
||||
@@ -112,7 +108,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 +118,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.1)
|
||||
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.4)
|
||||
parser (2.7.0.5)
|
||||
ast (~> 2.4.0)
|
||||
paypal-sdk-core (0.2.10)
|
||||
multi_json (~> 1.0)
|
||||
@@ -559,8 +543,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 +588,16 @@ 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.4.2)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-rails (2.5.1)
|
||||
activesupport
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
ruby-progressbar (1.10.1)
|
||||
@@ -670,13 +655,16 @@ GEM
|
||||
tzinfo (0.3.56)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unicode-display_width (1.6.1)
|
||||
unicorn (5.5.3)
|
||||
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)
|
||||
@@ -739,7 +727,6 @@ DEPENDENCIES
|
||||
foreigner
|
||||
foundation-icons-sass-rails
|
||||
foundation-rails
|
||||
foundation_rails_helper!
|
||||
fuubar (~> 2.5.0)
|
||||
geocoder
|
||||
gmaps4rails
|
||||
@@ -786,7 +773,6 @@ DEPENDENCIES
|
||||
select2-rails (~> 3.4.7)
|
||||
selenium-webdriver
|
||||
shoulda-matchers
|
||||
simple_form!
|
||||
simplecov
|
||||
spinjs-rails
|
||||
spree_core!
|
||||
@@ -802,6 +788,7 @@ DEPENDENCIES
|
||||
uglifier (>= 1.0.3)
|
||||
unicorn
|
||||
unicorn-rails
|
||||
unicorn-worker-killer
|
||||
web!
|
||||
webdrivers
|
||||
webmock
|
||||
|
||||
@@ -35,7 +35,7 @@ We use [BrowserStack](https://www.browserstack.com/) as a manual testing tool. B
|
||||
Copyright (c) 2012 - 2020 Open Food Foundation, released under the AGPL licence.
|
||||
|
||||
[survey]: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit#
|
||||
[slack-invite]: https://join.slack.com/t/openfoodnetwork/shared_invite/enQtNzY3NDEwNzM2MDM0LWFmNGRhNDUwYzNmNWNkYmFkMzgxNDg1OTg1ODNjNWY4Y2FhNDIwNmE4ZWI0OThiMGNmZjFkODczNGZiYTJmNWI
|
||||
[slack-invite]: https://join.slack.com/t/openfoodnetwork/shared_invite/zt-9sjkjdlu-r02kUMP1zbrTgUhZhYPF~A
|
||||
[contributor-guide]: https://ofn-user-guide.gitbook.io/ofn-contributor-guide/who-are-we
|
||||
[ofn-install]: https://github.com/openfoodfoundation/ofn-install
|
||||
[super-admin-guide]: https://ofn-user-guide.gitbook.io/ofn-super-admin-guide
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,77 +1,13 @@
|
||||
@import "typography";
|
||||
|
||||
.darkswarm navigation {
|
||||
display: block;
|
||||
background: $white;
|
||||
|
||||
distributor.details {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
min-height: 150px;
|
||||
padding: 30px 0 20px 0;
|
||||
position: relative;
|
||||
|
||||
select {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
location {
|
||||
@include headingFont;
|
||||
}
|
||||
@media all and (max-width: 768px) {
|
||||
location, location + small {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
#distributor_title h3 {
|
||||
margin-top: 0;
|
||||
padding-top: 0.45em;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ordercycle {
|
||||
float: right;
|
||||
background: $teal-400;
|
||||
color: $white;
|
||||
background: $grey-050;
|
||||
color: $grey-800;
|
||||
width: 100%;
|
||||
border-radius: 0.5em 0.5em 0 0;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
padding: 1em;
|
||||
margin-top: 3em;
|
||||
height: 7.6em;
|
||||
|
||||
&.requires-selection {
|
||||
background-color: $red-700;
|
||||
|
||||
.order-cycle-select {
|
||||
border: 1px solid $red-500;
|
||||
|
||||
.select-label {
|
||||
background-color: rgba($red-500, 0.5);
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: $white;
|
||||
background-image: url('/assets/black-caret.svg');
|
||||
color: $grey-500;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
margin-top: 1em;
|
||||
padding: 1em 1.25em 0;
|
||||
|
||||
p {
|
||||
max-width: 400px;
|
||||
@@ -97,14 +33,10 @@ ordercycle {
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@media all and (max-width: 480px) {
|
||||
padding: 0.5em 1em 0.75em;
|
||||
}
|
||||
|
||||
form.custom {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.order-cycle-select {
|
||||
border: 1px solid $teal-300;
|
||||
display: inline-block;
|
||||
@@ -164,9 +96,44 @@ ordercycle {
|
||||
|
||||
closing {
|
||||
@include headingFont;
|
||||
color: $white;
|
||||
color: $grey-800;
|
||||
font-size: 1.25rem;
|
||||
display: block;
|
||||
padding: 0.5em 0;
|
||||
|
||||
span {
|
||||
@media all and (max-width: 768px) {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shop ordercycle {
|
||||
background: $teal-400;
|
||||
color: $white;
|
||||
|
||||
&.requires-selection {
|
||||
background-color: $red-700;
|
||||
|
||||
.order-cycle-select {
|
||||
border: 1px solid $red-500;
|
||||
|
||||
.select-label {
|
||||
background-color: rgba($red-500, 0.5);
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: $white;
|
||||
background-image: url('/assets/black-caret.svg');
|
||||
color: $grey-500;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closing {
|
||||
color: $white;
|
||||
padding: 0 0 12px;
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
@@ -181,11 +148,25 @@ ordercycle {
|
||||
float: none;
|
||||
padding: 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
@media all and (max-width: 768px) {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
form.custom {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
shop navigation ordercycle {
|
||||
margin-top: 3em;
|
||||
padding: 1em;
|
||||
height: 7.6em;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
.sub-header {
|
||||
form {
|
||||
p {
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,6 @@
|
||||
@import "mixins";
|
||||
@import "branding";
|
||||
|
||||
// .darkswarm
|
||||
// product
|
||||
|
||||
ordercycle {
|
||||
.joyride-tip-guide {
|
||||
background-color: $clr-brick;
|
||||
|
||||
.joyride-nub.right {
|
||||
border-color: $clr-brick !important;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop over
|
||||
// Foundation overrides
|
||||
.joyride-tip-guide.price_breakdown {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
}
|
||||
|
||||
products .filter-box {
|
||||
background: #f7f7f7;
|
||||
background: $grey-050;
|
||||
}
|
||||
|
||||
.filter-box {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
$ofn-brand: #f27052;
|
||||
|
||||
$distributor-header-shadow: 0 1px 0 rgba(0, 0, 0, 0.05), 0 8px 6px -6px rgba(0, 0, 0, 0.2);
|
||||
|
||||
// e.g. australia, uk, norway specific color
|
||||
|
||||
$ofn-grey: #808184;
|
||||
@@ -39,6 +41,8 @@ $light-grey-transparency: rgba(0, 0, 0, .1);
|
||||
$black: #000;
|
||||
$white: #fff;
|
||||
|
||||
$grey-050: #f7f7f7;
|
||||
|
||||
$grey-400: #bbb;
|
||||
$grey-500: #999;
|
||||
$grey-600: #777;
|
||||
|
||||
@@ -44,7 +44,7 @@ checkout {
|
||||
h5 {
|
||||
margin: 0;
|
||||
padding: 0.65em;
|
||||
background: #f7f7f7;
|
||||
background: $grey-050;
|
||||
|
||||
.label {
|
||||
font-size: 1em;
|
||||
@@ -109,9 +109,4 @@ checkout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #c82020;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
52
app/assets/stylesheets/darkswarm/distributor_header.css.scss
Normal file
52
app/assets/stylesheets/darkswarm/distributor_header.css.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
@import 'typography';
|
||||
|
||||
section {
|
||||
:not(shop) navigation {
|
||||
box-shadow: $distributor-header-shadow;
|
||||
}
|
||||
}
|
||||
|
||||
.darkswarm navigation {
|
||||
display: block;
|
||||
background: $white;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
.details {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
min-height: 150px;
|
||||
padding: 30px 0 0;
|
||||
position: relative;
|
||||
|
||||
select {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
location {
|
||||
@include headingFont;
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
location, location + small {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
#distributor_title h3 {
|
||||
margin-top: 0;
|
||||
padding-top: 0.45em;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,10 @@
|
||||
|
||||
.more-controls {
|
||||
text-align: center;
|
||||
|
||||
.spinner {
|
||||
height: 2.25em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
.tab-buttons {
|
||||
color: $dark-grey;
|
||||
box-shadow: 0 1px 0 rgba(0,0,0,0.05), 0 8px 6px -6px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: $distributor-header-shadow;
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -32,6 +32,12 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@object = Enterprise.where(permalink: params[:id]).
|
||||
includes(users: [:ship_address, :bill_address]).first
|
||||
super
|
||||
end
|
||||
|
||||
def welcome
|
||||
render layout: "spree/layouts/bare_admin"
|
||||
end
|
||||
@@ -172,12 +178,14 @@ module Admin
|
||||
end
|
||||
|
||||
def load_methods_and_fees
|
||||
enterprise_payment_methods = @enterprise.payment_methods.to_a
|
||||
enterprise_shipping_methods = @enterprise.shipping_methods.to_a
|
||||
# rubocop:disable Style/TernaryParentheses
|
||||
@payment_methods = Spree::PaymentMethod.managed_by(spree_current_user).sort_by! do |pm|
|
||||
[(@enterprise.payment_methods.include? pm) ? 0 : 1, pm.name]
|
||||
[(enterprise_payment_methods.include? pm) ? 0 : 1, pm.name]
|
||||
end
|
||||
@shipping_methods = Spree::ShippingMethod.managed_by(spree_current_user).sort_by! do |sm|
|
||||
[(@enterprise.shipping_methods.include? sm) ? 0 : 1, sm.name]
|
||||
[(enterprise_shipping_methods.include? sm) ? 0 : 1, sm.name]
|
||||
end
|
||||
# rubocop:enable Style/TernaryParentheses
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Api
|
||||
class CustomersController < BaseController
|
||||
class CustomersController < Api::BaseController
|
||||
skip_authorization_check only: :index
|
||||
|
||||
def index
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Api
|
||||
class EnterpriseAttachmentController < BaseController
|
||||
class EnterpriseAttachmentController < Api::BaseController
|
||||
class MissingImplementationError < StandardError; end
|
||||
class UnknownEnterpriseAuthorizationActionError < StandardError; end
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Api
|
||||
class EnterpriseFeesController < BaseController
|
||||
class EnterpriseFeesController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def destroy
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Api
|
||||
class LogosController < EnterpriseAttachmentController
|
||||
class LogosController < Api::EnterpriseAttachmentController
|
||||
private
|
||||
|
||||
def attachment_name
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Api
|
||||
class OrderCyclesController < BaseController
|
||||
class OrderCyclesController < Api::BaseController
|
||||
include EnterprisesHelper
|
||||
respond_to :json
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Api
|
||||
class OrdersController < BaseController
|
||||
class OrdersController < Api::BaseController
|
||||
def show
|
||||
authorize! :read, order
|
||||
render json: order, serializer: Api::OrderDetailedSerializer, current_order: order
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Api
|
||||
class ProductImagesController < BaseController
|
||||
class ProductImagesController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def update_product_image
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module Api
|
||||
class PromoImagesController < EnterpriseAttachmentController
|
||||
class PromoImagesController < Api::EnterpriseAttachmentController
|
||||
private
|
||||
|
||||
def attachment_name
|
||||
|
||||
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
|
||||
|
||||
@@ -8,7 +8,7 @@ class ProducersController < BaseController
|
||||
.activated
|
||||
.visible
|
||||
.is_primary_producer
|
||||
.includes(address: :state)
|
||||
.includes(address: [:state, :country])
|
||||
.includes(:properties)
|
||||
.includes(supplied_products: :properties)
|
||||
.all
|
||||
|
||||
@@ -4,13 +4,6 @@ class ShopsController < BaseController
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
def index
|
||||
@enterprises = Enterprise
|
||||
.activated
|
||||
.visible
|
||||
.is_distributor
|
||||
.includes(address: :state)
|
||||
.includes(:properties)
|
||||
.includes(supplied_products: :properties)
|
||||
.all
|
||||
@enterprises = ShopsListService.new.open_shops
|
||||
end
|
||||
end
|
||||
|
||||
@@ -109,7 +109,10 @@ module Spree
|
||||
private
|
||||
|
||||
def load_order
|
||||
@order = Order.find_by_number!(params[:id], include: :adjustments) if params[:id]
|
||||
if params[:id]
|
||||
@order = Order.includes(:adjustments, :shipments, line_items: :adjustments).
|
||||
find_by_number!(params[:id])
|
||||
end
|
||||
authorize! action, @order
|
||||
end
|
||||
|
||||
@@ -128,7 +131,7 @@ module Spree
|
||||
def load_distribution_choices
|
||||
@shops = Enterprise.is_distributor.managed_by(spree_current_user).by_name
|
||||
|
||||
ocs = OrderCycle.managed_by(spree_current_user)
|
||||
ocs = OrderCycle.includes(:suppliers, :distributors).managed_by(spree_current_user)
|
||||
@order_cycles = ocs.soonest_closing +
|
||||
ocs.soonest_opening +
|
||||
ocs.closed +
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
module ApplicationHelper
|
||||
include FoundationRailsHelper::FlashHelper
|
||||
|
||||
def feature?(feature)
|
||||
OpenFoodNetwork::FeatureToggle.enabled? feature
|
||||
end
|
||||
|
||||
@@ -3,10 +3,10 @@ require 'open_food_network/enterprise_injection_data'
|
||||
module InjectionHelper
|
||||
include SerializerHelper
|
||||
|
||||
def inject_enterprises(enterprises = Enterprise.activated.includes(address: :state).all)
|
||||
def inject_enterprises(enterprises = nil)
|
||||
inject_json_ams(
|
||||
"enterprises",
|
||||
enterprises,
|
||||
enterprises || default_enterprise_query,
|
||||
Api::EnterpriseSerializer,
|
||||
enterprise_injection_data
|
||||
)
|
||||
@@ -17,7 +17,10 @@ module InjectionHelper
|
||||
|
||||
inject_json_ams(
|
||||
"groups",
|
||||
EnterpriseGroup.on_front_page.by_position.select(select_only).includes(address: :state).all,
|
||||
EnterpriseGroup.on_front_page.by_position.select(select_only).
|
||||
includes(enterprises: [:shipping_methods, { address: [:state, :country] }],
|
||||
address: :state).
|
||||
all,
|
||||
Api::GroupListSerializer
|
||||
)
|
||||
end
|
||||
@@ -35,13 +38,21 @@ module InjectionHelper
|
||||
|
||||
inject_json_ams(
|
||||
"enterprises",
|
||||
Enterprise.activated.visible.select(select_only).includes(address: :state).all,
|
||||
Enterprise.activated.visible.select(select_only).includes(address: [:state, :country]).all,
|
||||
Api::EnterpriseShopfrontListSerializer
|
||||
)
|
||||
end
|
||||
|
||||
def inject_enterprise_and_relatives
|
||||
inject_json_ams "enterprises", current_distributor.relatives_including_self.activated.includes(address: :state).all, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
enterprises_and_relatives = current_distributor.
|
||||
relatives_including_self.
|
||||
activated.
|
||||
includes(address: [:state, :country]).
|
||||
all
|
||||
|
||||
inject_json_ams "enterprises",
|
||||
enterprises_and_relatives,
|
||||
Api::EnterpriseSerializer, enterprise_injection_data
|
||||
end
|
||||
|
||||
def inject_group_enterprises
|
||||
@@ -138,6 +149,10 @@ module InjectionHelper
|
||||
|
||||
private
|
||||
|
||||
def default_enterprise_query
|
||||
Enterprise.activated.includes(address: [:state, :country]).all
|
||||
end
|
||||
|
||||
def enterprise_injection_data
|
||||
@enterprise_injection_data ||= OpenFoodNetwork::EnterpriseInjectionData.new
|
||||
{ data: @enterprise_injection_data }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,5 +5,21 @@ module Spree
|
||||
def variant_options(v, _options = {})
|
||||
v.options_text
|
||||
end
|
||||
|
||||
# Overriden to eager-load :states
|
||||
def available_countries
|
||||
checkout_zone = Zone.find_by_name(Spree::Config[:checkout_zone])
|
||||
|
||||
countries = if checkout_zone && checkout_zone.kind == 'country'
|
||||
checkout_zone.country_list
|
||||
else
|
||||
Country.includes(:states).all
|
||||
end
|
||||
|
||||
countries.collect do |country|
|
||||
country.name = Spree.t(country.iso, scope: 'country_names', default: country.name)
|
||||
country
|
||||
end.sort { |a, b| a.name <=> b.name }
|
||||
end
|
||||
end
|
||||
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,43 @@ 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.present?
|
||||
|
||||
order.process_payments!
|
||||
return false if order.errors.present?
|
||||
|
||||
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!
|
||||
end
|
||||
|
||||
def send_failed_payment_email
|
||||
@order.update!
|
||||
record_and_log_error(:failed_payment, @order)
|
||||
SubscriptionMailer.failed_payment_email(@order).deliver
|
||||
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
|
||||
|
||||
@@ -54,6 +54,8 @@ module Calculator
|
||||
# Customer ends up getting 350mL (line_item.final_weight_volume) of wine
|
||||
# that represent 2.8 (quantity_implied_in_final_weight_volume) glasses of wine
|
||||
def quantity_implied_in_final_weight_volume(line_item)
|
||||
return line_item.quantity if line_item.variant.unit_value.to_f.zero?
|
||||
|
||||
(1.0 * line_item.final_weight_volume / line_item.variant.unit_value).round(3)
|
||||
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,6 +36,10 @@ Spree::LineItem.class_eval do
|
||||
end
|
||||
}
|
||||
|
||||
scope :in_orders, lambda { |orders|
|
||||
where(order_id: orders)
|
||||
}
|
||||
|
||||
# Find line items that are from order sorted by variant name and unit value
|
||||
scope :sorted_by_name_and_unit_value, -> {
|
||||
joins(variant: :product).
|
||||
|
||||
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
|
||||
@@ -24,8 +24,10 @@ module Stock
|
||||
#
|
||||
# @return [Array<Spree::ShippingMethod>]
|
||||
def shipping_methods
|
||||
super.delete_if do |shipping_method|
|
||||
!ships_with?(order.distributor, shipping_method)
|
||||
available_shipping_methods = super.to_a
|
||||
|
||||
available_shipping_methods.keep_if do |shipping_method|
|
||||
ships_with?(order.distributor.shipping_methods.to_a, shipping_method)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,11 +35,11 @@ module Stock
|
||||
|
||||
# Checks whether the given distributor provides the specified shipping method
|
||||
#
|
||||
# @param distributor [Spree::Enterprise]
|
||||
# @param shipping_methods [Array<Spree::ShippingMethod>]
|
||||
# @param shipping_method [Spree::ShippingMethod]
|
||||
# @return [Boolean]
|
||||
def ships_with?(distributor, shipping_method)
|
||||
distributor.shipping_methods.include?(shipping_method)
|
||||
def ships_with?(shipping_methods, shipping_method)
|
||||
shipping_methods.include?(shipping_method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
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: {
|
||||
@@ -107,7 +113,7 @@ module Api
|
||||
end
|
||||
|
||||
def active
|
||||
data.active_distributors.andand.include? enterprise
|
||||
data.active_distributor_ids.andand.include? enterprise.id
|
||||
end
|
||||
|
||||
# Map svg icons.
|
||||
|
||||
@@ -9,7 +9,7 @@ module Api
|
||||
end
|
||||
|
||||
def active
|
||||
options[:data].active_distributors.andand.include? object
|
||||
options[:data].active_distributor_ids.andand.include? object.id
|
||||
end
|
||||
end
|
||||
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?
|
||||
|
||||
@@ -2,24 +2,28 @@ require 'open_food_network/permissions'
|
||||
|
||||
module Permissions
|
||||
class Order
|
||||
def initialize(user)
|
||||
def initialize(user, search_params = nil)
|
||||
@user = user
|
||||
@permissions = OpenFoodNetwork::Permissions.new(@user)
|
||||
@search_params = search_params
|
||||
end
|
||||
|
||||
# Find orders that the user can see
|
||||
def visible_orders
|
||||
Spree::Order.
|
||||
orders = Spree::Order.
|
||||
with_line_items_variants_and_products_outer.
|
||||
where(visible_orders_where_values)
|
||||
|
||||
filtered_orders(orders)
|
||||
end
|
||||
|
||||
# Any orders that the user can edit
|
||||
def editable_orders
|
||||
Spree::Order.where(
|
||||
managed_orders_where_values.
|
||||
or(coordinated_orders_where_values)
|
||||
)
|
||||
orders = Spree::Order.
|
||||
where(managed_orders_where_values.
|
||||
or(coordinated_orders_where_values))
|
||||
|
||||
filtered_orders(orders)
|
||||
end
|
||||
|
||||
def visible_line_items
|
||||
@@ -35,6 +39,18 @@ module Permissions
|
||||
|
||||
private
|
||||
|
||||
attr_reader :search_params
|
||||
|
||||
def filtered_orders(orders)
|
||||
return orders unless filter_orders?
|
||||
|
||||
orders.complete.not_state(:canceled).search(search_params).result
|
||||
end
|
||||
|
||||
def filter_orders?
|
||||
search_params.present?
|
||||
end
|
||||
|
||||
def visible_orders_where_values
|
||||
# Grouping keeps the 2 where clauses from produced_orders_where_values inside parentheses
|
||||
# This way it makes the OR work between the 3 types of orders:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
.darkswarm.footer-pad
|
||||
- content_for :order_cycle_form do
|
||||
|
||||
%closing
|
||||
= t :checkout_now
|
||||
%p
|
||||
@@ -15,16 +14,23 @@
|
||||
%strong
|
||||
= pickup_time current_order_cycle
|
||||
|
||||
= render partial: "shopping_shared/header", locals: { hide_oc_selector: true }
|
||||
- content_for :ordercycle_sidebar do
|
||||
.show-for-large-up.large-4.columns
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
|
||||
= render partial: "shopping_shared/header"
|
||||
|
||||
.sub-header.show-for-medium-down
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
|
||||
%accordion{"close-others" => "false"}
|
||||
%checkout.row{"ng-controller" => "CheckoutCtrl"}
|
||||
.small-12.medium-8.large-9.columns
|
||||
.small-12.medium-8.columns
|
||||
- unless spree_current_user
|
||||
= render partial: "checkout/authentication"
|
||||
%div{"ng-show" => "enabled", "ng-controller" => "AccordionCtrl"}
|
||||
= render partial: "checkout/form"
|
||||
.small-12.medium-4.large-3.columns
|
||||
.small-12.medium-4.columns
|
||||
= render partial: "checkout/summary"
|
||||
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
- if oc_select_options.count > 1
|
||||
%option{value: "", disabled: "", selected: ""}= t :shopping_oc_select
|
||||
|
||||
- content_for :ordercycle_sidebar do
|
||||
.show-for-large-up.large-4.columns
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
|
||||
= render partial: "shopping_shared/header"
|
||||
= render partial: "shopping_shared/tabs"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,4 @@
|
||||
= distributor.name
|
||||
%location= distributor.address.city
|
||||
|
||||
- unless defined? hide_oc_selector
|
||||
.show-for-large-up.large-4.columns
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
= yield :ordercycle_sidebar
|
||||
|
||||
@@ -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'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user