mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-15 19:06:50 +00:00
Compare commits
493 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
917079931e | ||
|
|
46e54f48c9 | ||
|
|
059dceb304 | ||
|
|
f0abe650f6 | ||
|
|
282df9859e | ||
|
|
3474c60f4c | ||
|
|
503148b13b | ||
|
|
8442c7d334 | ||
|
|
b14cd08990 | ||
|
|
50ebfe412c | ||
|
|
e59ab6b2d9 | ||
|
|
417d39f684 | ||
|
|
35169f66dc | ||
|
|
64568f4aa4 | ||
|
|
734aebbaaa | ||
|
|
8a2be468fc | ||
|
|
feb429fee7 | ||
|
|
b75101f24f | ||
|
|
1e71db9315 | ||
|
|
82b742608d | ||
|
|
49aa9e0768 | ||
|
|
a85cfab506 | ||
|
|
e2e3aa9281 | ||
|
|
6bd0f2c088 | ||
|
|
ab2968ffd2 | ||
|
|
83bf19084b | ||
|
|
40a59c988b | ||
|
|
43d983cac2 | ||
|
|
ad3e772944 | ||
|
|
6a438a07fe | ||
|
|
ea238829a8 | ||
|
|
91fddeaa8b | ||
|
|
0de8a90b14 | ||
|
|
9fe128d494 | ||
|
|
193e17b625 | ||
|
|
6ad03e6d5c | ||
|
|
1f55ff4b72 | ||
|
|
be13d43e0c | ||
|
|
af7b663334 | ||
|
|
da24638079 | ||
|
|
8ab1cbe600 | ||
|
|
cad0245510 | ||
|
|
93edf4e3ad | ||
|
|
caa2764317 | ||
|
|
4f1e6382c9 | ||
|
|
54d33ca103 | ||
|
|
787205dcca | ||
|
|
fcb0996a76 | ||
|
|
76d874d5f9 | ||
|
|
81711e4c43 | ||
|
|
e64f60a166 | ||
|
|
24bc56781b | ||
|
|
6757c8df74 | ||
|
|
647a384561 | ||
|
|
ec828c335d | ||
|
|
6d03a8ddf3 | ||
|
|
05878fcbb8 | ||
|
|
fd8973862e | ||
|
|
40c77948b9 | ||
|
|
a95aa1b3e9 | ||
|
|
706eb737b1 | ||
|
|
c31758d347 | ||
|
|
6139ba3015 | ||
|
|
256d5ba61c | ||
|
|
cb42e7e119 | ||
|
|
566d310880 | ||
|
|
5cdce35ee8 | ||
|
|
ffe3f12a23 | ||
|
|
bd48a982fb | ||
|
|
5d732d80a6 | ||
|
|
254e11aa36 | ||
|
|
4223b36bc3 | ||
|
|
fcea437d7e | ||
|
|
8716b75d3d | ||
|
|
e055b8b16c | ||
|
|
f5875e4c0b | ||
|
|
56d23c172c | ||
|
|
19bb40d1d3 | ||
|
|
4169a956c9 | ||
|
|
d54dbdfe2d | ||
|
|
fed2ae9a93 | ||
|
|
f00b2f0397 | ||
|
|
c101c4e42f | ||
|
|
11ba33d7f4 | ||
|
|
8e663dac3f | ||
|
|
df0795acf1 | ||
|
|
7e1af9e04b | ||
|
|
4805adec42 | ||
|
|
7939bf8038 | ||
|
|
d4e0b2ab51 | ||
|
|
1014a50aff | ||
|
|
0afbdf157e | ||
|
|
5012c52438 | ||
|
|
615a81c55d | ||
|
|
99b31d05cb | ||
|
|
1a72b5b227 | ||
|
|
a1aea54405 | ||
|
|
e01354e863 | ||
|
|
ffe4603f2f | ||
|
|
ebc794194f | ||
|
|
287f65ec8e | ||
|
|
1288592d58 | ||
|
|
0836d844a6 | ||
|
|
96355a1ed4 | ||
|
|
ce44f19b4a | ||
|
|
6c214543ad | ||
|
|
8b1713d169 | ||
|
|
587ce5ad9d | ||
|
|
f51705cb57 | ||
|
|
55df9416cc | ||
|
|
0376c04ad5 | ||
|
|
2709479bf2 | ||
|
|
c5fc621aa4 | ||
|
|
bfd0e7f784 | ||
|
|
ec3c157f1e | ||
|
|
32aacbd2b0 | ||
|
|
655dc92246 | ||
|
|
fece8beef5 | ||
|
|
53e3621e04 | ||
|
|
1949839056 | ||
|
|
00a0006ff2 | ||
|
|
325f9aa6f3 | ||
|
|
95ec5c3c58 | ||
|
|
ef87cdb167 | ||
|
|
65d4596f3b | ||
|
|
a9e295bc11 | ||
|
|
ac8caf7710 | ||
|
|
8a1e61fd60 | ||
|
|
baf38b6b30 | ||
|
|
3507405dae | ||
|
|
c25fe6ae57 | ||
|
|
7bcf3206d8 | ||
|
|
e808c7fb2b | ||
|
|
df81e8ed35 | ||
|
|
e9d7a0b099 | ||
|
|
da7bbcf82f | ||
|
|
1742d2807f | ||
|
|
d3c5e2365a | ||
|
|
27e53f9dcc | ||
|
|
5d0f55b8c3 | ||
|
|
9d89b4726b | ||
|
|
9b37eacb8d | ||
|
|
bbe22bbfeb | ||
|
|
85d5e2ee70 | ||
|
|
9a4051f37b | ||
|
|
05ed4639b2 | ||
|
|
2bcf84d9a9 | ||
|
|
99acf752f4 | ||
|
|
5086f2d8b5 | ||
|
|
0b46c41ffd | ||
|
|
4fe3f60009 | ||
|
|
dfea0cd805 | ||
|
|
0f04b2fb10 | ||
|
|
146296d0b2 | ||
|
|
a6efad73a8 | ||
|
|
7f9bbd23e5 | ||
|
|
29d63b0f0f | ||
|
|
25e9fd22d8 | ||
|
|
5caeb160ef | ||
|
|
1310965975 | ||
|
|
0c43dd4222 | ||
|
|
bb427db66a | ||
|
|
1165b00600 | ||
|
|
2d45952611 | ||
|
|
17eb8d5cd8 | ||
|
|
c8ca993fa9 | ||
|
|
de20dd949b | ||
|
|
4c9507caa3 | ||
|
|
f1e6f8bb66 | ||
|
|
918d4401ff | ||
|
|
4925e2088d | ||
|
|
bba59c1ffd | ||
|
|
f3e7ba0462 | ||
|
|
23fc77351e | ||
|
|
3bd5ae2eec | ||
|
|
0123d6fb2e | ||
|
|
e0d7252fe3 | ||
|
|
24defac470 | ||
|
|
46696dfa17 | ||
|
|
1af811cf51 | ||
|
|
1850f298a6 | ||
|
|
ae3fa00429 | ||
|
|
80ade22bd6 | ||
|
|
4fb458afe0 | ||
|
|
c25750387f | ||
|
|
5b7fbc875a | ||
|
|
e1976c6cc2 | ||
|
|
91daec4806 | ||
|
|
bdc42deeb6 | ||
|
|
a66fec0b26 | ||
|
|
c52c2ebfe1 | ||
|
|
d8354298f5 | ||
|
|
2baf7c0250 | ||
|
|
0986971473 | ||
|
|
29aa3a8059 | ||
|
|
e09745179f | ||
|
|
49c6a22fde | ||
|
|
a37b0eb698 | ||
|
|
2e36c699f6 | ||
|
|
cb4e7d6fe3 | ||
|
|
94d560d341 | ||
|
|
eea227bc22 | ||
|
|
6d6f8735e3 | ||
|
|
aec9a960e2 | ||
|
|
f1713b11a6 | ||
|
|
c86f7f9d50 | ||
|
|
5e933af079 | ||
|
|
f7c47fecc4 | ||
|
|
7d1cb0c957 | ||
|
|
7f6780b5e9 | ||
|
|
dfcd9391d9 | ||
|
|
6dd4bb3e3b | ||
|
|
cda2408c69 | ||
|
|
eb22bff908 | ||
|
|
9281cd1a62 | ||
|
|
b99d985b75 | ||
|
|
ecfa47cd78 | ||
|
|
10898fdcfc | ||
|
|
718e6765e1 | ||
|
|
686fe8c028 | ||
|
|
cb0a30ef52 | ||
|
|
5c3acbbcaf | ||
|
|
31d49ee99e | ||
|
|
1e08f2713e | ||
|
|
f0fd3bb73c | ||
|
|
6d22652dfa | ||
|
|
2ab6bcb2e4 | ||
|
|
ca612282f9 | ||
|
|
823614c214 | ||
|
|
cc1fa7f563 | ||
|
|
8955972b05 | ||
|
|
5fe5804b56 | ||
|
|
d60d29b685 | ||
|
|
ac347b9c8e | ||
|
|
8a8a178683 | ||
|
|
5c136f8baa | ||
|
|
c372bf746a | ||
|
|
79a22aefc3 | ||
|
|
17fe492bf4 | ||
|
|
47faedc295 | ||
|
|
ce8a2b3251 | ||
|
|
ac2f59bdb2 | ||
|
|
8d327355f9 | ||
|
|
c8cb15df33 | ||
|
|
d602482d91 | ||
|
|
51f89b995a | ||
|
|
2b10862779 | ||
|
|
6594b30bf4 | ||
|
|
b131c352a9 | ||
|
|
e62b640372 | ||
|
|
838974973d | ||
|
|
2c41d065df | ||
|
|
045da3c0e5 | ||
|
|
cfc51f399f | ||
|
|
fb2575aaeb | ||
|
|
be5a228509 | ||
|
|
dfe56c1eed | ||
|
|
1ead9208ee | ||
|
|
1d86315108 | ||
|
|
f60a79437e | ||
|
|
f79691e4bf | ||
|
|
9549f4e506 | ||
|
|
448308710a | ||
|
|
f73745a803 | ||
|
|
3788b33eb0 | ||
|
|
c328ee8087 | ||
|
|
efbec02fb9 | ||
|
|
117f6d3300 | ||
|
|
56b58219da | ||
|
|
260211cf15 | ||
|
|
7219f72ac7 | ||
|
|
891f79666d | ||
|
|
312c240968 | ||
|
|
b6faa43879 | ||
|
|
84197aca19 | ||
|
|
9632f42a40 | ||
|
|
a52401107a | ||
|
|
3704b18952 | ||
|
|
feb7e173b1 | ||
|
|
3dc7c2bf56 | ||
|
|
2d707e8acb | ||
|
|
dd8f0aafab | ||
|
|
564ea0bd49 | ||
|
|
1c6d10d4e7 | ||
|
|
b5cf47d306 | ||
|
|
34aba72dea | ||
|
|
7b6b365c4f | ||
|
|
8244fa7685 | ||
|
|
3eae329cc4 | ||
|
|
848144d378 | ||
|
|
d614780059 | ||
|
|
2550f8fd80 | ||
|
|
7c712f0058 | ||
|
|
725e2bfa48 | ||
|
|
a2de846f2c | ||
|
|
9b879da616 | ||
|
|
0f7f1a5d5c | ||
|
|
8104d8e37b | ||
|
|
4c274e0a90 | ||
|
|
470986dc19 | ||
|
|
a8cdca89a1 | ||
|
|
1061bf50b4 | ||
|
|
12c017ab99 | ||
|
|
8db7352774 | ||
|
|
4396c39c83 | ||
|
|
d53bfe455d | ||
|
|
6538c7adca | ||
|
|
ca80177954 | ||
|
|
e901886915 | ||
|
|
add6d15fc4 | ||
|
|
0167357f8f | ||
|
|
61750c51b3 | ||
|
|
93660efdf7 | ||
|
|
07fb607c35 | ||
|
|
62efde4c98 | ||
|
|
8937c3395a | ||
|
|
4115b857f7 | ||
|
|
e0e78f2798 | ||
|
|
315f951f8f | ||
|
|
93a63c5eb5 | ||
|
|
03ee9529f1 | ||
|
|
5884edaa1b | ||
|
|
1ea3160a6a | ||
|
|
f161f51a0e | ||
|
|
b156f722f1 | ||
|
|
07fb7b5b5e | ||
|
|
6c4c0ebf6f | ||
|
|
ee88e2fdfa | ||
|
|
9f612270c7 | ||
|
|
2ab9ccf73d | ||
|
|
78abe36327 | ||
|
|
b891a03468 | ||
|
|
63807f198b | ||
|
|
25371ee9d0 | ||
|
|
6ee77fa406 | ||
|
|
83fa080f76 | ||
|
|
17793e7b12 | ||
|
|
45f4365385 | ||
|
|
bfb4997207 | ||
|
|
c273c6b155 | ||
|
|
40892580cd | ||
|
|
323602abbb | ||
|
|
fddfd0dbfb | ||
|
|
c1dc87ae21 | ||
|
|
f8c2dfb3f7 | ||
|
|
df209fdc2b | ||
|
|
1f904a3e2f | ||
|
|
80c4d9d03b | ||
|
|
f38e13b1a0 | ||
|
|
93922b484f | ||
|
|
82b630c0c4 | ||
|
|
6d55f8ef2e | ||
|
|
076200597d | ||
|
|
5b3e79e6c8 | ||
|
|
2e129eab8f | ||
|
|
30decf3f34 | ||
|
|
16709704fd | ||
|
|
656361c82d | ||
|
|
c82444efa9 | ||
|
|
507fa028c1 | ||
|
|
e1c3f0a31c | ||
|
|
e48cdeba20 | ||
|
|
71b6938961 | ||
|
|
586acad8f1 | ||
|
|
0c9223809b | ||
|
|
53e7b02471 | ||
|
|
a873fa692b | ||
|
|
04fb49bc25 | ||
|
|
b5e76e1dab | ||
|
|
2952ebb05c | ||
|
|
3d82309c5f | ||
|
|
6d1a6c6d0e | ||
|
|
b13a1e8843 | ||
|
|
71e4911b9e | ||
|
|
1b7a5fdb2c | ||
|
|
765655ae25 | ||
|
|
ef298e3b62 | ||
|
|
95ed806370 | ||
|
|
612ab097b7 | ||
|
|
054d967323 | ||
|
|
859f7efd02 | ||
|
|
d5cc60fd3a | ||
|
|
94faf4cf69 | ||
|
|
b86d8e1603 | ||
|
|
a87f10b2a6 | ||
|
|
80112709f3 | ||
|
|
50bd274715 | ||
|
|
d073a181e9 | ||
|
|
02b9dfb517 | ||
|
|
22f4ae115a | ||
|
|
8abea0afcf | ||
|
|
5cb59d941a | ||
|
|
7af36510c8 | ||
|
|
6290e7ad1c | ||
|
|
06b0b54685 | ||
|
|
d9d77d2b25 | ||
|
|
4113880401 | ||
|
|
d1dd563720 | ||
|
|
ced3408aaa | ||
|
|
4c141df474 | ||
|
|
540d487584 | ||
|
|
3d9bc2ef4b | ||
|
|
0d254a8ba4 | ||
|
|
3a75135029 | ||
|
|
1085da83a9 | ||
|
|
5cf8eb5efc | ||
|
|
a83daae873 | ||
|
|
d3e00cd4b7 | ||
|
|
7a741d92e7 | ||
|
|
02c0c6aa5e | ||
|
|
33b680c67c | ||
|
|
b1aafbf843 | ||
|
|
c80199e8b1 | ||
|
|
b1721d69a2 | ||
|
|
109432d282 | ||
|
|
5f01bb40d2 | ||
|
|
39cae4468a | ||
|
|
3a8c44d0c6 | ||
|
|
6dfef8104d | ||
|
|
9e25893401 | ||
|
|
8028610fe6 | ||
|
|
ba355fdaab | ||
|
|
c226b10827 | ||
|
|
80e817725e | ||
|
|
9f98dc253d | ||
|
|
5824516ce0 | ||
|
|
bcbc7c7930 | ||
|
|
03a9f6811c | ||
|
|
4e24af2f94 | ||
|
|
0a4c2a1903 | ||
|
|
0fa272a125 | ||
|
|
2c3778360b | ||
|
|
8b036e5108 | ||
|
|
c4b2748282 | ||
|
|
5af5eb7ecf | ||
|
|
289414a504 | ||
|
|
d88db1365d | ||
|
|
3af5330998 | ||
|
|
e09fa3e04a | ||
|
|
2937bdc1d2 | ||
|
|
c7894892f6 | ||
|
|
7303c40c92 | ||
|
|
50c7392c5e | ||
|
|
4871e0082e | ||
|
|
8897e99113 | ||
|
|
527136105f | ||
|
|
8a198705e3 | ||
|
|
b9bb4a4dcb | ||
|
|
1c580c42f4 | ||
|
|
5e4381cc63 | ||
|
|
9aecf9feb4 | ||
|
|
308c559810 | ||
|
|
500b9ed1c7 | ||
|
|
c747b2e60c | ||
|
|
9833ac67df | ||
|
|
5bb47823c6 | ||
|
|
909bc2792c | ||
|
|
04dd463f8e | ||
|
|
b117fd03da | ||
|
|
d799230440 | ||
|
|
92bd7a5d37 | ||
|
|
898ab08bab | ||
|
|
b1a3bff2ed | ||
|
|
4315a05eb8 | ||
|
|
0aea201d53 | ||
|
|
5a259f1b91 | ||
|
|
9f832e6743 | ||
|
|
95972c75c6 | ||
|
|
3f6e5e7d09 | ||
|
|
0001ffa970 | ||
|
|
74fb6c3143 | ||
|
|
9d12e55bd7 | ||
|
|
b3570991f4 | ||
|
|
c8e134cef5 | ||
|
|
67cac1f4d6 | ||
|
|
349862c72e | ||
|
|
86c87962f9 | ||
|
|
4b9141f66d | ||
|
|
d80e1efa7b | ||
|
|
54d068ee08 | ||
|
|
141a883e4d | ||
|
|
5b8f590520 | ||
|
|
0cff734b86 | ||
|
|
98a29785a7 | ||
|
|
d2737bd8b0 | ||
|
|
05fe7cd4b9 | ||
|
|
30528cab0f | ||
|
|
539ffb1f35 | ||
|
|
df2bad9b8f | ||
|
|
fa3b84b71f | ||
|
|
2ca4febf90 | ||
|
|
e2aca63fff | ||
|
|
e537bda9b7 |
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -216,9 +216,9 @@ jobs:
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
name: failed-admin-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
@@ -294,9 +294,9 @@ jobs:
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
name: failed-consumer-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
@@ -371,15 +371,6 @@ jobs:
|
||||
run: |
|
||||
bin/rake knapsack_pro:rspec
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
test_the_rest:
|
||||
runs-on: ubuntu-22.04
|
||||
services:
|
||||
|
||||
@@ -6,35 +6,12 @@
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines.
|
||||
Layout/EmptyLineBetweenDefs:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
Layout/EmptyLines:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented, indented_relative_to_receiver
|
||||
Layout/MultilineMethodCallIndentation:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented
|
||||
Layout/MultilineOperationIndentation:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 16
|
||||
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
||||
Lint/EmptyBlock:
|
||||
@@ -150,7 +127,7 @@ Metrics/BlockNesting:
|
||||
Exclude:
|
||||
- 'app/models/spree/payment/processing.rb'
|
||||
|
||||
# Offense count: 47
|
||||
# Offense count: 46
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ClassLength:
|
||||
Exclude:
|
||||
@@ -186,7 +163,6 @@ Metrics/ClassLength:
|
||||
- 'app/models/spree/variant.rb'
|
||||
- 'app/models/spree/zone.rb'
|
||||
- 'app/reflexes/admin/orders_reflex.rb'
|
||||
- 'app/reflexes/products_reflex.rb'
|
||||
- 'app/serializers/api/cached_enterprise_serializer.rb'
|
||||
- 'app/serializers/api/enterprise_shopfront_serializer.rb'
|
||||
- 'app/services/cart_service.rb'
|
||||
@@ -253,7 +229,7 @@ Metrics/MethodLength:
|
||||
- 'lib/spree/localized_number.rb'
|
||||
- 'lib/tasks/sample_data/product_factory.rb'
|
||||
|
||||
# Offense count: 48
|
||||
# Offense count: 49
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ModuleLength:
|
||||
Exclude:
|
||||
@@ -283,6 +259,7 @@ Metrics/ModuleLength:
|
||||
- 'spec/controllers/payment_gateways/stripe_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/adjustments_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/payment_methods_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/variants_controller_spec.rb'
|
||||
- 'spec/lib/open_food_network/address_finder_spec.rb'
|
||||
- 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb'
|
||||
- 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb'
|
||||
@@ -415,7 +392,6 @@ RSpecRails/HaveHttpStatus:
|
||||
- 'spec/controllers/stripe/webhooks_controller_spec.rb'
|
||||
- 'spec/controllers/user_passwords_controller_spec.rb'
|
||||
- 'spec/controllers/user_registrations_controller_spec.rb'
|
||||
- 'spec/requests/admin/images_spec.rb'
|
||||
- 'spec/requests/api/routes_spec.rb'
|
||||
- 'spec/requests/checkout/stripe_sca_spec.rb'
|
||||
- 'spec/requests/home_controller_spec.rb'
|
||||
@@ -645,26 +621,6 @@ Rails/ResponseParsedBody:
|
||||
- 'spec/controllers/spree/credit_cards_controller_spec.rb'
|
||||
- 'spec/controllers/user_registrations_controller_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/RootPathnameMethods:
|
||||
Exclude:
|
||||
- 'spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/SelectMap:
|
||||
Exclude:
|
||||
- 'app/models/enterprise.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Configuration parameters: ForbiddenMethods, AllowedMethods.
|
||||
# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all
|
||||
Rails/SkipsModelValidations:
|
||||
Exclude:
|
||||
- 'app/models/variant_override.rb'
|
||||
- 'spec/models/spree/line_item_spec.rb'
|
||||
|
||||
# Offense count: 7
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
@@ -746,7 +702,7 @@ Style/ClassAndModuleChildren:
|
||||
- 'lib/open_food_network/locking.rb'
|
||||
- 'spec/models/spree/payment_method_spec.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: always, always_true, never
|
||||
@@ -893,39 +849,6 @@ Style/ReturnNilInPredicateMethodDefinition:
|
||||
- 'app/serializers/api/admin/customer_serializer.rb'
|
||||
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
|
||||
|
||||
# Offense count: 204
|
||||
Style/Send:
|
||||
Exclude:
|
||||
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
|
||||
- 'spec/controllers/payment_gateways/paypal_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/base_controller_spec.rb'
|
||||
- 'spec/controllers/spree/orders_controller_spec.rb'
|
||||
- 'spec/helpers/order_cycles_helper_spec.rb'
|
||||
- 'spec/jobs/subscription_confirm_job_spec.rb'
|
||||
- 'spec/jobs/subscription_placement_job_spec.rb'
|
||||
- 'spec/lib/open_food_network/address_finder_spec.rb'
|
||||
- 'spec/lib/open_food_network/enterprise_fee_applicator_spec.rb'
|
||||
- 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb'
|
||||
- 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb'
|
||||
- 'spec/lib/open_food_network/permissions_spec.rb'
|
||||
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
|
||||
- 'spec/lib/reports/xero_invoices_report_spec.rb'
|
||||
- 'spec/lib/stripe/webhook_handler_spec.rb'
|
||||
- 'spec/models/calculator/weight_spec.rb'
|
||||
- 'spec/models/enterprise_spec.rb'
|
||||
- 'spec/models/exchange_spec.rb'
|
||||
- 'spec/models/spree/order_inventory_spec.rb'
|
||||
- 'spec/models/spree/payment_spec.rb'
|
||||
- 'spec/models/spree/return_authorization_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_order_cycles_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_payment_methods_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_products_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_shipping_methods_spec.rb'
|
||||
- 'spec/services/cart_service_spec.rb'
|
||||
- 'spec/services/products_renderer_spec.rb'
|
||||
- 'spec/services/variant_units/option_value_namer_spec.rb'
|
||||
- 'spec/support/localized_number_helper.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Style/SlicingWithRange:
|
||||
|
||||
1
Gemfile
1
Gemfile
@@ -16,7 +16,6 @@ gem "image_processing"
|
||||
|
||||
gem 'activemerchant', '>= 1.78.0'
|
||||
gem 'angular-rails-templates', '>= 0.3.0'
|
||||
gem 'awesome_nested_set'
|
||||
gem 'ransack', '~> 4.1.0'
|
||||
gem 'responders'
|
||||
gem 'webpacker', '~> 5'
|
||||
|
||||
@@ -161,8 +161,6 @@ GEM
|
||||
activerecord (>= 3.1.0, < 8)
|
||||
ast (2.4.2)
|
||||
attr_required (1.0.2)
|
||||
awesome_nested_set (3.6.0)
|
||||
activerecord (>= 4.0.0, < 7.2)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.929.0)
|
||||
aws-sdk-core (3.196.1)
|
||||
@@ -358,7 +356,7 @@ GEM
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
immigrant (0.3.6)
|
||||
activerecord (>= 3.0)
|
||||
invisible_captcha (2.2.0)
|
||||
invisible_captcha (2.3.0)
|
||||
rails (>= 5.2)
|
||||
io-console (0.7.2)
|
||||
ipaddress (0.8.3)
|
||||
@@ -417,7 +415,7 @@ GEM
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.2)
|
||||
marcel (1.0.4)
|
||||
matrix (0.4.2)
|
||||
method_source (1.1.0)
|
||||
mime-types (3.5.2)
|
||||
@@ -449,7 +447,7 @@ GEM
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
newrelic_rpm (9.9.0)
|
||||
nio4r (2.7.0)
|
||||
nio4r (2.7.1)
|
||||
nokogiri (1.16.5)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
@@ -863,7 +861,6 @@ DEPENDENCIES
|
||||
angularjs-file-upload-rails (~> 2.4.1)
|
||||
angularjs-rails (= 1.8.0)
|
||||
arel-helpers (~> 2.12)
|
||||
awesome_nested_set
|
||||
aws-sdk-s3
|
||||
bigdecimal (= 3.0.2)
|
||||
bootsnap
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
//= require jquery.ui.all
|
||||
//= require jquery.powertip
|
||||
//= require jquery.cookie
|
||||
//= require jquery.jstree/jquery.jstree
|
||||
//= require jquery.vAlign
|
||||
//= require angular
|
||||
//= require angular-resource
|
||||
|
||||
@@ -47,7 +47,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
removeClearedValues()
|
||||
params = {
|
||||
'q[name_cont]': $scope.q.query,
|
||||
'q[supplier_id_eq]': $scope.q.producerFilter,
|
||||
'q[variants_supplier_id_eq]': $scope.q.producerFilter,
|
||||
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter,
|
||||
'q[s]': $scope.sorting,
|
||||
import_date: $scope.q.importDateFilter,
|
||||
@@ -126,8 +126,11 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
DisplayProperties.setShowVariants 0, showVariants
|
||||
|
||||
$scope.addVariant = (product) ->
|
||||
# Set new variant category to same as last product variant category to keep compactibility with deleted variant callback to set new variant category
|
||||
newVariantId = $scope.nextVariantId();
|
||||
newVariantCategoryId = product.variants[product.variants.length - 1]?.category_id
|
||||
product.variants.push
|
||||
id: $scope.nextVariantId()
|
||||
id: newVariantId
|
||||
unit_value: null
|
||||
unit_description: null
|
||||
on_demand: false
|
||||
@@ -136,8 +139,9 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
on_hand: null
|
||||
price: null
|
||||
tax_category_id: null
|
||||
category_id: null
|
||||
category_id: newVariantCategoryId
|
||||
DisplayProperties.setShowVariants product.id, true
|
||||
DirtyProducts.addVariantProperty(product.id, newVariantId, 'category_id', newVariantCategoryId)
|
||||
|
||||
|
||||
$scope.nextVariantId = ->
|
||||
@@ -217,7 +221,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
products: productsToSubmit
|
||||
filters:
|
||||
'q[name_cont]': $scope.q.query
|
||||
'q[supplier_id_eq]': $scope.q.producerFilter
|
||||
'q[variants_supplier_id_eq]': $scope.q.producerFilter
|
||||
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter
|
||||
'q[s]': $scope.sorting
|
||||
import_date: $scope.q.importDateFilter
|
||||
@@ -314,9 +318,6 @@ filterSubmitProducts = (productsToFilter) ->
|
||||
if product.hasOwnProperty("name")
|
||||
filteredProduct.name = product.name
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("producer_id")
|
||||
filteredProduct.supplier_id = product.producer_id
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("price")
|
||||
filteredProduct.price = product.price
|
||||
hasUpdatableProperty = true
|
||||
@@ -379,6 +380,9 @@ filterSubmitVariant = (variant) ->
|
||||
if variant.hasOwnProperty("display_as")
|
||||
filteredVariant.display_as = variant.display_as
|
||||
hasUpdatableProperty = true
|
||||
if variant.hasOwnProperty("producer_id")
|
||||
filteredVariant.supplier_id = variant.producer_id
|
||||
hasUpdatableProperty = true
|
||||
{filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty}
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# Used like a regular angular filter where an object is passed
|
||||
# Adds the additional special case that a value of 0 for the filter
|
||||
# acts as a bypass for that particular attribute
|
||||
|
||||
# NOTE the name doesn't reflect what the filter does, it only fiters on the variant.producer_id
|
||||
angular.module("admin.indexUtils").filter "attrFilter", ($filter) ->
|
||||
return (objects, filters) ->
|
||||
Object.keys(filters).reduce (filtered, attr) ->
|
||||
filter = filters[attr]
|
||||
return filtered if !filter? || filter == 0
|
||||
return $filter('filter')(filtered, (object) ->
|
||||
object[attr] == filter
|
||||
)
|
||||
, objects
|
||||
filter = filters["producer_id"]
|
||||
|
||||
return objects if !filter? || filter == 0
|
||||
|
||||
return $filter('filter')(objects, (product) ->
|
||||
for variant in product.variants
|
||||
return true if variant["producer_id"] == filter
|
||||
false
|
||||
, true)
|
||||
|
||||
@@ -27,7 +27,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
"order_bill_address_full_name_reversed",
|
||||
"order_bill_address_full_name_with_comma",
|
||||
"order_bill_address_full_name_with_comma_reversed",
|
||||
"variant_product_supplier_name",
|
||||
"variant_supplier_name",
|
||||
"order_email",
|
||||
"order_number",
|
||||
"product_name"].join("_or_") + "_cont"
|
||||
@@ -81,7 +81,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
"q[order_shipment_state_not_eq]": "shipped",
|
||||
"q[order_completed_at_not_null]": "true",
|
||||
"q[order_distributor_id_eq]": $scope.distributorFilter,
|
||||
"q[variant_product_supplier_id_eq]": $scope.supplierFilter,
|
||||
"q[variant_supplier_id_eq]": $scope.supplierFilter,
|
||||
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
|
||||
"q[order_completed_at_gteq]": if formattedStartDate then formattedStartDate else undefined,
|
||||
"q[order_completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
|
||||
@@ -105,7 +105,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "supplier", Enterprises.byID
|
||||
$scope.loadOrders()
|
||||
RequestMonitor.load $q.all([$scope.orders.$promise]).then ->
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID
|
||||
$scope.bulk_order_form.$setPristine()
|
||||
@@ -133,7 +133,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
return $http(
|
||||
method: 'GET'
|
||||
url: "/admin/orders/#{order.number}/fire?e=cancel&send_cancellation_email=#{sendEmailCancellation}&restock_items=#{restock_items}")
|
||||
|
||||
|
||||
$scope.deleteLineItem = (lineItem) ->
|
||||
if lineItem.order.item_count == 1
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) ->
|
||||
@@ -167,7 +167,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.cancelOrder(order, sendEmailCancellation, restock_items).then(-> $scope.refreshData())
|
||||
else
|
||||
Promise.all(LineItems.delete(item) for item in items).then(-> $scope.refreshData())
|
||||
, "js.admin.deleting_item_will_cancel_order")
|
||||
, "js.admin.deleting_item_will_cancel_order")
|
||||
else
|
||||
ofnDeleteLineItemsAlert(() ->
|
||||
Promise.all(LineItems.delete(item) for item in lineItemsToDelete).then(-> $scope.refreshData())
|
||||
@@ -199,7 +199,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.getLineItemScale = (lineItem) ->
|
||||
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
|
||||
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
|
||||
lineItem.units_product.variant_unit_scale
|
||||
else
|
||||
1
|
||||
@@ -252,7 +252,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
scale = $scope.getScale(unitsProduct, unitsVariant)
|
||||
if scale
|
||||
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
|
||||
else
|
||||
else
|
||||
''
|
||||
|
||||
$scope.fulfilled = (sumOfUnitValues) ->
|
||||
|
||||
@@ -3,6 +3,7 @@ angular.module('admin.orderCycles')
|
||||
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
|
||||
|
||||
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
$scope.order_cycle_id = order_cycle_id
|
||||
$scope.order_cycle = OrderCycle.load(order_cycle_id)
|
||||
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
|
||||
@@ -18,6 +19,8 @@ angular.module('admin.orderCycles')
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
|
||||
$scope.order_cycle?.confirm = $($event.target).data('confirm');
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
OrderCycle.update(destination, $scope.order_cycle_form)
|
||||
|
||||
@@ -25,4 +28,4 @@ angular.module('admin.orderCycles')
|
||||
if $scope.order_cycle_form?.$dirty
|
||||
t('admin.unsaved_confirm_leave')
|
||||
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, OrderCycle, ExchangeProduct, ocInstance) ->
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, EnterpriseFee, OrderCycle, ExchangeProduct, ocInstance) ->
|
||||
$controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})
|
||||
|
||||
$scope.view = 'incoming'
|
||||
|
||||
# NB: weirdly at this next line $scope.order_cycle.id comes out undefined so we use $scope.order_cycle_id instead
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: $scope.order_cycle_id, per_item: true)
|
||||
$scope.exchangeTotalVariants = (exchange) ->
|
||||
return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]?
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
|
||||
$scope.order_cycle?.confirm = $($event.target).data('confirm');
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
OrderCycle.mirrorIncomingToOutgoingProducts()
|
||||
OrderCycle.update(destination, $scope.order_cycle_form) if OrderCycle.confirmNoDistributors()
|
||||
|
||||
@@ -6,6 +6,8 @@ angular.module('admin.orderCycles').factory('EnterpriseFee', ($resource) ->
|
||||
params:
|
||||
order_cycle_id: '@order_cycle_id'
|
||||
coordinator_id: '@coordinator_id'
|
||||
per_item: '@per_item'
|
||||
per_order: '@per_order'
|
||||
})
|
||||
|
||||
{
|
||||
|
||||
@@ -161,7 +161,11 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
|
||||
StatusMessage.display('failure', t('js.order_cycles.create_failure'))
|
||||
|
||||
update: (destination, form) ->
|
||||
oc = new OrderCycleResource({order_cycle: this.dataForSubmit()})
|
||||
oc = new OrderCycleResource({
|
||||
order_cycle: this.dataForSubmit(),
|
||||
confirm: this.order_cycle.confirm,
|
||||
trigger_action: this.order_cycle.trigger_action
|
||||
})
|
||||
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
|
||||
form.$setPristine() if form
|
||||
if destination?
|
||||
@@ -171,6 +175,8 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
|
||||
, (response) ->
|
||||
if response.data.errors?
|
||||
StatusMessage.display('failure', response.data.errors[0])
|
||||
else if (response.data.trigger_action)
|
||||
StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action)
|
||||
else
|
||||
StatusMessage.display('failure', t('js.order_cycles.update_failure'))
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
root = exports ? this
|
||||
|
||||
root.taxon_tree_menu = (obj, context) ->
|
||||
|
||||
base_url = Spree.url(Spree.routes.taxonomy_taxons)
|
||||
admin_base_url = Spree.url(Spree.routes.admin_taxonomy_taxons)
|
||||
edit_url = Spree.url(Spree.routes.admin_taxonomy_taxons + '/' + obj.attr("id") + "/edit");
|
||||
|
||||
create:
|
||||
label: "<i class='icon-plus'></i> " + Spree.translations.add,
|
||||
action: (obj) -> context.create(obj)
|
||||
rename:
|
||||
label: "<i class='icon-pencil'></i> " + Spree.translations.rename,
|
||||
action: (obj) -> context.rename(obj)
|
||||
remove:
|
||||
label: "<i class='icon-trash'></i> " + Spree.translations.remove,
|
||||
action: (obj) -> context.remove(obj)
|
||||
edit:
|
||||
separator_before: true,
|
||||
label: "<i class='icon-edit'></i> " + Spree.translations.edit,
|
||||
action: (obj) -> window.location = edit_url.toString()
|
||||
@@ -1,139 +0,0 @@
|
||||
handle_ajax_error = (XMLHttpRequest, textStatus, errorThrown) ->
|
||||
$.jstree.rollback(last_rollback)
|
||||
$("#ajax_error").show().html("<strong>" + server_error + "</strong><br />" + taxonomy_tree_error)
|
||||
|
||||
handle_move = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
position = data.rslt.cp
|
||||
node = data.rslt.o
|
||||
new_parent = data.rslt.np
|
||||
|
||||
url = new URL(Spree.routes.admin_taxonomy_taxons)
|
||||
url.pathname = url.pathname + '/' + node.attr("id")
|
||||
data = {
|
||||
_method: "put",
|
||||
"taxon[position]": position,
|
||||
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
|
||||
}
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url.toString(),
|
||||
data: data,
|
||||
error: handle_ajax_error
|
||||
|
||||
true
|
||||
|
||||
handle_create = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
name = data.rslt.name
|
||||
position = data.rslt.position
|
||||
new_parent = data.rslt.parent
|
||||
|
||||
data = {
|
||||
"taxon[name]": name,
|
||||
"taxon[position]": position
|
||||
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
|
||||
}
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: base_url.toString(),
|
||||
data: data,
|
||||
error: handle_ajax_error,
|
||||
success: (data,result) ->
|
||||
node.attr('id', data.id)
|
||||
|
||||
handle_rename = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
name = data.rslt.new_name
|
||||
# change the name inside the main input field as well if taxon is the root one
|
||||
document.getElementById("taxonomy_name").value = name if node.parents("[id]").attr("id") == "taxonomy_tree"
|
||||
|
||||
url = new URL(base_url)
|
||||
url.pathname = url.pathname + '/' + node.attr("id")
|
||||
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url.toString(),
|
||||
data: {_method: "put", "taxon[name]": name },
|
||||
error: handle_ajax_error
|
||||
|
||||
handle_delete = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
delete_url = new URL(base_url)
|
||||
delete_url.pathname = delete_url.pathname + '/' + node.attr("id")
|
||||
if confirm(Spree.translations.are_you_sure_delete)
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: delete_url.toString(),
|
||||
data: {_method: "delete"},
|
||||
error: handle_ajax_error
|
||||
else
|
||||
$.jstree.rollback(last_rollback)
|
||||
last_rollback = null
|
||||
|
||||
root = exports ? this
|
||||
root.setup_taxonomy_tree = (taxonomy_id) ->
|
||||
if taxonomy_id != undefined
|
||||
# this is defined within admin/taxonomies/edit
|
||||
root.base_url = Spree.url(Spree.routes.taxonomy_taxons)
|
||||
|
||||
$.ajax
|
||||
url: base_url.pathname.replace("/taxons", "/jstree"),
|
||||
success: (taxonomy) ->
|
||||
last_rollback = null
|
||||
|
||||
conf =
|
||||
json_data:
|
||||
data: taxonomy,
|
||||
ajax:
|
||||
url: (e) ->
|
||||
base_url.pathname + '/' + e.attr('id') + '/jstree'
|
||||
themes:
|
||||
theme: "apple",
|
||||
url: "/assets/jquery.jstree/themes/apple/style.css"
|
||||
strings:
|
||||
new_node: new_taxon,
|
||||
loading: Spree.translations.loading + "..."
|
||||
crrm:
|
||||
move:
|
||||
check_move: (m) ->
|
||||
position = m.cp
|
||||
node = m.o
|
||||
new_parent = m.np
|
||||
|
||||
# no parent or cant drag and drop
|
||||
if !new_parent || node.attr("rel") == "root"
|
||||
return false
|
||||
|
||||
# can't drop before root
|
||||
if new_parent.attr("id") == "taxonomy_tree" && position == 0
|
||||
return false
|
||||
|
||||
true
|
||||
contextmenu:
|
||||
items: (obj) ->
|
||||
taxon_tree_menu(obj, this)
|
||||
plugins: ["themes", "json_data", "dnd", "crrm", "contextmenu"]
|
||||
|
||||
$("#taxonomy_tree").jstree(conf)
|
||||
.bind("move_node.jstree", handle_move)
|
||||
.bind("remove.jstree", handle_delete)
|
||||
.bind("create.jstree", handle_create)
|
||||
.bind("rename.jstree", handle_rename)
|
||||
.bind "loaded.jstree", ->
|
||||
$(this).jstree("core").toggle_node($('.jstree-icon').first())
|
||||
|
||||
$("#taxonomy_tree a").on "dblclick", (e) ->
|
||||
$("#taxonomy_tree").jstree("rename", this)
|
||||
|
||||
# surpress form submit on enter/return
|
||||
$(document).keypress (e) ->
|
||||
if e.keyCode == 13
|
||||
e.preventDefault()
|
||||
@@ -10,7 +10,9 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
||||
|
||||
statusMessage:
|
||||
text: ""
|
||||
style: {}
|
||||
style: {},
|
||||
type: null,
|
||||
actionName: null
|
||||
|
||||
invalidMessage: ""
|
||||
|
||||
@@ -23,11 +25,15 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
||||
active: ->
|
||||
@statusMessage.text != ''
|
||||
|
||||
display: (type, text) ->
|
||||
display: (type, text, actionName = null) ->
|
||||
@statusMessage.text = text
|
||||
@statusMessage.type = type
|
||||
@statusMessage.actionName = actionName
|
||||
@statusMessage.style = @types[type].style
|
||||
null
|
||||
|
||||
clear: ->
|
||||
@statusMessage.text = ''
|
||||
@statusMessage.style = {}
|
||||
@statusMessage.type = null
|
||||
@statusMessage.actionName = null
|
||||
|
||||
@@ -2,10 +2,10 @@ angular.module("admin.variantOverrides").directive "trackInheritance", (VariantO
|
||||
require: "ngModel"
|
||||
link: (scope, element, attrs, ngModel) ->
|
||||
# This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it
|
||||
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant.id
|
||||
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant
|
||||
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
if ngModel.$dirty && viewValue
|
||||
DirtyVariantOverrides.inherit scope.hub_id, scope.variant.id, scope.variantOverrides[scope.hub_id][scope.variant.id].id
|
||||
DirtyVariantOverrides.inherit scope.hub_id, scope.variant, scope.variantOverrides[scope.hub_id][scope.variant.id].id
|
||||
scope.displayDirty()
|
||||
viewValue
|
||||
|
||||
@@ -2,4 +2,8 @@ angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
|
||||
return (products, hubPermissions, hub_id) ->
|
||||
return [] if !hub_id
|
||||
return [] if !hubPermissions[hub_id]
|
||||
return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)
|
||||
|
||||
return $filter('filter')(products, ((product) ->
|
||||
for variant in product.variants
|
||||
return hubPermissions[hub_id].indexOf(variant.producer_id) > -1
|
||||
), true)
|
||||
|
||||
@@ -12,11 +12,11 @@ angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http
|
||||
@add(hub_id, variant_id, vo_id)
|
||||
@dirtyVariantOverrides[hub_id][variant_id][attr] = value
|
||||
|
||||
inherit: (hub_id, variant_id, vo_id) ->
|
||||
@add(hub_id, variant_id, vo_id)
|
||||
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant_id))
|
||||
inherit: (hub_id, variant, vo_id) ->
|
||||
@add(hub_id, variant.id, vo_id)
|
||||
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant))
|
||||
delete blankVo[attr] for attr, value of blankVo when attr not in @requiredAttrs()
|
||||
@dirtyVariantOverrides[hub_id][variant_id] = blankVo
|
||||
@dirtyVariantOverrides[hub_id][variant.id] = blankVo
|
||||
|
||||
count: ->
|
||||
count = 0
|
||||
|
||||
@@ -13,17 +13,18 @@ angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOve
|
||||
@variantOverrides[hub.id] ||= {}
|
||||
for product in products
|
||||
for variant in product.variants
|
||||
@inherit(hub.id, variant.id) unless @variantOverrides[hub.id][variant.id]
|
||||
@inherit(hub.id, variant) unless @variantOverrides[hub.id][variant.id]
|
||||
|
||||
inherit: (hub_id, variant_id) ->
|
||||
inherit: (hub_id, variant) ->
|
||||
# This method is called from the trackInheritance directive, to reinstate inheritance
|
||||
@variantOverrides[hub_id][variant_id] ||= {}
|
||||
angular.extend @variantOverrides[hub_id][variant_id], @newFor hub_id, variant_id
|
||||
@variantOverrides[hub_id][variant.id] ||= {}
|
||||
angular.extend @variantOverrides[hub_id][variant.id], @newFor(hub_id, variant)
|
||||
|
||||
newFor: (hub_id, variant_id) ->
|
||||
newFor: (hub_id, variant) ->
|
||||
# These properties need to match those checked in VariantOverrideSet.deletable?
|
||||
hub_id: hub_id
|
||||
variant_id: variant_id
|
||||
variant_id: variant.id
|
||||
producer_id: variant.producer_id
|
||||
sku: null
|
||||
price: null
|
||||
count_on_hand: null
|
||||
|
||||
@@ -4,15 +4,18 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.query = ""
|
||||
$scope.taxonSelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.propertySelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.producerPropertySelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.filtersActive = true
|
||||
$scope.page = 1
|
||||
$scope.per_page = 10
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
$scope.supplied_taxons = null
|
||||
$scope.supplied_properties = null
|
||||
$scope.supplied_producer_properties = null
|
||||
$scope.showFilterSidebar = false
|
||||
$scope.activeTaxons = []
|
||||
$scope.activeProperties = []
|
||||
$scope.activeProducerProperties = []
|
||||
|
||||
# Update filters after initial load of shop tab
|
||||
$timeout =>
|
||||
@@ -45,6 +48,12 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.supplied_properties[property.id] = Properties.properties_by_id[property.id]
|
||||
)
|
||||
|
||||
OrderCycleResource.producerProperties params, (data)=>
|
||||
$scope.supplied_producer_properties = {}
|
||||
data.map( (property) ->
|
||||
$scope.supplied_producer_properties[property.id] = Properties.properties_by_id[property.id]
|
||||
)
|
||||
|
||||
$scope.loadMore = ->
|
||||
if ($scope.page * $scope.per_page) <= Products.products.length
|
||||
$scope.loadMoreProducts()
|
||||
@@ -52,6 +61,7 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.$watch 'query', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeTaxons', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeProducerProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
|
||||
$scope.loadProducts = ->
|
||||
$scope.page = 1
|
||||
@@ -66,8 +76,9 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
id: $scope.order_cycle.order_cycle_id,
|
||||
page: page || $scope.page,
|
||||
per_page: $scope.per_page,
|
||||
'q[name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_supplier_name_cont]': $scope.query,
|
||||
'q[name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_variants_supplier_name_cont]': $scope.query,
|
||||
'q[with_properties][]': $scope.activeProperties,
|
||||
'q[with_variants_supplier_properties][]': $scope.activeProducerProperties,
|
||||
'q[variants_primary_taxon_id_in_any][]': $scope.activeTaxons
|
||||
}
|
||||
|
||||
@@ -86,6 +97,12 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
Properties.properties_by_id[property_id].name
|
||||
).join($scope.filtersJoinWord()) if $scope.activeProperties?
|
||||
|
||||
$scope.appliedProducerPropertiesList = ->
|
||||
$scope.activeProducerProperties.map( (property_id) ->
|
||||
Properties.properties_by_id[property_id].name
|
||||
).join($scope.filtersJoinWord()) if $scope.activeProducerProperties?
|
||||
|
||||
|
||||
$scope.filtersJoinWord = ->
|
||||
$sce.trustAsHtml(" <span class='join-word'>#{t('products_or')}</span> ")
|
||||
|
||||
@@ -99,6 +116,7 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.clearFilters = ->
|
||||
$scope.taxonSelectors.clearAll()
|
||||
$scope.propertySelectors.clearAll()
|
||||
$scope.producerPropertySelectors.clearAll()
|
||||
|
||||
$scope.refreshStaleData = ->
|
||||
# If the products template has already been loaded but the controller is being initialized
|
||||
@@ -109,7 +127,7 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.loadProducts()
|
||||
|
||||
$scope.filtersCount = () ->
|
||||
$scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive()
|
||||
$scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive() + $scope.producerPropertySelectors.totalActive()
|
||||
|
||||
$scope.toggleFilterSidebar = ->
|
||||
$scope.showFilterSidebar = !$scope.showFilterSidebar
|
||||
|
||||
@@ -18,4 +18,11 @@ angular.module('Darkswarm').factory 'OrderCycleResource', ($resource) ->
|
||||
url: '/api/v0/order_cycles/:id/properties.json'
|
||||
params:
|
||||
id: '@id'
|
||||
'producerProperties':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/v0/order_cycles/:id/producer_properties.json'
|
||||
params:
|
||||
id: '@id'
|
||||
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ angular.module('Darkswarm').factory 'Products', (OrderCycleResource, OrderCycle,
|
||||
|
||||
dereference: ->
|
||||
for product in @fetched_products
|
||||
product.supplier = Shopfront.producers_by_id[product.supplier.id]
|
||||
product.supplier = Shopfront.producers_by_id[product.variants[0].supplier.id]
|
||||
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
|
||||
|
||||
product.properties = angular.copy(product.properties_with_values)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#save-bar.animate-show{ "ng-show": 'dirty || persist || StatusMessage.active()' }
|
||||
.container
|
||||
.seven.columns.alpha
|
||||
%h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style' }
|
||||
%h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style', data: { 'order-cycle-form-target': 'statusMessage' }, "ng-attr-data-type": "{{StatusMessage.statusMessage.type}}", "ng-attr-data-action-name": "{{StatusMessage.statusMessage.actionName}}" }
|
||||
{{ StatusMessage.statusMessage.text || " " }}
|
||||
%h5#status-message{ style: 'color: #C85136', "ng-show": "StatusMessage.invalidMessage !== ''" }
|
||||
{{ StatusMessage.invalidMessage || " " }}
|
||||
|
||||
@@ -24,6 +24,19 @@
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* prevent arrow on selected admin menu item appearing above modal */
|
||||
|
||||
22
app/controllers/admin/connected_app_settings_controller.rb
Normal file
22
app/controllers/admin/connected_app_settings_controller.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ConnectedAppSettingsController < Spree::Admin::BaseController
|
||||
def update
|
||||
Spree::Config.set(connected_apps_enabled:)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = t(:successfully_updated, resource: t('.resource'))
|
||||
redirect_to main_app.edit_admin_connected_app_settings_path
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def connected_apps_enabled
|
||||
params.require(:preferences).require(:connected_apps_enabled).compact_blank.join(",")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,12 +5,12 @@ module Admin
|
||||
def create
|
||||
authorize! :admin, enterprise
|
||||
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id)
|
||||
attributes = {}
|
||||
attributes[:type] = connected_app_params[:type] if connected_app_params[:type]
|
||||
|
||||
ConnectAppJob.perform_later(
|
||||
app, spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request),
|
||||
)
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id, **attributes)
|
||||
app.connect(api_key: spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request))
|
||||
|
||||
render_panel
|
||||
end
|
||||
@@ -18,15 +18,9 @@ module Admin
|
||||
def destroy
|
||||
authorize! :admin, enterprise
|
||||
|
||||
app = enterprise.connected_apps.first
|
||||
app = enterprise.connected_apps.find(params.require(:id))
|
||||
app.destroy
|
||||
|
||||
WebhookDeliveryJob.perform_later(
|
||||
app.data["destroy"],
|
||||
"disconnect-app",
|
||||
nil
|
||||
)
|
||||
|
||||
render_panel
|
||||
end
|
||||
|
||||
@@ -39,5 +33,9 @@ module Admin
|
||||
def render_panel
|
||||
redirect_to "#{edit_admin_enterprise_path(enterprise)}#/connected_apps_panel"
|
||||
end
|
||||
|
||||
def connected_app_params
|
||||
params.permit(:type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,13 +35,7 @@ module Admin
|
||||
private
|
||||
|
||||
def fetch_catalog(url)
|
||||
if url =~ /food-data-collaboration/
|
||||
fdc_json = FdcRequest.new(spree_current_user).call(url)
|
||||
fdc_message = JSON.parse(fdc_json)
|
||||
fdc_message["products"]
|
||||
else
|
||||
DfcRequest.new(spree_current_user).call(url)
|
||||
end
|
||||
DfcRequest.new(spree_current_user).call(url)
|
||||
end
|
||||
|
||||
# Most of this code is the same as in the DfcProvider::SuppliedProductsController.
|
||||
|
||||
@@ -65,7 +65,9 @@ module Admin
|
||||
order_cycle ||= OrderCycle.new(coordinator:) if coordinator.present?
|
||||
enterprises = OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user,
|
||||
order_cycle).visible_enterprises
|
||||
EnterpriseFee.for_enterprises(enterprises).order('enterprise_id', 'fee_type', 'name')
|
||||
|
||||
fees = EnterpriseFee.for_enterprises(enterprises).order('enterprise_id', 'fee_type', 'name')
|
||||
filter_fees(fees)
|
||||
else
|
||||
collection = EnterpriseFee.managed_by(spree_current_user).order('enterprise_id',
|
||||
'fee_type', 'name')
|
||||
@@ -74,6 +76,12 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def filter_fees(fees)
|
||||
fees = fees.per_item if params[:per_item]
|
||||
fees = fees.per_order if params[:per_order]
|
||||
fees
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index, :for_order_cycle, :bulk_update]
|
||||
end
|
||||
|
||||
@@ -189,10 +189,7 @@ module Admin
|
||||
.visible_enterprises
|
||||
|
||||
if enterprises.present?
|
||||
enterprises.includes(
|
||||
supplied_products:
|
||||
[:supplier, :variants, :image]
|
||||
)
|
||||
enterprises.includes(supplied_products: [:variants, :image])
|
||||
end
|
||||
when :index
|
||||
if spree_current_user.admin?
|
||||
|
||||
@@ -11,6 +11,7 @@ module Admin
|
||||
before_action :remove_protected_attrs, only: [:update]
|
||||
before_action :require_order_cycle_set_params, only: [:bulk_update]
|
||||
around_action :protect_invalid_destroy, only: :destroy
|
||||
before_action :verify_datetime_change, only: :update
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
@@ -235,7 +236,7 @@ module Admin
|
||||
else
|
||||
begin
|
||||
yield
|
||||
rescue ActiveRecord::InvalidForeignKey
|
||||
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::DeleteRestrictionError
|
||||
redirect_to main_app.admin_order_cycles_url
|
||||
flash[:error] = I18n.t('admin.order_cycles.destroy_errors.orders_present')
|
||||
end
|
||||
@@ -294,5 +295,22 @@ module Admin
|
||||
collection_attributes: [:id] + PermittedAttributes::OrderCycle.basic_attributes
|
||||
).to_h.with_indifferent_access
|
||||
end
|
||||
|
||||
# Check that order cycle datetime values changed if it has existing orders
|
||||
def verify_datetime_change
|
||||
return unless params[:order_cycle][:confirm]
|
||||
return unless @order_cycle.orders.exists?
|
||||
return if same_dates(@order_cycle.orders_open_at, order_cycle_params[:orders_open_at]) &&
|
||||
same_dates(@order_cycle.orders_close_at, order_cycle_params[:orders_close_at])
|
||||
|
||||
render json: { trigger_action: params[:order_cycle][:trigger_action] },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def same_dates(date, string)
|
||||
false unless date && string
|
||||
|
||||
DateTime.parse(string).to_fs(:short) == date.to_fs(:short)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,8 @@ require 'roo'
|
||||
|
||||
module Admin
|
||||
class ProductImportController < Spree::Admin::BaseController
|
||||
TMPDIR_PREFIX = "product_import-"
|
||||
|
||||
before_action :validate_upload_presence, except: %i[index guide validate_data]
|
||||
|
||||
def index
|
||||
@@ -101,8 +103,7 @@ module Admin
|
||||
|
||||
def save_uploaded_file(upload)
|
||||
extension = File.extname(upload.original_filename)
|
||||
directory = Dir.mktmpdir 'product_import'
|
||||
File.open(File.join(directory, "import#{extension}"), 'wb') do |f|
|
||||
File.open(File.join(mktmpdir, "import#{extension}"), 'wb') do |f|
|
||||
data = UploadSanitizer.new(upload.read).call
|
||||
f.write(data)
|
||||
f.path
|
||||
@@ -126,6 +127,14 @@ module Admin
|
||||
ProductImport::ProductImporter
|
||||
end
|
||||
|
||||
def mktmpdir
|
||||
Dir::Tmpname.create(TMPDIR_PREFIX, Rails.root.join('tmp') ) { |tmpname| Dir.mkdir(tmpname) }
|
||||
end
|
||||
|
||||
def tmpdir_base
|
||||
Rails.root.join('tmp', TMPDIR_PREFIX).to_s
|
||||
end
|
||||
|
||||
def file_path
|
||||
@file_path ||= validate_file_path(sanitize_file_path(params[:filepath]))
|
||||
end
|
||||
@@ -134,8 +143,9 @@ module Admin
|
||||
FilePathSanitizer.new.sanitize(file_path, on_error: method(:raise_invalid_file_path))
|
||||
end
|
||||
|
||||
# Ensure file is under the safe tmp directory
|
||||
def validate_file_path(file_path)
|
||||
return file_path if file_path.to_s.match?(TEMP_FILE_PATH_REGEX)
|
||||
return file_path if file_path.to_s.match?(%r{^#{tmpdir_base}[A-Za-z0-9-]*/import\.csv$})
|
||||
|
||||
raise_invalid_file_path
|
||||
end
|
||||
@@ -145,6 +155,5 @@ module Admin
|
||||
notice: I18n.t(:product_import_no_data_in_spreadsheet_notice)
|
||||
raise 'Invalid File Path'
|
||||
end
|
||||
TEMP_FILE_PATH_REGEX = %r{^/tmp/product_import[A-Za-z0-9-]*/import\.csv$}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,8 @@ module Admin
|
||||
def index
|
||||
fetch_products
|
||||
render "index", locals: { producers:, categories:, tax_category_options:, flash: }
|
||||
|
||||
session[:products_return_to_url] = request.url
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
@@ -70,6 +72,29 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def clone
|
||||
@product = Spree::Product.find(params[:id])
|
||||
status = :ok
|
||||
|
||||
begin
|
||||
@cloned_product = @product.duplicate
|
||||
flash.now[:success] = t('.success')
|
||||
|
||||
@product_index = "-#{@cloned_product.id}"
|
||||
@producer_options = producers
|
||||
@category_options = categories
|
||||
@tax_category_options = tax_category_options
|
||||
rescue ActiveRecord::ActiveRecordError => _e
|
||||
flash.now[:error] = t('.error')
|
||||
status = :unprocessable_entity
|
||||
@product_index = "-1" # Create a unique enough index
|
||||
end
|
||||
|
||||
respond_with do |format|
|
||||
format.turbo_stream { render :clone, status: }
|
||||
end
|
||||
end
|
||||
|
||||
def index_url(params)
|
||||
"/admin/products?#{params.to_query}" # todo: fix routing so this can be automaticly generated
|
||||
end
|
||||
@@ -126,7 +151,7 @@ module Admin
|
||||
|
||||
def ransack_query
|
||||
query = {}
|
||||
query.merge!(supplier_id_in: @producer_id) if @producer_id.present?
|
||||
query.merge!(variants_supplier_id_in: @producer_id) if @producer_id.present?
|
||||
if @search_term.present?
|
||||
query.merge!(Spree::Variant::SEARCH_KEY => @search_term)
|
||||
end
|
||||
@@ -140,13 +165,13 @@ module Admin
|
||||
def product_query_includes
|
||||
[
|
||||
:image,
|
||||
:supplier,
|
||||
{ variants: [
|
||||
:default_price,
|
||||
:primary_taxon,
|
||||
:product,
|
||||
:stock_items,
|
||||
:tax_category,
|
||||
:supplier,
|
||||
] },
|
||||
]
|
||||
end
|
||||
|
||||
@@ -61,6 +61,9 @@ module Admin
|
||||
def render_in_background
|
||||
cable_ready[ScopedChannel.for_id(params[:uuid])]
|
||||
.inner_html(
|
||||
selector: "#report-go",
|
||||
html: helpers.button(t(:go), "report__submit-btn", "submit", disabled: true)
|
||||
).inner_html(
|
||||
selector: "#report-table",
|
||||
html: render_to_string(partial: "admin/reports/loading")
|
||||
).scroll_into_view(
|
||||
|
||||
@@ -7,9 +7,11 @@ module Api
|
||||
include ApiActionCaching
|
||||
|
||||
skip_authorization_check
|
||||
skip_before_action :authenticate_user, :ensure_api_key, only: [:taxons, :properties]
|
||||
skip_before_action :authenticate_user, :ensure_api_key, only: [
|
||||
:taxons, :properties, :producer_properties
|
||||
]
|
||||
|
||||
caches_action :taxons, :properties,
|
||||
caches_action :taxons, :properties, :producer_properties,
|
||||
expires_in: CacheService::FILTERS_EXPIRY,
|
||||
cache_path: proc { |controller| controller.request.url }
|
||||
|
||||
@@ -41,7 +43,13 @@ module Api
|
||||
|
||||
def properties
|
||||
render plain: ActiveModel::ArraySerializer.new(
|
||||
product_properties | producer_properties, each_serializer: Api::PropertySerializer
|
||||
product_properties, each_serializer: Api::PropertySerializer
|
||||
).to_json
|
||||
end
|
||||
|
||||
def producer_properties
|
||||
render plain: ActiveModel::ArraySerializer.new(
|
||||
load_producer_properties, each_serializer: Api::PropertySerializer
|
||||
).to_json
|
||||
end
|
||||
|
||||
@@ -58,7 +66,7 @@ module Api
|
||||
select('DISTINCT spree_properties.*')
|
||||
end
|
||||
|
||||
def producer_properties
|
||||
def load_producer_properties
|
||||
producers = Enterprise.
|
||||
joins(:supplied_products).
|
||||
where(spree_products: { id: distributed_products })
|
||||
@@ -86,8 +94,9 @@ module Api
|
||||
end
|
||||
|
||||
def distributed_products
|
||||
OrderCycles::DistributedProductsService.new(distributor, order_cycle,
|
||||
customer).products_relation
|
||||
OrderCycles::DistributedProductsService.new(
|
||||
distributor, order_cycle, customer
|
||||
).products_relation.pluck(:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,7 +54,7 @@ module Api
|
||||
end
|
||||
|
||||
def overridable
|
||||
@products = product_finder.paged_products_for_producers
|
||||
@products = product_finder.products_for_producers
|
||||
|
||||
render_paged_products @products, ::Api::Admin::ProductSimpleSerializer
|
||||
end
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V0
|
||||
class TaxonomiesController < Api::V0::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check only: :jstree
|
||||
|
||||
def jstree
|
||||
@taxonomy = Spree::Taxonomy.find(params[:id])
|
||||
render json: @taxonomy.root, serializer: Api::TaxonJstreeSerializer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,12 +5,10 @@ module Api
|
||||
class TaxonsController < Api::V0::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check only: [:index, :show, :jstree]
|
||||
skip_authorization_check only: [:index, :show]
|
||||
|
||||
def index
|
||||
@taxons = if taxonomy
|
||||
taxonomy.root.children
|
||||
elsif params[:ids]
|
||||
@taxons = if params[:ids]
|
||||
Spree::Taxon.where(id: raw_params[:ids].split(","))
|
||||
else
|
||||
Spree::Taxon.ransack(raw_params[:q]).result
|
||||
@@ -18,23 +16,9 @@ module Api
|
||||
render json: @taxons, each_serializer: Api::TaxonSerializer
|
||||
end
|
||||
|
||||
def jstree
|
||||
@taxon = taxon
|
||||
render json: @taxon.children, each_serializer: Api::TaxonJstreeSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
authorize! :create, Spree::Taxon
|
||||
@taxon = Spree::Taxon.new(taxon_params)
|
||||
@taxon.taxonomy_id = params[:taxonomy_id]
|
||||
taxonomy = Spree::Taxonomy.find_by(id: params[:taxonomy_id])
|
||||
|
||||
if taxonomy.nil?
|
||||
@taxon.errors.add(:taxonomy_id, I18n.t(:invalid_taxonomy_id, scope: 'spree.api'))
|
||||
invalid_resource!(@taxon) && return
|
||||
end
|
||||
|
||||
@taxon.parent_id = taxonomy.root.id unless params.dig(:taxon, :parent_id)
|
||||
|
||||
if @taxon.save
|
||||
render json: @taxon, serializer: Api::TaxonSerializer, status: :created
|
||||
@@ -60,20 +44,14 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
def taxonomy
|
||||
return if params[:taxonomy_id].blank?
|
||||
|
||||
@taxonomy ||= Spree::Taxonomy.find(params[:taxonomy_id])
|
||||
end
|
||||
|
||||
def taxon
|
||||
@taxon ||= taxonomy.taxons.find(params[:id])
|
||||
@taxon = Spree::Taxon.find(params[:id])
|
||||
end
|
||||
|
||||
def taxon_params
|
||||
return if params[:taxon].blank?
|
||||
|
||||
params.require(:taxon).permit([:name, :parent_id, :position])
|
||||
params.require(:taxon).permit([:name, :position])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,15 +19,18 @@ module Spree
|
||||
|
||||
before_action :authorize_admin
|
||||
before_action :set_locale
|
||||
before_action :warn_invalid_order_cycles, if: :html_request?
|
||||
before_action :warn_invalid_order_cycles, if: :page_load_request?
|
||||
|
||||
# Warn the user when they have an active order cycle with hubs that are not ready
|
||||
# for checkout (ie. does not have valid shipping and payment methods).
|
||||
def warn_invalid_order_cycles
|
||||
return if flash[:notice].present?
|
||||
return if flash[:notice].present? || session[:displayed_order_cycle_warning]
|
||||
|
||||
warning = OrderCycles::WarningService.new(spree_current_user).call
|
||||
flash[:notice] = warning if warning.present?
|
||||
return if warning.blank?
|
||||
|
||||
flash.now[:notice] = warning
|
||||
session[:displayed_order_cycle_warning] = true
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -81,6 +84,12 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def page_load_request?
|
||||
return false if request.format.include?('turbo')
|
||||
|
||||
html_request?
|
||||
end
|
||||
|
||||
def html_request?
|
||||
request.format.html?
|
||||
end
|
||||
|
||||
@@ -15,6 +15,8 @@ module Spree
|
||||
invoice_pdf = filepath(invoice_id)
|
||||
|
||||
send_file(invoice_pdf, type: 'application/pdf', disposition: :inline)
|
||||
rescue ActionController::MissingFile
|
||||
render "errors/not_found", status: :not_found, formats: :html
|
||||
end
|
||||
|
||||
def generate
|
||||
|
||||
@@ -8,6 +8,7 @@ module Spree
|
||||
before_action :setup_property, only: [:index]
|
||||
|
||||
def index
|
||||
@supplier = @product.variants.first.supplier
|
||||
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
|
||||
end
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@ module Spree
|
||||
include OpenFoodNetwork::SpreeApiKeyLoader
|
||||
include OrderCyclesHelper
|
||||
include EnterprisesHelper
|
||||
helper ::Admin::ProductsHelper
|
||||
|
||||
before_action :load_data
|
||||
before_action :load_producers, only: [:index, :new]
|
||||
before_action :load_form_data, only: [:index, :new, :create, :edit, :update]
|
||||
before_action :load_spree_api_key, only: [:index, :variant_overrides]
|
||||
before_action :strip_new_properties, only: [:create, :update]
|
||||
@@ -41,6 +43,7 @@ module Spree
|
||||
flash[:success] = flash_message_for(@object, :successfully_created)
|
||||
redirect_after_save
|
||||
else
|
||||
load_producers
|
||||
# Re-fill the form with deleted params on product
|
||||
@on_hand = request.params[:product][:on_hand]
|
||||
@on_demand = request.params[:product][:on_demand]
|
||||
@@ -52,14 +55,9 @@ module Spree
|
||||
def update
|
||||
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
|
||||
|
||||
original_supplier_id = @product.supplier_id
|
||||
delete_stock_params_and_set_after do
|
||||
params[:product] ||= {} if params[:clear_product_properties]
|
||||
if @object.update(permitted_resource_params)
|
||||
if original_supplier_id != @product.supplier_id
|
||||
ExchangeVariantDeleter.new.delete(@product)
|
||||
end
|
||||
|
||||
flash[:success] = flash_message_for(@object, :successfully_updated)
|
||||
end
|
||||
redirect_to spree.edit_admin_product_url(@object, @url_filters)
|
||||
@@ -157,12 +155,15 @@ module Spree
|
||||
end
|
||||
|
||||
def load_form_data
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
managed_product_enterprises.is_primary_producer.by_name
|
||||
@taxons = Spree::Taxon.order(:name)
|
||||
@import_dates = product_import_dates.uniq.to_json
|
||||
end
|
||||
|
||||
def load_producers
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
managed_product_enterprises.is_primary_producer.by_name
|
||||
end
|
||||
|
||||
def product_import_dates
|
||||
options = [{ id: '0', name: '' }]
|
||||
product_import_dates_query.collect(&:import_date).
|
||||
@@ -173,12 +174,10 @@ module Spree
|
||||
|
||||
def product_import_dates_query
|
||||
Spree::Variant.
|
||||
select('DISTINCT spree_variants.import_date').
|
||||
joins(:product).
|
||||
where(spree_products: { supplier_id: editable_enterprises.collect(&:id) }).
|
||||
select('import_date').distinct.
|
||||
where(supplier_id: editable_enterprises.collect(&:id)).
|
||||
where.not(spree_variants: { import_date: nil }).
|
||||
where(spree_variants: { deleted_at: nil }).
|
||||
order('spree_variants.import_date DESC')
|
||||
order('import_date DESC')
|
||||
end
|
||||
|
||||
def strip_new_properties
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class TaxonomiesController < ::Admin::ResourceController
|
||||
respond_to :json, only: [:get_children]
|
||||
|
||||
def get_children
|
||||
@taxons = Taxon.find(params[:parent_id]).children
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def location_after_save
|
||||
if @taxonomy.created_at == @taxonomy.updated_at
|
||||
spree.edit_admin_taxonomy_url(@taxonomy)
|
||||
else
|
||||
spree.admin_taxonomies_url
|
||||
end
|
||||
end
|
||||
|
||||
def permitted_resource_params
|
||||
params.require(:taxonomy).permit(:name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,122 +2,70 @@
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class TaxonsController < Spree::Admin::BaseController
|
||||
respond_to :html, :json, :js
|
||||
class TaxonsController < ::Admin::ResourceController
|
||||
before_action :set_taxon, except: %i[create index new]
|
||||
|
||||
def edit
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.find(params[:id])
|
||||
@permalink_part = @taxon.permalink.split("/").last
|
||||
def index
|
||||
@taxons = Taxon.order(:name)
|
||||
end
|
||||
|
||||
def new
|
||||
@taxon = Taxon.new
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.build(params[:taxon])
|
||||
@taxon = Spree::Taxon.new(taxon_params)
|
||||
if @taxon.save
|
||||
respond_with(@taxon) do |format|
|
||||
format.json { render json: @taxon.to_json }
|
||||
end
|
||||
flash[:success] = flash_message_for(@taxon, :successfully_created)
|
||||
redirect_to edit_admin_taxon_path(@taxon.id)
|
||||
else
|
||||
flash[:error] = Spree.t('errors.messages.could_not_create_taxon')
|
||||
respond_with(@taxon) do |format|
|
||||
format.html do
|
||||
if redirect_to @taxonomy
|
||||
spree.edit_admin_taxonomy_url(@taxonomy)
|
||||
else
|
||||
spree.admin_taxonomies_url
|
||||
end
|
||||
end
|
||||
end
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.find(params[:id])
|
||||
parent_id = params[:taxon][:parent_id]
|
||||
new_position = params[:taxon][:position]
|
||||
|
||||
if parent_id || new_position # taxon is being moved
|
||||
new_parent = parent_id.nil? ? @taxon.parent : Taxon.find(parent_id.to_i)
|
||||
new_position = new_position.nil? ? -1 : new_position.to_i
|
||||
|
||||
# Bellow is a very complicated way of finding where in nested set we
|
||||
# should actually move the taxon to achieve sane results,
|
||||
# JS is giving us the desired position, which was awesome for previous setup,
|
||||
# but now it's quite complicated to find where we should put it as we have
|
||||
# to differenciate between moving to the same branch, up down and into
|
||||
# first position.
|
||||
new_siblings = new_parent.children
|
||||
if new_position <= 0 && new_siblings.empty?
|
||||
@taxon.move_to_child_of(new_parent)
|
||||
elsif new_parent.id != @taxon.parent_id
|
||||
if new_position.zero?
|
||||
@taxon.move_to_left_of(new_siblings.first)
|
||||
else
|
||||
@taxon.move_to_right_of(new_siblings[new_position - 1])
|
||||
end
|
||||
elsif new_position < new_siblings.index(@taxon)
|
||||
@taxon.move_to_left_of(new_siblings[new_position]) # we move up
|
||||
else
|
||||
@taxon.move_to_right_of(new_siblings[new_position - 1]) # we move down
|
||||
end
|
||||
# Reset legacy position, if any extensions still rely on it
|
||||
new_parent.children.reload.each do |t|
|
||||
t.update_columns(
|
||||
position: t.position,
|
||||
updated_at: Time.zone.now
|
||||
)
|
||||
end
|
||||
|
||||
if parent_id
|
||||
@taxon.reload
|
||||
@taxon.set_permalink
|
||||
@taxon.save!
|
||||
@update_children = true
|
||||
end
|
||||
end
|
||||
|
||||
if params.key? "permalink_part"
|
||||
parent_permalink = @taxon.permalink.split("/")[0...-1].join("/")
|
||||
parent_permalink += "/" if parent_permalink.present?
|
||||
params[:taxon][:permalink] = parent_permalink + params[:permalink_part]
|
||||
end
|
||||
# check if we need to rename child taxons if parent name or permalink changes
|
||||
if params[:taxon][:name] != @taxon.name || params[:taxon][:permalink] != @taxon.permalink
|
||||
@update_children = true
|
||||
end
|
||||
|
||||
if @taxon.update(taxon_params)
|
||||
flash[:success] = flash_message_for(@taxon, :successfully_updated)
|
||||
end
|
||||
|
||||
# rename child taxons
|
||||
if @update_children
|
||||
@taxon.descendants.each do |taxon|
|
||||
taxon.reload
|
||||
taxon.set_permalink
|
||||
taxon.save!
|
||||
end
|
||||
end
|
||||
|
||||
respond_with(@taxon) do |format|
|
||||
format.html { redirect_to spree.edit_admin_taxonomy_url(@taxonomy) }
|
||||
format.json { render json: @taxon.to_json }
|
||||
redirect_to edit_admin_taxon_path(@taxon.id)
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@taxon = Taxon.find(params[:id])
|
||||
@taxon.destroy
|
||||
respond_with(@taxon) { |format| format.json { render json: '' } }
|
||||
status = if @taxon.destroy
|
||||
flash_message = t('.delete_taxon.success')
|
||||
status = :ok
|
||||
else
|
||||
flash_message = t('.delete_taxon.error')
|
||||
status = :unprocessable_entity
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = flash_message if status == :ok
|
||||
flash[:error] = flash_message if status == :unprocessable_entity
|
||||
redirect_to admin_taxons_path
|
||||
}
|
||||
format.turbo_stream {
|
||||
flash[:success] = flash_message if status == :ok
|
||||
flash[:error] = flash_message if status == :unprocessable_entity
|
||||
render :destroy_taxon, status:
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_taxon
|
||||
@taxon = Taxon.find(params[:id])
|
||||
end
|
||||
|
||||
def taxon_params
|
||||
params.require(:taxon).permit(
|
||||
:name, :parent_id, :position, :icon, :description, :permalink, :taxonomy_id,
|
||||
:name, :position, :icon, :description, :permalink,
|
||||
:meta_description, :meta_keywords, :meta_title, :dfc_id
|
||||
)
|
||||
end
|
||||
|
||||
@@ -46,7 +46,13 @@ module Spree
|
||||
def update
|
||||
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
|
||||
|
||||
original_supplier_id = @object.supplier_id
|
||||
|
||||
if @object.update(permitted_resource_params)
|
||||
if original_supplier_id != @object.supplier_id
|
||||
ExchangeVariantDeleter.new.delete(@object)
|
||||
end
|
||||
|
||||
flash[:success] = flash_message_for(@object, :successfully_updated)
|
||||
redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters)
|
||||
else
|
||||
@@ -113,6 +119,8 @@ module Spree
|
||||
private
|
||||
|
||||
def load_data
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
managed_product_enterprises.is_primary_producer.by_name
|
||||
@tax_categories = TaxCategory.order(:name)
|
||||
@shipping_categories = ShippingCategory.order(:name)
|
||||
end
|
||||
|
||||
@@ -47,14 +47,8 @@ module Spree
|
||||
@user = Spree::User.new(user_params)
|
||||
|
||||
if @user.save
|
||||
render cable_ready: cable_car.inner_html(
|
||||
"#signup-feedback",
|
||||
partial("layouts/alert",
|
||||
locals: {
|
||||
type: "success",
|
||||
message: t('devise.user_registrations.spree_user.signed_up_but_unconfirmed')
|
||||
})
|
||||
)
|
||||
flash[:success] = t('devise.user_registrations.spree_user.signed_up_but_unconfirmed')
|
||||
render cable_ready: cable_car.redirect_to(url: main_app.root_path)
|
||||
else
|
||||
render status: :unprocessable_entity, cable_ready: cable_car.morph(
|
||||
"#signup-tab",
|
||||
|
||||
@@ -26,7 +26,8 @@ module Admin
|
||||
show_enterprise_fees = can?(:manage_enterprise_fees,
|
||||
enterprise) && (is_shop || enterprise.is_primary_producer)
|
||||
show_connected_apps = can?(:manage_connected_apps, enterprise) &&
|
||||
feature?(:connected_apps, spree_current_user, enterprise)
|
||||
feature?(:connected_apps, spree_current_user, enterprise) &&
|
||||
Spree::Config.connected_apps_enabled.present?
|
||||
|
||||
build_enterprise_side_menu_items(
|
||||
is_shop:,
|
||||
@@ -38,6 +39,11 @@ module Admin
|
||||
)
|
||||
end
|
||||
|
||||
def connected_apps_enabled
|
||||
connected_apps_enabled = Spree::Config.connected_apps_enabled&.split(',') || []
|
||||
ConnectedApp::TYPES & connected_apps_enabled
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_enterprise_side_menu_items(
|
||||
|
||||
@@ -9,5 +9,33 @@ module Admin
|
||||
new_admin_product_image_path(product.id)
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_new_variant(product)
|
||||
product.variants.build
|
||||
end
|
||||
|
||||
def unit_value_with_description(variant)
|
||||
precised_unit_value = nil
|
||||
|
||||
if variant.unit_value
|
||||
scaled_unit_value = variant.unit_value / (variant.product.variant_unit_scale || 1)
|
||||
precised_unit_value = number_with_precision(
|
||||
scaled_unit_value,
|
||||
precision: nil,
|
||||
strip_insignificant_zeros: true,
|
||||
significant: false,
|
||||
)
|
||||
end
|
||||
|
||||
[precised_unit_value, variant.unit_description].compact_blank.join(" ")
|
||||
end
|
||||
|
||||
def products_return_to_url(url_filters)
|
||||
if feature?(:admin_style_v3, spree_current_user)
|
||||
return session[:products_return_to_url] || admin_products_url
|
||||
end
|
||||
|
||||
"#{admin_products_path}#{url_filters.empty? ? '' : "#?#{url_filters.to_query}"}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module I18nHelper
|
||||
def locale_options
|
||||
OpenFoodNetwork::I18nConfig.available_locales.map do |locale|
|
||||
OpenFoodNetwork::I18nConfig.selectable_locales.map do |locale|
|
||||
[t('language_name', locale:), locale]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,4 +10,8 @@ module MailerHelper
|
||||
link_to ofn, "https://www.openfoodnetwork.org"
|
||||
end
|
||||
end
|
||||
|
||||
def order_reply_email(order)
|
||||
order.distributor.email_address.presence || order.distributor.contact.email
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
module TaxonsHelper
|
||||
def taxon_path(taxon)
|
||||
taxon.ancestors.reverse.collect(&:name).join( " >> ")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -19,8 +19,8 @@ class ConnectAppJob < ApplicationJob
|
||||
|
||||
selector = "#connected-app-discover-regen.enterprise_#{enterprise.id}"
|
||||
html = ApplicationController.render(
|
||||
partial: "admin/enterprises/form/connected_apps",
|
||||
locals: { enterprise: },
|
||||
partial: "admin/enterprises/form/connected_apps/discover_regen",
|
||||
locals: { enterprise:, connected_app: enterprise.connected_apps.discover_regen.first },
|
||||
)
|
||||
|
||||
cable_ready[channel].morph(selector:, html:).broadcast
|
||||
|
||||
@@ -39,10 +39,14 @@ class ReportJob < ApplicationJob
|
||||
end
|
||||
|
||||
def broadcast_result(channel, format, blob)
|
||||
cable_ready[channel].inner_html(
|
||||
selector: "#report-table",
|
||||
html: actioncable_content(format, blob)
|
||||
).broadcast
|
||||
cable_ready[channel]
|
||||
.inner_html(
|
||||
selector: "#report-go",
|
||||
html: Spree::Admin::BaseController.helpers.button(I18n.t(:go), "report__submit-btn")
|
||||
).inner_html(
|
||||
selector: "#report-table",
|
||||
html: actioncable_content(format, blob)
|
||||
).broadcast
|
||||
end
|
||||
|
||||
def broadcast_error(channel)
|
||||
@@ -53,7 +57,14 @@ class ReportJob < ApplicationJob
|
||||
end
|
||||
|
||||
def actioncable_content(format, blob)
|
||||
return blob.result if format.to_sym == :html
|
||||
if format.to_sym == :html
|
||||
return blob.result if blob.byte_size < 10**6 # 1 MB
|
||||
|
||||
return render(
|
||||
partial: "admin/reports/display",
|
||||
locals: { file_url: blob.expiring_service_url }
|
||||
)
|
||||
end
|
||||
|
||||
render(partial: "admin/reports/download", locals: { file_url: blob.expiring_service_url })
|
||||
end
|
||||
|
||||
@@ -60,11 +60,12 @@ class ProducerMailer < ApplicationMailer
|
||||
|
||||
def line_items_from(order_cycle, producer)
|
||||
@line_items ||= Spree::LineItem.
|
||||
includes(variant: [:product]).
|
||||
includes(variant: :product).
|
||||
joins(variant: :product).
|
||||
from_order_cycle(order_cycle).
|
||||
sorted_by_name_and_unit_value.
|
||||
merge(Spree::Product.with_deleted.in_supplier(producer)).
|
||||
merge(Spree::Order.by_state(["complete", "resumed"]))
|
||||
merge(Spree::Variant.with_deleted.where(supplier: producer)).
|
||||
merge(Spree::Order.by_state(["complete", "resumed"])).
|
||||
sorted_by_name_and_unit_value
|
||||
end
|
||||
|
||||
def total_from_line_items(line_items)
|
||||
@@ -81,7 +82,7 @@ class ProducerMailer < ApplicationMailer
|
||||
line_items.map do |line_item|
|
||||
{
|
||||
sku: line_item.variant.sku,
|
||||
supplier_name: line_item.product.supplier.name,
|
||||
supplier_name: line_item.variant.supplier.name,
|
||||
product_and_full_name: line_item.product_and_full_name,
|
||||
quantity: line_item.quantity,
|
||||
first_name: line_item.order.billing_address.first_name,
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Rails 5 introduced some breaking changes to these built-in methods, and the new versions
|
||||
# no longer work correctly in relation to decrementing stock with LineItems / VariantOverrides.
|
||||
# The following methods re-instate the pre-Rails-5 versions, which work as expected.
|
||||
# https://apidock.com/rails/v4.2.9/ActiveRecord/Persistence/increment%21
|
||||
# https://apidock.com/rails/v4.2.9/ActiveRecord/Persistence/decrement%21
|
||||
|
||||
module LineItemStockChanges
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def increment!(attribute, by = 1)
|
||||
increment(attribute, by).update_attribute(attribute, self[attribute])
|
||||
end
|
||||
|
||||
def decrement!(attribute, by = 1)
|
||||
decrement(attribute, by).update_attribute(attribute, self[attribute])
|
||||
end
|
||||
end
|
||||
@@ -4,8 +4,34 @@
|
||||
#
|
||||
# Here we store keys and links to access the app.
|
||||
class ConnectedApp < ApplicationRecord
|
||||
TYPES = ['discover_regen', 'affiliate_sales_data'].freeze
|
||||
|
||||
belongs_to :enterprise
|
||||
after_destroy :disconnect
|
||||
|
||||
scope :discover_regen, -> { where(type: "ConnectedApp") }
|
||||
scope :affiliate_sales_data, -> { where(type: "ConnectedApps::AffiliateSalesData") }
|
||||
|
||||
scope :connecting, -> { where(data: nil) }
|
||||
scope :ready, -> { where.not(data: nil) }
|
||||
|
||||
def connecting?
|
||||
data.nil?
|
||||
end
|
||||
|
||||
def ready?
|
||||
!connecting?
|
||||
end
|
||||
|
||||
def connect(api_key:, channel:)
|
||||
ConnectAppJob.perform_later(self, api_key, channel:)
|
||||
end
|
||||
|
||||
def disconnect
|
||||
WebhookDeliveryJob.perform_later(
|
||||
data["destroy"],
|
||||
"disconnect-app",
|
||||
nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
13
app/models/connected_apps/affiliate_sales_data.rb
Normal file
13
app/models/connected_apps/affiliate_sales_data.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# An enterprise can opt-in for their data to be included in the affiliate_sales_data endpoint
|
||||
#
|
||||
module ConnectedApps
|
||||
class AffiliateSalesData < ConnectedApp
|
||||
def connect(_opts)
|
||||
update! data: true # not-nil value indicates it is ready
|
||||
end
|
||||
|
||||
def disconnect; end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
require "mini_magick"
|
||||
|
||||
class Enterprise < ApplicationRecord
|
||||
SELLS = %w(unspecified none own any).freeze
|
||||
ENTERPRISE_SEARCH_RADIUS = 100
|
||||
@@ -39,13 +41,13 @@ class Enterprise < ApplicationRecord
|
||||
class_name: 'EnterpriseGroup'
|
||||
has_many :producer_properties, foreign_key: 'producer_id', dependent: :destroy
|
||||
has_many :properties, through: :producer_properties
|
||||
has_many :supplied_products, class_name: 'Spree::Product',
|
||||
foreign_key: 'supplier_id',
|
||||
dependent: :destroy
|
||||
has_many :supplied_variants, through: :supplied_products, source: :variants
|
||||
has_many :supplied_variants,
|
||||
class_name: 'Spree::Variant', foreign_key: 'supplier_id', dependent: :destroy
|
||||
has_many :supplied_products, through: :supplied_variants, source: :product
|
||||
has_many :distributed_orders, class_name: 'Spree::Order',
|
||||
foreign_key: 'distributor_id',
|
||||
dependent: :restrict_with_exception
|
||||
|
||||
belongs_to :address, class_name: 'Spree::Address'
|
||||
belongs_to :business_address, optional: true, class_name: 'Spree::Address', dependent: :destroy
|
||||
has_many :enterprise_fees, dependent: :restrict_with_exception
|
||||
@@ -167,7 +169,7 @@ class Enterprise < ApplicationRecord
|
||||
scope :is_distributor, -> { where.not(sells: 'none') }
|
||||
scope :is_hub, -> { where(sells: 'any') }
|
||||
scope :supplying_variant_in, lambda { |variants|
|
||||
joins(supplied_products: :variants).
|
||||
joins(:supplied_variants).
|
||||
where(spree_variants: { id: variants }).
|
||||
select('DISTINCT enterprises.*')
|
||||
}
|
||||
@@ -205,14 +207,14 @@ class Enterprise < ApplicationRecord
|
||||
select('DISTINCT enterprises.*')
|
||||
}
|
||||
|
||||
scope :distributing_products, lambda { |product_ids|
|
||||
scope :distributing_variants, lambda { |variants_ids|
|
||||
exchanges = joins("
|
||||
INNER JOIN exchanges
|
||||
ON (exchanges.receiver_id = enterprises.id AND exchanges.incoming = 'f')
|
||||
ON (exchanges.receiver_id = enterprises.id AND exchanges.incoming = false)
|
||||
").
|
||||
joins('INNER JOIN exchange_variants ON (exchange_variants.exchange_id = exchanges.id)').
|
||||
joins('INNER JOIN spree_variants ON (spree_variants.id = exchange_variants.variant_id)').
|
||||
where(spree_variants: { product_id: product_ids }).select('DISTINCT enterprises.id')
|
||||
where(spree_variants: { id: variants_ids }).select('DISTINCT enterprises.id')
|
||||
|
||||
where(id: exchanges)
|
||||
}
|
||||
@@ -427,10 +429,9 @@ class Enterprise < ApplicationRecord
|
||||
test_permalink = UrlGenerator.to_url(test_permalink)
|
||||
test_permalink = "my-enterprise" if test_permalink.blank?
|
||||
existing = Enterprise.
|
||||
select(:permalink).
|
||||
order(:permalink).
|
||||
where("permalink LIKE ?", "#{test_permalink}%").
|
||||
map(&:permalink)
|
||||
pluck(:permalink)
|
||||
|
||||
if existing.include?(test_permalink)
|
||||
used_indices = existing.map do |p|
|
||||
@@ -598,7 +599,7 @@ class Enterprise < ApplicationRecord
|
||||
# Touch distributors without them touching their distributors.
|
||||
# We avoid an infinite loop and don't need to touch the whole distributor tree.
|
||||
def touch_distributors
|
||||
Enterprise.distributing_products(supplied_products.select(:id)).
|
||||
Enterprise.distributing_variants(supplied_variants.select(:id)).
|
||||
where.not(enterprises: { id: }).
|
||||
update_all(updated_at: Time.zone.now)
|
||||
end
|
||||
|
||||
@@ -108,6 +108,6 @@ class EnterpriseRelationship < ApplicationRecord
|
||||
|
||||
def child_variant_overrides
|
||||
VariantOverride.unscoped.for_hubs(child)
|
||||
.joins(variant: :product).where(spree_products: { supplier_id: parent })
|
||||
.joins(:variant).where(spree_variants: { supplier_id: parent } )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,6 +24,7 @@ class OrderCycle < ApplicationRecord
|
||||
where incoming: false
|
||||
}, class_name: "Exchange", dependent: :destroy
|
||||
|
||||
has_many :orders, class_name: 'Spree::Order', dependent: :restrict_with_exception
|
||||
has_many :suppliers, -> { distinct }, source: :sender, through: :cached_incoming_exchanges
|
||||
has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges
|
||||
has_many :order_cycle_schedules, dependent: :destroy
|
||||
|
||||
@@ -54,10 +54,7 @@ module ProductImport
|
||||
if settings.importing_into_inventory?
|
||||
VariantOverride.for_hubs([enterprise_id]).count
|
||||
else
|
||||
Spree::Variant.
|
||||
joins(:product).
|
||||
where(spree_products: { supplier_id: enterprise_id }).
|
||||
count
|
||||
Spree::Variant.where(supplier_id: enterprise_id).count
|
||||
end
|
||||
|
||||
@enterprise_products[enterprise_id] = products_count
|
||||
@@ -165,11 +162,10 @@ module ProductImport
|
||||
return
|
||||
end
|
||||
|
||||
product = Spree::Product.new
|
||||
product = Spree::Product.new(supplier_id: entry.enterprise_id)
|
||||
product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
product.supplier_id = entry.producer_id
|
||||
|
||||
if product.save
|
||||
ensure_variant_updated(product, entry)
|
||||
@@ -228,10 +224,13 @@ module ProductImport
|
||||
# Ensure attributes are correctly copied to a new product's variant
|
||||
variant = product.variants.first
|
||||
variant.display_name = entry.display_name if entry.display_name
|
||||
variant.import_date = @import_time
|
||||
variant.supplier_id = entry.producer_id
|
||||
variant.save
|
||||
|
||||
# on_demand and on_hand require a stock level, which is created after the variant is created
|
||||
variant.on_demand = entry.on_demand if entry.on_demand
|
||||
variant.on_hand = entry.on_hand if entry.on_hand
|
||||
variant.import_date = @import_time
|
||||
variant.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,6 +73,7 @@ module ProductImport
|
||||
# Variant needs a product. Product needs to be assigned first in order for
|
||||
# delegate to work. name= will fail otherwise.
|
||||
new_variant = Spree::Variant.new(product_id:, **variant_attributes)
|
||||
new_variant.supplier_id = entry.producer_id
|
||||
|
||||
new_variant.save
|
||||
if new_variant.persisted?
|
||||
@@ -287,9 +288,7 @@ module ProductImport
|
||||
end
|
||||
|
||||
def inventory_validation(entry)
|
||||
products = Spree::Product.where(supplier_id: entry.producer_id,
|
||||
name: entry.name,
|
||||
deleted_at: nil)
|
||||
products = Spree::Product.in_supplier(entry.producer_id).where(name: entry.name)
|
||||
|
||||
if products.empty?
|
||||
mark_as_invalid(entry, attribute: 'name',
|
||||
@@ -358,9 +357,7 @@ module ProductImport
|
||||
end
|
||||
|
||||
def product_validation(entry)
|
||||
products = Spree::Product.where(supplier_id: entry.enterprise_id,
|
||||
name: entry.name,
|
||||
deleted_at: nil)
|
||||
products = Spree::Product.in_supplier(entry.enterprise_id).where(name: entry.name)
|
||||
|
||||
if products.empty?
|
||||
mark_as_new_product(entry)
|
||||
@@ -380,11 +377,10 @@ module ProductImport
|
||||
end
|
||||
|
||||
def mark_as_new_product(entry)
|
||||
new_product = Spree::Product.new
|
||||
new_product = Spree::Product.new(supplier_id: entry.enterprise_id)
|
||||
new_product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
new_product.supplier_id = entry.producer_id
|
||||
entry.on_hand = 0 if entry.on_hand.nil?
|
||||
|
||||
if new_product.valid?
|
||||
|
||||
@@ -287,8 +287,6 @@ module ProductImport
|
||||
end
|
||||
|
||||
def delete_uploaded_file
|
||||
return unless @file.path == Rails.root.join("tmp/product_import").to_s
|
||||
|
||||
File.delete(@file)
|
||||
end
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ module Spree
|
||||
can [:index, :read], StockLocation
|
||||
can [:index, :read], StockMovement
|
||||
can [:index, :read], Taxon
|
||||
can [:index, :read], Taxonomy
|
||||
can [:index, :read], Variant
|
||||
can [:index, :read], Zone
|
||||
end
|
||||
@@ -189,20 +188,22 @@ module Spree
|
||||
:seo, :group_buy_options,
|
||||
:bulk_update, :clone, :delete,
|
||||
:destroy], Spree::Product do |product|
|
||||
OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? product.supplier
|
||||
OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include?(
|
||||
product.variants.first.supplier
|
||||
)
|
||||
end
|
||||
|
||||
can [:admin, :index, :bulk_update, :destroy, :destroy_variant], :products_v3
|
||||
can [:admin, :index, :bulk_update, :destroy, :destroy_variant, :clone], :products_v3
|
||||
|
||||
can [:create], Spree::Variant
|
||||
can [:admin, :index, :read, :edit,
|
||||
:update, :search, :delete, :destroy], Spree::Variant do |variant|
|
||||
OpenFoodNetwork::Permissions.new(user).
|
||||
managed_product_enterprises.include? variant.product.supplier
|
||||
managed_product_enterprises.include? variant.supplier
|
||||
end
|
||||
|
||||
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
|
||||
next false unless vo.hub.present? && vo.variant&.product&.supplier.present?
|
||||
next false unless vo.hub.present? && vo.variant&.supplier.present?
|
||||
|
||||
hub_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_hubs.
|
||||
@@ -210,14 +211,14 @@ module Spree
|
||||
|
||||
producer_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_producers.
|
||||
include? vo.variant.product.supplier
|
||||
include? vo.variant.supplier
|
||||
|
||||
hub_auth && producer_auth
|
||||
end
|
||||
|
||||
can [:admin, :create, :update], InventoryItem do |ii|
|
||||
next false unless ii.enterprise.present? &&
|
||||
ii.variant&.product&.supplier.present?
|
||||
ii.variant&.supplier.present?
|
||||
|
||||
hub_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_hubs.
|
||||
@@ -225,7 +226,7 @@ module Spree
|
||||
|
||||
producer_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_producers.
|
||||
include? ii.variant.product.supplier
|
||||
include? ii.variant.supplier
|
||||
|
||||
hub_auth && producer_auth
|
||||
end
|
||||
|
||||
@@ -139,5 +139,8 @@ module Spree
|
||||
|
||||
# Available units
|
||||
preference :available_units, :string, default: "g,kg,T,mL,L,kL"
|
||||
|
||||
# Connected Apps
|
||||
preference :connected_apps_enabled, :string, default: nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "mini_magick"
|
||||
|
||||
module Spree
|
||||
class Image < Asset
|
||||
has_one_attached :attachment, service: image_service do |attachment|
|
||||
|
||||
@@ -5,7 +5,6 @@ require 'open_food_network/scope_variant_to_hub'
|
||||
module Spree
|
||||
class LineItem < ApplicationRecord
|
||||
include VariantUnits::VariantAndLineItemNaming
|
||||
include LineItemStockChanges
|
||||
|
||||
searchable_attributes :price, :quantity, :order_id, :variant_id, :tax_category_id
|
||||
searchable_associations :order, :order_cycle, :variant, :product, :supplier, :tax_category
|
||||
@@ -16,7 +15,7 @@ module Spree
|
||||
|
||||
belongs_to :variant, -> { with_deleted }, class_name: "Spree::Variant"
|
||||
has_one :product, through: :variant
|
||||
has_one :supplier, through: :product
|
||||
has_one :supplier, through: :variant
|
||||
belongs_to :tax_category, class_name: "Spree::TaxCategory", optional: true
|
||||
|
||||
has_many :adjustments, as: :adjustable, dependent: :destroy
|
||||
@@ -85,13 +84,11 @@ module Spree
|
||||
where(order_cycles: { id: order_cycle })
|
||||
}
|
||||
|
||||
# Here we are simply joining the line item to its variant and product
|
||||
# We dont use joins here to avoid the default scopes,
|
||||
# and with that, include deleted variants and deleted products
|
||||
# Here we are simply joining the line item to its variant
|
||||
# We dont use joins here to avoid the default scopes, and with that, include deleted variants
|
||||
scope :supplied_by_any, lambda { |enterprises|
|
||||
product_ids = Spree::Product.unscoped.where(supplier_id: enterprises).select(:id)
|
||||
variant_ids = Spree::Variant.unscoped.where(product_id: product_ids).select(:id)
|
||||
where(spree_line_items: { variant_id: variant_ids })
|
||||
variant_ids = Spree::Variant.unscoped.where(supplier: enterprises).select(:id)
|
||||
where(variant_id: variant_ids)
|
||||
}
|
||||
|
||||
scope :with_tax, -> {
|
||||
|
||||
@@ -5,19 +5,15 @@ require 'open_food_network/property_merge'
|
||||
# PRODUCTS
|
||||
# Products represent an entity for sale in a store.
|
||||
# Products can have variations, called variants
|
||||
# Products properties include description, permalink, availability,
|
||||
# shipping category, etc. that do not change by variant.
|
||||
#
|
||||
# MASTER VARIANT
|
||||
# Every product has one master variant, which stores master price and sku, size and weight, etc.
|
||||
# Price, SKU, size, weight, etc. are all delegated to the master variant.
|
||||
# Contains on_hand inventory levels only when there are no variants for the product.
|
||||
# Products properties include description, meta_keywork, etc. that do not change by variant.
|
||||
#
|
||||
# VARIANTS
|
||||
# All variants can access the product properties directly (via reverse delegation).
|
||||
# Every product has at least one variant (standard variant), which stores price and availability,
|
||||
# shipping category, sku, size and weight, etc.
|
||||
# All variants can access the product name, description, and meta_keyword directly (via reverse
|
||||
# delegation).
|
||||
# Inventory units are tied to Variant.
|
||||
# The master variant can have inventory units, but not option values.
|
||||
# All other variants have option values and may have inventory units.
|
||||
# All variants have option values and may have inventory units.
|
||||
# Sum of on_hand each variant's inventory level determine "on_hand" level for the product.
|
||||
#
|
||||
module Spree
|
||||
@@ -26,15 +22,14 @@ module Spree
|
||||
include LogDestroyPerformer
|
||||
|
||||
self.belongs_to_required_by_default = false
|
||||
self.ignored_columns += [:supplier_id]
|
||||
|
||||
acts_as_paranoid
|
||||
|
||||
searchable_attributes :supplier_id, :meta_keywords, :sku
|
||||
searchable_associations :supplier, :properties, :variants
|
||||
searchable_attributes :meta_keywords, :sku
|
||||
searchable_associations :properties, :variants
|
||||
searchable_scopes :active, :with_properties
|
||||
|
||||
belongs_to :supplier, class_name: 'Enterprise', optional: false, touch: true
|
||||
|
||||
has_one :image, class_name: "Spree::Image", as: :viewable, dependent: :destroy
|
||||
|
||||
has_many :product_properties, dependent: :destroy
|
||||
@@ -45,7 +40,6 @@ module Spree
|
||||
has_many :prices, -> { order('spree_variants.id, currency') }, through: :variants
|
||||
|
||||
has_many :stock_items, through: :variants
|
||||
has_many :supplier_properties, through: :supplier, source: :properties
|
||||
has_many :variant_images, -> { order(:position) }, source: :images,
|
||||
through: :variants
|
||||
|
||||
@@ -68,28 +62,28 @@ module Spree
|
||||
accepts_nested_attributes_for :image
|
||||
accepts_nested_attributes_for :product_properties,
|
||||
allow_destroy: true,
|
||||
reject_if: lambda { |pp| pp[:property_name].blank? }
|
||||
reject_if: ->(pp) { pp[:property_name].blank? }
|
||||
|
||||
# Transient attributes used temporarily when creating a new product,
|
||||
# these values are persisted on the product's variant
|
||||
attr_accessor :price, :display_as, :unit_value, :unit_description, :tax_category_id,
|
||||
:shipping_category_id, :primary_taxon_id
|
||||
:shipping_category_id, :primary_taxon_id, :supplier_id
|
||||
|
||||
after_validation :validate_variant_attrs, on: :create
|
||||
after_create :ensure_standard_variant
|
||||
after_update :touch_supplier, if: :saved_change_to_primary_taxon_id?
|
||||
around_destroy :destruction
|
||||
after_save :update_units
|
||||
after_touch :touch_supplier
|
||||
|
||||
# -- Scopes
|
||||
scope :with_properties, ->(*property_ids) {
|
||||
left_outer_joins(:product_properties).
|
||||
left_outer_joins(:supplier_properties).
|
||||
where(inherits_properties: true).
|
||||
where(producer_properties: { property_id: property_ids }).
|
||||
or(
|
||||
where(spree_product_properties: { property_id: property_ids })
|
||||
)
|
||||
where(spree_product_properties: { property_id: property_ids })
|
||||
}
|
||||
|
||||
scope :with_order_cycles_outer, -> {
|
||||
scope :with_order_cycles_outer, lambda {
|
||||
joins("
|
||||
LEFT OUTER JOIN spree_variants AS o_spree_variants
|
||||
ON (o_spree_variants.product_id = spree_products.id)").
|
||||
@@ -111,9 +105,7 @@ module Spree
|
||||
where(import_date: import_date.all_day))
|
||||
}
|
||||
|
||||
scope :with_order_cycles_inner, -> {
|
||||
joins(variants: { exchanges: :order_cycle })
|
||||
}
|
||||
scope :with_order_cycles_inner, -> { joins(variants: { exchanges: :order_cycle }) }
|
||||
|
||||
scope :visible_for, lambda { |enterprise|
|
||||
joins('
|
||||
@@ -126,8 +118,9 @@ module Spree
|
||||
distinct
|
||||
}
|
||||
|
||||
# -- Scopes
|
||||
scope :in_supplier, lambda { |supplier| where(supplier_id: supplier) }
|
||||
scope :in_supplier, lambda { |supplier|
|
||||
distinct.joins(:variants).where(spree_variants: { supplier: })
|
||||
}
|
||||
|
||||
# Products distributed via the given distributor through an OC
|
||||
scope :in_distributor, lambda { |distributor|
|
||||
@@ -144,18 +137,6 @@ module Spree
|
||||
distinct
|
||||
}
|
||||
|
||||
# Products supplied by a given enterprise or distributed via that enterprise through an OC
|
||||
scope :in_supplier_or_distributor, lambda { |enterprise|
|
||||
enterprise = enterprise.respond_to?(:id) ? enterprise.id : enterprise.to_i
|
||||
|
||||
with_order_cycles_outer.
|
||||
where("
|
||||
spree_products.supplier_id = ?
|
||||
OR (o_exchanges.incoming = ? AND o_exchanges.receiver_id = ?)
|
||||
", enterprise, false, enterprise).
|
||||
select('distinct spree_products.*')
|
||||
}
|
||||
|
||||
# Products distributed by the given order cycle
|
||||
scope :in_order_cycle, lambda { |order_cycle|
|
||||
with_order_cycles_inner.
|
||||
@@ -170,27 +151,17 @@ module Spree
|
||||
where.not(order_cycles: { id: nil })
|
||||
}
|
||||
|
||||
scope :by_producer, -> { joins(:supplier).order('enterprises.name') }
|
||||
scope :by_name, -> { order('name') }
|
||||
scope :by_producer, -> { joins(variants: :supplier).order('enterprises.name') }
|
||||
scope :by_name, -> { order('spree_products.name') }
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
where(nil)
|
||||
else
|
||||
where(supplier_id: user.enterprises.select("enterprises.id"))
|
||||
in_supplier(user.enterprises)
|
||||
end
|
||||
}
|
||||
|
||||
scope :stockable_by, lambda { |enterprise|
|
||||
return where('1=0') if enterprise.blank?
|
||||
|
||||
permitted_producer_ids = EnterpriseRelationship.joins(:parent).permitting(enterprise.id)
|
||||
.with_permission(:add_to_order_cycle)
|
||||
.where(enterprises: { is_primary_producer: true })
|
||||
.pluck(:parent_id)
|
||||
where(spree_products: { supplier_id: [enterprise.id] | permitted_producer_ids })
|
||||
}
|
||||
|
||||
scope :active, lambda { where(spree_products: { deleted_at: nil }) }
|
||||
|
||||
def self.group_by_products_id
|
||||
@@ -236,7 +207,10 @@ module Spree
|
||||
ps = product_properties.all
|
||||
|
||||
if inherits_properties
|
||||
ps = OpenFoodNetwork::PropertyMerge.merge(ps, supplier.producer_properties)
|
||||
# NOTE: Set the supplier as the first variant supplier. If variants have different supplier,
|
||||
# result might not be correct
|
||||
supplier = variants.first.supplier
|
||||
ps = OpenFoodNetwork::PropertyMerge.merge(ps, supplier&.producer_properties || [])
|
||||
end
|
||||
|
||||
ps.
|
||||
@@ -263,8 +237,6 @@ module Spree
|
||||
|
||||
def destruction
|
||||
transaction do
|
||||
touch_distributors
|
||||
|
||||
ExchangeVariant.
|
||||
where(exchange_variants: { variant_id: variants.with_deleted.
|
||||
select(:id) }).destroy_all
|
||||
@@ -285,6 +257,7 @@ module Spree
|
||||
variant.tax_category_id = tax_category_id
|
||||
variant.shipping_category_id = shipping_category_id
|
||||
variant.primary_taxon_id = primary_taxon_id
|
||||
variant.supplier_id = supplier_id
|
||||
variants << variant
|
||||
end
|
||||
|
||||
@@ -292,6 +265,7 @@ module Spree
|
||||
def variant_unit_with_scale
|
||||
scale_clean = ActiveSupport::NumberHelper.number_to_rounded(variant_unit_scale,
|
||||
precision: nil,
|
||||
significant: false,
|
||||
strip_insignificant_zeros: true)
|
||||
[variant_unit, scale_clean].compact_blank.join("_")
|
||||
end
|
||||
@@ -316,14 +290,44 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def validate_variant_attrs
|
||||
# Avoid running validation when we can't set variant attrs
|
||||
# eg clone product. Will raise error if clonning a product with no variant
|
||||
return if variants.first&.valid?
|
||||
|
||||
unless Spree::Taxon.find_by(id: primary_taxon_id)
|
||||
errors.add(:primary_taxon_id,
|
||||
I18n.t('activerecord.errors.models.spree/product.must_exist'))
|
||||
end
|
||||
return if Enterprise.find_by(id: supplier_id)
|
||||
|
||||
errors.add(:supplier_id,
|
||||
I18n.t('activerecord.errors.models.spree/product.must_exist'))
|
||||
end
|
||||
|
||||
def update_units
|
||||
return unless saved_change_to_variant_unit? || saved_change_to_variant_unit_name?
|
||||
|
||||
variants.each(&:update_units)
|
||||
variants.each do |v|
|
||||
if v.persisted?
|
||||
v.update_units
|
||||
else
|
||||
v.assign_units
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def touch_distributors
|
||||
Enterprise.distributing_products(id).each(&:touch)
|
||||
def touch_supplier
|
||||
return if variants.empty?
|
||||
|
||||
# Assume the product supplier is the supplier of the first variant
|
||||
# Will breack if product has mutiple variants with different supplier
|
||||
first_variant = variants.first
|
||||
|
||||
# The variant is invalid if no supplier is present, but this method can be triggered when
|
||||
# importing product. In this scenario the variant has not been updated with the supplier yet
|
||||
# hence the check.
|
||||
first_variant.supplier.touch if first_variant.supplier.present?
|
||||
end
|
||||
|
||||
def validate_image
|
||||
|
||||
@@ -6,6 +6,8 @@ module Spree
|
||||
has_many :products, through: :product_properties
|
||||
has_many :producer_properties, dependent: :destroy
|
||||
|
||||
after_touch :touch_producer_properties
|
||||
|
||||
validates :name, :presentation, presence: true
|
||||
|
||||
scope :sorted, -> { order(:name) }
|
||||
@@ -13,5 +15,11 @@ module Spree
|
||||
def property
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def touch_producer_properties
|
||||
producer_properties.each(&:touch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,19 +2,11 @@
|
||||
|
||||
module Spree
|
||||
class Taxon < ApplicationRecord
|
||||
self.belongs_to_required_by_default = false
|
||||
|
||||
acts_as_nested_set dependent: :destroy
|
||||
|
||||
belongs_to :taxonomy, class_name: 'Spree::Taxonomy', touch: true
|
||||
|
||||
has_many :variants, class_name: "Spree::Variant", foreign_key: "primary_taxon_id",
|
||||
inverse_of: :primary_taxon, dependent: :restrict_with_error
|
||||
|
||||
has_many :products, through: :variants, dependent: nil
|
||||
|
||||
before_create :set_permalink
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
# Indicate which filters should be used for this taxon
|
||||
@@ -31,33 +23,13 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
def set_permalink
|
||||
if parent.present?
|
||||
self.permalink = [parent.permalink, permalink_end].join('/')
|
||||
elsif permalink.blank?
|
||||
self.permalink = UrlGenerator.to_url(name)
|
||||
end
|
||||
end
|
||||
|
||||
# For #2759
|
||||
def to_param
|
||||
permalink
|
||||
end
|
||||
|
||||
def pretty_name
|
||||
ancestor_chain = ancestors.inject("") do |name, ancestor|
|
||||
name + "#{ancestor.name} -> "
|
||||
end
|
||||
ancestor_chain + name.to_s
|
||||
end
|
||||
|
||||
# Find all the taxons of supplied products for each enterprise, indexed by enterprise.
|
||||
# Format: {enterprise_id => [taxon_id, ...]}
|
||||
def self.supplied_taxons
|
||||
taxons = {}
|
||||
|
||||
Spree::Taxon.
|
||||
joins(products: :supplier).
|
||||
joins(variants: :supplier).
|
||||
select('spree_taxons.*, enterprises.id AS enterprise_id').
|
||||
each do |t|
|
||||
taxons[t.enterprise_id.to_i] ||= Set.new
|
||||
@@ -90,13 +62,5 @@ module Spree
|
||||
ts[t.enterprise_id.to_i] << t.id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def permalink_end
|
||||
return UrlGenerator.to_url(name) if permalink.blank?
|
||||
|
||||
permalink.split('/').last
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
class Taxonomy < ApplicationRecord
|
||||
validates :name, presence: true
|
||||
|
||||
has_many :taxons, dependent: :nullify
|
||||
has_one :root, -> { where parent_id: nil }, class_name: "Spree::Taxon", dependent: :destroy
|
||||
|
||||
after_save :set_name
|
||||
|
||||
default_scope -> { order("#{table_name}.position") }
|
||||
|
||||
private
|
||||
|
||||
def set_name
|
||||
if root
|
||||
root.update_columns(
|
||||
name:,
|
||||
updated_at: Time.zone.now
|
||||
)
|
||||
else
|
||||
self.root = Taxon.create!(taxonomy_id: id, name:)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -156,6 +156,12 @@ module Spree
|
||||
self.disabled_at = value == '1' ? Time.zone.now : nil
|
||||
end
|
||||
|
||||
def affiliate_enterprises
|
||||
return [] unless Flipper.enabled?(:affiliate_sales_data, self)
|
||||
|
||||
Enterprise.joins(:connected_apps).merge(ConnectedApps::AffiliateSalesData.ready)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def password_required?
|
||||
|
||||
@@ -13,8 +13,8 @@ module Spree
|
||||
|
||||
acts_as_paranoid
|
||||
|
||||
searchable_attributes :sku, :display_as, :display_name, :primary_taxon_id
|
||||
searchable_associations :product, :default_price, :primary_taxon
|
||||
searchable_attributes :sku, :display_as, :display_name, :primary_taxon_id, :supplier_id
|
||||
searchable_associations :product, :default_price, :primary_taxon, :supplier
|
||||
searchable_scopes :active, :deleted
|
||||
|
||||
NAME_FIELDS = ["display_name", "display_as", "weight", "unit_value", "unit_description"].freeze
|
||||
@@ -23,12 +23,15 @@ module Spree
|
||||
meta_keywords
|
||||
variants_display_as
|
||||
variants_display_name
|
||||
supplier_name).join('_or_')}_cont".freeze
|
||||
variants_supplier_name).join('_or_')}_cont".freeze
|
||||
|
||||
belongs_to :product, -> { with_deleted }, touch: true, class_name: 'Spree::Product'
|
||||
belongs_to :product, -> {
|
||||
with_deleted
|
||||
}, touch: true, class_name: 'Spree::Product', optional: false
|
||||
belongs_to :tax_category, class_name: 'Spree::TaxCategory'
|
||||
belongs_to :shipping_category, class_name: 'Spree::ShippingCategory', optional: false
|
||||
belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true, optional: false
|
||||
belongs_to :supplier, class_name: 'Enterprise', optional: false, touch: true
|
||||
|
||||
delegate :name, :name=, :description, :description=, :meta_keywords, to: :product
|
||||
|
||||
@@ -58,6 +61,7 @@ module Spree
|
||||
has_many :variant_overrides, dependent: :destroy
|
||||
has_many :inventory_items, dependent: :destroy
|
||||
has_many :semantic_links, dependent: :delete_all
|
||||
has_many :supplier_properties, through: :supplier, source: :properties
|
||||
|
||||
localize_number :price, :weight
|
||||
|
||||
@@ -65,7 +69,7 @@ module Spree
|
||||
validate :check_currency
|
||||
validates :price, numericality: { greater_than_or_equal_to: 0 }, presence: true
|
||||
validates :tax_category, presence: true,
|
||||
if: proc { Spree::Config[:products_require_tax_category] }
|
||||
if: proc { Spree::Config.products_require_tax_category }
|
||||
|
||||
validates :unit_value, presence: true, if: ->(variant) {
|
||||
%w(weight volume).include?(variant.product&.variant_unit)
|
||||
@@ -83,7 +87,6 @@ module Spree
|
||||
before_validation :ensure_unit_value
|
||||
before_validation :update_weight_from_unit_value, if: ->(v) { v.product.present? }
|
||||
before_validation :convert_variant_weight_to_decimal
|
||||
before_validation :assign_related_taxon, if: ->(v) { v.primary_taxon.blank? }
|
||||
|
||||
before_save :assign_units, if: ->(variant) {
|
||||
variant.new_record? || variant.changed_attributes.keys.intersection(NAME_FIELDS).any?
|
||||
@@ -94,7 +97,7 @@ module Spree
|
||||
after_save :save_default_price
|
||||
|
||||
# default variant scope only lists non-deleted variants
|
||||
scope :deleted, lambda { where.not(deleted_at: nil) }
|
||||
scope :deleted, -> { where.not(deleted_at: nil) }
|
||||
|
||||
scope :with_order_cycles_inner, -> { joins(exchanges: :order_cycle) }
|
||||
|
||||
@@ -139,11 +142,9 @@ module Spree
|
||||
.where("o_inventory_items.id IS NULL OR o_inventory_items.visible = (?)", true)
|
||||
}
|
||||
|
||||
scope :stockable_by, lambda { |enterprise|
|
||||
return where("1=0") if enterprise.blank?
|
||||
|
||||
joins(:product).
|
||||
where(spree_products: { id: Spree::Product.stockable_by(enterprise).select(:id) })
|
||||
scope :with_properties, lambda { |property_ids|
|
||||
left_outer_joins(:supplier_properties).
|
||||
where(producer_properties: { property_id: property_ids })
|
||||
}
|
||||
|
||||
# Define sope as class method to allow chaining with other scopes filtering id.
|
||||
@@ -215,10 +216,6 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def assign_related_taxon
|
||||
self.primary_taxon ||= product.variants.last&.primary_taxon
|
||||
end
|
||||
|
||||
def check_currency
|
||||
return unless currency.nil?
|
||||
|
||||
@@ -250,8 +247,16 @@ module Spree
|
||||
end
|
||||
|
||||
def destruction
|
||||
exchange_variants.reload.destroy_all
|
||||
yield
|
||||
transaction do
|
||||
# Even tough Enterprise will touch associated variant distributors when touched,
|
||||
# the variant will be removed from the exchange by the time it's triggered,
|
||||
# so it won't be able to find the deleted variant's distributors.
|
||||
# This why we do it here
|
||||
touch_distributors
|
||||
|
||||
exchange_variants.reload.destroy_all
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_unit_value
|
||||
@@ -268,5 +273,9 @@ module Spree
|
||||
def convert_variant_weight_to_decimal
|
||||
self.weight = weight.to_d
|
||||
end
|
||||
|
||||
def touch_distributors
|
||||
Enterprise.distributing_variants(id).each(&:touch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -52,11 +52,14 @@ class VariantOverride < ApplicationRecord
|
||||
return
|
||||
end
|
||||
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
# Cf. conversation https://github.com/openfoodfoundation/openfoodnetwork/pull/12647
|
||||
if quantity > 0
|
||||
increment! :count_on_hand, quantity
|
||||
elsif quantity < 0
|
||||
decrement! :count_on_hand, -quantity
|
||||
end
|
||||
# rubocop:enable Rails/SkipsModelValidations
|
||||
end
|
||||
|
||||
def default_stock?
|
||||
|
||||
@@ -31,16 +31,18 @@ class ProductScopeQuery
|
||||
product_scope.find(@params[:product_id])
|
||||
end
|
||||
|
||||
def paged_products_for_producers
|
||||
def products_for_producers
|
||||
producer_ids = OpenFoodNetwork::Permissions.new(@user).
|
||||
variant_override_producers.by_name.select('enterprises.id')
|
||||
|
||||
# Use `order("enterprises.name")` instead of `by_producer scope`, the scope adds a join
|
||||
# on variants which messes our query
|
||||
Spree::Product.where(nil).
|
||||
merge(product_scope).
|
||||
includes(variants: [:product, :default_price, :stock_items]).
|
||||
where(supplier_id: producer_ids).
|
||||
by_producer.by_name.
|
||||
ransack(@params[:q]).result
|
||||
includes(variants: [:product, :default_price, :stock_items, :supplier]).
|
||||
where(variants: { supplier_id: producer_ids }).
|
||||
order("enterprises.name, spree_products.name").
|
||||
ransack(@params[:q]).result(distinct: true)
|
||||
end
|
||||
|
||||
def product_scope
|
||||
|
||||
@@ -37,9 +37,12 @@ module Admin
|
||||
|
||||
return if notify_if_abn_related_issue(visible_orders)
|
||||
|
||||
file_id = "#{Time.zone.now.to_i}-#{SecureRandom.hex(2)}"
|
||||
|
||||
cable_ready.append(
|
||||
selector: "#orders-index",
|
||||
html: render(partial: "spree/admin/orders/bulk/invoice_modal")
|
||||
html: render(partial: "spree/admin/orders/bulk/invoice_modal",
|
||||
locals: { invoice_url: "/admin/orders/invoices/#{file_id}" })
|
||||
).broadcast
|
||||
|
||||
# Preserve order of bulk_ids.
|
||||
@@ -49,7 +52,7 @@ module Admin
|
||||
|
||||
BulkInvoiceJob.perform_later(
|
||||
visible_order_ids,
|
||||
"tmp/invoices/#{Time.zone.now.to_i}-#{SecureRandom.hex(2)}.pdf",
|
||||
"tmp/invoices/#{file_id}.pdf",
|
||||
channel: SessionChannel.for_request(request),
|
||||
current_user_id: current_user.id
|
||||
)
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProductsReflex < ApplicationReflex
|
||||
include Pagy::Backend
|
||||
|
||||
before_reflex :init_filters_params, :init_pagination_params
|
||||
|
||||
def change_per_page
|
||||
@per_page = element.value.to_i
|
||||
@page = 1
|
||||
|
||||
fetch_and_render_products_with_flash
|
||||
end
|
||||
|
||||
def clear_search
|
||||
@search_term = nil
|
||||
@producer_id = nil
|
||||
@category_id = nil
|
||||
@page = 1
|
||||
|
||||
fetch_and_render_products_with_flash
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_filters_params
|
||||
# params comes from the form
|
||||
# _params comes from the url
|
||||
# priority is given to params from the form (if present) over url params
|
||||
@search_term = params[:search_term] || params[:_search_term]
|
||||
@producer_id = params[:producer_id] || params[:_producer_id]
|
||||
@category_id = params[:category_id] || params[:_category_id]
|
||||
end
|
||||
|
||||
def init_pagination_params
|
||||
# prority is given to element dataset (if present) over url params
|
||||
@page = element.dataset.page || params[:_page] || 1
|
||||
@per_page = element.dataset.perpage || params[:_per_page] || 15
|
||||
end
|
||||
|
||||
def fetch_and_render_products_with_flash
|
||||
fetch_products
|
||||
render_products
|
||||
end
|
||||
|
||||
def render_products
|
||||
cable_ready.replace(
|
||||
selector: "#products-content",
|
||||
html: render(partial: "admin/products_v3/content",
|
||||
locals: { products: @products, pagy: @pagy, search_term: @search_term,
|
||||
producer_options: producers, producer_id: @producer_id,
|
||||
category_options: categories, tax_category_options:,
|
||||
category_id: @category_id, flashes: flash })
|
||||
)
|
||||
|
||||
cable_ready.replace_state(
|
||||
url: current_url,
|
||||
)
|
||||
|
||||
morph :nothing
|
||||
end
|
||||
|
||||
def render_products_form_with_flash
|
||||
locals = { products: @products }
|
||||
locals[:error_counts] = @error_counts if @error_counts.present?
|
||||
locals[:flashes] = flash if flash.any?
|
||||
|
||||
cable_ready.replace(
|
||||
selector: "#products-form",
|
||||
html: render(partial: "admin/products_v3/table", locals:)
|
||||
)
|
||||
morph :nothing
|
||||
|
||||
# dunno why this doesn't work. The HTML stops after the first `<col>` element, wtf?!
|
||||
# morph "#products-form", render(partial: "admin/products_v3/table", locals:)
|
||||
end
|
||||
|
||||
def producers
|
||||
producers = OpenFoodNetwork::Permissions.new(current_user)
|
||||
.managed_product_enterprises.is_primary_producer.by_name
|
||||
producers.map { |p| [p.name, p.id] }
|
||||
end
|
||||
|
||||
def categories
|
||||
Spree::Taxon.order(:name).map { |c| [c.name, c.id] }
|
||||
end
|
||||
|
||||
def tax_category_options
|
||||
Spree::TaxCategory.order(:name).pluck(:name, :id)
|
||||
end
|
||||
|
||||
def fetch_products
|
||||
product_query = OpenFoodNetwork::Permissions.new(current_user)
|
||||
.editable_products.merge(product_scope).ransack(ransack_query).result(distinct: true)
|
||||
@pagy, @products = pagy(product_query.order(:name), items: @per_page, page: @page,
|
||||
size: [1, 2, 2, 1])
|
||||
end
|
||||
|
||||
def product_scope
|
||||
scope = if current_user.has_spree_role?("admin") || current_user.enterprises.present?
|
||||
Spree::Product
|
||||
else
|
||||
Spree::Product.active
|
||||
end
|
||||
|
||||
scope.includes(product_query_includes)
|
||||
end
|
||||
|
||||
def ransack_query
|
||||
query = {}
|
||||
query.merge!(supplier_id_in: @producer_id) if @producer_id.present?
|
||||
if @search_term.present?
|
||||
query.merge!(Spree::Variant::SEARCH_KEY => @search_term)
|
||||
end
|
||||
query.merge!(variants_primary_taxon_id_in: @category_id) if @category_id.present?
|
||||
query
|
||||
end
|
||||
|
||||
# Optimise by pre-loading required columns
|
||||
def product_query_includes
|
||||
# TODO: add other fields used in columns? (eg supplier: [:name])
|
||||
[
|
||||
# variants: [
|
||||
# :default_price,
|
||||
# :stock_locations,
|
||||
# :stock_items,
|
||||
# :variant_overrides
|
||||
# ]
|
||||
]
|
||||
end
|
||||
|
||||
def current_url
|
||||
url = URI(request.original_url)
|
||||
url.query = url.query.present? ? "#{url.query}&" : ""
|
||||
# add params with _ to avoid conflicts with params from the form
|
||||
url.query += "_page=#{@page}"
|
||||
url.query += "&_per_page=#{@per_page}"
|
||||
url.query += "&_search_term=#{@search_term}" if @search_term.present?
|
||||
url.query += "&_producer_id=#{@producer_id}" if @producer_id.present?
|
||||
url.query += "&_category_id=#{@category_id}" if @category_id.present?
|
||||
url.to_s
|
||||
end
|
||||
|
||||
# Similar to spree/admin/products_controller
|
||||
def product_set_from_params
|
||||
# Form field names:
|
||||
# '[products][0][id]' (hidden field)
|
||||
# '[products][0][name]'
|
||||
# '[products][0][variants_attributes][0][id]' (hidden field)
|
||||
# '[products][0][variants_attributes][0][display_name]'
|
||||
#
|
||||
# Resulting in params:
|
||||
# "products" => {
|
||||
# "0" => {
|
||||
# "id" => "123"
|
||||
# "name" => "Pommes",
|
||||
# "variants_attributes" => {
|
||||
# "0" => {
|
||||
# "id" => "1234",
|
||||
# "display_name" => "Large box",
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
collection_hash = products_bulk_params[:products]
|
||||
.transform_values { |product|
|
||||
# Convert variants_attributes form hash to an array if present
|
||||
product[:variants_attributes] &&= product[:variants_attributes].values
|
||||
product
|
||||
}.with_indifferent_access
|
||||
Sets::ProductSet.new(collection_attributes: collection_hash)
|
||||
end
|
||||
|
||||
def products_bulk_params
|
||||
params.permit(products: ::PermittedAttributes::Product.attributes)
|
||||
.to_h.with_indifferent_access
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,7 @@ module Api
|
||||
attributes :name, :supplier_name, :image_url, :variants
|
||||
|
||||
def supplier_name
|
||||
object.supplier&.name
|
||||
object.variants.first.supplier&.name
|
||||
end
|
||||
|
||||
def image_url
|
||||
|
||||
@@ -9,7 +9,7 @@ module Api
|
||||
has_one :order, serializer: Api::Admin::IdSerializer
|
||||
|
||||
def supplier
|
||||
{ id: object.product.supplier_id }
|
||||
{ id: object.supplier.id }
|
||||
end
|
||||
|
||||
def units_product
|
||||
|
||||
@@ -7,8 +7,6 @@ module Api
|
||||
:inherits_properties, :on_hand, :price, :import_date, :image_url,
|
||||
:thumb_url, :variants
|
||||
|
||||
has_one :supplier, key: :producer_id, embed: :id
|
||||
|
||||
def variants
|
||||
ActiveModel::ArraySerializer.new(
|
||||
object.variants,
|
||||
|
||||
@@ -3,13 +3,9 @@
|
||||
module Api
|
||||
module Admin
|
||||
class ProductSimpleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :producer_id
|
||||
attributes :id, :name
|
||||
|
||||
has_many :variants, key: :variants, serializer: Api::Admin::VariantSimpleSerializer
|
||||
|
||||
def producer_id
|
||||
object.supplier_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
module Api
|
||||
module Admin
|
||||
class TaxonSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :pretty_name
|
||||
attributes :id, :name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,6 +9,7 @@ module Api
|
||||
:price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name
|
||||
|
||||
has_one :primary_taxon, key: :category_id, embed: :id
|
||||
has_one :supplier, key: :producer_id, embed: :id
|
||||
|
||||
def name
|
||||
if object.full_name.present?
|
||||
@@ -31,7 +32,7 @@ module Api
|
||||
end
|
||||
|
||||
def producer_name
|
||||
object.product.supplier.name
|
||||
object.supplier.name
|
||||
end
|
||||
|
||||
def image
|
||||
|
||||
@@ -6,7 +6,7 @@ module Api
|
||||
attributes :id, :name, :import_date,
|
||||
:options_text, :unit_value, :unit_description, :unit_to_display,
|
||||
:display_as, :display_name, :name_to_display,
|
||||
:price, :on_demand, :on_hand
|
||||
:price, :on_demand, :on_hand, :producer_id
|
||||
|
||||
has_many :variant_overrides
|
||||
|
||||
@@ -27,6 +27,10 @@ module Api
|
||||
def price
|
||||
object.price.nil? ? 0.to_f : object.price
|
||||
end
|
||||
|
||||
def producer_id
|
||||
object.supplier_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,6 @@ class Api::ProductSerializer < ActiveModel::Serializer
|
||||
has_many :variants, serializer: Api::VariantSerializer
|
||||
|
||||
has_one :image, serializer: Api::ImageSerializer
|
||||
has_one :supplier, serializer: Api::IdSerializer
|
||||
|
||||
# return an unformatted descripton
|
||||
def description
|
||||
|
||||
@@ -4,5 +4,5 @@ class Api::TaxonSerializer < ActiveModel::Serializer
|
||||
cached
|
||||
delegate :cache_key, to: :object
|
||||
|
||||
attributes :id, :name, :permalink, :pretty_name, :position, :parent_id, :taxonomy_id
|
||||
attributes :id, :name, :permalink, :position
|
||||
end
|
||||
|
||||
@@ -9,6 +9,8 @@ class Api::VariantSerializer < ActiveModel::Serializer
|
||||
:tag_list, :thumb_url,
|
||||
:unit_price_price, :unit_price_unit
|
||||
|
||||
has_one :supplier, serializer: Api::IdSerializer
|
||||
|
||||
delegate :price, to: :object
|
||||
|
||||
def fees
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
class Invoice
|
||||
class ProductSerializer < ActiveModel::Serializer
|
||||
attributes :name
|
||||
has_one :supplier, serializer: Invoice::EnterpriseSerializer
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,5 +4,6 @@ class Invoice
|
||||
class VariantSerializer < ActiveModel::Serializer
|
||||
attributes :id, :display_name, :options_text
|
||||
has_one :product, serializer: Invoice::ProductSerializer
|
||||
has_one :supplier, serializer: Invoice::EnterpriseSerializer
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,7 +30,7 @@ class ExchangeProductsRenderer
|
||||
end
|
||||
|
||||
def supplied_products(enterprises_query_matcher)
|
||||
products_relation = Spree::Product.where(supplier_id: enterprises_query_matcher).order(:name)
|
||||
products_relation = Spree::Product.in_supplier(enterprises_query_matcher).order(:name)
|
||||
|
||||
filter_visible(products_relation)
|
||||
end
|
||||
@@ -95,7 +95,7 @@ class ExchangeProductsRenderer
|
||||
return enterprises if enterprises.empty?
|
||||
|
||||
enterprises.includes(
|
||||
supplied_products: [:supplier, :variants, :image]
|
||||
supplied_products: [{ variants: :supplier }, :image]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ExchangeVariantDeleter
|
||||
def delete(product)
|
||||
ExchangeVariant.
|
||||
where(variant_id: product.variants.select(:id)).
|
||||
delete_all
|
||||
def delete(variant)
|
||||
ExchangeVariant.where(variant_id: variant.id).delete_all
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# The stock-checking includes on_demand and stock level overrides from variant_overrides.
|
||||
|
||||
module OrderCycles
|
||||
class DistributedProductsService
|
||||
class DistributedProductsService # rubocop:disable Metrics/ClassLength
|
||||
def initialize(distributor, order_cycle, customer)
|
||||
@distributor = distributor
|
||||
@order_cycle = order_cycle
|
||||
@@ -12,23 +12,15 @@ module OrderCycles
|
||||
end
|
||||
|
||||
def products_relation
|
||||
Spree::Product.where(id: stocked_products).group("spree_products.id")
|
||||
relation_by_sorting.order(Arel.sql(order))
|
||||
end
|
||||
|
||||
# Joins on the first product variant to allow us to filter product by taxon. This is so
|
||||
# enterprise can display product sorted by category in a custom order on their shopfront.
|
||||
#
|
||||
# Caveat, the category sorting won't work properly if there are multiple variant with different
|
||||
# category for a given product.
|
||||
#
|
||||
def products_taxons_relation
|
||||
Spree::Product.where(id: stocked_products).
|
||||
joins("LEFT JOIN (
|
||||
SELECT DISTINCT ON(product_id) id, product_id, primary_taxon_id
|
||||
FROM spree_variants WHERE deleted_at IS NULL
|
||||
) first_variant ON spree_products.id = first_variant.product_id").
|
||||
select("spree_products.*, first_variant.primary_taxon_id").
|
||||
group("spree_products.id, first_variant.primary_taxon_id")
|
||||
def products_relation_incl_supplier_properties
|
||||
query = relation_by_sorting
|
||||
|
||||
query = supplier_property_join(query)
|
||||
|
||||
query.order(Arel.sql(order))
|
||||
end
|
||||
|
||||
def variants_relation
|
||||
@@ -42,6 +34,81 @@ module OrderCycles
|
||||
|
||||
attr_reader :distributor, :order_cycle, :customer
|
||||
|
||||
def relation_by_sorting
|
||||
query = Spree::Product.where(id: stocked_products)
|
||||
|
||||
if sorting == "by_producer"
|
||||
# Joins on the first product variant to allow us to filter product by supplier. This is so
|
||||
# enterprise can display product sorted by supplier in a custom order on their shopfront.
|
||||
#
|
||||
# Caveat, the supplier sorting won't work properly if there are multiple variant with
|
||||
# different supplier for a given product.
|
||||
query.
|
||||
joins("LEFT JOIN (SELECT DISTINCT ON(product_id) id, product_id, supplier_id
|
||||
FROM spree_variants WHERE deleted_at IS NULL) first_variant
|
||||
ON spree_products.id = first_variant.product_id").
|
||||
select("spree_products.*, first_variant.supplier_id").
|
||||
group("spree_products.id, first_variant.supplier_id")
|
||||
elsif sorting == "by_category"
|
||||
# Joins on the first product variant to allow us to filter product by taxon. # This is so
|
||||
# enterprise can display product sorted by category in a custom order on their shopfront.
|
||||
#
|
||||
# Caveat, the category sorting won't work properly if there are multiple variant with
|
||||
# different category for a given product.
|
||||
query.
|
||||
joins("LEFT JOIN (
|
||||
SELECT DISTINCT ON(product_id) id, product_id, primary_taxon_id,
|
||||
supplier_id
|
||||
FROM spree_variants WHERE deleted_at IS NULL
|
||||
) first_variant ON spree_products.id = first_variant.product_id").
|
||||
select("spree_products.*, first_variant.primary_taxon_id").
|
||||
group("spree_products.id, first_variant.primary_taxon_id")
|
||||
else
|
||||
query.group("spree_products.id")
|
||||
end
|
||||
end
|
||||
|
||||
def sorting
|
||||
distributor.preferred_shopfront_product_sorting_method
|
||||
end
|
||||
|
||||
def sorting_by_producer?
|
||||
sorting == "by_producer" &&
|
||||
distributor.preferred_shopfront_producer_order.present?
|
||||
end
|
||||
|
||||
def sorting_by_category?
|
||||
sorting == "by_category" &&
|
||||
distributor.preferred_shopfront_taxon_order.present?
|
||||
end
|
||||
|
||||
def supplier_property_join(query)
|
||||
query.joins("
|
||||
JOIN enterprises ON enterprises.id = first_variant.supplier_id
|
||||
LEFT OUTER JOIN producer_properties ON producer_properties.producer_id = enterprises.id
|
||||
")
|
||||
end
|
||||
|
||||
def order
|
||||
if sorting_by_producer?
|
||||
order_by_producer = distributor
|
||||
.preferred_shopfront_producer_order
|
||||
.split(",").map { |id| "first_variant.supplier_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
|
||||
"#{order_by_producer}, spree_products.name ASC, spree_products.id ASC"
|
||||
elsif sorting_by_category?
|
||||
order_by_category = distributor
|
||||
.preferred_shopfront_taxon_order
|
||||
.split(",").map { |id| "first_variant.primary_taxon_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
|
||||
"#{order_by_category}, spree_products.name ASC, spree_products.id ASC"
|
||||
else
|
||||
"spree_products.name ASC, spree_products.id"
|
||||
end
|
||||
end
|
||||
|
||||
def stocked_products
|
||||
order_cycle.
|
||||
variants_distributed_by(distributor).
|
||||
|
||||
@@ -83,7 +83,7 @@ module Permissions
|
||||
Spree::Order.with_line_items_variants_and_products_outer.
|
||||
where(
|
||||
distributor_id: granted_distributor_ids,
|
||||
spree_products: { supplier_id: enterprises_with_associated_orders }
|
||||
spree_variants: { supplier_id: enterprises_with_associated_orders }
|
||||
).
|
||||
where_clause.__send__(:predicates).
|
||||
reduce(:and)
|
||||
|
||||
@@ -4,11 +4,11 @@ module PermittedAttributes
|
||||
class Product
|
||||
def self.attributes
|
||||
[
|
||||
:id, :name, :description, :supplier_id, :price,
|
||||
:id, :name, :description, :price,
|
||||
:variant_unit, :variant_unit_scale, :variant_unit_with_scale, :unit_value,
|
||||
:unit_description, :variant_unit_name,
|
||||
:display_as, :sku, :group_buy, :group_buy_unit_size,
|
||||
:taxon_ids, :primary_taxon_id, :tax_category_id,
|
||||
:taxon_ids, :primary_taxon_id, :tax_category_id, :supplier_id,
|
||||
:meta_keywords, :notes, :inherits_properties,
|
||||
{ product_properties_attributes: [:id, :property_name, :value],
|
||||
variants_attributes: [PermittedAttributes::Variant.attributes],
|
||||
|
||||
@@ -7,7 +7,8 @@ module PermittedAttributes
|
||||
:id, :sku, :on_hand, :on_demand, :shipping_category_id,
|
||||
:price, :unit_value, :unit_description,
|
||||
:display_name, :display_as, :tax_category_id,
|
||||
:weight, :height, :width, :depth, :taxon_ids, :primary_taxon_id
|
||||
:weight, :height, :width, :depth, :taxon_ids, :primary_taxon_id,
|
||||
:supplier_id
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
require 'open_food_network/scope_product_to_hub'
|
||||
|
||||
|
||||
class ProductsRenderer
|
||||
include Pagy::Backend
|
||||
|
||||
@@ -34,12 +35,15 @@ class ProductsRenderer
|
||||
return unless order_cycle
|
||||
|
||||
@products ||= begin
|
||||
results = distributed_products.
|
||||
products_taxons_relation.
|
||||
order(Arel.sql(products_order))
|
||||
results = if supplier_properties.present?
|
||||
distributed_products.products_relation_incl_supplier_properties
|
||||
else
|
||||
distributed_products.products_relation
|
||||
end
|
||||
|
||||
filter_and_paginate(results).
|
||||
each { |product| product_scoper.scope(product) } # Scope results with variant_overrides
|
||||
results = filter(results)
|
||||
# Scope results with variant_overrides
|
||||
paginate(results).each { |product| product_scoper.scope(product) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,10 +55,50 @@ class ProductsRenderer
|
||||
OpenFoodNetwork::EnterpriseFeeCalculator.new distributor, order_cycle
|
||||
end
|
||||
|
||||
def filter_and_paginate(query)
|
||||
results = query.ransack(args[:q]).result
|
||||
def filter(query)
|
||||
ransack_results = query.ransack(args[:q]).result.to_a
|
||||
|
||||
_pagy, paginated_results = pagy_arel(
|
||||
return ransack_results if supplier_properties.blank?
|
||||
|
||||
supplier_properties_results = []
|
||||
if supplier_properties.present?
|
||||
# We can't search on an association's scope with ransack, a work around is to define
|
||||
# the a scope on the parent (Spree::Product) but because we are joining on "first_variant"
|
||||
# to get the supplier it doesn't work, so we do the filtering manually here
|
||||
# see:
|
||||
# OrderCycleDistributedProducts#products_relation
|
||||
supplier_properties_results = query.
|
||||
where(producer_properties: { property_id: supplier_property_ids }).
|
||||
where(inherits_properties: true)
|
||||
end
|
||||
|
||||
if supplier_properties_results.present? && with_properties.present?
|
||||
# apply "OR" between property search
|
||||
return ransack_results | supplier_properties_results
|
||||
end
|
||||
|
||||
# Intersect the result to apply "AND" with other search criteria
|
||||
return ransack_results.intersection(supplier_properties_results) \
|
||||
unless supplier_properties_results.empty?
|
||||
|
||||
# We should get here but just in case we return the ransack results
|
||||
ransack_results
|
||||
end
|
||||
|
||||
def supplier_properties
|
||||
args[:q]&.slice("with_variants_supplier_properties")
|
||||
end
|
||||
|
||||
def supplier_property_ids
|
||||
supplier_properties["with_variants_supplier_properties"]
|
||||
end
|
||||
|
||||
def with_properties
|
||||
args[:q]&.dig("with_properties")
|
||||
end
|
||||
|
||||
def paginate(results)
|
||||
_pagy, paginated_results = pagy_array(
|
||||
results,
|
||||
page: args[:page] || 1,
|
||||
items: args[:per_page] || DEFAULT_PER_PAGE
|
||||
@@ -67,27 +111,6 @@ class ProductsRenderer
|
||||
OrderCycles::DistributedProductsService.new(distributor, order_cycle, customer)
|
||||
end
|
||||
|
||||
def products_order
|
||||
if distributor.preferred_shopfront_product_sorting_method == "by_producer" &&
|
||||
distributor.preferred_shopfront_producer_order.present?
|
||||
order_by_producer = distributor
|
||||
.preferred_shopfront_producer_order
|
||||
.split(",").map { |id| "spree_products.supplier_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
"#{order_by_producer}, spree_products.name ASC, spree_products.id ASC"
|
||||
elsif distributor.preferred_shopfront_product_sorting_method == "by_category" &&
|
||||
distributor.preferred_shopfront_taxon_order.present?
|
||||
order_by_category = distributor
|
||||
.preferred_shopfront_taxon_order
|
||||
.split(",").map { |id| "first_variant.primary_taxon_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
"#{order_by_category}, spree_products.name ASC, spree_products.id ASC"
|
||||
else
|
||||
"spree_products.name ASC, spree_products.id"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def variants_for_shop
|
||||
@variants_for_shop ||= begin
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
|
||||
@@ -55,8 +55,6 @@ module Sets
|
||||
def update_product(product, attributes)
|
||||
return false unless update_product_only_attributes(product, attributes)
|
||||
|
||||
ExchangeVariantDeleter.new.delete(product) if product.saved_change_to_supplier_id?
|
||||
|
||||
update_product_variants(product, attributes)
|
||||
end
|
||||
|
||||
@@ -107,6 +105,8 @@ module Sets
|
||||
if variant.present?
|
||||
variant.assign_attributes(variant_attributes.except(:id))
|
||||
variant.save if variant.changed?
|
||||
|
||||
ExchangeVariantDeleter.new.delete(variant) if variant.saved_change_to_supplier_id?
|
||||
else
|
||||
variant = create_variant(product, variant_attributes)
|
||||
end
|
||||
|
||||
@@ -19,5 +19,7 @@ class ShopsListService
|
||||
.includes(address: [:state, :country])
|
||||
.includes(:properties)
|
||||
.includes(supplied_products: :properties)
|
||||
.with_attached_promo_image
|
||||
.with_attached_logo
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,9 +15,11 @@ class WeightsAndMeasures
|
||||
|
||||
def system
|
||||
return "custom" unless scales = scales_for_variant_unit(ignore_available_units: true)
|
||||
return "custom" unless product_scale = @variant.product.variant_unit_scale
|
||||
|
||||
scales[product_scale.to_f]['system']
|
||||
product_scale = @variant.product.variant_unit_scale&.to_f
|
||||
return "custom" unless product_scale.present? && product_scale.positive?
|
||||
|
||||
scales[product_scale]['system']
|
||||
end
|
||||
|
||||
# @returns enumerable with label and value for select
|
||||
@@ -25,7 +27,7 @@ class WeightsAndMeasures
|
||||
available_units_sorted.flat_map do |measurement, measurement_info|
|
||||
measurement_info.filter_map do |scale, unit_info|
|
||||
scale_clean =
|
||||
ActiveSupport::NumberHelper.number_to_rounded(scale, precision: nil,
|
||||
ActiveSupport::NumberHelper.number_to_rounded(scale, precision: nil, significant: false,
|
||||
strip_insignificant_zeros: true)
|
||||
[
|
||||
"#{I18n.t(measurement)} (#{unit_info['name']})", # Label (eg "Weight (g)")
|
||||
|
||||
25
app/views/admin/connected_app_settings/edit.html.haml
Normal file
25
app/views/admin/connected_app_settings/edit.html.haml
Normal file
@@ -0,0 +1,25 @@
|
||||
= render :partial => 'spree/admin/shared/configuration_menu'
|
||||
|
||||
- content_for :page_title do
|
||||
= t('.title')
|
||||
|
||||
= form_tag main_app.admin_connected_app_settings_path, :method => :put do
|
||||
|
||||
%fieldset
|
||||
%legend= t('.enabled_legend')
|
||||
|
||||
= t('.info_html')
|
||||
|
||||
.field
|
||||
-# Blank value in case nothing is selected
|
||||
= hidden_field_tag("preferences[connected_apps_enabled][]", "")
|
||||
|
||||
- ConnectedApp::TYPES.each do |type|
|
||||
%label
|
||||
= check_box_tag("preferences[connected_apps_enabled][]", type,
|
||||
Spree::Config.connected_apps_enabled&.split(',')&.include?(type))
|
||||
= t('.connected_apps_enabled.' + type)
|
||||
%br
|
||||
|
||||
.form-buttons
|
||||
= button t(:update), 'icon-refresh'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user