mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-13 18:46:49 +00:00
Compare commits
248 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6ba956355 | ||
|
|
76cffe4c7f | ||
|
|
26477a8d4b | ||
|
|
52ab6c52bf | ||
|
|
413e93fe40 | ||
|
|
9e1b2eb4ca | ||
|
|
1ceae6cf8d | ||
|
|
5183d93601 | ||
|
|
247854b7fe | ||
|
|
85bd803785 | ||
|
|
c6543edc85 | ||
|
|
a91d0973d4 | ||
|
|
61c581ae52 | ||
|
|
db8e2891d4 | ||
|
|
a947d8df6c | ||
|
|
818d41ffac | ||
|
|
14f67053c8 | ||
|
|
76e32b126f | ||
|
|
c234cfb890 | ||
|
|
38bda1697d | ||
|
|
5fb9ebc594 | ||
|
|
acbd4b076d | ||
|
|
123f7aa68e | ||
|
|
2c4af84a81 | ||
|
|
34ed16ff30 | ||
|
|
0b20b80219 | ||
|
|
bef745378e | ||
|
|
a6cb5903d6 | ||
|
|
d1b36aded0 | ||
|
|
9c3c74aa93 | ||
|
|
67a5a1cdc2 | ||
|
|
9723e2cd49 | ||
|
|
4af014df6b | ||
|
|
be6c64db75 | ||
|
|
1aebc30128 | ||
|
|
f8209ac7d5 | ||
|
|
6d50176e6b | ||
|
|
d6d2c19dc7 | ||
|
|
f897478736 | ||
|
|
064b86da5d | ||
|
|
f7b58300f9 | ||
|
|
0fe4edfbf5 | ||
|
|
f45eb35eb1 | ||
|
|
1a0e99dce2 | ||
|
|
d7caf91de1 | ||
|
|
1e2ab27cda | ||
|
|
3e7bd7dc19 | ||
|
|
c60261a847 | ||
|
|
11fea650d6 | ||
|
|
92f1fa3b52 | ||
|
|
ad52022927 | ||
|
|
9cffe48c70 | ||
|
|
d006ded439 | ||
|
|
b2e5ff46a8 | ||
|
|
f46e0a2a31 | ||
|
|
dd66df6379 | ||
|
|
d7b9dc1190 | ||
|
|
5b6efaf687 | ||
|
|
8aab9bacbe | ||
|
|
66fdbe4379 | ||
|
|
abcc22c34b | ||
|
|
0d34b607c3 | ||
|
|
f840179573 | ||
|
|
b5a521476b | ||
|
|
7c64777a50 | ||
|
|
c98b4b276b | ||
|
|
f1138709aa | ||
|
|
b29983ac60 | ||
|
|
91188c5724 | ||
|
|
bf291ec318 | ||
|
|
5846593637 | ||
|
|
688dad2334 | ||
|
|
6d419d60ae | ||
|
|
0dc8ae1561 | ||
|
|
f396f6bebd | ||
|
|
07fcc8f361 | ||
|
|
237cd5438b | ||
|
|
4e366d0f2e | ||
|
|
fb3af77d0b | ||
|
|
1fa6e4bea8 | ||
|
|
498a2b7462 | ||
|
|
80edfe469c | ||
|
|
b2616d317f | ||
|
|
0ac0bdc381 | ||
|
|
1a756cbc6b | ||
|
|
34466c8218 | ||
|
|
00e869f40c | ||
|
|
42cd9a5152 | ||
|
|
cc342387cc | ||
|
|
6715f872e3 | ||
|
|
e849c4867b | ||
|
|
ada34d27ca | ||
|
|
40c329ba68 | ||
|
|
3c7af90dfa | ||
|
|
241e581779 | ||
|
|
85dede84cc | ||
|
|
0288dfc992 | ||
|
|
3372339907 | ||
|
|
f7bb609546 | ||
|
|
02c0b89fa0 | ||
|
|
f57c9d4a25 | ||
|
|
783c3c9e90 | ||
|
|
44753d0320 | ||
|
|
dd7d5803ba | ||
|
|
c9e23154d8 | ||
|
|
279b633513 | ||
|
|
9d0ac79983 | ||
|
|
deb17f47a7 | ||
|
|
61ee0f04a6 | ||
|
|
353804a3fa | ||
|
|
598426a5e9 | ||
|
|
7b0c55e15a | ||
|
|
cbe2477d04 | ||
|
|
c730958fe4 | ||
|
|
37e5e1923c | ||
|
|
542c1bf684 | ||
|
|
06c896b93b | ||
|
|
6433d69d02 | ||
|
|
bf8c632fce | ||
|
|
b3c89a9d6c | ||
|
|
d45403f1d4 | ||
|
|
f2affe80cd | ||
|
|
573a69477f | ||
|
|
c6ce516129 | ||
|
|
2539b84b33 | ||
|
|
dd6d0d25da | ||
|
|
c54cff10d4 | ||
|
|
ab330e882e | ||
|
|
a50ae3f8ce | ||
|
|
9f3b4100c3 | ||
|
|
b625ea0c61 | ||
|
|
8857404ddf | ||
|
|
49f98422fd | ||
|
|
20cabe6f12 | ||
|
|
fe849b8dfd | ||
|
|
167b44c30f | ||
|
|
ad247e2116 | ||
|
|
e46875376e | ||
|
|
4d49dc3689 | ||
|
|
d7223c508a | ||
|
|
a91a23aa39 | ||
|
|
8d30dc997f | ||
|
|
370f66e36b | ||
|
|
f7cdcf02e0 | ||
|
|
cf40bfa58e | ||
|
|
8e03f402b1 | ||
|
|
1ee43fb495 | ||
|
|
5a84a3688b | ||
|
|
e139d24639 | ||
|
|
0dfe749496 | ||
|
|
d6eade6fa3 | ||
|
|
109aed684e | ||
|
|
35d76ac991 | ||
|
|
1ab9e9d7a5 | ||
|
|
e5e716e150 | ||
|
|
b6510c3ab6 | ||
|
|
a158a986c6 | ||
|
|
c7643db66f | ||
|
|
eb8c22aa06 | ||
|
|
d0656485d7 | ||
|
|
640cc1b6d3 | ||
|
|
d65d17a9f3 | ||
|
|
bbea00e431 | ||
|
|
3cecba70e8 | ||
|
|
8bbff09066 | ||
|
|
a7a89d7ccb | ||
|
|
3ccf76ff5f | ||
|
|
6004208496 | ||
|
|
3ce9c712cf | ||
|
|
bfb0032fd2 | ||
|
|
fe37516ead | ||
|
|
289b75e143 | ||
|
|
09f0f8c33f | ||
|
|
8bc1718978 | ||
|
|
cbfce69a6d | ||
|
|
4e0ecdd44c | ||
|
|
23ab9a4bed | ||
|
|
f623446e3e | ||
|
|
6944fe1e46 | ||
|
|
7c9e3d7f06 | ||
|
|
614dc5d255 | ||
|
|
da7456e6e0 | ||
|
|
f134cd9473 | ||
|
|
bdcadf9fc6 | ||
|
|
9b7139fd45 | ||
|
|
543e275d2e | ||
|
|
560fa6b949 | ||
|
|
dbf34da87b | ||
|
|
d6022062e1 | ||
|
|
b082d3301b | ||
|
|
48cd542138 | ||
|
|
e2d341c9c2 | ||
|
|
89873a2640 | ||
|
|
b4be2cc2d4 | ||
|
|
35f89a9750 | ||
|
|
592a53b6f5 | ||
|
|
eab9d42eb0 | ||
|
|
cf05369ca9 | ||
|
|
e96252f2ed | ||
|
|
06e1f56ae9 | ||
|
|
fe0de98821 | ||
|
|
a2bc61cb4d | ||
|
|
d997b8f5ee | ||
|
|
cf3f321632 | ||
|
|
797a3ad091 | ||
|
|
01d1e8243c | ||
|
|
a1a5c3b7fe | ||
|
|
bc826f73a1 | ||
|
|
7c264af0c2 | ||
|
|
c5a17bcde0 | ||
|
|
b712ec7f13 | ||
|
|
32a7f13dd2 | ||
|
|
713769b497 | ||
|
|
495de37620 | ||
|
|
05d24cf11a | ||
|
|
edd84530af | ||
|
|
6677543de0 | ||
|
|
30aa31252b | ||
|
|
b83d74a609 | ||
|
|
d31b50be3d | ||
|
|
42e3f2f2f4 | ||
|
|
96737da128 | ||
|
|
7f9f0d840c | ||
|
|
686840e262 | ||
|
|
2377b833ee | ||
|
|
3f3c33bce6 | ||
|
|
e35eff95bb | ||
|
|
de9476a8a5 | ||
|
|
bce41a2247 | ||
|
|
4c4bdd78e7 | ||
|
|
36883bc051 | ||
|
|
bb56e9a5b9 | ||
|
|
50731e929e | ||
|
|
baa09b88f7 | ||
|
|
2f60a85593 | ||
|
|
78cf35807a | ||
|
|
eb85dccac1 | ||
|
|
f32454b404 | ||
|
|
591efecde6 | ||
|
|
24afa21885 | ||
|
|
bdb3dd5aaf | ||
|
|
25fbab2e37 | ||
|
|
4d37aaac64 | ||
|
|
19e28cb14a | ||
|
|
a44a251d96 | ||
|
|
2921ee19e1 | ||
|
|
6796d91a07 | ||
|
|
69afcf7510 |
@@ -110,18 +110,6 @@ Metrics/LineLength:
|
||||
- app/models/variant_override.rb
|
||||
- app/models/variant_override_set.rb
|
||||
- app/overrides/add_enterprise_fees_to_admin_configurations_menu.rb
|
||||
- app/serializers/api/admin/basic_enterprise_serializer.rb
|
||||
- app/serializers/api/admin/enterprise_fee_serializer.rb
|
||||
- app/serializers/api/admin/enterprise_serializer.rb
|
||||
- app/serializers/api/admin/exchange_serializer.rb
|
||||
- app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb
|
||||
- app/serializers/api/admin/index_enterprise_serializer.rb
|
||||
- app/serializers/api/admin/index_order_cycle_serializer.rb
|
||||
- app/serializers/api/admin/line_item_serializer.rb
|
||||
- app/serializers/api/admin/order_cycle_serializer.rb
|
||||
- app/serializers/api/admin/subscription_serializer.rb
|
||||
- app/serializers/api/admin/tag_rule_serializer.rb
|
||||
- app/serializers/api/admin/variant_override_serializer.rb
|
||||
- app/services/cart_service.rb
|
||||
- app/services/default_stock_location.rb
|
||||
- app/services/embedded_page_service.rb
|
||||
@@ -148,11 +136,9 @@ Metrics/LineLength:
|
||||
- lib/open_food_network/order_cycle_form_applicator.rb
|
||||
- lib/open_food_network/order_cycle_management_report.rb
|
||||
- lib/open_food_network/order_grouper.rb
|
||||
- lib/open_food_network/orders_and_fulfillments_report.rb
|
||||
- lib/open_food_network/payments_report.rb
|
||||
- lib/open_food_network/permalink_generator.rb
|
||||
- lib/open_food_network/products_cache.rb
|
||||
- lib/open_food_network/products_renderer.rb
|
||||
- lib/open_food_network/reports/bulk_coop_allocation_report.rb
|
||||
- lib/open_food_network/reports/line_items.rb
|
||||
- lib/open_food_network/sales_tax_report.rb
|
||||
@@ -262,13 +248,10 @@ Metrics/LineLength:
|
||||
- spec/helpers/order_cycles_helper_spec.rb
|
||||
- spec/helpers/spree/admin/base_helper_spec.rb
|
||||
- spec/jobs/confirm_order_job_spec.rb
|
||||
- spec/jobs/products_cache_integrity_checker_job_spec.rb
|
||||
- spec/jobs/refresh_products_cache_job_spec.rb
|
||||
- spec/jobs/subscription_confirm_job_spec.rb
|
||||
- spec/jobs/subscription_placement_job_spec.rb
|
||||
- spec/lib/open_food_network/address_finder_spec.rb
|
||||
- spec/lib/open_food_network/bulk_coop_report_spec.rb
|
||||
- spec/lib/open_food_network/cached_products_renderer_spec.rb
|
||||
- spec/lib/open_food_network/customers_report_spec.rb
|
||||
- spec/lib/open_food_network/enterprise_fee_applicator_spec.rb
|
||||
- spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
|
||||
@@ -285,7 +268,6 @@ Metrics/LineLength:
|
||||
- spec/lib/open_food_network/permissions_spec.rb
|
||||
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
|
||||
- spec/lib/open_food_network/products_cache_spec.rb
|
||||
- spec/lib/open_food_network/products_renderer_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
|
||||
@@ -454,7 +436,6 @@ Metrics/AbcSize:
|
||||
- lib/open_food_network/order_cycle_form_applicator.rb
|
||||
- lib/open_food_network/order_cycle_management_report.rb
|
||||
- lib/open_food_network/order_cycle_permissions.rb
|
||||
- lib/open_food_network/orders_and_fulfillments_report.rb
|
||||
- lib/open_food_network/packing_report.rb
|
||||
- lib/open_food_network/payments_report.rb
|
||||
- lib/open_food_network/permissions.rb
|
||||
@@ -532,7 +513,6 @@ Metrics/CyclomaticComplexity:
|
||||
- lib/discourse/single_sign_on.rb
|
||||
- lib/open_food_network/bulk_coop_report.rb
|
||||
- lib/open_food_network/enterprise_issue_validator.rb
|
||||
- lib/open_food_network/orders_and_fulfillments_report.rb
|
||||
- lib/spree/core/controller_helpers/order_decorator.rb
|
||||
- lib/spree/core/controller_helpers/respond_with_decorator.rb
|
||||
- lib/spree/localized_number.rb
|
||||
@@ -557,7 +537,6 @@ Metrics/PerceivedComplexity:
|
||||
- lib/discourse/single_sign_on.rb
|
||||
- lib/open_food_network/bulk_coop_report.rb
|
||||
- lib/open_food_network/enterprise_issue_validator.rb
|
||||
- lib/open_food_network/orders_and_fulfillments_report.rb
|
||||
- lib/spree/core/controller_helpers/order_decorator.rb
|
||||
- lib/spree/core/controller_helpers/respond_with_decorator.rb
|
||||
- lib/spree/localized_number.rb
|
||||
@@ -627,12 +606,10 @@ Metrics/MethodLength:
|
||||
- lib/open_food_network/order_cycle_management_report.rb
|
||||
- lib/open_food_network/order_cycle_permissions.rb
|
||||
- lib/open_food_network/order_grouper.rb
|
||||
- lib/open_food_network/orders_and_fulfillments_report.rb
|
||||
- lib/open_food_network/packing_report.rb
|
||||
- lib/open_food_network/payments_report.rb
|
||||
- lib/open_food_network/permissions.rb
|
||||
- lib/open_food_network/products_and_inventory_report.rb
|
||||
- lib/open_food_network/products_renderer.rb
|
||||
- lib/open_food_network/rack_request_blocker.rb
|
||||
- lib/open_food_network/reports/bulk_coop_allocation_report.rb
|
||||
- lib/open_food_network/reports/bulk_coop_supplier_report.rb
|
||||
@@ -671,7 +648,6 @@ Metrics/ClassLength:
|
||||
- lib/open_food_network/order_cycle_form_applicator.rb
|
||||
- lib/open_food_network/order_cycle_management_report.rb
|
||||
- lib/open_food_network/order_cycle_permissions.rb
|
||||
- lib/open_food_network/orders_and_fulfillments_report.rb
|
||||
- lib/open_food_network/packing_report.rb
|
||||
- lib/open_food_network/payments_report.rb
|
||||
- lib/open_food_network/permissions.rb
|
||||
@@ -688,7 +664,6 @@ Metrics/ModuleLength:
|
||||
- spec/controllers/api/orders_controller_spec.rb
|
||||
- spec/controllers/spree/api/products_controller_spec.rb
|
||||
- spec/lib/open_food_network/address_finder_spec.rb
|
||||
- spec/lib/open_food_network/cached_products_renderer_spec.rb
|
||||
- spec/lib/open_food_network/customers_report_spec.rb
|
||||
- spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
|
||||
- spec/lib/open_food_network/option_value_namer_spec.rb
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.1.9
|
||||
2.2.10
|
||||
|
||||
@@ -19,6 +19,10 @@ If you want to run the whole test suite, we recommend using a free CI service to
|
||||
|
||||
bundle exec rspec spec
|
||||
|
||||
## Which issue to pick first?
|
||||
|
||||
We have curated all issues interesting for new members of the community within the [Welcome New Developers project board][welcome-dev]. Have a look and pick the one you would prefer working on!
|
||||
|
||||
## Internationalisation (i18n)
|
||||
|
||||
The locale `en` is maintained in the source code, but other locales are managed at [Transifex][ofn-transifex]. Read more about [internationalisation][i18n] in the developer wiki.
|
||||
@@ -62,3 +66,4 @@ From here, your pull request will progress through the [Review, Test, Merge & De
|
||||
[slack-dev]: https://openfoodnetwork.slack.com/messages/C2GQ45KNU
|
||||
[ofn-transifex]: https://www.transifex.com/open-food-foundation/open-food-network/
|
||||
[i18n]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/i18n
|
||||
[welcome-dev]: https://github.com/openfoodfoundation/openfoodnetwork/projects/27
|
||||
|
||||
14
Gemfile
14
Gemfile
@@ -1,9 +1,9 @@
|
||||
source 'https://rubygems.org'
|
||||
ruby "2.1.9"
|
||||
ruby "2.2.10"
|
||||
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
|
||||
|
||||
gem 'i18n', '~> 0.6.11'
|
||||
gem 'i18n-js', '~> 3.3.0'
|
||||
gem 'i18n-js', '~> 3.4.1'
|
||||
gem 'rails', '~> 3.2.22'
|
||||
gem 'rails-i18n', '~> 3.0.0'
|
||||
gem 'rails_safe_tasks', '~> 1.0'
|
||||
@@ -15,12 +15,12 @@ gem 'nokogiri', '>= 1.6.7.1'
|
||||
gem "order_management", path: "./engines/order_management"
|
||||
gem 'web', path: './engines/web'
|
||||
|
||||
gem 'pg'
|
||||
gem 'activerecord-postgresql-adapter'
|
||||
gem 'pg', '~> 0.21.0'
|
||||
|
||||
# OFN-maintained and patched version of Spree v2.0.4. See
|
||||
# https://github.com/openfoodfoundation/openfoodnetwork/wiki/Spree-2.0-upgrade
|
||||
# for details.
|
||||
gem 'spree_api', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
gem 'spree_backend', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
gem 'spree_core', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
|
||||
@@ -39,7 +39,7 @@ gem 'activemerchant', '~> 1.78'
|
||||
gem 'devise', '~> 2.2.5'
|
||||
gem 'devise-encryptable', '0.2.0'
|
||||
gem 'jwt', '~> 2.2'
|
||||
gem 'oauth2', '~> 1.4.1' # Used for Stripe Connect
|
||||
gem 'oauth2', '~> 1.4.2' # Used for Stripe Connect
|
||||
|
||||
gem 'daemons'
|
||||
gem 'delayed_job_active_record'
|
||||
@@ -98,6 +98,8 @@ gem 'roo-xls', '~> 1.1.0'
|
||||
|
||||
gem 'whenever', require: false
|
||||
|
||||
gem 'test-unit', '~> 3.0'
|
||||
|
||||
# Gems used only for assets and not required
|
||||
# in production environments by default.
|
||||
group :assets do
|
||||
@@ -136,7 +138,7 @@ group :test, :development do
|
||||
gem 'capybara', '>= 2.15.4'
|
||||
gem 'database_cleaner', '0.7.1', require: false
|
||||
gem "factory_bot_rails", require: false
|
||||
gem 'fuubar', '~> 2.4.1'
|
||||
gem 'fuubar', '~> 2.5.0'
|
||||
gem 'json_spec', '~> 1.1.4'
|
||||
gem 'knapsack'
|
||||
gem 'letter_opener', '>= 1.4.1'
|
||||
|
||||
66
Gemfile.lock
66
Gemfile.lock
@@ -129,8 +129,10 @@ GEM
|
||||
activesupport (= 3.2.22.5)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activerecord-import (1.0.2)
|
||||
activerecord-import (1.0.3)
|
||||
activerecord (>= 3.2)
|
||||
activerecord-postgresql-adapter (0.0.1)
|
||||
pg
|
||||
activeresource (3.2.22.5)
|
||||
activemodel (= 3.2.22.5)
|
||||
activesupport (= 3.2.22.5)
|
||||
@@ -164,7 +166,7 @@ GEM
|
||||
bcrypt-ruby (3.1.5)
|
||||
bcrypt (>= 3.1.3)
|
||||
blockenspiel (0.5.0)
|
||||
bugsnag (6.12.1)
|
||||
bugsnag (6.12.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
builder (3.0.4)
|
||||
byebug (9.0.6)
|
||||
@@ -223,7 +225,7 @@ GEM
|
||||
activerecord (>= 3.2.0, < 5.0)
|
||||
fog (~> 1.0)
|
||||
rails (>= 3.2.0, < 5.0)
|
||||
ddtrace (0.27.0)
|
||||
ddtrace (0.28.0)
|
||||
msgpack
|
||||
debugger-linecache (1.2.0)
|
||||
deface (1.0.2)
|
||||
@@ -424,7 +426,7 @@ GEM
|
||||
foundation-rails (5.5.2.1)
|
||||
railties (>= 3.1.0)
|
||||
sass (>= 3.3.0, < 3.5)
|
||||
fuubar (2.4.1)
|
||||
fuubar (2.5.0)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
geocoder (1.1.8)
|
||||
@@ -437,7 +439,7 @@ GEM
|
||||
httparty (0.16.2)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.6.11)
|
||||
i18n-js (3.3.0)
|
||||
i18n-js (3.4.1)
|
||||
i18n (>= 0.6.6)
|
||||
immigrant (0.3.6)
|
||||
activerecord (>= 3.0)
|
||||
@@ -491,8 +493,8 @@ GEM
|
||||
newrelic_rpm (3.18.1.330)
|
||||
nokogiri (1.6.8.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
oauth2 (1.4.1)
|
||||
faraday (>= 0.8, < 0.16.0)
|
||||
oauth2 (1.4.2)
|
||||
faraday (>= 0.8, < 2.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
@@ -522,6 +524,7 @@ GEM
|
||||
polyamorous (0.5.0)
|
||||
activerecord (~> 3.0)
|
||||
polyglot (0.3.5)
|
||||
power_assert (1.1.5)
|
||||
powerpack (0.1.1)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
@@ -596,29 +599,29 @@ GEM
|
||||
nokogiri
|
||||
roo (>= 2.0.0beta1, < 3)
|
||||
spreadsheet (> 0.9.0)
|
||||
rspec (3.8.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-core (3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-expectations (3.8.2)
|
||||
rspec (3.9.0)
|
||||
rspec-core (~> 3.9.0)
|
||||
rspec-expectations (~> 3.9.0)
|
||||
rspec-mocks (~> 3.9.0)
|
||||
rspec-core (3.9.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-expectations (3.9.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-mocks (3.8.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-mocks (3.9.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-rails (3.8.2)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (3.9.0)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.8.0)
|
||||
rspec-expectations (~> 3.8.0)
|
||||
rspec-mocks (~> 3.8.0)
|
||||
rspec-support (~> 3.8.0)
|
||||
rspec-core (~> 3.9.0)
|
||||
rspec-expectations (~> 3.9.0)
|
||||
rspec-mocks (~> 3.9.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-retry (0.6.1)
|
||||
rspec-core (> 3.3)
|
||||
rspec-support (3.8.0)
|
||||
rspec-support (3.9.0)
|
||||
rubocop (0.57.2)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
@@ -630,7 +633,7 @@ GEM
|
||||
ruby-ole (1.2.12.1)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-rc4 (0.1.5)
|
||||
rubyzip (1.2.2)
|
||||
rubyzip (1.3.0)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.3.14)
|
||||
sass-rails (3.2.6)
|
||||
@@ -671,6 +674,8 @@ GEM
|
||||
stripe (4.24.0)
|
||||
faraday (~> 0.13)
|
||||
net-http-persistent (~> 3.0)
|
||||
test-unit (3.3.3)
|
||||
power_assert
|
||||
thor (0.20.3)
|
||||
tilt (1.4.1)
|
||||
timecop (0.9.1)
|
||||
@@ -722,6 +727,7 @@ DEPENDENCIES
|
||||
active_model_serializers (= 0.8.4)
|
||||
activemerchant (~> 1.78)
|
||||
activerecord-import
|
||||
activerecord-postgresql-adapter
|
||||
acts-as-taggable-on (~> 3.4)
|
||||
andand
|
||||
angular-rails-templates (~> 0.3.0)
|
||||
@@ -757,12 +763,12 @@ DEPENDENCIES
|
||||
foundation-icons-sass-rails
|
||||
foundation-rails
|
||||
foundation_rails_helper!
|
||||
fuubar (~> 2.4.1)
|
||||
fuubar (~> 2.5.0)
|
||||
geocoder
|
||||
gmaps4rails
|
||||
haml
|
||||
i18n (~> 0.6.11)
|
||||
i18n-js (~> 3.3.0)
|
||||
i18n-js (~> 3.4.1)
|
||||
immigrant
|
||||
jquery-migrate-rails
|
||||
jquery-rails (= 3.0.4)
|
||||
@@ -777,13 +783,13 @@ DEPENDENCIES
|
||||
momentjs-rails
|
||||
newrelic_rpm (~> 3.0)
|
||||
nokogiri (>= 1.6.7.1)
|
||||
oauth2 (~> 1.4.1)
|
||||
oauth2 (~> 1.4.2)
|
||||
ofn-qz!
|
||||
oj
|
||||
order_management!
|
||||
paper_trail (~> 5.2.3)
|
||||
paperclip (~> 3.4.1)
|
||||
pg
|
||||
pg (~> 0.21.0)
|
||||
pry-byebug (>= 3.4.3)
|
||||
rabl
|
||||
rack-mini-profiler (< 1.0.0)
|
||||
@@ -806,7 +812,6 @@ DEPENDENCIES
|
||||
simple_form!
|
||||
simplecov
|
||||
spinjs-rails
|
||||
spree_api!
|
||||
spree_backend!
|
||||
spree_core!
|
||||
spree_i18n!
|
||||
@@ -814,6 +819,7 @@ DEPENDENCIES
|
||||
spring (= 1.7.2)
|
||||
spring-commands-rspec
|
||||
stripe
|
||||
test-unit (~> 3.0)
|
||||
timecop
|
||||
truncate_html
|
||||
turbo-sprockets-rails3
|
||||
@@ -828,7 +834,7 @@ DEPENDENCIES
|
||||
wkhtmltopdf-binary
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.1.9p490
|
||||
ruby 2.2.10p489
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.2
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
|
||||
# Open Food Network
|
||||
|
||||
The Open Food Network is an online marketplace for local food. It enables a network of independent online food stores that connect farmers and food hubs (including coops, online farmers' markets, independent food businesses etc); with individuals and local businesses. It gives farmers and food hubs an easier and fairer way to distribute their food.
|
||||
The Open Food Network is an online marketplace for local food. It enables a network of independent online food stores that connects farmers and food hubs (including co-ops, online farmers markets, independent food businesses, etc) with individuals and local businesses. It gives farmers and food hubs an easier and fairer way to distribute their food.
|
||||
|
||||
Supported by the Open Food Foundation and a network of global affiliates, we are proudly open source and not-for-profit - we're trying to seriously disrupt the concentration of power in global agri-food systems, and we need as many smart people working together on this as possible.
|
||||
|
||||
We're part of global movement - get involved!
|
||||
|
||||
* Join the conversation [on Slack][slack-invite]. Make sure you introduce yourself in the #general channel
|
||||
* Join the conversation [on Slack][slack-invite]. Make sure you introduce yourself in the #general channel.
|
||||
* Head to [https://openfoodnetwork.org](https://openfoodnetwork.org) for more information about the global OFN project.
|
||||
* Check out the [User Guide](https://guide.openfoodnetwork.org/) for a list of features and tutorials.
|
||||
* Join our [discussion forum](https://community.openfoodnetwork.org).
|
||||
|
||||
## Contributing
|
||||
|
||||
If you are interested in contributing to the OFN in any capacity, please introducing yourself [on Slack][slack-invite], and have a look through our [Contributor Guide][contributor-guide]
|
||||
If you are interested in contributing to the OFN in any capacity, please introduce yourself [on Slack][slack-invite], and have a look through our [Contributor Guide][contributor-guide].
|
||||
|
||||
Our [GETTING_STARTED](GETTING_STARTED.md) and [CONTRIBUTING](CONTRIBUTING.md) guides are the best place to start for developers looking to set up a development environment and make contributions to the codebase.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories, RequestMonitor) ->
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor) ->
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.columns = Columns.columns
|
||||
@@ -39,12 +39,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
$scope.DisplayProperties = DisplayProperties
|
||||
|
||||
$scope.initialise = ->
|
||||
SpreeApiAuth.authorise()
|
||||
.then ->
|
||||
$scope.spree_api_key_ok = true
|
||||
$scope.fetchProducts()
|
||||
.catch (message) ->
|
||||
$scope.api_error_msg = message
|
||||
$scope.fetchProducts()
|
||||
|
||||
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter, per_page]', ->
|
||||
$scope.page = 1 # Reset page when changing filters for new search
|
||||
@@ -108,9 +103,15 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
$scope.categoryFilter = "0"
|
||||
$scope.importDateFilter = "0"
|
||||
|
||||
confirm_unsaved_changes = () ->
|
||||
(DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
|
||||
|
||||
editProductUrl = (product, variant) ->
|
||||
"/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||
|
||||
$scope.editWarn = (product, variant) ->
|
||||
if (DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
|
||||
window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||
if confirm_unsaved_changes()
|
||||
window.open(editProductUrl(product, variant), "_blank")
|
||||
|
||||
|
||||
$scope.toggleShowAllVariants = ->
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
angular.module("admin.indexUtils").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) ->
|
||||
new class SpreeApiAuth
|
||||
authorise: ->
|
||||
deferred = $q.defer()
|
||||
|
||||
$http.get("/api/users/authorise_api?token=" + SpreeApiKey)
|
||||
.success (response) ->
|
||||
if response?.success == "Use of API Authorised"
|
||||
$http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey
|
||||
deferred.resolve()
|
||||
|
||||
.error (response) ->
|
||||
error = response?.error || t('js.unauthorized')
|
||||
deferred.reject(error)
|
||||
|
||||
deferred.promise
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, Views, SpreeApiAuth, PagedFetcher, StatusMessage, RequestMonitor, hubs, producers, hubPermissions, InventoryItems, VariantOverrides, DirtyVariantOverrides) ->
|
||||
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, Views, PagedFetcher, StatusMessage, RequestMonitor, hubs, producers, hubPermissions, InventoryItems, VariantOverrides, DirtyVariantOverrides) ->
|
||||
$scope.hubs = Indexer.index hubs
|
||||
$scope.hub_id = if hubs.length == 1 then hubs[0].id else null
|
||||
$scope.products = []
|
||||
@@ -39,13 +39,7 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
|
||||
$scope.producerFilter != 0 || $scope.query != ''
|
||||
|
||||
$scope.initialise = ->
|
||||
SpreeApiAuth.authorise()
|
||||
.then ->
|
||||
$scope.spree_api_key_ok = true
|
||||
$scope.fetchProducts()
|
||||
.catch (message) ->
|
||||
$scope.api_error_msg = message
|
||||
|
||||
$scope.fetchProducts()
|
||||
|
||||
$scope.fetchProducts = ->
|
||||
url = "/api/products/overridable?page=::page::;per_page=100"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) ->
|
||||
Darkswarm.controller "OrderCycleCtrl", ($scope, $rootScope, $timeout, OrderCycle) ->
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
$scope.OrderCycle = OrderCycle
|
||||
|
||||
@@ -6,11 +6,12 @@ Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) ->
|
||||
# This is a hack. We should probably write our own "popover" directive
|
||||
# That takes an expression instead of a trigger, and binds to that
|
||||
$timeout =>
|
||||
$rootScope.$broadcast 'orderCycleSelected'
|
||||
if !$scope.OrderCycle.selected()
|
||||
$("#order_cycle_id").trigger("openTrigger")
|
||||
|
||||
|
||||
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart, ChangeableOrdersAlert) ->
|
||||
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $rootScope, $timeout, OrderCycle, Products, Variants, Cart, ChangeableOrdersAlert) ->
|
||||
# Track previous order cycle id for use with revertOrderCycle()
|
||||
$scope.previous_order_cycle_id = OrderCycle.order_cycle.order_cycle_id
|
||||
$scope.$watch 'order_cycle.order_cycle_id', (newValue, oldValue)->
|
||||
@@ -32,3 +33,4 @@ Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Prod
|
||||
Products.update()
|
||||
Cart.reloadFinalisedLineItems()
|
||||
ChangeableOrdersAlert.reload()
|
||||
$rootScope.$broadcast 'orderCycleSelected'
|
||||
|
||||
@@ -1,41 +1,65 @@
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, FilterSelectorsService, Cart, Taxons, Properties) ->
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, OrderCycleResource, FilterSelectorsService, Cart, Dereferencer, Taxons, Properties, currentHub, $timeout) ->
|
||||
$scope.Products = Products
|
||||
$scope.Cart = Cart
|
||||
$scope.query = ""
|
||||
$scope.taxonSelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.propertySelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.filtersActive = true
|
||||
$scope.limit = 10
|
||||
$scope.page = 1
|
||||
$scope.per_page = 10
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
# $scope.infiniteDisabled = true
|
||||
$scope.supplied_taxons = null
|
||||
$scope.supplied_properties = null
|
||||
|
||||
# All of this logic basically just replicates the functionality filtering an ng-repeat
|
||||
# except that it allows us to filter a separate list before rendering, meaning that
|
||||
# we can get much better performance when applying filters by resetting the limit on the
|
||||
# number of products being rendered each time a filter is changed.
|
||||
$rootScope.$on "orderCycleSelected", ->
|
||||
$scope.update_filters()
|
||||
$scope.clearAll()
|
||||
|
||||
$scope.$watch "Products.loading", (newValue, oldValue) ->
|
||||
$scope.updateFilteredProducts()
|
||||
$scope.$broadcast("loadFilterSelectors") if !newValue
|
||||
$scope.update_filters = ->
|
||||
order_cycle_id = OrderCycle.order_cycle.order_cycle_id
|
||||
|
||||
$scope.incrementLimit = ->
|
||||
if $scope.limit < Products.products.length
|
||||
$scope.limit += 10
|
||||
$scope.updateVisibleProducts()
|
||||
return unless order_cycle_id
|
||||
|
||||
$scope.$watch 'query', -> $scope.updateFilteredProducts()
|
||||
$scope.$watchCollection 'activeTaxons', -> $scope.updateFilteredProducts()
|
||||
$scope.$watchCollection 'activeProperties', -> $scope.updateFilteredProducts()
|
||||
params = {
|
||||
id: order_cycle_id,
|
||||
distributor: currentHub.id
|
||||
}
|
||||
OrderCycleResource.taxons params, (data)=>
|
||||
$scope.supplied_taxons = {}
|
||||
data.map( (taxon) ->
|
||||
$scope.supplied_taxons[taxon.id] = Taxons.taxons_by_id[taxon.id]
|
||||
)
|
||||
OrderCycleResource.properties params, (data)=>
|
||||
$scope.supplied_properties = {}
|
||||
data.map( (property) ->
|
||||
$scope.supplied_properties[property.id] = Properties.properties_by_id[property.id]
|
||||
)
|
||||
|
||||
$scope.updateFilteredProducts = ->
|
||||
$scope.limit = 10
|
||||
f1 = $filter('products')(Products.products, $scope.query)
|
||||
f2 = $filter('taxons')(f1, $scope.activeTaxons)
|
||||
$scope.filteredProducts = $filter('properties')(f2, $scope.activeProperties)
|
||||
$scope.updateVisibleProducts()
|
||||
$scope.loadMore = ->
|
||||
if ($scope.page * $scope.per_page) <= Products.products.length
|
||||
$scope.loadMoreProducts()
|
||||
|
||||
$scope.updateVisibleProducts = ->
|
||||
$scope.visibleProducts = $filter('limitTo')($scope.filteredProducts, $scope.limit)
|
||||
$scope.$watch 'query', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeTaxons', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
|
||||
$scope.loadProducts = ->
|
||||
$scope.page = 1
|
||||
Products.update($scope.queryParams())
|
||||
|
||||
$scope.loadMoreProducts = ->
|
||||
Products.update($scope.queryParams($scope.page + 1), true)
|
||||
$scope.page += 1
|
||||
|
||||
$scope.queryParams = (page = null) ->
|
||||
{
|
||||
id: $scope.order_cycle.order_cycle_id,
|
||||
page: page || $scope.page,
|
||||
per_page: $scope.per_page,
|
||||
'q[name_or_meta_keywords_or_supplier_name_cont]': $scope.query,
|
||||
'q[properties_id_or_supplier_properties_id_in_any][]': $scope.activeProperties,
|
||||
'q[primary_taxon_id_in_any][]': $scope.activeTaxons
|
||||
}
|
||||
|
||||
$scope.searchKeypress = (e)->
|
||||
code = e.keyCode || e.which
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
Darkswarm.factory 'OrderCycleResource', ($resource) ->
|
||||
$resource('/api/order_cycles/:id', {}, {
|
||||
'products':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/order_cycles/:id/products'
|
||||
params:
|
||||
id: '@id'
|
||||
'taxons':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/order_cycles/:id/taxons'
|
||||
params:
|
||||
id: '@id'
|
||||
'properties':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/order_cycles/:id/properties'
|
||||
params:
|
||||
id: '@id'
|
||||
})
|
||||
@@ -1,26 +1,34 @@
|
||||
Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Properties, Cart, Variants) ->
|
||||
Darkswarm.factory 'Products', (OrderCycleResource, OrderCycle, Shopfront, currentHub, Dereferencer, Taxons, Properties, Cart, Variants) ->
|
||||
new class Products
|
||||
constructor: ->
|
||||
@update()
|
||||
|
||||
# TODO: don't need to scope this into object
|
||||
# Already on object as far as controller scope is concerned
|
||||
products: null
|
||||
products: []
|
||||
fetched_products: []
|
||||
loading: true
|
||||
|
||||
update: =>
|
||||
update: (params = {}, load_more = false) =>
|
||||
@loading = true
|
||||
@products = []
|
||||
$resource("/shop/products").query (products)=>
|
||||
@products = products
|
||||
order_cycle_id = OrderCycle.order_cycle.order_cycle_id
|
||||
|
||||
if order_cycle_id == undefined
|
||||
@loading = false
|
||||
return
|
||||
|
||||
params['id'] = order_cycle_id
|
||||
params['distributor'] = currentHub.id
|
||||
|
||||
OrderCycleResource.products params, (data)=>
|
||||
@products = [] unless load_more
|
||||
@fetched_products = data
|
||||
@extend()
|
||||
@dereference()
|
||||
@registerVariants()
|
||||
@products = @products.concat(@fetched_products)
|
||||
@loading = false
|
||||
|
||||
extend: ->
|
||||
for product in @products
|
||||
for product in @fetched_products
|
||||
if product.variants?.length > 0
|
||||
prices = (v.price for v in product.variants)
|
||||
product.price = Math.min.apply(null, prices)
|
||||
@@ -30,7 +38,7 @@ Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Prope
|
||||
product.largeImage = product.images[0]?.large_url if product.images
|
||||
|
||||
dereference: ->
|
||||
for product in @products
|
||||
for product in @fetched_products
|
||||
product.supplier = Shopfront.producers_by_id[product.supplier.id]
|
||||
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
|
||||
|
||||
@@ -40,7 +48,7 @@ Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Prope
|
||||
# May return different objects! If the variant has already been registered
|
||||
# by another service, we fetch those
|
||||
registerVariants: ->
|
||||
for product in @products
|
||||
for product in @fetched_products
|
||||
if product.variants
|
||||
product.variant_names = ""
|
||||
product.variants = for variant in product.variants
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
.small-4.medium-2.large-2.columns.variant-price
|
||||
.table-cell.price
|
||||
%i.ofn-i_009-close
|
||||
{{ ::variant.price_with_fees | localizeCurrency }}
|
||||
{{ variant.price_with_fees | localizeCurrency }}
|
||||
|
||||
-# Now in a template in app/assets/javascripts/templates !
|
||||
%price-breakdown{"price-breakdown" => "_", variant: "variant",
|
||||
|
||||
@@ -255,6 +255,9 @@ text-angular {
|
||||
background-color: #4583bf;
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: $spree-green
|
||||
}
|
||||
}
|
||||
|
||||
span.required {
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
require 'open_food_network/products_cache_integrity_checker'
|
||||
|
||||
module Admin
|
||||
class CacheSettingsController < Spree::Admin::BaseController
|
||||
def edit
|
||||
@results = Exchange.cachable.map do |exchange|
|
||||
checker = OpenFoodNetwork::ProductsCacheIntegrityChecker
|
||||
.new(exchange.receiver, exchange.order_cycle)
|
||||
|
||||
{
|
||||
distributor: exchange.receiver,
|
||||
order_cycle: exchange.order_cycle,
|
||||
status: checker.ok?,
|
||||
diff: checker.diff
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
Spree::Config.set(params[:preferences])
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to main_app.edit_admin_cache_settings_path }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,16 +1,44 @@
|
||||
# Base controller for OFN's API
|
||||
# Includes the minimum machinery required by ActiveModelSerializers
|
||||
require_dependency 'spree/api/controller_setup'
|
||||
|
||||
module Api
|
||||
class BaseController < Spree::Api::BaseController
|
||||
# Need to include these because Spree::Api::BaseContoller inherits
|
||||
# from ActionController::Metal rather than ActionController::Base
|
||||
# and they are required by ActiveModelSerializers
|
||||
class BaseController < ActionController::Metal
|
||||
include Spree::Api::ControllerSetup
|
||||
include Spree::Core::ControllerHelpers::SSL
|
||||
include ::ActionController::Head
|
||||
|
||||
respond_to :json
|
||||
|
||||
attr_accessor :current_api_user
|
||||
|
||||
before_filter :set_content_type
|
||||
before_filter :authenticate_user
|
||||
after_filter :set_jsonp_format
|
||||
|
||||
rescue_from Exception, with: :error_during_processing
|
||||
rescue_from CanCan::AccessDenied, with: :unauthorized
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
||||
|
||||
helper Spree::Api::ApiHelpers
|
||||
|
||||
ssl_allowed
|
||||
|
||||
# Include these because we inherit from ActionController::Metal
|
||||
# rather than ActionController::Base and these are required for AMS
|
||||
include ActionController::Serialization
|
||||
include ActionController::UrlFor
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
use_renderers :json
|
||||
check_authorization
|
||||
|
||||
def set_jsonp_format
|
||||
return unless params[:callback] && request.get?
|
||||
|
||||
self.response_body = "#{params[:callback]}(#{response_body})"
|
||||
headers["Content-Type"] = 'application/javascript'
|
||||
end
|
||||
|
||||
def respond_with_conflict(json_hash)
|
||||
render json: json_hash, status: :conflict
|
||||
end
|
||||
@@ -19,16 +47,62 @@ module Api
|
||||
|
||||
# Use logged in user (spree_current_user) for API authentication (current_api_user)
|
||||
def authenticate_user
|
||||
@current_api_user = try_spree_current_user
|
||||
super
|
||||
return if @current_api_user = try_spree_current_user
|
||||
if api_key.blank?
|
||||
# An anonymous user
|
||||
@current_api_user = Spree.user_class.new
|
||||
return
|
||||
end
|
||||
|
||||
return if @current_api_user = Spree.user_class.find_by_spree_api_key(api_key.to_s)
|
||||
|
||||
invalid_api_key
|
||||
end
|
||||
|
||||
# Allows API access without authentication, but only for OFN controllers which inherit
|
||||
# from Api::BaseController. @current_api_user will now initialize an empty Spree::User
|
||||
# unless one is present. We now also apply devise's `check_authorization`. See here for
|
||||
# details: https://github.com/CanCanCommunity/cancancan/wiki/Ensure-Authorization
|
||||
def requires_authentication?
|
||||
false
|
||||
def set_content_type
|
||||
content_type = case params[:format]
|
||||
when "json"
|
||||
"application/json"
|
||||
when "xml"
|
||||
"text/xml"
|
||||
end
|
||||
headers["Content-Type"] = content_type
|
||||
end
|
||||
|
||||
def error_during_processing(exception)
|
||||
render(text: { exception: exception.message }.to_json,
|
||||
status: :unprocessable_entity) && return
|
||||
end
|
||||
|
||||
def current_ability
|
||||
Spree::Ability.new(current_api_user)
|
||||
end
|
||||
|
||||
def api_key
|
||||
request.headers["X-Spree-Token"] || params[:token]
|
||||
end
|
||||
helper_method :api_key
|
||||
|
||||
def invalid_resource!(resource)
|
||||
@resource = resource
|
||||
render(json: { error: I18n.t(:invalid_resource, scope: "spree.api"),
|
||||
errors: @resource.errors },
|
||||
status: :unprocessable_entity)
|
||||
end
|
||||
|
||||
def invalid_api_key
|
||||
render(json: { error: I18n.t(:invalid_api_key, key: api_key, scope: "spree.api") },
|
||||
status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def unauthorized
|
||||
render(json: { error: I18n.t(:unauthorized, scope: "spree.api") },
|
||||
status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def not_found
|
||||
render(json: { error: I18n.t(:resource_not_found, scope: "spree.api") },
|
||||
status: :not_found) && return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
88
app/controllers/api/order_cycles_controller.rb
Normal file
88
app/controllers/api/order_cycles_controller.rb
Normal file
@@ -0,0 +1,88 @@
|
||||
module Api
|
||||
class OrderCyclesController < BaseController
|
||||
include EnterprisesHelper
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check
|
||||
|
||||
def products
|
||||
products = ProductsRenderer.new(
|
||||
distributor,
|
||||
order_cycle,
|
||||
customer,
|
||||
search_params
|
||||
).products_json
|
||||
|
||||
render json: products
|
||||
rescue ProductsRenderer::NoProducts
|
||||
render status: :not_found, json: ''
|
||||
end
|
||||
|
||||
def taxons
|
||||
taxons = Spree::Taxon.
|
||||
joins(:products).
|
||||
where(spree_products: { id: distributed_products }).
|
||||
select('DISTINCT spree_taxons.*')
|
||||
|
||||
render json: ActiveModel::ArraySerializer.new(taxons, each_serializer: Api::TaxonSerializer)
|
||||
end
|
||||
|
||||
def properties
|
||||
render json: ActiveModel::ArraySerializer.new(
|
||||
product_properties | producer_properties, each_serializer: Api::PropertySerializer
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def product_properties
|
||||
Spree::Property.
|
||||
joins(:products).
|
||||
where(spree_products: { id: distributed_products }).
|
||||
select('DISTINCT spree_properties.*')
|
||||
end
|
||||
|
||||
def producer_properties
|
||||
producers = Enterprise.
|
||||
joins(:supplied_products).
|
||||
where(spree_products: { id: distributed_products })
|
||||
|
||||
Spree::Property.
|
||||
joins(:producer_properties).
|
||||
where(producer_properties: { producer_id: producers }).
|
||||
select('DISTINCT spree_properties.*')
|
||||
end
|
||||
|
||||
def search_params
|
||||
permitted_search_params = params.slice :q, :page, :per_page
|
||||
|
||||
if permitted_search_params.key? :q
|
||||
permitted_search_params[:q].slice!(*permitted_ransack_params)
|
||||
end
|
||||
|
||||
permitted_search_params
|
||||
end
|
||||
|
||||
def permitted_ransack_params
|
||||
[:name_or_meta_keywords_or_supplier_name_cont,
|
||||
:properties_id_or_supplier_properties_id_in_any,
|
||||
:primary_taxon_id_in_any]
|
||||
end
|
||||
|
||||
def distributor
|
||||
Enterprise.find_by_id(params[:distributor])
|
||||
end
|
||||
|
||||
def order_cycle
|
||||
OrderCycle.find_by_id(params[:id])
|
||||
end
|
||||
|
||||
def customer
|
||||
@current_api_user.andand.customer_of(distributor) || nil
|
||||
end
|
||||
|
||||
def distributed_products
|
||||
OrderCycleDistributedProducts.new(distributor, order_cycle, customer).products_relation
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,10 @@
|
||||
module Api
|
||||
class OrdersController < BaseController
|
||||
def show
|
||||
authorize! :read, order
|
||||
render json: order, serializer: Api::OrderDetailedSerializer, current_order: order
|
||||
end
|
||||
|
||||
def index
|
||||
authorize! :admin, Spree::Order
|
||||
|
||||
@@ -19,5 +24,12 @@ module Api
|
||||
each_serializer: Api::Admin::OrderSerializer
|
||||
)
|
||||
end
|
||||
|
||||
def order
|
||||
@order ||= Spree::Order.
|
||||
where(number: params[:id]).
|
||||
includes(line_items: { variant: [:product, :stock_items, :default_price] }).
|
||||
first!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,7 +47,6 @@ module Api
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: 204
|
||||
end
|
||||
|
||||
# TODO: This should be named 'managed'. Is the action above used? Maybe we should remove it.
|
||||
def bulk_products
|
||||
product_query = OpenFoodNetwork::Permissions.new(current_api_user).
|
||||
editable_products.merge(product_scope)
|
||||
@@ -94,10 +93,13 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
# Copied and modified from SpreeApi::BaseController to allow
|
||||
# enterprise users to access inactive products
|
||||
def find_product(id)
|
||||
product_scope.find_by_permalink!(id.to_s)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
product_scope.find(id)
|
||||
end
|
||||
|
||||
def product_scope
|
||||
# This line modified
|
||||
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present?
|
||||
scope = Spree::Product
|
||||
if params[:show_deleted]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
require 'open_food_network/cached_products_renderer'
|
||||
|
||||
class ShopController < BaseController
|
||||
layout "darkswarm"
|
||||
before_filter :require_distributor_chosen, :set_order_cycles, except: :changeable_orders_alert
|
||||
@@ -9,19 +7,6 @@ class ShopController < BaseController
|
||||
redirect_to main_app.enterprise_shop_path(current_distributor)
|
||||
end
|
||||
|
||||
def products
|
||||
renderer = OpenFoodNetwork::CachedProductsRenderer.new(current_distributor,
|
||||
current_order_cycle)
|
||||
|
||||
# If we add any more filtering logic, we should probably
|
||||
# move it all to a lib class like 'CachedProductsFilterer'
|
||||
products_json = filter(renderer.products_json)
|
||||
|
||||
render json: products_json
|
||||
rescue OpenFoodNetwork::CachedProductsRenderer::NoProducts
|
||||
render status: :not_found, json: ''
|
||||
end
|
||||
|
||||
def order_cycle
|
||||
if request.post?
|
||||
if oc = OrderCycle.with_distributor(@distributor).active.find_by_id(params[:order_cycle_id])
|
||||
@@ -39,27 +24,4 @@ class ShopController < BaseController
|
||||
def changeable_orders_alert
|
||||
render layout: false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_json(products_json)
|
||||
if applicator.rules.any?
|
||||
filter(products_json)
|
||||
else
|
||||
products_json
|
||||
end
|
||||
end
|
||||
|
||||
def filter(products_json)
|
||||
products_hash = JSON.parse(products_json)
|
||||
applicator.filter!(products_hash)
|
||||
JSON.unparse(products_hash)
|
||||
end
|
||||
|
||||
def applicator
|
||||
return @applicator unless @applicator.nil?
|
||||
@applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor,
|
||||
"FilterProducts",
|
||||
current_customer.andand.tag_list)
|
||||
end
|
||||
end
|
||||
|
||||
35
app/controllers/spree/admin/general_settings_controller.rb
Normal file
35
app/controllers/spree/admin/general_settings_controller.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
module Spree
|
||||
module Admin
|
||||
class GeneralSettingsController < Spree::Admin::BaseController
|
||||
def edit
|
||||
@preferences_general = [:site_name, :default_seo_title, :default_meta_keywords,
|
||||
:default_meta_description, :site_url, :bugherd_api_key]
|
||||
@preferences_security = [:allow_ssl_in_production,
|
||||
:allow_ssl_in_staging, :allow_ssl_in_development_and_test,
|
||||
:check_for_spree_alerts]
|
||||
@preferences_currency = [:display_currency, :hide_cents]
|
||||
end
|
||||
|
||||
def update
|
||||
params.each do |name, value|
|
||||
next unless Spree::Config.has_preference? name
|
||||
Spree::Config[name] = value
|
||||
end
|
||||
flash[:success] = Spree.t(:successfully_updated, resource: Spree.t(:general_settings))
|
||||
|
||||
redirect_to edit_admin_general_settings_path
|
||||
end
|
||||
|
||||
def dismiss_alert
|
||||
return unless request.xhr? && params[:alert_id]
|
||||
dismissed = Spree::Config[:dismissed_spree_alerts] || ''
|
||||
Spree::Config.set(dismissed_spree_alerts: dismissed.
|
||||
split(',').
|
||||
push(params[:alert_id]).
|
||||
join(','))
|
||||
filter_dismissed_alerts
|
||||
render nothing: true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,14 +0,0 @@
|
||||
module Spree
|
||||
module Admin
|
||||
GeneralSettingsController.class_eval do
|
||||
end
|
||||
|
||||
module GeneralSettingsEditPreferences
|
||||
def edit
|
||||
super
|
||||
@preferences_general << :bugherd_api_key
|
||||
end
|
||||
end
|
||||
GeneralSettingsController.prepend(GeneralSettingsEditPreferences)
|
||||
end
|
||||
end
|
||||
79
app/controllers/spree/admin/image_settings_controller.rb
Normal file
79
app/controllers/spree/admin/image_settings_controller.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
module Spree
|
||||
module Admin
|
||||
class ImageSettingsController < Spree::Admin::BaseController
|
||||
def edit
|
||||
@styles = ActiveSupport::JSON.decode(Spree::Config[:attachment_styles])
|
||||
@headers = ActiveSupport::JSON.decode(Spree::Config[:s3_headers])
|
||||
end
|
||||
|
||||
def update
|
||||
update_styles(params)
|
||||
update_headers(params) if Spree::Config[:use_s3]
|
||||
|
||||
Spree::Config.set(params[:preferences])
|
||||
update_paperclip_settings
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = Spree.t(:image_settings_updated)
|
||||
redirect_to spree.edit_admin_image_settings_path
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_styles(params)
|
||||
if params[:new_attachment_styles].present?
|
||||
params[:new_attachment_styles].each do |_index, style|
|
||||
params[:attachment_styles][style[:name]] = style[:value] unless style[:value].empty?
|
||||
end
|
||||
end
|
||||
|
||||
styles = params[:attachment_styles]
|
||||
|
||||
Spree::Config[:attachment_styles] = ActiveSupport::JSON.encode(styles) unless styles.nil?
|
||||
end
|
||||
|
||||
def update_headers(params)
|
||||
if params[:new_s3_headers].present?
|
||||
params[:new_s3_headers].each do |_index, header|
|
||||
params[:s3_headers][header[:name]] = header[:value] unless header[:value].empty?
|
||||
end
|
||||
end
|
||||
|
||||
headers = params[:s3_headers]
|
||||
|
||||
Spree::Config[:s3_headers] = ActiveSupport::JSON.encode(headers) unless headers.nil?
|
||||
end
|
||||
|
||||
def update_paperclip_settings
|
||||
if Spree::Config[:use_s3]
|
||||
s3_creds = { access_key_id: Spree::Config[:s3_access_key],
|
||||
secret_access_key: Spree::Config[:s3_secret],
|
||||
bucket: Spree::Config[:s3_bucket] }
|
||||
Spree::Image.attachment_definitions[:attachment][:storage] = :s3
|
||||
Spree::Image.attachment_definitions[:attachment][:s3_credentials] = s3_creds
|
||||
Spree::Image.attachment_definitions[:attachment][:s3_headers] =
|
||||
ActiveSupport::JSON.decode(Spree::Config[:s3_headers])
|
||||
Spree::Image.attachment_definitions[:attachment][:bucket] = Spree::Config[:s3_bucket]
|
||||
else
|
||||
Spree::Image.attachment_definitions[:attachment].delete :storage
|
||||
end
|
||||
|
||||
Spree::Image.attachment_definitions[:attachment][:styles] =
|
||||
ActiveSupport::JSON.decode(Spree::Config[:attachment_styles]).symbolize_keys!
|
||||
Spree::Image.attachment_definitions[:attachment][:path] = Spree::Config[:attachment_path]
|
||||
Spree::Image.attachment_definitions[:attachment][:default_url] =
|
||||
Spree::Config[:attachment_default_url]
|
||||
Spree::Image.attachment_definitions[:attachment][:default_style] =
|
||||
Spree::Config[:attachment_default_style]
|
||||
|
||||
# Spree stores attachent definitions in JSON. This converts the style name and format to
|
||||
# strings. However, when paperclip encounters these, it doesn't recognise the format.
|
||||
# Here we solve that problem by converting format and style name to symbols.
|
||||
Spree::Image.reformat_styles
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
Spree::Admin::ImageSettingsController.class_eval do
|
||||
# Spree stores attachent definitions in JSON. This converts the style name and format to
|
||||
# strings. However, when paperclip encounters these, it doesn't recognise the format.
|
||||
# Here we solve that problem by converting format and style name to symbols.
|
||||
def update_paperclip_settings_with_format_styles
|
||||
update_paperclip_settings_without_format_styles
|
||||
Spree::Image.reformat_styles
|
||||
end
|
||||
|
||||
alias_method_chain :update_paperclip_settings, :format_styles
|
||||
end
|
||||
39
app/controllers/spree/admin/mail_methods_controller.rb
Normal file
39
app/controllers/spree/admin/mail_methods_controller.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
module Spree
|
||||
module Admin
|
||||
class MailMethodsController < Spree::Admin::BaseController
|
||||
after_filter :initialize_mail_settings
|
||||
|
||||
def update
|
||||
if params[:smtp_password].blank?
|
||||
params.delete(:smtp_password)
|
||||
end
|
||||
|
||||
params.each do |name, value|
|
||||
next unless Spree::Config.has_preference? name
|
||||
Spree::Config[name] = value
|
||||
end
|
||||
|
||||
flash[:success] = Spree.t(:successfully_updated, resource: Spree.t(:mail_methods))
|
||||
render :edit
|
||||
end
|
||||
|
||||
def testmail
|
||||
if TestMailer.test_email(try_spree_current_user).deliver
|
||||
flash[:success] = Spree.t('admin.mail_methods.testmail.delivery_success')
|
||||
else
|
||||
flash[:error] = Spree.t('admin.mail_methods.testmail.delivery_error')
|
||||
end
|
||||
rescue StandardError => e
|
||||
flash[:error] = Spree.t('admin.mail_methods.testmail.error') % { e: e }
|
||||
ensure
|
||||
redirect_to edit_admin_mail_method_url
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize_mail_settings
|
||||
Spree::Core::MailSettings.init
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -30,6 +30,11 @@ Spree::Admin::ProductsController.class_eval do
|
||||
@show_latest_import = params[:latest_import] || false
|
||||
end
|
||||
|
||||
def new
|
||||
@object.shipping_category = DefaultShippingCategory.find_or_create
|
||||
super
|
||||
end
|
||||
|
||||
def create
|
||||
delete_stock_params_and_set_after do
|
||||
super
|
||||
@@ -41,11 +46,12 @@ Spree::Admin::ProductsController.class_eval do
|
||||
end
|
||||
|
||||
def update
|
||||
original_supplier_id = @product.supplier_id
|
||||
|
||||
delete_stock_params_and_set_after do
|
||||
super
|
||||
ExchangeVariantDeleter.new.delete(@product) if original_supplier_id != @product.supplier_id
|
||||
end
|
||||
|
||||
clear_variants_unit_description if @object.variant_unit == 'items'
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
@@ -190,10 +196,4 @@ Spree::Admin::ProductsController.class_eval do
|
||||
def set_product_master_variant_price_to_zero
|
||||
@product.price = 0 if @product.price.nil?
|
||||
end
|
||||
|
||||
def clear_variants_unit_description
|
||||
@object.variants.each do |variant|
|
||||
variant.update_attribute :unit_description, ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -109,7 +109,7 @@ Spree::Admin::ReportsController.class_eval do
|
||||
end
|
||||
|
||||
def orders_and_fulfillment
|
||||
params[:q] ||= {}
|
||||
params[:q] ||= orders_and_fulfillment_default_filters
|
||||
|
||||
# -- Prepare Form Options
|
||||
permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||
@@ -278,4 +278,10 @@ Spree::Admin::ReportsController.class_eval do
|
||||
def timestamp
|
||||
Time.zone.now.strftime("%Y%m%d")
|
||||
end
|
||||
|
||||
def orders_and_fulfillment_default_filters
|
||||
now = Time.zone.now
|
||||
{ completed_at_gt: (now - 1.month).beginning_of_day,
|
||||
completed_at_lt: (now + 1.day).beginning_of_day }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,6 +19,11 @@ module Spree
|
||||
collection
|
||||
end
|
||||
|
||||
def new
|
||||
@object.shipping_categories = [DefaultShippingCategory.find_or_create]
|
||||
super
|
||||
end
|
||||
|
||||
def destroy
|
||||
# Our reports are not adapted to soft deleted shipping_methods so here we prevent
|
||||
# the deletion (even soft) of shipping_methods that are referenced in orders
|
||||
|
||||
@@ -58,14 +58,14 @@ module Spree
|
||||
|
||||
def generate_api_key
|
||||
if @user.generate_spree_api_key!
|
||||
flash[:success] = Spree.t('api.key_generated')
|
||||
flash[:success] = t('spree.api.key_generated')
|
||||
end
|
||||
redirect_to edit_admin_user_path(@user)
|
||||
end
|
||||
|
||||
def clear_api_key
|
||||
if @user.clear_spree_api_key!
|
||||
flash[:success] = Spree.t('api.key_cleared')
|
||||
flash[:success] = t('spree.api.key_cleared')
|
||||
end
|
||||
redirect_to edit_admin_user_path(@user)
|
||||
end
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
require_dependency 'spree/api/controller_setup'
|
||||
|
||||
module Spree
|
||||
module Api
|
||||
class BaseController < ActionController::Metal
|
||||
include Spree::Api::ControllerSetup
|
||||
include Spree::Core::ControllerHelpers::SSL
|
||||
include ::ActionController::Head
|
||||
|
||||
self.responder = Spree::Api::Responders::AppResponder
|
||||
|
||||
respond_to :json
|
||||
|
||||
attr_accessor :current_api_user
|
||||
|
||||
before_filter :set_content_type
|
||||
before_filter :check_for_user_or_api_key, :if => :requires_authentication?
|
||||
before_filter :authenticate_user
|
||||
after_filter :set_jsonp_format
|
||||
|
||||
rescue_from Exception, :with => :error_during_processing
|
||||
rescue_from CanCan::AccessDenied, :with => :unauthorized
|
||||
rescue_from ActiveRecord::RecordNotFound, :with => :not_found
|
||||
|
||||
helper Spree::Api::ApiHelpers
|
||||
|
||||
ssl_allowed
|
||||
|
||||
def set_jsonp_format
|
||||
if params[:callback] && request.get?
|
||||
self.response_body = "#{params[:callback]}(#{response_body})"
|
||||
headers["Content-Type"] = 'application/javascript'
|
||||
end
|
||||
end
|
||||
|
||||
def map_nested_attributes_keys(klass, attributes)
|
||||
nested_keys = klass.nested_attributes_options.keys
|
||||
attributes.inject({}) do |h, (k, v)|
|
||||
key = nested_keys.include?(k.to_sym) ? "#{k}_attributes" : k
|
||||
h[key] = v
|
||||
h
|
||||
end.with_indifferent_access
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_content_type
|
||||
content_type = case params[:format]
|
||||
when "json"
|
||||
"application/json"
|
||||
when "xml"
|
||||
"text/xml"
|
||||
end
|
||||
headers["Content-Type"] = content_type
|
||||
end
|
||||
|
||||
def check_for_user_or_api_key
|
||||
# User is already authenticated with Spree, make request this way instead.
|
||||
return true if @current_api_user = try_spree_current_user ||
|
||||
!requires_authentication?
|
||||
|
||||
return if api_key.present?
|
||||
render("spree/api/errors/must_specify_api_key", status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def authenticate_user
|
||||
return if @current_api_user
|
||||
|
||||
if requires_authentication? || api_key.present?
|
||||
unless @current_api_user = Spree.user_class.find_by_spree_api_key(api_key.to_s)
|
||||
render("spree/api/errors/invalid_api_key", status: :unauthorized) && return
|
||||
end
|
||||
else
|
||||
# An anonymous user
|
||||
@current_api_user = Spree.user_class.new
|
||||
end
|
||||
end
|
||||
|
||||
def unauthorized
|
||||
render("spree/api/errors/unauthorized", status: :unauthorized) && return
|
||||
end
|
||||
|
||||
def error_during_processing(exception)
|
||||
render(text: { exception: exception.message }.to_json,
|
||||
status: :unprocessable_entity) && return
|
||||
end
|
||||
|
||||
def requires_authentication?
|
||||
true
|
||||
end
|
||||
|
||||
def not_found
|
||||
render("spree/api/errors/not_found", status: :not_found) && return
|
||||
end
|
||||
|
||||
def current_ability
|
||||
Spree::Ability.new(current_api_user)
|
||||
end
|
||||
|
||||
def invalid_resource!(resource)
|
||||
@resource = resource
|
||||
render "spree/api/errors/invalid_resource", status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def api_key
|
||||
request.headers["X-Spree-Token"] || params[:token]
|
||||
end
|
||||
helper_method :api_key
|
||||
|
||||
def find_product(id)
|
||||
product_scope.find_by_permalink!(id.to_s)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
product_scope.find(id)
|
||||
end
|
||||
|
||||
def product_scope
|
||||
if current_api_user.has_spree_role?("admin")
|
||||
scope = Product
|
||||
if params[:show_deleted]
|
||||
scope = scope.with_deleted
|
||||
end
|
||||
else
|
||||
scope = Product.active
|
||||
end
|
||||
|
||||
scope.includes(:master)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
module Spree
|
||||
module Api
|
||||
class UsersController < Spree::Api::BaseController
|
||||
respond_to :json
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/helpers/spree/admin/general_settings_helper.rb
Normal file
13
app/helpers/spree/admin/general_settings_helper.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Spree
|
||||
module Admin
|
||||
module GeneralSettingsHelper
|
||||
def currency_options
|
||||
currencies = ::Money::Currency.table.map do |_code, details|
|
||||
iso = details[:iso_code]
|
||||
[iso, "#{details[:name]} (#{iso})"]
|
||||
end
|
||||
options_from_collection_for_select(currencies, :first, :last, Spree::Config[:currency])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
require 'open_food_network/products_cache_integrity_checker'
|
||||
|
||||
ProductsCacheIntegrityCheckerJob = Struct.new(:distributor_id, :order_cycle_id) do
|
||||
def perform
|
||||
unless checker.ok?
|
||||
exception = RuntimeError.new(
|
||||
"Products JSON differs from cached version for distributor: #{distributor_id}, " \
|
||||
"order cycle: #{order_cycle_id}"
|
||||
)
|
||||
|
||||
Bugsnag.notify(exception) do |report|
|
||||
report.add_tab(:products_cache, diff: checker.diff.to_s(:text))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def checker
|
||||
OpenFoodNetwork::ProductsCacheIntegrityChecker.new(distributor, order_cycle)
|
||||
end
|
||||
|
||||
def distributor
|
||||
Enterprise.find distributor_id
|
||||
end
|
||||
|
||||
def order_cycle
|
||||
OrderCycle.find order_cycle_id
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
require 'open_food_network/products_renderer'
|
||||
|
||||
RefreshProductsCacheJob = Struct.new(:distributor_id, :order_cycle_id) do
|
||||
def perform
|
||||
Rails.cache.write(key, products_json)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def key
|
||||
"products-json-#{distributor_id}-#{order_cycle_id}"
|
||||
end
|
||||
|
||||
def products_json
|
||||
distributor = Enterprise.find distributor_id
|
||||
order_cycle = OrderCycle.find order_cycle_id
|
||||
OpenFoodNetwork::ProductsRenderer.new(distributor, order_cycle).products_json
|
||||
end
|
||||
end
|
||||
@@ -1,13 +1,4 @@
|
||||
class CoordinatorFee < ActiveRecord::Base
|
||||
belongs_to :order_cycle
|
||||
belongs_to :enterprise_fee
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
order_cycle.refresh_products_cache
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,10 +10,6 @@ class EnterpriseFee < ActiveRecord::Base
|
||||
has_many :exchange_fees, dependent: :destroy
|
||||
has_many :exchanges, through: :exchange_fees
|
||||
|
||||
after_save :refresh_products_cache
|
||||
# After destroy, the products cache is refreshed via the after_destroy hook for
|
||||
# coordinator_fees and exchange_fees
|
||||
|
||||
attr_accessible :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type, :inherits_tax_category
|
||||
|
||||
FEE_TYPES = %w(packing transport admin sales fundraising).freeze
|
||||
@@ -59,8 +55,4 @@ class EnterpriseFee < ActiveRecord::Base
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.enterprise_fee_changed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,9 +23,6 @@ class Exchange < ActiveRecord::Base
|
||||
validates :order_cycle, :sender, :receiver, presence: true
|
||||
validates :sender_id, uniqueness: { scope: [:order_cycle_id, :receiver_id, :incoming] }
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
accepts_nested_attributes_for :variants
|
||||
|
||||
scope :in_order_cycle, lambda { |order_cycle| where(order_cycle_id: order_cycle) }
|
||||
@@ -98,12 +95,4 @@ class Exchange < ActiveRecord::Base
|
||||
def participant
|
||||
incoming? ? sender : receiver
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.exchange_changed self
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
OpenFoodNetwork::ProductsCache.exchange_destroyed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
class ExchangeFee < ActiveRecord::Base
|
||||
belongs_to :exchange
|
||||
belongs_to :enterprise_fee
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
exchange.refresh_products_cache
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
require 'open_food_network/products_cache'
|
||||
|
||||
class InventoryItem < ActiveRecord::Base
|
||||
attr_accessible :enterprise, :enterprise_id, :variant, :variant_id, :visible
|
||||
|
||||
@@ -13,12 +11,4 @@ class InventoryItem < ActiveRecord::Base
|
||||
|
||||
scope :visible, -> { where(visible: true) }
|
||||
scope :hidden, -> { where(visible: false) }
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.inventory_item_changed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,8 +23,6 @@ class OrderCycle < ActiveRecord::Base
|
||||
validates :name, :coordinator_id, presence: true
|
||||
validate :orders_close_at_after_orders_open_at?
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
preference :product_selection_from_coordinator_inventory_only, :boolean, default: false
|
||||
|
||||
scope :active, lambda {
|
||||
@@ -247,10 +245,6 @@ class OrderCycle < ActiveRecord::Base
|
||||
coordinator.users.include? user
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.order_cycle_changed self
|
||||
end
|
||||
|
||||
def items_bought_by_user(user, distributor)
|
||||
# The Spree::Order.complete scope only checks for completed_at date
|
||||
# it does not ensure state is "complete"
|
||||
|
||||
@@ -4,9 +4,6 @@ class ProducerProperty < ActiveRecord::Base
|
||||
|
||||
default_scope order("#{table_name}.position")
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
scope :ever_sold_by, ->(shop) {
|
||||
joins(producer: { supplied_products: { variants: { exchanges: :order_cycle } } }).
|
||||
merge(Exchange.outgoing).
|
||||
@@ -29,14 +26,4 @@ class ProducerProperty < ActiveRecord::Base
|
||||
Spree::Property.create(name: name, presentation: name)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.producer_property_changed self
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
OpenFoodNetwork::ProductsCache.producer_property_destroyed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -207,12 +207,15 @@ class AbilityDecorator
|
||||
end
|
||||
|
||||
def add_order_management_abilities(user)
|
||||
# Enterprise User can only access orders that they are a distributor for
|
||||
can [:index, :create], Spree::Order
|
||||
can [:read, :update, :fire, :resend, :invoice, :print, :print_ticket], Spree::Order do |order|
|
||||
# We allow editing orders with a nil distributor as this state occurs
|
||||
# during the order creation process from the admin backend
|
||||
order.distributor.nil? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)
|
||||
order.distributor.nil? ||
|
||||
# Enterprise User can access orders that they are a distributor for
|
||||
user.enterprises.include?(order.distributor) ||
|
||||
# Enterprise User can access orders that are placed inside a OC they coordinate
|
||||
order.order_cycle.andand.coordinated_by?(user)
|
||||
end
|
||||
can [:admin, :bulk_management, :managed], Spree::Order do
|
||||
user.admin? || user.enterprises.any?(&:is_distributor)
|
||||
|
||||
@@ -2,16 +2,9 @@ Spree::Classification.class_eval do
|
||||
belongs_to :product, class_name: "Spree::Product", touch: true
|
||||
|
||||
before_destroy :dont_destroy_if_primary_taxon
|
||||
after_destroy :refresh_products_cache
|
||||
after_save :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
product = Spree::Product.with_deleted.find(product_id) if product.blank?
|
||||
product.refresh_products_cache
|
||||
end
|
||||
|
||||
def dont_destroy_if_primary_taxon
|
||||
if product.primary_taxon == taxon
|
||||
errors.add :base, I18n.t(:spree_classification_primary_taxon_error, taxon: taxon.name, product: product.name)
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
Spree::Image.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
# Spree stores attachent definitions in JSON. This converts the style name and format to
|
||||
# strings. However, when paperclip encounters these, it doesn't recognise the format.
|
||||
# Here we solve that problem by converting format and style name to symbols.
|
||||
@@ -23,10 +20,4 @@ Spree::Image.class_eval do
|
||||
end
|
||||
|
||||
reformat_styles
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
viewable.try :refresh_products_cache
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
module Spree
|
||||
OptionType.class_eval do
|
||||
has_many :products, through: :product_option_types
|
||||
after_save :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
products(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
module Spree
|
||||
OptionValue.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
around_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
variants(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
vs = variants(:reload).to_a
|
||||
yield
|
||||
vs.each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
require 'open_food_network/products_cache'
|
||||
|
||||
module Spree
|
||||
Preference.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# When the setting preferred_product_selection_from_inventory_only has changed, we want to
|
||||
# refresh all active exchanges for this enterprise.
|
||||
def refresh_products_cache
|
||||
if product_selection_from_inventory_only_changed?
|
||||
OpenFoodNetwork::ProductsCache.distributor_changed(enterprise)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def product_selection_from_inventory_only_changed?
|
||||
!!(key =~ product_selection_from_inventory_only_regex)
|
||||
end
|
||||
|
||||
def enterprise
|
||||
enterprise_id = key.match(product_selection_from_inventory_only_regex)[1]
|
||||
Enterprise.find(enterprise_id)
|
||||
end
|
||||
|
||||
def product_selection_from_inventory_only_regex
|
||||
/^enterprise\/product_selection_from_inventory_only\/(\d+)$/
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,8 +2,6 @@ module Spree
|
||||
Price.class_eval do
|
||||
acts_as_paranoid without_default_scope: true
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# Allow prices to access associated soft-deleted variants.
|
||||
def variant
|
||||
Spree::Variant.unscoped { super }
|
||||
@@ -16,9 +14,5 @@ module Spree
|
||||
self.currency = Spree::Config[:currency]
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
variant.andand.refresh_products_cache
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,7 +38,6 @@ Spree::Product.class_eval do
|
||||
after_save :remove_previous_primary_taxon_from_taxons
|
||||
after_save :ensure_standard_variant
|
||||
after_save :update_units
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# -- Joins
|
||||
scope :with_order_cycles_outer, -> {
|
||||
@@ -192,23 +191,17 @@ Spree::Product.class_eval do
|
||||
|
||||
def destroy_with_delete_from_order_cycles
|
||||
transaction do
|
||||
OpenFoodNetwork::ProductsCache.product_deleted(self) do
|
||||
touch_distributors
|
||||
touch_distributors
|
||||
|
||||
ExchangeVariant.
|
||||
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted.
|
||||
select(:id)).destroy_all
|
||||
ExchangeVariant.
|
||||
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted.
|
||||
select(:id)).destroy_all
|
||||
|
||||
destroy_without_delete_from_order_cycles
|
||||
end
|
||||
destroy_without_delete_from_order_cycles
|
||||
end
|
||||
end
|
||||
alias_method_chain :destroy, :delete_from_order_cycles
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.product_changed self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_available_on_to_now
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
module Spree
|
||||
ProductProperty.class_eval do
|
||||
belongs_to :product, class_name: "Spree::Product", touch: true
|
||||
|
||||
after_save :refresh_products_cache
|
||||
after_destroy :refresh_products_cache
|
||||
|
||||
delegate :refresh_products_cache, to: :product
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,20 @@ class Spree::ProductSet < ModelSet
|
||||
super(Spree::Product, [], attributes, proc { |attrs| attrs[:product_id].blank? })
|
||||
end
|
||||
|
||||
def save
|
||||
@collection_hash.each_value.all? do |product_attributes|
|
||||
update_attributes(product_attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def collection_attributes=(attributes)
|
||||
@collection = Spree::Product
|
||||
.where(id: attributes.each_value.map { |product| product[:id] })
|
||||
@collection_hash = attributes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# A separate method of updating products was required due to an issue with
|
||||
# the way Rails' assign_attributes and updates_attributes behave when
|
||||
# delegated attributes of a nested object are updated via the parent object
|
||||
@@ -19,14 +33,11 @@ class Spree::ProductSet < ModelSet
|
||||
def update_attributes(attributes)
|
||||
split_taxon_ids!(attributes)
|
||||
|
||||
found_model = @collection.find do |model|
|
||||
model.id.to_s == attributes[:id].to_s && model.persisted?
|
||||
end
|
||||
|
||||
if found_model.nil?
|
||||
product = find_model(@collection, attributes[:id])
|
||||
if product.nil?
|
||||
@klass.new(attributes).save unless @reject_if.andand.call(attributes)
|
||||
else
|
||||
update_product(found_model, attributes)
|
||||
update_product(product, attributes)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,28 +45,34 @@ class Spree::ProductSet < ModelSet
|
||||
attributes[:taxon_ids] = attributes[:taxon_ids].split(',') if attributes[:taxon_ids].present?
|
||||
end
|
||||
|
||||
def update_product(found_model, attributes)
|
||||
update_product_only_attributes(found_model, attributes) &&
|
||||
update_product_variants(found_model, attributes) &&
|
||||
update_product_master(found_model, attributes)
|
||||
def update_product(product, attributes)
|
||||
original_supplier = product.supplier_id
|
||||
return false unless update_product_only_attributes(product, attributes)
|
||||
ExchangeVariantDeleter.new.delete(product) if original_supplier != product.supplier_id
|
||||
|
||||
update_product_variants(product, attributes) &&
|
||||
update_product_master(product, attributes)
|
||||
end
|
||||
|
||||
def update_product_only_attributes(product, attributes)
|
||||
variant_related_attrs = [:id, :variants_attributes, :master_attributes]
|
||||
product_related_attrs = attributes.except(*variant_related_attrs)
|
||||
|
||||
return true if product_related_attrs.blank?
|
||||
|
||||
product.assign_attributes(product_related_attrs)
|
||||
|
||||
product.variants.each do |variant|
|
||||
validate_presence_of_unit_value(product, variant)
|
||||
end
|
||||
validate_presence_of_unit_value_in_product(product)
|
||||
|
||||
product.save if errors.empty?
|
||||
product.errors.empty? && product.save
|
||||
end
|
||||
|
||||
def validate_presence_of_unit_value(product, variant)
|
||||
def validate_presence_of_unit_value_in_product(product)
|
||||
product.variants.each do |variant|
|
||||
validate_presence_of_unit_value_in_variant(product, variant)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_presence_of_unit_value_in_variant(product, variant)
|
||||
return unless %w(weight volume).include?(product.variant_unit)
|
||||
return if variant.unit_value.present?
|
||||
|
||||
@@ -79,12 +96,9 @@ class Spree::ProductSet < ModelSet
|
||||
end
|
||||
|
||||
def create_or_update_variant(product, variant_attributes)
|
||||
found_variant = product.variants_including_master.find do |variant|
|
||||
variant.id.to_s == variant_attributes[:id].to_s && variant.persisted?
|
||||
end
|
||||
|
||||
if found_variant.present?
|
||||
found_variant.update_attributes(variant_attributes.except(:id))
|
||||
variant = find_model(product.variants_including_master, variant_attributes[:id])
|
||||
if variant.present?
|
||||
variant.update_attributes(variant_attributes.except(:id))
|
||||
else
|
||||
create_variant(product, variant_attributes)
|
||||
end
|
||||
@@ -115,15 +129,9 @@ class Spree::ProductSet < ModelSet
|
||||
end
|
||||
end
|
||||
|
||||
def collection_attributes=(attributes)
|
||||
@collection = Spree::Product
|
||||
.where(id: attributes.each_value.map { |product| product[:id] })
|
||||
@collection_hash = attributes
|
||||
end
|
||||
|
||||
def save
|
||||
@collection_hash.each_value.all? do |product_attributes|
|
||||
update_attributes(product_attributes)
|
||||
def find_model(collection, model_id)
|
||||
collection.find do |model|
|
||||
model.id.to_s == model_id.to_s && model.persisted?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,19 +20,8 @@ module Spree
|
||||
merge(OrderCycle.active)
|
||||
}
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# When a Property is destroyed, dependent-destroy will destroy all ProductProperties,
|
||||
# which will take care of refreshing the products cache
|
||||
|
||||
def property
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
product_properties(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
Spree::StockMovement.class_eval do
|
||||
after_save :refresh_products_cache
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
return if stock_item.variant.blank?
|
||||
OpenFoodNetwork::ProductsCache.variant_changed stock_item.variant
|
||||
end
|
||||
end
|
||||
@@ -4,8 +4,6 @@ Spree::Taxon.class_eval do
|
||||
attachment_definitions[:icon][:path] = 'public/images/spree/taxons/:id/:style/:basename.:extension'
|
||||
attachment_definitions[:icon][:url] = '/images/spree/taxons/:id/:style/:basename.:extension'
|
||||
|
||||
after_save :refresh_products_cache
|
||||
|
||||
# Indicate which filters should be used for this taxon
|
||||
def applicable_filters
|
||||
fs = []
|
||||
@@ -49,10 +47,4 @@ Spree::Taxon.class_eval do
|
||||
ts[t.enterprise_id.to_i] << t.id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache
|
||||
products(:reload).each(&:refresh_products_cache)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
require 'open_food_network/enterprise_fee_calculator'
|
||||
require 'open_food_network/variant_and_line_item_naming'
|
||||
require 'concerns/variant_stock'
|
||||
require 'open_food_network/products_cache'
|
||||
|
||||
Spree::Variant.class_eval do
|
||||
extend Spree::LocalizedNumber
|
||||
@@ -30,7 +29,6 @@ Spree::Variant.class_eval do
|
||||
|
||||
before_validation :update_weight_from_unit_value, if: ->(v) { v.product.present? }
|
||||
after_save :update_units
|
||||
after_save :refresh_products_cache
|
||||
around_destroy :destruction
|
||||
|
||||
scope :with_order_cycles_inner, -> { joins(exchanges: :order_cycle) }
|
||||
@@ -108,14 +106,6 @@ Spree::Variant.class_eval do
|
||||
OpenFoodNetwork::EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for self
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
if is_master?
|
||||
product.refresh_products_cache
|
||||
else
|
||||
OpenFoodNetwork::ProductsCache.variant_changed self
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_weight_from_unit_value
|
||||
@@ -123,21 +113,7 @@ Spree::Variant.class_eval do
|
||||
end
|
||||
|
||||
def destruction
|
||||
if is_master?
|
||||
exchange_variants(:reload).destroy_all
|
||||
yield
|
||||
product.refresh_products_cache
|
||||
|
||||
else
|
||||
OpenFoodNetwork::ProductsCache.variant_destroyed(self) do
|
||||
# Remove this association here instead of using dependent: :destroy because
|
||||
# dependent-destroy acts before this around_filter is called, so ProductsCache
|
||||
# has no way of knowing which exchanges the variant was a member of.
|
||||
exchange_variants(:reload).destroy_all
|
||||
|
||||
# Destroy the variant
|
||||
yield
|
||||
end
|
||||
end
|
||||
exchange_variants(:reload).destroy_all
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,9 +12,6 @@ class VariantOverride < ActiveRecord::Base
|
||||
# Default stock can be nil, indicating stock should not be reset or zero, meaning reset to zero. Need to ensure this can be set by the user.
|
||||
validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
||||
|
||||
after_save :refresh_products_cache_from_save
|
||||
after_destroy :refresh_products_cache_from_destroy
|
||||
|
||||
default_scope where(permission_revoked_at: nil)
|
||||
|
||||
scope :for_hubs, lambda { |hubs|
|
||||
@@ -73,14 +70,4 @@ class VariantOverride < ActiveRecord::Base
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_products_cache_from_save
|
||||
OpenFoodNetwork::ProductsCache.variant_override_changed self
|
||||
end
|
||||
|
||||
def refresh_products_cache_from_destroy
|
||||
OpenFoodNetwork::ProductsCache.variant_override_destroyed self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,11 @@ class Api::AddressSerializer < ActiveModel::Serializer
|
||||
|
||||
attributes :id, :zipcode, :city, :state_name, :state_id,
|
||||
:phone, :firstname, :lastname, :address1, :address2, :city, :country_id,
|
||||
:zipcode
|
||||
:zipcode, :country_name
|
||||
|
||||
def country_name
|
||||
object.country.andand.name
|
||||
end
|
||||
|
||||
def state_name
|
||||
object.state.andand.abbr
|
||||
|
||||
8
app/serializers/api/adjustment_serializer.rb
Normal file
8
app/serializers/api/adjustment_serializer.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
module Api
|
||||
class AdjustmentSerializer < ActiveModel::Serializer
|
||||
attributes :id, :amount, :label, :eligible,
|
||||
:source_type, :source_id,
|
||||
:adjustable_type, :adjustable_id,
|
||||
:originator_type, :originator_id
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::Admin::BasicEnterpriseSerializer < ActiveModel::Serializer
|
||||
attributes :name, :id, :is_primary_producer, :is_distributor, :sells, :category, :payment_method_ids, :shipping_method_ids
|
||||
attributes :producer_profile_only, :permalink
|
||||
attributes :name, :id, :is_primary_producer, :is_distributor, :sells, :category,
|
||||
:payment_method_ids, :shipping_method_ids, :producer_profile_only, :permalink
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::Admin::CustomerSerializer < ActiveModel::Serializer
|
||||
attributes :id, :email, :enterprise_id, :user_id, :code, :tags, :tag_list, :name
|
||||
attributes :allow_charges, :default_card_present?
|
||||
attributes :id, :email, :enterprise_id, :user_id, :code, :tags, :tag_list, :name,
|
||||
:allow_charges, :default_card_present?
|
||||
|
||||
has_one :ship_address, serializer: Api::AddressSerializer
|
||||
has_one :bill_address, serializer: Api::AddressSerializer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::Admin::EnterpriseFeeSerializer < ActiveModel::Serializer
|
||||
attributes :id, :enterprise_id, :fee_type, :name, :tax_category_id, :inherits_tax_category, :calculator_type
|
||||
attributes :enterprise_name, :calculator_description, :calculator_settings
|
||||
attributes :id, :enterprise_id, :fee_type, :name, :tax_category_id, :inherits_tax_category,
|
||||
:calculator_type, :enterprise_name, :calculator_description, :calculator_settings
|
||||
|
||||
def enterprise_name
|
||||
object.enterprise.andand.name
|
||||
@@ -16,7 +16,9 @@ class Api::Admin::EnterpriseFeeSerializer < ActiveModel::Serializer
|
||||
result = nil
|
||||
|
||||
options[:controller].__send__(:with_format, :html) do
|
||||
result = options[:controller].render_to_string partial: 'admin/enterprise_fees/calculator_settings', locals: { enterprise_fee: object }
|
||||
result = options[:controller].
|
||||
render_to_string(partial: 'admin/enterprise_fees/calculator_settings',
|
||||
locals: { enterprise_fee: object })
|
||||
end
|
||||
|
||||
result.gsub('[0]', '[{{ $index }}]').gsub('_0_', '_{{ $index }}_')
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
|
||||
attributes :name, :id, :is_primary_producer, :is_distributor, :sells, :category, :payment_method_ids, :shipping_method_ids
|
||||
attributes :producer_profile_only, :long_description, :permalink
|
||||
attributes :preferred_shopfront_message, :preferred_shopfront_closed_message, :preferred_shopfront_taxon_order, :preferred_shopfront_order_cycle_order
|
||||
attributes :preferred_product_selection_from_inventory_only
|
||||
attributes :owner, :contact, :users, :tag_groups, :default_tag_group
|
||||
attributes :require_login, :allow_guest_orders, :allow_order_changes
|
||||
attributes :logo, :promo_image
|
||||
attributes :name, :id, :is_primary_producer, :is_distributor, :sells, :category, :permalink,
|
||||
:payment_method_ids, :shipping_method_ids, :producer_profile_only, :long_description,
|
||||
:preferred_shopfront_message, :preferred_shopfront_closed_message,
|
||||
:preferred_shopfront_taxon_order, :preferred_shopfront_order_cycle_order,
|
||||
:preferred_product_selection_from_inventory_only,
|
||||
:owner, :contact, :users, :tag_groups, :default_tag_group,
|
||||
:require_login, :allow_guest_orders, :allow_order_changes,
|
||||
:logo, :promo_image
|
||||
|
||||
has_one :owner, serializer: Api::Admin::UserSerializer
|
||||
has_many :users, serializer: Api::Admin::UserSerializer
|
||||
@@ -21,7 +22,9 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
|
||||
|
||||
def tag_groups
|
||||
object.tag_rules.prioritised.reject(&:is_default).each_with_object([]) do |tag_rule, tag_groups|
|
||||
tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.split(",").map{ |t| { text: t } })
|
||||
tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.
|
||||
split(",").
|
||||
map{ |t| { text: t } })
|
||||
if tag_group[:rules].empty?
|
||||
tag_groups << tag_group
|
||||
tag_group[:position] = tag_groups.count
|
||||
@@ -32,13 +35,16 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
|
||||
|
||||
def default_tag_group
|
||||
default_rules = object.tag_rules.select(&:is_default)
|
||||
serialized_rules = ActiveModel::ArraySerializer.new(default_rules, each_serializer: Api::Admin::TagRuleSerializer)
|
||||
serialized_rules =
|
||||
ActiveModel::ArraySerializer.new(default_rules,
|
||||
each_serializer: Api::Admin::TagRuleSerializer)
|
||||
{ tags: [], rules: serialized_rules }
|
||||
end
|
||||
|
||||
def find_match(tag_groups, tags)
|
||||
tag_groups.each do |tag_group|
|
||||
return tag_group if tag_group[:tags].length == tags.length && (tag_group[:tags] & tags) == tag_group[:tags]
|
||||
return tag_group if tag_group[:tags].length == tags.length &&
|
||||
(tag_group[:tags] & tags) == tag_group[:tags]
|
||||
end
|
||||
{ tags: tags, rules: [] }
|
||||
end
|
||||
@@ -56,7 +62,7 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
|
||||
# # medium: LOGO_MEDIUM_URL
|
||||
# # }
|
||||
def attachment_urls(attachment, versions)
|
||||
return unless attachment.exists?
|
||||
return unless attachment.file?
|
||||
|
||||
versions.each_with_object({}) do |version, urls|
|
||||
urls[version] = attachment.url(version)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Api::Admin::ExchangeSerializer < ActiveModel::Serializer
|
||||
attributes :id, :sender_id, :receiver_id, :incoming, :variants, :receival_instructions, :pickup_time, :pickup_instructions
|
||||
attributes :tags, :tag_list
|
||||
attributes :id, :sender_id, :receiver_id, :incoming, :variants,
|
||||
:receival_instructions, :pickup_time, :pickup_instructions,
|
||||
:tags, :tag_list
|
||||
|
||||
has_many :enterprise_fees, serializer: Api::Admin::BasicEnterpriseFeeSerializer
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
require 'open_food_network/enterprise_issue_validator'
|
||||
|
||||
class Api::Admin::ForOrderCycle::EnterpriseSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :managed, :supplied_products
|
||||
attributes :issues_summary_supplier, :issues_summary_distributor
|
||||
attributes :is_primary_producer, :is_distributor, :sells
|
||||
attributes :id, :name, :managed, :supplied_products,
|
||||
:issues_summary_supplier, :issues_summary_distributor,
|
||||
:is_primary_producer, :is_distributor, :sells
|
||||
|
||||
def issues_summary_supplier
|
||||
issues = OpenFoodNetwork::EnterpriseIssueValidator.new(object).issues_summary confirmation_only: true
|
||||
issues =
|
||||
OpenFoodNetwork::EnterpriseIssueValidator.
|
||||
new(object).
|
||||
issues_summary(confirmation_only: true)
|
||||
|
||||
if issues.nil? && products.empty?
|
||||
issues = "no products in inventory"
|
||||
end
|
||||
@@ -23,7 +27,8 @@ class Api::Admin::ForOrderCycle::EnterpriseSerializer < ActiveModel::Serializer
|
||||
|
||||
def supplied_products
|
||||
serializer = Api::Admin::ForOrderCycle::SuppliedProductSerializer
|
||||
ActiveModel::ArraySerializer.new(products, each_serializer: serializer, order_cycle: order_cycle)
|
||||
ActiveModel::ArraySerializer.new(products, each_serializer: serializer,
|
||||
order_cycle: order_cycle)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
require 'open_food_network/enterprise_issue_validator'
|
||||
|
||||
class Api::Admin::IndexEnterpriseSerializer < ActiveModel::Serializer
|
||||
attributes :name, :id, :permalink, :is_primary_producer, :sells, :producer_profile_only, :owned, :edit_path
|
||||
|
||||
attributes :issues, :warnings
|
||||
attributes :name, :id, :permalink, :is_primary_producer, :sells, :producer_profile_only, :owned,
|
||||
:edit_path, :issues, :warnings
|
||||
|
||||
def owned
|
||||
return true if options[:spree_current_user].admin?
|
||||
|
||||
@@ -5,9 +5,9 @@ module Api
|
||||
class IndexOrderCycleSerializer < ActiveModel::Serializer
|
||||
include OrderCyclesHelper
|
||||
|
||||
attributes :id, :name, :orders_open_at, :orders_close_at, :status, :variant_count, :deletable
|
||||
attributes :coordinator, :producers, :shops, :viewing_as_coordinator
|
||||
attributes :edit_path, :clone_path, :delete_path, :subscriptions_count
|
||||
attributes :id, :name, :orders_open_at, :orders_close_at, :status, :variant_count, :deletable,
|
||||
:coordinator, :producers, :shops, :viewing_as_coordinator,
|
||||
:edit_path, :clone_path, :delete_path, :subscriptions_count
|
||||
|
||||
has_many :schedules, serializer: Api::Admin::IdNameSerializer
|
||||
|
||||
@@ -68,7 +68,10 @@ module Api
|
||||
private
|
||||
|
||||
def visible_enterprises
|
||||
@visible_enterprises ||= OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object).visible_enterprises
|
||||
@visible_enterprises ||=
|
||||
OpenFoodNetwork::OrderCyclePermissions.
|
||||
new(options[:current_user], object).
|
||||
visible_enterprises
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Api::Admin::LineItemSerializer < ActiveModel::Serializer
|
||||
attributes :id, :quantity, :max_quantity, :price, :supplier, :final_weight_volume, :units_product, :units_variant
|
||||
attributes :id, :quantity, :max_quantity, :price, :supplier, :final_weight_volume,
|
||||
:units_product, :units_variant
|
||||
|
||||
has_one :order, serializer: Api::Admin::IdSerializer
|
||||
|
||||
@@ -20,7 +21,8 @@ class Api::Admin::LineItemSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def max_quantity
|
||||
return object.quantity unless object.max_quantity.present? && object.max_quantity > object.quantity
|
||||
return object.quantity unless object.max_quantity.present? &&
|
||||
object.max_quantity > object.quantity
|
||||
object.max_quantity
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
require 'open_food_network/order_cycle_permissions'
|
||||
|
||||
class Api::Admin::OrderCycleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :orders_open_at, :orders_close_at, :coordinator_id, :exchanges
|
||||
attributes :editable_variants_for_incoming_exchanges, :editable_variants_for_outgoing_exchanges
|
||||
attributes :visible_variants_for_outgoing_exchanges
|
||||
attributes :viewing_as_coordinator, :schedule_ids, :subscriptions_count
|
||||
attributes :id, :name, :orders_open_at, :orders_close_at, :coordinator_id, :exchanges,
|
||||
:editable_variants_for_incoming_exchanges, :editable_variants_for_outgoing_exchanges,
|
||||
:visible_variants_for_outgoing_exchanges,
|
||||
:viewing_as_coordinator, :schedule_ids, :subscriptions_count
|
||||
|
||||
has_many :coordinator_fees, serializer: Api::IdSerializer
|
||||
|
||||
@@ -25,8 +25,14 @@ class Api::Admin::OrderCycleSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def exchanges
|
||||
scoped_exchanges = OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object).visible_exchanges.by_enterprise_name
|
||||
ActiveModel::ArraySerializer.new(scoped_exchanges, each_serializer: Api::Admin::ExchangeSerializer, current_user: options[:current_user])
|
||||
scoped_exchanges =
|
||||
OpenFoodNetwork::OrderCyclePermissions.
|
||||
new(options[:current_user], object).
|
||||
visible_exchanges.by_enterprise_name
|
||||
|
||||
ActiveModel::ArraySerializer.
|
||||
new(scoped_exchanges, each_serializer: Api::Admin::ExchangeSerializer,
|
||||
current_user: options[:current_user])
|
||||
end
|
||||
|
||||
def editable_variants_for_incoming_exchanges
|
||||
@@ -66,9 +72,13 @@ class Api::Admin::OrderCycleSerializer < ActiveModel::Serializer
|
||||
# for shops. We need this here to allow hubs to restrict visible variants to only those in
|
||||
# their inventory if they so choose
|
||||
variants = if enterprise.prefers_product_selection_from_inventory_only?
|
||||
permissions.visible_variants_for_outgoing_exchanges_to(enterprise).visible_for(enterprise)
|
||||
permissions.
|
||||
visible_variants_for_outgoing_exchanges_to(enterprise).
|
||||
visible_for(enterprise)
|
||||
else
|
||||
permissions.visible_variants_for_outgoing_exchanges_to(enterprise).not_hidden_for(enterprise)
|
||||
permissions.
|
||||
visible_variants_for_outgoing_exchanges_to(enterprise).
|
||||
not_hidden_for(enterprise)
|
||||
end.pluck(:id)
|
||||
visible[enterprise.id] = variants if variants.any?
|
||||
end
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
class Api::Admin::OrderSerializer < ActiveModel::Serializer
|
||||
attributes :id, :number, :full_name, :email, :phone, :completed_at, :display_total
|
||||
attributes :edit_path, :state, :payment_state, :shipment_state
|
||||
attributes :payments_path, :ship_path, :ready_to_ship, :created_at
|
||||
attributes :distributor_name, :special_instructions, :payment_capture_path
|
||||
attributes :id, :number, :user_id, :full_name, :email, :phone, :completed_at, :display_total,
|
||||
:edit_path, :state, :payment_state, :shipment_state,
|
||||
:payments_path, :ship_path, :ready_to_ship, :created_at,
|
||||
:distributor_name, :special_instructions, :payment_capture_path,
|
||||
:item_total, :adjustment_total, :payment_total, :total
|
||||
|
||||
has_one :distributor, serializer: Api::Admin::IdSerializer
|
||||
has_one :order_cycle, serializer: Api::Admin::IdSerializer
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
module Api
|
||||
module Admin
|
||||
class SubscriptionSerializer < ActiveModel::Serializer
|
||||
attributes :id, :shop_id, :customer_id, :schedule_id, :payment_method_id, :shipping_method_id, :begins_at, :ends_at
|
||||
attributes :customer_email, :schedule_name, :edit_path, :canceled_at, :paused_at, :state
|
||||
attributes :shipping_fee_estimate, :payment_fee_estimate
|
||||
attributes :id, :shop_id, :customer_id, :schedule_id, :payment_method_id, :shipping_method_id,
|
||||
:begins_at, :ends_at,
|
||||
:customer_email, :schedule_name, :edit_path, :canceled_at, :paused_at, :state,
|
||||
:shipping_fee_estimate, :payment_fee_estimate
|
||||
|
||||
has_many :subscription_line_items, serializer: Api::Admin::SubscriptionLineItemSerializer
|
||||
has_many :closed_proxy_orders, serializer: Api::Admin::ProxyOrderSerializer
|
||||
|
||||
@@ -16,7 +16,8 @@ module Api::Admin::TagRule
|
||||
end
|
||||
|
||||
class FilterShippingMethodsSerializer < BaseSerializer
|
||||
attributes :preferred_matched_shipping_methods_visibility, :preferred_shipping_method_tags, :shipping_method_tags
|
||||
attributes :preferred_matched_shipping_methods_visibility, :preferred_shipping_method_tags,
|
||||
:shipping_method_tags
|
||||
|
||||
def shipping_method_tags
|
||||
object.preferred_shipping_method_tags.split(",")
|
||||
@@ -24,7 +25,8 @@ module Api::Admin::TagRule
|
||||
end
|
||||
|
||||
class FilterPaymentMethodsSerializer < BaseSerializer
|
||||
attributes :preferred_matched_payment_methods_visibility, :preferred_payment_method_tags, :payment_method_tags
|
||||
attributes :preferred_matched_payment_methods_visibility, :preferred_payment_method_tags,
|
||||
:payment_method_tags
|
||||
|
||||
def payment_method_tags
|
||||
object.preferred_payment_method_tags.split(",")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::Admin::VariantOverrideSerializer < ActiveModel::Serializer
|
||||
attributes :id, :hub_id, :variant_id, :sku, :price, :count_on_hand, :on_demand, :default_stock, :resettable
|
||||
attributes :tag_list, :tags, :import_date
|
||||
attributes :id, :hub_id, :variant_id, :sku, :price, :count_on_hand, :on_demand, :default_stock,
|
||||
:resettable, :tag_list, :tags, :import_date
|
||||
|
||||
def tag_list
|
||||
object.tag_list.join(",")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
class Api::Admin::VariantSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :producer_name, :image, :sku, :import_date
|
||||
attributes :options_text, :unit_value, :unit_description, :unit_to_display
|
||||
attributes :display_as, :display_name, :name_to_display
|
||||
attributes :price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name
|
||||
attributes :id, :name, :producer_name, :image, :sku, :import_date,
|
||||
:options_text, :unit_value, :unit_description, :unit_to_display,
|
||||
:display_as, :display_name, :name_to_display,
|
||||
:price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name
|
||||
|
||||
has_many :variant_overrides
|
||||
|
||||
|
||||
12
app/serializers/api/order_detailed_serializer.rb
Normal file
12
app/serializers/api/order_detailed_serializer.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
module Api
|
||||
class OrderDetailedSerializer < Api::Admin::OrderSerializer
|
||||
has_one :shipping_method, serializer: Api::ShippingMethodSerializer
|
||||
has_one :ship_address, serializer: Api::AddressSerializer
|
||||
has_one :bill_address, serializer: Api::AddressSerializer
|
||||
|
||||
has_many :line_items, serializer: Api::LineItemSerializer
|
||||
has_many :adjustments, serializer: Api::AdjustmentSerializer
|
||||
|
||||
has_many :payments, serializer: Api::PaymentSerializer
|
||||
end
|
||||
end
|
||||
@@ -1,9 +1,9 @@
|
||||
module Api
|
||||
class OrderSerializer < ActiveModel::Serializer
|
||||
attributes :number, :completed_at, :total, :state, :shipment_state, :payment_state
|
||||
attributes :outstanding_balance, :payments, :path, :cancel_path
|
||||
attributes :changes_allowed, :changes_allowed_until, :item_count
|
||||
attributes :shop_id
|
||||
attributes :number, :completed_at, :total, :state, :shipment_state, :payment_state,
|
||||
:outstanding_balance, :payments, :path, :cancel_path,
|
||||
:changes_allowed, :changes_allowed_until, :item_count,
|
||||
:shop_id
|
||||
|
||||
has_many :payments, serializer: Api::PaymentSerializer
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module Api
|
||||
class PaymentSerializer < ActiveModel::Serializer
|
||||
attributes :amount, :updated_at, :payment_method, :state
|
||||
|
||||
def payment_method
|
||||
object.payment_method.try(:name)
|
||||
end
|
||||
|
||||
@@ -1,43 +1,11 @@
|
||||
require 'open_food_network/scope_variant_to_hub'
|
||||
|
||||
class Api::ProductSerializer < ActiveModel::Serializer
|
||||
# TODO
|
||||
# Prices can't be cached? How?
|
||||
def serializable_hash
|
||||
cached_serializer_hash.merge(uncached_serializer_hash)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cached_serializer_hash
|
||||
Api::CachedProductSerializer.new(object, @options).serializable_hash
|
||||
end
|
||||
|
||||
def uncached_serializer_hash
|
||||
Api::UncachedProductSerializer.new(object, @options).serializable_hash
|
||||
end
|
||||
end
|
||||
|
||||
class Api::UncachedProductSerializer < ActiveModel::Serializer
|
||||
attributes :price
|
||||
|
||||
def price
|
||||
if options[:enterprise_fee_calculator]
|
||||
object.master.price + options[:enterprise_fee_calculator].indexed_fees_for(object.master)
|
||||
else
|
||||
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
# cached
|
||||
# delegate :cache_key, to: :object
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
attributes :id, :name, :permalink, :meta_keywords
|
||||
attributes :group_buy, :notes, :description, :description_html
|
||||
attributes :properties_with_values
|
||||
attributes :properties_with_values, :price
|
||||
|
||||
has_many :variants, serializer: Api::VariantSerializer
|
||||
has_one :master, serializer: Api::VariantSerializer
|
||||
@@ -70,4 +38,12 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
def master
|
||||
options[:master_variants][object.id].andand.first
|
||||
end
|
||||
|
||||
def price
|
||||
if options[:enterprise_fee_calculator]
|
||||
object.master.price + options[:enterprise_fee_calculator].indexed_fees_for(object.master)
|
||||
else
|
||||
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
class Api::VariantSerializer < ActiveModel::Serializer
|
||||
attributes :id, :is_master, :product_name, :sku
|
||||
attributes :options_text, :unit_value, :unit_description, :unit_to_display
|
||||
attributes :display_as, :display_name, :name_to_display
|
||||
attributes :price, :on_demand, :on_hand, :fees, :price_with_fees
|
||||
attributes :tag_list
|
||||
attributes :id, :is_master, :product_name, :sku,
|
||||
:options_text, :unit_value, :unit_description, :unit_to_display,
|
||||
:display_as, :display_name, :name_to_display,
|
||||
:price, :on_demand, :on_hand, :fees, :price_with_fees,
|
||||
:tag_list
|
||||
|
||||
delegate :price, to: :object
|
||||
|
||||
|
||||
13
app/services/default_shipping_category.rb
Normal file
13
app/services/default_shipping_category.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# Encapsulates the concept of default stock location in creation of a product or a shipping method.
|
||||
|
||||
class DefaultShippingCategory
|
||||
NAME = 'Default'.freeze
|
||||
|
||||
def self.create!
|
||||
Spree::ShippingCategory.create!(name: NAME)
|
||||
end
|
||||
|
||||
def self.find_or_create
|
||||
Spree::ShippingCategory.find_or_create_by_name(NAME)
|
||||
end
|
||||
end
|
||||
7
app/services/exchange_variant_deleter.rb
Normal file
7
app/services/exchange_variant_deleter.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class ExchangeVariantDeleter
|
||||
def delete(product)
|
||||
ExchangeVariant.
|
||||
where(variant_id: product.variants.select(:id)).
|
||||
delete_all
|
||||
end
|
||||
end
|
||||
@@ -1,36 +1,42 @@
|
||||
# Returns a (paginatable) AR object for the products or variants in stock for a given shop and OC.
|
||||
# The stock-checking includes on_demand and stock level overrides from variant_overrides.
|
||||
class OrderCycleDistributedProducts
|
||||
def initialize(distributor, order_cycle)
|
||||
def initialize(distributor, order_cycle, customer)
|
||||
@distributor = distributor
|
||||
@order_cycle = order_cycle
|
||||
@customer = customer
|
||||
end
|
||||
|
||||
def products_relation
|
||||
Spree::Product.where(id: stocked_products)
|
||||
Spree::Product.where(id: stocked_products).group("spree_products.id")
|
||||
end
|
||||
|
||||
def variants_relation
|
||||
@order_cycle.
|
||||
variants_distributed_by(@distributor).
|
||||
merge(stocked_variants_and_overrides)
|
||||
order_cycle.
|
||||
variants_distributed_by(distributor).
|
||||
merge(stocked_variants_and_overrides).
|
||||
select("DISTINCT spree_variants.*")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :distributor, :order_cycle, :customer
|
||||
|
||||
def stocked_products
|
||||
@order_cycle.
|
||||
variants_distributed_by(@distributor).
|
||||
order_cycle.
|
||||
variants_distributed_by(distributor).
|
||||
merge(stocked_variants_and_overrides).
|
||||
select("DISTINCT spree_variants.product_id")
|
||||
end
|
||||
|
||||
def stocked_variants_and_overrides
|
||||
Spree::Variant.
|
||||
stocked_variants = Spree::Variant.
|
||||
joins("LEFT OUTER JOIN variant_overrides ON variant_overrides.variant_id = spree_variants.id
|
||||
AND variant_overrides.hub_id = #{@distributor.id}").
|
||||
AND variant_overrides.hub_id = #{distributor.id}").
|
||||
joins(:stock_items).
|
||||
where(query_stock_with_overrides)
|
||||
|
||||
ProductTagRulesFilterer.new(distributor, customer, stocked_variants).call
|
||||
end
|
||||
|
||||
def query_stock_with_overrides
|
||||
|
||||
111
app/services/product_tag_rules_filterer.rb
Normal file
111
app/services/product_tag_rules_filterer.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
# Takes a Spree::Variant AR object and filters results based on applicable tag rules.
|
||||
# Tag rules exists in the context of enterprise, customer, and variant_overrides,
|
||||
# and are applied to variant_overrides only. Returns a Spree::Variant AR object.
|
||||
|
||||
class ProductTagRulesFilterer
|
||||
def initialize(distributor, customer, variants_relation)
|
||||
@distributor = distributor
|
||||
@customer = customer
|
||||
@variants_relation = variants_relation
|
||||
end
|
||||
|
||||
def call
|
||||
return variants_relation unless distributor_rules.any?
|
||||
|
||||
filter(variants_relation)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :distributor, :customer, :variants_relation
|
||||
|
||||
def distributor_rules
|
||||
@distributor_rules ||= TagRule::FilterProducts.prioritised.for(distributor).all
|
||||
end
|
||||
|
||||
def filter(variants_relation)
|
||||
return variants_relation unless overrides_to_hide.any?
|
||||
|
||||
variants_relation.where(query_with_tag_rules)
|
||||
end
|
||||
|
||||
def query_with_tag_rules
|
||||
"#{variant_not_overriden} OR ( #{variant_overriden}
|
||||
AND ( #{override_not_hidden_by_rule}
|
||||
OR #{override_shown_by_rule} ) )"
|
||||
end
|
||||
|
||||
def variant_not_overriden
|
||||
"variant_overrides.id IS NULL"
|
||||
end
|
||||
|
||||
def variant_overriden
|
||||
"variant_overrides.id IS NOT NULL"
|
||||
end
|
||||
|
||||
def override_not_hidden_by_rule
|
||||
return "FALSE" unless overrides_to_hide.any?
|
||||
"variant_overrides.id NOT IN (#{overrides_to_hide.join(',')})"
|
||||
end
|
||||
|
||||
def override_shown_by_rule
|
||||
return "FALSE" unless overrides_to_show.any?
|
||||
"variant_overrides.id IN (#{overrides_to_show.join(',')})"
|
||||
end
|
||||
|
||||
def overrides_to_hide
|
||||
@overrides_to_hide ||= VariantOverride.where(hub_id: distributor.id).
|
||||
tagged_with(default_rule_tags + hide_rule_tags, any: true).
|
||||
pluck(:id)
|
||||
end
|
||||
|
||||
def overrides_to_show
|
||||
@overrides_to_show ||= VariantOverride.where(hub_id: distributor.id).
|
||||
tagged_with(show_rule_tags, any: true).
|
||||
pluck(:id)
|
||||
end
|
||||
|
||||
def default_rule_tags
|
||||
default_rules.map(&:preferred_variant_tags)
|
||||
end
|
||||
|
||||
def hide_rule_tags
|
||||
hide_rules.map(&:preferred_variant_tags)
|
||||
end
|
||||
|
||||
def show_rule_tags
|
||||
show_rules.map(&:preferred_variant_tags)
|
||||
end
|
||||
|
||||
def default_rules
|
||||
# These rules hide a variant_override with tag X and apply to all customers
|
||||
distributor_rules.select(&:is_default?)
|
||||
end
|
||||
|
||||
def non_default_rules
|
||||
# These rules show or hide a variant_override with tag X for customer with tag Y
|
||||
distributor_rules.reject(&:is_default?)
|
||||
end
|
||||
|
||||
def customer_applicable_rules
|
||||
# Rules which apply specifically to the current customer
|
||||
@customer_applicable_rules ||= non_default_rules.select{ |rule| customer_tagged?(rule) }
|
||||
end
|
||||
|
||||
def hide_rules
|
||||
@hide_rules ||= customer_applicable_rules.
|
||||
select{ |rule| rule.preferred_matched_variants_visibility == 'hidden' }
|
||||
end
|
||||
|
||||
def show_rules
|
||||
customer_applicable_rules - hide_rules
|
||||
end
|
||||
|
||||
def customer_tagged?(rule)
|
||||
customer_tag_list.include? rule.preferred_customer_tags
|
||||
end
|
||||
|
||||
def customer_tag_list
|
||||
customer.andand.tag_list || []
|
||||
end
|
||||
end
|
||||
98
app/services/products_renderer.rb
Normal file
98
app/services/products_renderer.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
require 'open_food_network/scope_product_to_hub'
|
||||
|
||||
class ProductsRenderer
|
||||
class NoProducts < RuntimeError; end
|
||||
DEFAULT_PAGE = 1
|
||||
DEFAULT_PER_PAGE = 10
|
||||
|
||||
def initialize(distributor, order_cycle, customer, args = {})
|
||||
@distributor = distributor
|
||||
@order_cycle = order_cycle
|
||||
@customer = customer
|
||||
@args = args
|
||||
end
|
||||
|
||||
def products_json
|
||||
raise NoProducts unless order_cycle && distributor && products
|
||||
|
||||
ActiveModel::ArraySerializer.new(products,
|
||||
each_serializer: Api::ProductSerializer,
|
||||
current_order_cycle: order_cycle,
|
||||
current_distributor: distributor,
|
||||
variants: variants_for_shop_by_id,
|
||||
master_variants: master_variants_for_shop_by_id,
|
||||
enterprise_fee_calculator: enterprise_fee_calculator).to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :order_cycle, :distributor, :customer, :args
|
||||
|
||||
def products
|
||||
return unless order_cycle
|
||||
|
||||
@products ||= begin
|
||||
results = distributed_products.products_relation.order(taxon_order)
|
||||
|
||||
filter_and_paginate(results).
|
||||
each { |product| product_scoper.scope(product) } # Scope results with variant_overrides
|
||||
end
|
||||
end
|
||||
|
||||
def product_scoper
|
||||
OpenFoodNetwork::ScopeProductToHub.new(distributor)
|
||||
end
|
||||
|
||||
def enterprise_fee_calculator
|
||||
OpenFoodNetwork::EnterpriseFeeCalculator.new distributor, order_cycle
|
||||
end
|
||||
|
||||
def filter_and_paginate(query)
|
||||
query.
|
||||
ransack(args[:q]).
|
||||
result.
|
||||
page(args[:page] || DEFAULT_PAGE).
|
||||
per(args[:per_page] || DEFAULT_PER_PAGE)
|
||||
end
|
||||
|
||||
def distributed_products
|
||||
OrderCycleDistributedProducts.new(distributor, order_cycle, customer)
|
||||
end
|
||||
|
||||
def taxon_order
|
||||
if distributor.preferred_shopfront_taxon_order.present?
|
||||
distributor
|
||||
.preferred_shopfront_taxon_order
|
||||
.split(",").map { |id| "spree_products.primary_taxon_id=#{id} DESC" }
|
||||
.join(", ") + ", spree_products.name ASC, spree_products.id ASC"
|
||||
else
|
||||
"spree_products.name ASC"
|
||||
end
|
||||
end
|
||||
|
||||
def variants_for_shop
|
||||
@variants_for_shop ||= begin
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
|
||||
distributed_products.variants_relation.
|
||||
includes(:default_price, :stock_locations, :product).
|
||||
where(product_id: products).
|
||||
each { |v| scoper.scope(v) } # Scope results with variant_overrides
|
||||
end
|
||||
end
|
||||
|
||||
def variants_for_shop_by_id
|
||||
index_by_product_id variants_for_shop.reject(&:is_master)
|
||||
end
|
||||
|
||||
def master_variants_for_shop_by_id
|
||||
index_by_product_id variants_for_shop.select(&:is_master)
|
||||
end
|
||||
|
||||
def index_by_product_id(variants)
|
||||
variants.each_with_object({}) do |v, vs|
|
||||
vs[v.product_id] ||= []
|
||||
vs[v.product_id] << v
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,31 +0,0 @@
|
||||
- content_for :page_title do
|
||||
= t(:cache_settings)
|
||||
|
||||
= form_tag main_app.admin_cache_settings_path, :method => :put do
|
||||
.field
|
||||
= hidden_field_tag 'preferences[enable_products_cache?]', '0'
|
||||
= check_box_tag 'preferences[enable_products_cache?]', '1', Spree::Config[:enable_products_cache?]
|
||||
= label_tag nil, t('.enable_products_cache')
|
||||
.form-buttons
|
||||
= button t(:update), 'icon-refresh'
|
||||
|
||||
%br
|
||||
%br
|
||||
|
||||
%h4= t(:cache_state)
|
||||
%br
|
||||
%table.index
|
||||
%thead
|
||||
%tr
|
||||
%th= t('.distributor')
|
||||
%th= t('.order_cycle')
|
||||
%th= t('.status')
|
||||
%th= t('.diff')
|
||||
%tbody
|
||||
- @results.each do |result|
|
||||
%tr
|
||||
%td= result[:distributor].name
|
||||
%td= result[:order_cycle].name
|
||||
%td= result[:status] ? t(:ok) : t('.error')
|
||||
%td
|
||||
%pre= result[:diff].to_s(:text)
|
||||
@@ -1,4 +1,4 @@
|
||||
= content_for :page_title do
|
||||
- content_for :page_title do
|
||||
= t 'admin_enterprise_groups'
|
||||
|
||||
- if admin_user?
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= f.label :name, t('.name')
|
||||
%span.required *
|
||||
.six.columns.omega
|
||||
- if viewing_as_coordinator_of?(@order_cycle)
|
||||
= f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true, 'ng-disabled' => '!loaded()'
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= label_tag t('.ready_for')
|
||||
%span.required *
|
||||
.six.columns
|
||||
= text_field_tag 'order_cycle_outgoing_exchange_0_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_time', 'required' => 'required', 'placeholder' => t('.ready_for_placeholder'), 'ng-model' => 'outgoing_exchange.pickup_time', 'size' => 30, 'maxlength' => 35
|
||||
%span.icon-question-sign{'ofn-with-tip' => t('admin.order_cycles.exchange_form.pickup_time_tip')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= content_for :page_title do
|
||||
- content_for :page_title do
|
||||
= t :admin_order_cycles
|
||||
|
||||
- content_for :main_ng_app_name do
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#{t('admin.product_import.title')}
|
||||
|
||||
= render partial: 'ams_data'
|
||||
= render partial: 'admin/shared/product_sub_menu'
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
.import-wrapper{ng: {app: 'admin.productImport', controller: 'ImportFormCtrl'}}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- content_for :page_title do
|
||||
#{t('admin.product_import.title')}
|
||||
|
||||
= render partial: 'admin/shared/product_sub_menu'
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
= render 'upload_sidebar'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= content_for :sub_menu do
|
||||
- content_for :sub_menu do
|
||||
%ul#sub_nav.inline-menu{"data-hook" => "admin_enterprise_sub_tabs"}
|
||||
= tab :enterprises, url: main_app.admin_enterprises_path
|
||||
= tab :enterprise_relationships, url: main_app.admin_enterprise_relationships_path
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
= content_for :sub_menu do
|
||||
%ul#sub_nav.inline-menu
|
||||
= tab :products, match_path: '/products'
|
||||
= tab :option_types, match_path: '/option_types'
|
||||
= tab :properties
|
||||
= tab :prototypes
|
||||
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory'
|
||||
= tab :import, url: main_app.admin_product_import_path, match_path: '/product_import'
|
||||
@@ -1,4 +1,4 @@
|
||||
= content_for :sub_menu do
|
||||
- content_for :sub_menu do
|
||||
%ul#sub_nav.inline-menu{"data-hook" => "admin_user_sub_tabs"}
|
||||
= tab :users, url: spree.admin_users_path
|
||||
= tab :roles, url: main_app.admin_enterprise_roles_path, match_path: '/enterprise_roles'
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
%h1.page-title= t("admin.variant_overrides.index.title")
|
||||
%a.with-tip{ 'data-powertip' => "#{t("admin.variant_overrides.index.description")}" }=t('admin.whats_this')
|
||||
|
||||
= render partial: 'admin/shared/product_sub_menu'
|
||||
= render partial: 'spree/admin/shared/product_sub_menu'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } }
|
||||
%save-bar{ dirty: "customers_form.$dirty", persist: "false" }
|
||||
%input.red{ type: "button", value: "Save Changes", ng: { click: "update()", disabled: "!variant_overrides_form.$dirty" } }
|
||||
%input.red{ type: "button", value: t(:save_changes), ng: { click: "update()", disabled: "!variant_overrides_form.$dirty" } }
|
||||
%table.index.bulk#variant-overrides
|
||||
%col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } }
|
||||
%col.product{ width: "20%", ng: { show: 'columns.product.visible' } }
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
%td.reset{ ng: { show: 'columns.reset.visible' } }
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-resettable', type: 'checkbox', ng: {model: 'variantOverrides[hub_id][variant.id].resettable'}, placeholder: '{{ variant.resettable }}', 'ofn-track-variant-override' => 'resettable'}
|
||||
%td.reset{ ng: { show: 'columns.reset.visible' } }
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : "Default stock"}}', 'ofn-track-variant-override' => 'default_stock'}
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : ("admin.variant_overrides.index.default_stock" | t)}}', 'ofn-track-variant-override' => 'default_stock'}
|
||||
%td.inheritance{ ng: { show: 'columns.inheritance.visible' } }
|
||||
%input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true }
|
||||
%td.tags{ ng: { show: 'columns.tags.visible' } }
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
%h3
|
||||
= "#{t(:email_welcome)}!"
|
||||
= "#{t(".email_welcome")}!"
|
||||
%p.lead
|
||||
%strong
|
||||
= @enterprise.name
|
||||
= "#{t(:email_registered)} #{ Spree::Config.site_name }!"
|
||||
= "#{t(".email_registered")} #{ Spree::Config.site_name }!"
|
||||
|
||||
%p
|
||||
= t :email_userguide_html, link: link_to('Open Food Network User Guide', ContentConfig.user_guide_link)
|
||||
= t(".email_userguide_html", link: link_to(t(".userguide"), ContentConfig.user_guide_link))
|
||||
%p
|
||||
= t(".email_admin_html", link: link_to(t(".admin_panel"), spree.admin_url))
|
||||
|
||||
%p
|
||||
= t :email_admin_html, link: link_to('Admin Panel', spree.admin_url)
|
||||
|
||||
%p
|
||||
= t :email_community_html, link: link_to(t(:join_community), ContentConfig.community_forum_url)
|
||||
= t(".email_community_html", link: link_to(t(".join_community"), ContentConfig.community_forum_url))
|
||||
|
||||
= render 'shared/mailers/signoff'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.filter-shopfront.taxon-selectors.text-right
|
||||
%single-line-selectors{ selectors: "taxonSelectors", objects: "Products.products | products:query | properties:activeProperties | taxonsOf", "active-selectors" => "activeTaxons"}
|
||||
.filter-shopfront.taxon-selectors.text-right{ng: {show: 'supplied_taxons != null'}}
|
||||
%single-line-selectors{ selectors: "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"}
|
||||
|
||||
.filter-shopfront.property-selectors.text-right
|
||||
%single-line-selectors{ selectors: "propertySelectors", objects: "Products.products | products:query | taxons:activeTaxons | propertiesOf", "active-selectors" => "activeProperties"}
|
||||
.filter-shopfront.property-selectors.text-right{ng: {show: 'supplied_properties != null'}}
|
||||
%single-line-selectors{ selectors: "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"}
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
.small-12.medium-6.large-5.columns
|
||||
%input#search.text{"ng-model" => "query",
|
||||
placeholder: t(:products_search),
|
||||
"ng-debounce" => "100",
|
||||
"ng-debounce" => "200",
|
||||
"ofn-disable-enter" => true}
|
||||
|
||||
.small-12.medium-6.large-6.large-offset-1.columns
|
||||
= render partial: "shop/products/filters"
|
||||
|
||||
%div.pad-top{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'filteredProducts.length <= limit' }
|
||||
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in visibleProducts track by product.id", "id" => "product-{{ product.id }}"}
|
||||
%div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' }
|
||||
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"}
|
||||
= render "shop/products/summary"
|
||||
%shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
.small-12.columns.text-center
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
|
||||
%div{"ng-show" => "filteredProducts.length == 0 && !Products.loading"}
|
||||
%div{"ng-show" => "Products.products.length == 0 && !Products.loading"}
|
||||
.row.summary
|
||||
.small-12.columns
|
||||
%p.no-results
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user