mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-12 18:36:49 +00:00
Compare commits
384 Commits
RachL-patc
...
v5.0.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c08f925ff8 | ||
|
|
54fad01a91 | ||
|
|
32c4b4557b | ||
|
|
e2161660b3 | ||
|
|
139dba6637 | ||
|
|
a0833af780 | ||
|
|
0797614769 | ||
|
|
c84881252c | ||
|
|
1ee2f4d18e | ||
|
|
6885184bcc | ||
|
|
1a393592b4 | ||
|
|
f8e55f1eb3 | ||
|
|
e1f62148c9 | ||
|
|
ea6efa9164 | ||
|
|
4b6c3fe1d0 | ||
|
|
3bf01602d9 | ||
|
|
fa4785bc85 | ||
|
|
6fb86dd4ac | ||
|
|
c4d74ac10d | ||
|
|
6ef73acfcd | ||
|
|
ffad3f249b | ||
|
|
be78c2ac41 | ||
|
|
900b751559 | ||
|
|
cd7e92c6ca | ||
|
|
6b7373e4cf | ||
|
|
6e8eb443c1 | ||
|
|
42df158669 | ||
|
|
5e42a9be55 | ||
|
|
d4a060c6a2 | ||
|
|
f7998f6570 | ||
|
|
a953e3dde3 | ||
|
|
75c0752340 | ||
|
|
bcb4525cdd | ||
|
|
ff7c1e6d1f | ||
|
|
01036e6321 | ||
|
|
95625c16b2 | ||
|
|
690d137971 | ||
|
|
09fe6d7542 | ||
|
|
f6bd1c49ee | ||
|
|
bb040812aa | ||
|
|
e38d305ca1 | ||
|
|
46c92db415 | ||
|
|
3e4d689903 | ||
|
|
adf1c63c11 | ||
|
|
f21f8f38da | ||
|
|
5d360730c7 | ||
|
|
8636d3fc00 | ||
|
|
7b8b7b6bbc | ||
|
|
2d77b54685 | ||
|
|
0b8807146e | ||
|
|
486af8f58b | ||
|
|
c258bee44d | ||
|
|
c975a2c8c4 | ||
|
|
ea9a5c8dd5 | ||
|
|
d49cea5e3d | ||
|
|
920002e084 | ||
|
|
698d1daa57 | ||
|
|
f8f6c80aa9 | ||
|
|
a2459a3742 | ||
|
|
64608beaa8 | ||
|
|
187a78b78d | ||
|
|
70ebe7b964 | ||
|
|
33ed0998a9 | ||
|
|
f098327808 | ||
|
|
c27aa00bed | ||
|
|
8e15d571e7 | ||
|
|
d5ff1f5c71 | ||
|
|
87d20877ad | ||
|
|
33c6f3b94f | ||
|
|
aa9daed66e | ||
|
|
6dfa184a15 | ||
|
|
34fdcface2 | ||
|
|
450576a938 | ||
|
|
1650ccd55a | ||
|
|
531c068347 | ||
|
|
a4b92f289c | ||
|
|
68a0f8df1f | ||
|
|
248110cfb3 | ||
|
|
31c717349f | ||
|
|
cf35e48d14 | ||
|
|
917e0ff01a | ||
|
|
ed926e9e26 | ||
|
|
51300eecd9 | ||
|
|
ca12e35537 | ||
|
|
a1df61c612 | ||
|
|
fcc31fffcd | ||
|
|
971d5bea44 | ||
|
|
8c68179069 | ||
|
|
042cc238c8 | ||
|
|
25a90a44f9 | ||
|
|
8383441f79 | ||
|
|
229ee7028c | ||
|
|
fcd366cc06 | ||
|
|
0ca164a354 | ||
|
|
d7ae91c23e | ||
|
|
17e7f7d26d | ||
|
|
c71ae35685 | ||
|
|
1cf5dac979 | ||
|
|
d0a3d4996e | ||
|
|
a472f3e4f5 | ||
|
|
ace3bfa2a5 | ||
|
|
06d9d96f54 | ||
|
|
3c1dd10219 | ||
|
|
5726eeffaa | ||
|
|
84648690a6 | ||
|
|
1afa7fe5c0 | ||
|
|
a8d1d0c591 | ||
|
|
9e7e40a5a8 | ||
|
|
f9bd720341 | ||
|
|
20df5c23e8 | ||
|
|
c9eed4f5b8 | ||
|
|
18ec97992d | ||
|
|
6b76fbc817 | ||
|
|
a3e939e0ac | ||
|
|
eace31f1fc | ||
|
|
600195a726 | ||
|
|
e2e12ccb52 | ||
|
|
92c45084dc | ||
|
|
244e0524c7 | ||
|
|
96c7c828c1 | ||
|
|
9c153c6083 | ||
|
|
e946b50515 | ||
|
|
3ef5b41282 | ||
|
|
a007fdaab8 | ||
|
|
983e3e717b | ||
|
|
1e3f86625f | ||
|
|
9cd3bbf46f | ||
|
|
f9f5d0eb51 | ||
|
|
0b0b6a04e1 | ||
|
|
0f77d1bad5 | ||
|
|
cd38e02cac | ||
|
|
d6faa23fc2 | ||
|
|
9dc364979a | ||
|
|
3c7c02da2f | ||
|
|
657df9eb8f | ||
|
|
6ae3c8b102 | ||
|
|
e37881837b | ||
|
|
57c237c72d | ||
|
|
6030d7e05b | ||
|
|
30dfae7e18 | ||
|
|
14334b02bf | ||
|
|
772f1f8fde | ||
|
|
a5c199d397 | ||
|
|
1586c8ef28 | ||
|
|
e2bc86faa9 | ||
|
|
7f2266ef40 | ||
|
|
b8c5b24c17 | ||
|
|
f8788d358e | ||
|
|
c1198c8e1f | ||
|
|
6cf1a0500d | ||
|
|
314126a937 | ||
|
|
f9d436b1c9 | ||
|
|
49ec9a3136 | ||
|
|
2937595a33 | ||
|
|
dc7b6245fd | ||
|
|
884206b4ed | ||
|
|
2297b650f8 | ||
|
|
a3ec3e74ae | ||
|
|
0805501445 | ||
|
|
edf236778e | ||
|
|
bf98603fcf | ||
|
|
5f486bd611 | ||
|
|
1df3a6bb66 | ||
|
|
8e0c0392d9 | ||
|
|
428eb465c0 | ||
|
|
1147976069 | ||
|
|
45a4b33920 | ||
|
|
0bd6fe6709 | ||
|
|
21195c5750 | ||
|
|
faad7fa95c | ||
|
|
ddaeff7c53 | ||
|
|
ef08ae49fe | ||
|
|
7e3baabd23 | ||
|
|
a89d65cfc7 | ||
|
|
ab57618e59 | ||
|
|
cdcc6871fd | ||
|
|
98adefbd67 | ||
|
|
ce38c1a3d5 | ||
|
|
6923349de6 | ||
|
|
1fb987f0bd | ||
|
|
0b389b8ff4 | ||
|
|
35fa4155e4 | ||
|
|
1b03ee1a02 | ||
|
|
bad32e226e | ||
|
|
65f7980246 | ||
|
|
65abda2a38 | ||
|
|
f99f2c81ac | ||
|
|
bc53e4301c | ||
|
|
807782cc2a | ||
|
|
6adc6321f5 | ||
|
|
558d4debdb | ||
|
|
8f761fc438 | ||
|
|
be98544537 | ||
|
|
7f3b1c0d7a | ||
|
|
d71b282fda | ||
|
|
f5856d54da | ||
|
|
386970adb0 | ||
|
|
deee451b88 | ||
|
|
77864fc149 | ||
|
|
5fdd0e5d42 | ||
|
|
38ed025d88 | ||
|
|
b8aa970040 | ||
|
|
e2e2285f81 | ||
|
|
c1f8d3035a | ||
|
|
f74492190d | ||
|
|
874c464088 | ||
|
|
5a69acb742 | ||
|
|
72376da98f | ||
|
|
a30f764a22 | ||
|
|
5db8cb452e | ||
|
|
54f83b45c8 | ||
|
|
a7140d1f60 | ||
|
|
a529d95fc5 | ||
|
|
fa82f80ab9 | ||
|
|
a7dfa36883 | ||
|
|
60b62d41d6 | ||
|
|
0f706a9929 | ||
|
|
b9483ce63d | ||
|
|
a3a79686db | ||
|
|
0616827419 | ||
|
|
71ee7d5390 | ||
|
|
e689844a0f | ||
|
|
5c08446515 | ||
|
|
ad7ba3e680 | ||
|
|
e54a1afe52 | ||
|
|
7d0bcfa06a | ||
|
|
9bfac66412 | ||
|
|
e26a1d4d3d | ||
|
|
8d1d0c27c9 | ||
|
|
13e008e91e | ||
|
|
e47a57fa3c | ||
|
|
d5c3e94b24 | ||
|
|
9b965f657c | ||
|
|
58c39166e1 | ||
|
|
b6eca58798 | ||
|
|
bf41658d32 | ||
|
|
88837b55b9 | ||
|
|
9c0a15f431 | ||
|
|
fcbaefb2c8 | ||
|
|
9ca1b48d2e | ||
|
|
e76d6ad3df | ||
|
|
e1febc6e00 | ||
|
|
7d27f46d68 | ||
|
|
7d7253cf0e | ||
|
|
4fa4eb1b4e | ||
|
|
d16cd8c84e | ||
|
|
9870abfb1c | ||
|
|
8e46c0f897 | ||
|
|
3e031ab735 | ||
|
|
141000f0df | ||
|
|
7302c2d161 | ||
|
|
4be4c7622b | ||
|
|
604a47bd96 | ||
|
|
241a6d8128 | ||
|
|
bb70d21a35 | ||
|
|
626a269cf8 | ||
|
|
302336ab02 | ||
|
|
ee2a6bf2e6 | ||
|
|
7ca5927411 | ||
|
|
2926a9662c | ||
|
|
118ed915dc | ||
|
|
6e40e4da60 | ||
|
|
693bef1e7a | ||
|
|
0d4904d9e4 | ||
|
|
5def2b53e5 | ||
|
|
42940f4729 | ||
|
|
f2da3bb11c | ||
|
|
f8003b00db | ||
|
|
521b72a6c9 | ||
|
|
aa4552aac4 | ||
|
|
93a3130851 | ||
|
|
f3a30f94db | ||
|
|
16cae2dbcc | ||
|
|
cedf040b47 | ||
|
|
4a6e4d4c6d | ||
|
|
4c71ea3866 | ||
|
|
bc970927a5 | ||
|
|
c331d57cdb | ||
|
|
5845fee663 | ||
|
|
3199118bae | ||
|
|
de938f6f10 | ||
|
|
697f430156 | ||
|
|
c41c15b895 | ||
|
|
980f86ce89 | ||
|
|
af200ab4a0 | ||
|
|
4c6c1eedb1 | ||
|
|
bbdee7c0f3 | ||
|
|
11959515b8 | ||
|
|
cb781536b6 | ||
|
|
53d2166579 | ||
|
|
d95bb7736a | ||
|
|
7d5bb4a6fa | ||
|
|
5719d0682d | ||
|
|
c4c95d472e | ||
|
|
3e7f61c4d1 | ||
|
|
db76cd1659 | ||
|
|
e791184468 | ||
|
|
23287573f4 | ||
|
|
925ac2ea6a | ||
|
|
355c9686e3 | ||
|
|
58d174fad9 | ||
|
|
d90c4f6aed | ||
|
|
f5b9ca361c | ||
|
|
16d6e1f935 | ||
|
|
1e6fbadd8b | ||
|
|
d102652c03 | ||
|
|
1b50217242 | ||
|
|
73819a4638 | ||
|
|
9ab2a3ae3d | ||
|
|
d413a142c9 | ||
|
|
48ad7ed8a0 | ||
|
|
a2c4c44eea | ||
|
|
e7ece294cc | ||
|
|
d7313ffec9 | ||
|
|
b42cba8c37 | ||
|
|
7726c7d129 | ||
|
|
d4d995851f | ||
|
|
12cf62c2ff | ||
|
|
0569b30e0d | ||
|
|
9f3da1af4f | ||
|
|
afb336d789 | ||
|
|
92c4cb9b7f | ||
|
|
724d5a2ca0 | ||
|
|
6251814152 | ||
|
|
cf13dc2ff6 | ||
|
|
4906c19c8e | ||
|
|
129ccc33f8 | ||
|
|
9399c7e129 | ||
|
|
c17eddd69b | ||
|
|
b30096317c | ||
|
|
c89b4fb86b | ||
|
|
3a367ceb6e | ||
|
|
f9fb7bf399 | ||
|
|
0f9b933117 | ||
|
|
7cbe77668a | ||
|
|
479eacc956 | ||
|
|
e7213dba68 | ||
|
|
078e191d26 | ||
|
|
b554eda7c7 | ||
|
|
477447ad92 | ||
|
|
039399ee37 | ||
|
|
d36438037a | ||
|
|
5b58f7b20e | ||
|
|
02e2214caa | ||
|
|
0ec8d13641 | ||
|
|
151fc7bf85 | ||
|
|
15c920c911 | ||
|
|
b4aaa0fae1 | ||
|
|
efe0a2a701 | ||
|
|
3f905cce16 | ||
|
|
5c5213e872 | ||
|
|
3a7aed154c | ||
|
|
60ace5d3ff | ||
|
|
1dec3debe1 | ||
|
|
711f37bce1 | ||
|
|
a493d70f5c | ||
|
|
c0887b1806 | ||
|
|
3d09ac01cc | ||
|
|
3a3d729dcb | ||
|
|
283706114e | ||
|
|
7ca544540b | ||
|
|
b1b4b10417 | ||
|
|
3b83200a14 | ||
|
|
7cd8311dcb | ||
|
|
87d7f73ba9 | ||
|
|
14e7c57102 | ||
|
|
9f859f420d | ||
|
|
af33fc357e | ||
|
|
6a8cc410d2 | ||
|
|
61e7c1db07 | ||
|
|
0d8df5d2a8 | ||
|
|
73a1698aad | ||
|
|
97d41c230e | ||
|
|
1b4efd2164 | ||
|
|
1e13005fb5 | ||
|
|
bab7756017 | ||
|
|
7a5074cc90 | ||
|
|
41ffe848ed | ||
|
|
0797314360 | ||
|
|
3302f0e78d | ||
|
|
bafb881c46 | ||
|
|
32ab821839 | ||
|
|
008d764c34 | ||
|
|
9c51615b03 |
@@ -16,6 +16,7 @@ SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
OFN_REDIS_URL="redis://localhost:6379/1"
|
||||
OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
|
||||
OFN_REDIS_CABLE_URL="redis://localhost:6379/0"
|
||||
|
||||
SITE_URL="0.0.0.0:3000"
|
||||
|
||||
|
||||
43
.github/workflows/build.yml
vendored
43
.github/workflows/build.yml
vendored
@@ -38,10 +38,10 @@ jobs:
|
||||
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||
ci_node_total: [8]
|
||||
ci_node_total: [4]
|
||||
# Indexes for parallel jobs (starting from zero).
|
||||
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7]
|
||||
ci_node_index: [0, 1, 2, 3]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bin/rake db:create db:schema:load
|
||||
bin/rails db:create db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}"
|
||||
run: |
|
||||
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
|
||||
bin/rake knapsack_pro:rspec
|
||||
bin/rails assets:precompile knapsack_pro:rspec
|
||||
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -116,10 +116,10 @@ jobs:
|
||||
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||
ci_node_total: [4]
|
||||
ci_node_total: [2]
|
||||
# Indexes for parallel jobs (starting from zero).
|
||||
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||
ci_node_index: [0, 1, 2, 3]
|
||||
ci_node_index: [0, 1]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bin/rake db:create db:schema:load
|
||||
bin/rails db:create db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
@@ -184,10 +184,10 @@ jobs:
|
||||
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||
ci_node_total: [14]
|
||||
ci_node_total: [10]
|
||||
# Indexes for parallel jobs (starting from zero).
|
||||
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
|
||||
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -211,7 +211,7 @@ jobs:
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bin/rake db:create db:schema:load
|
||||
bin/rails db:create db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
|
||||
@@ -230,7 +230,7 @@ jobs:
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
|
||||
|
||||
run: |
|
||||
bin/rake knapsack_pro:queue:rspec
|
||||
bin/rails assets:precompile knapsack_pro:queue:rspec
|
||||
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -271,10 +271,10 @@ jobs:
|
||||
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||
ci_node_total: [12]
|
||||
ci_node_total: [6]
|
||||
# Indexes for parallel jobs (starting from zero).
|
||||
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||
ci_node_index: [0, 1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -298,7 +298,7 @@ jobs:
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bin/rake db:create db:schema:load
|
||||
bin/rails db:create db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
|
||||
@@ -317,7 +317,7 @@ jobs:
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}"
|
||||
|
||||
run: |
|
||||
bin/rake knapsack_pro:queue:rspec
|
||||
bin/rails assets:precompile knapsack_pro:queue:rspec
|
||||
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -386,7 +386,7 @@ jobs:
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bin/rake db:create db:schema:load
|
||||
bin/rails db:create db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
|
||||
@@ -405,7 +405,7 @@ jobs:
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}"
|
||||
|
||||
run: |
|
||||
bin/rake knapsack_pro:rspec
|
||||
bin/rails assets:precompile knapsack_pro:rspec
|
||||
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -437,10 +437,10 @@ jobs:
|
||||
# [n] - where the n is a number of parallel jobs you want to run your tests on.
|
||||
# Use a higher number if you have slow tests to split them between more parallel jobs.
|
||||
# Remember to update the value of the `ci_node_index` below to (0..n-1).
|
||||
ci_node_total: [5]
|
||||
ci_node_total: [3]
|
||||
# Indexes for parallel jobs (starting from zero).
|
||||
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
|
||||
ci_node_index: [0, 1, 2, 3, 4]
|
||||
ci_node_index: [0, 1, 2]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -465,7 +465,7 @@ jobs:
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bin/rake db:create db:schema:load
|
||||
bin/rails db:create db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
@@ -482,7 +482,7 @@ jobs:
|
||||
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||
KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}"
|
||||
run: |
|
||||
bin/rake knapsack_pro:rspec
|
||||
bin/rails assets:precompile knapsack_pro:rspec
|
||||
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -553,7 +553,6 @@ jobs:
|
||||
with:
|
||||
pattern: simplecov-chunk-*
|
||||
path: tmp/simplecov
|
||||
merge-multiple: true
|
||||
|
||||
- name: collate results from each of the workers
|
||||
run: bundle exec rake 'simplecov:collate_results[tmp/simplecov]'
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
# These are the rules we agreed upon and we work towards.
|
||||
AllCops:
|
||||
NewCops: enable
|
||||
MigratedSchemaVersion: "20250111000000"
|
||||
Exclude:
|
||||
- bin/**/*
|
||||
- db/**/*
|
||||
- config/**/*
|
||||
- db/bad_migrations/*
|
||||
- db/migrate/201*
|
||||
- db/migrate/202[0-4]*
|
||||
- db/schema.rb
|
||||
- script/**/*
|
||||
- vendor/**/*
|
||||
- node_modules/**/*
|
||||
|
||||
@@ -34,12 +34,6 @@ Lint/EmptyClass:
|
||||
Exclude:
|
||||
- 'spec/lib/reports/report_loader_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: AllowComments.
|
||||
Lint/EmptyFile:
|
||||
Exclude:
|
||||
- 'spec/lib/open_food_network/enterprise_injection_data_spec.rb'
|
||||
|
||||
# Offense count: 2
|
||||
Lint/FloatComparison:
|
||||
Exclude:
|
||||
@@ -92,7 +86,6 @@ Metrics/AbcSize:
|
||||
- 'app/controllers/admin/enterprises_controller.rb'
|
||||
- 'app/controllers/payment_gateways/paypal_controller.rb'
|
||||
- 'app/controllers/spree/admin/payments_controller.rb'
|
||||
- 'app/controllers/spree/admin/taxons_controller.rb'
|
||||
- 'app/controllers/spree/admin/variants_controller.rb'
|
||||
- 'app/controllers/spree/orders_controller.rb'
|
||||
- 'app/helpers/spree/admin/navigation_helper.rb'
|
||||
@@ -127,7 +120,7 @@ Metrics/BlockNesting:
|
||||
Exclude:
|
||||
- 'app/models/spree/payment/processing.rb'
|
||||
|
||||
# Offense count: 46
|
||||
# Offense count: 47
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ClassLength:
|
||||
Exclude:
|
||||
@@ -137,6 +130,7 @@ Metrics/ClassLength:
|
||||
- 'app/controllers/admin/resource_controller.rb'
|
||||
- 'app/controllers/admin/subscriptions_controller.rb'
|
||||
- 'app/controllers/application_controller.rb'
|
||||
- 'app/controllers/checkout_controller.rb'
|
||||
- 'app/controllers/payment_gateways/paypal_controller.rb'
|
||||
- 'app/controllers/spree/admin/orders_controller.rb'
|
||||
- 'app/controllers/spree/admin/payment_methods_controller.rb'
|
||||
@@ -183,7 +177,7 @@ Metrics/ClassLength:
|
||||
Metrics/CyclomaticComplexity:
|
||||
Exclude:
|
||||
- 'app/controllers/admin/enterprises_controller.rb'
|
||||
- 'app/controllers/spree/admin/taxons_controller.rb'
|
||||
- 'app/controllers/spree/admin/payments_controller.rb'
|
||||
- 'app/controllers/spree/orders_controller.rb'
|
||||
- 'app/helpers/checkout_helper.rb'
|
||||
- 'app/helpers/order_cycles_helper.rb'
|
||||
@@ -208,13 +202,12 @@ Metrics/CyclomaticComplexity:
|
||||
- 'lib/spree/localized_number.rb'
|
||||
- 'spec/models/product_importer_spec.rb'
|
||||
|
||||
# Offense count: 24
|
||||
# Offense count: 23
|
||||
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
|
||||
Metrics/MethodLength:
|
||||
Exclude:
|
||||
- 'app/controllers/admin/enterprises_controller.rb'
|
||||
- 'app/controllers/payment_gateways/paypal_controller.rb'
|
||||
- 'app/controllers/spree/admin/taxons_controller.rb'
|
||||
- 'app/controllers/spree/orders_controller.rb'
|
||||
- 'app/helpers/spree/admin/navigation_helper.rb'
|
||||
- 'app/models/spree/ability.rb'
|
||||
@@ -293,19 +286,17 @@ Metrics/ParameterLists:
|
||||
- 'spec/support/controller_requests_helper.rb'
|
||||
- 'spec/system/admin/reports_spec.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Offense count: 3
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
||||
Metrics/PerceivedComplexity:
|
||||
Exclude:
|
||||
- 'app/controllers/spree/admin/taxons_controller.rb'
|
||||
- 'app/models/enterprise_relationship.rb'
|
||||
- 'app/models/spree/ability.rb'
|
||||
- 'app/models/spree/order/checkout.rb'
|
||||
|
||||
# Offense count: 8
|
||||
# Offense count: 7
|
||||
Naming/AccessorMethodName:
|
||||
Exclude:
|
||||
- 'app/controllers/spree/admin/taxonomies_controller.rb'
|
||||
- 'app/mailers/producer_mailer.rb'
|
||||
- 'app/models/spree/order.rb'
|
||||
- 'app/services/checkout/post_checkout_actions.rb'
|
||||
@@ -348,12 +339,11 @@ Naming/VariableNumber:
|
||||
- 'app/models/preference_sections/main_links_section.rb'
|
||||
- 'lib/spree/core/controller_helpers/common.rb'
|
||||
- 'spec/controllers/spree/admin/search_controller_spec.rb'
|
||||
- 'spec/factories/stock_location_factory.rb'
|
||||
- 'spec/models/spree/stock_item_spec.rb'
|
||||
- 'spec/models/spree/tax_rate_spec.rb'
|
||||
- 'spec/requests/api/orders_spec.rb'
|
||||
|
||||
# Offense count: 142
|
||||
# Offense count: 143
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: ResponseMethods.
|
||||
# ResponseMethods: response, last_response
|
||||
@@ -557,7 +547,7 @@ RSpecRails/InferredSpecType:
|
||||
- 'spec/requests/voucher_adjustments_spec.rb'
|
||||
- 'spec/routing/stripe_spec.rb'
|
||||
|
||||
# Offense count: 22
|
||||
# Offense count: 21
|
||||
# Configuration parameters: IgnoreScopes, Include.
|
||||
# Include: app/models/**/*.rb
|
||||
Rails/InverseOf:
|
||||
@@ -572,7 +562,6 @@ Rails/InverseOf:
|
||||
- 'app/models/spree/price.rb'
|
||||
- 'app/models/spree/product.rb'
|
||||
- 'app/models/spree/stock_item.rb'
|
||||
- 'app/models/spree/taxonomy.rb'
|
||||
- 'app/models/spree/variant.rb'
|
||||
- 'app/models/subscription_line_item.rb'
|
||||
|
||||
@@ -720,7 +709,7 @@ Style/GlobalStdStream:
|
||||
- 'lib/tasks/subscriptions/debug.rake'
|
||||
- 'lib/tasks/subscriptions/test.rake'
|
||||
|
||||
# Offense count: 12
|
||||
# Offense count: 10
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: AllowSplatArgument.
|
||||
Style/HashConversion:
|
||||
@@ -728,9 +717,7 @@ Style/HashConversion:
|
||||
- 'app/controllers/admin/column_preferences_controller.rb'
|
||||
- 'app/controllers/admin/variant_overrides_controller.rb'
|
||||
- 'app/controllers/spree/admin/products_controller.rb'
|
||||
- 'app/models/order_cycle.rb'
|
||||
- 'app/models/product_import/product_importer.rb'
|
||||
- 'app/models/spree/shipping_method.rb'
|
||||
- 'app/serializers/api/admin/exchange_serializer.rb'
|
||||
- 'app/services/variants_stock_levels.rb'
|
||||
- 'spec/controllers/admin/inventory_items_controller_spec.rb'
|
||||
|
||||
@@ -89,4 +89,4 @@ RUN ./script/install-bundler
|
||||
RUN yarn install
|
||||
|
||||
# Run bundler install in parallel with the amount of available CPUs
|
||||
RUN bundle install --jobs="$(nproc)"
|
||||
RUN bundle install --jobs="$(nproc)"
|
||||
2
Gemfile
2
Gemfile
@@ -86,7 +86,7 @@ gem "active_model_serializers", "0.8.4"
|
||||
gem 'activerecord-session_store'
|
||||
gem 'acts-as-taggable-on'
|
||||
gem 'angularjs-file-upload-rails', '~> 2.4.1'
|
||||
gem 'bigdecimal', '3.0.2'
|
||||
gem 'bigdecimal'
|
||||
gem 'bootsnap', require: false
|
||||
gem 'geocoder'
|
||||
gem 'gmaps4rails'
|
||||
|
||||
22
Gemfile.lock
22
Gemfile.lock
@@ -180,7 +180,7 @@ GEM
|
||||
base64 (0.2.0)
|
||||
bcp47_spec (0.2.1)
|
||||
bcrypt (3.1.20)
|
||||
bigdecimal (3.0.2)
|
||||
bigdecimal (3.1.8)
|
||||
bindata (2.5.0)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.18.3)
|
||||
@@ -191,7 +191,7 @@ GEM
|
||||
bullet (7.1.6)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
cable_ready (5.0.5)
|
||||
cable_ready (5.0.6)
|
||||
actionpack (>= 5.2)
|
||||
actionview (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
@@ -683,10 +683,10 @@ GEM
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.25.1)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-rails (2.24.1)
|
||||
rubocop-rails (2.28.0)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop (>= 1.52.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (2.29.2)
|
||||
rubocop (~> 1.40)
|
||||
@@ -755,16 +755,16 @@ GEM
|
||||
state_machines-activerecord (0.9.0)
|
||||
activerecord (>= 6.0)
|
||||
state_machines-activemodel (>= 0.9.0)
|
||||
stimulus_reflex (3.5.1)
|
||||
actioncable (>= 5.2, < 8)
|
||||
actionpack (>= 5.2, < 8)
|
||||
actionview (>= 5.2, < 8)
|
||||
activesupport (>= 5.2, < 8)
|
||||
stimulus_reflex (3.5.3)
|
||||
actioncable (>= 5.2)
|
||||
actionpack (>= 5.2)
|
||||
actionview (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
cable_ready (~> 5.0)
|
||||
nokogiri (~> 1.0)
|
||||
nokogiri-html5-inference (~> 0.3)
|
||||
rack (>= 2, < 4)
|
||||
railties (>= 5.2, < 8)
|
||||
railties (>= 5.2)
|
||||
redis (>= 4.0, < 6.0)
|
||||
stringex (2.8.6)
|
||||
stringio (3.1.0)
|
||||
@@ -865,7 +865,7 @@ DEPENDENCIES
|
||||
angularjs-rails (= 1.8.0)
|
||||
arel-helpers (~> 2.12)
|
||||
aws-sdk-s3
|
||||
bigdecimal (= 3.0.2)
|
||||
bigdecimal
|
||||
bootsnap
|
||||
bugsnag
|
||||
bullet
|
||||
|
||||
32
alpine.Dockerfile
Normal file
32
alpine.Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM ruby:3.1.4-alpine3.19 AS base
|
||||
ENV LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8 \
|
||||
TZ=Europe/London \
|
||||
RAILS_ROOT=/usr/src/app
|
||||
RUN apk --no-cache upgrade && \
|
||||
apk add --no-cache tzdata postgresql-client imagemagick imagemagick-jpeg && \
|
||||
apk add --no-cache --virtual wkhtmltopdf
|
||||
|
||||
WORKDIR $RAILS_ROOT
|
||||
|
||||
# Development dependencies
|
||||
FROM base AS development-base
|
||||
RUN apk add --no-cache --virtual .build-deps \
|
||||
build-base postgresql-dev git nodejs yarn && \
|
||||
apk add --no-cache --virtual .dev-utils \
|
||||
bash curl less vim chromium-chromedriver zlib-dev openssl-dev \
|
||||
readline-dev yaml-dev sqlite-dev libxml2-dev libxslt-dev libffi-dev vips-dev && \
|
||||
curl -o /usr/local/bin/wait-for-it https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh && \
|
||||
chmod +x /usr/local/bin/wait-for-it
|
||||
|
||||
# Install yarn dependencies separately for caching
|
||||
FROM development-base AS yarn-dependencies
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
# Install Ruby gems
|
||||
FROM development-base
|
||||
COPY . $RAILS_ROOT
|
||||
COPY Gemfile Gemfile.lock ./
|
||||
RUN bundle install --jobs "$(nproc)"
|
||||
COPY --from=yarn-dependencies $RAILS_ROOT/node_modules ./node_modules
|
||||
@@ -67,8 +67,5 @@
|
||||
// foundation
|
||||
//= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js
|
||||
|
||||
// LocalStorage
|
||||
//= require ../shared/angular-local-storage.js
|
||||
|
||||
// requires the rest of the JS code in this folder
|
||||
//= require_tree .
|
||||
|
||||
@@ -307,9 +307,6 @@ filterSubmitProducts = (productsToFilter) ->
|
||||
variantHasUpdatableProperty = result.hasUpdatableProperty
|
||||
filteredVariants.push filteredVariant if variantHasUpdatableProperty
|
||||
|
||||
if product.hasOwnProperty("sku")
|
||||
filteredProduct.sku = product.sku
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("name")
|
||||
filteredProduct.name = product.name
|
||||
hasUpdatableProperty = true
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
angular.module("admin.indexUtils").factory 'KeyValueMapStore', (localStorageService)->
|
||||
new class KeyValueMapStore
|
||||
localStorageKey: ''
|
||||
storableKeys: []
|
||||
|
||||
constructor: ->
|
||||
localStorageService.setStorageType("sessionStorage")
|
||||
|
||||
getStoredKeyValueMap: ->
|
||||
localStorageService.get(@localStorageKey) || {}
|
||||
|
||||
setStoredValues: (source) ->
|
||||
keyValueMap = {}
|
||||
for key in @storableKeys
|
||||
keyValueMap[key] = source[key]
|
||||
localStorageService.set(@localStorageKey, keyValueMap)
|
||||
|
||||
restoreValues: (target) ->
|
||||
storedKeyValueMap = @getStoredKeyValueMap()
|
||||
|
||||
return false if _.isEmpty(storedKeyValueMap)
|
||||
|
||||
for k,v of storedKeyValueMap
|
||||
target[k] = v
|
||||
|
||||
return true
|
||||
|
||||
clearKeyValueMap: () ->
|
||||
localStorageService.remove(@localStorageKey)
|
||||
@@ -187,18 +187,17 @@ addVariantFromStockLocation = function() {
|
||||
$('#stock_details').hide();
|
||||
|
||||
var variant_id = $('input.variant_autocomplete').val();
|
||||
var stock_location_id = $(this).data('stock-location-id');
|
||||
var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();
|
||||
var quantity = $("input.quantity").val();
|
||||
|
||||
var shipment = _.find(shipments, function(shipment){
|
||||
return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
|
||||
return shipment.state == 'ready' || shipment.state == 'pending';
|
||||
});
|
||||
|
||||
if(shipment==undefined){
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: Spree.url(Spree.routes.orders_api + "/" + order_number + "/shipments.json"),
|
||||
data: { variant_id: variant_id, quantity: quantity, stock_location_id: stock_location_id }
|
||||
data: { variant_id: variant_id, quantity: quantity }
|
||||
}).done(function( msg ) {
|
||||
window.location.reload();
|
||||
}).error(function( msg ) {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#= require angular-google-maps.min.js
|
||||
#= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js
|
||||
#= require ../shared/ng-infinite-scroll.min.js
|
||||
#= require ../shared/angular-local-storage.js
|
||||
#= require ../shared/angular-slideables.js
|
||||
#= require ../shared/shared
|
||||
#= require_tree ../shared/directives
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
angular.module("Darkswarm", [
|
||||
'ngResource',
|
||||
'mm.foundation',
|
||||
'LocalStorageModule',
|
||||
'infinite-scroll',
|
||||
'angular-flash.service',
|
||||
'templates',
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
angular.module('Darkswarm').directive "ofnDisableScroll", ()->
|
||||
# Stops scrolling from incrementing or decrementing input value
|
||||
# Useful for number inputs
|
||||
restrict: 'A'
|
||||
link: (scope, element, attrs)->
|
||||
element.bind 'focus', ->
|
||||
element.bind 'mousewheel', (e)->
|
||||
e.preventDefault()
|
||||
element.bind 'blur', ->
|
||||
element.unbind 'mousewheel'
|
||||
@@ -1,5 +0,0 @@
|
||||
angular.module('Darkswarm').directive "integer", ->
|
||||
restrict: 'A'
|
||||
link: (scope, elem, attr) ->
|
||||
elem.bind 'input', ->
|
||||
elem.val Math.round(elem.val())
|
||||
@@ -20,10 +20,13 @@ angular.module('Darkswarm').directive 'mapSearch', ($timeout, Search) ->
|
||||
$timeout =>
|
||||
map = ctrl.getMap()
|
||||
|
||||
searchBox = scope.createSearchBox map
|
||||
scope.bindSearchResponse map, searchBox
|
||||
scope.biasResults map, searchBox
|
||||
scope.performUrlSearch map
|
||||
if !map
|
||||
alert(t('gmap_load_failure'))
|
||||
else
|
||||
searchBox = scope.createSearchBox map
|
||||
scope.bindSearchResponse map, searchBox
|
||||
scope.biasResults map, searchBox
|
||||
scope.performUrlSearch map
|
||||
|
||||
scope.createSearchBox = (map) ->
|
||||
map.controls[google.maps.ControlPosition.TOP_LEFT].push scope.input
|
||||
|
||||
@@ -13,6 +13,8 @@ angular.module('Darkswarm').directive 'ofnOpenStreetMap', ($window, MapCentreCal
|
||||
|
||||
buildMarker = (enterprise, latlng, title) ->
|
||||
icon = L.icon
|
||||
iconAnchor: [14, 33]
|
||||
iconSize: [28, 33]
|
||||
iconUrl: enterprise.icon
|
||||
marker = L.marker latlng,
|
||||
draggable: true,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
angular.module('Darkswarm').directive "renderSvg", ()->
|
||||
# Magical directive that'll render SVGs from URLs
|
||||
# If only there were a neater way of doing this
|
||||
restrict: 'E'
|
||||
priority: 99
|
||||
template: "<svg-wrapper></svg-wrapper>"
|
||||
|
||||
# Fetch SVG via ajax, inject into page using DOM
|
||||
link: (scope, elem, attr)->
|
||||
if /.svg/.test attr.path # Only do this if we've got an svg
|
||||
$.ajax
|
||||
url: attr.path
|
||||
success: (html)->
|
||||
elem.html($(html).find("svg"))
|
||||
@@ -1,9 +0,0 @@
|
||||
angular.module('Darkswarm').directive "ofnScrollTo", ($location, $anchorScroll)->
|
||||
# Onclick sets $location.hash to attrs.ofnScrollTo
|
||||
# Then triggers anchorScroll
|
||||
restrict: 'A'
|
||||
link: (scope, element, attrs)->
|
||||
element.bind 'click', (ev)->
|
||||
ev.stopPropagation()
|
||||
$location.hash attrs.ofnScrollTo
|
||||
$anchorScroll()
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('Darkswarm').factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService, Messages) ->
|
||||
angular.module('Darkswarm').factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, Messages) ->
|
||||
# Handles syncing of current cart/order state to server
|
||||
new class Cart
|
||||
dirty: false
|
||||
@@ -113,7 +113,6 @@ angular.module('Darkswarm').factory 'Cart', (CurrentOrder, Variants, $timeout, $
|
||||
|
||||
clear: ->
|
||||
@line_items = []
|
||||
localStorageService.clearAll() # One day this will have to be moar GRANULAR
|
||||
|
||||
isOnlyItemInOrder: (id) =>
|
||||
deletedItem = @line_items_finalised.find((item) -> item.id == id)
|
||||
|
||||
@@ -1,546 +0,0 @@
|
||||
/**
|
||||
* An Angular module that gives you access to the browsers local storage
|
||||
* @version v0.5.0 - 2016-08-29
|
||||
* @link https://github.com/grevory/angular-local-storage
|
||||
* @author grevory <greg@gregpike.ca>
|
||||
* @license MIT License, http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
(function (window, angular) {
|
||||
var isDefined = angular.isDefined,
|
||||
isUndefined = angular.isUndefined,
|
||||
isNumber = angular.isNumber,
|
||||
isObject = angular.isObject,
|
||||
isArray = angular.isArray,
|
||||
extend = angular.extend,
|
||||
toJson = angular.toJson;
|
||||
|
||||
angular
|
||||
.module('LocalStorageModule', [])
|
||||
.provider('localStorageService', function() {
|
||||
// You should set a prefix to avoid overwriting any local storage variables from the rest of your app
|
||||
// e.g. localStorageServiceProvider.setPrefix('yourAppName');
|
||||
// With provider you can use config as this:
|
||||
// myApp.config(function (localStorageServiceProvider) {
|
||||
// localStorageServiceProvider.prefix = 'yourAppName';
|
||||
// });
|
||||
this.prefix = 'ls';
|
||||
|
||||
// You could change web storage type localstorage or sessionStorage
|
||||
this.storageType = 'localStorage';
|
||||
|
||||
// Cookie options (usually in case of fallback)
|
||||
// expiry = Number of days before cookies expire // 0 = Does not expire
|
||||
// path = The web path the cookie represents
|
||||
// secure = Wether the cookies should be secure (i.e only sent on HTTPS requests)
|
||||
this.cookie = {
|
||||
expiry: 30,
|
||||
path: '/',
|
||||
secure: false
|
||||
};
|
||||
|
||||
// Decides wether we should default to cookies if localstorage is not supported.
|
||||
this.defaultToCookie = true;
|
||||
|
||||
// Send signals for each of the following actions?
|
||||
this.notify = {
|
||||
setItem: true,
|
||||
removeItem: false
|
||||
};
|
||||
|
||||
// Setter for the prefix
|
||||
this.setPrefix = function(prefix) {
|
||||
this.prefix = prefix;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Setter for the storageType
|
||||
this.setStorageType = function(storageType) {
|
||||
this.storageType = storageType;
|
||||
return this;
|
||||
};
|
||||
// Setter for defaultToCookie value, default is true.
|
||||
this.setDefaultToCookie = function (shouldDefault) {
|
||||
this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value.
|
||||
return this;
|
||||
};
|
||||
// Setter for cookie config
|
||||
this.setStorageCookie = function(exp, path, secure) {
|
||||
this.cookie.expiry = exp;
|
||||
this.cookie.path = path;
|
||||
this.cookie.secure = secure;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Setter for cookie domain
|
||||
this.setStorageCookieDomain = function(domain) {
|
||||
this.cookie.domain = domain;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Setter for notification config
|
||||
// itemSet & itemRemove should be booleans
|
||||
this.setNotify = function(itemSet, itemRemove) {
|
||||
this.notify = {
|
||||
setItem: itemSet,
|
||||
removeItem: itemRemove
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) {
|
||||
var self = this;
|
||||
var prefix = self.prefix;
|
||||
var cookie = self.cookie;
|
||||
var notify = self.notify;
|
||||
var storageType = self.storageType;
|
||||
var webStorage;
|
||||
|
||||
// When Angular's $document is not available
|
||||
if (!$document) {
|
||||
$document = document;
|
||||
} else if ($document[0]) {
|
||||
$document = $document[0];
|
||||
}
|
||||
|
||||
// If there is a prefix set in the config lets use that with an appended period for readability
|
||||
if (prefix.substr(-1) !== '.') {
|
||||
prefix = !!prefix ? prefix + '.' : '';
|
||||
}
|
||||
var deriveQualifiedKey = function(key) {
|
||||
return prefix + key;
|
||||
};
|
||||
|
||||
// Removes prefix from the key.
|
||||
var underiveQualifiedKey = function (key) {
|
||||
return key.replace(new RegExp('^' + prefix, 'g'), '');
|
||||
};
|
||||
|
||||
// Check if the key is within our prefix namespace.
|
||||
var isKeyPrefixOurs = function (key) {
|
||||
return key.indexOf(prefix) === 0;
|
||||
};
|
||||
|
||||
// Checks the browser to see if local storage is supported
|
||||
var checkSupport = function () {
|
||||
try {
|
||||
var supported = (storageType in $window && $window[storageType] !== null);
|
||||
|
||||
// When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage
|
||||
// is available, but trying to call .setItem throws an exception.
|
||||
//
|
||||
// "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage
|
||||
// that exceeded the quota."
|
||||
var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7));
|
||||
if (supported) {
|
||||
webStorage = $window[storageType];
|
||||
webStorage.setItem(key, '');
|
||||
webStorage.removeItem(key);
|
||||
}
|
||||
|
||||
return supported;
|
||||
} catch (e) {
|
||||
// Only change storageType to cookies if defaulting is enabled.
|
||||
if (self.defaultToCookie)
|
||||
storageType = 'cookie';
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
var browserSupportsLocalStorage = checkSupport();
|
||||
|
||||
// Directly adds a value to local storage
|
||||
// If local storage is not available in the browser use cookies
|
||||
// Example use: localStorageService.add('library','angular');
|
||||
var addToLocalStorage = function (key, value, type) {
|
||||
setStorageType(type);
|
||||
|
||||
// Let's convert undefined values to null to get the value consistent
|
||||
if (isUndefined(value)) {
|
||||
value = null;
|
||||
} else {
|
||||
value = toJson(value);
|
||||
}
|
||||
|
||||
// If this browser does not support local storage use cookies
|
||||
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
|
||||
if (!browserSupportsLocalStorage) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
|
||||
}
|
||||
|
||||
if (notify.setItem) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'});
|
||||
}
|
||||
return addToCookies(key, value);
|
||||
}
|
||||
|
||||
try {
|
||||
if (webStorage) {
|
||||
webStorage.setItem(deriveQualifiedKey(key), value);
|
||||
}
|
||||
if (notify.setItem) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType});
|
||||
}
|
||||
} catch (e) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
|
||||
return addToCookies(key, value);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Directly get a value from local storage
|
||||
// Example use: localStorageService.get('library'); // returns 'angular'
|
||||
var getFromLocalStorage = function (key, type) {
|
||||
setStorageType(type);
|
||||
|
||||
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
|
||||
if (!browserSupportsLocalStorage) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
|
||||
}
|
||||
|
||||
return getFromCookies(key);
|
||||
}
|
||||
|
||||
var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null;
|
||||
// angular.toJson will convert null to 'null', so a proper conversion is needed
|
||||
// FIXME not a perfect solution, since a valid 'null' string can't be stored
|
||||
if (!item || item === 'null') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(item);
|
||||
} catch (e) {
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
// Remove an item from local storage
|
||||
// Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular'
|
||||
//
|
||||
// This is var-arg removal, check the last argument to see if it is a storageType
|
||||
// and set type accordingly before removing.
|
||||
//
|
||||
var removeFromLocalStorage = function () {
|
||||
// can't pop on arguments, so we do this
|
||||
var consumed = 0;
|
||||
if (arguments.length >= 1 &&
|
||||
(arguments[arguments.length - 1] === 'localStorage' ||
|
||||
arguments[arguments.length - 1] === 'sessionStorage')) {
|
||||
consumed = 1;
|
||||
setStorageType(arguments[arguments.length - 1]);
|
||||
}
|
||||
|
||||
var i, key;
|
||||
for (i = 0; i < arguments.length - consumed; i++) {
|
||||
key = arguments[i];
|
||||
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
|
||||
if (!browserSupportsLocalStorage) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
|
||||
}
|
||||
|
||||
if (notify.removeItem) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'});
|
||||
}
|
||||
removeFromCookies(key);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
webStorage.removeItem(deriveQualifiedKey(key));
|
||||
if (notify.removeItem) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.removeitem', {
|
||||
key: key,
|
||||
storageType: self.storageType
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
|
||||
removeFromCookies(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Return array of keys for local storage
|
||||
// Example use: var keys = localStorageService.keys()
|
||||
var getKeysForLocalStorage = function (type) {
|
||||
setStorageType(type);
|
||||
|
||||
if (!browserSupportsLocalStorage) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
|
||||
return [];
|
||||
}
|
||||
|
||||
var prefixLength = prefix.length;
|
||||
var keys = [];
|
||||
for (var key in webStorage) {
|
||||
// Only return keys that are for this app
|
||||
if (key.substr(0, prefixLength) === prefix) {
|
||||
try {
|
||||
keys.push(key.substr(prefixLength));
|
||||
} catch (e) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.error', e.Description);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
|
||||
// Remove all data for this app from local storage
|
||||
// Also optionally takes a regular expression string and removes the matching key-value pairs
|
||||
// Example use: localStorageService.clearAll();
|
||||
// Should be used mostly for development purposes
|
||||
var clearAllFromLocalStorage = function (regularExpression, type) {
|
||||
setStorageType(type);
|
||||
|
||||
// Setting both regular expressions independently
|
||||
// Empty strings result in catchall RegExp
|
||||
var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp();
|
||||
var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp();
|
||||
|
||||
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
|
||||
if (!browserSupportsLocalStorage) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
|
||||
}
|
||||
return clearAllFromCookies();
|
||||
}
|
||||
if (!browserSupportsLocalStorage && !self.defaultToCookie)
|
||||
return false;
|
||||
var prefixLength = prefix.length;
|
||||
|
||||
for (var key in webStorage) {
|
||||
// Only remove items that are for this app and match the regular expression
|
||||
if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) {
|
||||
try {
|
||||
removeFromLocalStorage(key.substr(prefixLength));
|
||||
} catch (e) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
|
||||
return clearAllFromCookies();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Checks the browser to see if cookies are supported
|
||||
var browserSupportsCookies = (function() {
|
||||
try {
|
||||
return $window.navigator.cookieEnabled ||
|
||||
("cookie" in $document && ($document.cookie.length > 0 ||
|
||||
($document.cookie = "test").indexOf.call($document.cookie, "test") > -1));
|
||||
} catch (e) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
|
||||
return false;
|
||||
}
|
||||
}());
|
||||
|
||||
// Directly adds a value to cookies
|
||||
// Typically used as a fallback if local storage is not available in the browser
|
||||
// Example use: localStorageService.cookie.add('library','angular');
|
||||
var addToCookies = function (key, value, daysToExpiry, secure) {
|
||||
|
||||
if (isUndefined(value)) {
|
||||
return false;
|
||||
} else if(isArray(value) || isObject(value)) {
|
||||
value = toJson(value);
|
||||
}
|
||||
|
||||
if (!browserSupportsCookies) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var expiry = '',
|
||||
expiryDate = new Date(),
|
||||
cookieDomain = '';
|
||||
|
||||
if (value === null) {
|
||||
// Mark that the cookie has expired one day ago
|
||||
expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000));
|
||||
expiry = "; expires=" + expiryDate.toGMTString();
|
||||
value = '';
|
||||
} else if (isNumber(daysToExpiry) && daysToExpiry !== 0) {
|
||||
expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000));
|
||||
expiry = "; expires=" + expiryDate.toGMTString();
|
||||
} else if (cookie.expiry !== 0) {
|
||||
expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000));
|
||||
expiry = "; expires=" + expiryDate.toGMTString();
|
||||
}
|
||||
if (!!key) {
|
||||
var cookiePath = "; path=" + cookie.path;
|
||||
if (cookie.domain) {
|
||||
cookieDomain = "; domain=" + cookie.domain;
|
||||
}
|
||||
/* Providing the secure parameter always takes precedence over config
|
||||
* (allows developer to mix and match secure + non-secure) */
|
||||
if (typeof secure === 'boolean') {
|
||||
if (secure === true) {
|
||||
/* We've explicitly specified secure,
|
||||
* add the secure attribute to the cookie (after domain) */
|
||||
cookieDomain += "; secure";
|
||||
}
|
||||
// else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says
|
||||
}
|
||||
else if (cookie.secure === true) {
|
||||
// secure parameter wasn't specified, get default from config
|
||||
cookieDomain += "; secure";
|
||||
}
|
||||
$document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain;
|
||||
}
|
||||
} catch (e) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Directly get a value from a cookie
|
||||
// Example use: localStorageService.cookie.get('library'); // returns 'angular'
|
||||
var getFromCookies = function (key) {
|
||||
if (!browserSupportsCookies) {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
|
||||
return false;
|
||||
}
|
||||
|
||||
var cookies = $document.cookie && $document.cookie.split(';') || [];
|
||||
for(var i=0; i < cookies.length; i++) {
|
||||
var thisCookie = cookies[i];
|
||||
while (thisCookie.charAt(0) === ' ') {
|
||||
thisCookie = thisCookie.substring(1,thisCookie.length);
|
||||
}
|
||||
if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) {
|
||||
var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length));
|
||||
try {
|
||||
return JSON.parse(storedValues);
|
||||
} catch(e) {
|
||||
return storedValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
var removeFromCookies = function (key) {
|
||||
addToCookies(key,null);
|
||||
};
|
||||
|
||||
var clearAllFromCookies = function () {
|
||||
var thisCookie = null;
|
||||
var prefixLength = prefix.length;
|
||||
var cookies = $document.cookie.split(';');
|
||||
for(var i = 0; i < cookies.length; i++) {
|
||||
thisCookie = cookies[i];
|
||||
|
||||
while (thisCookie.charAt(0) === ' ') {
|
||||
thisCookie = thisCookie.substring(1, thisCookie.length);
|
||||
}
|
||||
|
||||
var key = thisCookie.substring(prefixLength, thisCookie.indexOf('='));
|
||||
removeFromCookies(key);
|
||||
}
|
||||
};
|
||||
|
||||
var getStorageType = function() {
|
||||
return storageType;
|
||||
};
|
||||
|
||||
var setStorageType = function(type) {
|
||||
if (type && storageType !== type) {
|
||||
storageType = type;
|
||||
browserSupportsLocalStorage = checkSupport();
|
||||
}
|
||||
return browserSupportsLocalStorage;
|
||||
};
|
||||
|
||||
// Add a listener on scope variable to save its changes to local storage
|
||||
// Return a function which when called cancels binding
|
||||
var bindToScope = function(scope, key, def, lsKey, type) {
|
||||
lsKey = lsKey || key;
|
||||
var value = getFromLocalStorage(lsKey, type);
|
||||
|
||||
if (value === null && isDefined(def)) {
|
||||
value = def;
|
||||
} else if (isObject(value) && isObject(def)) {
|
||||
value = extend(value, def);
|
||||
}
|
||||
|
||||
$parse(key).assign(scope, value);
|
||||
|
||||
return scope.$watch(key, function(newVal) {
|
||||
addToLocalStorage(lsKey, newVal, type);
|
||||
}, isObject(scope[key]));
|
||||
};
|
||||
|
||||
// Add listener to local storage, for update callbacks.
|
||||
if (browserSupportsLocalStorage) {
|
||||
if ($window.addEventListener) {
|
||||
$window.addEventListener("storage", handleStorageChangeCallback, false);
|
||||
$rootScope.$on('$destroy', function() {
|
||||
$window.removeEventListener("storage", handleStorageChangeCallback);
|
||||
});
|
||||
} else if($window.attachEvent){
|
||||
// attachEvent and detachEvent are proprietary to IE v6-10
|
||||
$window.attachEvent("onstorage", handleStorageChangeCallback);
|
||||
$rootScope.$on('$destroy', function() {
|
||||
$window.detachEvent("onstorage", handleStorageChangeCallback);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Callback handler for storage changed.
|
||||
function handleStorageChangeCallback(e) {
|
||||
if (!e) { e = $window.event; }
|
||||
if (notify.setItem) {
|
||||
if (isKeyPrefixOurs(e.key)) {
|
||||
var key = underiveQualifiedKey(e.key);
|
||||
// Use timeout, to avoid using $rootScope.$apply.
|
||||
$timeout(function () {
|
||||
$rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return localStorageService.length
|
||||
// ignore keys that not owned
|
||||
var lengthOfLocalStorage = function(type) {
|
||||
setStorageType(type);
|
||||
|
||||
var count = 0;
|
||||
var storage = $window[storageType];
|
||||
for(var i = 0; i < storage.length; i++) {
|
||||
if(storage.key(i).indexOf(prefix) === 0 ) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
return {
|
||||
isSupported: browserSupportsLocalStorage,
|
||||
getStorageType: getStorageType,
|
||||
setStorageType: setStorageType,
|
||||
set: addToLocalStorage,
|
||||
add: addToLocalStorage, //DEPRECATED
|
||||
get: getFromLocalStorage,
|
||||
keys: getKeysForLocalStorage,
|
||||
remove: removeFromLocalStorage,
|
||||
clearAll: clearAllFromLocalStorage,
|
||||
bind: bindToScope,
|
||||
deriveKey: deriveQualifiedKey,
|
||||
underiveKey: underiveQualifiedKey,
|
||||
length: lengthOfLocalStorage,
|
||||
defaultToCookie: this.defaultToCookie,
|
||||
cookie: {
|
||||
isSupported: browserSupportsCookies,
|
||||
set: addToCookies,
|
||||
add: addToCookies, //DEPRECATED
|
||||
get: getFromCookies,
|
||||
remove: removeFromCookies,
|
||||
clearAll: clearAllFromCookies
|
||||
}
|
||||
};
|
||||
}];
|
||||
});
|
||||
})(window, window.angular);
|
||||
@@ -1,5 +1,4 @@
|
||||
window.OFNShared = angular.module("OFNShared", [
|
||||
"mm.foundation",
|
||||
"LocalStorageModule"
|
||||
]).config ($httpProvider) ->
|
||||
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"
|
||||
|
||||
@@ -44,9 +44,9 @@ module Admin
|
||||
|
||||
create_connected_app
|
||||
|
||||
jwt_service = VineJwtService.new(secret: connected_app_params[:vine_secret])
|
||||
vine_api = VineApiService.new(api_key: connected_app_params[:vine_api_key],
|
||||
jwt_generator: jwt_service)
|
||||
jwt_service = Vine::JwtService.new(secret: connected_app_params[:vine_secret])
|
||||
vine_api = Vine::ApiService.new(api_key: connected_app_params[:vine_api_key],
|
||||
jwt_generator: jwt_service)
|
||||
|
||||
if !@app.connect(api_key: connected_app_params[:vine_api_key],
|
||||
secret: connected_app_params[:vine_secret], vine_api:)
|
||||
@@ -77,7 +77,7 @@ module Admin
|
||||
|
||||
def log_and_notify_exception(exception)
|
||||
Rails.logger.error exception.inspect
|
||||
Bugsnag.notify(exception)
|
||||
Alert.raise(exception)
|
||||
end
|
||||
|
||||
def vine_params_empty?
|
||||
|
||||
@@ -19,15 +19,12 @@ module Admin
|
||||
.find(params.require(:enterprise_id))
|
||||
|
||||
catalog_url = params.require(:catalog_url)
|
||||
|
||||
json_catalog = fetch_catalog(catalog_url)
|
||||
graph = DfcIo.import(json_catalog)
|
||||
catalog = DfcCatalog.load(spree_current_user, catalog_url)
|
||||
catalog.apply_wholesale_values!
|
||||
|
||||
# * First step: import all products for given enterprise.
|
||||
# * Second step: render table and let user decide which ones to import.
|
||||
imported = graph.map do |subject|
|
||||
next unless subject.is_a? DataFoodConsortium::Connector::SuppliedProduct
|
||||
|
||||
imported = catalog.products.map do |subject|
|
||||
existing_variant = enterprise.supplied_variants.linked_to(subject.semanticId)
|
||||
|
||||
if existing_variant
|
||||
@@ -44,11 +41,5 @@ module Admin
|
||||
flash[:error] = e.message
|
||||
redirect_to admin_product_import_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_catalog(url)
|
||||
DfcRequest.new(spree_current_user).call(url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,13 +30,13 @@ module Admin
|
||||
def validate_data
|
||||
return unless process_data('validate')
|
||||
|
||||
render json: @importer.import_results, response: 200
|
||||
render json: @importer.import_results
|
||||
end
|
||||
|
||||
def save_data
|
||||
return unless process_data('save')
|
||||
|
||||
render json: @importer.save_results, response: 200
|
||||
render json: @importer.save_results
|
||||
end
|
||||
|
||||
def reset_absent_products
|
||||
@@ -76,7 +76,7 @@ module Admin
|
||||
begin
|
||||
@importer.public_send("#{method}_entries")
|
||||
rescue StandardError => e
|
||||
render json: e.message, response: 500
|
||||
render plain: e.message, status: :internal_server_error
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ module Admin
|
||||
|
||||
def product_scope
|
||||
user = spree_current_user
|
||||
scope = if user.has_spree_role?("admin") || user.enterprises.present?
|
||||
scope = if user.admin? || user.enterprises.present?
|
||||
Spree::Product
|
||||
else
|
||||
Spree::Product.active
|
||||
|
||||
@@ -71,6 +71,12 @@ module Admin
|
||||
|
||||
def load_collection
|
||||
collection_hash = Hash[variant_overrides_params.each_with_index.map { |vo, i| [i, vo] }]
|
||||
|
||||
# Reset count_on_hand when switching to producer settings:
|
||||
collection_hash.each_value do |vo|
|
||||
vo["count_on_hand"] = nil if vo.fetch("on_demand", :unchanged).nil?
|
||||
end
|
||||
|
||||
@vo_set = Sets::VariantOverrideSet.new(@variant_overrides,
|
||||
collection_attributes: collection_hash)
|
||||
end
|
||||
|
||||
@@ -66,7 +66,7 @@ module Api
|
||||
end
|
||||
|
||||
def error_during_processing(exception)
|
||||
Bugsnag.notify(exception)
|
||||
Alert.raise(exception)
|
||||
|
||||
render(json: { exception: exception.message },
|
||||
status: :unprocessable_entity) && return
|
||||
|
||||
@@ -14,7 +14,7 @@ module Api
|
||||
def create
|
||||
variant = scoped_variant(params[:variant_id])
|
||||
quantity = params[:quantity].to_i
|
||||
@shipment = get_or_create_shipment(params[:stock_location_id])
|
||||
@shipment = @order.shipment || @order.shipments.create
|
||||
|
||||
@order.contents.add(variant, quantity, @shipment)
|
||||
|
||||
@@ -24,6 +24,7 @@ module Api
|
||||
Orders::WorkflowService.new(@order).advance_to_payment if @order.line_items.any?
|
||||
|
||||
@order.recreate_all_fees!
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
|
||||
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
|
||||
end
|
||||
@@ -73,6 +74,7 @@ module Api
|
||||
|
||||
@order.contents.add(variant, quantity, @shipment)
|
||||
@order.recreate_all_fees!
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
|
||||
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
|
||||
end
|
||||
@@ -86,6 +88,7 @@ module Api
|
||||
@shipment.reload if @shipment.persisted?
|
||||
|
||||
@order.recreate_all_fees!
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
|
||||
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
|
||||
end
|
||||
@@ -113,10 +116,6 @@ module Api
|
||||
variant
|
||||
end
|
||||
|
||||
def get_or_create_shipment(stock_location_id)
|
||||
@order.shipment || @order.shipments.create(stock_location_id:)
|
||||
end
|
||||
|
||||
def shipment_params
|
||||
return {} unless params.has_key? :shipment
|
||||
|
||||
|
||||
@@ -55,14 +55,14 @@ module Api
|
||||
|
||||
def scope
|
||||
if @product
|
||||
variants = if current_api_user.has_spree_role?("admin") || params[:show_deleted]
|
||||
variants = if current_api_user.admin? || params[:show_deleted]
|
||||
@product.variants.with_deleted
|
||||
else
|
||||
@product.variants
|
||||
end
|
||||
else
|
||||
variants = Spree::Variant.where(nil)
|
||||
if current_api_user.has_spree_role?("admin")
|
||||
if current_api_user.admin?
|
||||
unless params[:show_deleted]
|
||||
variants = Spree::Variant.active
|
||||
end
|
||||
|
||||
@@ -52,7 +52,7 @@ module Api
|
||||
end
|
||||
|
||||
def error_during_processing(exception)
|
||||
Bugsnag.notify(exception)
|
||||
Alert.raise(exception)
|
||||
|
||||
if Rails.env.development? || Rails.env.test?
|
||||
render status: :unprocessable_entity,
|
||||
|
||||
@@ -11,7 +11,7 @@ class CartController < BaseController
|
||||
order.cap_quantity_at_stock!
|
||||
order.recreate_all_fees!
|
||||
|
||||
StockSyncJob.sync_linked_catalogs(order)
|
||||
StockSyncJob.sync_linked_catalogs_later(order)
|
||||
|
||||
render json: { error: false, stock_levels: stock_levels(order) }, status: :ok
|
||||
else
|
||||
|
||||
@@ -78,8 +78,21 @@ class CheckoutController < BaseController
|
||||
|
||||
return true if redirect_to_payment_gateway
|
||||
|
||||
# Redeem VINE voucher
|
||||
vine_voucher_redeemer = Vine::VoucherRedeemerService.new(order: @order)
|
||||
unless vine_voucher_redeemer.redeem
|
||||
# rubocop:disable Rails/DeprecatedActiveModelErrorsMethods
|
||||
flash[:error] = if vine_voucher_redeemer.errors.keys.include?(:redeeming_failed)
|
||||
vine_voucher_redeemer.errors[:redeeming_failed]
|
||||
else
|
||||
I18n.t('checkout.errors.voucher_redeeming_error')
|
||||
end
|
||||
return false
|
||||
# rubocop:enable Rails/DeprecatedActiveModelErrorsMethods
|
||||
end
|
||||
@order.process_payments!
|
||||
@order.confirm!
|
||||
BackorderJob.check_stock(@order)
|
||||
order_completion_reset @order
|
||||
end
|
||||
|
||||
|
||||
@@ -50,9 +50,7 @@ module OrderCompletion
|
||||
end
|
||||
|
||||
def order_invalid!
|
||||
Bugsnag.notify("Notice: invalid order loaded during checkout") do |payload|
|
||||
payload.add_metadata :order, :order, @order
|
||||
end
|
||||
Alert.raise_with_record("Notice: invalid order loaded during checkout", @order)
|
||||
|
||||
flash[:error] = t('checkout.order_not_loaded')
|
||||
redirect_to main_app.shop_path
|
||||
@@ -92,9 +90,7 @@ module OrderCompletion
|
||||
end
|
||||
|
||||
def notify_failure(error = RuntimeError.new(order_processing_error))
|
||||
Bugsnag.notify(error) do |payload|
|
||||
payload.add_metadata :order, @order
|
||||
end
|
||||
Alert.raise_with_record(error, @order)
|
||||
flash[:error] = order_processing_error if flash.blank?
|
||||
end
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ module OrderStockCheck
|
||||
def check_order_cycle_expiry
|
||||
return unless current_order_cycle&.closed?
|
||||
|
||||
Bugsnag.notify("Notice: order cycle closed during checkout completion") do |payload|
|
||||
payload.add_metadata :order, :order, current_order
|
||||
end
|
||||
Alert.raise_with_record("Notice: order cycle closed during checkout completion", current_order)
|
||||
current_order.empty!
|
||||
current_order.set_order_cycle! nil
|
||||
|
||||
|
||||
@@ -4,11 +4,6 @@ class ErrorsController < ApplicationController
|
||||
layout "errors"
|
||||
|
||||
def not_found
|
||||
Bugsnag.notify("404") do |event|
|
||||
event.severity = "info"
|
||||
|
||||
event.add_metadata(:request, :env, request.env)
|
||||
end
|
||||
render status: :not_found, formats: :html
|
||||
end
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ module Spree
|
||||
@order.restock_items = params.fetch(:restock_items, "true") == "true"
|
||||
|
||||
if @order.public_send(event.to_s)
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
flash[:success] = Spree.t(:order_updated)
|
||||
else
|
||||
flash[:error] = Spree.t(:cannot_perform_operation)
|
||||
|
||||
@@ -24,9 +24,12 @@ module Spree
|
||||
end
|
||||
|
||||
def create
|
||||
# Try to redeem VINE voucher first as we don't want to create a payment and complete
|
||||
# the order if it fails
|
||||
return redirect_to spree.admin_order_payments_path(@order) unless redeem_vine_voucher
|
||||
|
||||
@payment = @order.payments.build(object_params)
|
||||
load_payment_source
|
||||
|
||||
begin
|
||||
unless @payment.save
|
||||
redirect_to spree.admin_order_payments_path(@order)
|
||||
@@ -51,6 +54,10 @@ module Spree
|
||||
event = params[:e]
|
||||
return unless event && @payment.payment_source
|
||||
|
||||
# capture_and_complete_order will complete the order, so we want to try to redeem VINE
|
||||
# voucher first and exit if it fails
|
||||
return if event == "capture_and_complete_order" && !redeem_vine_voucher
|
||||
|
||||
# Because we have a transition method also called void, we do this to avoid conflicts.
|
||||
event = "void_transaction" if event == "void"
|
||||
if allowed_events.include?(event) && @payment.public_send("#{event}!")
|
||||
@@ -60,7 +67,7 @@ module Spree
|
||||
end
|
||||
rescue StandardError => e
|
||||
logger.error e.message
|
||||
Bugsnag.notify(e)
|
||||
Alert.raise(e)
|
||||
flash[:error] = e.message
|
||||
ensure
|
||||
redirect_to request.referer
|
||||
@@ -182,6 +189,22 @@ module Spree
|
||||
%w{capture void_transaction credit refund resend_authorization_email
|
||||
capture_and_complete_order}
|
||||
end
|
||||
|
||||
def redeem_vine_voucher
|
||||
vine_voucher_redeemer = Vine::VoucherRedeemerService.new(order: @order)
|
||||
if vine_voucher_redeemer.redeem == false
|
||||
# rubocop:disable Rails/DeprecatedActiveModelErrorsMethods
|
||||
flash[:error] = if vine_voucher_redeemer.errors.keys.include?(:redeeming_failed)
|
||||
vine_voucher_redeemer.errors[:redeeming_failed]
|
||||
else
|
||||
I18n.t('checkout.errors.voucher_redeeming_error')
|
||||
end
|
||||
# rubocop:enable Rails/DeprecatedActiveModelErrorsMethods
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,7 @@ module Spree
|
||||
include OrderCyclesHelper
|
||||
include EnterprisesHelper
|
||||
helper ::Admin::ProductsHelper
|
||||
helper Spree::Admin::TaxCategoriesHelper
|
||||
|
||||
before_action :load_data
|
||||
before_action :load_producers, only: [:index, :new]
|
||||
@@ -213,7 +214,7 @@ module Spree
|
||||
end
|
||||
|
||||
def notify_bugsnag(error, product, variant)
|
||||
Bugsnag.notify(error) do |report|
|
||||
Alert.raise(error) do |report|
|
||||
report.add_metadata(:product,
|
||||
{ product: product.attributes, variant: variant.attributes })
|
||||
report.add_metadata(:product, :product_error, product.errors.first) unless product.valid?
|
||||
|
||||
@@ -11,8 +11,6 @@ module Spree
|
||||
|
||||
# http://spreecommerce.com/blog/2010/11/02/json-hijacking-vulnerability/
|
||||
before_action :check_json_authenticity, only: :index
|
||||
before_action :load_roles, only: [:edit, :new, :update, :create,
|
||||
:generate_api_key, :clear_api_key]
|
||||
|
||||
def index
|
||||
respond_with(@collection) do |format|
|
||||
@@ -22,17 +20,9 @@ module Spree
|
||||
end
|
||||
|
||||
def create
|
||||
if params[:user]
|
||||
roles = params[:user].delete("spree_role_ids")
|
||||
end
|
||||
|
||||
@user = Spree::User.new(user_params)
|
||||
if @user.save
|
||||
|
||||
if roles
|
||||
@user.spree_roles = roles.compact_blank.collect{ |r| Spree::Role.find(r) }
|
||||
end
|
||||
|
||||
flash[:success] = Spree.t(:created_successfully)
|
||||
redirect_to edit_admin_user_path(@user)
|
||||
else
|
||||
@@ -41,15 +31,7 @@ module Spree
|
||||
end
|
||||
|
||||
def update
|
||||
if params[:user]
|
||||
roles = params[:user].delete("spree_role_ids")
|
||||
end
|
||||
|
||||
if @user.update(user_params)
|
||||
if roles
|
||||
@user.spree_roles = roles.compact_blank.collect{ |r| Spree::Role.find(r) }
|
||||
end
|
||||
|
||||
flash[:success] = update_message
|
||||
redirect_to edit_admin_user_path(@user)
|
||||
else
|
||||
@@ -123,10 +105,6 @@ module Spree
|
||||
sign_in(@user, event: :authentication, bypass: true)
|
||||
end
|
||||
|
||||
def load_roles
|
||||
@roles = Spree::Role.where(nil)
|
||||
end
|
||||
|
||||
def new_email_unconfirmed?
|
||||
params[:user][:email] != @user.email
|
||||
end
|
||||
@@ -137,7 +115,7 @@ module Spree
|
||||
|
||||
def user_params
|
||||
::PermittedAttributes::User.new(params).call(
|
||||
%i[enterprise_limit show_api_key_view]
|
||||
%i[admin enterprise_limit show_api_key_view]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
module Spree
|
||||
class ApiKeysController < ::BaseController
|
||||
include Spree::Core::ControllerHelpers
|
||||
include I18nHelper
|
||||
|
||||
prepend_before_action :load_object
|
||||
|
||||
@@ -70,14 +70,15 @@ module Spree
|
||||
@order.recreate_all_fees! # Enterprise fees on line items and on the order itself
|
||||
|
||||
# Re apply the voucher
|
||||
VoucherAdjustmentsService.new(@order).update
|
||||
@order.update_totals_and_states
|
||||
OrderManagement::Order::Updater.new(@order).update_voucher
|
||||
|
||||
if @order.complete?
|
||||
@order.update_payment_fees!
|
||||
@order.create_tax_charge!
|
||||
end
|
||||
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
|
||||
respond_with(@order) do |format|
|
||||
format.html do
|
||||
if params.key?(:checkout)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
module Spree
|
||||
class UsersController < ::BaseController
|
||||
include Spree::Core::ControllerHelpers
|
||||
include I18nHelper
|
||||
include CablecarResponses
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'open_food_network/error_logger'
|
||||
require "spree/core/controller_helpers/auth"
|
||||
require "spree/core/controller_helpers/common"
|
||||
require "spree/core/controller_helpers/order"
|
||||
@@ -37,7 +36,7 @@ class UserRegistrationsController < Devise::RegistrationsController
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
OpenFoodNetwork::ErrorLogger.notify(e)
|
||||
Alert.raise(e)
|
||||
render_error(message: I18n.t('unknown_error', scope: I18N_SCOPE))
|
||||
end
|
||||
|
||||
|
||||
@@ -4,7 +4,16 @@ class VoucherAdjustmentsController < BaseController
|
||||
before_action :set_order
|
||||
|
||||
def create
|
||||
if add_voucher
|
||||
if voucher_params[:voucher_code].blank?
|
||||
@order.errors.add(:voucher_code, I18n.t('checkout.errors.voucher_not_found'))
|
||||
return render_error
|
||||
end
|
||||
|
||||
voucher = load_voucher
|
||||
|
||||
return render_error unless valid_voucher?(voucher)
|
||||
|
||||
if add_voucher_to_order(voucher)
|
||||
update_payment_section
|
||||
elsif @order.errors.present?
|
||||
render_error
|
||||
@@ -30,19 +39,28 @@ class VoucherAdjustmentsController < BaseController
|
||||
@order = current_order
|
||||
end
|
||||
|
||||
def add_voucher
|
||||
if voucher_params[:voucher_code].blank?
|
||||
@order.errors.add(:voucher_code, I18n.t('checkout.errors.voucher_not_found'))
|
||||
return false
|
||||
end
|
||||
|
||||
voucher = Voucher.find_by(code: voucher_params[:voucher_code], enterprise: @order.distributor)
|
||||
def valid_voucher?(voucher)
|
||||
return false if @order.errors.present?
|
||||
|
||||
if voucher.nil?
|
||||
@order.errors.add(:voucher_code, I18n.t('checkout.errors.voucher_not_found'))
|
||||
return false
|
||||
end
|
||||
|
||||
if !voucher.valid?
|
||||
@order.errors.add(
|
||||
:voucher_code,
|
||||
I18n.t(
|
||||
'checkout.errors.create_voucher_error', error: voucher.errors.full_messages.to_sentence
|
||||
)
|
||||
)
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def add_voucher_to_order(voucher)
|
||||
adjustment = voucher.create_adjustment(voucher.code, @order)
|
||||
|
||||
unless adjustment.persisted?
|
||||
@@ -51,14 +69,38 @@ class VoucherAdjustmentsController < BaseController
|
||||
return false
|
||||
end
|
||||
|
||||
# calculate_voucher_adjustment
|
||||
clear_payments
|
||||
|
||||
VoucherAdjustmentsService.new(@order).update
|
||||
@order.update_totals_and_states
|
||||
OrderManagement::Order::Updater.new(@order).update_voucher
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def load_voucher
|
||||
voucher = Voucher.find_by(code: voucher_params[:voucher_code],
|
||||
enterprise: @order.distributor)
|
||||
return voucher unless voucher.nil? || voucher.is_a?(Vouchers::Vine)
|
||||
|
||||
vine_voucher
|
||||
end
|
||||
|
||||
def vine_voucher
|
||||
vine_voucher_validator = Vine::VoucherValidatorService.new(
|
||||
voucher_code: voucher_params[:voucher_code], enterprise: @order.distributor
|
||||
)
|
||||
voucher = vine_voucher_validator.validate
|
||||
|
||||
return nil if vine_voucher_validator.errors[:not_found_voucher].present?
|
||||
|
||||
if vine_voucher_validator.errors.present?
|
||||
@order.errors.add(:voucher_code, I18n.t('checkout.errors.add_voucher_error'))
|
||||
return nil
|
||||
end
|
||||
|
||||
voucher
|
||||
end
|
||||
|
||||
def update_payment_section
|
||||
render cable_ready: cable_car.replace(
|
||||
selector: "#checkout-payment-methods",
|
||||
|
||||
@@ -14,6 +14,7 @@ module Admin
|
||||
# e.g producer_options = [['producer name', id]]
|
||||
product.variants.build do |new_variant|
|
||||
new_variant.supplier_id = producer_options.first.second if producer_options.one?
|
||||
new_variant.tax_category_id = product.variants.first.tax_category_id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ module SharedHelper
|
||||
end
|
||||
|
||||
def admin_user?
|
||||
spree_current_user&.has_spree_role? 'admin'
|
||||
spree_current_user&.admin?
|
||||
end
|
||||
|
||||
def current_shop_products_path
|
||||
|
||||
20
app/helpers/spree/admin/tax_categories_helper.rb
Normal file
20
app/helpers/spree/admin/tax_categories_helper.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
module TaxCategoriesHelper
|
||||
def tax_category_dropdown_options(require_tax_category)
|
||||
if require_tax_category
|
||||
{
|
||||
include_blank: false,
|
||||
selected: Spree::TaxCategory.find_by(is_default: true)&.id
|
||||
}
|
||||
else
|
||||
{
|
||||
include_blank: t(:none),
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@
|
||||
module Spree
|
||||
module BaseHelper
|
||||
def available_countries
|
||||
checkout_zone = Zone.find_by(name: Spree::Config[:checkout_zone])
|
||||
checkout_zone = Zone.find_by(name: ENV.fetch("CHECKOUT_ZONE", nil))
|
||||
|
||||
countries = if checkout_zone && checkout_zone.kind == 'country'
|
||||
checkout_zone.countries
|
||||
|
||||
@@ -1,98 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# When orders are cancelled, we need to amend
|
||||
# When orders are created, adjusted or cancelled, we need to amend
|
||||
# an existing backorder as well.
|
||||
# We're not dealing with line item changes just yet.
|
||||
class AmendBackorderJob < ApplicationJob
|
||||
sidekiq_options retry: 0
|
||||
|
||||
def self.schedule_bulk_update_for(orders)
|
||||
# We can have one backorder per order cycle and distributor.
|
||||
groups = orders.group_by { |order| [order.order_cycle, order.distributor] }
|
||||
groups.each_value do |orders_with_same_backorder|
|
||||
# We need to trigger only one update per backorder.
|
||||
perform_later(orders_with_same_backorder.first)
|
||||
end
|
||||
end
|
||||
|
||||
def perform(order)
|
||||
OrderLocker.lock_order_and_variants(order) do
|
||||
amend_backorder(order)
|
||||
end
|
||||
end
|
||||
|
||||
# The following is a mix of the BackorderJob and the CompleteBackorderJob.
|
||||
# TODO: Move the common code into a re-usable service class.
|
||||
def amend_backorder(order)
|
||||
order_cycle = order.order_cycle
|
||||
distributor = order.distributor
|
||||
user = distributor.owner
|
||||
items = backorderable_items(order)
|
||||
backorder = BackorderUpdater.new.amend_backorder(order)
|
||||
|
||||
return if items.empty?
|
||||
if backorder
|
||||
user = order.distributor.owner
|
||||
urls = nil # Not needed to send order. The backorder id is the URL.
|
||||
FdcBackorderer.new(user, urls).send_order(backorder)
|
||||
elsif !order.order_cycle.closed?
|
||||
|
||||
# We are assuming that all variants are linked to the same wholesale
|
||||
# shop and its catalog:
|
||||
reference_link = items[0].variant.semantic_links[0].semantic_id
|
||||
urls = FdcUrlBuilder.new(reference_link)
|
||||
orderer = FdcBackorderer.new(user, urls)
|
||||
|
||||
backorder = orderer.find_open_order(order)
|
||||
|
||||
variants = order_cycle.variants_distributed_by(distributor)
|
||||
adjust_quantities(order_cycle, user, backorder, urls, variants)
|
||||
|
||||
FdcBackorderer.new(user, urls).send_order(backorder)
|
||||
end
|
||||
|
||||
# Check if we have enough stock to reduce the backorder.
|
||||
#
|
||||
# Our local stock can increase when users cancel their orders.
|
||||
# But stock levels could also have been adjusted manually. So we review all
|
||||
# quantities before finalising the order.
|
||||
def adjust_quantities(order_cycle, user, order, urls, variants)
|
||||
broker = FdcOfferBroker.new(user, urls)
|
||||
|
||||
order.lines.each do |line|
|
||||
line.quantity = line.quantity.to_i
|
||||
wholesale_product_id = line.offer.offeredItem.semanticId
|
||||
transformation = broker.wholesale_to_retail(wholesale_product_id)
|
||||
linked_variant = variants.linked_to(transformation.retail_product_id)
|
||||
|
||||
# Assumption: If a transformation is present then we only sell the retail
|
||||
# variant. If that can't be found, it was deleted and we'll ignore that
|
||||
# for now.
|
||||
next if linked_variant.nil?
|
||||
|
||||
# Find all line items for this order cycle
|
||||
# Update quantity accordingly
|
||||
if linked_variant.on_demand
|
||||
release_superfluous_stock(line, linked_variant, transformation)
|
||||
else
|
||||
aggregate_final_quantities(order_cycle, line, linked_variant, transformation)
|
||||
end
|
||||
# We don't have an order to amend but the order cycle is or will open.
|
||||
# We can assume that this job was triggered by an admin creating a new
|
||||
# order or adding backorderable items to an order.
|
||||
BackorderJob.new.place_backorder(order)
|
||||
end
|
||||
|
||||
# Clean up empty lines:
|
||||
order.lines.reject! { |line| line.quantity.zero? }
|
||||
end
|
||||
|
||||
# We look at all linked variants.
|
||||
def backorderable_items(order)
|
||||
order.line_items.select do |item|
|
||||
# TODO: scope variants to hub.
|
||||
# We are only supporting producer stock at the moment.
|
||||
item.variant.semantic_links.present?
|
||||
end
|
||||
end
|
||||
|
||||
def release_superfluous_stock(line, linked_variant, transformation)
|
||||
# Note that a division of integers dismisses the remainder, like `floor`:
|
||||
wholesale_items_contained_in_stock = linked_variant.on_hand / transformation.factor
|
||||
|
||||
# But maybe we didn't actually order that much:
|
||||
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
|
||||
line.quantity -= deductable_quantity
|
||||
|
||||
retail_stock_changes = deductable_quantity * transformation.factor
|
||||
linked_variant.on_hand -= retail_stock_changes
|
||||
end
|
||||
|
||||
def aggregate_final_quantities(order_cycle, line, variant, transformation)
|
||||
orders = order_cycle.orders.invoiceable
|
||||
quantity = Spree::LineItem.where(order: orders, variant:).sum(:quantity)
|
||||
wholesale_quantity = (quantity.to_f / transformation.factor).ceil
|
||||
line.quantity = wholesale_quantity
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,9 +19,7 @@ class BackorderJob < ApplicationJob
|
||||
rescue StandardError => e
|
||||
# Errors here shouldn't affect the checkout. So let's report them
|
||||
# separately:
|
||||
Bugsnag.notify(e) do |payload|
|
||||
payload.add_metadata(:order, :order, order)
|
||||
end
|
||||
Alert.raise_with_record(e, order)
|
||||
end
|
||||
|
||||
def perform(order)
|
||||
@@ -119,7 +117,8 @@ class BackorderJob < ApplicationJob
|
||||
end
|
||||
|
||||
def load_broker(user, urls)
|
||||
FdcOfferBroker.new(user, urls)
|
||||
catalog = DfcCatalog.load(user, urls.catalog_url)
|
||||
FdcOfferBroker.new(catalog)
|
||||
end
|
||||
|
||||
def place_order(user, order, orderer, backorder)
|
||||
|
||||
@@ -24,8 +24,7 @@ class CompleteBackorderJob < ApplicationJob
|
||||
|
||||
urls = FdcUrlBuilder.new(order.lines[0].offer.offeredItem.semanticId)
|
||||
|
||||
variants = order_cycle.variants_distributed_by(distributor)
|
||||
adjust_quantities(order_cycle, user, order, urls, variants)
|
||||
BackorderUpdater.new.update(order, user, distributor, order_cycle)
|
||||
|
||||
FdcBackorderer.new(user, urls).complete_order(order)
|
||||
|
||||
@@ -36,55 +35,4 @@ class CompleteBackorderJob < ApplicationJob
|
||||
|
||||
raise
|
||||
end
|
||||
|
||||
# Check if we have enough stock to reduce the backorder.
|
||||
#
|
||||
# Our local stock can increase when users cancel their orders.
|
||||
# But stock levels could also have been adjusted manually. So we review all
|
||||
# quantities before finalising the order.
|
||||
def adjust_quantities(order_cycle, user, order, urls, variants)
|
||||
broker = FdcOfferBroker.new(user, urls)
|
||||
|
||||
order.lines.each do |line|
|
||||
line.quantity = line.quantity.to_i
|
||||
wholesale_product_id = line.offer.offeredItem.semanticId
|
||||
transformation = broker.wholesale_to_retail(wholesale_product_id)
|
||||
linked_variant = variants.linked_to(transformation.retail_product_id)
|
||||
|
||||
# Assumption: If a transformation is present then we only sell the retail
|
||||
# variant. If that can't be found, it was deleted and we'll ignore that
|
||||
# for now.
|
||||
next if linked_variant.nil?
|
||||
|
||||
# Find all line items for this order cycle
|
||||
# Update quantity accordingly
|
||||
if linked_variant.on_demand
|
||||
release_superfluous_stock(line, linked_variant, transformation)
|
||||
else
|
||||
aggregate_final_quantities(order_cycle, line, linked_variant, transformation)
|
||||
end
|
||||
end
|
||||
|
||||
# Clean up empty lines:
|
||||
order.lines.reject! { |line| line.quantity.zero? }
|
||||
end
|
||||
|
||||
def release_superfluous_stock(line, linked_variant, transformation)
|
||||
# Note that a division of integers dismisses the remainder, like `floor`:
|
||||
wholesale_items_contained_in_stock = linked_variant.on_hand / transformation.factor
|
||||
|
||||
# But maybe we didn't actually order that much:
|
||||
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
|
||||
line.quantity -= deductable_quantity
|
||||
|
||||
retail_stock_changes = deductable_quantity * transformation.factor
|
||||
linked_variant.on_hand -= retail_stock_changes
|
||||
end
|
||||
|
||||
def aggregate_final_quantities(order_cycle, line, variant, transformation)
|
||||
orders = order_cycle.orders.invoiceable
|
||||
quantity = Spree::LineItem.where(order: orders, variant:).sum(:quantity)
|
||||
wholesale_quantity = (quantity.to_f / transformation.factor).ceil
|
||||
line.quantity = wholesale_quantity
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,11 +22,7 @@ class ReportJob < ApplicationJob
|
||||
|
||||
broadcast_result(channel, format, blob) if channel
|
||||
rescue StandardError => e
|
||||
Bugsnag.notify(e) do |payload|
|
||||
payload.add_metadata :report, {
|
||||
report_class:, user:, params:, format:
|
||||
}
|
||||
end
|
||||
Alert.raise(e, { report: { report_class:, user:, params:, format: } })
|
||||
|
||||
broadcast_error(channel)
|
||||
end
|
||||
|
||||
@@ -8,30 +8,12 @@ class StockSyncJob < ApplicationJob
|
||||
# product. These variants are rare though and we check first before we
|
||||
# enqueue a new job. That should save some time loading the order with
|
||||
# all the stock data to make this decision.
|
||||
def self.sync_linked_catalogs(order)
|
||||
user = order.distributor.owner
|
||||
catalog_ids(order).each do |catalog_id|
|
||||
perform_later(user, catalog_id)
|
||||
end
|
||||
rescue StandardError => e
|
||||
# Errors here shouldn't affect the shopping. So let's report them
|
||||
# separately:
|
||||
Bugsnag.notify(e) do |payload|
|
||||
payload.add_metadata(:order, :order, order)
|
||||
end
|
||||
def self.sync_linked_catalogs_later(order)
|
||||
sync_catalogs_by_perform_method(order, :perform_later)
|
||||
end
|
||||
|
||||
def self.sync_linked_catalogs_now(order)
|
||||
user = order.distributor.owner
|
||||
catalog_ids(order).each do |catalog_id|
|
||||
perform_now(user, catalog_id)
|
||||
end
|
||||
rescue StandardError => e
|
||||
# Errors here shouldn't affect the shopping. So let's report them
|
||||
# separately:
|
||||
Bugsnag.notify(e) do |payload|
|
||||
payload.add_metadata(:order, :order, order)
|
||||
end
|
||||
sync_catalogs_by_perform_method(order, :perform_now)
|
||||
end
|
||||
|
||||
def self.catalog_ids(order)
|
||||
@@ -44,7 +26,10 @@ class StockSyncJob < ApplicationJob
|
||||
end
|
||||
|
||||
def perform(user, catalog_id)
|
||||
products = load_products(user, catalog_id)
|
||||
catalog = DfcCatalog.load(user, catalog_id)
|
||||
catalog.apply_wholesale_values!
|
||||
|
||||
products = catalog.products
|
||||
products_by_id = products.index_by(&:semanticId)
|
||||
product_ids = products_by_id.keys
|
||||
variants = linked_variants(user.enterprises, product_ids)
|
||||
@@ -62,18 +47,23 @@ class StockSyncJob < ApplicationJob
|
||||
end
|
||||
end
|
||||
|
||||
def load_products(user, catalog_id)
|
||||
json_catalog = DfcRequest.new(user).call(catalog_id)
|
||||
graph = DfcIo.import(json_catalog)
|
||||
|
||||
graph.select do |subject|
|
||||
subject.is_a? DataFoodConsortium::Connector::SuppliedProduct
|
||||
end
|
||||
end
|
||||
|
||||
def linked_variants(enterprises, product_ids)
|
||||
Spree::Variant.where(supplier: enterprises)
|
||||
.includes(:semantic_links).references(:semantic_links)
|
||||
.where(semantic_links: { semantic_id: product_ids })
|
||||
end
|
||||
|
||||
def self.sync_catalogs_by_perform_method(order, perform_method)
|
||||
distributor = order.distributor
|
||||
return unless distributor
|
||||
|
||||
user = distributor.owner
|
||||
catalog_ids(order).each do |catalog_id|
|
||||
public_send(perform_method, user, catalog_id)
|
||||
end
|
||||
rescue StandardError => e
|
||||
# Errors here shouldn't affect the shopping. So let's report them
|
||||
# separately:
|
||||
Alert.raise_with_record(e, order)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,9 +55,7 @@ class SubscriptionConfirmJob < ApplicationJob
|
||||
if order.errors.any?
|
||||
send_failed_payment_email(order)
|
||||
else
|
||||
Bugsnag.notify(e) do |payload|
|
||||
payload.add_metadata :order, :order, order
|
||||
end
|
||||
Alert.raise_with_record(e, order)
|
||||
send_failed_payment_email(order, e.message)
|
||||
end
|
||||
end
|
||||
@@ -108,8 +106,6 @@ class SubscriptionConfirmJob < ApplicationJob
|
||||
record_and_log_error(:failed_payment, order, error_message)
|
||||
SubscriptionMailer.failed_payment_email(order).deliver_now
|
||||
rescue StandardError => e
|
||||
Bugsnag.notify(e) do |payload|
|
||||
payload.add_metadata :subscription_data, { order:, error_message: }
|
||||
end
|
||||
Alert.raise(e, { subscription_data: { order:, error_message: } })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,14 +79,19 @@ class ProducerMailer < ApplicationMailer
|
||||
def set_customer_data(line_items)
|
||||
return unless @coordinator.show_customer_names_to_suppliers?
|
||||
|
||||
@display_business_name = false
|
||||
line_items.map do |line_item|
|
||||
customer_code = line_item.order.customer&.code
|
||||
@display_business_name = true if customer_code.present?
|
||||
|
||||
{
|
||||
sku: line_item.variant.sku,
|
||||
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,
|
||||
last_name: line_item.order.billing_address.last_name
|
||||
last_name: line_item.order.billing_address.last_name,
|
||||
business_name: customer_code,
|
||||
}
|
||||
end.sort_by { |line_item| [line_item[:last_name].downcase, line_item[:first_name].downcase] }
|
||||
end
|
||||
|
||||
@@ -28,7 +28,7 @@ module Calculator
|
||||
# In theory it should never be called any more after this has been deployed.
|
||||
# If the message below doesn't show up in Bugsnag, we can safely delete this method and all
|
||||
# the related methods below it.
|
||||
Bugsnag.notify("Calculator::DefaultTax was called with legacy tax calculations")
|
||||
Alert.raise("Calculator::DefaultTax was called with legacy tax calculations")
|
||||
|
||||
calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(order.distributor,
|
||||
order.order_cycle)
|
||||
|
||||
@@ -78,11 +78,6 @@ module VariantStock
|
||||
on_demand || total_on_hand >= quantity
|
||||
end
|
||||
|
||||
# Moving Spree::StockLocation.fill_status to the variant enables us
|
||||
# to override this behaviour for variant overrides
|
||||
# We can have this responsibility here in the variant because there is
|
||||
# only one stock item per variant
|
||||
#
|
||||
# Here we depend only on variant.total_on_hand and variant.on_demand.
|
||||
# This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand.
|
||||
def fill_status(quantity)
|
||||
@@ -107,13 +102,15 @@ module VariantStock
|
||||
raise_error_if_no_stock_item_available
|
||||
|
||||
# Creates a stock movement: it updates stock_item.count_on_hand and fills backorders
|
||||
#
|
||||
# This is the original Spree::StockLocation#move,
|
||||
# except that we raise an error if the stock item is missing,
|
||||
# because, unlike Spree, we should always have exactly one stock item per variant.
|
||||
stock_item.stock_movements.create!(quantity:, originator:)
|
||||
end
|
||||
|
||||
# There shouldn't be any other stock items, because we should
|
||||
# have only one stock location.
|
||||
def stock_item
|
||||
stock_items.first
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Persists the single stock item associated to this variant. As defined in
|
||||
@@ -139,10 +136,4 @@ module VariantStock
|
||||
def overwrite_stock_levels(new_level)
|
||||
stock_item.adjust_count_on_hand(new_level.to_i - stock_item.count_on_hand)
|
||||
end
|
||||
|
||||
# There shouldn't be any other stock items, because we should
|
||||
# have only one stock location.
|
||||
def stock_item
|
||||
stock_items.first
|
||||
end
|
||||
end
|
||||
|
||||
31
app/models/concerns/vouchers/flat_ratable.rb
Normal file
31
app/models/concerns/vouchers/flat_ratable.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/concern"
|
||||
|
||||
module Vouchers
|
||||
module FlatRatable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
validates :amount,
|
||||
presence: true,
|
||||
numericality: { greater_than: 0 }
|
||||
end
|
||||
|
||||
def display_value
|
||||
Spree::Money.new(amount)
|
||||
end
|
||||
|
||||
# We limit adjustment to the maximum amount needed to cover the order, ie if the voucher
|
||||
# covers more than the order.total we only need to create an adjustment covering the order.total
|
||||
def compute_amount(order)
|
||||
-amount.clamp(0, order.pre_discount_total)
|
||||
end
|
||||
|
||||
def rate(order)
|
||||
amount = compute_amount(order)
|
||||
|
||||
amount / order.pre_discount_total
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -218,7 +218,7 @@ class Enterprise < ApplicationRecord
|
||||
}
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
joins(:enterprise_roles).where(enterprise_roles: { user_id: user.id })
|
||||
@@ -481,7 +481,7 @@ class Enterprise < ApplicationRecord
|
||||
|
||||
image_variant_url_for(image.variant(name))
|
||||
rescue StandardError => e
|
||||
Bugsnag.notify "Enterprise ##{id} #{image.try(:name)} error: #{e.message}"
|
||||
Alert.raise "Enterprise ##{id} #{image.try(:name)} error: #{e.message}"
|
||||
Rails.logger.error(e.message)
|
||||
|
||||
nil
|
||||
|
||||
@@ -28,7 +28,7 @@ class EnterpriseFee < ApplicationRecord
|
||||
scope :for_enterprises, lambda { |enterprises| where(enterprise_id: enterprises) }
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
where(enterprise_id: user.enterprises.select(&:id))
|
||||
|
||||
@@ -38,7 +38,7 @@ class EnterpriseGroup < ApplicationRecord
|
||||
scope :by_position, -> { order('position ASC') }
|
||||
scope :on_front_page, -> { where(on_front_page: true) }
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
where(owner_id: user.id)
|
||||
|
||||
@@ -75,7 +75,7 @@ class Exchange < ApplicationRecord
|
||||
}
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
joins("LEFT JOIN enterprises senders ON senders.id = exchanges.sender_id").
|
||||
|
||||
@@ -32,6 +32,6 @@ class Invoice < ApplicationRecord
|
||||
end
|
||||
|
||||
def previous_invoice
|
||||
order.invoices.where("id < ?", id).first
|
||||
order.invoices.where(id: ...id).first
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,7 +53,7 @@ class OrderCycle < ApplicationRecord
|
||||
Time.zone.now,
|
||||
Time.zone.now)
|
||||
}
|
||||
scope :active_or_complete, lambda { where('order_cycles.orders_open_at <= ?', Time.zone.now) }
|
||||
scope :active_or_complete, lambda { where(order_cycles: { orders_open_at: ..Time.zone.now }) }
|
||||
scope :inactive, lambda {
|
||||
where('order_cycles.orders_open_at > ? OR order_cycles.orders_close_at < ?',
|
||||
Time.zone.now,
|
||||
@@ -64,8 +64,8 @@ class OrderCycle < ApplicationRecord
|
||||
where('order_cycles.orders_close_at > ? OR order_cycles.orders_close_at IS NULL', Time.zone.now)
|
||||
}
|
||||
scope :closed, lambda {
|
||||
where('order_cycles.orders_close_at < ?',
|
||||
Time.zone.now).order("order_cycles.orders_close_at DESC")
|
||||
where(order_cycles: { orders_close_at: ...Time.zone.now })
|
||||
.order("order_cycles.orders_close_at DESC")
|
||||
}
|
||||
scope :unprocessed, -> { where(processed_at: nil) }
|
||||
scope :undated, -> { where('order_cycles.orders_open_at IS NULL OR orders_close_at IS NULL') }
|
||||
@@ -84,7 +84,7 @@ class OrderCycle < ApplicationRecord
|
||||
}
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
where(coordinator_id: user.enterprises.to_a)
|
||||
@@ -93,7 +93,7 @@ class OrderCycle < ApplicationRecord
|
||||
|
||||
# Return order cycles that user coordinates, sends to or receives from
|
||||
scope :visible_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
with_exchanging_enterprises_outer.
|
||||
|
||||
@@ -79,10 +79,9 @@ module ProductImport
|
||||
if entry.attributes['on_hand'].present?
|
||||
new_variant.on_hand = entry.attributes['on_hand']
|
||||
end
|
||||
check_on_hand_nil(entry, new_variant)
|
||||
end
|
||||
|
||||
check_on_hand_nil(entry, new_variant)
|
||||
|
||||
if new_variant.valid?
|
||||
entry.product_object = new_variant
|
||||
entry.validates_as = 'new_variant' unless entry.errors?
|
||||
@@ -161,7 +160,7 @@ module ProductImport
|
||||
end
|
||||
|
||||
def unit_fields_validation(entry)
|
||||
unit_types = ['g', 'oz', 'lb', 'kg', 't', 'ml', 'l', 'kl', '']
|
||||
unit_types = ['mg', 'g', 'kg', 'oz', 'lb', 't', 'ml', 'cl', 'dl', 'l', 'kl', 'gal', '']
|
||||
|
||||
if entry.units.blank?
|
||||
mark_as_invalid(entry, attribute: 'units',
|
||||
@@ -297,7 +296,7 @@ module ProductImport
|
||||
unscaled_units = entry.unscaled_units.to_f || 0
|
||||
entry.unit_value = unscaled_units * unit_scale unless unit_scale.nil?
|
||||
|
||||
if entry.match_inventory_variant?(existing_variant)
|
||||
if entry.match_variant?(existing_variant)
|
||||
variant_override = create_inventory_item(entry, existing_variant)
|
||||
return validate_inventory_item(entry, variant_override)
|
||||
end
|
||||
|
||||
@@ -85,10 +85,6 @@ module ProductImport
|
||||
end
|
||||
|
||||
def match_variant?(variant)
|
||||
match_display_name?(variant) && variant.unit_value.to_d == unscaled_units.to_d
|
||||
end
|
||||
|
||||
def match_inventory_variant?(variant)
|
||||
match_display_name?(variant) && variant.unit_value.to_d == unit_value.to_d
|
||||
end
|
||||
|
||||
|
||||
@@ -32,14 +32,18 @@ module ProductImport
|
||||
|
||||
def unit_scales
|
||||
{
|
||||
'mg' => { scale: 0.001, unit: 'weight' },
|
||||
'g' => { scale: 1, unit: 'weight' },
|
||||
'kg' => { scale: 1000, unit: 'weight' },
|
||||
'oz' => { scale: 28.35, unit: 'weight' },
|
||||
'lb' => { scale: 453.6, unit: 'weight' },
|
||||
't' => { scale: 1_000_000, unit: 'weight' },
|
||||
'ml' => { scale: 0.001, unit: 'volume' },
|
||||
'cl' => { scale: 0.01, unit: 'volume' },
|
||||
'dl' => { scale: 0.1, unit: 'volume' },
|
||||
'l' => { scale: 1, unit: 'volume' },
|
||||
'kl' => { scale: 1000, unit: 'volume' }
|
||||
'kl' => { scale: 1000, unit: 'volume' },
|
||||
'gal' => { scale: 4.54609, unit: 'volume' },
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ module Spree
|
||||
|
||||
user ||= Spree::User.new
|
||||
|
||||
if user.respond_to?(:has_spree_role?) && user.has_spree_role?('admin')
|
||||
if user.try(:admin?)
|
||||
can :manage, :all
|
||||
else
|
||||
can [:index, :read], Country
|
||||
|
||||
@@ -126,7 +126,7 @@ module Spree
|
||||
end
|
||||
|
||||
def address_and_city
|
||||
[address1, address2, city].select(&:present?).join(' ')
|
||||
[address1, address2, city].compact_blank.join(' ')
|
||||
end
|
||||
|
||||
private
|
||||
@@ -176,7 +176,7 @@ module Spree
|
||||
end
|
||||
|
||||
def render_address(parts)
|
||||
parts.select(&:present?).join(', ')
|
||||
parts.compact_blank.join(', ')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,14 +33,10 @@ module Spree
|
||||
preference :allow_backorder_shipping, :boolean, default: false
|
||||
preference :allow_checkout_on_gateway_error, :boolean, default: false
|
||||
preference :allow_guest_checkout, :boolean, default: true
|
||||
# Replace with the name of a zone if you would like to limit the countries
|
||||
preference :checkout_zone, :string, default: nil
|
||||
preference :currency, :string, default: "USD"
|
||||
preference :currency_decimal_mark, :string, default: "."
|
||||
preference :currency_symbol_position, :string, default: "before"
|
||||
preference :currency_thousands_separator, :string, default: ","
|
||||
preference :display_currency, :boolean, default: false
|
||||
preference :default_country_id, :integer
|
||||
preference :default_meta_description, :string, default: 'OFN demo site'
|
||||
preference :default_meta_keywords, :string, default: 'ofn, demo'
|
||||
preference :default_seo_title, :string, default: ''
|
||||
|
||||
@@ -34,7 +34,7 @@ module Spree
|
||||
|
||||
image_variant_url_for(variant(size))
|
||||
rescue StandardError => e
|
||||
Bugsnag.notify "Product ##{viewable_id} Image ##{id} error: #{e.message}"
|
||||
Alert.raise "Product ##{viewable_id} Image ##{id} error: #{e.message}"
|
||||
Rails.logger.error(e.message)
|
||||
|
||||
self.class.default_image_url(size)
|
||||
|
||||
@@ -35,18 +35,6 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
# This was refactored from a simpler query because the previous implementation
|
||||
# lead to issues once users tried to modify the objects returned. That's due
|
||||
# to ActiveRecord `joins(shipment: :stock_location)` only return readonly
|
||||
# objects
|
||||
#
|
||||
# Returns an array of backordered inventory units as per a given stock item
|
||||
def self.backordered_for_stock_item(stock_item)
|
||||
backordered_per_variant(stock_item).select do |unit|
|
||||
unit.shipment.stock_location == stock_item.stock_location
|
||||
end
|
||||
end
|
||||
|
||||
def self.finalize_units!(inventory_units)
|
||||
inventory_units.map do |iu|
|
||||
iu.update_columns(
|
||||
@@ -57,8 +45,7 @@ module Spree
|
||||
end
|
||||
|
||||
def find_stock_item
|
||||
Spree::StockItem.find_by(stock_location_id: shipment.stock_location_id,
|
||||
variant_id:)
|
||||
Spree::StockItem.find_by(variant_id:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -55,7 +55,7 @@ module Spree
|
||||
|
||||
# -- Scopes
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
# Find line items that are from orders distributed by the user or supplied by the user
|
||||
|
||||
@@ -106,7 +106,6 @@ module Spree
|
||||
before_validation :clone_billing_address, if: :use_billing?
|
||||
before_validation :ensure_customer
|
||||
|
||||
before_save :update_shipping_fees!, if: :complete?
|
||||
before_save :update_payment_fees!, if: :complete?
|
||||
before_create :link_by_email
|
||||
|
||||
@@ -125,7 +124,7 @@ module Spree
|
||||
}
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
# Find orders that are distributed by the user or have products supplied by the user
|
||||
@@ -140,7 +139,7 @@ module Spree
|
||||
}
|
||||
|
||||
scope :distributed_by_user, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
where(spree_orders: { distributor_id: user.enterprises.select(&:id) })
|
||||
@@ -392,8 +391,6 @@ module Spree
|
||||
|
||||
deliver_order_confirmation_email
|
||||
|
||||
BackorderJob.check_stock(self)
|
||||
|
||||
state_changes.create(
|
||||
previous_state: 'cart',
|
||||
next_state: 'complete',
|
||||
@@ -535,7 +532,7 @@ module Spree
|
||||
# because an outdated shipping fee is not as bad as a lost payment.
|
||||
# And the shipping fee is already up-to-date when this error occurs.
|
||||
# https://github.com/openfoodfoundation/openfoodnetwork/issues/3924
|
||||
Bugsnag.notify(e) do |report|
|
||||
Alert.raise(e) do |report|
|
||||
report.add_metadata(:order, attributes)
|
||||
report.add_metadata(:shipment, shipment.attributes)
|
||||
report.add_metadata(:shipment_in_db, Spree::Shipment.find_by(id: shipment.id).attributes)
|
||||
@@ -674,10 +671,6 @@ module Spree
|
||||
break if payment_total >= total
|
||||
|
||||
yield payment
|
||||
|
||||
if payment.completed?
|
||||
self.payment_total += payment.amount
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -142,8 +142,6 @@ module Spree
|
||||
|
||||
OrderMailer.cancel_email(id).deliver_later if send_cancellation_email
|
||||
update(payment_state: updater.update_payment_state)
|
||||
|
||||
AmendBackorderJob.perform_later(self)
|
||||
end
|
||||
|
||||
def after_resume
|
||||
|
||||
@@ -38,6 +38,7 @@ module Spree
|
||||
line_item.price = variant.price
|
||||
order.line_items << line_item
|
||||
end
|
||||
update_shipment
|
||||
|
||||
order.reload
|
||||
line_item
|
||||
|
||||
@@ -12,8 +12,8 @@ module Spree
|
||||
# have inventory assigned via +order.create_proposed_shipment+) or when
|
||||
# shipment is explicitly passed
|
||||
#
|
||||
# In case shipment is passed the stock location should only unstock or
|
||||
# restock items if the order is completed. That is so because stock items
|
||||
# In case shipment is passed stock should only be adjusted
|
||||
# if the order is completed. That is so because stock items
|
||||
# are always unstocked when the order is completed through +shipment.finalize+
|
||||
def verify(line_item, shipment = nil)
|
||||
if order.completed? || shipment.present?
|
||||
@@ -60,27 +60,24 @@ module Spree
|
||||
# Returns either one of the shipment:
|
||||
#
|
||||
# first unshipped that already includes this variant
|
||||
# first unshipped that's leaving from a stock_location that stocks this variant
|
||||
def determine_target_shipment(variant)
|
||||
target_shipment = order.shipments.detect do |shipment|
|
||||
(shipment.ready? || shipment.pending?) && shipment.contains?(variant)
|
||||
end
|
||||
|
||||
target_shipment || order.shipments.detect do |shipment|
|
||||
(shipment.ready? || shipment.pending?) &&
|
||||
variant.stock_location_ids.include?(shipment.stock_location_id)
|
||||
shipment.ready? || shipment.pending?
|
||||
end
|
||||
end
|
||||
|
||||
def add_to_shipment(shipment, variant, quantity)
|
||||
on_hand, back_order = shipment.stock_location.fill_status(variant, quantity)
|
||||
on_hand, back_order = variant.fill_status(quantity)
|
||||
|
||||
on_hand.times { shipment.set_up_inventory('on_hand', variant, order) }
|
||||
back_order.times { shipment.set_up_inventory('backordered', variant, order) }
|
||||
|
||||
# adding to this shipment, and removing from stock_location
|
||||
if order.completed?
|
||||
shipment.stock_location.unstock(variant, quantity, shipment)
|
||||
variant.move(-quantity, shipment)
|
||||
end
|
||||
|
||||
quantity
|
||||
@@ -103,9 +100,8 @@ module Spree
|
||||
end
|
||||
shipment.destroy if shipment.inventory_units.reload.count == 0
|
||||
|
||||
# removing this from shipment, and adding to stock_location
|
||||
if order.completed? && restock_item
|
||||
shipment.stock_location.restock variant, removed_quantity, shipment
|
||||
variant.move(removed_quantity, shipment)
|
||||
end
|
||||
|
||||
removed_quantity
|
||||
|
||||
@@ -168,7 +168,7 @@ module Spree
|
||||
scope :by_name, -> { order('spree_products.name') }
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
in_supplier(user.enterprises)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
class Role < ApplicationRecord
|
||||
has_and_belongs_to_many :users, join_table: 'spree_roles_users',
|
||||
class_name: "Spree::User"
|
||||
end
|
||||
end
|
||||
@@ -5,10 +5,10 @@ require 'ostruct'
|
||||
module Spree
|
||||
class Shipment < ApplicationRecord
|
||||
self.belongs_to_required_by_default = false
|
||||
self.ignored_columns += [:stock_location_id]
|
||||
|
||||
belongs_to :order, class_name: 'Spree::Order'
|
||||
belongs_to :address, class_name: 'Spree::Address'
|
||||
belongs_to :stock_location, class_name: 'Spree::StockLocation'
|
||||
|
||||
has_many :shipping_rates, dependent: :delete_all
|
||||
has_many :shipping_methods, through: :shipping_rates
|
||||
@@ -257,7 +257,7 @@ module Spree
|
||||
end
|
||||
|
||||
def to_package
|
||||
package = OrderManagement::Stock::Package.new(stock_location, order)
|
||||
package = OrderManagement::Stock::Package.new(order)
|
||||
grouped_inventory_units = inventory_units.includes(:variant).group_by do |iu|
|
||||
[iu.variant, iu.state_name]
|
||||
end
|
||||
@@ -313,11 +313,11 @@ module Spree
|
||||
end
|
||||
|
||||
def manifest_unstock(item)
|
||||
stock_location.unstock item.variant, item.quantity, self
|
||||
item.variant.move(-1 * item.quantity, self)
|
||||
end
|
||||
|
||||
def manifest_restock(item)
|
||||
stock_location.restock item.variant, item.quantity, self
|
||||
item.variant.move(item.quantity, self)
|
||||
end
|
||||
|
||||
def generate_shipment_number
|
||||
|
||||
@@ -37,7 +37,7 @@ module Spree
|
||||
after_save :touch_distributors
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
if user.admin?
|
||||
where(nil)
|
||||
else
|
||||
joins(:distributors).
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
module Spree
|
||||
class StockItem < ApplicationRecord
|
||||
self.ignored_columns += [:stock_location_id]
|
||||
|
||||
acts_as_paranoid
|
||||
|
||||
belongs_to :stock_location, class_name: 'Spree::StockLocation', inverse_of: :stock_items
|
||||
belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant'
|
||||
has_many :stock_movements, dependent: :destroy
|
||||
|
||||
validates :variant_id, uniqueness: { scope: [:stock_location_id, :deleted_at] }
|
||||
validates :variant_id, uniqueness: { scope: [:deleted_at] }
|
||||
validates :count_on_hand, numericality: { greater_than_or_equal_to: 0, unless: :backorderable? }
|
||||
|
||||
delegate :weight, to: :variant
|
||||
delegate :name, to: :variant, prefix: true
|
||||
|
||||
def backordered_inventory_units
|
||||
Spree::InventoryUnit.backordered_for_stock_item(self)
|
||||
Spree::InventoryUnit.backordered_per_variant(self)
|
||||
end
|
||||
|
||||
def adjust_count_on_hand(value)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
class StockLocation < ApplicationRecord
|
||||
self.belongs_to_required_by_default = false
|
||||
self.ignored_columns += [:backorderable_default, :active]
|
||||
|
||||
has_many :stock_items, dependent: :delete_all, inverse_of: :stock_location
|
||||
has_many :stock_movements, through: :stock_items
|
||||
|
||||
belongs_to :state, class_name: 'Spree::State'
|
||||
belongs_to :country, class_name: 'Spree::Country'
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
after_create :create_stock_items
|
||||
|
||||
# Wrapper for creating a new stock item respecting the backorderable config
|
||||
def stock_item(variant)
|
||||
stock_items.where(variant_id: variant).order(:id).first
|
||||
end
|
||||
|
||||
def stock_item_or_create(variant)
|
||||
stock_item(variant) || stock_items.create(variant:)
|
||||
end
|
||||
|
||||
def count_on_hand(variant)
|
||||
stock_item(variant).try(:count_on_hand)
|
||||
end
|
||||
|
||||
def backorderable?(variant)
|
||||
stock_item(variant).try(:backorderable?)
|
||||
end
|
||||
|
||||
def restock(variant, quantity, originator = nil)
|
||||
move(variant, quantity, originator)
|
||||
end
|
||||
|
||||
def unstock(variant, quantity, originator = nil)
|
||||
move(variant, -quantity, originator)
|
||||
end
|
||||
|
||||
def move(variant, quantity, originator = nil)
|
||||
variant.move(quantity, originator)
|
||||
end
|
||||
|
||||
def fill_status(variant, quantity)
|
||||
variant.fill_status(quantity)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_stock_items
|
||||
Variant.find_each { |variant| stock_items.create!(variant:) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -105,16 +105,7 @@ module Spree
|
||||
if default_zone_or_zone_match?(item.order)
|
||||
calculator.compute(item)
|
||||
else
|
||||
# Tax refund should not be possible with the way our production server are configured
|
||||
Bugsnag.notify(
|
||||
"Notice: Tax refund should not be possible, please check the default zone and " \
|
||||
"the tax rate zone configuration"
|
||||
) do |payload|
|
||||
payload.add_metadata :order_tax_zone, item.order.tax_zone
|
||||
payload.add_metadata :tax_rate_zone, zone
|
||||
payload.add_metadata :default_zone, Zone.default_tax
|
||||
end
|
||||
# In this case, it's a refund.
|
||||
# In this case, it's a refund (for instance offering a manual discount via an adjustment)
|
||||
calculator.compute(item) * - 1
|
||||
end
|
||||
else
|
||||
|
||||
@@ -18,17 +18,11 @@ module Spree
|
||||
belongs_to :ship_address, class_name: 'Spree::Address'
|
||||
belongs_to :bill_address, class_name: 'Spree::Address'
|
||||
|
||||
has_and_belongs_to_many :spree_roles,
|
||||
join_table: 'spree_roles_users',
|
||||
class_name: "Spree::Role"
|
||||
|
||||
before_validation :set_login
|
||||
after_create :associate_customers, :associate_orders
|
||||
before_destroy :check_completed_orders
|
||||
|
||||
roles_table_name = Role.table_name
|
||||
|
||||
scope :admin, lambda { includes(:spree_roles).where("#{roles_table_name}.name" => "admin") }
|
||||
scope :admin, -> { where(admin: true) }
|
||||
|
||||
has_many :enterprise_roles, dependent: :destroy
|
||||
has_many :enterprises, through: :enterprise_roles
|
||||
@@ -60,16 +54,6 @@ module Spree
|
||||
User.admin.count > 0
|
||||
end
|
||||
|
||||
# Whether a user has a role or not.
|
||||
def has_spree_role?(role_in_question)
|
||||
spree_roles.where(name: role_in_question.to_s).any?
|
||||
end
|
||||
|
||||
# Checks whether the specified user is a superadmin, with full control of the instance
|
||||
def admin?
|
||||
has_spree_role?('admin')
|
||||
end
|
||||
|
||||
# Send devise-based user emails asyncronously via ActiveJob
|
||||
# See: https://github.com/heartcombo/devise/tree/v3.5.10#activejob-integration
|
||||
def send_devise_notification(notification, *args)
|
||||
|
||||
@@ -39,7 +39,6 @@ module Spree
|
||||
has_many :line_items, inverse_of: :variant, dependent: nil
|
||||
|
||||
has_many :stock_items, dependent: :destroy, inverse_of: :variant
|
||||
has_many :stock_locations, through: :stock_items
|
||||
has_many :images, -> { order(:position) }, as: :viewable,
|
||||
dependent: :destroy,
|
||||
class_name: "Spree::Image"
|
||||
@@ -268,9 +267,7 @@ module Spree
|
||||
def create_stock_items
|
||||
return unless stock_items.empty?
|
||||
|
||||
StockLocation.find_each do |stock_location|
|
||||
stock_items.create!(stock_location:)
|
||||
end
|
||||
stock_items.create!
|
||||
end
|
||||
|
||||
def update_weight_from_unit_value
|
||||
@@ -293,7 +290,7 @@ module Spree
|
||||
end
|
||||
|
||||
def ensure_unit_value
|
||||
Bugsnag.notify("Trying to set unit_value to NaN") if unit_value&.nan?
|
||||
Alert.raise("Trying to set unit_value to NaN") if unit_value&.nan?
|
||||
return unless (variant_unit == "items" && unit_value.nil?) || unit_value&.nan?
|
||||
|
||||
self.unit_value = 1.0
|
||||
|
||||
@@ -7,6 +7,8 @@ module Spree
|
||||
has_and_belongs_to_many :shipping_methods, join_table: 'spree_shipping_methods_zones'
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
validates :zone_members, presence: true
|
||||
|
||||
after_save :remove_defunct_members
|
||||
after_save :remove_previous_default
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class StripeAccount < ApplicationRecord
|
||||
|
||||
destroy && Stripe::OAuth.deauthorize(stripe_user_id:)
|
||||
rescue Stripe::OAuth::OAuthError => e
|
||||
Bugsnag.notify(
|
||||
Alert.raise(
|
||||
e,
|
||||
stripe_account: stripe_user_id,
|
||||
enterprise_id:
|
||||
|
||||
@@ -48,8 +48,8 @@ class VariantOverride < ApplicationRecord
|
||||
|
||||
def move_stock!(quantity)
|
||||
unless stock_overridden?
|
||||
Bugsnag.notify RuntimeError.new "Attempting to move stock of a VariantOverride " \
|
||||
"without a count_on_hand specified."
|
||||
Alert.raise "Attempting to move stock of a VariantOverride " \
|
||||
"without a count_on_hand specified."
|
||||
return
|
||||
end
|
||||
|
||||
@@ -73,8 +73,8 @@ class VariantOverride < ApplicationRecord
|
||||
self.attributes = { on_demand: false, count_on_hand: default_stock }
|
||||
save
|
||||
else
|
||||
Bugsnag.notify RuntimeError.new "Attempting to reset stock level for a variant " \
|
||||
"with no default stock level."
|
||||
Alert.raise "Attempting to reset stock level for a variant " \
|
||||
"with no default stock level."
|
||||
end
|
||||
end
|
||||
self
|
||||
|
||||
@@ -14,7 +14,7 @@ class Voucher < ApplicationRecord
|
||||
class_name: 'Spree::Adjustment',
|
||||
dependent: nil
|
||||
|
||||
validates :code, presence: true, uniqueness: { scope: :enterprise_id }
|
||||
validates :code, presence: true
|
||||
|
||||
TYPES = ["Vouchers::FlatRate", "Vouchers::PercentageRate"].freeze
|
||||
|
||||
|
||||
@@ -2,24 +2,8 @@
|
||||
|
||||
module Vouchers
|
||||
class FlatRate < Voucher
|
||||
validates :amount,
|
||||
presence: true,
|
||||
numericality: { greater_than: 0 }
|
||||
include FlatRatable
|
||||
|
||||
def display_value
|
||||
Spree::Money.new(amount)
|
||||
end
|
||||
|
||||
# We limit adjustment to the maximum amount needed to cover the order, ie if the voucher
|
||||
# covers more than the order.total we only need to create an adjustment covering the order.total
|
||||
def compute_amount(order)
|
||||
-amount.clamp(0, order.pre_discount_total)
|
||||
end
|
||||
|
||||
def rate(order)
|
||||
amount = compute_amount(order)
|
||||
|
||||
amount / order.pre_discount_total
|
||||
end
|
||||
validates_with ScopedUniquenessValidator
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,7 @@ module Vouchers
|
||||
validates :amount,
|
||||
presence: true,
|
||||
numericality: { greater_than: 0, less_than_or_equal_to: 100 }
|
||||
validates_with ScopedUniquenessValidator
|
||||
|
||||
def display_value
|
||||
ActionController::Base.helpers.number_to_percentage(amount, precision: 2)
|
||||
|
||||
13
app/models/vouchers/vine.rb
Normal file
13
app/models/vouchers/vine.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
module Vouchers
|
||||
class Vine < Voucher
|
||||
include FlatRatable
|
||||
|
||||
# a VINE voucher :
|
||||
# - can potentially be associated with mutiple enterprise
|
||||
# - code ( "short code" in VINE ) can be recycled, but they shouldn't be linked to the same
|
||||
# voucher_id
|
||||
validates :code, uniqueness: { scope: [:enterprise_id, :external_voucher_id] }
|
||||
end
|
||||
end
|
||||
@@ -46,7 +46,7 @@ class ProductScopeQuery
|
||||
end
|
||||
|
||||
def product_scope
|
||||
if @user.has_spree_role?("admin") || @user.enterprises.present?
|
||||
if @user.admin? || @user.enterprises.present?
|
||||
scope = Spree::Product
|
||||
if @params[:show_deleted]
|
||||
scope = scope.with_deleted
|
||||
@@ -61,7 +61,7 @@ class ProductScopeQuery
|
||||
def product_query_includes
|
||||
[
|
||||
image: { attachment_attachment: :blob },
|
||||
variants: [:default_price, :stock_locations, :stock_items, :variant_overrides]
|
||||
variants: [:default_price, :stock_items, :variant_overrides]
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ module Api
|
||||
:terms_and_conditions_file_name, :terms_and_conditions_updated_at,
|
||||
:preferred_invoice_order_by_supplier, :preferred_product_low_stock_display,
|
||||
:visible, :hide_ofn_navigation, :white_label_logo,
|
||||
:white_label_logo_link
|
||||
:white_label_logo_link, :external_billing_id
|
||||
|
||||
has_one :owner, serializer: Api::Admin::UserSerializer
|
||||
has_many :users, serializer: Api::Admin::UserSerializer
|
||||
|
||||
@@ -11,7 +11,6 @@ module Api
|
||||
object.variants,
|
||||
each_serializer: Api::Admin::VariantSerializer,
|
||||
image: thumb_url,
|
||||
stock_location: Spree::StockLocation.first
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -6,6 +6,12 @@ module Api
|
||||
attributes :id, :hub_id, :variant_id, :sku, :price, :count_on_hand, :on_demand,
|
||||
:default_stock, :resettable, :tag_list, :tags, :import_date
|
||||
|
||||
def count_on_hand
|
||||
return if object.on_demand
|
||||
|
||||
object.count_on_hand
|
||||
end
|
||||
|
||||
def tag_list
|
||||
object.tag_list.join(",")
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ module Api
|
||||
attributes :id, :name, :producer_name, :image, :sku, :import_date, :tax_category_id,
|
||||
:options_text, :unit_value, :unit_description, :unit_to_display,
|
||||
:display_as, :display_name, :name_to_display, :variant_overrides_count,
|
||||
:price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name,
|
||||
:price, :on_demand, :on_hand, :in_stock,
|
||||
:variant_unit, :variant_unit_scale, :variant_unit_name, :variant_unit_with_scale
|
||||
|
||||
has_one :primary_taxon, key: :category_id, embed: :id
|
||||
@@ -44,18 +44,6 @@ module Api
|
||||
object.in_stock?
|
||||
end
|
||||
|
||||
def stock_location_id
|
||||
return if object.stock_items.empty?
|
||||
|
||||
options[:stock_location]&.id || object.stock_items.first.stock_location.id
|
||||
end
|
||||
|
||||
def stock_location_name
|
||||
return if object.stock_items.empty?
|
||||
|
||||
options[:stock_location]&.name || object.stock_items.first.stock_location.name
|
||||
end
|
||||
|
||||
def variant_overrides_count
|
||||
object.variant_overrides.count
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user