mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-12 18:36:49 +00:00
Compare commits
380 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab143ceaed | ||
|
|
3ca491683a | ||
|
|
b85e439b0e | ||
|
|
fa8c1270ce | ||
|
|
09caf88b6c | ||
|
|
b99bcc3c12 | ||
|
|
ad70a63370 | ||
|
|
60470aaf9f | ||
|
|
2cf407ce35 | ||
|
|
c38c59b0b9 | ||
|
|
424932d61b | ||
|
|
5518ffa856 | ||
|
|
dd5041db65 | ||
|
|
052dbca1ba | ||
|
|
86bfd1bebb | ||
|
|
468576ee8a | ||
|
|
c9b60d0f62 | ||
|
|
52b39d04a4 | ||
|
|
01d741509f | ||
|
|
66fe26fcc5 | ||
|
|
460ab6cdb4 | ||
|
|
7457543c2b | ||
|
|
6b6ab864ef | ||
|
|
e73c8232de | ||
|
|
37821beb1b | ||
|
|
2292cbaae4 | ||
|
|
987347d5ab | ||
|
|
3c7970f72f | ||
|
|
d4980893a1 | ||
|
|
0d6d071db2 | ||
|
|
cba9382230 | ||
|
|
7fcee0fe6d | ||
|
|
f3347a8e16 | ||
|
|
782988b61c | ||
|
|
21a1ba2fb5 | ||
|
|
ba25986756 | ||
|
|
098ad6f250 | ||
|
|
319657d2c5 | ||
|
|
eb773a813e | ||
|
|
5dedbc4231 | ||
|
|
84c00fe215 | ||
|
|
77d7d2b804 | ||
|
|
b34fb79d69 | ||
|
|
1c41061f9e | ||
|
|
b86081090f | ||
|
|
dbdbe01d6a | ||
|
|
0996c28c09 | ||
|
|
5f70ef6e75 | ||
|
|
e0b3e9a040 | ||
|
|
2876b98155 | ||
|
|
545ca85644 | ||
|
|
c64ba75e8d | ||
|
|
56b9737676 | ||
|
|
d967d855fc | ||
|
|
1e9b4516cb | ||
|
|
ffebaee9c7 | ||
|
|
066f978c59 | ||
|
|
d1392d400a | ||
|
|
20984ac008 | ||
|
|
7f9dfb6142 | ||
|
|
6c71259b25 | ||
|
|
29a54cbfe4 | ||
|
|
05820c7ef0 | ||
|
|
492f96c90f | ||
|
|
ace3767eb2 | ||
|
|
c7c8bfe212 | ||
|
|
dadc17398f | ||
|
|
194c87e805 | ||
|
|
ead21a97cc | ||
|
|
a0012e1c5b | ||
|
|
9c24c1cd05 | ||
|
|
4aac97c985 | ||
|
|
7e4c00ba3f | ||
|
|
65433c6ac6 | ||
|
|
b3205c5459 | ||
|
|
d52db51b29 | ||
|
|
e088b27a13 | ||
|
|
4573127c59 | ||
|
|
61d7adaf74 | ||
|
|
bdc2d002fa | ||
|
|
13e4411b89 | ||
|
|
c2a9a698f4 | ||
|
|
9a7807b134 | ||
|
|
24414b83dd | ||
|
|
4f4b3fb206 | ||
|
|
6afda87baf | ||
|
|
0a28abbf2d | ||
|
|
26ba76cff9 | ||
|
|
a3458aa562 | ||
|
|
0e429da377 | ||
|
|
eb51b87bea | ||
|
|
d3de1ce47e | ||
|
|
5639b21c77 | ||
|
|
068c8feea7 | ||
|
|
6e5cbebf4a | ||
|
|
9cfd7db99d | ||
|
|
bcfa8982ef | ||
|
|
805f91e838 | ||
|
|
a3757992b5 | ||
|
|
7180af2736 | ||
|
|
fe6c810505 | ||
|
|
44872844ed | ||
|
|
bf0b941e1c | ||
|
|
e2b26b2e9c | ||
|
|
92d0a5b735 | ||
|
|
8e27ace563 | ||
|
|
93881a742e | ||
|
|
b92e257e44 | ||
|
|
fa908d4024 | ||
|
|
7fd14cf29b | ||
|
|
ef0e9dc0ca | ||
|
|
89cb447387 | ||
|
|
b568d25446 | ||
|
|
303464a04e | ||
|
|
0662c57d9d | ||
|
|
acb7e9751b | ||
|
|
9ae3ad9279 | ||
|
|
240e2a36d3 | ||
|
|
8087e6b31b | ||
|
|
39875308c2 | ||
|
|
c424e7b65e | ||
|
|
9d253e1e3e | ||
|
|
1990417b72 | ||
|
|
7a22f7f783 | ||
|
|
74c7a01151 | ||
|
|
f724d1b572 | ||
|
|
8845260979 | ||
|
|
1903134e13 | ||
|
|
4174ea69a1 | ||
|
|
676add18c3 | ||
|
|
a35f8cdb02 | ||
|
|
8a107bee98 | ||
|
|
210029aaff | ||
|
|
5bc5ef9a9d | ||
|
|
f2cd122ec8 | ||
|
|
4054bdd722 | ||
|
|
3a4ef2697c | ||
|
|
2bdda7de04 | ||
|
|
92a881c584 | ||
|
|
647a7bdddf | ||
|
|
e0228f66af | ||
|
|
38ea95ea85 | ||
|
|
6ceeda7d9e | ||
|
|
703706ee0b | ||
|
|
e2a3dd0c6f | ||
|
|
a3b8638faf | ||
|
|
4f015320a3 | ||
|
|
0f1d57db73 | ||
|
|
5f84c51c13 | ||
|
|
d215c76bc9 | ||
|
|
e1a80edb7e | ||
|
|
a4372e4d31 | ||
|
|
b6d3c3039a | ||
|
|
f199cb1bea | ||
|
|
be123b2a72 | ||
|
|
60d29d619f | ||
|
|
78fd785f0c | ||
|
|
f9f4bdae8f | ||
|
|
0f16c43f21 | ||
|
|
865024fc1f | ||
|
|
407d890d23 | ||
|
|
0e5d7c1eb1 | ||
|
|
a38b18bd0d | ||
|
|
6405c34428 | ||
|
|
e7df9bb58d | ||
|
|
d3af3d3f27 | ||
|
|
9682544442 | ||
|
|
f4bcf6c6d5 | ||
|
|
e6fa08edfc | ||
|
|
18fc4b7c92 | ||
|
|
612cc45ab7 | ||
|
|
16e289bf37 | ||
|
|
b54c6fcb26 | ||
|
|
8365c66add | ||
|
|
5590671c23 | ||
|
|
9e43661127 | ||
|
|
ec581dccb8 | ||
|
|
4d098448f5 | ||
|
|
53ebe10483 | ||
|
|
eb5f8b85ff | ||
|
|
1b18808d21 | ||
|
|
6ef345c5d8 | ||
|
|
a25a75bbe8 | ||
|
|
ea1ec1a1c6 | ||
|
|
975afb3152 | ||
|
|
265871932f | ||
|
|
c34570e96e | ||
|
|
ac166f3590 | ||
|
|
e4985a9d51 | ||
|
|
eb7de18298 | ||
|
|
67a7140642 | ||
|
|
868929eed3 | ||
|
|
8e6d53f6c6 | ||
|
|
f072e9d9c2 | ||
|
|
7c985f39ab | ||
|
|
efb83c2f95 | ||
|
|
74e81b078f | ||
|
|
3fa2b3161f | ||
|
|
fe2bf8d531 | ||
|
|
eb858159ce | ||
|
|
43869fc140 | ||
|
|
a5c4364f92 | ||
|
|
02775d033d | ||
|
|
6559b4d3a4 | ||
|
|
d851aa5106 | ||
|
|
c5b9727177 | ||
|
|
248b0016d4 | ||
|
|
d8d6bad11c | ||
|
|
ab16931d70 | ||
|
|
e33de8a20e | ||
|
|
468cb3f57e | ||
|
|
1152f307e2 | ||
|
|
25525d4f75 | ||
|
|
a759d8c7c7 | ||
|
|
b3242041e5 | ||
|
|
106bb7a27f | ||
|
|
2334ab6d00 | ||
|
|
11af5dffdc | ||
|
|
3ce7e96777 | ||
|
|
670fff9d36 | ||
|
|
171ba09663 | ||
|
|
65c9c287e8 | ||
|
|
4332734f27 | ||
|
|
340f669506 | ||
|
|
3b7ad0ef4e | ||
|
|
6e23f5bdac | ||
|
|
8b5378e673 | ||
|
|
ba32e2d676 | ||
|
|
74c4722cfe | ||
|
|
b3eda9fecb | ||
|
|
c224df9b6a | ||
|
|
b9c86d54b0 | ||
|
|
cebba4dd43 | ||
|
|
43ba73ac19 | ||
|
|
728326c2a5 | ||
|
|
8bcc9456d8 | ||
|
|
5f51b21fe9 | ||
|
|
2984829790 | ||
|
|
f366aa2605 | ||
|
|
bf16a10129 | ||
|
|
43836d2b30 | ||
|
|
82156e32e0 | ||
|
|
69cf7dff2c | ||
|
|
a8ca471cd2 | ||
|
|
aa52cf8bf0 | ||
|
|
ac6501c5d8 | ||
|
|
568e570b4b | ||
|
|
42be6c905f | ||
|
|
d482cccefe | ||
|
|
52becf6abc | ||
|
|
15319d66e2 | ||
|
|
d8f4df4bcc | ||
|
|
240d4a7802 | ||
|
|
3dff11e405 | ||
|
|
2d9b41729e | ||
|
|
de8d8e658c | ||
|
|
a7013b5542 | ||
|
|
24fe7e8878 | ||
|
|
eb94c5a5bd | ||
|
|
4ef61b642e | ||
|
|
23f4faf192 | ||
|
|
becd57f7a4 | ||
|
|
2e4b224d48 | ||
|
|
2b210bd096 | ||
|
|
53183b8598 | ||
|
|
91306d5ce4 | ||
|
|
e20e19f963 | ||
|
|
2ab07bc6a9 | ||
|
|
15e56b21ae | ||
|
|
629db3ae4d | ||
|
|
43274ecb4f | ||
|
|
437c7367db | ||
|
|
acfe0c540a | ||
|
|
ac2ab34e11 | ||
|
|
41c0204cfa | ||
|
|
910cc99c2f | ||
|
|
a0a361673a | ||
|
|
70005a99a3 | ||
|
|
8f8dce4bab | ||
|
|
8973a1b76c | ||
|
|
c1b28543c6 | ||
|
|
c33352904a | ||
|
|
8bd3062b16 | ||
|
|
ed91c179cd | ||
|
|
c7fb85a715 | ||
|
|
e901615b61 | ||
|
|
d8e6d98912 | ||
|
|
c455dfb609 | ||
|
|
63eb0980eb | ||
|
|
06ead827d8 | ||
|
|
6dd4a866e5 | ||
|
|
04c962432a | ||
|
|
d96d6b2337 | ||
|
|
9147518422 | ||
|
|
d23397f250 | ||
|
|
993a684e44 | ||
|
|
427dc54945 | ||
|
|
ffceff3f0a | ||
|
|
b5d159e163 | ||
|
|
4658a53aeb | ||
|
|
3bc834435d | ||
|
|
9111ff1a38 | ||
|
|
b0eac1ecaa | ||
|
|
2e31f234d6 | ||
|
|
e29a81cacc | ||
|
|
a672af1a79 | ||
|
|
75207247e6 | ||
|
|
8af40f4675 | ||
|
|
63ac6c5088 | ||
|
|
d8444dcf3c | ||
|
|
71c7c35679 | ||
|
|
f282ff805d | ||
|
|
2f7dc9a578 | ||
|
|
a9829ba5d9 | ||
|
|
8532fa16cd | ||
|
|
c3f01be580 | ||
|
|
fcc746a1b7 | ||
|
|
8f7b3df9b5 | ||
|
|
e23045b19e | ||
|
|
5e1dea61a8 | ||
|
|
cedf1b26f2 | ||
|
|
47a93568dc | ||
|
|
62471bf2ab | ||
|
|
cdf5bcb7eb | ||
|
|
7414047b92 | ||
|
|
4ef5dfe430 | ||
|
|
e58a1d080f | ||
|
|
357a88fa22 | ||
|
|
f71045b3f2 | ||
|
|
dcdd3f2444 | ||
|
|
6820919552 | ||
|
|
4a4173bdc0 | ||
|
|
9a7e782102 | ||
|
|
9fa892346e | ||
|
|
7341912390 | ||
|
|
d0c797b797 | ||
|
|
cb2e17d7dc | ||
|
|
147654df41 | ||
|
|
14cf168e3b | ||
|
|
c3ee7b7c64 | ||
|
|
a6414b6dbe | ||
|
|
4c41c84cc1 | ||
|
|
e53f733966 | ||
|
|
2a8809e6e8 | ||
|
|
31a54e49c5 | ||
|
|
0fabde8849 | ||
|
|
314fed063d | ||
|
|
0d5c08c363 | ||
|
|
59f56cb0f6 | ||
|
|
cf712e9478 | ||
|
|
2ff8356c63 | ||
|
|
4aad80c134 | ||
|
|
71ffa6b178 | ||
|
|
e3de71668a | ||
|
|
538e4e54d2 | ||
|
|
1ddbabd841 | ||
|
|
0414f4984d | ||
|
|
9c421e146e | ||
|
|
e6cd33ee57 | ||
|
|
8a544f3ab3 | ||
|
|
635ea9c505 | ||
|
|
fbbe586996 | ||
|
|
a5184cce9d | ||
|
|
69b57544f1 | ||
|
|
e014e6c1a4 | ||
|
|
9994bc75ca | ||
|
|
3f5a964dec | ||
|
|
b4befea606 | ||
|
|
58465c4645 | ||
|
|
0b05312f19 | ||
|
|
e209452f8b | ||
|
|
a8078b22f8 | ||
|
|
da837ff100 | ||
|
|
da2598282b | ||
|
|
0308f1465d | ||
|
|
a6e4893287 | ||
|
|
26769b4150 | ||
|
|
84745e4ccb | ||
|
|
aac7a5e559 | ||
|
|
0a2941ed96 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,6 +31,7 @@ public/system
|
||||
public/stylesheets
|
||||
public/images
|
||||
public/spree
|
||||
public/assets
|
||||
config/abr.yml
|
||||
config/initializers/feature_toggle.rb
|
||||
NERD_tree*
|
||||
|
||||
@@ -88,7 +88,6 @@ Layout/LineLength:
|
||||
- app/models/spree/tax_rate_decorator.rb
|
||||
- app/models/spree/taxon_decorator.rb
|
||||
- app/models/spree/user.rb
|
||||
- app/models/spree/variant_decorator.rb
|
||||
- app/models/subscription.rb
|
||||
- app/models/variant_override.rb
|
||||
- app/models/variant_override_set.rb
|
||||
@@ -156,7 +155,6 @@ Layout/LineLength:
|
||||
- spec/controllers/spree/admin/orders/customer_details_controller_spec.rb
|
||||
- spec/controllers/spree/admin/orders_controller_spec.rb
|
||||
- spec/controllers/spree/admin/payment_methods_controller_spec.rb
|
||||
- spec/controllers/spree/admin/payments_controller_spec.rb
|
||||
- spec/controllers/spree/admin/products_controller_spec.rb
|
||||
- spec/controllers/spree/admin/reports_controller_spec.rb
|
||||
- spec/controllers/spree/admin/variants_controller_spec.rb
|
||||
@@ -181,7 +179,6 @@ Layout/LineLength:
|
||||
- spec/features/admin/image_settings_spec.rb
|
||||
- spec/features/admin/multilingual_spec.rb
|
||||
- spec/features/admin/order_cycles_spec.rb
|
||||
- spec/features/admin/orders_spec.rb
|
||||
- spec/features/admin/overview_spec.rb
|
||||
- spec/features/admin/payment_method_spec.rb
|
||||
- spec/features/admin/product_import_spec.rb
|
||||
@@ -377,7 +374,6 @@ Metrics/AbcSize:
|
||||
- app/helpers/spree/admin/base_helper.rb
|
||||
- app/helpers/spree/admin/zones_helper.rb
|
||||
- app/helpers/spree/orders_helper.rb
|
||||
- app/mailers/producer_mailer.rb
|
||||
- app/models/calculator/flat_percent_per_item.rb
|
||||
- app/models/column_preference.rb
|
||||
- app/models/enterprise.rb
|
||||
@@ -468,7 +464,6 @@ Metrics/BlockLength:
|
||||
- spec/factories/shipping_method_factory.rb
|
||||
- spec/factories/subscription_factory.rb
|
||||
- spec/factories/variant_factory.rb
|
||||
- spec/features/admin/orders_spec.rb
|
||||
- spec/features/consumer/shopping/embedded_shopfronts_spec.rb
|
||||
- spec/lib/open_food_network/group_buy_report_spec.rb
|
||||
- spec/models/tag_rule/discount_order_spec.rb
|
||||
@@ -574,7 +569,6 @@ Metrics/MethodLength:
|
||||
- app/helpers/spree/admin/navigation_helper.rb
|
||||
- app/helpers/spree/admin/base_helper.rb
|
||||
- app/jobs/subscription_placement_job.rb
|
||||
- app/mailers/producer_mailer.rb
|
||||
- app/models/column_preference.rb
|
||||
- app/models/enterprise.rb
|
||||
- app/models/enterprise_relationship.rb
|
||||
@@ -680,7 +674,6 @@ Metrics/ModuleLength:
|
||||
- engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/form_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/payment_setup_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/validator_spec.rb
|
||||
- engines/order_management/spec/services/order_management/subscriptions/variants_list_spec.rb
|
||||
|
||||
@@ -173,7 +173,6 @@ Naming/MethodParameterName:
|
||||
Naming/PredicateName:
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'app/mailers/producer_mailer.rb'
|
||||
- 'app/models/enterprise.rb'
|
||||
- 'app/models/enterprise_relationship.rb'
|
||||
- 'app/models/order_cycle.rb'
|
||||
@@ -630,7 +629,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'app/jobs/subscription_placement_job.rb'
|
||||
- 'app/jobs/welcome_enterprise_job.rb'
|
||||
- 'app/mailers/enterprise_mailer.rb'
|
||||
- 'app/mailers/producer_mailer.rb'
|
||||
- 'app/mailers/spree/base_mailer_decorator.rb'
|
||||
- 'app/mailers/spree/order_mailer_decorator.rb'
|
||||
- 'app/mailers/spree/user_mailer.rb'
|
||||
@@ -855,7 +853,6 @@ Style/FrozenStringLiteralComment:
|
||||
- 'app/validators/date_time_string_validator.rb'
|
||||
- 'app/validators/distributors_validator.rb'
|
||||
- 'app/validators/integer_array_validator.rb'
|
||||
- 'app/views/spree/admin/taxons/search.rabl'
|
||||
- 'config.ru'
|
||||
- 'engines/order_management/app/controllers/order_management/application_controller.rb'
|
||||
- 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/authorizer.rb'
|
||||
|
||||
@@ -52,3 +52,5 @@ The default admin user is 'ofn@example.com' with 'ofn123' password.
|
||||
Check the app in the browser at `http://localhost:3000`.
|
||||
|
||||
You will then get the trace of the containers in the terminal. You can stop the containers using Ctrl-C in the terminal.
|
||||
|
||||
You can find some useful tips and commands [here](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Docker:-useful-tips-and-commands).
|
||||
|
||||
@@ -27,6 +27,9 @@ RUN sh -c "echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main'
|
||||
apt-get update && \
|
||||
apt-get install -yqq --no-install-recommends postgresql-client-9.5 libpq-dev
|
||||
|
||||
# Install node
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Install Chrome
|
||||
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
|
||||
sh -c "echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google-chrome.list" && \
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
This is a general guide to setting up an Open Food Network development environment on your local machine.
|
||||
|
||||
The fastest way to make it work locally is to use Docker, see the [Docker setup guide](DOCKER.md).
|
||||
|
||||
The following guides are located in the wiki and provide more OS-specific step-by-step instructions:
|
||||
|
||||
- [Ubuntu Setup Guide][ubuntu]
|
||||
@@ -11,7 +13,7 @@ The following guides are located in the wiki and provide more OS-specific step-b
|
||||
### Dependencies
|
||||
|
||||
* Rails 3.2.x
|
||||
* Ruby 2.1.9
|
||||
* Ruby 2.3.7
|
||||
* PostgreSQL database
|
||||
* PhantomJS (for testing)
|
||||
* See Gemfile for a list of gems required
|
||||
@@ -58,10 +60,10 @@ Now, your dreams of spinning up a development server can be realised:
|
||||
|
||||
bundle exec rails server
|
||||
|
||||
To login as Spree default user, use:
|
||||
To login as the default user, use:
|
||||
|
||||
email: spree@example.com
|
||||
password: spree123
|
||||
email: ofn@example.com
|
||||
password: ofn123
|
||||
|
||||
### Testing
|
||||
|
||||
@@ -118,7 +120,7 @@ $ createdb open_food_network_test --owner=ofn
|
||||
If these commands succeed, you should be able to [continue the setup process](#get-it-running).
|
||||
|
||||
[developer-wiki]: https://github.com/openfoodfoundation/openfoodnetwork/wiki
|
||||
[sierra]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup%3A-macOS-%28Sierra%2C-HighSierra-and-Mojave%29
|
||||
[sierra]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup%3A-macOS-%28Sierra%2C-HighSierra%2C-Mojave-and-Catalina%29
|
||||
[el-capitan]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-OS-X-(El-Capitan)
|
||||
[ubuntu]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-Ubuntu
|
||||
[wiki]: https://github.com/openfoodfoundation/openfoodnetwork/wiki
|
||||
|
||||
8
Gemfile
8
Gemfile
@@ -9,8 +9,6 @@ gem 'rails-i18n', '~> 3.0.0'
|
||||
gem 'rails_safe_tasks', '~> 1.0'
|
||||
|
||||
gem "activerecord-import"
|
||||
# Patched version. See http://rubysec.com/advisories/CVE-2015-5312/.
|
||||
gem 'nokogiri', '>= 1.6.7.1'
|
||||
|
||||
gem "catalog", path: "./engines/catalog"
|
||||
gem "order_management", path: "./engines/order_management"
|
||||
@@ -55,7 +53,6 @@ gem 'aws-sdk'
|
||||
gem 'bugsnag'
|
||||
gem 'db2fog'
|
||||
gem 'haml'
|
||||
gem 'rabl'
|
||||
gem 'redcarpet'
|
||||
gem 'sass', "~> 3.3"
|
||||
gem 'sass-rails', '~> 3.2.3'
|
||||
@@ -70,7 +67,6 @@ gem 'angularjs-file-upload-rails', '~> 2.4.1'
|
||||
gem 'blockenspiel'
|
||||
gem 'custom_error_message', github: 'jeremydurham/custom-err-msg'
|
||||
gem 'dalli'
|
||||
gem 'deface', '1.0.2'
|
||||
gem 'diffy'
|
||||
gem 'figaro'
|
||||
gem 'geocoder'
|
||||
@@ -98,7 +94,7 @@ gem 'test-unit', '~> 3.3'
|
||||
gem 'coffee-rails', '~> 3.2.1'
|
||||
gem 'compass-rails'
|
||||
|
||||
gem 'mini_racer', '0.2.9'
|
||||
gem 'mini_racer', '0.2.10'
|
||||
|
||||
gem 'uglifier', '>= 1.0.3'
|
||||
|
||||
@@ -125,7 +121,7 @@ group :test, :development do
|
||||
# Pretty printed test output
|
||||
gem 'atomic'
|
||||
gem 'awesome_print'
|
||||
gem 'capybara', '>= 2.18.0' # 3.0 requires nokogiri 1.8
|
||||
gem 'capybara', '>= 2.18.0' # 3.0 requires rack 1.6 that only works with Rails 4.2
|
||||
gem 'database_cleaner', '0.7.1', require: false
|
||||
gem "factory_bot_rails", require: false
|
||||
gem 'fuubar', '~> 2.5.0'
|
||||
|
||||
38
Gemfile.lock
38
Gemfile.lock
@@ -23,7 +23,7 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/openfoodfoundation/spree.git
|
||||
revision: 8a8585a43cd04d1a50dc65227f337a91b18d66d5
|
||||
revision: e10ca1f689b1658040b081939b7523f6fb68895a
|
||||
branch: 2-0-4-stable
|
||||
specs:
|
||||
spree_core (2.0.4)
|
||||
@@ -32,14 +32,13 @@ GIT
|
||||
awesome_nested_set (= 2.1.5)
|
||||
aws-sdk (~> 1.11.1)
|
||||
cancan (~> 1.6.10)
|
||||
deface (>= 0.9.1)
|
||||
ffaker (~> 1.16)
|
||||
highline (= 1.6.18)
|
||||
httparty (~> 0.11)
|
||||
json (>= 1.7.7)
|
||||
kaminari (~> 0.14.1)
|
||||
money (= 5.1.1)
|
||||
paperclip (~> 3.0)
|
||||
paperclip (~> 3.4.1)
|
||||
paranoia (~> 1.3)
|
||||
rails (~> 3.2.14)
|
||||
ransack (= 0.7.2)
|
||||
@@ -167,7 +166,6 @@ GEM
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.10.0)
|
||||
colorize (0.8.1)
|
||||
combine_pdf (1.0.16)
|
||||
ruby-rc4 (>= 0.1.5)
|
||||
compass (1.0.3)
|
||||
@@ -198,14 +196,9 @@ GEM
|
||||
activerecord (>= 3.2.0, < 5.0)
|
||||
fog (~> 1.0)
|
||||
rails (>= 3.2.0, < 5.0)
|
||||
ddtrace (0.34.1)
|
||||
ddtrace (0.35.1)
|
||||
msgpack
|
||||
debugger-linecache (1.2.0)
|
||||
deface (1.0.2)
|
||||
colorize (>= 0.5.8)
|
||||
nokogiri (~> 1.6.0)
|
||||
polyglot
|
||||
rails (>= 3.1)
|
||||
delayed_job (4.1.8)
|
||||
activesupport (>= 3.0, < 6.1)
|
||||
delayed_job_active_record (4.1.4)
|
||||
@@ -238,7 +231,7 @@ GEM
|
||||
railties (>= 3.0.0)
|
||||
faraday (1.0.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffaker (1.22.1)
|
||||
ffaker (1.32.1)
|
||||
ffi (1.12.2)
|
||||
figaro (1.1.1)
|
||||
thor (~> 0.14)
|
||||
@@ -449,9 +442,9 @@ GEM
|
||||
method_source (0.9.2)
|
||||
mime-types (1.25.1)
|
||||
mini_mime (1.0.1)
|
||||
mini_portile2 (2.1.0)
|
||||
mini_racer (0.2.9)
|
||||
libv8 (>= 6.9.411)
|
||||
mini_portile2 (2.4.0)
|
||||
mini_racer (0.2.10)
|
||||
libv8 (> 7.3)
|
||||
momentjs-rails (2.20.1)
|
||||
railties (>= 3.1)
|
||||
money (5.1.1)
|
||||
@@ -461,8 +454,8 @@ GEM
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
newrelic_rpm (3.18.1.330)
|
||||
nokogiri (1.6.8.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
nokogiri (1.10.9)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
oauth2 (1.4.4)
|
||||
faraday (>= 0.8, < 2.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
@@ -483,7 +476,7 @@ GEM
|
||||
parallel (1.19.1)
|
||||
paranoia (1.3.4)
|
||||
activerecord (~> 3.1)
|
||||
parser (2.7.0.5)
|
||||
parser (2.7.1.0)
|
||||
ast (~> 2.4.0)
|
||||
paypal-sdk-core (0.2.10)
|
||||
multi_json (~> 1.0)
|
||||
@@ -502,8 +495,6 @@ GEM
|
||||
byebug (>= 9.0, < 9.1)
|
||||
pry (~> 0.10)
|
||||
public_suffix (4.0.3)
|
||||
rabl (0.8.4)
|
||||
activesupport (>= 2.3.14)
|
||||
rack (1.4.7)
|
||||
rack-cache (1.11.0)
|
||||
rack (>= 0.4)
|
||||
@@ -596,7 +587,7 @@ GEM
|
||||
rexml
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-rails (2.5.1)
|
||||
rubocop-rails (2.5.2)
|
||||
activesupport
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
@@ -656,7 +647,7 @@ GEM
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unicode-display_width (1.7.0)
|
||||
unicorn (5.5.4)
|
||||
unicorn (5.5.5)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
unicorn-rails (2.2.1)
|
||||
@@ -715,7 +706,6 @@ DEPENDENCIES
|
||||
db2fog
|
||||
ddtrace
|
||||
debugger-linecache
|
||||
deface (= 1.0.2)
|
||||
delayed_job_active_record
|
||||
delayed_job_web
|
||||
devise (~> 2.2.5)
|
||||
@@ -742,10 +732,9 @@ DEPENDENCIES
|
||||
kaminari (~> 0.14.1)
|
||||
knapsack
|
||||
letter_opener (>= 1.4.1)
|
||||
mini_racer (= 0.2.9)
|
||||
mini_racer (= 0.2.10)
|
||||
momentjs-rails
|
||||
newrelic_rpm (~> 3.0)
|
||||
nokogiri (>= 1.6.7.1)
|
||||
oauth2 (~> 1.4.4)
|
||||
ofn-qz!
|
||||
oj
|
||||
@@ -754,7 +743,6 @@ DEPENDENCIES
|
||||
paperclip (~> 3.4.1)
|
||||
pg (~> 0.21.0)
|
||||
pry-byebug (>= 3.4.3)
|
||||
rabl
|
||||
rack-mini-profiler (< 3.0.0)
|
||||
rack-rewrite
|
||||
rack-ssl
|
||||
|
||||
@@ -29,7 +29,7 @@ angular.module("admin.enterprises")
|
||||
# from a directive "nav-check" in the page - if we pass it here it will be called in the test suite,
|
||||
# and on all new uses of this contoller, and we might not want that.
|
||||
enterpriseNavCallback = ->
|
||||
if $scope.enterprise_form != undefined && $scope.enterprise_form.$dirty
|
||||
if $scope.enterprise_form?.$dirty
|
||||
t('admin.unsaved_confirm_leave')
|
||||
|
||||
# Register the NavigationCheck callback
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
angular.module('admin.orderCycles')
|
||||
.controller 'AdminEditOrderCycleCtrl', ($scope, $controller, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, ocInstance) ->
|
||||
.controller 'AdminEditOrderCycleCtrl', ($scope, $controller, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, NavigationCheck, ocInstance) ->
|
||||
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
|
||||
|
||||
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
@@ -18,5 +18,12 @@ angular.module('admin.orderCycles')
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
NavigationCheck.clear()
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
OrderCycle.update(destination, $scope.order_cycle_form)
|
||||
|
||||
warnAboutUnsavedChanges = ->
|
||||
if $scope.order_cycle_form?.$dirty
|
||||
t('admin.unsaved_confirm_leave')
|
||||
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
@@ -67,7 +67,7 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $timeout, Reque
|
||||
return unless sort && sort.predicate != ""
|
||||
|
||||
$scope.sorting = sort.getSortingExpr()
|
||||
$scope.fetchProducts()
|
||||
$scope.fetchResults()
|
||||
, true
|
||||
|
||||
$scope.capturePayment = (order) ->
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Darkswarm.directive "darkerBackground", ->
|
||||
restrict: "A"
|
||||
link: (scope, elm, attr)->
|
||||
toggleClass = (value) ->
|
||||
elm.closest('.page-view').toggleClass("with-darker-background", value)
|
||||
|
||||
toggleClass(true)
|
||||
|
||||
# if an OrderCycle is selected, disable darker background
|
||||
scope.$watch 'order_cycle.order_cycle_id', (newvalue, oldvalue) ->
|
||||
toggleClass(false) if newvalue
|
||||
@@ -5,5 +5,9 @@ Darkswarm.directive "shopVariant", ->
|
||||
scope:
|
||||
variant: '='
|
||||
controller: ($scope, Cart) ->
|
||||
$scope.$watchGroup ['variant.line_item.quantity', 'variant.line_item.max_quantity'], ->
|
||||
$scope.$watchGroup [
|
||||
'variant.line_item.quantity',
|
||||
'variant.line_item.max_quantity'
|
||||
], (new_value, old_value) ->
|
||||
return if old_value[0] == null && new_value[0] == null
|
||||
Cart.adjust($scope.variant.line_item)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService) ->
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService, RailsFlashLoader) ->
|
||||
# Handles syncing of current cart/order state to server
|
||||
new class Cart
|
||||
dirty: false
|
||||
@@ -50,7 +50,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
|
||||
@popQueue() if @update_enqueued
|
||||
|
||||
.error (response, status)=>
|
||||
@scheduleRetry(status)
|
||||
RailsFlashLoader.loadFlash({error: t('js.cart.add_to_cart_failed')})
|
||||
@update_running = false
|
||||
|
||||
compareAndNotifyStockLevels: (stockLevels) =>
|
||||
@@ -87,13 +87,6 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
|
||||
max_quantity: li.max_quantity
|
||||
{variants: variants}
|
||||
|
||||
scheduleRetry: (status) =>
|
||||
console.log "Error updating cart: #{status}. Retrying in 3 seconds..."
|
||||
$timeout =>
|
||||
console.log "Retrying cart update"
|
||||
@orderChanged()
|
||||
, 3000
|
||||
|
||||
saved: =>
|
||||
@dirty = false
|
||||
$(window).unbind "beforeunload"
|
||||
|
||||
@@ -14,15 +14,28 @@ Darkswarm.factory 'Checkout', ($injector, CurrentOrder, ShippingMethods, StripeE
|
||||
|
||||
submit: =>
|
||||
Loading.message = t 'submitting_order'
|
||||
$http.put('/checkout.json', {order: @preprocess()}).success (data, status)=>
|
||||
Navigation.go data.path
|
||||
.error (response, status)=>
|
||||
if response.path
|
||||
Navigation.go response.path
|
||||
else
|
||||
Loading.clear()
|
||||
@errors = response.errors
|
||||
RailsFlashLoader.loadFlash(response.flash)
|
||||
$http.put('/checkout.json', {order: @preprocess()})
|
||||
.then (response) =>
|
||||
Navigation.go response.data.path
|
||||
.catch (response) =>
|
||||
try
|
||||
@handle_checkout_error_response(response)
|
||||
catch error
|
||||
@loadFlash(error: t("checkout.failed")) # inform the user about the unexpected error
|
||||
throw error # generate a BugsnagJS alert
|
||||
|
||||
handle_checkout_error_response: (response) =>
|
||||
if response.data.path
|
||||
Navigation.go response.data.path
|
||||
else
|
||||
throw response unless response.data.flash
|
||||
|
||||
@errors = response.data.errors
|
||||
@loadFlash(response.data.flash)
|
||||
|
||||
loadFlash: (flash) =>
|
||||
Loading.clear()
|
||||
RailsFlashLoader.loadFlash(flash)
|
||||
|
||||
# Rails wants our Spree::Address data to be provided with _attributes
|
||||
preprocess: ->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Darkswarm.factory 'OrderCycle', ($resource, orderCycleData) ->
|
||||
class OrderCycle
|
||||
@order_cycle = orderCycleData # Object or {} due to RABL
|
||||
@order_cycle = orderCycleData # Object or {}
|
||||
@push_order_cycle: (callback) ->
|
||||
new $resource("/shop/order_cycle").save {order_cycle_id: @order_cycle.order_cycle_id}, (order_data)->
|
||||
OrderCycle.order_cycle.orders_close_at = order_data.orders_close_at
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
%hr
|
||||
%div.menu_item.text-center
|
||||
%input.fullwidth.orange{ type: "button", ng: { value: "saved() ? 'Saved': 'Saving'", show: "saved() || saving", disabled: "saved()" } }
|
||||
%input.fullwidth.red{ type: "button", value: 'Save As Default', ng: { show: "!saved() && !saving", click: "saveColumnPreferences(action)"} }
|
||||
%input.fullwidth.red{ type: "button", :value => t('admin.column_save_as_default').html_safe, ng: { show: "!saved() && !saving", click: "saveColumnPreferences(action)"} }
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
min: 0,
|
||||
placeholder: "0",
|
||||
"ofn-disable-scroll" => true,
|
||||
"ng-debounce" => "500",
|
||||
onwheel: "this.blur()",
|
||||
"ng-model" => "variant.line_item.quantity",
|
||||
"ofn-on-hand" => "{{variant.on_demand && 9999 || variant.on_hand }}",
|
||||
"ng-disabled" => "!variant.on_demand && variant.on_hand == 0",
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"ng-model" => "variant.line_item.quantity",
|
||||
placeholder: "{{::'shop_variant_quantity_min' | t}}",
|
||||
"ofn-disable-scroll" => true,
|
||||
"ng-debounce" => "500",
|
||||
onwheel: "this.blur()",
|
||||
"ofn-on-hand" => "{{variant.on_demand && 9999 || variant.on_hand }}",
|
||||
name: "variants[{{::variant.id}}]", id: "variants_{{::variant.id}}"}
|
||||
%span.bulk-input
|
||||
@@ -19,6 +21,8 @@
|
||||
"ng-model" => "variant.line_item.max_quantity",
|
||||
placeholder: "{{::'shop_variant_quantity_max' | t}}",
|
||||
"ofn-disable-scroll" => true,
|
||||
"ng-debounce" => "500",
|
||||
onwheel: "this.blur()",
|
||||
min: "{{variant.line_item.quantity}}",
|
||||
name: "variant_attributes[{{::variant.id}}][max_quantity]",
|
||||
id: "variants_{{::variant.id}}_max"}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
input#search {
|
||||
@include medium-input(rgba(0, 0, 0, 0.3), #777, $clr-brick);
|
||||
|
||||
// avoid zoom on iphone, see issue #4535
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
// ordering
|
||||
|
||||
@@ -49,7 +49,7 @@ ordercycle {
|
||||
border-radius: 0.25em 0 0 0.25em;
|
||||
float: left;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
line-height: 1.3em;
|
||||
padding: 0.5em 0.75em;
|
||||
height: 2.35em;
|
||||
|
||||
@@ -60,6 +60,15 @@ ordercycle {
|
||||
}
|
||||
|
||||
select {
|
||||
background-image: url('/assets/white-caret.svg');
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
select,
|
||||
p {
|
||||
width: inherit;
|
||||
display: inline-block;
|
||||
color: $white;
|
||||
@@ -67,10 +76,9 @@ ordercycle {
|
||||
border: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
line-height: 1.3em;
|
||||
padding: 0.5em 1.25em 0.5em 0.75em;
|
||||
height: 2.35em;
|
||||
background-image: url('/assets/white-caret.svg');
|
||||
background-size: 30px auto;
|
||||
border-radius: 0 0.25em 0.25em 0;
|
||||
min-width: 13em;
|
||||
@@ -80,6 +88,10 @@ ordercycle {
|
||||
}
|
||||
}
|
||||
|
||||
option {
|
||||
color: $grey-700;
|
||||
}
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
float: none;
|
||||
margin-right: 1em;
|
||||
|
||||
@@ -38,14 +38,15 @@ $med-drk-grey: #444;
|
||||
$dark-grey: #333;
|
||||
$light-grey: #ddd;
|
||||
$light-grey-transparency: rgba(0, 0, 0, .1);
|
||||
$very-light-grey-transparency: rgba(0, 0, 0, .05);
|
||||
$black: #000;
|
||||
$white: #fff;
|
||||
|
||||
$grey-050: #f7f7f7;
|
||||
|
||||
$grey-400: #bbb;
|
||||
$grey-500: #999;
|
||||
$grey-600: #777;
|
||||
$grey-650: #666;
|
||||
$grey-700: #555;
|
||||
$grey-800: #333;
|
||||
|
||||
@@ -54,6 +55,7 @@ $teal-400: #4cb5c5;
|
||||
$teal-500: #0096ad;
|
||||
|
||||
$orange-400: #ff9466;
|
||||
$orange-450: #f4704c;
|
||||
$orange-500: #f27052;
|
||||
$orange-600: #d7583a;
|
||||
|
||||
|
||||
@@ -114,7 +114,16 @@
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.shopfront-message {
|
||||
.select-oc-message {
|
||||
margin-top: 1rem;
|
||||
|
||||
.highlighted {
|
||||
color: $red-700;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.open-shop-message {
|
||||
a {
|
||||
color: #0096ad;
|
||||
|
||||
@@ -125,21 +134,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
.shopfront_closed_message, .shopfront_hidden_message {
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
.closed-shop-header {
|
||||
background-color: $grey-650;
|
||||
color: $white;
|
||||
|
||||
h4 {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1rem 0 0.4rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.shopfront_closed_message {
|
||||
border: 2px solid #eb4c46;
|
||||
}
|
||||
.warning-sign {
|
||||
margin: 0 10px 0 5px;
|
||||
display: inline-block;
|
||||
|
||||
.shopfront_closed_message {
|
||||
margin: 2em 0em;
|
||||
}
|
||||
strong {
|
||||
color: $grey-650;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 23px;
|
||||
}
|
||||
|
||||
.shopfront_hidden_message {
|
||||
border: 2px solid #db4;
|
||||
margin: 2em 0em;
|
||||
.rectangle {
|
||||
background-color: $white;
|
||||
border-radius: 4px;
|
||||
color: $grey-650;
|
||||
height: 23px;
|
||||
position: absolute;
|
||||
top: 27px;
|
||||
transform: rotate(-315deg);
|
||||
width: 23px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +76,9 @@
|
||||
// content revealed in accordion
|
||||
|
||||
.page-view {
|
||||
margin-bottom: 5em;
|
||||
background: none;
|
||||
border: none;
|
||||
padding-bottom: 5em;
|
||||
|
||||
.content {
|
||||
padding: 1.25em 0;
|
||||
@@ -121,5 +121,13 @@
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&.with-darker-background {
|
||||
background-color: $very-light-grey-transparency;
|
||||
|
||||
a {
|
||||
color: $teal-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,3 +115,14 @@
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.links {
|
||||
.button {
|
||||
padding: 1.125rem 0 1.1875rem;
|
||||
width: 210px;
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,13 +64,13 @@
|
||||
|
||||
.button.primary, button.primary {
|
||||
font-family: $body-font;
|
||||
background: $clr-brick;
|
||||
background: $orange-450;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button.primary:hover, .button.primary:active, .button.primary:focus, button.primary:hover, button.primary:active, button.primary:focus {
|
||||
background: $clr-brick-bright;
|
||||
text-shadow: 0 1px 0 $clr-brick;
|
||||
background: $orange-400;
|
||||
text-shadow: 0 1px 0 $orange-450;
|
||||
}
|
||||
|
||||
button.success, .button.success {
|
||||
|
||||
@@ -138,6 +138,10 @@ a {
|
||||
&.ms {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
&.ig {
|
||||
background-color: #fb3958 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar .soc-btn {
|
||||
|
||||
@@ -17,8 +17,9 @@ module Admin
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: params[:enterprise_id]))
|
||||
render_as_json @collection, tag_rule_mapping: tag_rule_mapping
|
||||
render_as_json @collection,
|
||||
tag_rule_mapping: tag_rule_mapping,
|
||||
customer_tags: customer_tags_by_id
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -64,8 +65,13 @@ module Admin
|
||||
def collection
|
||||
return Customer.where("1=0") unless json_request? && params[:enterprise_id].present?
|
||||
|
||||
enterprise = Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id])
|
||||
Customer.of(enterprise)
|
||||
Customer.of(managed_enterprise_id).
|
||||
includes(:bill_address, :ship_address, user: :credit_cards)
|
||||
end
|
||||
|
||||
def managed_enterprise_id
|
||||
@managed_enterprise_id ||= Enterprise.managed_by(spree_current_user).
|
||||
select('enterprises.id').find_by_id(params[:enterprise_id])
|
||||
end
|
||||
|
||||
def load_managed_shops
|
||||
@@ -80,5 +86,28 @@ module Admin
|
||||
def ams_prefix_whitelist
|
||||
[:subscription]
|
||||
end
|
||||
|
||||
def tag_rule_mapping
|
||||
TagRule.mapping_for(Enterprise.where(id: managed_enterprise_id))
|
||||
end
|
||||
|
||||
# Fetches tags for all customers of the enterprise and returns a hash indexed by customer_id
|
||||
def customer_tags_by_id
|
||||
customer_tags = ::ActsAsTaggableOn::Tag.
|
||||
joins(:taggings).
|
||||
includes(:taggings).
|
||||
where(taggings:
|
||||
{ taggable_type: 'Customer',
|
||||
taggable_id: Customer.of(managed_enterprise_id),
|
||||
context: 'tags' })
|
||||
|
||||
customer_tags.each_with_object({}) do |tag, indexed_hash|
|
||||
tag.taggings.each do |tagging|
|
||||
customer_id = tagging.taggable_id
|
||||
indexed_hash[customer_id] ||= []
|
||||
indexed_hash[customer_id] << tag.name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
module Admin
|
||||
class EnterpriseRelationshipsController < ResourceController
|
||||
def index
|
||||
@my_enterprises = Enterprise.managed_by(spree_current_user).by_name
|
||||
@all_enterprises = Enterprise.by_name
|
||||
@enterprise_relationships = EnterpriseRelationship.by_name.involving_enterprises @my_enterprises
|
||||
@my_enterprises = Enterprise.
|
||||
includes(:shipping_methods, :payment_methods).
|
||||
managed_by(spree_current_user).by_name
|
||||
@all_enterprises = Enterprise.includes(:shipping_methods, :payment_methods).by_name
|
||||
@enterprise_relationships = EnterpriseRelationship.
|
||||
includes(:parent, :child).
|
||||
by_name.involving_enterprises @my_enterprises
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
module Api
|
||||
class OrderCyclesController < Api::BaseController
|
||||
include EnterprisesHelper
|
||||
respond_to :json
|
||||
include ApiActionCaching
|
||||
|
||||
skip_authorization_check
|
||||
skip_before_filter :authenticate_user, :ensure_api_key, only: [:taxons, :properties]
|
||||
|
||||
caches_action :taxons, :properties,
|
||||
expires_in: CacheService::FILTERS_EXPIRY,
|
||||
cache_path: proc { |controller| controller.request.url }
|
||||
|
||||
def products
|
||||
return render_no_products unless order_cycle.open?
|
||||
|
||||
products = ProductsRenderer.new(
|
||||
distributor,
|
||||
order_cycle,
|
||||
@@ -15,7 +22,7 @@ module Api
|
||||
|
||||
render json: products
|
||||
rescue ProductsRenderer::NoProducts
|
||||
render status: :not_found, json: ''
|
||||
render_no_products
|
||||
end
|
||||
|
||||
def taxons
|
||||
@@ -35,6 +42,10 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
def render_no_products
|
||||
render status: :not_found, json: ''
|
||||
end
|
||||
|
||||
def product_properties
|
||||
Spree::Property.
|
||||
joins(:products).
|
||||
@@ -70,11 +81,11 @@ module Api
|
||||
end
|
||||
|
||||
def distributor
|
||||
Enterprise.find_by_id(params[:distributor])
|
||||
@distributor ||= Enterprise.find_by_id(params[:distributor])
|
||||
end
|
||||
|
||||
def order_cycle
|
||||
OrderCycle.find_by_id(params[:id])
|
||||
@order_cycle ||= OrderCycle.find_by_id(params[:id])
|
||||
end
|
||||
|
||||
def customer
|
||||
|
||||
@@ -14,10 +14,6 @@ class BaseController < ApplicationController
|
||||
|
||||
helper 'spree/base'
|
||||
|
||||
# Spree::Core::ControllerHelpers declares helper_method get_taxonomies, so we need to
|
||||
# include Spree::ProductsHelper so that method is available on the controller
|
||||
include Spree::ProductsHelper
|
||||
|
||||
before_filter :set_locale
|
||||
before_filter :check_order_cycle_expiry
|
||||
|
||||
|
||||
@@ -4,24 +4,24 @@ class CartController < BaseController
|
||||
before_filter :check_authorization
|
||||
|
||||
def populate
|
||||
order = current_order(true)
|
||||
|
||||
# Without intervention, the Spree::Adjustment#update_adjustable callback is called many times
|
||||
# during cart population, for both taxation and enterprise fees. This operation triggers a
|
||||
# costly Spree::Order#update!, which only needs to be run once. We avoid this by disabling
|
||||
# callbacks on Spree::Adjustment and then manually invoke Spree::Order#update! on success.
|
||||
Spree::Adjustment.without_callbacks do
|
||||
cart_service = CartService.new(current_order(true))
|
||||
cart_service = CartService.new(order)
|
||||
|
||||
if cart_service.populate(params.slice(:products, :variants, :quantity), true)
|
||||
fire_event('spree.cart.add')
|
||||
fire_event('spree.order.contents_changed')
|
||||
|
||||
current_order.cap_quantity_at_stock!
|
||||
current_order.update!
|
||||
order.update_distribution_charge!
|
||||
order.cap_quantity_at_stock!
|
||||
order.update!
|
||||
|
||||
variant_ids = variant_ids_in(cart_service.variants_h)
|
||||
|
||||
render json: { error: false,
|
||||
stock_levels: VariantsStockLevels.new.call(current_order, variant_ids) },
|
||||
stock_levels: VariantsStockLevels.new.call(order, variant_ids) },
|
||||
status: :ok
|
||||
else
|
||||
render json: { error: true }, status: :precondition_failed
|
||||
|
||||
@@ -133,13 +133,6 @@ class CheckoutController < Spree::StoreController
|
||||
@order.ship_address = finder.ship_address
|
||||
end
|
||||
|
||||
def before_delivery
|
||||
return if params[:order].present?
|
||||
|
||||
packages = @order.shipments.map(&:to_package)
|
||||
@differentiator = Spree::Stock::Differentiator.new(@order, packages)
|
||||
end
|
||||
|
||||
def before_payment
|
||||
current_order.payments.destroy_all if request.put?
|
||||
end
|
||||
@@ -153,10 +146,12 @@ class CheckoutController < Spree::StoreController
|
||||
end
|
||||
|
||||
def valid_payment_intent_provided?
|
||||
params["payment_intent"]&.starts_with?("pi_") &&
|
||||
@order.state == "payment" &&
|
||||
@order.payments.last.state == "pending" &&
|
||||
@order.payments.last.response_code == params["payment_intent"]
|
||||
return false unless params["payment_intent"]&.starts_with?("pi_")
|
||||
|
||||
last_payment = OrderPaymentFinder.new(@order).last_payment
|
||||
@order.state == "payment" &&
|
||||
last_payment&.state == "pending" &&
|
||||
last_payment&.response_code == params["payment_intent"]
|
||||
end
|
||||
|
||||
def handle_redirect_from_stripe
|
||||
|
||||
@@ -18,9 +18,9 @@ class HomeController < BaseController
|
||||
|
||||
private
|
||||
|
||||
# Cache the value of the query count for 24 hours
|
||||
def cached_count(key, query)
|
||||
Rails.cache.fetch("home_stats_count_#{key}", expires_in: 1.day, race_condition_ttl: 10) do
|
||||
# Cache the value of the query count
|
||||
def cached_count(statistic, query)
|
||||
CacheService.home_stats(statistic) do
|
||||
query.count
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,10 +4,6 @@ module Spree
|
||||
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
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ module Spree
|
||||
return
|
||||
end
|
||||
|
||||
authorize_stripe_sca_payment
|
||||
|
||||
if @order.completed?
|
||||
@payment.process!
|
||||
flash[:success] = flash_message_for(@payment, :successfully_created)
|
||||
@@ -93,7 +95,7 @@ module Spree
|
||||
available(:back_end).
|
||||
select{ |pm| pm.has_distributor? @order.distributor }
|
||||
|
||||
@payment_method = if @payment && @payment.payment_method
|
||||
@payment_method = if @payment&.payment_method
|
||||
@payment.payment_method
|
||||
else
|
||||
@payment_methods.first
|
||||
@@ -124,6 +126,13 @@ module Spree
|
||||
def load_payment
|
||||
@payment = Payment.find(params[:id])
|
||||
end
|
||||
|
||||
def authorize_stripe_sca_payment
|
||||
return unless @payment.payment_method.class == Spree::Gateway::StripeSCA
|
||||
|
||||
@payment.authorize!
|
||||
raise Spree::Core::GatewayError, I18n.t('authorization_failure') unless @payment.pending?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
module Spree
|
||||
module Admin
|
||||
module Reports
|
||||
class EnterpriseFeeSummariesController < BaseController
|
||||
before_filter :load_report_parameters
|
||||
before_filter :load_permissions
|
||||
|
||||
def new; end
|
||||
|
||||
def create
|
||||
return respond_to_invalid_parameters unless @report_parameters.valid?
|
||||
|
||||
@report_parameters.authorize!(@permissions)
|
||||
|
||||
@report = report_klass::ReportService.new(@permissions, @report_parameters)
|
||||
renderer.render(self)
|
||||
rescue ::Reports::Authorizer::ParameterNotAllowedError => e
|
||||
flash[:error] = e.message
|
||||
render_report_form
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def respond_to_invalid_parameters
|
||||
flash[:error] = I18n.t("invalid_filter_parameters", scope: i18n_scope)
|
||||
render_report_form
|
||||
end
|
||||
|
||||
def i18n_scope
|
||||
"order_management.reports.enterprise_fee_summary"
|
||||
end
|
||||
|
||||
def render_report_form
|
||||
render action: :new
|
||||
end
|
||||
|
||||
def report_klass
|
||||
OrderManagement::Reports::EnterpriseFeeSummary
|
||||
end
|
||||
|
||||
def load_report_parameters
|
||||
@report_parameters = report_klass::Parameters.new(params[:report] || {})
|
||||
end
|
||||
|
||||
def load_permissions
|
||||
@permissions = report_klass::Permissions.new(spree_current_user)
|
||||
end
|
||||
|
||||
def report_renderer_klass
|
||||
case params[:report_format]
|
||||
when "csv"
|
||||
report_klass::Renderers::CsvRenderer
|
||||
when nil, "", "html"
|
||||
report_klass::Renderers::HtmlRenderer
|
||||
else
|
||||
raise Reports::UnsupportedReportFormatException
|
||||
end
|
||||
end
|
||||
|
||||
def renderer
|
||||
@renderer ||= report_renderer_klass.new(@report)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -298,11 +298,20 @@ module Spree
|
||||
end
|
||||
|
||||
def url_for_report(report)
|
||||
public_send("#{report}_admin_reports_url".to_sym)
|
||||
if report_in_order_management_engine?(report)
|
||||
main_app.public_send("new_order_management_reports_#{report}_url".to_sym)
|
||||
else
|
||||
public_send("#{report}_admin_reports_url".to_sym)
|
||||
end
|
||||
rescue NoMethodError
|
||||
url_for([:new, :admin, :reports, report.to_s.singularize])
|
||||
end
|
||||
|
||||
# List of reports that have been moved to the Order Management engine
|
||||
def report_in_order_management_engine?(report)
|
||||
report == :enterprise_fee_summary
|
||||
end
|
||||
|
||||
def timestamp
|
||||
Time.zone.now.strftime("%Y%m%d")
|
||||
end
|
||||
|
||||
@@ -3,14 +3,6 @@ module Spree
|
||||
class TaxonsController < Spree::Admin::BaseController
|
||||
respond_to :html, :json, :js
|
||||
|
||||
def search
|
||||
@taxons = if params[:ids]
|
||||
Spree::Taxon.where(id: params[:ids].split(','))
|
||||
else
|
||||
Spree::Taxon.limit(20).search(name_cont: params[:q]).result
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.build(params[:taxon])
|
||||
|
||||
@@ -47,7 +47,7 @@ module InjectionHelper
|
||||
enterprises_and_relatives = current_distributor.
|
||||
relatives_including_self.
|
||||
activated.
|
||||
includes(address: [:state, :country]).
|
||||
includes(:properties, address: [:state, :country], supplied_products: :properties).
|
||||
all
|
||||
|
||||
inject_json_ams "enterprises",
|
||||
|
||||
7
app/helpers/order_helper.rb
Normal file
7
app/helpers/order_helper.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module OrderHelper
|
||||
def last_payment_method(order)
|
||||
OrderPaymentFinder.new(order).last_payment&.payment_method
|
||||
end
|
||||
end
|
||||
@@ -13,7 +13,7 @@ module ShopHelper
|
||||
end
|
||||
|
||||
def require_customer?
|
||||
current_distributor.require_login? && !user_is_related_to_distributor?
|
||||
@require_customer ||= current_distributor.require_login? && !user_is_related_to_distributor?
|
||||
end
|
||||
|
||||
def user_is_related_to_distributor?
|
||||
@@ -48,6 +48,6 @@ module ShopHelper
|
||||
end
|
||||
|
||||
def no_open_order_cycles?
|
||||
@order_cycles && @order_cycles.empty?
|
||||
@no_open_order_cycles ||= @order_cycles&.empty?
|
||||
end
|
||||
end
|
||||
|
||||
13
app/helpers/spree/products_helper.rb
Normal file
13
app/helpers/spree/products_helper.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module ProductsHelper
|
||||
def product_has_variant_unit_option_type?(product)
|
||||
product.option_types.any? { |option_type| variant_unit_option_type? option_type }
|
||||
end
|
||||
|
||||
def variant_unit_option_type?(option_type)
|
||||
Spree::Product.all_variant_unit_option_types.include? option_type
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,23 +0,0 @@
|
||||
module Spree
|
||||
ProductsHelper.class_eval do
|
||||
# Return the price of the variant, overriding sprees price diff capability.
|
||||
# This will allways return the variant price as if the show_variant_full_price is set.
|
||||
def variant_price_diff(variant)
|
||||
"(#{Spree::Money.new(variant.price)})"
|
||||
end
|
||||
|
||||
def product_has_variant_unit_option_type?(product)
|
||||
product.option_types.any? { |option_type| variant_unit_option_type? option_type }
|
||||
end
|
||||
|
||||
def variant_unit_option_type?(option_type)
|
||||
Spree::Product.all_variant_unit_option_types.include? option_type
|
||||
end
|
||||
|
||||
def product_variant_unit_options
|
||||
[[I18n.t(:weight), 'weight'],
|
||||
[I18n.t(:volume), 'volume'],
|
||||
[I18n.t(:items), 'items']]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -57,16 +57,28 @@ class SubscriptionConfirmJob
|
||||
return true unless order.payment_required?
|
||||
|
||||
setup_payment!(order)
|
||||
return false if order.errors.present?
|
||||
return false if order.errors.any?
|
||||
|
||||
authorize_payment!(order)
|
||||
return false if order.errors.any?
|
||||
|
||||
order.process_payments!
|
||||
return false if order.errors.present?
|
||||
return false if order.errors.any?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def setup_payment!(order)
|
||||
OrderManagement::Subscriptions::PaymentSetup.new(order).call!
|
||||
return if order.errors.any?
|
||||
|
||||
OrderManagement::Subscriptions::StripePaymentSetup.new(order).call!
|
||||
end
|
||||
|
||||
def authorize_payment!(order)
|
||||
return if order.subscription.payment_method.class != Spree::Gateway::StripeSCA
|
||||
|
||||
OrderManagement::Subscriptions::StripeScaPaymentAuthorize.new(order).call!
|
||||
end
|
||||
|
||||
def send_confirmation_email(order)
|
||||
|
||||
@@ -1,41 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProducerMailer < Spree::BaseMailer
|
||||
include I18nHelper
|
||||
|
||||
def order_cycle_report(producer, order_cycle)
|
||||
@producer = producer
|
||||
@coordinator = order_cycle.coordinator
|
||||
@order_cycle = order_cycle
|
||||
line_items = line_items_from(@order_cycle, @producer)
|
||||
@grouped_line_items = line_items.group_by(&:product_and_full_name)
|
||||
@receival_instructions = @order_cycle.receival_instructions_for @producer
|
||||
@total = total_from_line_items(line_items)
|
||||
@tax_total = tax_total_from_line_items(line_items)
|
||||
|
||||
I18n.with_locale valid_locale(@producer.owner) do
|
||||
order_cycle_subject = I18n.t('producer_mailer.order_cycle.subject', producer: producer.name)
|
||||
subject = "[#{Spree::Config.site_name}] #{order_cycle_subject}"
|
||||
with_unscoped_products_and_variants do
|
||||
load_data
|
||||
|
||||
return unless has_orders?(order_cycle, producer)
|
||||
I18n.with_locale(owner_locale) do
|
||||
return unless orders?(order_cycle, producer)
|
||||
|
||||
mail(
|
||||
to: @producer.contact.email,
|
||||
from: from_address,
|
||||
subject: subject,
|
||||
reply_to: @coordinator.contact.email,
|
||||
cc: @coordinator.contact.email
|
||||
)
|
||||
mail(
|
||||
to: @producer.contact.email,
|
||||
from: from_address,
|
||||
subject: subject,
|
||||
reply_to: @coordinator.contact.email,
|
||||
cc: @coordinator.contact.email
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def has_orders?(order_cycle, producer)
|
||||
def owner_locale
|
||||
valid_locale(@producer.owner)
|
||||
end
|
||||
|
||||
def load_data
|
||||
@coordinator = @order_cycle.coordinator
|
||||
|
||||
line_items = line_items_from(@order_cycle, @producer)
|
||||
|
||||
@grouped_line_items = line_items.group_by(&:product_and_full_name)
|
||||
@receival_instructions = @order_cycle.receival_instructions_for(@producer)
|
||||
@total = total_from_line_items(line_items)
|
||||
@tax_total = tax_total_from_line_items(line_items)
|
||||
end
|
||||
|
||||
def subject
|
||||
order_cycle_subject = I18n.t('producer_mailer.order_cycle.subject', producer: @producer.name)
|
||||
"[#{Spree::Config.site_name}] #{order_cycle_subject}"
|
||||
end
|
||||
|
||||
def orders?(order_cycle, producer)
|
||||
line_items_from(order_cycle, producer).any?
|
||||
end
|
||||
|
||||
def line_items_from(order_cycle, producer)
|
||||
Spree::LineItem.
|
||||
includes(variant: { option_values: :option_type }).
|
||||
@line_items ||= Spree::LineItem.
|
||||
includes(:option_values, variant: [:product, { option_values: :option_type }]).
|
||||
from_order_cycle(order_cycle).
|
||||
sorted_by_name_and_unit_value.
|
||||
merge(Spree::Product.in_supplier(producer)).
|
||||
@@ -49,4 +66,22 @@ class ProducerMailer < Spree::BaseMailer
|
||||
def tax_total_from_line_items(line_items)
|
||||
Spree::Money.new line_items.sum(&:included_tax)
|
||||
end
|
||||
|
||||
# This hack makes ActiveRecord skip the default_scope (deleted_at IS NULL)
|
||||
# when eager loading associations. Further details:
|
||||
# https://github.com/rails/rails/issues/11036
|
||||
def with_unscoped_products_and_variants
|
||||
variant_default_scopes = Spree::Variant.default_scopes
|
||||
product_default_scopes = Spree::Product.default_scopes
|
||||
|
||||
Spree::Variant.default_scopes = []
|
||||
Spree::Product.default_scopes = []
|
||||
|
||||
return_value = yield
|
||||
|
||||
Spree::Variant.default_scopes = variant_default_scopes
|
||||
Spree::Product.default_scopes = product_default_scopes
|
||||
|
||||
return_value
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@ Spree::OrderMailer.class_eval do
|
||||
helper HtmlHelper
|
||||
helper CheckoutHelper
|
||||
helper SpreeCurrencyHelper
|
||||
helper OrderHelper
|
||||
include I18nHelper
|
||||
|
||||
def cancel_email(order_or_order_id, resend = false)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class SubscriptionMailer < Spree::BaseMailer
|
||||
helper CheckoutHelper
|
||||
helper ShopMailHelper
|
||||
helper OrderHelper
|
||||
include I18nHelper
|
||||
|
||||
def confirmation_email(order)
|
||||
|
||||
21
app/models/concerns/api_action_caching.rb
Normal file
21
app/models/concerns/api_action_caching.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# API controllers inherit from ActionController::Metal to keep them slim and fast.
|
||||
# This concern adds the minimum requirements needed to use Action Caching in the API.
|
||||
|
||||
module ApiActionCaching
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ActionController::Caching
|
||||
include ActionController::Caching::Actions
|
||||
include AbstractController::Layouts
|
||||
|
||||
# These configs are not assigned to the controller automatically with ActionController::Metal
|
||||
self.cache_store = Rails.configuration.cache_store
|
||||
self.perform_caching = true
|
||||
|
||||
# ActionController::Caching asks for a controller's layout, but they're not used in the API
|
||||
layout false
|
||||
end
|
||||
end
|
||||
@@ -40,9 +40,18 @@ module VariantStock
|
||||
|
||||
# Checks whether this variant is produced on demand.
|
||||
def on_demand
|
||||
# A variant that has not been saved yet, doesn't have a stock item
|
||||
# A variant that has not been saved yet or has been soft-deleted doesn't have a stock item
|
||||
# This provides a default value for variant.on_demand using Spree::StockLocation.backorderable_default
|
||||
return Spree::StockLocation.first.backorderable_default if stock_items.empty?
|
||||
return Spree::StockLocation.first.backorderable_default if new_record? || deleted?
|
||||
|
||||
# This can be removed unless we have seen this error in Bugsnag recently
|
||||
if stock_item.nil?
|
||||
Bugsnag.notify(
|
||||
RuntimeError.new("Variant #stock_item called, but the stock_item does not exist!"),
|
||||
object: as_json
|
||||
)
|
||||
return Spree::StockLocation.first.backorderable_default
|
||||
end
|
||||
|
||||
stock_item.backorderable?
|
||||
end
|
||||
|
||||
@@ -57,8 +57,8 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration
|
||||
# Other
|
||||
preference :footer_facebook_url, :string, default: "https://www.facebook.com/OpenFoodNet"
|
||||
preference :footer_twitter_url, :string, default: "https://twitter.com/OpenFoodNet"
|
||||
preference :footer_instagram_url, :string, default: ""
|
||||
preference :footer_linkedin_url, :string, default: "http://www.linkedin.com/groups/Open-Food-Foundation-4743336"
|
||||
preference :footer_instagram_url, :string, default: "https://www.instagram.com/openfoodnetworkuk/"
|
||||
preference :footer_linkedin_url, :string, default: "https://www.linkedin.com/company/openfoodnetwork/"
|
||||
preference :footer_googleplus_url, :string, default: ""
|
||||
preference :footer_pinterest_url, :string, default: ""
|
||||
preference :footer_email, :string, default: "hello@openfoodnetwork.org"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class DistributorShippingMethod < ActiveRecord::Base
|
||||
self.table_name = "distributors_shipping_methods"
|
||||
belongs_to :shipping_method, class_name: Spree::ShippingMethod
|
||||
belongs_to :shipping_method, class_name: Spree::ShippingMethod, touch: true
|
||||
belongs_to :distributor, class_name: Enterprise, touch: true
|
||||
end
|
||||
|
||||
@@ -16,7 +16,8 @@ class OrderCycle < ActiveRecord::Base
|
||||
has_many :suppliers, source: :sender, through: :cached_incoming_exchanges, uniq: true
|
||||
has_many :distributors, source: :receiver, through: :cached_outgoing_exchanges, uniq: true
|
||||
|
||||
has_and_belongs_to_many :schedules, join_table: 'order_cycle_schedules'
|
||||
has_many :schedules, through: :order_cycle_schedules
|
||||
has_many :order_cycle_schedules
|
||||
has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } }
|
||||
|
||||
attr_accessor :incoming_exchanges, :outgoing_exchanges
|
||||
@@ -240,7 +241,8 @@ class OrderCycle < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def exchanges_supplying(order)
|
||||
exchanges.supplying_to(order.distributor).with_any_variant(order.variants.map(&:id))
|
||||
variant_ids_relation = Spree::LineItem.in_orders(order).select(:variant_id)
|
||||
exchanges.supplying_to(order.distributor).with_any_variant(variant_ids_relation)
|
||||
end
|
||||
|
||||
def coordinated_by?(user)
|
||||
|
||||
6
app/models/order_cycle_schedule.rb
Normal file
6
app/models/order_cycle_schedule.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class OrderCycleSchedule < ActiveRecord::Base
|
||||
belongs_to :schedule
|
||||
belongs_to :order_cycle
|
||||
end
|
||||
@@ -4,18 +4,6 @@ class ProducerProperty < ActiveRecord::Base
|
||||
|
||||
default_scope { order("#{table_name}.position") }
|
||||
|
||||
scope :ever_sold_by, ->(shop) {
|
||||
joins(producer: { supplied_products: { variants: { exchanges: :order_cycle } } }).
|
||||
merge(Exchange.outgoing).
|
||||
merge(Exchange.to_enterprise(shop)).
|
||||
select('DISTINCT producer_properties.*')
|
||||
}
|
||||
|
||||
scope :currently_sold_by, ->(shop) {
|
||||
ever_sold_by(shop).
|
||||
merge(OrderCycle.active)
|
||||
}
|
||||
|
||||
def property_name
|
||||
property.name if property
|
||||
end
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
class Schedule < ActiveRecord::Base
|
||||
has_and_belongs_to_many :order_cycles, join_table: 'order_cycle_schedules'
|
||||
has_paper_trail meta: { custom_data: proc { |schedule| schedule.order_cycle_ids.to_s } }
|
||||
|
||||
has_many :order_cycles, through: :order_cycle_schedules
|
||||
has_many :order_cycle_schedules, dependent: :destroy
|
||||
has_many :coordinators, uniq: true, through: :order_cycles
|
||||
|
||||
attr_accessible :name, :order_cycle_ids
|
||||
|
||||
@@ -14,7 +14,6 @@ Spree::AppConfiguration.class_eval do
|
||||
preference :privacy_policy_url, :string, default: nil
|
||||
preference :cookies_consent_banner_toggle, :boolean, default: false
|
||||
preference :cookies_policy_matomo_section, :boolean, default: false
|
||||
preference :cookies_policy_ga_section, :boolean, default: false
|
||||
|
||||
# Tax Preferences
|
||||
preference :products_require_tax_category, :boolean, default: false
|
||||
|
||||
@@ -17,9 +17,9 @@ module Spree
|
||||
order_amount = line_items_for(object).map { |x| x.price * x.quantity }.sum
|
||||
|
||||
if order_amount < min
|
||||
cost = preferred_normal_amount.to_i
|
||||
cost = preferred_normal_amount.to_f
|
||||
elsif order_amount >= min
|
||||
cost = preferred_discount_amount.to_i
|
||||
cost = preferred_discount_amount.to_f
|
||||
end
|
||||
|
||||
cost
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Spree::Classification.class_eval do
|
||||
belongs_to :product, class_name: "Spree::Product", touch: true
|
||||
belongs_to :taxon, class_name: "Spree::Taxon", touch: true
|
||||
|
||||
before_destroy :dont_destroy_if_primary_taxon
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
Spree::Money.class_eval do
|
||||
# return the currency symbol (on it's own) for the current default currency
|
||||
def self.currency_symbol
|
||||
Money.new(0, Spree::Config[:currency]).symbol
|
||||
end
|
||||
end
|
||||
@@ -210,7 +210,7 @@ Spree::Order.class_eval do
|
||||
end
|
||||
|
||||
def cap_quantity_at_stock!
|
||||
line_items.each(&:cap_quantity_at_stock!)
|
||||
line_items.includes(variant: :stock_items).all.each(&:cap_quantity_at_stock!)
|
||||
end
|
||||
|
||||
def set_distributor!(distributor)
|
||||
@@ -237,7 +237,10 @@ Spree::Order.class_eval do
|
||||
with_lock do
|
||||
EnterpriseFee.clear_all_adjustments_on_order self
|
||||
|
||||
line_items.each do |line_item|
|
||||
loaded_line_items =
|
||||
line_items.includes(variant: :product, order: [:distributor, :order_cycle]).all
|
||||
|
||||
loaded_line_items.each do |line_item|
|
||||
if provided_by_order_cycle? line_item
|
||||
OpenFoodNetwork::EnterpriseFeeCalculator.new.create_line_item_adjustments_for line_item
|
||||
end
|
||||
@@ -263,7 +266,11 @@ Spree::Order.class_eval do
|
||||
end
|
||||
|
||||
def line_item_variants
|
||||
line_items.map(&:variant)
|
||||
if line_items.loaded?
|
||||
line_items.map(&:variant)
|
||||
else
|
||||
line_items.includes(:variant).map(&:variant)
|
||||
end
|
||||
end
|
||||
|
||||
# Show already bought line items of this order cycle
|
||||
|
||||
@@ -12,7 +12,7 @@ Spree::Product.class_eval do
|
||||
has_many :option_types, through: :product_option_types, dependent: :destroy
|
||||
|
||||
belongs_to :supplier, class_name: 'Enterprise', touch: true
|
||||
belongs_to :primary_taxon, class_name: 'Spree::Taxon'
|
||||
belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true
|
||||
|
||||
delegate_belongs_to :master, :unit_value, :unit_description
|
||||
delegate :images_attributes=, :display_as=, to: :master
|
||||
|
||||
@@ -2,11 +2,16 @@ module Spree
|
||||
class Property < ActiveRecord::Base
|
||||
has_many :product_properties, dependent: :destroy
|
||||
has_many :products, through: :product_properties
|
||||
has_many :producer_properties
|
||||
|
||||
attr_accessible :name, :presentation
|
||||
|
||||
validates :name, :presentation, presence: true
|
||||
|
||||
scope :sorted, -> { order(:name) }
|
||||
|
||||
def property
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
module Spree
|
||||
Property.class_eval do
|
||||
has_many :producer_properties
|
||||
|
||||
scope :applied_by, ->(enterprise) {
|
||||
select('DISTINCT spree_properties.*').
|
||||
joins(:product_properties).
|
||||
where('spree_product_properties.product_id IN (?)', enterprise.supplied_product_ids)
|
||||
}
|
||||
|
||||
scope :ever_sold_by, ->(shop) {
|
||||
joins(products: { variants: { exchanges: :order_cycle } }).
|
||||
merge(Exchange.outgoing).
|
||||
merge(Exchange.to_enterprise(shop)).
|
||||
select('DISTINCT spree_properties.*')
|
||||
}
|
||||
|
||||
scope :currently_sold_by, ->(shop) {
|
||||
ever_sold_by(shop).
|
||||
merge(OrderCycle.active)
|
||||
}
|
||||
|
||||
def property
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,6 +11,10 @@ module Spree
|
||||
end
|
||||
|
||||
def total_on_hand
|
||||
# Associated stock_items no longer exist if the variant has been soft-deleted. A variant
|
||||
# may still be in an active cart after it's deleted, so this will mark it as out of stock.
|
||||
return 0 if @variant.deleted?
|
||||
|
||||
stock_items.sum(&:count_on_hand)
|
||||
end
|
||||
|
||||
|
||||
@@ -125,7 +125,12 @@ module Spree
|
||||
end
|
||||
|
||||
def default_card
|
||||
credit_cards.where(is_default: true).first
|
||||
# Don't re-fetch associated cards from the DB if they're already eager-loaded
|
||||
if credit_cards.loaded?
|
||||
credit_cards.to_a.find(&:is_default)
|
||||
else
|
||||
credit_cards.where(is_default: true).first
|
||||
end
|
||||
end
|
||||
|
||||
# Checks whether the specified user is a superadmin, with full control of the
|
||||
|
||||
@@ -16,7 +16,8 @@ Spree::Variant.class_eval do
|
||||
has_many :variant_overrides
|
||||
has_many :inventory_items
|
||||
|
||||
attr_accessible :unit_value, :unit_description, :images_attributes, :display_as, :display_name, :import_date
|
||||
attr_accessible :unit_value, :unit_description, :images_attributes,
|
||||
:display_as, :display_name, :import_date
|
||||
accepts_nested_attributes_for :images
|
||||
|
||||
validates :unit_value, presence: true, if: ->(variant) {
|
||||
@@ -53,13 +54,23 @@ Spree::Variant.class_eval do
|
||||
}
|
||||
|
||||
scope :visible_for, lambda { |enterprise|
|
||||
joins(:inventory_items).where('inventory_items.enterprise_id = (?) AND inventory_items.visible = (?)', enterprise, true)
|
||||
joins(:inventory_items).
|
||||
where(
|
||||
'inventory_items.enterprise_id = (?) AND inventory_items.visible = (?)',
|
||||
enterprise,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
scope :not_hidden_for, lambda { |enterprise|
|
||||
return where("1=0") if enterprise.blank?
|
||||
|
||||
joins("LEFT OUTER JOIN (SELECT * from inventory_items WHERE enterprise_id = #{sanitize enterprise.andand.id}) AS o_inventory_items ON o_inventory_items.variant_id = spree_variants.id")
|
||||
joins("
|
||||
LEFT OUTER JOIN (SELECT *
|
||||
FROM inventory_items
|
||||
WHERE enterprise_id = #{sanitize enterprise.andand.id})
|
||||
AS o_inventory_items
|
||||
ON o_inventory_items.variant_id = spree_variants.id")
|
||||
.where("o_inventory_items.id IS NULL OR o_inventory_items.visible = (?)", true)
|
||||
}
|
||||
|
||||
@@ -68,7 +79,8 @@ Spree::Variant.class_eval do
|
||||
scope :stockable_by, lambda { |enterprise|
|
||||
return where("1=0") if enterprise.blank?
|
||||
|
||||
joins(:product).where(spree_products: { id: Spree::Product.stockable_by(enterprise).pluck(:id) })
|
||||
joins(:product).
|
||||
where(spree_products: { id: Spree::Product.stockable_by(enterprise).pluck(:id) })
|
||||
}
|
||||
|
||||
# Define sope as class method to allow chaining with other scopes filtering id.
|
||||
@@ -85,7 +97,19 @@ Spree::Variant.class_eval do
|
||||
]
|
||||
end
|
||||
|
||||
# We override in_stock? to avoid depending on the non-overridable method Spree::Stock::Quantifier.can_supply?
|
||||
def self.active(currency = nil)
|
||||
# "where(id:" is necessary so that the returned relation has no includes
|
||||
# The relation without includes will not be readonly and allow updates on it
|
||||
where("spree_variants.id in (?)", joins(:prices).
|
||||
where(deleted_at: nil).
|
||||
where('spree_prices.currency' =>
|
||||
currency || Spree::Config[:currency]).
|
||||
where('spree_prices.amount IS NOT NULL').
|
||||
select("spree_variants.id"))
|
||||
end
|
||||
|
||||
# We override in_stock? to avoid depending
|
||||
# on the non-overridable method Spree::Stock::Quantifier.can_supply?
|
||||
# VariantStock implements can_supply? itself which depends on overridable methods
|
||||
def in_stock?(quantity = 1)
|
||||
can_supply?(quantity)
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
class Api::Admin::BasicEnterpriseSerializer < ActiveModel::Serializer
|
||||
attributes :name, :id, :is_primary_producer, :is_distributor, :sells, :category,
|
||||
:payment_method_ids, :shipping_method_ids, :producer_profile_only, :permalink
|
||||
|
||||
def payment_method_ids
|
||||
object.payment_methods.map(&:id)
|
||||
end
|
||||
|
||||
def shipping_method_ids
|
||||
object.shipping_methods.map(&:id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
|
||||
has_one :bill_address, serializer: Api::AddressSerializer
|
||||
|
||||
def tag_list
|
||||
object.tag_list.join(",")
|
||||
customer_tag_list.join(",")
|
||||
end
|
||||
|
||||
def name
|
||||
@@ -14,7 +14,7 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def tags
|
||||
object.tag_list.map do |tag|
|
||||
customer_tag_list.map do |tag|
|
||||
tag_rule_map = options[:tag_rule_mapping].andand[tag]
|
||||
tag_rule_map || { text: tag, rules: nil }
|
||||
end
|
||||
@@ -25,4 +25,12 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
|
||||
|
||||
object.user.default_card.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def customer_tag_list
|
||||
return object.tag_list unless options[:customer_tags]
|
||||
|
||||
options[:customer_tags].andand[object.id] || []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ class Api::Admin::OrderSerializer < ActiveModel::Serializer
|
||||
:edit_path, :state, :payment_state, :shipment_state,
|
||||
:payments_path, :ready_to_ship, :ready_to_capture, :created_at,
|
||||
:distributor_name, :special_instructions,
|
||||
:item_total, :adjustment_total, :payment_total, :total
|
||||
:item_total, :adjustment_total, :payment_total, :total, :display_outstanding_balance
|
||||
|
||||
has_one :distributor, serializer: Api::Admin::IdSerializer
|
||||
has_one :order_cycle, serializer: Api::Admin::IdSerializer
|
||||
@@ -16,6 +16,12 @@ class Api::Admin::OrderSerializer < ActiveModel::Serializer
|
||||
object.distributor.andand.name
|
||||
end
|
||||
|
||||
def display_outstanding_balance
|
||||
return "" if object.outstanding_balance.zero?
|
||||
|
||||
object.display_outstanding_balance.to_s
|
||||
end
|
||||
|
||||
def edit_path
|
||||
return '' unless object.id
|
||||
|
||||
|
||||
@@ -62,10 +62,14 @@ module Api
|
||||
end
|
||||
|
||||
def supplied_taxons
|
||||
return [] unless enterprise.is_primary_producer
|
||||
|
||||
ids_to_objs data.supplied_taxons[enterprise.id]
|
||||
end
|
||||
|
||||
def supplied_properties
|
||||
return [] unless enterprise.is_primary_producer
|
||||
|
||||
(product_properties + producer_properties).uniq do |property_object|
|
||||
property_object.property.presentation
|
||||
end
|
||||
@@ -113,7 +117,7 @@ module Api
|
||||
end
|
||||
|
||||
def active
|
||||
data.active_distributor_ids.andand.include? enterprise.id
|
||||
@active ||= data.active_distributor_ids.andand.include? enterprise.id
|
||||
end
|
||||
|
||||
# Map svg icons.
|
||||
|
||||
@@ -18,7 +18,8 @@ module Api
|
||||
end
|
||||
|
||||
def active
|
||||
enterprise.ready_for_checkout? && OrderCycle.active.with_distributor(enterprise).exists?
|
||||
@active ||=
|
||||
enterprise.ready_for_checkout? && OrderCycle.active.with_distributor(enterprise).exists?
|
||||
end
|
||||
|
||||
def pickup
|
||||
@@ -73,12 +74,16 @@ module Api
|
||||
end
|
||||
|
||||
def supplied_taxons
|
||||
return [] unless enterprise.is_primary_producer
|
||||
|
||||
ActiveModel::ArraySerializer.new(
|
||||
enterprise.supplied_taxons, each_serializer: Api::TaxonSerializer
|
||||
)
|
||||
end
|
||||
|
||||
def supplied_properties
|
||||
return [] unless enterprise.is_primary_producer
|
||||
|
||||
(product_properties + producer_properties).uniq do |property_object|
|
||||
property_object.property.presentation
|
||||
end
|
||||
@@ -118,7 +123,7 @@ module Api
|
||||
private
|
||||
|
||||
def product_properties
|
||||
enterprise.supplied_products.flat_map(&:properties)
|
||||
enterprise.supplied_products.includes(:properties).flat_map(&:properties)
|
||||
end
|
||||
|
||||
def producer_properties
|
||||
|
||||
62
app/services/cache_service.rb
Normal file
62
app/services/cache_service.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CacheService
|
||||
HOME_STATS_EXPIRY = 1.day.freeze
|
||||
FILTERS_EXPIRY = 30.seconds.freeze
|
||||
SHOPS_EXPIRY = 15.seconds.freeze
|
||||
|
||||
def self.cache(cache_key, options = {})
|
||||
Rails.cache.fetch cache_key.to_s, options do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
# Yields a cached query, expired by the most recently updated record for a given class.
|
||||
# E.g: if *any* Spree::Taxon record is updated, all keys based on Spree::Taxon will auto-expire.
|
||||
def self.cached_data_by_class(cache_key, cached_class)
|
||||
Rails.cache.fetch "#{cache_key}-#{cached_class}-#{latest_timestamp_by_class(cached_class)}" do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the :updated_at value of the most recently updated record for a given class, and returns
|
||||
# it as a timestamp, eg: `1583836069`.
|
||||
def self.latest_timestamp_by_class(cached_class)
|
||||
cached_class.maximum(:updated_at).to_i
|
||||
end
|
||||
|
||||
def self.home_stats(statistic)
|
||||
Rails.cache.fetch("home_stats_count_#{statistic}",
|
||||
expires_in: HOME_STATS_EXPIRY,
|
||||
race_condition_ttl: 10) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
module FragmentCaching
|
||||
# Rails' caching in views is called "Fragment Caching" and uses some slightly different logic.
|
||||
# Note: keys supplied here are actually prepended with "views/" under the hood.
|
||||
|
||||
def self.ams_all_taxons_key
|
||||
"inject-all-taxons-#{CacheService.latest_timestamp_by_class(Spree::Taxon)}"
|
||||
end
|
||||
|
||||
def self.ams_all_properties_key
|
||||
"inject-all-properties-#{CacheService.latest_timestamp_by_class(Spree::Property)}"
|
||||
end
|
||||
|
||||
def self.ams_shops
|
||||
[
|
||||
"shops/index/inject_enterprises",
|
||||
{ expires_in: SHOPS_EXPIRY }
|
||||
]
|
||||
end
|
||||
|
||||
def self.ams_shop(enterprise)
|
||||
[
|
||||
"enterprises/shop/inject_enterprise_shopfront-#{enterprise.id}",
|
||||
{ expires_in: SHOPS_EXPIRY }
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -15,28 +15,53 @@ class CartService
|
||||
@distributor, @order_cycle = distributor_and_order_cycle
|
||||
|
||||
@order.with_lock do
|
||||
variants = read_variants from_hash
|
||||
attempt_cart_add_variants variants
|
||||
overwrite_variants variants unless !overwrite
|
||||
variants_data = read_variants from_hash
|
||||
attempt_cart_add_variants variants_data
|
||||
overwrite_variants variants_data if overwrite
|
||||
end
|
||||
valid?
|
||||
end
|
||||
|
||||
def attempt_cart_add_variants(variants)
|
||||
variants.each do |v|
|
||||
if varies_from_cart(v)
|
||||
attempt_cart_add(v[:variant_id], v[:quantity], v[:max_quantity])
|
||||
private
|
||||
|
||||
def attempt_cart_add_variants(variants_data)
|
||||
loaded_variants = indexed_variants(variants_data)
|
||||
|
||||
variants_data.each do |variant_data|
|
||||
loaded_variant = loaded_variants[variant_data[:variant_id].to_i]
|
||||
|
||||
if loaded_variant.deleted?
|
||||
remove_deleted_variant(loaded_variant)
|
||||
next
|
||||
end
|
||||
|
||||
next unless varies_from_cart(variant_data, loaded_variant)
|
||||
|
||||
attempt_cart_add(loaded_variant, variant_data[:quantity], variant_data[:max_quantity])
|
||||
end
|
||||
end
|
||||
|
||||
def attempt_cart_add(variant_id, quantity, max_quantity = nil)
|
||||
def indexed_variants(variants_data)
|
||||
@indexed_variants ||= begin
|
||||
variant_ids_in_data = variants_data.map{ |v| v[:variant_id] }
|
||||
|
||||
Spree::Variant.with_deleted.where(id: variant_ids_in_data).
|
||||
includes(:default_price, :stock_items, :product).
|
||||
index_by(&:id)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_deleted_variant(variant)
|
||||
line_item_for_variant(variant).andand.destroy
|
||||
end
|
||||
|
||||
def attempt_cart_add(variant, quantity, max_quantity = nil)
|
||||
quantity = quantity.to_i
|
||||
max_quantity = max_quantity.to_i if max_quantity
|
||||
variant = Spree::Variant.find(variant_id)
|
||||
OpenFoodNetwork::ScopeVariantToHub.new(@distributor).scope(variant)
|
||||
return unless quantity > 0
|
||||
|
||||
return unless quantity > 0 && valid_variant?(variant)
|
||||
scoper.scope(variant)
|
||||
return unless valid_variant?(variant)
|
||||
|
||||
cart_add(variant, quantity, max_quantity)
|
||||
end
|
||||
@@ -66,25 +91,12 @@ class CartService
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def scoper
|
||||
@scoper ||= OpenFoodNetwork::ScopeVariantToHub.new(@distributor)
|
||||
end
|
||||
|
||||
def read_variants(data)
|
||||
@variants_h = read_products_hash(data) +
|
||||
read_variants_hash(data)
|
||||
end
|
||||
|
||||
def read_products_hash(data)
|
||||
# This is most probably dead code, this bugsnag notification will confirm it
|
||||
notify_bugsnag(data) if data[:products].present?
|
||||
|
||||
(data[:products] || []).map do |_product_id, variant_id|
|
||||
{ variant_id: variant_id, quantity: data[:quantity] }
|
||||
end
|
||||
end
|
||||
|
||||
def notify_bugsnag(data)
|
||||
Bugsnag.notify(RuntimeError.new("CartService.populate called with products hash"),
|
||||
data: data.as_json)
|
||||
@variants_h = read_variants_hash(data)
|
||||
end
|
||||
|
||||
def read_variants_hash(data)
|
||||
@@ -110,8 +122,9 @@ class CartService
|
||||
[@order.distributor, @order.order_cycle]
|
||||
end
|
||||
|
||||
def varies_from_cart(variant_data)
|
||||
li = line_item_for_variant_id variant_data[:variant_id]
|
||||
# Returns true if the saved cart differs from what's in the posted data, otherwise false
|
||||
def varies_from_cart(variant_data, loaded_variant)
|
||||
li = line_item_for_variant loaded_variant
|
||||
|
||||
li_added = li.nil? && (variant_data[:quantity].to_i > 0 || variant_data[:max_quantity].to_i > 0)
|
||||
li_quantity_changed = li.present? && li.quantity.to_i != variant_data[:quantity].to_i
|
||||
@@ -143,8 +156,8 @@ class CartService
|
||||
false
|
||||
end
|
||||
|
||||
def line_item_for_variant_id(variant_id)
|
||||
order.find_line_item_by_variant Spree::Variant.find(variant_id)
|
||||
def line_item_for_variant(variant)
|
||||
order.find_line_item_by_variant variant
|
||||
end
|
||||
|
||||
def variant_ids_in_cart
|
||||
|
||||
@@ -12,11 +12,8 @@ module Checkout
|
||||
def path
|
||||
return unless stripe_payment_method?
|
||||
|
||||
payment = @order.pending_payments.last
|
||||
return unless payment&.checkout?
|
||||
|
||||
payment.authorize!
|
||||
raise unless payment.pending?
|
||||
payment = OrderManagement::Subscriptions::StripeScaPaymentAuthorize.new(@order).call!
|
||||
raise if @order.errors.any?
|
||||
|
||||
field_with_url(payment) if url?(field_with_url(payment))
|
||||
end
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module OrderPaymentFinder
|
||||
def self.last_payment_method(order)
|
||||
# `max_by` avoids additional database queries when payments are loaded
|
||||
# already. There is usually only one payment and this shouldn't cause
|
||||
# any overhead compared to `order(:created_at).last`. Using `last`
|
||||
# without order is not deterministic.
|
||||
#
|
||||
# We are not using `updated_at` because all payments are touched when the
|
||||
# order is updated and then all payments have the same `updated_at` value.
|
||||
order.payments.max_by(&:created_at)&.payment_method
|
||||
class OrderPaymentFinder
|
||||
def initialize(order)
|
||||
@order = order
|
||||
end
|
||||
|
||||
def last_payment
|
||||
last(@order.payments)
|
||||
end
|
||||
|
||||
def last_pending_payment
|
||||
last(@order.pending_payments)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# `max_by` avoids additional database queries when payments are loaded
|
||||
# already. There is usually only one payment and this shouldn't cause
|
||||
# any overhead compared to `order(:created_at).last`. Using `last`
|
||||
# without order is not deterministic.
|
||||
#
|
||||
# We are not using `updated_at` because all payments are touched when the
|
||||
# order is updated and then all payments have the same `updated_at` value.
|
||||
def last(payments)
|
||||
payments.max_by(&:created_at)
|
||||
end
|
||||
end
|
||||
|
||||
36
app/services/variant_overrides_indexed.rb
Normal file
36
app/services/variant_overrides_indexed.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Produces mappings of variant overrides by distributor id and variant id
|
||||
# The primary use case for data structured in this way is for injection into
|
||||
# the initializer of the OpenFoodNetwork::ScopeVariantToHub class
|
||||
|
||||
class VariantOverridesIndexed
|
||||
def initialize(variant_ids, distributor_ids)
|
||||
@variant_ids = variant_ids
|
||||
@distributor_ids = distributor_ids
|
||||
end
|
||||
|
||||
def indexed
|
||||
scoped_variant_overrides.each_with_object(hash_of_hashes) do |variant_override, indexed|
|
||||
indexed[variant_override.hub_id][variant_override.variant] = variant_override
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :variant_ids, :distributor_ids
|
||||
|
||||
def scoped_variant_overrides
|
||||
VariantOverride
|
||||
.joins(:variant)
|
||||
.preload(:variant)
|
||||
.where(
|
||||
hub_id: distributor_ids,
|
||||
variant_id: variant_ids,
|
||||
)
|
||||
end
|
||||
|
||||
def hash_of_hashes
|
||||
Hash.new { |hash, key| hash[key] = {} }
|
||||
end
|
||||
end
|
||||
@@ -5,10 +5,10 @@ require 'open_food_network/scope_variant_to_hub'
|
||||
|
||||
class VariantsStockLevels
|
||||
def call(order, requested_variant_ids)
|
||||
variant_stock_levels = variant_stock_levels(order.line_items)
|
||||
variant_stock_levels = variant_stock_levels(order.line_items.includes(variant: :stock_items))
|
||||
|
||||
order_variant_ids = variant_stock_levels.keys
|
||||
missing_variants = Spree::Variant.includes(:stock_items).
|
||||
missing_variants = Spree::Variant.with_deleted.includes(:stock_items).
|
||||
where(id: (requested_variant_ids - order_variant_ids))
|
||||
|
||||
missing_variants.each do |missing_variant|
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
%td
|
||||
%ul
|
||||
%li{"ng-repeat" => "permission in enterprise_relationship.permissions"}
|
||||
to {{ EnterpriseRelationships.permission_presentation(permission.name) }}
|
||||
= t 'admin_enterprise_relationships_to'
|
||||
{{ EnterpriseRelationships.permission_presentation(permission.name) }}
|
||||
%td.actions
|
||||
%a.delete-enterprise-relationship.icon-trash.no-text{'ng-click' => 'delete(enterprise_relationship)'}
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
%div{"ng-repeat" => "permission in EnterpriseRelationships.all_permissions"}
|
||||
%label
|
||||
%input{type: "checkbox", "ng-model" => "permissions[permission]"}
|
||||
to {{ EnterpriseRelationships.permission_presentation(permission) }}
|
||||
= t 'admin_enterprise_relationships_to'
|
||||
{{ EnterpriseRelationships.permission_presentation(permission) }}
|
||||
%td.actions
|
||||
%input{type: "button", value: t(:admin_enterprise_relationships_button_create), "ng-click" => "create()"}
|
||||
.errors {{ EnterpriseRelationships.create_errors }}
|
||||
|
||||
@@ -31,5 +31,4 @@
|
||||
|
||||
//= f.submit "Purchase", class: "button", "ofn-focus" => "accordion['payment']"
|
||||
%a.button.secondary{href: main_app.cart_url}
|
||||
%i.ofn-i_008-caret-left
|
||||
= t :checkout_back_to_cart
|
||||
|
||||
24
app/views/enterprises/_change_order_cycle.html.haml
Normal file
24
app/views/enterprises/_change_order_cycle.html.haml
Normal file
@@ -0,0 +1,24 @@
|
||||
%div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true}
|
||||
%closing
|
||||
%div{"ng-if" => "OrderCycle.selected()"}
|
||||
= t :enterprises_next_closing
|
||||
%strong {{ OrderCycle.orders_close_at() | date_in_words }}
|
||||
%div{"ng-if" => "!OrderCycle.selected()"}
|
||||
= t :enterprises_choose
|
||||
|
||||
.order-cycle-select
|
||||
.select-label
|
||||
%span= t :enterprises_ready_for
|
||||
|
||||
- if oc_select_options.count == 1
|
||||
%p
|
||||
= oc_select_options.first[:time]
|
||||
|
||||
- else
|
||||
%select.select2.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id",
|
||||
"ofn-change-order-cycle" => true,
|
||||
"disabled" => require_customer?,
|
||||
"ng-options" => "oc.id as oc.time for oc in #{oc_select_options.to_json}"}
|
||||
|
||||
- if oc_select_options.count > 1
|
||||
%option{value: "", disabled: "", selected: ""}= t :shopping_oc_select
|
||||
@@ -6,7 +6,8 @@
|
||||
= current_distributor.logo.url
|
||||
|
||||
- content_for :injection_data do
|
||||
= inject_enterprise_shopfront(@enterprise)
|
||||
- cache(*CacheService::FragmentCaching.ams_shop(@enterprise)) do
|
||||
= inject_enterprise_shopfront(@enterprise)
|
||||
|
||||
%shop.darkswarm
|
||||
- if @shopfront_layout == 'embedded'
|
||||
@@ -17,26 +18,7 @@
|
||||
%a.close{ ng: { click: "alert.close()" } } ×
|
||||
|
||||
- content_for :order_cycle_form do
|
||||
|
||||
%div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true}
|
||||
%closing
|
||||
%div{"ng-if" => "OrderCycle.selected()"}
|
||||
= t :enterprises_next_closing
|
||||
%strong {{ OrderCycle.orders_close_at() | date_in_words }}
|
||||
%div{"ng-if" => "!OrderCycle.selected()"}
|
||||
= t :enterprises_choose
|
||||
|
||||
.order-cycle-select
|
||||
.select-label
|
||||
%span= t :enterprises_ready_for
|
||||
|
||||
%select.select2.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id",
|
||||
"ofn-change-order-cycle" => true,
|
||||
"disabled" => require_customer?,
|
||||
"ng-options" => "oc.id as oc.time for oc in #{oc_select_options.to_json}"}
|
||||
|
||||
- if oc_select_options.count > 1
|
||||
%option{value: "", disabled: "", selected: ""}= t :shopping_oc_select
|
||||
= render partial: "change_order_cycle"
|
||||
|
||||
- content_for :ordercycle_sidebar do
|
||||
.show-for-large-up.large-4.columns
|
||||
|
||||
@@ -48,12 +48,13 @@
|
||||
= inject_current_hub
|
||||
= inject_current_user
|
||||
= inject_rails_flash
|
||||
= inject_taxons
|
||||
= inject_properties
|
||||
- cache CacheService::FragmentCaching.ams_all_taxons_key do
|
||||
= inject_taxons
|
||||
- cache CacheService::FragmentCaching.ams_all_properties_key do
|
||||
= inject_properties
|
||||
= inject_current_order
|
||||
= inject_currency_config
|
||||
= yield :injection_data
|
||||
|
||||
= render "layouts/bugherd_script"
|
||||
= render "layouts/matomo_tag"
|
||||
= render 'spree/shared/google_analytics'
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
%h2
|
||||
%i.ofn-i_040-hub>
|
||||
= t :modal_hubs
|
||||
%h5
|
||||
= t :modal_hubs_abstract
|
||||
%p
|
||||
= t :modal_hubs_content1
|
||||
%p
|
||||
= t :modal_hubs_content2
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"}
|
||||
%i.ofn-i_009-close
|
||||
@@ -1,9 +0,0 @@
|
||||
%h2
|
||||
%i.ofn-i_035-groups
|
||||
= t :modal_groups
|
||||
%p
|
||||
= t :modal_groups
|
||||
%p
|
||||
= t :modal_groups
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"}
|
||||
%i.ofn-i_009-close
|
||||
@@ -1,17 +0,0 @@
|
||||
%h2
|
||||
= t :modal_how
|
||||
%h5
|
||||
= t :modal_how_shop
|
||||
%p
|
||||
= t :modal_how_shop_explained
|
||||
%h5
|
||||
= t :modal_how_pickup
|
||||
%p
|
||||
= t :modal_how_pickup_explained
|
||||
%h5
|
||||
= t :modal_how
|
||||
%p
|
||||
= t :modal_how
|
||||
%a.button.neutral-btn.dark{:href => "http://www.openfoodnetwork.org" , :target => "_blank" } Open Food Network
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"}
|
||||
%i.ofn-i_009-close
|
||||
@@ -1,7 +0,0 @@
|
||||
%h2
|
||||
%i.ofn-i_036-producers
|
||||
= t :modal_producers
|
||||
%p
|
||||
= t :modal_producers_explained
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"}
|
||||
%i.ofn-i_009-close
|
||||
@@ -18,7 +18,7 @@
|
||||
%a.soc-btn.li{href: ContentConfig.footer_linkedin_url}
|
||||
LinkedIn
|
||||
-if ContentConfig.footer_instagram_url.present?
|
||||
%a.soc-btn.tw{href: ContentConfig.footer_instagram_url}
|
||||
%a.soc-btn.ig{href: ContentConfig.footer_instagram_url}
|
||||
Instagram
|
||||
%table.column{:align => "left"}
|
||||
%tr
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
- if require_customer?
|
||||
.row.footer-pad
|
||||
.small-12.columns
|
||||
.shopfront_hidden_message
|
||||
= t '.require_customer_login'
|
||||
- if spree_current_user.nil?
|
||||
= t '.require_login_html',
|
||||
{login: ('<a auth="login">' + t('.login') + '</a>').html_safe,
|
||||
signup: ('<a auth="signup">' + t('.signup') + '</a>').html_safe,
|
||||
contact: link_to(t('.contact'), '#contact'),
|
||||
enterprise: current_distributor.name}
|
||||
- else
|
||||
= t '.require_customer_html',
|
||||
{contact: link_to(t('.contact'), '#contact'),
|
||||
enterprise: current_distributor.name}
|
||||
- elsif current_distributor.preferred_shopfront_message.present?
|
||||
.row
|
||||
.small-12.columns
|
||||
.shopfront-message
|
||||
= current_distributor.preferred_shopfront_message.html_safe
|
||||
21
app/views/shop/messages/_closed_shop.html.haml
Normal file
21
app/views/shop/messages/_closed_shop.html.haml
Normal file
@@ -0,0 +1,21 @@
|
||||
.row.closed-shop-header
|
||||
.small-12.columns
|
||||
.content{ "darker-background" => true }
|
||||
%h4
|
||||
.warning-sign
|
||||
.rectangle
|
||||
%strong !
|
||||
.message
|
||||
= t :shopping_oc_closed
|
||||
%p
|
||||
= render partial: "shopping_shared/next_order_cycle"
|
||||
= render partial: "shopping_shared/last_order_cycle"
|
||||
|
||||
.row
|
||||
.small-12.columns
|
||||
.content
|
||||
.shopfront_closed_message
|
||||
- if shopfront_closed_message?
|
||||
= current_distributor.preferred_shopfront_closed_message.html_safe
|
||||
- else
|
||||
= t :shopping_oc_closed_description
|
||||
19
app/views/shop/messages/_customer_required.html.haml
Normal file
19
app/views/shop/messages/_customer_required.html.haml
Normal file
@@ -0,0 +1,19 @@
|
||||
.content{ "darker-background" => true }
|
||||
.row.footer-pad
|
||||
.small-12.columns
|
||||
%strong
|
||||
= t '.require_customer_login'
|
||||
%p
|
||||
- if spree_current_user.nil?
|
||||
%p
|
||||
= t '.require_login_html',
|
||||
{login: ('<a auth="login">' + t('.login') + '</a>').html_safe,
|
||||
signup: ('<a auth="signup">' + t('.signup') + '</a>').html_safe}
|
||||
%p
|
||||
= t '.require_login_2_html',
|
||||
{contact: link_to(t('.contact'), '#contact'),
|
||||
enterprise: current_distributor.name}
|
||||
- else
|
||||
= t '.require_customer_html',
|
||||
{contact: link_to(t('.contact'), '#contact'),
|
||||
enterprise: current_distributor.name}
|
||||
5
app/views/shop/messages/_open_shop.html.haml
Normal file
5
app/views/shop/messages/_open_shop.html.haml
Normal file
@@ -0,0 +1,5 @@
|
||||
.content
|
||||
.row
|
||||
.small-12.columns
|
||||
.open-shop-message
|
||||
= current_distributor.preferred_shopfront_message.html_safe
|
||||
3
app/views/shop/messages/_select_oc.html.haml
Normal file
3
app/views/shop/messages/_select_oc.html.haml
Normal file
@@ -0,0 +1,3 @@
|
||||
.content.footer-pad{ "darker-background" => true, "ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id == null" }
|
||||
.select-oc-message
|
||||
= t '.select_oc_html'
|
||||
@@ -1,4 +1,2 @@
|
||||
- if most_recently_closed = OrderCycle.most_recently_closed_for(@distributor)
|
||||
(
|
||||
= t :shopping_oc_last_closed, distance_of_time: distance_of_time_in_words_to_now(most_recently_closed.orders_close_at)
|
||||
)
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
- if next_oc = OrderCycle.first_opening_for(@distributor)
|
||||
(
|
||||
= t :shopping_oc_next_open, distance_of_time: distance_of_time_in_words_to_now(next_oc.orders_open_at)
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- content_for :injection_data do
|
||||
= inject_current_order_cycle
|
||||
|
||||
- unless no_open_order_cycles?
|
||||
- unless no_open_order_cycles? || require_customer?
|
||||
%ordercycle{"ng-controller" => "OrderCycleCtrl", "ng-cloak" => true,
|
||||
"ng-class" => "{'requires-selection': !OrderCycle.selected()}"}
|
||||
%form.custom
|
||||
|
||||
@@ -2,5 +2,12 @@
|
||||
.order-cycle-bar.hide-for-large-up
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
|
||||
.content
|
||||
= render partial: 'shop/messages'
|
||||
- if require_customer?
|
||||
= render partial: "shop/messages/customer_required"
|
||||
|
||||
- else
|
||||
- if no_open_order_cycles?
|
||||
= render partial: "shop/messages/closed_shop"
|
||||
|
||||
- else
|
||||
= render partial: "shop/messages/open_shop"
|
||||
|
||||
@@ -2,23 +2,11 @@
|
||||
.order-cycle-bar.hide-for-large-up
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
|
||||
.row
|
||||
.small-12.columns
|
||||
- if no_open_order_cycles?
|
||||
.content
|
||||
%h4
|
||||
%i.ofn-i_012-warning
|
||||
= t :shopping_oc_closed
|
||||
%small
|
||||
%em
|
||||
= render partial: "shopping_shared/next_order_cycle"
|
||||
= render partial: "shopping_shared/last_order_cycle"
|
||||
%p
|
||||
= t :shopping_oc_closed_description
|
||||
- if no_open_order_cycles?
|
||||
= render partial: "shop/messages/closed_shop"
|
||||
|
||||
- if shopfront_closed_message?
|
||||
.shopfront_closed_message
|
||||
= current_distributor.preferred_shopfront_closed_message.html_safe
|
||||
|
||||
- unless require_customer?
|
||||
- else
|
||||
.row
|
||||
.small-12.columns
|
||||
= render partial: "shop/messages/select_oc"
|
||||
= render partial: "shop/products/form"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
= t :shops_title
|
||||
|
||||
- content_for :injection_data do
|
||||
= inject_enterprises(@enterprises)
|
||||
- cache(*CacheService::FragmentCaching.ams_shops) do
|
||||
= inject_enterprises(@enterprises)
|
||||
|
||||
#panes
|
||||
#shops.pane
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user