mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-22 20:16:50 +00:00
Compare commits
292 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af3aed827a | ||
|
|
f73be6447e | ||
|
|
98eabc9d0f | ||
|
|
169cbbe1a1 | ||
|
|
afc4c1e967 | ||
|
|
d76c4bddb0 | ||
|
|
ae993784d8 | ||
|
|
d1abe22c32 | ||
|
|
2817b8891e | ||
|
|
ab87610d91 | ||
|
|
54252f5444 | ||
|
|
7b6b0dbb78 | ||
|
|
b2e15f52cf | ||
|
|
5d18c48b6c | ||
|
|
0c2dcbc50d | ||
|
|
4a028b2238 | ||
|
|
43a366005c | ||
|
|
64470e977a | ||
|
|
a696c66857 | ||
|
|
cfeb0afbd4 | ||
|
|
4968e3dc8d | ||
|
|
5324747f89 | ||
|
|
ef2856d169 | ||
|
|
d9c79ee49c | ||
|
|
d1f9b0855d | ||
|
|
b82726e7ba | ||
|
|
7e7ab2e36d | ||
|
|
e35a5179bb | ||
|
|
85385a1989 | ||
|
|
a816814819 | ||
|
|
94b98867d8 | ||
|
|
35ef1b9c7f | ||
|
|
8badfb2505 | ||
|
|
d61acd2cc1 | ||
|
|
7417cee20a | ||
|
|
d489c77efe | ||
|
|
e2423ad612 | ||
|
|
8b036113d9 | ||
|
|
7f09044ae1 | ||
|
|
e9c7e1778c | ||
|
|
32cd14ef54 | ||
|
|
ad585f1eab | ||
|
|
d9368c1bfc | ||
|
|
b6bfb4e866 | ||
|
|
4d222c61c6 | ||
|
|
d599cf77a2 | ||
|
|
8f7505d53d | ||
|
|
867e17301f | ||
|
|
95135ca526 | ||
|
|
de063fecb1 | ||
|
|
ef9ca33913 | ||
|
|
2710eafc33 | ||
|
|
4718fdb0be | ||
|
|
ce6ae04147 | ||
|
|
1621f97fdb | ||
|
|
96f9894f41 | ||
|
|
66b519bd1c | ||
|
|
1b8e256e8a | ||
|
|
b73e529bfc | ||
|
|
25b1620707 | ||
|
|
5f9b14df9f | ||
|
|
922b853e3a | ||
|
|
8d747a2508 | ||
|
|
a50be52cde | ||
|
|
d62d002bc5 | ||
|
|
0c7448ba43 | ||
|
|
24afd40414 | ||
|
|
524aec7868 | ||
|
|
f2eb4b05f4 | ||
|
|
ffaf1b4ea0 | ||
|
|
eb547f4861 | ||
|
|
c9daca22d5 | ||
|
|
7b22740289 | ||
|
|
66c8a5c424 | ||
|
|
cfeac651b6 | ||
|
|
05315ff8e0 | ||
|
|
c4ee6b14ff | ||
|
|
a78f46259c | ||
|
|
1e79fde236 | ||
|
|
a9fe52a4ff | ||
|
|
075f5499f8 | ||
|
|
ed61f7e7bc | ||
|
|
bd6019036e | ||
|
|
0bbc3d2758 | ||
|
|
ae6182579b | ||
|
|
1e05811917 | ||
|
|
5f86a26f42 | ||
|
|
3f1b907ef2 | ||
|
|
d9c296cdb3 | ||
|
|
23aa762be2 | ||
|
|
61f2954973 | ||
|
|
d354317c73 | ||
|
|
19ef047193 | ||
|
|
037eb456c0 | ||
|
|
aed78f3138 | ||
|
|
c31416c536 | ||
|
|
917079931e | ||
|
|
46e54f48c9 | ||
|
|
059dceb304 | ||
|
|
f0abe650f6 | ||
|
|
282df9859e | ||
|
|
3474c60f4c | ||
|
|
503148b13b | ||
|
|
8442c7d334 | ||
|
|
f154de66f9 | ||
|
|
4a30493716 | ||
|
|
f325857e1f | ||
|
|
58872a7017 | ||
|
|
7392079d4d | ||
|
|
506126c1d3 | ||
|
|
fa004d0897 | ||
|
|
db7add88fe | ||
|
|
b14cd08990 | ||
|
|
f21aca234c | ||
|
|
93a6ff4b50 | ||
|
|
50ebfe412c | ||
|
|
e59ab6b2d9 | ||
|
|
417d39f684 | ||
|
|
35169f66dc | ||
|
|
64568f4aa4 | ||
|
|
734aebbaaa | ||
|
|
8a2be468fc | ||
|
|
feb429fee7 | ||
|
|
b75101f24f | ||
|
|
1e71db9315 | ||
|
|
82b742608d | ||
|
|
49aa9e0768 | ||
|
|
a85cfab506 | ||
|
|
e2e3aa9281 | ||
|
|
6bd0f2c088 | ||
|
|
ab2968ffd2 | ||
|
|
83bf19084b | ||
|
|
40a59c988b | ||
|
|
43d983cac2 | ||
|
|
ad3e772944 | ||
|
|
6a438a07fe | ||
|
|
ea238829a8 | ||
|
|
91fddeaa8b | ||
|
|
0de8a90b14 | ||
|
|
9fe128d494 | ||
|
|
193e17b625 | ||
|
|
6ad03e6d5c | ||
|
|
97a72dfde7 | ||
|
|
1f55ff4b72 | ||
|
|
be13d43e0c | ||
|
|
af7b663334 | ||
|
|
da24638079 | ||
|
|
8ab1cbe600 | ||
|
|
cad0245510 | ||
|
|
93edf4e3ad | ||
|
|
caa2764317 | ||
|
|
4f1e6382c9 | ||
|
|
54d33ca103 | ||
|
|
787205dcca | ||
|
|
fcb0996a76 | ||
|
|
76d874d5f9 | ||
|
|
81711e4c43 | ||
|
|
e64f60a166 | ||
|
|
24bc56781b | ||
|
|
6757c8df74 | ||
|
|
647a384561 | ||
|
|
ec828c335d | ||
|
|
6d03a8ddf3 | ||
|
|
05878fcbb8 | ||
|
|
fd8973862e | ||
|
|
40c77948b9 | ||
|
|
a95aa1b3e9 | ||
|
|
706eb737b1 | ||
|
|
c31758d347 | ||
|
|
6139ba3015 | ||
|
|
5ca7f40a4e | ||
|
|
a2f4df191a | ||
|
|
256d5ba61c | ||
|
|
cb42e7e119 | ||
|
|
566d310880 | ||
|
|
5cdce35ee8 | ||
|
|
ffe3f12a23 | ||
|
|
bd48a982fb | ||
|
|
5d732d80a6 | ||
|
|
254e11aa36 | ||
|
|
4223b36bc3 | ||
|
|
fcea437d7e | ||
|
|
b49da46842 | ||
|
|
8716b75d3d | ||
|
|
e055b8b16c | ||
|
|
f5875e4c0b | ||
|
|
56d23c172c | ||
|
|
19bb40d1d3 | ||
|
|
4169a956c9 | ||
|
|
d54dbdfe2d | ||
|
|
fed2ae9a93 | ||
|
|
f00b2f0397 | ||
|
|
c101c4e42f | ||
|
|
11ba33d7f4 | ||
|
|
8e663dac3f | ||
|
|
df0795acf1 | ||
|
|
7e1af9e04b | ||
|
|
4805adec42 | ||
|
|
7939bf8038 | ||
|
|
d4e0b2ab51 | ||
|
|
1014a50aff | ||
|
|
2d24593403 | ||
|
|
0afbdf157e | ||
|
|
5012c52438 | ||
|
|
2201d2e8c2 | ||
|
|
b6c407971d | ||
|
|
cd8dc41b15 | ||
|
|
a1887bdc76 | ||
|
|
e9f89362f4 | ||
|
|
675b7febdf | ||
|
|
90fdf59415 | ||
|
|
615a81c55d | ||
|
|
99b31d05cb | ||
|
|
1a72b5b227 | ||
|
|
a1aea54405 | ||
|
|
e01354e863 | ||
|
|
ffe4603f2f | ||
|
|
ebc794194f | ||
|
|
287f65ec8e | ||
|
|
1288592d58 | ||
|
|
0836d844a6 | ||
|
|
96355a1ed4 | ||
|
|
ce44f19b4a | ||
|
|
6c214543ad | ||
|
|
8b1713d169 | ||
|
|
587ce5ad9d | ||
|
|
f51705cb57 | ||
|
|
55df9416cc | ||
|
|
0376c04ad5 | ||
|
|
2709479bf2 | ||
|
|
c5fc621aa4 | ||
|
|
bfd0e7f784 | ||
|
|
ec3c157f1e | ||
|
|
32aacbd2b0 | ||
|
|
655dc92246 | ||
|
|
fece8beef5 | ||
|
|
53e3621e04 | ||
|
|
1949839056 | ||
|
|
00a0006ff2 | ||
|
|
325f9aa6f3 | ||
|
|
95ec5c3c58 | ||
|
|
ef87cdb167 | ||
|
|
65d4596f3b | ||
|
|
a9e295bc11 | ||
|
|
ac8caf7710 | ||
|
|
8a1e61fd60 | ||
|
|
baf38b6b30 | ||
|
|
3507405dae | ||
|
|
c25fe6ae57 | ||
|
|
7bcf3206d8 | ||
|
|
e808c7fb2b | ||
|
|
df81e8ed35 | ||
|
|
e9d7a0b099 | ||
|
|
da7bbcf82f | ||
|
|
1742d2807f | ||
|
|
d3c5e2365a | ||
|
|
27e53f9dcc | ||
|
|
5d0f55b8c3 | ||
|
|
9d89b4726b | ||
|
|
9b37eacb8d | ||
|
|
bbe22bbfeb | ||
|
|
85d5e2ee70 | ||
|
|
9a4051f37b | ||
|
|
05ed4639b2 | ||
|
|
2bcf84d9a9 | ||
|
|
99acf752f4 | ||
|
|
5086f2d8b5 | ||
|
|
0b46c41ffd | ||
|
|
4fe3f60009 | ||
|
|
dfea0cd805 | ||
|
|
0f04b2fb10 | ||
|
|
a6efad73a8 | ||
|
|
29d63b0f0f | ||
|
|
25e9fd22d8 | ||
|
|
bb427db66a | ||
|
|
1165b00600 | ||
|
|
2d45952611 | ||
|
|
1af811cf51 | ||
|
|
1850f298a6 | ||
|
|
80ade22bd6 | ||
|
|
4fb458afe0 | ||
|
|
2baf7c0250 | ||
|
|
29aa3a8059 | ||
|
|
718e6765e1 | ||
|
|
31d49ee99e | ||
|
|
1e08f2713e | ||
|
|
8955972b05 | ||
|
|
5fe5804b56 | ||
|
|
9d12e55bd7 | ||
|
|
b3570991f4 | ||
|
|
e2aca63fff | ||
|
|
e537bda9b7 |
120
.github/workflows/build.yml
vendored
120
.github/workflows/build.yml
vendored
@@ -3,7 +3,7 @@ name: Build
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches-ignore:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
pull_request:
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
- name: Setup redis
|
||||
uses: supercharge/redis-github-action@1.4.0
|
||||
with:
|
||||
with:
|
||||
redis-version: 6
|
||||
|
||||
- name: Set up Ruby
|
||||
@@ -81,11 +81,19 @@ jobs:
|
||||
# RSpec split test files by test examples feature - it's optional
|
||||
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
||||
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}"
|
||||
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
|
||||
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: simplecov-chunk-controllers-${{ matrix.ci_node_index }}
|
||||
path: coverage/*.*
|
||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
||||
if-no-files-found: ignore
|
||||
|
||||
models:
|
||||
runs-on: ubuntu-22.04
|
||||
services:
|
||||
@@ -116,7 +124,7 @@ jobs:
|
||||
|
||||
- name: Setup redis
|
||||
uses: supercharge/redis-github-action@1.4.0
|
||||
with:
|
||||
with:
|
||||
redis-version: 6
|
||||
|
||||
- name: Set up Ruby
|
||||
@@ -141,10 +149,18 @@ jobs:
|
||||
# RSpec split test files by test examples feature - it's optional
|
||||
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
||||
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}"
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}"
|
||||
run: |
|
||||
bin/rake knapsack_pro:rspec
|
||||
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: simplecov-chunk-models-${{ matrix.ci_node_index }}
|
||||
path: coverage/*.*
|
||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
||||
if-no-files-found: ignore
|
||||
|
||||
system_admin:
|
||||
runs-on: ubuntu-22.04
|
||||
services:
|
||||
@@ -175,7 +191,7 @@ jobs:
|
||||
|
||||
- name: Setup redis
|
||||
uses: supercharge/redis-github-action@1.4.0
|
||||
with:
|
||||
with:
|
||||
redis-version: 6
|
||||
|
||||
- name: Set up Ruby
|
||||
@@ -209,16 +225,24 @@ jobs:
|
||||
# RSpec split test files by test examples feature - it's optional
|
||||
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
||||
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
|
||||
|
||||
run: |
|
||||
bin/rake knapsack_pro:queue:rspec
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
name: simplecov-chunk-system-admin-${{ matrix.ci_node_index }}
|
||||
path: coverage/*.*
|
||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: failed-admin_${{ matrix.ci_node_index }}-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
@@ -247,13 +271,13 @@ jobs:
|
||||
ci_node_total: [12]
|
||||
# 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, 6, 7, 8, 9, 10, 11]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup redis
|
||||
uses: supercharge/redis-github-action@1.4.0
|
||||
with:
|
||||
with:
|
||||
redis-version: 6
|
||||
|
||||
- name: Set up Ruby
|
||||
@@ -287,16 +311,24 @@ jobs:
|
||||
# RSpec split test files by test examples feature - it's optional
|
||||
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
|
||||
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}"
|
||||
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}"
|
||||
|
||||
run: |
|
||||
bin/rake knapsack_pro:queue:rspec
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
name: simplecov-chunk-system-consumer-${{ matrix.ci_node_index }}
|
||||
path: coverage/*.*
|
||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: failed-consumer_${{ matrix.ci_node_index }}-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
@@ -331,7 +363,7 @@ jobs:
|
||||
|
||||
- name: Setup redis
|
||||
uses: supercharge/redis-github-action@1.4.0
|
||||
with:
|
||||
with:
|
||||
redis-version: 6
|
||||
|
||||
- name: Set up Ruby
|
||||
@@ -371,13 +403,12 @@ jobs:
|
||||
run: |
|
||||
bin/rake knapsack_pro:rspec
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
name: simplecov-chunk-engines-${{ matrix.ci_node_index }}
|
||||
path: coverage/*.*
|
||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
||||
if-no-files-found: ignore
|
||||
|
||||
test_the_rest:
|
||||
@@ -410,7 +441,7 @@ jobs:
|
||||
|
||||
- name: Setup redis
|
||||
uses: supercharge/redis-github-action@1.4.0
|
||||
with:
|
||||
with:
|
||||
redis-version: 6
|
||||
|
||||
- name: Set up Ruby
|
||||
@@ -448,6 +479,14 @@ jobs:
|
||||
run: |
|
||||
bin/rake knapsack_pro:rspec
|
||||
|
||||
- name: Save SimpleCov file
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: simplecov-chunk-the-rest-${{ matrix.ci_node_index }}
|
||||
path: coverage/*.*
|
||||
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
|
||||
if-no-files-found: ignore
|
||||
|
||||
non_knapsack_jest_karma:
|
||||
runs-on: ubuntu-22.04
|
||||
services:
|
||||
@@ -485,3 +524,38 @@ jobs:
|
||||
|
||||
- name: Run jest tests
|
||||
run: yarn jest
|
||||
|
||||
collate_simplecov_results:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- controllers
|
||||
- models
|
||||
- engines
|
||||
- system_admin
|
||||
- system_consumer
|
||||
- test_the_rest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||
|
||||
- name: Download individual results from individual runners
|
||||
uses: actions/download-artifact@v3
|
||||
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]'
|
||||
|
||||
- name: Upload collated results
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: combined-simplecov-report
|
||||
path: coverage/**/*.*
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
@@ -12,22 +12,6 @@ Layout/EmptyLines:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented, indented_relative_to_receiver
|
||||
Layout/MultilineMethodCallIndentation:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented
|
||||
Layout/MultilineOperationIndentation:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 16
|
||||
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
||||
Lint/EmptyBlock:
|
||||
@@ -101,7 +85,7 @@ Lint/UselessMethodDefinition:
|
||||
Exclude:
|
||||
- 'app/models/spree/gateway.rb'
|
||||
|
||||
# Offense count: 23
|
||||
# Offense count: 24
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
||||
Metrics/AbcSize:
|
||||
Exclude:
|
||||
@@ -114,6 +98,7 @@ Metrics/AbcSize:
|
||||
- 'app/helpers/spree/admin/navigation_helper.rb'
|
||||
- 'app/models/enterprise_group.rb'
|
||||
- 'app/models/enterprise_relationship.rb'
|
||||
- 'app/models/product_import/entry_processor.rb'
|
||||
- 'app/models/spree/ability.rb'
|
||||
- 'app/models/spree/address.rb'
|
||||
- 'app/models/spree/order/checkout.rb'
|
||||
@@ -142,7 +127,7 @@ Metrics/BlockNesting:
|
||||
Exclude:
|
||||
- 'app/models/spree/payment/processing.rb'
|
||||
|
||||
# Offense count: 47
|
||||
# Offense count: 46
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ClassLength:
|
||||
Exclude:
|
||||
@@ -178,7 +163,6 @@ Metrics/ClassLength:
|
||||
- 'app/models/spree/variant.rb'
|
||||
- 'app/models/spree/zone.rb'
|
||||
- 'app/reflexes/admin/orders_reflex.rb'
|
||||
- 'app/reflexes/products_reflex.rb'
|
||||
- 'app/serializers/api/cached_enterprise_serializer.rb'
|
||||
- 'app/serializers/api/enterprise_shopfront_serializer.rb'
|
||||
- 'app/services/cart_service.rb'
|
||||
@@ -408,7 +392,6 @@ RSpecRails/HaveHttpStatus:
|
||||
- 'spec/controllers/stripe/webhooks_controller_spec.rb'
|
||||
- 'spec/controllers/user_passwords_controller_spec.rb'
|
||||
- 'spec/controllers/user_registrations_controller_spec.rb'
|
||||
- 'spec/requests/admin/images_spec.rb'
|
||||
- 'spec/requests/api/routes_spec.rb'
|
||||
- 'spec/requests/checkout/stripe_sca_spec.rb'
|
||||
- 'spec/requests/home_controller_spec.rb'
|
||||
@@ -638,12 +621,6 @@ Rails/ResponseParsedBody:
|
||||
- 'spec/controllers/spree/credit_cards_controller_spec.rb'
|
||||
- 'spec/controllers/user_registrations_controller_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/RootPathnameMethods:
|
||||
Exclude:
|
||||
- 'spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb'
|
||||
|
||||
# Offense count: 7
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
@@ -725,7 +702,7 @@ Style/ClassAndModuleChildren:
|
||||
- 'lib/open_food_network/locking.rb'
|
||||
- 'spec/models/spree/payment_method_spec.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: always, always_true, never
|
||||
@@ -872,39 +849,6 @@ Style/ReturnNilInPredicateMethodDefinition:
|
||||
- 'app/serializers/api/admin/customer_serializer.rb'
|
||||
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
|
||||
|
||||
# Offense count: 207
|
||||
Style/Send:
|
||||
Exclude:
|
||||
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
|
||||
- 'spec/controllers/payment_gateways/paypal_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/base_controller_spec.rb'
|
||||
- 'spec/controllers/spree/orders_controller_spec.rb'
|
||||
- 'spec/helpers/order_cycles_helper_spec.rb'
|
||||
- 'spec/jobs/subscription_confirm_job_spec.rb'
|
||||
- 'spec/jobs/subscription_placement_job_spec.rb'
|
||||
- 'spec/lib/open_food_network/address_finder_spec.rb'
|
||||
- 'spec/lib/open_food_network/enterprise_fee_applicator_spec.rb'
|
||||
- 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb'
|
||||
- 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb'
|
||||
- 'spec/lib/open_food_network/permissions_spec.rb'
|
||||
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
|
||||
- 'spec/lib/reports/xero_invoices_report_spec.rb'
|
||||
- 'spec/lib/stripe/webhook_handler_spec.rb'
|
||||
- 'spec/models/calculator/weight_spec.rb'
|
||||
- 'spec/models/enterprise_spec.rb'
|
||||
- 'spec/models/exchange_spec.rb'
|
||||
- 'spec/models/spree/order_inventory_spec.rb'
|
||||
- 'spec/models/spree/payment_spec.rb'
|
||||
- 'spec/models/spree/return_authorization_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_order_cycles_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_payment_methods_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_products_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_shipping_methods_spec.rb'
|
||||
- 'spec/services/cart_service_spec.rb'
|
||||
- 'spec/services/products_renderer_spec.rb'
|
||||
- 'spec/services/variant_units/option_value_namer_spec.rb'
|
||||
- 'spec/support/localized_number_helper.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Style/SlicingWithRange:
|
||||
|
||||
@@ -14,4 +14,6 @@ SimpleCov.start 'rails' do
|
||||
add_filter '/log'
|
||||
add_filter '/db'
|
||||
add_filter '/lib/tasks/sample_data/'
|
||||
|
||||
formatter SimpleCov::Formatter::SimpleFormatter
|
||||
end
|
||||
|
||||
2
Gemfile
2
Gemfile
@@ -16,7 +16,6 @@ gem "image_processing"
|
||||
|
||||
gem 'activemerchant', '>= 1.78.0'
|
||||
gem 'angular-rails-templates', '>= 0.3.0'
|
||||
gem 'awesome_nested_set'
|
||||
gem 'ransack', '~> 4.1.0'
|
||||
gem 'responders'
|
||||
gem 'webpacker', '~> 5'
|
||||
@@ -105,6 +104,7 @@ gem 'sidekiq-scheduler'
|
||||
gem "cable_ready"
|
||||
gem "stimulus_reflex"
|
||||
|
||||
gem "turbo_power"
|
||||
gem "turbo-rails"
|
||||
|
||||
gem 'combine_pdf'
|
||||
|
||||
14
Gemfile.lock
14
Gemfile.lock
@@ -161,8 +161,6 @@ GEM
|
||||
activerecord (>= 3.1.0, < 8)
|
||||
ast (2.4.2)
|
||||
attr_required (1.0.2)
|
||||
awesome_nested_set (3.6.0)
|
||||
activerecord (>= 4.0.0, < 7.2)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.929.0)
|
||||
aws-sdk-core (3.196.1)
|
||||
@@ -358,7 +356,7 @@ GEM
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
immigrant (0.3.6)
|
||||
activerecord (>= 3.0)
|
||||
invisible_captcha (2.2.0)
|
||||
invisible_captcha (2.3.0)
|
||||
rails (>= 5.2)
|
||||
io-console (0.7.2)
|
||||
ipaddress (0.8.3)
|
||||
@@ -417,7 +415,7 @@ GEM
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.2)
|
||||
marcel (1.0.4)
|
||||
matrix (0.4.2)
|
||||
method_source (1.1.0)
|
||||
mime-types (3.5.2)
|
||||
@@ -449,7 +447,7 @@ GEM
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
newrelic_rpm (9.9.0)
|
||||
nio4r (2.7.0)
|
||||
nio4r (2.7.1)
|
||||
nokogiri (1.16.5)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
@@ -787,6 +785,8 @@ GEM
|
||||
actionpack (>= 6.0.0)
|
||||
activejob (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
turbo_power (0.6.2)
|
||||
turbo-rails (>= 1.3.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
@@ -837,7 +837,7 @@ GEM
|
||||
websocket-extensions (0.1.5)
|
||||
whenever (1.0.0)
|
||||
chronic (>= 0.6.3)
|
||||
wicked_pdf (2.6.3)
|
||||
wicked_pdf (2.8.1)
|
||||
activesupport
|
||||
wkhtmltopdf-binary (0.12.6.7)
|
||||
xml-simple (1.1.8)
|
||||
@@ -863,7 +863,6 @@ DEPENDENCIES
|
||||
angularjs-file-upload-rails (~> 2.4.1)
|
||||
angularjs-rails (= 1.8.0)
|
||||
arel-helpers (~> 2.12)
|
||||
awesome_nested_set
|
||||
aws-sdk-s3
|
||||
bigdecimal (= 3.0.2)
|
||||
bootsnap
|
||||
@@ -976,6 +975,7 @@ DEPENDENCIES
|
||||
stripe
|
||||
timecop
|
||||
turbo-rails
|
||||
turbo_power
|
||||
valid_email2
|
||||
validates_lengths_from_database
|
||||
vcr
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
//= require jquery.ui.all
|
||||
//= require jquery.powertip
|
||||
//= require jquery.cookie
|
||||
//= require jquery.jstree/jquery.jstree
|
||||
//= require jquery.vAlign
|
||||
//= require angular
|
||||
//= require angular-resource
|
||||
|
||||
@@ -126,8 +126,11 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
DisplayProperties.setShowVariants 0, showVariants
|
||||
|
||||
$scope.addVariant = (product) ->
|
||||
# Set new variant category to same as last product variant category to keep compactibility with deleted variant callback to set new variant category
|
||||
newVariantId = $scope.nextVariantId();
|
||||
newVariantCategoryId = product.variants[product.variants.length - 1]?.category_id
|
||||
product.variants.push
|
||||
id: $scope.nextVariantId()
|
||||
id: newVariantId
|
||||
unit_value: null
|
||||
unit_description: null
|
||||
on_demand: false
|
||||
@@ -136,8 +139,9 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
on_hand: null
|
||||
price: null
|
||||
tax_category_id: null
|
||||
category_id: null
|
||||
category_id: newVariantCategoryId
|
||||
DisplayProperties.setShowVariants product.id, true
|
||||
DirtyProducts.addVariantProperty(product.id, newVariantId, 'category_id', newVariantCategoryId)
|
||||
|
||||
|
||||
$scope.nextVariantId = ->
|
||||
|
||||
@@ -3,6 +3,7 @@ angular.module('admin.orderCycles')
|
||||
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
|
||||
|
||||
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
$scope.order_cycle_id = order_cycle_id
|
||||
$scope.order_cycle = OrderCycle.load(order_cycle_id)
|
||||
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
|
||||
@@ -18,6 +19,8 @@ angular.module('admin.orderCycles')
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
|
||||
$scope.order_cycle?.confirm = $($event.target).data('confirm');
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
OrderCycle.update(destination, $scope.order_cycle_form)
|
||||
|
||||
@@ -25,4 +28,4 @@ angular.module('admin.orderCycles')
|
||||
if $scope.order_cycle_form?.$dirty
|
||||
t('admin.unsaved_confirm_leave')
|
||||
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, OrderCycle, ExchangeProduct, ocInstance) ->
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, EnterpriseFee, OrderCycle, ExchangeProduct, ocInstance) ->
|
||||
$controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})
|
||||
|
||||
$scope.view = 'incoming'
|
||||
|
||||
# NB: weirdly at this next line $scope.order_cycle.id comes out undefined so we use $scope.order_cycle_id instead
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: $scope.order_cycle_id, per_item: true)
|
||||
$scope.exchangeTotalVariants = (exchange) ->
|
||||
return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]?
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, Columns, StatusMessage, RequestMonitor, OrderCycles, Enterprises, Schedules, Dereferencer) ->
|
||||
$scope.RequestMonitor = RequestMonitor
|
||||
$scope.columns = Columns.columns
|
||||
$scope.saveAll = -> OrderCycles.saveChanges($scope.order_cycles_form)
|
||||
$scope.saveAll = ($event) ->
|
||||
trigger_action = $($event.target).data('trigger-action')
|
||||
confirm = $($event.target).data('confirm')
|
||||
OrderCycles.saveChanges($scope.order_cycles_form, { trigger_action, confirm })
|
||||
|
||||
$scope.ordersCloseAtLimit = -31 # days
|
||||
|
||||
$scope.resetSelectFilters = ->
|
||||
|
||||
@@ -22,6 +22,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
|
||||
$scope.order_cycle?.confirm = $($event.target).data('confirm');
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
OrderCycle.mirrorIncomingToOutgoingProducts()
|
||||
OrderCycle.update(destination, $scope.order_cycle_form) if OrderCycle.confirmNoDistributors()
|
||||
|
||||
@@ -6,6 +6,8 @@ angular.module('admin.orderCycles').factory('EnterpriseFee', ($resource) ->
|
||||
params:
|
||||
order_cycle_id: '@order_cycle_id'
|
||||
coordinator_id: '@coordinator_id'
|
||||
per_item: '@per_item'
|
||||
per_order: '@per_order'
|
||||
})
|
||||
|
||||
{
|
||||
|
||||
@@ -161,7 +161,11 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
|
||||
StatusMessage.display('failure', t('js.order_cycles.create_failure'))
|
||||
|
||||
update: (destination, form) ->
|
||||
oc = new OrderCycleResource({order_cycle: this.dataForSubmit()})
|
||||
oc = new OrderCycleResource({
|
||||
order_cycle: this.dataForSubmit(),
|
||||
confirm: this.order_cycle.confirm,
|
||||
trigger_action: this.order_cycle.trigger_action
|
||||
})
|
||||
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
|
||||
form.$setPristine() if form
|
||||
if destination?
|
||||
@@ -171,6 +175,8 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
|
||||
, (response) ->
|
||||
if response.data.errors?
|
||||
StatusMessage.display('failure', response.data.errors[0])
|
||||
else if (response.data.trigger_action)
|
||||
StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action)
|
||||
else
|
||||
StatusMessage.display('failure', t('js.order_cycles.update_failure'))
|
||||
|
||||
|
||||
@@ -29,13 +29,13 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy
|
||||
deferred.reject(response)
|
||||
deferred.promise
|
||||
|
||||
saveChanges: (form) ->
|
||||
saveChanges: (form, params = {}) ->
|
||||
changed = {}
|
||||
for id, orderCycle of @byID when not @saved(orderCycle)
|
||||
changed[Object.keys(changed).length] = @changesFor(orderCycle)
|
||||
if Object.keys(changed).length > 0
|
||||
StatusMessage.display('progress', "Saving...")
|
||||
OrderCycleResource.bulkUpdate { order_cycle_set: { collection_attributes: changed } }, (data) =>
|
||||
OrderCycleResource.bulkUpdate { order_cycle_set: { collection_attributes: changed }, confirm: params['confirm'], trigger_action: params['trigger_action'] }, (data) =>
|
||||
for orderCycle in data
|
||||
delete orderCycle.coordinator
|
||||
delete orderCycle.producers
|
||||
@@ -47,8 +47,10 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy
|
||||
, (response) =>
|
||||
if response.data.errors?
|
||||
StatusMessage.display('failure', response.data.errors[0])
|
||||
else if (response.data.trigger_action)
|
||||
StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action)
|
||||
else
|
||||
StatusMessage.display('failure', "Oh no! I was unable to save your changes.")
|
||||
StatusMessage.display('failure', t('js.order_cycles.bulk_save_error'))
|
||||
|
||||
saved: (order_cycle) ->
|
||||
@diff(order_cycle).length == 0
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
root = exports ? this
|
||||
|
||||
root.taxon_tree_menu = (obj, context) ->
|
||||
|
||||
base_url = Spree.url(Spree.routes.taxonomy_taxons)
|
||||
admin_base_url = Spree.url(Spree.routes.admin_taxonomy_taxons)
|
||||
edit_url = Spree.url(Spree.routes.admin_taxonomy_taxons + '/' + obj.attr("id") + "/edit");
|
||||
|
||||
create:
|
||||
label: "<i class='icon-plus'></i> " + Spree.translations.add,
|
||||
action: (obj) -> context.create(obj)
|
||||
rename:
|
||||
label: "<i class='icon-pencil'></i> " + Spree.translations.rename,
|
||||
action: (obj) -> context.rename(obj)
|
||||
remove:
|
||||
label: "<i class='icon-trash'></i> " + Spree.translations.remove,
|
||||
action: (obj) -> context.remove(obj)
|
||||
edit:
|
||||
separator_before: true,
|
||||
label: "<i class='icon-edit'></i> " + Spree.translations.edit,
|
||||
action: (obj) -> window.location = edit_url.toString()
|
||||
@@ -1,139 +0,0 @@
|
||||
handle_ajax_error = (XMLHttpRequest, textStatus, errorThrown) ->
|
||||
$.jstree.rollback(last_rollback)
|
||||
$("#ajax_error").show().html("<strong>" + server_error + "</strong><br />" + taxonomy_tree_error)
|
||||
|
||||
handle_move = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
position = data.rslt.cp
|
||||
node = data.rslt.o
|
||||
new_parent = data.rslt.np
|
||||
|
||||
url = new URL(Spree.routes.admin_taxonomy_taxons)
|
||||
url.pathname = url.pathname + '/' + node.attr("id")
|
||||
data = {
|
||||
_method: "put",
|
||||
"taxon[position]": position,
|
||||
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
|
||||
}
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url.toString(),
|
||||
data: data,
|
||||
error: handle_ajax_error
|
||||
|
||||
true
|
||||
|
||||
handle_create = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
name = data.rslt.name
|
||||
position = data.rslt.position
|
||||
new_parent = data.rslt.parent
|
||||
|
||||
data = {
|
||||
"taxon[name]": name,
|
||||
"taxon[position]": position
|
||||
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
|
||||
}
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: base_url.toString(),
|
||||
data: data,
|
||||
error: handle_ajax_error,
|
||||
success: (data,result) ->
|
||||
node.attr('id', data.id)
|
||||
|
||||
handle_rename = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
name = data.rslt.new_name
|
||||
# change the name inside the main input field as well if taxon is the root one
|
||||
document.getElementById("taxonomy_name").value = name if node.parents("[id]").attr("id") == "taxonomy_tree"
|
||||
|
||||
url = new URL(base_url)
|
||||
url.pathname = url.pathname + '/' + node.attr("id")
|
||||
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url.toString(),
|
||||
data: {_method: "put", "taxon[name]": name },
|
||||
error: handle_ajax_error
|
||||
|
||||
handle_delete = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
delete_url = new URL(base_url)
|
||||
delete_url.pathname = delete_url.pathname + '/' + node.attr("id")
|
||||
if confirm(Spree.translations.are_you_sure_delete)
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: delete_url.toString(),
|
||||
data: {_method: "delete"},
|
||||
error: handle_ajax_error
|
||||
else
|
||||
$.jstree.rollback(last_rollback)
|
||||
last_rollback = null
|
||||
|
||||
root = exports ? this
|
||||
root.setup_taxonomy_tree = (taxonomy_id) ->
|
||||
if taxonomy_id != undefined
|
||||
# this is defined within admin/taxonomies/edit
|
||||
root.base_url = Spree.url(Spree.routes.taxonomy_taxons)
|
||||
|
||||
$.ajax
|
||||
url: base_url.pathname.replace("/taxons", "/jstree"),
|
||||
success: (taxonomy) ->
|
||||
last_rollback = null
|
||||
|
||||
conf =
|
||||
json_data:
|
||||
data: taxonomy,
|
||||
ajax:
|
||||
url: (e) ->
|
||||
base_url.pathname + '/' + e.attr('id') + '/jstree'
|
||||
themes:
|
||||
theme: "apple",
|
||||
url: "/assets/jquery.jstree/themes/apple/style.css"
|
||||
strings:
|
||||
new_node: new_taxon,
|
||||
loading: Spree.translations.loading + "..."
|
||||
crrm:
|
||||
move:
|
||||
check_move: (m) ->
|
||||
position = m.cp
|
||||
node = m.o
|
||||
new_parent = m.np
|
||||
|
||||
# no parent or cant drag and drop
|
||||
if !new_parent || node.attr("rel") == "root"
|
||||
return false
|
||||
|
||||
# can't drop before root
|
||||
if new_parent.attr("id") == "taxonomy_tree" && position == 0
|
||||
return false
|
||||
|
||||
true
|
||||
contextmenu:
|
||||
items: (obj) ->
|
||||
taxon_tree_menu(obj, this)
|
||||
plugins: ["themes", "json_data", "dnd", "crrm", "contextmenu"]
|
||||
|
||||
$("#taxonomy_tree").jstree(conf)
|
||||
.bind("move_node.jstree", handle_move)
|
||||
.bind("remove.jstree", handle_delete)
|
||||
.bind("create.jstree", handle_create)
|
||||
.bind("rename.jstree", handle_rename)
|
||||
.bind "loaded.jstree", ->
|
||||
$(this).jstree("core").toggle_node($('.jstree-icon').first())
|
||||
|
||||
$("#taxonomy_tree a").on "dblclick", (e) ->
|
||||
$("#taxonomy_tree").jstree("rename", this)
|
||||
|
||||
# surpress form submit on enter/return
|
||||
$(document).keypress (e) ->
|
||||
if e.keyCode == 13
|
||||
e.preventDefault()
|
||||
@@ -10,7 +10,9 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
||||
|
||||
statusMessage:
|
||||
text: ""
|
||||
style: {}
|
||||
style: {},
|
||||
type: null,
|
||||
actionName: null
|
||||
|
||||
invalidMessage: ""
|
||||
|
||||
@@ -23,11 +25,15 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
||||
active: ->
|
||||
@statusMessage.text != ''
|
||||
|
||||
display: (type, text) ->
|
||||
display: (type, text, actionName = null) ->
|
||||
@statusMessage.text = text
|
||||
@statusMessage.type = type
|
||||
@statusMessage.actionName = actionName
|
||||
@statusMessage.style = @types[type].style
|
||||
null
|
||||
|
||||
clear: ->
|
||||
@statusMessage.text = ''
|
||||
@statusMessage.style = {}
|
||||
@statusMessage.type = null
|
||||
@statusMessage.actionName = null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#save-bar.animate-show{ "ng-show": 'dirty || persist || StatusMessage.active()' }
|
||||
.container
|
||||
.seven.columns.alpha
|
||||
%h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style' }
|
||||
%h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style', data: { 'order-cycle-form-target': 'statusMessage' }, "ng-attr-data-type": "{{StatusMessage.statusMessage.type}}", "ng-attr-data-action-name": "{{StatusMessage.statusMessage.actionName}}" }
|
||||
{{ StatusMessage.statusMessage.text || " " }}
|
||||
%h5#status-message{ style: 'color: #C85136', "ng-show": "StatusMessage.invalidMessage !== ''" }
|
||||
{{ StatusMessage.invalidMessage || " " }}
|
||||
|
||||
1
app/assets/stylesheets/mail.scss
Normal file
1
app/assets/stylesheets/mail.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import './mail/all.scss';
|
||||
3
app/assets/stylesheets/mail/all.scss
Normal file
3
app/assets/stylesheets/mail/all.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@import '../../../webpacker/css/admin/globals/palette.scss';
|
||||
@import 'email';
|
||||
@import 'payments_list';
|
||||
@@ -24,6 +24,19 @@
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* prevent arrow on selected admin menu item appearing above modal */
|
||||
|
||||
@@ -11,7 +11,8 @@ class SearchableDropdownComponent < ViewComponent::Base
|
||||
selected_option:,
|
||||
placeholder_value:,
|
||||
include_blank: false,
|
||||
aria_label: ''
|
||||
aria_label: '',
|
||||
other_attrs: {}
|
||||
)
|
||||
@f = form
|
||||
@name = name
|
||||
@@ -20,11 +21,13 @@ class SearchableDropdownComponent < ViewComponent::Base
|
||||
@placeholder_value = placeholder_value
|
||||
@include_blank = include_blank
|
||||
@aria_label = aria_label
|
||||
@other_attrs = other_attrs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :f, :name, :options, :selected_option, :placeholder_value, :include_blank, :aria_label
|
||||
attr_reader :f, :name, :options, :selected_option, :placeholder_value, :include_blank,
|
||||
:aria_label, :other_attrs
|
||||
|
||||
def classes
|
||||
"fullwidth #{remove_search_plugin? ? 'no-input' : ''}"
|
||||
|
||||
@@ -1 +1 @@
|
||||
= f.select name, options_for_select(options, selected_option), { include_blank: }, class: classes, data:, 'aria-label': aria_label
|
||||
= f.select name, options_for_select(options, selected_option), { include_blank: }, class: classes, data:, 'aria-label': aria_label, **other_attrs
|
||||
|
||||
22
app/controllers/admin/connected_app_settings_controller.rb
Normal file
22
app/controllers/admin/connected_app_settings_controller.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ConnectedAppSettingsController < Spree::Admin::BaseController
|
||||
def update
|
||||
Spree::Config.set(connected_apps_enabled:)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = t(:successfully_updated, resource: t('.resource'))
|
||||
redirect_to main_app.edit_admin_connected_app_settings_path
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def connected_apps_enabled
|
||||
params.require(:preferences).require(:connected_apps_enabled).compact_blank.join(",")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,12 +5,12 @@ module Admin
|
||||
def create
|
||||
authorize! :admin, enterprise
|
||||
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id)
|
||||
attributes = {}
|
||||
attributes[:type] = connected_app_params[:type] if connected_app_params[:type]
|
||||
|
||||
ConnectAppJob.perform_later(
|
||||
app, spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request),
|
||||
)
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id, **attributes)
|
||||
app.connect(api_key: spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request))
|
||||
|
||||
render_panel
|
||||
end
|
||||
@@ -18,15 +18,9 @@ module Admin
|
||||
def destroy
|
||||
authorize! :admin, enterprise
|
||||
|
||||
app = enterprise.connected_apps.first
|
||||
app = enterprise.connected_apps.find(params.require(:id))
|
||||
app.destroy
|
||||
|
||||
WebhookDeliveryJob.perform_later(
|
||||
app.data["destroy"],
|
||||
"disconnect-app",
|
||||
nil
|
||||
)
|
||||
|
||||
render_panel
|
||||
end
|
||||
|
||||
@@ -39,5 +33,9 @@ module Admin
|
||||
def render_panel
|
||||
redirect_to "#{edit_admin_enterprise_path(enterprise)}#/connected_apps_panel"
|
||||
end
|
||||
|
||||
def connected_app_params
|
||||
params.permit(:type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,13 +35,7 @@ module Admin
|
||||
private
|
||||
|
||||
def fetch_catalog(url)
|
||||
if url =~ /food-data-collaboration/
|
||||
fdc_json = FdcRequest.new(spree_current_user).call(url)
|
||||
fdc_message = JSON.parse(fdc_json)
|
||||
fdc_message["products"]
|
||||
else
|
||||
DfcRequest.new(spree_current_user).call(url)
|
||||
end
|
||||
DfcRequest.new(spree_current_user).call(url)
|
||||
end
|
||||
|
||||
# Most of this code is the same as in the DfcProvider::SuppliedProductsController.
|
||||
|
||||
@@ -65,7 +65,9 @@ module Admin
|
||||
order_cycle ||= OrderCycle.new(coordinator:) if coordinator.present?
|
||||
enterprises = OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user,
|
||||
order_cycle).visible_enterprises
|
||||
EnterpriseFee.for_enterprises(enterprises).order('enterprise_id', 'fee_type', 'name')
|
||||
|
||||
fees = EnterpriseFee.for_enterprises(enterprises).order('enterprise_id', 'fee_type', 'name')
|
||||
filter_fees(fees)
|
||||
else
|
||||
collection = EnterpriseFee.managed_by(spree_current_user).order('enterprise_id',
|
||||
'fee_type', 'name')
|
||||
@@ -74,6 +76,12 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def filter_fees(fees)
|
||||
fees = fees.per_item if params[:per_item]
|
||||
fees = fees.per_order if params[:per_order]
|
||||
fees
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index, :for_order_cycle, :bulk_update]
|
||||
end
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
module Admin
|
||||
class OrderCyclesController < Admin::ResourceController
|
||||
class DateTimeChangeError < StandardError; end
|
||||
|
||||
include ::OrderCyclesHelper
|
||||
include PaperTrailLogging
|
||||
|
||||
@@ -62,9 +64,7 @@ module Admin
|
||||
end
|
||||
|
||||
def update
|
||||
@order_cycle_form = OrderCycles::FormService.new(@order_cycle, order_cycle_params,
|
||||
spree_current_user)
|
||||
|
||||
@order_cycle_form = set_order_cycle_form
|
||||
if @order_cycle_form.save
|
||||
update_nil_subscription_line_items_price_estimate(@order_cycle)
|
||||
respond_to do |format|
|
||||
@@ -77,6 +77,9 @@ module Admin
|
||||
elsif request.format.json?
|
||||
render json: { errors: @order_cycle.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
rescue DateTimeChangeError
|
||||
render json: { trigger_action: params[:trigger_action] },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
@@ -90,6 +93,9 @@ module Admin
|
||||
order_cycle = order_cycle_set.collection.find{ |oc| oc.errors.present? }
|
||||
render json: { errors: order_cycle.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
rescue DateTimeChangeError
|
||||
render json: { trigger_action: params[:trigger_action] },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def bulk_update_nil_subscription_line_items_price_estimate
|
||||
@@ -235,7 +241,7 @@ module Admin
|
||||
else
|
||||
begin
|
||||
yield
|
||||
rescue ActiveRecord::InvalidForeignKey
|
||||
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::DeleteRestrictionError
|
||||
redirect_to main_app.admin_order_cycles_url
|
||||
flash[:error] = I18n.t('admin.order_cycles.destroy_errors.orders_present')
|
||||
end
|
||||
@@ -270,7 +276,10 @@ module Admin
|
||||
end
|
||||
|
||||
def order_cycle_set
|
||||
@order_cycle_set ||= Sets::OrderCycleSet.new(@order_cycles, order_cycle_bulk_params)
|
||||
@order_cycle_set ||= Sets::OrderCycleSet.new(
|
||||
@order_cycles, { **order_cycle_bulk_params,
|
||||
confirm_datetime_change: params[:confirm], error_class: DateTimeChangeError }
|
||||
)
|
||||
end
|
||||
|
||||
def require_order_cycle_set_params
|
||||
@@ -294,5 +303,14 @@ module Admin
|
||||
collection_attributes: [:id] + PermittedAttributes::OrderCycle.basic_attributes
|
||||
).to_h.with_indifferent_access
|
||||
end
|
||||
|
||||
def set_order_cycle_form
|
||||
OrderCycles::FormService.new(
|
||||
@order_cycle, order_cycle_params.merge(
|
||||
{ confirm_datetime_change: params[:order_cycle][:confirm],
|
||||
error_class: DateTimeChangeError }
|
||||
), spree_current_user
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,8 @@ module Admin
|
||||
def index
|
||||
fetch_products
|
||||
render "index", locals: { producers:, categories:, tax_category_options:, flash: }
|
||||
|
||||
session[:products_return_to_url] = request.url
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
|
||||
@@ -6,7 +6,7 @@ module Admin
|
||||
include ReportsActions
|
||||
helper ReportsHelper
|
||||
|
||||
before_action :authorize_report, only: [:show]
|
||||
before_action :authorize_report, only: [:show, :create]
|
||||
|
||||
# Define model class for Can? permissions
|
||||
def model_class
|
||||
@@ -20,14 +20,17 @@ module Admin
|
||||
end
|
||||
|
||||
def show
|
||||
@report = report_class.new(spree_current_user, params, render: render_data?)
|
||||
@rendering_options = rendering_options # also stores user preferences
|
||||
@report = report_class.new(spree_current_user, params, render: false)
|
||||
@rendering_options = rendering_options
|
||||
|
||||
if render_data?
|
||||
render_in_background
|
||||
else
|
||||
show_report
|
||||
end
|
||||
show_report
|
||||
end
|
||||
|
||||
def create
|
||||
@report = report_class.new(spree_current_user, params, render: true)
|
||||
update_rendering_options
|
||||
|
||||
render_in_background
|
||||
end
|
||||
|
||||
private
|
||||
@@ -54,31 +57,15 @@ module Admin
|
||||
@variant_serialized = Api::Admin::VariantSerializer.new(variant)
|
||||
end
|
||||
|
||||
def render_data?
|
||||
request.post?
|
||||
end
|
||||
|
||||
def render_in_background
|
||||
cable_ready[ScopedChannel.for_id(params[:uuid])]
|
||||
.inner_html(
|
||||
selector: "#report-go",
|
||||
html: helpers.button(t(:go), "report__submit-btn", "submit", disabled: true)
|
||||
).inner_html(
|
||||
selector: "#report-table",
|
||||
html: render_to_string(partial: "admin/reports/loading")
|
||||
).scroll_into_view(
|
||||
selector: "#report-table",
|
||||
block: "start"
|
||||
).broadcast
|
||||
@blob = ReportBlob.create_for_upload_later!(report_filename)
|
||||
|
||||
ReportJob.perform_later(
|
||||
report_class:, user: spree_current_user, params:,
|
||||
format: report_format,
|
||||
filename: report_filename,
|
||||
blob: @blob,
|
||||
channel: ScopedChannel.for_id(params[:uuid]),
|
||||
)
|
||||
|
||||
head :no_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V0
|
||||
class TaxonomiesController < Api::V0::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check only: :jstree
|
||||
|
||||
def jstree
|
||||
@taxonomy = Spree::Taxonomy.find(params[:id])
|
||||
render json: @taxonomy.root, serializer: Api::TaxonJstreeSerializer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,12 +5,10 @@ module Api
|
||||
class TaxonsController < Api::V0::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check only: [:index, :show, :jstree]
|
||||
skip_authorization_check only: [:index, :show]
|
||||
|
||||
def index
|
||||
@taxons = if taxonomy
|
||||
taxonomy.root.children
|
||||
elsif params[:ids]
|
||||
@taxons = if params[:ids]
|
||||
Spree::Taxon.where(id: raw_params[:ids].split(","))
|
||||
else
|
||||
Spree::Taxon.ransack(raw_params[:q]).result
|
||||
@@ -18,23 +16,9 @@ module Api
|
||||
render json: @taxons, each_serializer: Api::TaxonSerializer
|
||||
end
|
||||
|
||||
def jstree
|
||||
@taxon = taxon
|
||||
render json: @taxon.children, each_serializer: Api::TaxonJstreeSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
authorize! :create, Spree::Taxon
|
||||
@taxon = Spree::Taxon.new(taxon_params)
|
||||
@taxon.taxonomy_id = params[:taxonomy_id]
|
||||
taxonomy = Spree::Taxonomy.find_by(id: params[:taxonomy_id])
|
||||
|
||||
if taxonomy.nil?
|
||||
@taxon.errors.add(:taxonomy_id, I18n.t(:invalid_taxonomy_id, scope: 'spree.api'))
|
||||
invalid_resource!(@taxon) && return
|
||||
end
|
||||
|
||||
@taxon.parent_id = taxonomy.root.id unless params.dig(:taxon, :parent_id)
|
||||
|
||||
if @taxon.save
|
||||
render json: @taxon, serializer: Api::TaxonSerializer, status: :created
|
||||
@@ -60,20 +44,14 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
def taxonomy
|
||||
return if params[:taxonomy_id].blank?
|
||||
|
||||
@taxonomy ||= Spree::Taxonomy.find(params[:taxonomy_id])
|
||||
end
|
||||
|
||||
def taxon
|
||||
@taxon ||= taxonomy.taxons.find(params[:id])
|
||||
@taxon = Spree::Taxon.find(params[:id])
|
||||
end
|
||||
|
||||
def taxon_params
|
||||
return if params[:taxon].blank?
|
||||
|
||||
params.require(:taxon).permit([:name, :parent_id, :position])
|
||||
params.require(:taxon).permit([:name, :position])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,14 +88,10 @@ module ReportsActions
|
||||
display_header_row: false
|
||||
}
|
||||
end
|
||||
update_rendering_options
|
||||
@rendering_options
|
||||
end
|
||||
|
||||
def update_rendering_options
|
||||
return unless request.post?
|
||||
|
||||
@rendering_options.update(
|
||||
rendering_options.update(
|
||||
options: {
|
||||
fields_to_show: params[:fields_to_show],
|
||||
display_summary_row: params[:display_summary_row].present?,
|
||||
|
||||
@@ -19,15 +19,18 @@ module Spree
|
||||
|
||||
before_action :authorize_admin
|
||||
before_action :set_locale
|
||||
before_action :warn_invalid_order_cycles, if: :html_request?
|
||||
before_action :warn_invalid_order_cycles, if: :page_load_request?
|
||||
|
||||
# Warn the user when they have an active order cycle with hubs that are not ready
|
||||
# for checkout (ie. does not have valid shipping and payment methods).
|
||||
def warn_invalid_order_cycles
|
||||
return if flash[:notice].present?
|
||||
return if flash[:notice].present? || session[:displayed_order_cycle_warning]
|
||||
|
||||
warning = OrderCycles::WarningService.new(spree_current_user).call
|
||||
flash[:notice] = warning if warning.present?
|
||||
return if warning.blank?
|
||||
|
||||
flash.now[:notice] = warning
|
||||
session[:displayed_order_cycle_warning] = true
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -81,6 +84,12 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def page_load_request?
|
||||
return false if request.format.include?('turbo')
|
||||
|
||||
html_request?
|
||||
end
|
||||
|
||||
def html_request?
|
||||
request.format.html?
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ module Spree
|
||||
include OpenFoodNetwork::SpreeApiKeyLoader
|
||||
include OrderCyclesHelper
|
||||
include EnterprisesHelper
|
||||
helper ::Admin::ProductsHelper
|
||||
|
||||
before_action :load_data
|
||||
before_action :load_producers, only: [:index, :new]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class TaxonomiesController < ::Admin::ResourceController
|
||||
respond_to :json, only: [:get_children]
|
||||
|
||||
def get_children
|
||||
@taxons = Taxon.find(params[:parent_id]).children
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def location_after_save
|
||||
if @taxonomy.created_at == @taxonomy.updated_at
|
||||
spree.edit_admin_taxonomy_url(@taxonomy)
|
||||
else
|
||||
spree.admin_taxonomies_url
|
||||
end
|
||||
end
|
||||
|
||||
def permitted_resource_params
|
||||
params.require(:taxonomy).permit(:name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,122 +2,70 @@
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class TaxonsController < Spree::Admin::BaseController
|
||||
respond_to :html, :json, :js
|
||||
class TaxonsController < ::Admin::ResourceController
|
||||
before_action :set_taxon, except: %i[create index new]
|
||||
|
||||
def edit
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.find(params[:id])
|
||||
@permalink_part = @taxon.permalink.split("/").last
|
||||
def index
|
||||
@taxons = Taxon.order(:name)
|
||||
end
|
||||
|
||||
def new
|
||||
@taxon = Taxon.new
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.build(params[:taxon])
|
||||
@taxon = Spree::Taxon.new(taxon_params)
|
||||
if @taxon.save
|
||||
respond_with(@taxon) do |format|
|
||||
format.json { render json: @taxon.to_json }
|
||||
end
|
||||
flash[:success] = flash_message_for(@taxon, :successfully_created)
|
||||
redirect_to edit_admin_taxon_path(@taxon.id)
|
||||
else
|
||||
flash[:error] = Spree.t('errors.messages.could_not_create_taxon')
|
||||
respond_with(@taxon) do |format|
|
||||
format.html do
|
||||
if redirect_to @taxonomy
|
||||
spree.edit_admin_taxonomy_url(@taxonomy)
|
||||
else
|
||||
spree.admin_taxonomies_url
|
||||
end
|
||||
end
|
||||
end
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.find(params[:id])
|
||||
parent_id = params[:taxon][:parent_id]
|
||||
new_position = params[:taxon][:position]
|
||||
|
||||
if parent_id || new_position # taxon is being moved
|
||||
new_parent = parent_id.nil? ? @taxon.parent : Taxon.find(parent_id.to_i)
|
||||
new_position = new_position.nil? ? -1 : new_position.to_i
|
||||
|
||||
# Bellow is a very complicated way of finding where in nested set we
|
||||
# should actually move the taxon to achieve sane results,
|
||||
# JS is giving us the desired position, which was awesome for previous setup,
|
||||
# but now it's quite complicated to find where we should put it as we have
|
||||
# to differenciate between moving to the same branch, up down and into
|
||||
# first position.
|
||||
new_siblings = new_parent.children
|
||||
if new_position <= 0 && new_siblings.empty?
|
||||
@taxon.move_to_child_of(new_parent)
|
||||
elsif new_parent.id != @taxon.parent_id
|
||||
if new_position.zero?
|
||||
@taxon.move_to_left_of(new_siblings.first)
|
||||
else
|
||||
@taxon.move_to_right_of(new_siblings[new_position - 1])
|
||||
end
|
||||
elsif new_position < new_siblings.index(@taxon)
|
||||
@taxon.move_to_left_of(new_siblings[new_position]) # we move up
|
||||
else
|
||||
@taxon.move_to_right_of(new_siblings[new_position - 1]) # we move down
|
||||
end
|
||||
# Reset legacy position, if any extensions still rely on it
|
||||
new_parent.children.reload.each do |t|
|
||||
t.update_columns(
|
||||
position: t.position,
|
||||
updated_at: Time.zone.now
|
||||
)
|
||||
end
|
||||
|
||||
if parent_id
|
||||
@taxon.reload
|
||||
@taxon.set_permalink
|
||||
@taxon.save!
|
||||
@update_children = true
|
||||
end
|
||||
end
|
||||
|
||||
if params.key? "permalink_part"
|
||||
parent_permalink = @taxon.permalink.split("/")[0...-1].join("/")
|
||||
parent_permalink += "/" if parent_permalink.present?
|
||||
params[:taxon][:permalink] = parent_permalink + params[:permalink_part]
|
||||
end
|
||||
# check if we need to rename child taxons if parent name or permalink changes
|
||||
if params[:taxon][:name] != @taxon.name || params[:taxon][:permalink] != @taxon.permalink
|
||||
@update_children = true
|
||||
end
|
||||
|
||||
if @taxon.update(taxon_params)
|
||||
flash[:success] = flash_message_for(@taxon, :successfully_updated)
|
||||
end
|
||||
|
||||
# rename child taxons
|
||||
if @update_children
|
||||
@taxon.descendants.each do |taxon|
|
||||
taxon.reload
|
||||
taxon.set_permalink
|
||||
taxon.save!
|
||||
end
|
||||
end
|
||||
|
||||
respond_with(@taxon) do |format|
|
||||
format.html { redirect_to spree.edit_admin_taxonomy_url(@taxonomy) }
|
||||
format.json { render json: @taxon.to_json }
|
||||
redirect_to edit_admin_taxon_path(@taxon.id)
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@taxon = Taxon.find(params[:id])
|
||||
@taxon.destroy
|
||||
respond_with(@taxon) { |format| format.json { render json: '' } }
|
||||
status = if @taxon.destroy
|
||||
flash_message = t('.delete_taxon.success')
|
||||
status = :ok
|
||||
else
|
||||
flash_message = t('.delete_taxon.error')
|
||||
status = :unprocessable_entity
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = flash_message if status == :ok
|
||||
flash[:error] = flash_message if status == :unprocessable_entity
|
||||
redirect_to admin_taxons_path
|
||||
}
|
||||
format.turbo_stream {
|
||||
flash[:success] = flash_message if status == :ok
|
||||
flash[:error] = flash_message if status == :unprocessable_entity
|
||||
render :destroy_taxon, status:
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_taxon
|
||||
@taxon = Taxon.find(params[:id])
|
||||
end
|
||||
|
||||
def taxon_params
|
||||
params.require(:taxon).permit(
|
||||
:name, :parent_id, :position, :icon, :description, :permalink, :taxonomy_id,
|
||||
:name, :position, :icon, :description, :permalink,
|
||||
:meta_description, :meta_keywords, :meta_title, :dfc_id
|
||||
)
|
||||
end
|
||||
|
||||
@@ -26,7 +26,8 @@ module Admin
|
||||
show_enterprise_fees = can?(:manage_enterprise_fees,
|
||||
enterprise) && (is_shop || enterprise.is_primary_producer)
|
||||
show_connected_apps = can?(:manage_connected_apps, enterprise) &&
|
||||
feature?(:connected_apps, spree_current_user, enterprise)
|
||||
feature?(:connected_apps, spree_current_user, enterprise) &&
|
||||
Spree::Config.connected_apps_enabled.present?
|
||||
|
||||
build_enterprise_side_menu_items(
|
||||
is_shop:,
|
||||
@@ -38,6 +39,11 @@ module Admin
|
||||
)
|
||||
end
|
||||
|
||||
def connected_apps_enabled
|
||||
connected_apps_enabled = Spree::Config.connected_apps_enabled&.split(',') || []
|
||||
ConnectedApp::TYPES & connected_apps_enabled
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_enterprise_side_menu_items(
|
||||
|
||||
@@ -11,21 +11,31 @@ module Admin
|
||||
end
|
||||
|
||||
def prepare_new_variant(product)
|
||||
product.variants.build do |variant|
|
||||
variant.unit_value = 1.0 * (product.variant_unit_scale || 1)
|
||||
variant.unit_presentation = VariantUnits::OptionValueNamer.new(variant).name
|
||||
end
|
||||
product.variants.build
|
||||
end
|
||||
|
||||
def unit_value_with_description(variant)
|
||||
scaled_unit_value = variant.unit_value / (variant.product.variant_unit_scale || 1)
|
||||
precised_unit_value = number_with_precision(
|
||||
scaled_unit_value,
|
||||
precision: nil,
|
||||
strip_insignificant_zeros: true
|
||||
)
|
||||
precised_unit_value = nil
|
||||
|
||||
if variant.unit_value
|
||||
scaled_unit_value = variant.unit_value / (variant.product.variant_unit_scale || 1)
|
||||
precised_unit_value = number_with_precision(
|
||||
scaled_unit_value,
|
||||
precision: nil,
|
||||
strip_insignificant_zeros: true,
|
||||
significant: false,
|
||||
)
|
||||
end
|
||||
|
||||
[precised_unit_value, variant.unit_description].compact_blank.join(" ")
|
||||
end
|
||||
|
||||
def products_return_to_url(url_filters)
|
||||
if feature?(:admin_style_v3, spree_current_user)
|
||||
return session[:products_return_to_url] || admin_products_url
|
||||
end
|
||||
|
||||
"#{admin_products_path}#{url_filters.empty? ? '' : "#?#{url_filters.to_query}"}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -61,15 +61,6 @@ module ApplicationHelper
|
||||
classes << shopfront_layout
|
||||
end
|
||||
|
||||
def pdf_stylesheet_pack_tag(source)
|
||||
if running_in_development?
|
||||
options = { media: "all", host: "#{Webpacker.dev_server.host}:#{Webpacker.dev_server.port}" }
|
||||
stylesheet_pack_tag(source, **options)
|
||||
else
|
||||
wicked_pdf_stylesheet_pack_tag(source)
|
||||
end
|
||||
end
|
||||
|
||||
def cache_with_locale(key = nil, options = {}, &block)
|
||||
cache(cache_key_with_locale(key, I18n.locale), options) do
|
||||
yield(block)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module I18nHelper
|
||||
def locale_options
|
||||
OpenFoodNetwork::I18nConfig.available_locales.map do |locale|
|
||||
OpenFoodNetwork::I18nConfig.selectable_locales.map do |locale|
|
||||
[t('language_name', locale:), locale]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,11 +8,13 @@ module InjectionHelper
|
||||
include OrderCyclesHelper
|
||||
|
||||
def inject_enterprises(enterprises = nil)
|
||||
enterprises ||= default_enterprise_query
|
||||
|
||||
inject_json_array(
|
||||
"enterprises",
|
||||
enterprises || default_enterprise_query,
|
||||
enterprises,
|
||||
Api::EnterpriseSerializer,
|
||||
enterprise_injection_data,
|
||||
enterprise_injection_data(enterprises.map(&:id)),
|
||||
)
|
||||
end
|
||||
|
||||
@@ -57,15 +59,16 @@ module InjectionHelper
|
||||
inject_json_array "enterprises",
|
||||
enterprises_and_relatives,
|
||||
Api::EnterpriseSerializer,
|
||||
enterprise_injection_data
|
||||
enterprise_injection_data(enterprises_and_relatives.map(&:id))
|
||||
end
|
||||
|
||||
def inject_group_enterprises(group)
|
||||
enterprises = group.enterprises.activated.visible.all
|
||||
inject_json_array(
|
||||
"enterprises",
|
||||
group.enterprises.activated.visible.all,
|
||||
enterprises,
|
||||
Api::EnterpriseSerializer,
|
||||
enterprise_injection_data,
|
||||
enterprise_injection_data(enterprises.map(&:id)),
|
||||
)
|
||||
end
|
||||
|
||||
@@ -73,7 +76,7 @@ module InjectionHelper
|
||||
inject_json "currentHub",
|
||||
current_distributor,
|
||||
Api::EnterpriseSerializer,
|
||||
enterprise_injection_data
|
||||
enterprise_injection_data(current_distributor ? [current_distributor.id] : nil)
|
||||
end
|
||||
|
||||
def inject_current_order
|
||||
@@ -153,7 +156,9 @@ module InjectionHelper
|
||||
Enterprise.activated.includes(address: [:state, :country]).all
|
||||
end
|
||||
|
||||
def enterprise_injection_data
|
||||
@enterprise_injection_data ||= { data: OpenFoodNetwork::EnterpriseInjectionData.new }
|
||||
def enterprise_injection_data(enterprise_ids)
|
||||
{
|
||||
data: OpenFoodNetwork::EnterpriseInjectionData.new(enterprise_ids)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,4 +10,8 @@ module MailerHelper
|
||||
link_to ofn, "https://www.openfoodnetwork.org"
|
||||
end
|
||||
end
|
||||
|
||||
def order_reply_email(order)
|
||||
order.distributor.email_address.presence || order.distributor.contact.email
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
module TaxonsHelper
|
||||
def taxon_path(taxon)
|
||||
taxon.ancestors.reverse.collect(&:name).join( " >> ")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -19,8 +19,8 @@ class ConnectAppJob < ApplicationJob
|
||||
|
||||
selector = "#connected-app-discover-regen.enterprise_#{enterprise.id}"
|
||||
html = ApplicationController.render(
|
||||
partial: "admin/enterprises/form/connected_apps",
|
||||
locals: { enterprise: },
|
||||
partial: "admin/enterprises/form/connected_apps/discover_regen",
|
||||
locals: { enterprise:, connected_app: enterprise.connected_apps.discover_regen.first },
|
||||
)
|
||||
|
||||
cable_ready[channel].morph(selector:, html:).broadcast
|
||||
|
||||
@@ -9,12 +9,12 @@ class ReportJob < ApplicationJob
|
||||
|
||||
NOTIFICATION_TIME = 5.seconds
|
||||
|
||||
def perform(report_class:, user:, params:, format:, filename:, channel: nil)
|
||||
def perform(report_class:, user:, params:, format:, blob:, channel: nil)
|
||||
start_time = Time.zone.now
|
||||
|
||||
report = report_class.new(user, params, render: true)
|
||||
result = report.render_as(format)
|
||||
blob = ReportBlob.create!(filename, result)
|
||||
blob.store(result)
|
||||
|
||||
execution_time = Time.zone.now - start_time
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
# `count_on_hand` can either be: nil or a number
|
||||
#
|
||||
# This means that a variant override can be in six different stock states
|
||||
# but only three of them are valid.
|
||||
# but only four of them are valid.
|
||||
#
|
||||
# | on_demand | count_on_hand | stock_overridden? | use_producer_stock_settings? | valid? |
|
||||
# |-----------|---------------|-------------------|------------------------------|--------|
|
||||
# | 1 | nil | false | false | true |
|
||||
# | 1 | nil | true | false | true |
|
||||
# | 0 | x | true | false | true |
|
||||
# | nil | nil | false | true | true |
|
||||
# | 1 | x | ? | ? | false |
|
||||
# | 1 | x | true | false | true |
|
||||
# | 0 | nil | ? | ? | false |
|
||||
# | nil | x | ? | ? | false |
|
||||
#
|
||||
@@ -27,7 +27,6 @@ module StockSettingsOverrideValidation
|
||||
|
||||
def require_compatible_on_demand_and_count_on_hand
|
||||
disallow_count_on_hand_if_using_producer_stock_settings
|
||||
disallow_count_on_hand_if_on_demand
|
||||
require_count_on_hand_if_limited_stock
|
||||
end
|
||||
|
||||
@@ -39,14 +38,6 @@ module StockSettingsOverrideValidation
|
||||
errors.add(:count_on_hand, error_message)
|
||||
end
|
||||
|
||||
def disallow_count_on_hand_if_on_demand
|
||||
return unless on_demand? && count_on_hand.present?
|
||||
|
||||
error_message = I18n.t("count_on_hand.on_demand_but_count_on_hand_set",
|
||||
scope: i18n_scope_for_stock_settings_override_validation_error)
|
||||
errors.add(:count_on_hand, error_message)
|
||||
end
|
||||
|
||||
def require_count_on_hand_if_limited_stock
|
||||
return unless on_demand == false && count_on_hand.blank?
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ module VariantStock
|
||||
# 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)
|
||||
on_hand = if total_on_hand >= quantity || on_demand
|
||||
on_hand = if total_on_hand.to_i >= quantity || on_demand
|
||||
quantity
|
||||
else
|
||||
[0, total_on_hand].max
|
||||
@@ -112,8 +112,7 @@ module VariantStock
|
||||
#
|
||||
# This enables us to override this behaviour for variant overrides
|
||||
def move(quantity, originator = nil)
|
||||
# Don't change variant stock if variant is on_demand or has been deleted
|
||||
return if on_demand || deleted_at
|
||||
return if deleted_at
|
||||
|
||||
raise_error_if_no_stock_item_available
|
||||
|
||||
|
||||
@@ -4,8 +4,34 @@
|
||||
#
|
||||
# Here we store keys and links to access the app.
|
||||
class ConnectedApp < ApplicationRecord
|
||||
TYPES = ['discover_regen', 'affiliate_sales_data'].freeze
|
||||
|
||||
belongs_to :enterprise
|
||||
after_destroy :disconnect
|
||||
|
||||
scope :discover_regen, -> { where(type: "ConnectedApp") }
|
||||
scope :affiliate_sales_data, -> { where(type: "ConnectedApps::AffiliateSalesData") }
|
||||
|
||||
scope :connecting, -> { where(data: nil) }
|
||||
scope :ready, -> { where.not(data: nil) }
|
||||
|
||||
def connecting?
|
||||
data.nil?
|
||||
end
|
||||
|
||||
def ready?
|
||||
!connecting?
|
||||
end
|
||||
|
||||
def connect(api_key:, channel:)
|
||||
ConnectAppJob.perform_later(self, api_key, channel:)
|
||||
end
|
||||
|
||||
def disconnect
|
||||
WebhookDeliveryJob.perform_later(
|
||||
data["destroy"],
|
||||
"disconnect-app",
|
||||
nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
13
app/models/connected_apps/affiliate_sales_data.rb
Normal file
13
app/models/connected_apps/affiliate_sales_data.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# An enterprise can opt-in for their data to be included in the affiliate_sales_data endpoint
|
||||
#
|
||||
module ConnectedApps
|
||||
class AffiliateSalesData < ConnectedApp
|
||||
def connect(_opts)
|
||||
update! data: true # not-nil value indicates it is ready
|
||||
end
|
||||
|
||||
def disconnect; end
|
||||
end
|
||||
end
|
||||
@@ -477,7 +477,7 @@ class Enterprise < ApplicationRecord
|
||||
return unless image.variable?
|
||||
|
||||
image_variant_url_for(image.variant(name))
|
||||
rescue ActiveStorage::Error, MiniMagick::Error, ActionView::Template::Error => e
|
||||
rescue StandardError => e
|
||||
Bugsnag.notify "Enterprise ##{id} #{image.try(:name)} error: #{e.message}"
|
||||
Rails.logger.error(e.message)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class OrderCycle < ApplicationRecord
|
||||
where incoming: false
|
||||
}, class_name: "Exchange", dependent: :destroy
|
||||
|
||||
has_many :orders, class_name: 'Spree::Order', dependent: :restrict_with_exception
|
||||
has_many :suppliers, -> { distinct }, source: :sender, through: :cached_incoming_exchanges
|
||||
has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges
|
||||
has_many :order_cycle_schedules, dependent: :destroy
|
||||
@@ -147,17 +148,20 @@ class OrderCycle < ApplicationRecord
|
||||
|
||||
# Find the earliest closing times for each distributor in an active order cycle, and return
|
||||
# them in the format {distributor_id => closing_time, ...}
|
||||
def self.earliest_closing_times
|
||||
Hash[
|
||||
Exchange.
|
||||
outgoing.
|
||||
joins(:order_cycle).
|
||||
merge(OrderCycle.active).
|
||||
group('exchanges.receiver_id').
|
||||
select("exchanges.receiver_id AS receiver_id,
|
||||
MIN(order_cycles.orders_close_at) AS earliest_close_at").
|
||||
map { |ex| [ex.receiver_id, ex.earliest_close_at.to_time] }
|
||||
]
|
||||
#
|
||||
# Optionally, specify some distributor_ids as a parameter to scope the results
|
||||
def self.earliest_closing_times(distributor_ids = nil)
|
||||
cycles = Exchange.
|
||||
outgoing.
|
||||
joins(:order_cycle).
|
||||
merge(OrderCycle.active).
|
||||
group('exchanges.receiver_id')
|
||||
|
||||
cycles = cycles.where(receiver_id: distributor_ids) if distributor_ids.present?
|
||||
|
||||
cycles.pluck("exchanges.receiver_id AS receiver_id",
|
||||
"MIN(order_cycles.orders_close_at) AS earliest_close_at")
|
||||
.to_h
|
||||
end
|
||||
|
||||
def attachable_distributor_payment_methods
|
||||
@@ -313,6 +317,13 @@ class OrderCycle < ApplicationRecord
|
||||
coordinator.sells == 'own'
|
||||
end
|
||||
|
||||
def same_datetime_value(attribute, string)
|
||||
return true if self[attribute].blank? && string.blank?
|
||||
return false if self[attribute].blank? || string.blank?
|
||||
|
||||
DateTime.parse(string).to_fs(:short) == self[attribute]&.to_fs(:short)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def opening?
|
||||
|
||||
@@ -162,7 +162,7 @@ module ProductImport
|
||||
return
|
||||
end
|
||||
|
||||
product = Spree::Product.new
|
||||
product = Spree::Product.new(supplier_id: entry.enterprise_id)
|
||||
product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
|
||||
@@ -377,7 +377,7 @@ module ProductImport
|
||||
end
|
||||
|
||||
def mark_as_new_product(entry)
|
||||
new_product = Spree::Product.new
|
||||
new_product = Spree::Product.new(supplier_id: entry.enterprise_id)
|
||||
new_product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ class ReportBlob < ActiveStorage::Blob
|
||||
# AWS S3 limits URL expiry to one week.
|
||||
LIFETIME = 1.week
|
||||
|
||||
def self.create!(filename, content)
|
||||
def self.create_locally!(filename, content)
|
||||
create_and_upload!(
|
||||
io: StringIO.new(content),
|
||||
filename:,
|
||||
@@ -15,11 +15,34 @@ class ReportBlob < ActiveStorage::Blob
|
||||
)
|
||||
end
|
||||
|
||||
def self.create_for_upload_later!(filename)
|
||||
# ActiveStorage discourages modifying a blob later but we need a blob
|
||||
# before we know anything about the report file. It enables us to use the
|
||||
# same blob in the controller to read the result.
|
||||
create_before_direct_upload!(
|
||||
filename:,
|
||||
byte_size: 0,
|
||||
checksum: "0",
|
||||
content_type: content_type(filename),
|
||||
service_name: :local,
|
||||
).tap do |blob|
|
||||
ActiveStorage::PurgeJob.set(wait: LIFETIME).perform_later(blob)
|
||||
end
|
||||
end
|
||||
|
||||
def self.content_type(filename)
|
||||
MIME::Types.of(filename).first&.to_s || "application/octet-stream"
|
||||
end
|
||||
|
||||
def store(content)
|
||||
io = StringIO.new(content)
|
||||
upload(io, identify: false)
|
||||
save!
|
||||
end
|
||||
|
||||
def result
|
||||
return if checksum == "0"
|
||||
|
||||
@result ||= download.force_encoding(Encoding::UTF_8)
|
||||
end
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ module Spree
|
||||
can [:index, :read], StockLocation
|
||||
can [:index, :read], StockMovement
|
||||
can [:index, :read], Taxon
|
||||
can [:index, :read], Taxonomy
|
||||
can [:index, :read], Variant
|
||||
can [:index, :read], Zone
|
||||
end
|
||||
@@ -245,7 +244,7 @@ module Spree
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :show], ::Admin::ReportsController
|
||||
can [:admin, :show, :customers, :orders_and_distributors, :group_buys, :payments,
|
||||
can [:admin, :show, :create, :customers, :orders_and_distributors, :group_buys, :payments,
|
||||
:orders_and_fulfillment, :products_and_inventory, :order_cycle_management,
|
||||
:packing, :enterprise_fee_summary, :bulk_coop], :report
|
||||
end
|
||||
@@ -325,7 +324,7 @@ module Spree
|
||||
end
|
||||
|
||||
# Reports page
|
||||
can [:admin, :index, :show], ::Admin::ReportsController
|
||||
can [:admin, :index, :show, :create], ::Admin::ReportsController
|
||||
can [:admin, :customers, :group_buys, :sales_tax, :payments,
|
||||
:orders_and_distributors, :orders_and_fulfillment, :products_and_inventory,
|
||||
:order_cycle_management, :xero_invoices, :enterprise_fee_summary, :bulk_coop], :report
|
||||
|
||||
@@ -139,5 +139,8 @@ module Spree
|
||||
|
||||
# Available units
|
||||
preference :available_units, :string, default: "g,kg,T,mL,L,kL"
|
||||
|
||||
# Connected Apps
|
||||
preference :connected_apps_enabled, :string, default: nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,7 +33,7 @@ module Spree
|
||||
return self.class.default_image_url(size) unless attachment.attached?
|
||||
|
||||
image_variant_url_for(variant(size))
|
||||
rescue ActiveStorage::Error, MiniMagick::Error, ActionView::Template::Error => e
|
||||
rescue StandardError => e
|
||||
Bugsnag.notify "Product ##{viewable_id} Image ##{id} error: #{e.message}"
|
||||
Rails.logger.error(e.message)
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ module Spree
|
||||
attr_accessor :price, :display_as, :unit_value, :unit_description, :tax_category_id,
|
||||
:shipping_category_id, :primary_taxon_id, :supplier_id
|
||||
|
||||
after_validation :validate_variant_attrs, on: :create
|
||||
after_create :ensure_standard_variant
|
||||
after_update :touch_supplier, if: :saved_change_to_primary_taxon_id?
|
||||
around_destroy :destruction
|
||||
@@ -262,9 +263,12 @@ module Spree
|
||||
|
||||
# Format as per WeightsAndMeasures (todo: re-orgnaise maybe after product/variant refactor)
|
||||
def variant_unit_with_scale
|
||||
# Our code is based upon English based number formatting with a period `.`
|
||||
scale_clean = ActiveSupport::NumberHelper.number_to_rounded(variant_unit_scale,
|
||||
precision: nil,
|
||||
strip_insignificant_zeros: true)
|
||||
significant: false,
|
||||
strip_insignificant_zeros: true,
|
||||
locale: :en)
|
||||
[variant_unit, scale_clean].compact_blank.join("_")
|
||||
end
|
||||
|
||||
@@ -288,6 +292,15 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def validate_variant_attrs
|
||||
# Avoid running validation when we can't set variant attrs
|
||||
# eg clone product. Will raise error if clonning a product with no variant
|
||||
return if variants.first&.valid?
|
||||
|
||||
errors.add(:primary_taxon_id, :blank) unless Spree::Taxon.find_by(id: primary_taxon_id)
|
||||
errors.add(:supplier_id, :blank) unless Enterprise.find_by(id: supplier_id)
|
||||
end
|
||||
|
||||
def update_units
|
||||
return unless saved_change_to_variant_unit? || saved_change_to_variant_unit_name?
|
||||
|
||||
|
||||
@@ -87,16 +87,20 @@ module Spree
|
||||
|
||||
# Return the services (pickup, delivery) that different distributors provide, in the format:
|
||||
# {distributor_id => {pickup: true, delivery: false}, ...}
|
||||
def self.services
|
||||
Hash[
|
||||
Spree::ShippingMethod.
|
||||
joins(:distributor_shipping_methods).
|
||||
group('distributor_id').
|
||||
select("distributor_id").
|
||||
select("BOOL_OR(spree_shipping_methods.require_ship_address = 'f') AS pickup").
|
||||
select("BOOL_OR(spree_shipping_methods.require_ship_address = 't') AS delivery").
|
||||
map { |sm| [sm.distributor_id.to_i, { pickup: sm.pickup, delivery: sm.delivery }] }
|
||||
]
|
||||
#
|
||||
# Optionally, specify some distributor_ids as a parameter to scope the results
|
||||
def self.services(distributor_ids = nil)
|
||||
methods = Spree::ShippingMethod.joins(:distributor_shipping_methods).group('distributor_id')
|
||||
|
||||
if distributor_ids.present?
|
||||
methods = methods.where(distributor_shipping_methods: { distributor_id: distributor_ids })
|
||||
end
|
||||
|
||||
methods.
|
||||
pluck(Arel.sql("distributor_id"),
|
||||
Arel.sql("BOOL_OR(spree_shipping_methods.require_ship_address = 'f') AS pickup"),
|
||||
Arel.sql("BOOL_OR(spree_shipping_methods.require_ship_address = 't') AS delivery")).
|
||||
to_h { |(distributor_id, pickup, delivery)| [distributor_id.to_i, { pickup:, delivery: }] }
|
||||
end
|
||||
|
||||
def self.backend
|
||||
|
||||
@@ -2,19 +2,11 @@
|
||||
|
||||
module Spree
|
||||
class Taxon < ApplicationRecord
|
||||
self.belongs_to_required_by_default = false
|
||||
|
||||
acts_as_nested_set dependent: :destroy
|
||||
|
||||
belongs_to :taxonomy, class_name: 'Spree::Taxonomy', touch: true
|
||||
|
||||
has_many :variants, class_name: "Spree::Variant", foreign_key: "primary_taxon_id",
|
||||
inverse_of: :primary_taxon, dependent: :restrict_with_error
|
||||
|
||||
has_many :products, through: :variants, dependent: nil
|
||||
|
||||
before_create :set_permalink
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
# Indicate which filters should be used for this taxon
|
||||
@@ -31,40 +23,21 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
def set_permalink
|
||||
if parent.present?
|
||||
self.permalink = [parent.permalink, permalink_end].join('/')
|
||||
elsif permalink.blank?
|
||||
self.permalink = UrlGenerator.to_url(name)
|
||||
end
|
||||
end
|
||||
|
||||
# For #2759
|
||||
def to_param
|
||||
permalink
|
||||
end
|
||||
|
||||
def pretty_name
|
||||
ancestor_chain = ancestors.inject("") do |name, ancestor|
|
||||
name + "#{ancestor.name} -> "
|
||||
end
|
||||
ancestor_chain + name.to_s
|
||||
end
|
||||
|
||||
# Find all the taxons of supplied products for each enterprise, indexed by enterprise.
|
||||
# Format: {enterprise_id => [taxon_id, ...]}
|
||||
def self.supplied_taxons
|
||||
taxons = {}
|
||||
#
|
||||
# Optionally, specify some enterprise_ids to scope the results
|
||||
def self.supplied_taxons(enterprise_ids = nil)
|
||||
taxons = Spree::Taxon.joins(variants: :supplier)
|
||||
|
||||
Spree::Taxon.
|
||||
joins(variants: :supplier).
|
||||
select('spree_taxons.*, enterprises.id AS enterprise_id').
|
||||
each do |t|
|
||||
taxons[t.enterprise_id.to_i] ||= Set.new
|
||||
taxons[t.enterprise_id.to_i] << t.id
|
||||
end
|
||||
taxons = taxons.where(enterprises: { id: enterprise_ids }) if enterprise_ids.present?
|
||||
|
||||
taxons
|
||||
.pluck('spree_taxons.id, enterprises.id AS enterprise_id')
|
||||
.each_with_object({}) do |(taxon_id, enterprise_id), collection|
|
||||
collection[enterprise_id.to_i] ||= Set.new
|
||||
collection[enterprise_id.to_i] << taxon_id
|
||||
end
|
||||
end
|
||||
|
||||
# Find all the taxons of distributed products for each enterprise, indexed by enterprise.
|
||||
@@ -72,7 +45,9 @@ module Spree
|
||||
# or :current taxons (distributed in an open order cycle).
|
||||
#
|
||||
# Format: {enterprise_id => [taxon_id, ...]}
|
||||
def self.distributed_taxons(which_taxons = :all)
|
||||
#
|
||||
# Optionally, specify some enterprise_ids to scope the results
|
||||
def self.distributed_taxons(which_taxons = :all, enterprise_ids = nil)
|
||||
ents_and_vars = ExchangeVariant.joins(exchange: :order_cycle).merge(Exchange.outgoing)
|
||||
.select("DISTINCT variant_id, receiver_id AS enterprise_id")
|
||||
|
||||
@@ -85,18 +60,14 @@ module Spree
|
||||
INNER JOIN (#{ents_and_vars.to_sql}) AS ents_and_vars
|
||||
ON spree_variants.id = ents_and_vars.variant_id")
|
||||
|
||||
if enterprise_ids.present?
|
||||
taxons = taxons.where(ents_and_vars: { enterprise_id: enterprise_ids })
|
||||
end
|
||||
|
||||
taxons.each_with_object({}) do |t, ts|
|
||||
ts[t.enterprise_id.to_i] ||= Set.new
|
||||
ts[t.enterprise_id.to_i] << t.id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def permalink_end
|
||||
return UrlGenerator.to_url(name) if permalink.blank?
|
||||
|
||||
permalink.split('/').last
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
class Taxonomy < ApplicationRecord
|
||||
validates :name, presence: true
|
||||
|
||||
has_many :taxons, dependent: :nullify
|
||||
has_one :root, -> { where parent_id: nil }, class_name: "Spree::Taxon", dependent: :destroy
|
||||
|
||||
after_save :set_name
|
||||
|
||||
default_scope -> { order("#{table_name}.position") }
|
||||
|
||||
private
|
||||
|
||||
def set_name
|
||||
if root
|
||||
root.update_columns(
|
||||
name:,
|
||||
updated_at: Time.zone.now
|
||||
)
|
||||
else
|
||||
self.root = Taxon.create!(taxonomy_id: id, name:)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -156,6 +156,12 @@ module Spree
|
||||
self.disabled_at = value == '1' ? Time.zone.now : nil
|
||||
end
|
||||
|
||||
def affiliate_enterprises
|
||||
return [] unless Flipper.enabled?(:affiliate_sales_data, self)
|
||||
|
||||
Enterprise.joins(:connected_apps).merge(ConnectedApps::AffiliateSalesData.ready)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def password_required?
|
||||
|
||||
@@ -87,7 +87,6 @@ module Spree
|
||||
before_validation :ensure_unit_value
|
||||
before_validation :update_weight_from_unit_value, if: ->(v) { v.product.present? }
|
||||
before_validation :convert_variant_weight_to_decimal
|
||||
before_validation :assign_related_taxon, if: ->(v) { v.primary_taxon.blank? }
|
||||
|
||||
before_save :assign_units, if: ->(variant) {
|
||||
variant.new_record? || variant.changed_attributes.keys.intersection(NAME_FIELDS).any?
|
||||
@@ -217,10 +216,6 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def assign_related_taxon
|
||||
self.primary_taxon ||= product.variants.last&.primary_taxon
|
||||
end
|
||||
|
||||
def check_currency
|
||||
return unless currency.nil?
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ class VariantOverride < ApplicationRecord
|
||||
# Need to ensure this can be set by the user.
|
||||
validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
||||
validates :price, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
||||
validates :count_on_hand, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
||||
validates :count_on_hand, numericality: {
|
||||
greater_than_or_equal_to: 0, unless: :on_demand?
|
||||
}, allow_nil: true
|
||||
|
||||
default_scope { where(permission_revoked_at: nil) }
|
||||
|
||||
@@ -36,9 +38,8 @@ class VariantOverride < ApplicationRecord
|
||||
end
|
||||
|
||||
def stock_overridden?
|
||||
# If count_on_hand is present, it means on_demand is false
|
||||
# See StockSettingsOverrideValidation for details
|
||||
count_on_hand.present?
|
||||
# Testing for not nil because for a boolean `false.present?` is false.
|
||||
!on_demand.nil? || !count_on_hand.nil?
|
||||
end
|
||||
|
||||
def use_producer_stock_settings?
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProductsReflex < ApplicationReflex
|
||||
include Pagy::Backend
|
||||
|
||||
before_reflex :init_filters_params, :init_pagination_params
|
||||
|
||||
def change_per_page
|
||||
@per_page = element.value.to_i
|
||||
@page = 1
|
||||
|
||||
fetch_and_render_products_with_flash
|
||||
end
|
||||
|
||||
def clear_search
|
||||
@search_term = nil
|
||||
@producer_id = nil
|
||||
@category_id = nil
|
||||
@page = 1
|
||||
|
||||
fetch_and_render_products_with_flash
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_filters_params
|
||||
# params comes from the form
|
||||
# _params comes from the url
|
||||
# priority is given to params from the form (if present) over url params
|
||||
@search_term = params[:search_term] || params[:_search_term]
|
||||
@producer_id = params[:producer_id] || params[:_producer_id]
|
||||
@category_id = params[:category_id] || params[:_category_id]
|
||||
end
|
||||
|
||||
def init_pagination_params
|
||||
# prority is given to element dataset (if present) over url params
|
||||
@page = element.dataset.page || params[:_page] || 1
|
||||
@per_page = element.dataset.perpage || params[:_per_page] || 15
|
||||
end
|
||||
|
||||
def fetch_and_render_products_with_flash
|
||||
fetch_products
|
||||
render_products
|
||||
end
|
||||
|
||||
def render_products
|
||||
cable_ready.replace(
|
||||
selector: "#products-content",
|
||||
html: render(partial: "admin/products_v3/content",
|
||||
locals: { products: @products, pagy: @pagy, search_term: @search_term,
|
||||
producer_options: producers, producer_id: @producer_id,
|
||||
category_options: categories, tax_category_options:,
|
||||
category_id: @category_id, flashes: flash })
|
||||
)
|
||||
|
||||
cable_ready.replace_state(
|
||||
url: current_url,
|
||||
)
|
||||
|
||||
morph :nothing
|
||||
end
|
||||
|
||||
def render_products_form_with_flash
|
||||
locals = { products: @products }
|
||||
locals[:error_counts] = @error_counts if @error_counts.present?
|
||||
locals[:flashes] = flash if flash.any?
|
||||
|
||||
cable_ready.replace(
|
||||
selector: "#products-form",
|
||||
html: render(partial: "admin/products_v3/table", locals:)
|
||||
)
|
||||
morph :nothing
|
||||
|
||||
# dunno why this doesn't work. The HTML stops after the first `<col>` element, wtf?!
|
||||
# morph "#products-form", render(partial: "admin/products_v3/table", locals:)
|
||||
end
|
||||
|
||||
def producers
|
||||
producers = OpenFoodNetwork::Permissions.new(current_user)
|
||||
.managed_product_enterprises.is_primary_producer.by_name
|
||||
producers.map { |p| [p.name, p.id] }
|
||||
end
|
||||
|
||||
def categories
|
||||
Spree::Taxon.order(:name).map { |c| [c.name, c.id] }
|
||||
end
|
||||
|
||||
def tax_category_options
|
||||
Spree::TaxCategory.order(:name).pluck(:name, :id)
|
||||
end
|
||||
|
||||
def fetch_products
|
||||
product_query = OpenFoodNetwork::Permissions.new(current_user)
|
||||
.editable_products.merge(product_scope).ransack(ransack_query).result(distinct: true)
|
||||
@pagy, @products = pagy(product_query.order(:name), items: @per_page, page: @page,
|
||||
size: [1, 2, 2, 1])
|
||||
end
|
||||
|
||||
def product_scope
|
||||
scope = if current_user.has_spree_role?("admin") || current_user.enterprises.present?
|
||||
Spree::Product
|
||||
else
|
||||
Spree::Product.active
|
||||
end
|
||||
|
||||
scope.includes(product_query_includes)
|
||||
end
|
||||
|
||||
def ransack_query
|
||||
query = {}
|
||||
query.merge!(variants_supplier_id_in: @producer_id) if @producer_id.present?
|
||||
if @search_term.present?
|
||||
query.merge!(Spree::Variant::SEARCH_KEY => @search_term)
|
||||
end
|
||||
query.merge!(variants_primary_taxon_id_in: @category_id) if @category_id.present?
|
||||
query
|
||||
end
|
||||
|
||||
# Optimise by pre-loading required columns
|
||||
def product_query_includes
|
||||
# TODO: add other fields used in columns? (eg supplier: [:name])
|
||||
[
|
||||
# variants: [
|
||||
# :default_price,
|
||||
# :stock_locations,
|
||||
# :stock_items,
|
||||
# :variant_overrides
|
||||
# ]
|
||||
]
|
||||
end
|
||||
|
||||
def current_url
|
||||
url = URI(request.original_url)
|
||||
url.query = url.query.present? ? "#{url.query}&" : ""
|
||||
# add params with _ to avoid conflicts with params from the form
|
||||
url.query += "_page=#{@page}"
|
||||
url.query += "&_per_page=#{@per_page}"
|
||||
url.query += "&_search_term=#{@search_term}" if @search_term.present?
|
||||
url.query += "&_producer_id=#{@producer_id}" if @producer_id.present?
|
||||
url.query += "&_category_id=#{@category_id}" if @category_id.present?
|
||||
url.to_s
|
||||
end
|
||||
|
||||
# Similar to spree/admin/products_controller
|
||||
def product_set_from_params
|
||||
# Form field names:
|
||||
# '[products][0][id]' (hidden field)
|
||||
# '[products][0][name]'
|
||||
# '[products][0][variants_attributes][0][id]' (hidden field)
|
||||
# '[products][0][variants_attributes][0][display_name]'
|
||||
#
|
||||
# Resulting in params:
|
||||
# "products" => {
|
||||
# "0" => {
|
||||
# "id" => "123"
|
||||
# "name" => "Pommes",
|
||||
# "variants_attributes" => {
|
||||
# "0" => {
|
||||
# "id" => "1234",
|
||||
# "display_name" => "Large box",
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
collection_hash = products_bulk_params[:products]
|
||||
.transform_values { |product|
|
||||
# Convert variants_attributes form hash to an array if present
|
||||
product[:variants_attributes] &&= product[:variants_attributes].values
|
||||
product
|
||||
}.with_indifferent_access
|
||||
Sets::ProductSet.new(collection_attributes: collection_hash)
|
||||
end
|
||||
|
||||
def products_bulk_params
|
||||
params.permit(products: ::PermittedAttributes::Product.attributes)
|
||||
.to_h.with_indifferent_access
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@
|
||||
module Api
|
||||
module Admin
|
||||
class TaxonSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :pretty_name
|
||||
attributes :id, :name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,5 +4,5 @@ class Api::TaxonSerializer < ActiveModel::Serializer
|
||||
cached
|
||||
delegate :cache_key, to: :object
|
||||
|
||||
attributes :id, :name, :permalink, :pretty_name, :position, :parent_id, :taxonomy_id
|
||||
attributes :id, :name, :permalink, :position
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ module Api
|
||||
attributes :orders_close_at, :active
|
||||
|
||||
def orders_close_at
|
||||
options[:data].earliest_closing_times[object.id]
|
||||
options[:data].earliest_closing_times[object.id]&.to_time
|
||||
end
|
||||
|
||||
def active
|
||||
|
||||
@@ -16,7 +16,7 @@ module OrderCycles
|
||||
end
|
||||
|
||||
def products_relation_incl_supplier_properties
|
||||
query = relation_by_sorting(supplier_properties: true)
|
||||
query = relation_by_sorting
|
||||
|
||||
query = supplier_property_join(query)
|
||||
|
||||
@@ -34,10 +34,10 @@ module OrderCycles
|
||||
|
||||
attr_reader :distributor, :order_cycle, :customer
|
||||
|
||||
def relation_by_sorting(supplier_properties: false)
|
||||
def relation_by_sorting
|
||||
query = Spree::Product.where(id: stocked_products)
|
||||
|
||||
if sorting == "by_producer" || supplier_properties
|
||||
if sorting == "by_producer"
|
||||
# Joins on the first product variant to allow us to filter product by supplier. This is so
|
||||
# enterprise can display product sorted by supplier in a custom order on their shopfront.
|
||||
#
|
||||
@@ -57,7 +57,8 @@ module OrderCycles
|
||||
# different category for a given product.
|
||||
query.
|
||||
joins("LEFT JOIN (
|
||||
SELECT DISTINCT ON(product_id) id, product_id, primary_taxon_id
|
||||
SELECT DISTINCT ON(product_id) id, product_id, primary_taxon_id,
|
||||
supplier_id
|
||||
FROM spree_variants WHERE deleted_at IS NULL
|
||||
) first_variant ON spree_products.id = first_variant.product_id").
|
||||
select("spree_products.*, first_variant.primary_taxon_id").
|
||||
@@ -71,6 +72,16 @@ module OrderCycles
|
||||
distributor.preferred_shopfront_product_sorting_method
|
||||
end
|
||||
|
||||
def sorting_by_producer?
|
||||
sorting == "by_producer" &&
|
||||
distributor.preferred_shopfront_producer_order.present?
|
||||
end
|
||||
|
||||
def sorting_by_category?
|
||||
sorting == "by_category" &&
|
||||
distributor.preferred_shopfront_taxon_order.present?
|
||||
end
|
||||
|
||||
def supplier_property_join(query)
|
||||
query.joins("
|
||||
JOIN enterprises ON enterprises.id = first_variant.supplier_id
|
||||
@@ -79,16 +90,14 @@ module OrderCycles
|
||||
end
|
||||
|
||||
def order
|
||||
if distributor.preferred_shopfront_product_sorting_method == "by_producer" &&
|
||||
distributor.preferred_shopfront_producer_order.present?
|
||||
if sorting_by_producer?
|
||||
order_by_producer = distributor
|
||||
.preferred_shopfront_producer_order
|
||||
.split(",").map { |id| "first_variant.supplier_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
|
||||
"#{order_by_producer}, spree_products.name ASC, spree_products.id ASC"
|
||||
elsif distributor.preferred_shopfront_product_sorting_method == "by_category" &&
|
||||
distributor.preferred_shopfront_taxon_order.present?
|
||||
elsif sorting_by_category?
|
||||
order_by_category = distributor
|
||||
.preferred_shopfront_taxon_order
|
||||
.split(",").map { |id| "first_variant.primary_taxon_id=#{id} DESC" }
|
||||
|
||||
@@ -7,6 +7,8 @@ module OrderCycles
|
||||
class FormService
|
||||
def initialize(order_cycle, order_cycle_params, user)
|
||||
@order_cycle = order_cycle
|
||||
@confirm_datetime_change = order_cycle_params.delete :confirm_datetime_change
|
||||
@error_class = order_cycle_params.delete :error_class
|
||||
@order_cycle_params = order_cycle_params
|
||||
@specified_params = order_cycle_params.keys
|
||||
@user = user
|
||||
@@ -21,6 +23,9 @@ module OrderCycles
|
||||
end
|
||||
|
||||
def save
|
||||
# Check that order cycle datetime values changed if it has existing orders
|
||||
verify_datetime_change!
|
||||
|
||||
schedule_ids = build_schedule_ids
|
||||
order_cycle.assign_attributes(order_cycle_params)
|
||||
return false unless order_cycle.valid?
|
||||
@@ -229,5 +234,16 @@ module OrderCycles
|
||||
DistributorShippingMethod.where(distributor_id: user_distributors_ids)
|
||||
.pluck(:id)
|
||||
end
|
||||
|
||||
def verify_datetime_change!
|
||||
return unless @confirm_datetime_change
|
||||
return unless @order_cycle.orders.exists?
|
||||
return if @order_cycle.same_datetime_value(:orders_open_at,
|
||||
@order_cycle_params[:orders_open_at]) &&
|
||||
@order_cycle.same_datetime_value(:orders_close_at,
|
||||
@order_cycle_params[:orders_close_at])
|
||||
|
||||
raise @error_class
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,7 @@ module PermittedAttributes
|
||||
:unit_description, :variant_unit_name,
|
||||
:display_as, :sku, :group_buy, :group_buy_unit_size,
|
||||
:taxon_ids, :primary_taxon_id, :tax_category_id, :supplier_id,
|
||||
:meta_keywords, :notes, :inherits_properties,
|
||||
:meta_keywords, :notes, :inherits_properties, :shipping_category_id,
|
||||
{ product_properties_attributes: [:id, :property_name, :value],
|
||||
variants_attributes: [PermittedAttributes::Variant.attributes],
|
||||
image_attributes: [:attachment] }
|
||||
|
||||
@@ -3,7 +3,31 @@
|
||||
module Sets
|
||||
class OrderCycleSet < ModelSet
|
||||
def initialize(collection, attributes = {})
|
||||
@confirm_datetime_change = attributes.delete :confirm_datetime_change
|
||||
@error_class = attributes.delete :error_class
|
||||
|
||||
super(OrderCycle, collection, attributes)
|
||||
end
|
||||
|
||||
def process(order_cycle, attributes)
|
||||
if @confirm_datetime_change &&
|
||||
order_cycle.orders.exists? &&
|
||||
datetime_value_changed(order_cycle, attributes)
|
||||
raise @error_class
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def datetime_value_changed(order_cycle, attributes)
|
||||
# return true if either key is present in params and change in values detected
|
||||
return true if attributes.key?(:orders_open_at) &&
|
||||
!order_cycle.same_datetime_value(:orders_open_at, attributes[:orders_open_at])
|
||||
|
||||
attributes.key?(:orders_close_at) &&
|
||||
!order_cycle.same_datetime_value(:orders_close_at, attributes[:orders_close_at])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,5 +19,7 @@ class ShopsListService
|
||||
.includes(address: [:state, :country])
|
||||
.includes(:properties)
|
||||
.includes(supplied_products: :properties)
|
||||
.with_attached_promo_image
|
||||
.with_attached_logo
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,9 +26,15 @@ class WeightsAndMeasures
|
||||
def self.variant_unit_options
|
||||
available_units_sorted.flat_map do |measurement, measurement_info|
|
||||
measurement_info.filter_map do |scale, unit_info|
|
||||
# Our code is based upon English based number formatting
|
||||
# Some language locales like +hu+ uses a comma(,) for decimal separator
|
||||
# While in English, decimal separator is represented by a period.
|
||||
# e.g. en: 0.001, hu: 0,001
|
||||
# Hence the results become "weight_0,001" for hu while or code recognizes "weight_0.001"
|
||||
scale_clean =
|
||||
ActiveSupport::NumberHelper.number_to_rounded(scale, precision: nil, significant: false,
|
||||
strip_insignificant_zeros: true)
|
||||
strip_insignificant_zeros: true,
|
||||
locale: :en)
|
||||
[
|
||||
"#{I18n.t(measurement)} (#{unit_info['name']})", # Label (eg "Weight (g)")
|
||||
"#{measurement}_#{scale_clean}", # Scale ID (eg "weight_1")
|
||||
|
||||
25
app/views/admin/connected_app_settings/edit.html.haml
Normal file
25
app/views/admin/connected_app_settings/edit.html.haml
Normal file
@@ -0,0 +1,25 @@
|
||||
= render :partial => 'spree/admin/shared/configuration_menu'
|
||||
|
||||
- content_for :page_title do
|
||||
= t('.title')
|
||||
|
||||
= form_tag main_app.admin_connected_app_settings_path, :method => :put do
|
||||
|
||||
%fieldset
|
||||
%legend= t('.enabled_legend')
|
||||
|
||||
= t('.info_html')
|
||||
|
||||
.field
|
||||
-# Blank value in case nothing is selected
|
||||
= hidden_field_tag("preferences[connected_apps_enabled][]", "")
|
||||
|
||||
- ConnectedApp::TYPES.each do |type|
|
||||
%label
|
||||
= check_box_tag("preferences[connected_apps_enabled][]", type,
|
||||
Spree::Config.connected_apps_enabled&.split(',')&.include?(type))
|
||||
= t('.connected_apps_enabled.' + type)
|
||||
%br
|
||||
|
||||
.form-buttons
|
||||
= button t(:update), 'icon-refresh'
|
||||
@@ -1,30 +1,3 @@
|
||||
%div{ id: "connected-app-discover-regen", class: "enterprise_#{enterprise.id}" }
|
||||
.connected-app__head
|
||||
%div
|
||||
%h3= t ".title"
|
||||
%p= t ".tagline"
|
||||
%div
|
||||
- if enterprise.connected_apps.empty?
|
||||
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id), method: :post, disabled: !managed_by_user?(enterprise)
|
||||
-# This is only seen by super-admins:
|
||||
%em= t(".need_to_be_manager") unless managed_by_user?(enterprise)
|
||||
- elsif enterprise.connected_apps.connecting.present?
|
||||
%button{ disabled: true }
|
||||
%i.spinner.fa.fa-spin.fa-circle-o-notch
|
||||
|
||||
= t ".loading"
|
||||
- else
|
||||
= button_to t(".disable"), admin_enterprise_connected_app_path(0, enterprise_id: enterprise.id), method: :delete
|
||||
|
||||
.connected-app__connection
|
||||
- if enterprise.connected_apps.ready.present?
|
||||
.connected-app__note
|
||||
- link = enterprise.connected_apps[0].data["link"]
|
||||
%p= t ".note"
|
||||
%div
|
||||
%a{ href: link, target: "_blank", class: "button secondary" }
|
||||
= t ".link_label"
|
||||
|
||||
%hr
|
||||
.connected-app__description
|
||||
= t ".description_html"
|
||||
- connected_apps_enabled.each do |type|
|
||||
= render partial: "/admin/enterprises/form/connected_apps/#{type}",
|
||||
locals: { enterprise:, connected_app: enterprise.connected_apps.public_send(type).first }
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
%section.connected_app
|
||||
.connected-app__head
|
||||
%div
|
||||
%h3= t ".title"
|
||||
%p= t ".tagline"
|
||||
%div
|
||||
- if connected_app.nil?
|
||||
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id, type: "ConnectedApps::AffiliateSalesData"), method: :post, disabled: !managed_by_user?(enterprise)
|
||||
-# This is only seen by super-admins:
|
||||
%em= t(".need_to_be_manager") unless managed_by_user?(enterprise)
|
||||
- else
|
||||
= button_to t(".disable"), admin_enterprise_connected_app_path(connected_app.id, enterprise_id: enterprise.id), method: :delete
|
||||
%hr
|
||||
.connected-app__description
|
||||
= t ".description_html"
|
||||
@@ -0,0 +1,30 @@
|
||||
%section.connected_app{ id: "connected-app-discover-regen", class: "enterprise_#{enterprise.id}" }
|
||||
.connected-app__head
|
||||
%div
|
||||
%h3= t ".title"
|
||||
%p= t ".tagline"
|
||||
%div
|
||||
- if connected_app.nil?
|
||||
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id), method: :post, disabled: !managed_by_user?(enterprise)
|
||||
-# This is only seen by super-admins:
|
||||
%em= t(".need_to_be_manager") unless managed_by_user?(enterprise)
|
||||
- elsif connected_app&.connecting?
|
||||
%button{ disabled: true }
|
||||
%i.spinner.fa.fa-spin.fa-circle-o-notch
|
||||
|
||||
= t ".loading"
|
||||
- else
|
||||
= button_to t(".disable"), admin_enterprise_connected_app_path(connected_app.id, enterprise_id: enterprise.id), method: :delete
|
||||
|
||||
.connected-app__connection
|
||||
- if connected_app&.ready?
|
||||
.connected-app__note
|
||||
- link = connected_app.data["link"]
|
||||
%p= t ".note"
|
||||
%div
|
||||
%a{ href: link, target: "_blank", class: "button secondary" }
|
||||
= t ".link_label"
|
||||
|
||||
%hr
|
||||
.connected-app__description
|
||||
= t ".description_html"
|
||||
@@ -0,0 +1,21 @@
|
||||
.flex-column-gap-1
|
||||
%h6
|
||||
= t('.title')
|
||||
%div{ style: 'font-size: 1rem;' }
|
||||
= t('.content')
|
||||
%p.modal-actions.justify-end.gap-1
|
||||
- if action == 'simple_update'
|
||||
%button.button.secondary{ "ng-click": "submit($event, null)", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'save' } }
|
||||
= t('.proceed')
|
||||
%button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndNext' } }
|
||||
= t('.proceed')
|
||||
%button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndBack' } }
|
||||
= t('.proceed')
|
||||
%button.button.primary{ type: "button", 'data-action': 'click->modal#close' }
|
||||
= t('.cancel')
|
||||
|
||||
- if action == 'bulk_update'
|
||||
%button.button.secondary{ "ng-click": "saveAll($event)", type: "button", style: "display: none;", data: { action: 'click->modal#close', trigger_action: 'bulk_save' } }
|
||||
= t('.proceed')
|
||||
%button.button.primary{ type: "button", 'data-action': 'click->modal#close' }
|
||||
= t('.cancel')
|
||||
@@ -16,18 +16,25 @@
|
||||
|
||||
- ng_controller = @order_cycle.simple? ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl'
|
||||
= admin_inject_order_cycle_instance(@order_cycle)
|
||||
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f|
|
||||
%div{ data: { controller: 'order-cycle-form' } }
|
||||
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f|
|
||||
|
||||
%save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
|
||||
%input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'save' } }
|
||||
- if @order_cycle.simple?
|
||||
%input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndBack' } }
|
||||
- else
|
||||
%input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndNext' } }
|
||||
%input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" }
|
||||
%input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" }
|
||||
|
||||
%save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
|
||||
%input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" }
|
||||
- if @order_cycle.simple?
|
||||
%input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" }
|
||||
= render 'simple_form', f: f
|
||||
- else
|
||||
%input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" }
|
||||
%input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" }
|
||||
%input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" }
|
||||
= render 'form', f: f
|
||||
|
||||
- if @order_cycle.simple?
|
||||
= render 'simple_form', f: f
|
||||
- else
|
||||
= render 'form', f: f
|
||||
%div.warning-modal{ data: { controller: 'modal modal-link', 'modal-link-target-value': "linked-order-warning-modal" } }
|
||||
%button.modal-target-trigger{ type: 'button', data: { 'action': 'modal-link#open' }, style: 'display: none;' }
|
||||
= render ModalComponent.new(id: "linked-order-warning-modal", close_button: false) do
|
||||
.content.flex-column.gap-2
|
||||
= render 'date_time_warning_modal_content', action: 'simple_update'
|
||||
@@ -23,12 +23,20 @@
|
||||
.thirteen.columns.alpha
|
||||
%columns-dropdown{ action: "#{controller_name}_#{action_name}" }
|
||||
|
||||
%form{ name: 'order_cycles_form' }
|
||||
%save-bar{ dirty: "order_cycles_form.$dirty", persist: "false" }
|
||||
%input.red{ type: "button", value: t(:save_changes), "ng-click": "saveAll()", "ng-disabled": "!order_cycles_form.$dirty" }
|
||||
%table.index#listing_order_cycles{ "ng-show": 'orderCycles.length > 0' }
|
||||
= render 'admin/order_cycles/header' #, simple_index: simple_index
|
||||
%tbody
|
||||
= render 'admin/order_cycles/row' #, simple_index: simple_index
|
||||
= render 'admin/order_cycles/loading_flash'
|
||||
= render 'admin/order_cycles/show_more'
|
||||
%div{ data: { controller: 'order-cycle-form' } }
|
||||
%form{ name: 'order_cycles_form' }
|
||||
%save-bar{ dirty: "order_cycles_form.$dirty", persist: "false" }
|
||||
%input.red{ type: "button", value: t(:save_changes), "ng-click": "saveAll($event)", "ng-disabled": "!order_cycles_form.$dirty",
|
||||
data: { confirm: "true", 'trigger-action': 'bulk_save' } }
|
||||
%table.index#listing_order_cycles{ "ng-show": 'orderCycles.length > 0' }
|
||||
= render 'admin/order_cycles/header' #, simple_index: simple_index
|
||||
%tbody
|
||||
= render 'admin/order_cycles/row' #, simple_index: simple_index
|
||||
= render 'admin/order_cycles/loading_flash'
|
||||
= render 'admin/order_cycles/show_more'
|
||||
|
||||
%div.warning-modal{ data: { controller: 'modal modal-link', 'modal-link-target-value': "linked-order-warning-modal" } }
|
||||
%button.modal-target-trigger{ type: 'button', data: { 'action': 'modal-link#open' }, style: 'display: none;' }
|
||||
= render ModalComponent.new(id: "linked-order-warning-modal", close_button: false) do
|
||||
.content.flex-column.gap-2
|
||||
= render 'date_time_warning_modal_content', action: 'bulk_update'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
= form_with url: admin_products_path, id: "filters", method: :get, data: { "search-target": "form", 'turbo-frame': "_self" } do
|
||||
= form_with url: admin_products_path, id: "filters", method: :get, data: { "search-target": "form", 'turbo-frame': "_self", 'turbo-action': "advance" } do
|
||||
= hidden_field_tag :page, nil, class: "page"
|
||||
= hidden_field_tag :per_page, nil, class: "per-page"
|
||||
= hidden_field_tag :per_page, params[:per_page], class: "per-page"
|
||||
= hidden_field_tag '[q][s]', params.dig(:q, :s) || 'name asc', class: 'sort', 'data-default': 'name asc'
|
||||
|
||||
.query
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- if search_term.present? || producer_id.present? || category_id.present?
|
||||
= t('.no_products_found_for_search')
|
||||
%a{ href: "#", class: "button disruptive relaxed", data: { reflex: "click->products#clear_search" } }
|
||||
%a{ href: admin_products_path, class: "button disruptive relaxed", 'data-turbo-frame': "_self" }
|
||||
= t("admin.products_v3.sort.pagination.clear_search")
|
||||
- else
|
||||
= t('.no_products_found')
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
-# empty
|
||||
%td.col-on_hand.align-right
|
||||
-# empty
|
||||
%td.col-on_hand.align-right
|
||||
%td.col-producer.align-right
|
||||
-# empty
|
||||
%td.col-category.align-left
|
||||
-# empty
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#sort
|
||||
%div.pagination-description
|
||||
- if pagy.present?
|
||||
= t(".pagination.total_html", total: pagy.count, from: pagy.from, to: pagy.to)
|
||||
= t(".pagination.products_total_html", count: pagy.count, from: pagy.from, to: pagy.to)
|
||||
|
||||
- if search_term.present? || producer_id.present? || category_id.present?
|
||||
%a{ href: url_for(page: 1), class: "button disruptive", 'data-turbo-frame': "_self" }
|
||||
%a{ href: url_for(page: 1), class: "button disruptive", data: { 'turbo-frame': "_self", 'turbo-action': "advance" } }
|
||||
= t(".pagination.clear_search")
|
||||
|
||||
%form.with-dropdown
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
%table.products{ 'data-column-preferences-target': "table" }
|
||||
%colgroup
|
||||
-# The `min-width` property works in Chrome but not Firefox so is considered progressive enhancement.
|
||||
%col.col-image{ width:"56px" }= # (image size + padding)
|
||||
%col.col-image{ width:"44px" }= # (image size + padding)
|
||||
%col.col-name{ style:"min-width: 6em" }= # (grow to fill)
|
||||
%col.col-sku{ width:"8%", style:"min-width: 6em" }
|
||||
%col.col-unit_scale{ width:"8%" }
|
||||
%col.col-unit{ width:"8%" }
|
||||
%col.col-price{ width:"5%", style:"min-width: 5em" }
|
||||
%col.col-on_hand{ width:"10%"}
|
||||
%col.col-price{ width:"10%", style:"min-width: 5em" }
|
||||
%col.col-on_hand{ width:"8%" }
|
||||
%col.col-producer{ style:"min-width: 6em" }= # (grow to fill)
|
||||
%col.col-category{ width:"8%" }
|
||||
%col.col-tax_category{ width:"8%" }
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
= f.hidden_field :unit_value
|
||||
= f.hidden_field :unit_description
|
||||
= f.text_field :unit_value_with_description,
|
||||
value: unit_value_with_description(variant), 'aria-label': t('admin.products_page.columns.unit_value'), required: true
|
||||
value: unit_value_with_description(variant), 'aria-label': t('admin.products_page.columns.unit_value')
|
||||
.field
|
||||
= f.label :display_as, t('admin.products_page.columns.display_as')
|
||||
= f.text_field :display_as, placeholder: VariantUnits::OptionValueNamer.new(variant).name
|
||||
@@ -37,20 +37,24 @@
|
||||
= f.label :on_demand do
|
||||
= f.check_box :on_demand, 'data-action': 'change->toggle-control#disableIfPresent change->popout#closeIfChecked'
|
||||
= t(:on_demand)
|
||||
%td.col-producer.naked_inputs
|
||||
%td.col-producer.field.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :supplier_id,
|
||||
aria_label: t('.producer_field_name'),
|
||||
options: producer_options,
|
||||
selected_option: variant.supplier_id,
|
||||
placeholder_value: t('admin.products_v3.filters.search_for_producers')))
|
||||
include_blank: t('admin.products_v3.filters.select_producer'),
|
||||
placeholder_value: t('admin.products_v3.filters.select_producer')))
|
||||
= error_message_on variant, :supplier
|
||||
%td.col-category.field.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :primary_taxon_id,
|
||||
options: category_options,
|
||||
selected_option: variant.primary_taxon_id,
|
||||
aria_label: t('.category_field_name'),
|
||||
placeholder_value: t('admin.products_v3.filters.search_for_categories')))
|
||||
include_blank: t('admin.products_v3.filters.select_category'),
|
||||
placeholder_value: t('admin.products_v3.filters.select_category')))
|
||||
= error_message_on variant, :primary_taxon
|
||||
%td.col-tax_category.field.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :tax_category_id,
|
||||
|
||||
47
app/views/admin/reports/_fallback_display.html.haml
Normal file
47
app/views/admin/reports/_fallback_display.html.haml
Normal file
@@ -0,0 +1,47 @@
|
||||
.download.hidden
|
||||
= link_to t("admin.reports.download.button"), file_url, target: "_blank", class: "button icon icon-file"
|
||||
|
||||
:javascript
|
||||
(function () {
|
||||
const tryDownload = function() {
|
||||
const link = document.querySelector(".download a");
|
||||
|
||||
// If the report was already rendered via web sockets:
|
||||
if (link == null) return;
|
||||
|
||||
fetch(link.href).then((response) => {
|
||||
if (response.ok) {
|
||||
response.blob().then((blob) => blob.text()).then((text) => {
|
||||
const loading = document.querySelector(".loading");
|
||||
|
||||
if (loading == null) return;
|
||||
|
||||
loading.remove();
|
||||
document.querySelector("#report-go button").disabled = false;
|
||||
|
||||
if (link.href.endsWith(".html")) {
|
||||
// This replaces the hidden download button with the report:
|
||||
link.parentElement.outerHTML = text;
|
||||
} else {
|
||||
// Or just show the download button when it's ready:
|
||||
document.querySelector(".download").classList.remove("hidden")
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setTimeout(tryDownload, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
A lot of reports are rendered within 250ms. Others take at least
|
||||
2.5 seconds. There's a big gap in between. Observed on:
|
||||
https://openfoodnetwork.org.au/admin/sidekiq/metrics/ReportJob?period=8h
|
||||
https://openfoodnetwork.org.uk/admin/sidekiq/metrics/ReportJob?period=8h
|
||||
https://coopcircuits.fr/admin/sidekiq/metrics/ReportJob?period=8h
|
||||
|
||||
But let's leave the timed response to websockets for now and just poll
|
||||
as a backup mechanism.
|
||||
*/
|
||||
setTimeout(tryDownload, 3000);
|
||||
})();
|
||||
6
app/views/admin/reports/create.turbo_stream.haml
Normal file
6
app/views/admin/reports/create.turbo_stream.haml
Normal file
@@ -0,0 +1,6 @@
|
||||
= turbo_stream.update "report-go" do
|
||||
= button t(:go), "report__submit-btn", "submit", disabled: true
|
||||
= turbo_stream.update "report-table" do
|
||||
= render "admin/reports/loading"
|
||||
= render "admin/reports/fallback_display", file_url: @blob.expiring_service_url
|
||||
= turbo_stream.scroll_into_view("#report-table", behavior: "smooth")
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
- content_for :minimal_js, true
|
||||
|
||||
= form_for @report.search, { url: url_for, data: { remote: "true" } } do |f|
|
||||
= form_for @report.search, { url: url_for, data: { turbo: "true" } } do |f|
|
||||
= hidden_field_tag "uuid", request.uuid
|
||||
|
||||
%fieldset.no-border-bottom.print-hidden
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
|
||||
%title
|
||||
= Spree::Config[:site_name]
|
||||
= stylesheet_pack_tag 'mail'
|
||||
= stylesheet_link_tag 'mail'
|
||||
%body{:bgcolor => "#FFFFFF" }
|
||||
- unless @hide_ofn_navigation
|
||||
%table.head-wrap{:bgcolor => "#f2f2f2"}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
= cache_with_locale do
|
||||
.small-4.medium-4.large-5.columns.variant-name
|
||||
.small-3.columns.variant-name
|
||||
.inline{"ng-if" => "::variant.display_name"} {{ ::variant.display_name }}
|
||||
.variant-unit {{ ::variant.unit_to_display }}
|
||||
.small-3.medium-3.large-2.columns.variant-price
|
||||
.small-4.medium-3.columns.variant-price
|
||||
%price-breakdown{"price-breakdown" => "_", variant: "variant",
|
||||
"price-breakdown-append-to-body" => "true",
|
||||
"price-breakdown-placement" => "bottom",
|
||||
@@ -16,7 +16,7 @@
|
||||
key: "'js.shopfront.unit_price_tooltip'"}
|
||||
{{ variant.unit_price_price | localizeCurrency }} / {{ variant.unit_price_unit }}
|
||||
|
||||
.medium-2.large-2.columns.total-price
|
||||
.medium-3.columns.total-price
|
||||
%span{"ng-class" => "{filled: variant.line_item.total_price}"}
|
||||
{{ variant.line_item.total_price | localizeCurrency }}
|
||||
= render partial: "shop/products/shop_variant_no_group_buy"
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
%tr
|
||||
%th{:align => "left"}
|
||||
%h5= t(:invoice_column_item)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%th{:align => "right", :width => "6%" }
|
||||
%h5= t(:invoice_column_qty)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h5= t(:invoice_column_weight_volume)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h5= t(:invoice_column_price_per_unit_without_taxes)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%th{:align => "right", :width => "12%"}
|
||||
%h5= t(:invoice_column_price_without_taxes)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%th{:align => "right", :width => "9%" }
|
||||
%h5= t(:invoice_column_tax_rate)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%th{:align => "right", :width => "12%" }
|
||||
%h5= t(:invoice_column_price_with_taxes)
|
||||
%tbody
|
||||
- @order.sorted_line_items.each do |item|
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= pdf_stylesheet_pack_tag "mail"
|
||||
= wicked_pdf_stylesheet_link_tag "mail"
|
||||
|
||||
%table{:width => "100%"}
|
||||
%tbody
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= pdf_stylesheet_pack_tag "mail"
|
||||
= wicked_pdf_stylesheet_link_tag "mail"
|
||||
|
||||
%table{:width => "100%"}
|
||||
%tbody
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
= pdf_stylesheet_pack_tag "mail"
|
||||
= wicked_pdf_stylesheet_link_tag "mail"
|
||||
|
||||
%table{:width => "100%"}
|
||||
%tbody
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
= f.field_container :primary_taxon do
|
||||
= f.field_container :primary_taxon_id do
|
||||
= f.label :primary_taxon_id, t('.product_category')
|
||||
%span.required *
|
||||
%br
|
||||
= f.collection_select(:primary_taxon_id, Spree::Taxon.order(:name), :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"})
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :primary_taxon_id,
|
||||
aria_label: t('.product_category'),
|
||||
options: Spree::Taxon.select(:name, :id).order(:name).pluck(:name, :id),
|
||||
selected_option: @product.primary_taxon_id,
|
||||
include_blank: true,
|
||||
placeholder_value: t('.search_for_categories')))
|
||||
= f.error_message_on :primary_taxon_id
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user