mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-12 18:36:49 +00:00
Compare commits
518 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3616a27566 | ||
|
|
5ae61017f5 | ||
|
|
61dffb8821 | ||
|
|
f9fabd088c | ||
|
|
75c7e0b939 | ||
|
|
62eb2bba13 | ||
|
|
3fa9e857a8 | ||
|
|
7da10db055 | ||
|
|
7b2a146404 | ||
|
|
ddf68444fb | ||
|
|
4867649fc9 | ||
|
|
35841066d1 | ||
|
|
9bc81f236c | ||
|
|
e26a54d897 | ||
|
|
ed1d637eb5 | ||
|
|
831284c5c8 | ||
|
|
e45b8f6981 | ||
|
|
d0f8f985fb | ||
|
|
33eec61af8 | ||
|
|
00c324ae45 | ||
|
|
12a016d31d | ||
|
|
af50bde088 | ||
|
|
340b92e580 | ||
|
|
f64653db14 | ||
|
|
e6ef661f0d | ||
|
|
6a621f47aa | ||
|
|
dae23d3c3b | ||
|
|
def2693afc | ||
|
|
55f26f0a3d | ||
|
|
7245d1eff4 | ||
|
|
87ef0215b8 | ||
|
|
4b6a02cb92 | ||
|
|
196a301133 | ||
|
|
b9b8bc70dd | ||
|
|
d42da8737e | ||
|
|
6419edcb1f | ||
|
|
fca82375e8 | ||
|
|
ca0f746cf1 | ||
|
|
a93cf46f50 | ||
|
|
74a030f6db | ||
|
|
1f75c7e5e4 | ||
|
|
a1e6e4c38f | ||
|
|
77fb73f802 | ||
|
|
9f7376a5ae | ||
|
|
eae373ef4b | ||
|
|
465332d5f9 | ||
|
|
e98b89625f | ||
|
|
cf61254c9e | ||
|
|
1d92d6cc33 | ||
|
|
6b32764c99 | ||
|
|
be4fbc4d23 | ||
|
|
ea72a10b9a | ||
|
|
9b567a6710 | ||
|
|
a8ce31fa90 | ||
|
|
4c964fea0d | ||
|
|
6d7c41c04b | ||
|
|
a6a695660f | ||
|
|
0058ef5e04 | ||
|
|
4831bae9cb | ||
|
|
f7679780de | ||
|
|
f2ac354e00 | ||
|
|
88ffa46ce7 | ||
|
|
869431c68d | ||
|
|
a1f8530489 | ||
|
|
a75a0da981 | ||
|
|
5744240f91 | ||
|
|
873c56a642 | ||
|
|
f53a75660f | ||
|
|
50dcbe0b57 | ||
|
|
f9861fee79 | ||
|
|
d8ae97f923 | ||
|
|
9f5db217fe | ||
|
|
c1c5dcac09 | ||
|
|
6064f096a7 | ||
|
|
a62fa755f5 | ||
|
|
fa25991f0f | ||
|
|
6f2be1dfa6 | ||
|
|
c81a9fd032 | ||
|
|
665d0cc86b | ||
|
|
a34d8a3f20 | ||
|
|
6e35701dd6 | ||
|
|
9991458d2a | ||
|
|
a843fd73b7 | ||
|
|
9765b1e908 | ||
|
|
5ccadc1f7d | ||
|
|
539805e2c2 | ||
|
|
fac64ef5bf | ||
|
|
a17a0a62bc | ||
|
|
1e91de106f | ||
|
|
73252132a6 | ||
|
|
e3c383c61c | ||
|
|
aafb134be7 | ||
|
|
70a1996435 | ||
|
|
1712cb2617 | ||
|
|
25df057d05 | ||
|
|
24a9991162 | ||
|
|
156ff2da89 | ||
|
|
e36b4ed01a | ||
|
|
41b775b173 | ||
|
|
35570feba0 | ||
|
|
1d8cfe59a2 | ||
|
|
abd3efe82a | ||
|
|
389b53cb71 | ||
|
|
4f60273198 | ||
|
|
856fa8685a | ||
|
|
9ac4c0ba30 | ||
|
|
2709f237f5 | ||
|
|
9db4edcd0d | ||
|
|
22c9f33f4c | ||
|
|
24ea2a4398 | ||
|
|
8178f5388c | ||
|
|
c0c5e9d7dd | ||
|
|
19f8f5c6fa | ||
|
|
83a306c31b | ||
|
|
d6020cdb6f | ||
|
|
77c78f35d3 | ||
|
|
2280b15664 | ||
|
|
f843a0b4d9 | ||
|
|
965a274332 | ||
|
|
cb2a842746 | ||
|
|
a6655623d0 | ||
|
|
2a2d05ad39 | ||
|
|
e8127d81dc | ||
|
|
cd7906a57b | ||
|
|
02c573f146 | ||
|
|
112017a158 | ||
|
|
4a07d67037 | ||
|
|
54156dfd32 | ||
|
|
ee20d35487 | ||
|
|
be18244abc | ||
|
|
e58546a412 | ||
|
|
2c279fd02d | ||
|
|
8a048cc155 | ||
|
|
b3c378e8c1 | ||
|
|
39475be792 | ||
|
|
9e2e460ac5 | ||
|
|
18006ea9c8 | ||
|
|
71b648e9fa | ||
|
|
eacd76bfa4 | ||
|
|
791ff842aa | ||
|
|
ac0123734a | ||
|
|
7cab99efdf | ||
|
|
5378bb7b34 | ||
|
|
942824cd74 | ||
|
|
bdeb56bfaf | ||
|
|
fb4e573cfa | ||
|
|
e65df31bc3 | ||
|
|
ce1ac57522 | ||
|
|
09ff57d462 | ||
|
|
542cf0cf4f | ||
|
|
f4113745ce | ||
|
|
6d197c53e0 | ||
|
|
b2c6e6271c | ||
|
|
cdd36eeefc | ||
|
|
97148f6f57 | ||
|
|
6219b3f0c3 | ||
|
|
d66cac7a26 | ||
|
|
07c11b9b1f | ||
|
|
6a232a1f36 | ||
|
|
1a61357be8 | ||
|
|
b2eeb5d687 | ||
|
|
ffeca41ef4 | ||
|
|
68c0936766 | ||
|
|
23b6267e30 | ||
|
|
6872ccfb72 | ||
|
|
b1b791e4ee | ||
|
|
1779b759f7 | ||
|
|
3ebbf5a55d | ||
|
|
83ce2ac3dc | ||
|
|
aaf850a095 | ||
|
|
3e9bf8aa1f | ||
|
|
95832c96ac | ||
|
|
8a700307d2 | ||
|
|
86accc227e | ||
|
|
1756ddd0e9 | ||
|
|
fe71781d70 | ||
|
|
ec65951ef3 | ||
|
|
1681e8a572 | ||
|
|
e897eb0439 | ||
|
|
36e3362fc1 | ||
|
|
10e123a9c4 | ||
|
|
0101dcdd93 | ||
|
|
9416f61fb3 | ||
|
|
8efd69c3d1 | ||
|
|
dcbdfbb081 | ||
|
|
92968c5efe | ||
|
|
c8ac64566f | ||
|
|
633c7737e4 | ||
|
|
05a72be273 | ||
|
|
b1cd950051 | ||
|
|
f557996817 | ||
|
|
1a64a737d4 | ||
|
|
7c0087cb30 | ||
|
|
17448699f9 | ||
|
|
a413f22e12 | ||
|
|
893952f46b | ||
|
|
061e3cd722 | ||
|
|
a83790951d | ||
|
|
4e33529845 | ||
|
|
a2a8b330b7 | ||
|
|
9669016573 | ||
|
|
2755cb9ec7 | ||
|
|
fdcc4c2447 | ||
|
|
f42bf72b32 | ||
|
|
8ab77b077b | ||
|
|
9b03833df0 | ||
|
|
fada30435f | ||
|
|
fc0ffda8ec | ||
|
|
397729ed3d | ||
|
|
88312b8e4a | ||
|
|
9be3ff90f7 | ||
|
|
a5b5e5de32 | ||
|
|
e66dea7e03 | ||
|
|
4c5ed2fa45 | ||
|
|
40c0e69f11 | ||
|
|
f3837fb6af | ||
|
|
db7b7bbde2 | ||
|
|
ef61310bad | ||
|
|
91da0114e3 | ||
|
|
ee301c5e2f | ||
|
|
4a67ffab25 | ||
|
|
4d060815d0 | ||
|
|
f91bd03c25 | ||
|
|
7de4ec2a90 | ||
|
|
812d8cac4a | ||
|
|
d1faed282f | ||
|
|
26195314eb | ||
|
|
912483660f | ||
|
|
d12a7c2c76 | ||
|
|
dee1c3d139 | ||
|
|
101cff02c6 | ||
|
|
bb9c54a445 | ||
|
|
5ce2af454e | ||
|
|
e5f396f975 | ||
|
|
a57a93d414 | ||
|
|
a82b1d8129 | ||
|
|
5184fa540c | ||
|
|
951787d456 | ||
|
|
b35743b6e4 | ||
|
|
4fc30ba50e | ||
|
|
97a401a307 | ||
|
|
232e3b8262 | ||
|
|
b3b66d5825 | ||
|
|
1b532f995c | ||
|
|
f5544494f0 | ||
|
|
2de32b54ba | ||
|
|
b6e2dadbb3 | ||
|
|
473e635d54 | ||
|
|
3e11cdb5c1 | ||
|
|
842e8da2c3 | ||
|
|
24f8e6d2ec | ||
|
|
568d58405d | ||
|
|
b23489fa40 | ||
|
|
503658f930 | ||
|
|
24bf5f0fea | ||
|
|
9d86249bcb | ||
|
|
3e37c8a3f1 | ||
|
|
691d7d735b | ||
|
|
6d1dd76590 | ||
|
|
356e00bfbb | ||
|
|
5534109122 | ||
|
|
ac8790ecb3 | ||
|
|
2eb8121644 | ||
|
|
b685e0d3a7 | ||
|
|
1dd3d86a2b | ||
|
|
7ee41902f2 | ||
|
|
490ced4a92 | ||
|
|
a682115fb1 | ||
|
|
653067f58c | ||
|
|
06ed9c838e | ||
|
|
726d87de6b | ||
|
|
fbb97c3db8 | ||
|
|
cea8cbd924 | ||
|
|
73372a58ef | ||
|
|
287d6a926a | ||
|
|
04b07a1ff5 | ||
|
|
0da7c93bc6 | ||
|
|
bfe41d4cbc | ||
|
|
3e6e0b73ee | ||
|
|
592a468448 | ||
|
|
f09b1fc1a8 | ||
|
|
bdbd869b06 | ||
|
|
8b42bd205a | ||
|
|
6108a74fec | ||
|
|
5ecab7a0c2 | ||
|
|
d8785bdaba | ||
|
|
00841cb537 | ||
|
|
61c8859da8 | ||
|
|
e33ce235c4 | ||
|
|
ec2f99a467 | ||
|
|
67b5f08b07 | ||
|
|
95170bacd5 | ||
|
|
eebc49f27c | ||
|
|
8b0296eae6 | ||
|
|
b6120a9105 | ||
|
|
5d3dbca9c3 | ||
|
|
5bbd63bcd8 | ||
|
|
980b4a86ab | ||
|
|
118ed79070 | ||
|
|
623d1e0285 | ||
|
|
2dcced8810 | ||
|
|
5100ad6b51 | ||
|
|
7e2bead54d | ||
|
|
2b6e6c62dd | ||
|
|
05a15d9441 | ||
|
|
567196fe0e | ||
|
|
7bd32d4967 | ||
|
|
9b6f1a5e11 | ||
|
|
84389d1392 | ||
|
|
da3b467d47 | ||
|
|
ceffae2efc | ||
|
|
f19a6f0dec | ||
|
|
0a155da273 | ||
|
|
c0d02c8d42 | ||
|
|
e68d72c0dd | ||
|
|
034e8b180a | ||
|
|
ead0e1c08d | ||
|
|
8dfb628d88 | ||
|
|
85b3d7dac5 | ||
|
|
84040fd2a6 | ||
|
|
5dcf456c90 | ||
|
|
79ec3d5a3f | ||
|
|
e893197c49 | ||
|
|
60e67ae1a4 | ||
|
|
53a0f70aa0 | ||
|
|
64d6ae445b | ||
|
|
5712f67673 | ||
|
|
9c45444ea6 | ||
|
|
4e41a73c4f | ||
|
|
75267268d7 | ||
|
|
7455053879 | ||
|
|
c5addb8c1d | ||
|
|
e5d4b216ba | ||
|
|
b4fe44510b | ||
|
|
9df5d78f27 | ||
|
|
f9d72e10d5 | ||
|
|
723499332a | ||
|
|
6a06e0ac3b | ||
|
|
5600102729 | ||
|
|
a6ea975848 | ||
|
|
57917a498b | ||
|
|
b30da27e92 | ||
|
|
1e1e88fe51 | ||
|
|
e4c3c1664a | ||
|
|
5b0c8bbaef | ||
|
|
71f396a44f | ||
|
|
eb162f6b29 | ||
|
|
5aea361d87 | ||
|
|
3721c017fa | ||
|
|
12f0ed9955 | ||
|
|
4c4881430c | ||
|
|
bcc02f35e7 | ||
|
|
08b2b19d5e | ||
|
|
cccd35fdf3 | ||
|
|
d8ece7cd8b | ||
|
|
ee65d70eec | ||
|
|
ab1ed53507 | ||
|
|
2b3865855d | ||
|
|
1da18d3386 | ||
|
|
b025df1798 | ||
|
|
178924af5d | ||
|
|
bb1f7fde2f | ||
|
|
c61e7a1672 | ||
|
|
3c964933b1 | ||
|
|
e7b625edcf | ||
|
|
eab6cc563b | ||
|
|
8daf9b9247 | ||
|
|
5ba0f906a1 | ||
|
|
35c8236c21 | ||
|
|
b397a8e661 | ||
|
|
61def8f2e7 | ||
|
|
4ff0bf8162 | ||
|
|
02b9fca620 | ||
|
|
40f7d07e27 | ||
|
|
c56486d7ae | ||
|
|
56db90f49e | ||
|
|
a8b4037885 | ||
|
|
fc145d472d | ||
|
|
5037cce6f5 | ||
|
|
c399491314 | ||
|
|
c15c5435ff | ||
|
|
0cdb49818d | ||
|
|
9548f5c5f7 | ||
|
|
ba91abd20e | ||
|
|
0d07bf2a3b | ||
|
|
07a6e62d09 | ||
|
|
e9667ab289 | ||
|
|
d003ba81e3 | ||
|
|
a783f4609a | ||
|
|
756b3ec73b | ||
|
|
d9b47ad1b3 | ||
|
|
0235a04530 | ||
|
|
043abdef39 | ||
|
|
b372e131a1 | ||
|
|
642d3cbfbd | ||
|
|
930de1b6a2 | ||
|
|
e3db218e7c | ||
|
|
fcff302d55 | ||
|
|
150310bf67 | ||
|
|
926fc5ee43 | ||
|
|
ccc7a43a06 | ||
|
|
e4fcaa5992 | ||
|
|
b1d4461c77 | ||
|
|
45c77196b3 | ||
|
|
13d41dc1aa | ||
|
|
5ca382be42 | ||
|
|
3259db69f0 | ||
|
|
6efb71d903 | ||
|
|
91401446a5 | ||
|
|
59593c824a | ||
|
|
79cf03b124 | ||
|
|
a6b3c26bbe | ||
|
|
8345765ada | ||
|
|
03fb33ba86 | ||
|
|
2c8ce6e4e5 | ||
|
|
3e10c703bf | ||
|
|
42f8b2efed | ||
|
|
bd493c392a | ||
|
|
dc701f55b3 | ||
|
|
06e3328ce2 | ||
|
|
91fcb7c7c4 | ||
|
|
42f2e78b10 | ||
|
|
0501859c23 | ||
|
|
a7c970054c | ||
|
|
c97925aa4a | ||
|
|
5e89c54144 | ||
|
|
2307df980c | ||
|
|
b5f3f98f87 | ||
|
|
ac79e44d9a | ||
|
|
bf8a31abda | ||
|
|
f0977f3c39 | ||
|
|
16ad94f3a8 | ||
|
|
6223a738a7 | ||
|
|
a370216f54 | ||
|
|
473fb45132 | ||
|
|
ae0563a58f | ||
|
|
2b593a59f5 | ||
|
|
d3cb106ae4 | ||
|
|
ddfd14ebb3 | ||
|
|
1db220e1e0 | ||
|
|
29bc961e8a | ||
|
|
216e181c6e | ||
|
|
b65b94b6f9 | ||
|
|
85ee1e7806 | ||
|
|
7d24bcf868 | ||
|
|
1df1ba9403 | ||
|
|
e15ccdbd74 | ||
|
|
1a2703f630 | ||
|
|
09a5426095 | ||
|
|
bb7a2d7a5e | ||
|
|
080689ccfa | ||
|
|
785595a951 | ||
|
|
0aa8b1a30e | ||
|
|
874fb884b8 | ||
|
|
a91ae8947b | ||
|
|
11e83af0b6 | ||
|
|
9703d848ef | ||
|
|
4155b17633 | ||
|
|
d5bd058754 | ||
|
|
4ec46c2db6 | ||
|
|
aca1f92060 | ||
|
|
1b8dfaf049 | ||
|
|
ae8f1a92e8 | ||
|
|
698d3672a6 | ||
|
|
ed97400a23 | ||
|
|
45d65baf8e | ||
|
|
e329b4bfb0 | ||
|
|
76d77ceb51 | ||
|
|
f23094df09 | ||
|
|
9c539da811 | ||
|
|
6cf705ea55 | ||
|
|
d1901cbea4 | ||
|
|
5c72c35060 | ||
|
|
b3b8cb778f | ||
|
|
d8a7f60f40 | ||
|
|
a10bb5acbd | ||
|
|
cf11ef8ba2 | ||
|
|
8c69ee67b8 | ||
|
|
70614de955 | ||
|
|
5d282f7e9f | ||
|
|
73b388da87 | ||
|
|
38519b2bae | ||
|
|
37101a6b64 | ||
|
|
93811a6d8f | ||
|
|
ff2901a9d1 | ||
|
|
9944f3e1e6 | ||
|
|
cfcf73b6a1 | ||
|
|
eb42e81afc | ||
|
|
64af81a4a2 | ||
|
|
1a4e83d633 | ||
|
|
9078a1edaa | ||
|
|
32c107ab5a | ||
|
|
fbf52dd1f9 | ||
|
|
436a2ba0a2 | ||
|
|
412fffba1d | ||
|
|
5024da4123 | ||
|
|
6620243603 | ||
|
|
48361b146c | ||
|
|
1d4529092f | ||
|
|
6e08310744 | ||
|
|
5719455731 | ||
|
|
99f163d294 | ||
|
|
75fdfb3c6a | ||
|
|
56a2347ea0 | ||
|
|
ea8d189d6c | ||
|
|
bc6f14105e | ||
|
|
9def777d02 | ||
|
|
4143c50b1f | ||
|
|
dc32d282a6 | ||
|
|
2654db7c82 | ||
|
|
0dc0776705 | ||
|
|
ec9d50c824 | ||
|
|
60c08347a5 | ||
|
|
15360971b0 | ||
|
|
37d15a1be3 | ||
|
|
623471290e | ||
|
|
a876f81f0a | ||
|
|
271330d2fc |
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
.git
|
||||
.gitignore
|
||||
log/*
|
||||
tmp/*
|
||||
@@ -68,7 +68,6 @@ Metrics/LineLength:
|
||||
- app/helpers/shop_helper.rb
|
||||
- app/helpers/spree/admin/base_helper_decorator.rb
|
||||
- app/helpers/spree/admin/navigation_helper_decorator.rb
|
||||
- app/helpers/spree/admin/orders_helper_decorator.rb
|
||||
- app/helpers/spree/orders_helper.rb
|
||||
- app/jobs/subscription_confirm_job.rb
|
||||
- app/mailers/subscription_mailer.rb
|
||||
@@ -81,9 +80,7 @@ Metrics/LineLength:
|
||||
- app/models/enterprise_fee.rb
|
||||
- app/models/enterprise_group.rb
|
||||
- app/models/enterprise_role.rb
|
||||
- app/models/exchange.rb
|
||||
- app/models/inventory_item.rb
|
||||
- app/models/order_cycle.rb
|
||||
- app/models/product_import/entry_processor.rb
|
||||
- app/models/product_import/entry_validator.rb
|
||||
- app/models/product_import/product_importer.rb
|
||||
@@ -154,12 +151,10 @@ Metrics/LineLength:
|
||||
- lib/open_food_network/order_and_distributor_report.rb
|
||||
- lib/open_food_network/order_cycle_form_applicator.rb
|
||||
- lib/open_food_network/order_cycle_management_report.rb
|
||||
- lib/open_food_network/order_cycle_permissions.rb
|
||||
- lib/open_food_network/order_grouper.rb
|
||||
- lib/open_food_network/orders_and_fulfillments_report.rb
|
||||
- lib/open_food_network/payments_report.rb
|
||||
- lib/open_food_network/permalink_generator.rb
|
||||
- lib/open_food_network/permissions.rb
|
||||
- lib/open_food_network/products_cache.rb
|
||||
- lib/open_food_network/products_renderer.rb
|
||||
- lib/open_food_network/proxy_order_syncer.rb
|
||||
@@ -429,7 +424,6 @@ Metrics/AbcSize:
|
||||
- app/helpers/checkout_helper.rb
|
||||
- app/helpers/i18n_helper.rb
|
||||
- app/helpers/order_cycles_helper.rb
|
||||
- app/helpers/spree/admin/orders_helper_decorator.rb
|
||||
- app/helpers/spree/orders_helper.rb
|
||||
- app/jobs/subscription_placement_job.rb
|
||||
- app/mailers/producer_mailer.rb
|
||||
@@ -472,6 +466,7 @@ Metrics/AbcSize:
|
||||
- lib/open_food_network/orders_and_fulfillments_report.rb
|
||||
- lib/open_food_network/packing_report.rb
|
||||
- lib/open_food_network/payments_report.rb
|
||||
- lib/open_food_network/permissions.rb
|
||||
- lib/open_food_network/products_and_inventory_report.rb
|
||||
- lib/open_food_network/reports/line_items.rb
|
||||
- lib/open_food_network/sales_tax_report.rb
|
||||
@@ -493,6 +488,72 @@ Metrics/AbcSize:
|
||||
- spec/models/product_importer_spec.rb
|
||||
- spec/support/performance_helper.rb
|
||||
|
||||
Metrics/BlockLength:
|
||||
Max: 25
|
||||
ExcludedMethods: ["class_eval", "collection", "context", "describe", "it", "member", "namespace", "resource", "resources"]
|
||||
Exclude:
|
||||
- lib/tasks/data.rake
|
||||
- lib/tasks/dev.rake
|
||||
- spec/controllers/spree/admin/invoices_controller_spec.rb
|
||||
- spec/factories/variant_factory.rb
|
||||
- spec/features/admin/adjustments_spec.rb
|
||||
- spec/features/admin/bulk_order_management_spec.rb
|
||||
- spec/features/admin/bulk_product_update_spec.rb
|
||||
- spec/features/admin/caching_spec.rb
|
||||
- spec/features/admin/content_spec.rb
|
||||
- spec/features/admin/customers_spec.rb
|
||||
- spec/features/admin/enterprise_fees_spec.rb
|
||||
- spec/features/admin/enterprise_groups_spec.rb
|
||||
- spec/features/admin/enterprise_relationships_spec.rb
|
||||
- spec/features/admin/enterprise_roles_spec.rb
|
||||
- spec/features/admin/enterprises/images_spec.rb
|
||||
- spec/features/admin/enterprises/index_spec.rb
|
||||
- spec/features/admin/enterprises_spec.rb
|
||||
- spec/features/admin/enterprise_user_spec.rb
|
||||
- spec/features/admin/multilingual_spec.rb
|
||||
- spec/features/admin/order_cycles_spec.rb
|
||||
- spec/features/admin/orders_spec.rb
|
||||
- spec/features/admin/overview_spec.rb
|
||||
- spec/features/admin/payment_method_spec.rb
|
||||
- spec/features/admin/product_import_spec.rb
|
||||
- spec/features/admin/products_spec.rb
|
||||
- spec/features/admin/reports/enterprise_fee_summaries_spec.rb
|
||||
- spec/features/admin/reports_spec.rb
|
||||
- spec/features/admin/schedules_spec.rb
|
||||
- spec/features/admin/shipping_methods_spec.rb
|
||||
- spec/features/admin/subscriptions_spec.rb
|
||||
- spec/features/admin/tag_rules_spec.rb
|
||||
- spec/features/admin/tax_settings_spec.rb
|
||||
- spec/features/admin/users_spec.rb
|
||||
- spec/features/admin/variant_overrides_spec.rb
|
||||
- spec/features/admin/variants_spec.rb
|
||||
- spec/features/consumer/account/cards_spec.rb
|
||||
- spec/features/consumer/account/settings_spec.rb
|
||||
- spec/features/consumer/account_spec.rb
|
||||
- spec/features/consumer/authentication_spec.rb
|
||||
- spec/features/consumer/cookies_spec.rb
|
||||
- spec/features/consumer/external_services_spec.rb
|
||||
- spec/features/consumer/groups_spec.rb
|
||||
- spec/features/consumer/multilingual_spec.rb
|
||||
- spec/features/consumer/producers_spec.rb
|
||||
- spec/features/consumer/registration_spec.rb
|
||||
- spec/features/consumer/shopping/cart_spec.rb
|
||||
- spec/features/consumer/shopping/checkout_auth_spec.rb
|
||||
- spec/features/consumer/shopping/checkout_spec.rb
|
||||
- spec/features/consumer/shopping/embedded_groups_spec.rb
|
||||
- spec/features/consumer/shopping/embedded_shopfronts_spec.rb
|
||||
- spec/features/consumer/shopping/orders_spec.rb
|
||||
- spec/features/consumer/shopping/products_spec.rb
|
||||
- spec/features/consumer/shopping/shopping_spec.rb
|
||||
- spec/features/consumer/shopping/variant_overrides_spec.rb
|
||||
- spec/features/consumer/shops_spec.rb
|
||||
- spec/lib/open_food_network/group_buy_report_spec.rb
|
||||
- spec/models/tag_rule/discount_order_spec.rb
|
||||
- spec/spec_helper.rb
|
||||
- spec/support/delayed_job_helper.rb
|
||||
- spec/support/matchers/select2_matchers.rb
|
||||
- spec/support/matchers/table_matchers.rb
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 6
|
||||
Exclude:
|
||||
@@ -504,7 +565,6 @@ Metrics/CyclomaticComplexity:
|
||||
- app/helpers/checkout_helper.rb
|
||||
- app/helpers/i18n_helper.rb
|
||||
- app/helpers/order_cycles_helper.rb
|
||||
- app/helpers/spree/admin/orders_helper_decorator.rb
|
||||
- app/models/enterprise.rb
|
||||
- app/models/enterprise_relationship.rb
|
||||
- app/models/product_import/entry_processor.rb
|
||||
@@ -534,7 +594,6 @@ Metrics/PerceivedComplexity:
|
||||
- app/helpers/checkout_helper.rb
|
||||
- app/helpers/i18n_helper.rb
|
||||
- app/helpers/order_cycles_helper.rb
|
||||
- app/helpers/spree/admin/orders_helper_decorator.rb
|
||||
- app/models/enterprise_relationship.rb
|
||||
- app/models/product_import/entry_processor.rb
|
||||
- app/models/product_import/entry_validator.rb
|
||||
@@ -580,7 +639,6 @@ Metrics/MethodLength:
|
||||
- app/controllers/user_registrations_controller.rb
|
||||
- app/helpers/checkout_helper.rb
|
||||
- app/helpers/order_cycles_helper.rb
|
||||
- app/helpers/spree/admin/orders_helper_decorator.rb
|
||||
- app/jobs/subscription_placement_job.rb
|
||||
- app/mailers/producer_mailer.rb
|
||||
- app/models/column_preference.rb
|
||||
|
||||
@@ -13,8 +13,6 @@ AllCops:
|
||||
- 'script/**/*'
|
||||
- 'vendor/**/*'
|
||||
- 'node_modules/**/*'
|
||||
# The parser gem fails to parse this file with out current Ruby version.
|
||||
- 'spec/factories.rb'
|
||||
# Excluding: inadequate Naming/FileName rule rejects GemFile name with camelcase
|
||||
- 'engines/web/Gemfile'
|
||||
|
||||
@@ -187,7 +185,8 @@ Metrics/AbcSize:
|
||||
Max: 15
|
||||
|
||||
Metrics/BlockLength:
|
||||
ExcludedMethods: ["collection", "context", "describe", "it", "member", "namespace", "resource", "resources"]
|
||||
Max: 25
|
||||
ExcludedMethods: ["class_eval", "collection", "context", "describe", "it", "member", "namespace", "resource", "resources"]
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 3
|
||||
|
||||
@@ -61,7 +61,6 @@ Lint/IneffectiveAccessModifier:
|
||||
- 'app/models/column_preference.rb'
|
||||
- 'app/services/mail_configuration.rb'
|
||||
- 'lib/open_food_network/feature_toggle.rb'
|
||||
- 'lib/open_food_network/products_cache.rb'
|
||||
- 'spec/lib/open_food_network/reports/report_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
@@ -87,7 +86,6 @@ Lint/UselessAccessModifier:
|
||||
- 'app/models/column_preference.rb'
|
||||
- 'app/services/mail_configuration.rb'
|
||||
- 'lib/open_food_network/feature_toggle.rb'
|
||||
- 'lib/open_food_network/products_cache.rb'
|
||||
- 'lib/open_food_network/reports/bulk_coop_report.rb'
|
||||
- 'spec/lib/open_food_network/reports/report_spec.rb'
|
||||
|
||||
@@ -122,11 +120,6 @@ Naming/AccessorMethodName:
|
||||
- 'spec/support/request/shop_workflow.rb'
|
||||
- 'spec/support/request/web_helper.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Naming/BinaryOperatorParameterName:
|
||||
Exclude:
|
||||
- 'app/models/exchange.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: Blacklist.
|
||||
# Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$))
|
||||
@@ -175,7 +168,6 @@ Naming/UncommunicativeMethodParamName:
|
||||
- 'app/helpers/admin/injection_helper.rb'
|
||||
- 'app/helpers/spree/admin/base_helper_decorator.rb'
|
||||
- 'app/helpers/spree/base_helper_decorator.rb'
|
||||
- 'app/models/exchange.rb'
|
||||
- 'app/services/subscription_validator.rb'
|
||||
- 'lib/open_food_network/reports/bulk_coop_report.rb'
|
||||
- 'lib/open_food_network/xero_invoices_report.rb'
|
||||
@@ -433,7 +425,6 @@ Style/GuardClause:
|
||||
- 'app/services/order_syncer.rb'
|
||||
- 'lib/discourse/single_sign_on.rb'
|
||||
- 'lib/open_food_network/order_cycle_form_applicator.rb'
|
||||
- 'lib/open_food_network/products_cache.rb'
|
||||
- 'lib/open_food_network/products_renderer.rb'
|
||||
- 'lib/open_food_network/rack_request_blocker.rb'
|
||||
- 'lib/open_food_network/variant_and_line_item_naming.rb'
|
||||
|
||||
60
.travis.yml
60
.travis.yml
@@ -1,60 +0,0 @@
|
||||
language: ruby
|
||||
sudo: false
|
||||
cache: bundler
|
||||
bundler_args: --without development
|
||||
rvm:
|
||||
- "2.1.5"
|
||||
addons:
|
||||
postgresql: "9.5"
|
||||
|
||||
# Set the timezone for phantomjs with TZ
|
||||
# Set the timezone for karma with TIMEZONE
|
||||
env:
|
||||
global:
|
||||
- TZ="Australia/Melbourne"
|
||||
- TIMEZONE="Australia/Melbourne"
|
||||
- CI_NODE_TOTAL=5
|
||||
matrix:
|
||||
- CI_NODE_INDEX=0
|
||||
- CI_NODE_INDEX=1
|
||||
- CI_NODE_INDEX=2
|
||||
- CI_NODE_INDEX=3 RSPEC_ENGINES="true"
|
||||
- CI_NODE_INDEX=4 KARMA="true" GITHUB_DEPLOY="true"
|
||||
|
||||
before_script:
|
||||
- cp config/database.travis.yml config/database.yml
|
||||
- cp config/application.yml.example config/application.yml
|
||||
- RAILS_ENV=test bundle exec rake db:create db:schema:load
|
||||
|
||||
# Only install PhantomJS if it is not already present (ie. cached)
|
||||
- npm list -g phantomjs-prebuilt@~2.1.7 --depth=0 || npm install -g phantomjs-prebuilt@~2.1.7
|
||||
- export PATH=`npm bin -g`:$PATH
|
||||
|
||||
- >
|
||||
if [ "$KARMA" = "true" ]; then
|
||||
npm install -g npm@'3.8.8'
|
||||
npm install
|
||||
npm install -g karma-cli@0.1.2
|
||||
fi
|
||||
|
||||
script:
|
||||
- 'if [ "$KARMA" = "true" ]; then bundle exec rake karma:run; else echo "Skipping karma run"; fi'
|
||||
- 'if [ "$RSPEC_ENGINES" = "true" ]; then bundle exec rake ofn:specs:engines:rspec; else echo "Skipping RSpec run in engines"; fi'
|
||||
- "bundle exec rake 'knapsack:rspec[--format progress --tag ~performance]'"
|
||||
|
||||
after_success:
|
||||
- >
|
||||
if [ "$GITHUB_DEPLOY" = "true" -a "$TRAVIS_PULL_REQUEST" = "false" -a -n "$TRAVIS_BRANCH" -a "$TRAVIS_BRANCH" != "transifex" -a -n "$GITHUB_API_SECRET" ]; then
|
||||
description="`git show "$TRAVIS_BRANCH" -s --oneline --no-color`"
|
||||
data="{
|
||||
\"ref\":\"$TRAVIS_BRANCH\",
|
||||
\"description\":\"$description\",
|
||||
\"environment\":\"staging\",
|
||||
\"required_contexts\":[]}"
|
||||
curl -u "$GITHUB_API_SECRET" -d "$data" "https://api.github.com/repos/$TRAVIS_REPO_SLUG/deployments"
|
||||
else
|
||||
echo "Not deploying on this build."
|
||||
fi
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
47
DOCKER.md
Normal file
47
DOCKER.md
Normal file
@@ -0,0 +1,47 @@
|
||||
### Docker
|
||||
|
||||
It is possible to setup the Open Food Network app easily with Docker and Docker Compose.
|
||||
The objective is to spare configuration time, in order to help people testing the app and contribute to it.
|
||||
It can also be used as documentation. It is not perfect but it is used in many other projects and many devs are used to it nowadays.
|
||||
|
||||
### Install Docker
|
||||
|
||||
Please check the documentation here, https://docs.docker.com/install/ to install Docker.
|
||||
|
||||
For Docker Compose, information are here: https://docs.docker.com/compose/install/.
|
||||
|
||||
Better to have at least 2GB free on your computer in order to download images and create containers for Open Food Network app.
|
||||
|
||||
|
||||
### Use Docker with Open Food Network
|
||||
|
||||
Open a terminal with a shell.
|
||||
|
||||
Clone the repository:
|
||||
|
||||
```sh
|
||||
$ git clone git@github.com:openfoodfoundation/openfoodnetwork.git
|
||||
```
|
||||
|
||||
Go at the root of the app:
|
||||
|
||||
```sh
|
||||
$ cd openfoodnetwork
|
||||
```
|
||||
|
||||
Download the Docker images and build the containers:
|
||||
|
||||
```sh
|
||||
$ docker-compose build
|
||||
```
|
||||
|
||||
Run the app with all the required containers:
|
||||
|
||||
```sh
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
This command will setup the database and seed it with sample data. The default admin user is 'ofn@example.com' with 'ofn123' password.
|
||||
Check the app in the browser at `http:://localhost:3000`.
|
||||
|
||||
You will then get the trace of the containers in the terminal. You can stop the containers using Ctrl-C in the terminal.
|
||||
31
Dockerfile
Normal file
31
Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
FROM ubuntu:18.04
|
||||
|
||||
# Install all the requirements
|
||||
RUN apt-get update && apt-get install -y curl git build-essential software-properties-common wget zlib1g-dev libssl1.0-dev libreadline-dev libyaml-dev libffi-dev libxml2-dev libxslt1-dev wait-for-it
|
||||
|
||||
# Setup ENV variables
|
||||
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:$PATH
|
||||
ENV RBENV_ROOT /usr/local/src/rbenv
|
||||
ENV RUBY_VERSION 2.1.5
|
||||
ENV CONFIGURE_OPTS --disable-install-doc
|
||||
|
||||
# Rbenv & Ruby part
|
||||
RUN git clone https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
|
||||
git clone https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
|
||||
${RBENV_ROOT}/plugins/ruby-build/install.sh && \
|
||||
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh && \
|
||||
rbenv install $RUBY_VERSION && \
|
||||
rbenv global $RUBY_VERSION && \
|
||||
gem install bundler --version=1.17.2
|
||||
|
||||
# Postgres
|
||||
RUN sh -c "echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' > /etc/apt/sources.list.d/pgdg.list" && \
|
||||
wget --quiet -O - https://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | apt-key add - && \
|
||||
apt-get update && \
|
||||
apt-get install -yqq --no-install-recommends postgresql-client-9.5 libpq-dev
|
||||
|
||||
ENV BUNDLE_PATH /bundles
|
||||
|
||||
COPY . /usr/src/app/
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
12
Gemfile
12
Gemfile
@@ -3,11 +3,12 @@ ruby "2.1.5"
|
||||
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
|
||||
|
||||
gem 'i18n', '~> 0.6.11'
|
||||
gem 'i18n-js', '~> 3.2.2'
|
||||
gem 'i18n-js', '~> 3.3.0'
|
||||
gem 'rails', '~> 3.2.22'
|
||||
gem 'rails-i18n', '~> 3.0.0'
|
||||
gem 'rails_safe_tasks', '~> 1.0'
|
||||
|
||||
gem "activerecord-import"
|
||||
# Patched version. See http://rubysec.com/advisories/CVE-2015-5312/.
|
||||
gem 'nokogiri', '>= 1.6.7.1'
|
||||
|
||||
@@ -19,9 +20,10 @@ gem 'pg'
|
||||
# OFN-maintained and patched version of Spree v2.0.4. See
|
||||
# https://github.com/openfoodfoundation/openfoodnetwork/wiki/Spree-2.0-upgrade
|
||||
# for details.
|
||||
gem 'spree', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
gem 'spree_api', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
gem 'spree_backend', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
gem 'spree_core', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
|
||||
|
||||
gem 'spree_auth_devise', github: 'spree/spree_auth_devise', branch: '2-0-stable'
|
||||
gem 'spree_i18n', github: 'spree/spree_i18n', branch: '1-3-stable'
|
||||
|
||||
# Our branch contains two changes
|
||||
@@ -34,6 +36,8 @@ gem 'stripe'
|
||||
# which is needed for Pin Payments (and possibly others).
|
||||
gem 'activemerchant', '~> 1.78'
|
||||
|
||||
gem 'devise', '~> 2.2.5'
|
||||
gem 'devise-encryptable', '0.1.2'
|
||||
gem 'jwt', '~> 2.2'
|
||||
gem 'oauth2', '~> 1.4.1' # Used for Stripe Connect
|
||||
|
||||
@@ -122,7 +126,7 @@ group :test, :development do
|
||||
gem 'capybara', '>= 2.15.4'
|
||||
gem 'database_cleaner', '0.7.1', require: false
|
||||
gem "factory_bot_rails", require: false
|
||||
gem 'fuubar', '~> 2.4.0'
|
||||
gem 'fuubar', '~> 2.4.1'
|
||||
gem 'json_spec', '~> 1.1.4'
|
||||
gem 'knapsack'
|
||||
gem 'letter_opener', '>= 1.4.1'
|
||||
|
||||
65
Gemfile.lock
65
Gemfile.lock
@@ -31,16 +31,9 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/openfoodfoundation/spree.git
|
||||
revision: 46d6f8f5fd434105b0c69958276d1727a3066a97
|
||||
revision: 8a8585a43cd04d1a50dc65227f337a91b18d66d5
|
||||
branch: 2-0-4-stable
|
||||
specs:
|
||||
spree (2.0.4)
|
||||
spree_api (= 2.0.4)
|
||||
spree_backend (= 2.0.4)
|
||||
spree_cmd (= 2.0.4)
|
||||
spree_core (= 2.0.4)
|
||||
spree_frontend (= 2.0.4)
|
||||
spree_sample (= 2.0.4)
|
||||
spree_api (2.0.4)
|
||||
rabl (= 0.8.4)
|
||||
spree_core (= 2.0.4)
|
||||
@@ -53,8 +46,6 @@ GIT
|
||||
select2-rails (~> 3.4.7)
|
||||
spree_api (= 2.0.4)
|
||||
spree_core (= 2.0.4)
|
||||
spree_cmd (2.0.4)
|
||||
thor (>= 0.14.6)
|
||||
spree_core (2.0.4)
|
||||
activemerchant (~> 1.34)
|
||||
acts_as_list (= 0.2.0)
|
||||
@@ -75,28 +66,6 @@ GIT
|
||||
state_machine (= 1.2.0)
|
||||
stringex (~> 1.5.1)
|
||||
truncate_html (= 0.9.2)
|
||||
spree_frontend (2.0.4)
|
||||
canonical-rails
|
||||
deface (>= 0.9.0)
|
||||
jquery-rails (~> 3.0.0)
|
||||
rails (~> 3.2.13)
|
||||
spree_api (= 2.0.4)
|
||||
spree_core (= 2.0.4)
|
||||
stringex (~> 1.5.1)
|
||||
spree_sample (2.0.4)
|
||||
spree_core (= 2.0.4)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/spree/spree_auth_devise.git
|
||||
revision: 0181835fb6ac77a05191d26f6f32a0f4a548d851
|
||||
branch: 2-0-stable
|
||||
specs:
|
||||
spree_auth_devise (2.0.0)
|
||||
devise (~> 2.2.5)
|
||||
devise-encryptable (= 0.1.2)
|
||||
spree_backend (~> 2.0.0)
|
||||
spree_core (~> 2.0.0)
|
||||
spree_frontend (~> 2.0.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/spree/spree_i18n.git
|
||||
@@ -160,6 +129,8 @@ GEM
|
||||
activesupport (= 3.2.22.5)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activerecord-import (1.0.2)
|
||||
activerecord (>= 3.2)
|
||||
activeresource (3.2.22.5)
|
||||
activemodel (= 3.2.22.5)
|
||||
activesupport (= 3.2.22.5)
|
||||
@@ -189,7 +160,7 @@ GEM
|
||||
json (~> 1.4)
|
||||
nokogiri (>= 1.4.4)
|
||||
uuidtools (~> 2.1)
|
||||
bcrypt (3.1.11)
|
||||
bcrypt (3.1.13)
|
||||
bcrypt-ruby (3.1.5)
|
||||
bcrypt (>= 3.1.3)
|
||||
blockenspiel (0.5.0)
|
||||
@@ -198,8 +169,6 @@ GEM
|
||||
builder (3.0.4)
|
||||
byebug (9.0.6)
|
||||
cancan (1.6.10)
|
||||
canonical-rails (0.1.0)
|
||||
rails (>= 3.1, < 5.1)
|
||||
capybara (2.18.0)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
@@ -279,7 +248,7 @@ GEM
|
||||
devise (>= 2.1.0)
|
||||
diff-lcs (1.3)
|
||||
diffy (3.3.0)
|
||||
docile (1.3.1)
|
||||
docile (1.3.2)
|
||||
dry-inflector (0.1.2)
|
||||
em-websocket (0.5.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
@@ -456,7 +425,7 @@ GEM
|
||||
foundation-rails (5.5.2.1)
|
||||
railties (>= 3.1.0)
|
||||
sass (>= 3.3.0, < 3.5)
|
||||
fuubar (2.4.0)
|
||||
fuubar (2.4.1)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
geocoder (1.1.8)
|
||||
@@ -492,7 +461,7 @@ GEM
|
||||
httparty (0.16.2)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.6.11)
|
||||
i18n-js (3.2.2)
|
||||
i18n-js (3.3.0)
|
||||
i18n (>= 0.6.6)
|
||||
immigrant (0.3.6)
|
||||
activerecord (>= 3.0)
|
||||
@@ -514,7 +483,7 @@ GEM
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
kgio (2.11.2)
|
||||
knapsack (1.17.1)
|
||||
knapsack (1.17.2)
|
||||
rake
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
@@ -637,7 +606,7 @@ GEM
|
||||
trollop (~> 2.1)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
redcarpet (3.4.0)
|
||||
redcarpet (3.5.0)
|
||||
ref (2.0.0)
|
||||
request_store (1.4.1)
|
||||
rack (>= 1.4)
|
||||
@@ -704,7 +673,7 @@ GEM
|
||||
shellany (0.0.1)
|
||||
shoulda-matchers (2.8.0)
|
||||
activesupport (>= 3.0.0)
|
||||
simplecov (0.16.1)
|
||||
simplecov (0.17.0)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
@@ -729,7 +698,7 @@ GEM
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
state_machine (1.2.0)
|
||||
stringex (1.5.1)
|
||||
stripe (4.18.1)
|
||||
stripe (4.19.0)
|
||||
faraday (~> 0.13)
|
||||
net-http-persistent (~> 3.0)
|
||||
therubyracer (0.12.0)
|
||||
@@ -785,6 +754,7 @@ PLATFORMS
|
||||
DEPENDENCIES
|
||||
active_model_serializers (= 0.8.4)
|
||||
activemerchant (~> 1.78)
|
||||
activerecord-import
|
||||
acts-as-taggable-on (~> 3.4)
|
||||
andand
|
||||
angular-rails-templates (~> 0.3.0)
|
||||
@@ -809,6 +779,8 @@ DEPENDENCIES
|
||||
deface (= 1.0.2)
|
||||
delayed_job_active_record
|
||||
delayed_job_web
|
||||
devise (~> 2.2.5)
|
||||
devise-encryptable (= 0.1.2)
|
||||
diffy
|
||||
eventmachine (>= 1.2.3)
|
||||
factory_bot_rails
|
||||
@@ -817,7 +789,7 @@ DEPENDENCIES
|
||||
foundation-icons-sass-rails
|
||||
foundation-rails
|
||||
foundation_rails_helper!
|
||||
fuubar (~> 2.4.0)
|
||||
fuubar (~> 2.4.1)
|
||||
geocoder
|
||||
gmaps4rails
|
||||
guard
|
||||
@@ -826,7 +798,7 @@ DEPENDENCIES
|
||||
guard-rspec (~> 4.7.3)
|
||||
haml
|
||||
i18n (~> 0.6.11)
|
||||
i18n-js (~> 3.2.2)
|
||||
i18n-js (~> 3.3.0)
|
||||
immigrant
|
||||
jquery-migrate-rails
|
||||
jquery-rails (= 3.0.4)
|
||||
@@ -868,8 +840,9 @@ DEPENDENCIES
|
||||
simplecov
|
||||
skylight (< 2.0)
|
||||
spinjs-rails
|
||||
spree!
|
||||
spree_auth_devise!
|
||||
spree_api!
|
||||
spree_backend!
|
||||
spree_core!
|
||||
spree_i18n!
|
||||
spree_paypal_express!
|
||||
spring (= 1.7.2)
|
||||
|
||||
@@ -27,10 +27,13 @@ If you're interested in provisioning a server, see [ofn-install][ofn-install] fo
|
||||
|
||||
We also have a [Super Admin Guide][super-admin-guide] to help with configuration of new servers.
|
||||
|
||||
## Testing
|
||||
|
||||
We use [BrowserStack](https://www.browserstack.com/) as a manual testing tool. BrowserStack provides open source projects with unlimited and free of charge accounts. A big thanks to them!
|
||||
|
||||
## Licence
|
||||
|
||||
Copyright (c) 2012 - 2018 Open Food Foundation, released under the AGPL licence.
|
||||
Copyright (c) 2012 - 2019 Open Food Foundation, released under the AGPL licence.
|
||||
|
||||
[survey]: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit#
|
||||
[slack-invite]: https://openfoodnetwork.org/slack-invite
|
||||
|
||||
BIN
app/assets/images/datepicker/cal.gif
Normal file
BIN
app/assets/images/datepicker/cal.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 B |
3
app/assets/images/menu/icn-cart.svg
Executable file
3
app/assets/images/menu/icn-cart.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="18" viewBox="0 0 20 18">
|
||||
<path fill="#FFF" fill-opacity=".5" fill-rule="nonzero" d="M18.338 10.593l1.64-7.312a.841.841 0 0 0-.812-1.031H5.528L5.21.675A.836.836 0 0 0 4.393 0H.833A.839.839 0 0 0 0 .844v.562c0 .466.373.844.833.844H3.26l2.439 12.074c-.584.34-.977.977-.977 1.707 0 1.088.87 1.969 1.945 1.969 1.074 0 1.944-.881 1.944-1.969 0-.55-.224-1.049-.584-1.406h7.28c-.36.357-.585.855-.585 1.406 0 1.088.87 1.969 1.945 1.969 1.074 0 1.944-.881 1.944-1.969 0-.78-.447-1.453-1.096-1.772l.191-.853a.841.841 0 0 0-.812-1.031h-9.32l-.228-1.125h10.179c.389 0 .726-.273.813-.657z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 648 B |
7
app/assets/images/menu/icn-login.svg
Normal file
7
app/assets/images/menu/icn-login.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h16v16H0z"/>
|
||||
<path fill="#F4704C" d="M13.85 5.928h-1.234v-1.54c0-2.338-2.026-4.234-4.525-4.234-2.5 0-4.526 1.896-4.526 4.235v1.54H2.33c-.682 0-1.235.516-1.235 1.154v7.7c0 .637.553 1.154 1.235 1.154h11.52c.681 0 1.234-.517 1.234-1.154v-7.7c0-.638-.553-1.155-1.234-1.155zM4.8 4.388c0-1.7 1.473-3.08 3.29-3.08 1.818 0 3.292 1.38 3.292 3.08v1.54H4.799v-1.54z"/>
|
||||
<path fill="#FFF" d="M8.296 13.23c1.243 0 2.25-.942 2.25-2.105 0-1.162-1.007-2.105-2.25-2.105-1.242 0-2.25.943-2.25 2.105.003 1.162 1.009 2.103 2.25 2.106zm0-3.211c.654 0 1.183.495 1.183 1.106 0 .612-.53 1.107-1.183 1.107s-1.183-.495-1.183-1.107c0-.61.53-1.106 1.183-1.106z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 812 B |
6
app/assets/images/menu/icn-profile.svg
Executable file
6
app/assets/images/menu/icn-profile.svg
Executable file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<path id="a" d="M15 15.2a.79.79 0 0 1-.778.8H1.778A.79.79 0 0 1 1 15.2v-2.4c0-2.21 1.741-4 3.889-4h6.222c2.148 0 3.889 1.79 3.889 4v2.4zM8 8C5.852 8 4.111 6.21 4.111 4S5.852 0 8 0c2.148 0 3.889 1.79 3.889 4S10.148 8 8 8z"/>
|
||||
</defs>
|
||||
<use fill="#F4704C" fill-rule="nonzero" xlink:href="#a"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 451 B |
@@ -15,7 +15,6 @@
|
||||
//= require angular-animate
|
||||
//= require angular-sanitize
|
||||
//= require admin/spree_backend
|
||||
//= require admin/spree_auth
|
||||
//= require admin/spree_paypal_express
|
||||
//= require ../shared/ng-infinite-scroll.min.js
|
||||
//= require ../shared/ng-tags-input.min.js
|
||||
|
||||
@@ -5,7 +5,7 @@ angular.module('admin.orderCycles')
|
||||
$scope.distributor_enterprises = Enterprise.hub_enterprises
|
||||
$scope.supplied_products = Enterprise.supplied_products
|
||||
$scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id)
|
||||
$scope.schedules = Schedules.index()
|
||||
$scope.schedules = Schedules.index({enterprise_id: ocInstance.coordinator_id})
|
||||
|
||||
$scope.OrderCycle = OrderCycle
|
||||
$scope.order_cycle = OrderCycle.new({ coordinator_id: ocInstance.coordinator_id})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
angular.module('admin.orderCycles')
|
||||
.controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor) ->
|
||||
.controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, ocInstance) ->
|
||||
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
|
||||
$scope.supplier_enterprises = Enterprise.producer_enterprises
|
||||
$scope.distributor_enterprises = Enterprise.hub_enterprises
|
||||
$scope.supplied_products = Enterprise.supplied_products
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
|
||||
$scope.schedules = Schedules.index()
|
||||
$scope.schedules = Schedules.index({enterprise_id: ocInstance.coordinator_id})
|
||||
|
||||
$scope.OrderCycle = OrderCycle
|
||||
$scope.order_cycle = OrderCycle.load(order_cycle_id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl", ($scope, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, ocInstance) ->
|
||||
$scope.StatusMessage = StatusMessage
|
||||
$scope.OrderCycle = OrderCycle
|
||||
$scope.schedules = Schedules.index()
|
||||
$scope.schedules = Schedules.index({enterprise_id: ocInstance.coordinator_id})
|
||||
$scope.order_cycle = OrderCycle.new {coordinator_id: ocInstance.coordinator_id}, =>
|
||||
# TODO: make this a get method, which only fetches one enterprise
|
||||
$scope.enterprises = Enterprise.index {coordinator_id: ocInstance.coordinator_id}, (enterprises) =>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", ($scope, $location, $window, OrderCycle, Enterprise, EnterpriseFee, Schedules, RequestMonitor, StatusMessage) ->
|
||||
angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", ($scope, $location, $window, OrderCycle, Enterprise, EnterpriseFee, Schedules, RequestMonitor, StatusMessage, ocInstance) ->
|
||||
$scope.orderCycleId = ->
|
||||
$location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
|
||||
$scope.StatusMessage = StatusMessage
|
||||
$scope.enterprises = Enterprise.index(order_cycle_id: $scope.orderCycleId())
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: $scope.orderCycleId())
|
||||
$scope.schedules = Schedules.index()
|
||||
$scope.schedules = Schedules.index({enterprise_id: ocInstance.coordinator_id})
|
||||
$scope.OrderCycle = OrderCycle
|
||||
$scope.order_cycle = OrderCycle.load $scope.orderCycleId(), (order_cycle) =>
|
||||
$scope.init()
|
||||
|
||||
@@ -24,8 +24,3 @@ angular.module("admin.orders").controller "orderCtrl", ($scope, shops, orderCycl
|
||||
|
||||
for shop in $scope.shops
|
||||
shop.disabled = !$scope.distributorHasOrderCycles(shop)
|
||||
|
||||
# Removes the split button introduced by spree in the order form
|
||||
# We only have one stock location in OFN so it's meaningless to split the order between stock locations
|
||||
# We delete it instead of hiding or changing CSS so that, when spree code toggles the element, nothing hapens
|
||||
$('.split-item').remove()
|
||||
|
||||
@@ -3,6 +3,8 @@ angular.module("admin.resources").factory 'ScheduleResource', ($resource) ->
|
||||
'index':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
params:
|
||||
enterprise_id: '@enterprise_id'
|
||||
'create':
|
||||
method: 'POST'
|
||||
'update':
|
||||
|
||||
@@ -40,7 +40,7 @@ angular.module("admin.resources").factory "Schedules", ($q, $injector, RequestMo
|
||||
delete @byID[schedule.id]
|
||||
StatusMessage.display 'success', "#{t('js.admin.order_cycles.schedules.deleted_schedule')}: '#{schedule.name}'"
|
||||
|
||||
index: ->
|
||||
request = ScheduleResource.index (data) => @load(data)
|
||||
index: (params) ->
|
||||
request = ScheduleResource.index params, (data) => @load(data)
|
||||
RequestMonitor.load(request.$promise)
|
||||
request
|
||||
|
||||
@@ -5,7 +5,7 @@ angular.module("admin.subscriptions").controller "OrderUpdateIssuesController",
|
||||
OrderCycles.byID[id].name
|
||||
|
||||
$scope.orderCycleCloses = (id) ->
|
||||
closes_at = moment(OrderCycles.byID[id].orders_close_at)
|
||||
closes_at = moment(OrderCycles.byID[id].orders_close_at, "YYYY-MM-DD HH:mm:SS Z")
|
||||
key = if closes_at > moment() then "closes" else "closed"
|
||||
text = t("js.subscriptions." + key)
|
||||
"#{text} #{closes_at.fromNow()}"
|
||||
|
||||
@@ -15,7 +15,7 @@ angular.module("admin.subscriptions").controller "OrdersPanelController", ($scop
|
||||
$scope.orderCycleCloses = (id) ->
|
||||
oc = OrderCycles.byID[id]
|
||||
return t('js.subscriptions.close_date_not_set') unless oc?.orders_close_at?
|
||||
closes_at = moment(oc.orders_close_at)
|
||||
closes_at = moment(oc.orders_close_at, "YYYY-MM-DD HH:mm:SS Z")
|
||||
text = if closes_at > moment() then t('js.subscriptions.closes') else t('js.subscriptions.closed')
|
||||
"#{text} #{closes_at.fromNow()}"
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ angular.module("admin.subscriptions").controller "ProductsPanelController", ($sc
|
||||
|
||||
$scope.save = ->
|
||||
$scope.saving = true
|
||||
StatusMessage.display 'progress', 'Saving...'
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
$scope.subscription.update().then (response) ->
|
||||
$scope.saving = false
|
||||
StatusMessage.display 'success', 'Saved'
|
||||
StatusMessage.display 'success', t('js.changes_saved')
|
||||
, (response) ->
|
||||
$scope.saving = false
|
||||
if response.data?.errors?
|
||||
keys = Object.keys(response.data.errors)
|
||||
StatusMessage.display 'failure', response.data.errors[keys[0]][0]
|
||||
else
|
||||
StatusMessage.display 'success', 'Saved'
|
||||
StatusMessage.display 'success', t('js.changes_saved')
|
||||
|
||||
@@ -4,8 +4,8 @@ angular.module("admin.tagRules").directive "tagRule", ->
|
||||
link: (scope, element, attrs) ->
|
||||
scope.opt =
|
||||
"TagRule::FilterShippingMethods":
|
||||
textTop: "Shipping methods tagged"
|
||||
textBottom: "are:"
|
||||
textTop: t('js.admin.tag_rules.shipping_method_tagged_top')
|
||||
textBottom: t('js.admin.tag_rules.shipping_method_tagged_bottom')
|
||||
taggable: "shipping_method"
|
||||
tagsAttr: "shipping_method_tags"
|
||||
tagListAttr: "preferred_shipping_method_tags"
|
||||
@@ -13,8 +13,8 @@ angular.module("admin.tagRules").directive "tagRule", ->
|
||||
tagListFor: (rule) ->
|
||||
rule.preferred_shipping_method_tags
|
||||
"TagRule::FilterPaymentMethods":
|
||||
textTop: "Payment methods tagged"
|
||||
textBottom: "are:"
|
||||
textTop: t('js.admin.tag_rules.payment_method_tagged_top')
|
||||
textBottom: t('js.admin.tag_rules.payment_method_tagged_bottom')
|
||||
taggable: "payment_method"
|
||||
tagsAttr: "payment_method_tags"
|
||||
tagListAttr: "preferred_payment_method_tags"
|
||||
@@ -22,8 +22,8 @@ angular.module("admin.tagRules").directive "tagRule", ->
|
||||
tagListFor: (rule) ->
|
||||
rule.preferred_payment_method_tags
|
||||
"TagRule::FilterOrderCycles":
|
||||
textTop: "Order Cycles tagged"
|
||||
textBottom: "are:"
|
||||
textTop: t('js.admin.tag_rules.order_cycle_tagged_top')
|
||||
textBottom: t('js.admin.tag_rules.order_cycle_tagged_bottom')
|
||||
taggable: "exchange"
|
||||
tagsAttr: "exchange_tags"
|
||||
tagListAttr: "preferred_exchange_tags"
|
||||
@@ -31,8 +31,8 @@ angular.module("admin.tagRules").directive "tagRule", ->
|
||||
tagListFor: (rule) ->
|
||||
rule.preferred_exchange_tags
|
||||
"TagRule::FilterProducts":
|
||||
textTop: "Inventory variants tagged"
|
||||
textBottom: "are:"
|
||||
textTop: t('js.admin.tag_rules.inventory_tagged_top')
|
||||
textBottom: t('js.admin.tag_rules.inventory_tagged_bottom')
|
||||
taggable: "variant"
|
||||
tagsAttr: "variant_tags"
|
||||
tagListAttr: "preferred_variant_tags"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
Darkswarm.controller "GroupEnterpriseNodeCtrl", ($scope, CurrentHub) ->
|
||||
|
||||
$scope.active = false
|
||||
|
||||
$scope.toggle = ->
|
||||
$scope.active = !$scope.active
|
||||
|
||||
$scope.open = ->
|
||||
$scope.active
|
||||
|
||||
$scope.current = ->
|
||||
$scope.hub.id is CurrentHub.hub.id
|
||||
@@ -1,22 +1,6 @@
|
||||
Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter, Navigation) ->
|
||||
Darkswarm.controller "GroupPageCtrl", ($scope, enterprises, Enterprises, MapConfiguration, OfnMap) ->
|
||||
$scope.Enterprises = Enterprises
|
||||
|
||||
all_enterprises_by_id = Enterprises.enterprises_by_id
|
||||
|
||||
dereferenced_enterprises = group_enterprises.map (enterprise) =>
|
||||
all_enterprises_by_id[enterprise.id]
|
||||
|
||||
visible_enterprises = visibleFilter dereferenced_enterprises
|
||||
|
||||
# TODO: this is duplicate code with app/assets/javascripts/darkswarm/services/enterprises.js.coffee
|
||||
# It would be better to load only the needed enterprises (group + related shops).
|
||||
$scope.group_producers = visible_enterprises.filter (enterprise) ->
|
||||
enterprise.category in ["producer_hub", "producer_shop", "producer"]
|
||||
$scope.group_hubs = visible_enterprises.filter (enterprise) ->
|
||||
enterprise.category in ["hub", "hub_profile", "producer_hub", "producer_shop"]
|
||||
|
||||
$scope.producers_to_filter = $scope.group_producers
|
||||
|
||||
$scope.map = angular.copy MapConfiguration.options
|
||||
$scope.mapMarkers = OfnMap.enterprise_markers visible_enterprises
|
||||
$scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1
|
||||
$scope.mapMarkers = OfnMap.enterprise_markers enterprises
|
||||
$scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1
|
||||
|
||||
@@ -1,9 +1,44 @@
|
||||
Darkswarm.controller "HubNodeCtrl", ($scope, HashNavigation, Navigation, $location, $templateCache, CurrentHub) ->
|
||||
$scope.toggle = (e) ->
|
||||
HashNavigation.toggle $scope.hub.hash if !angular.element(e.target).inheritedData('is-link')
|
||||
Darkswarm.controller "HubNodeCtrl", ($scope, HashNavigation, CurrentHub, $http, $timeout) ->
|
||||
$scope.shopfront_loading = false
|
||||
$scope.enterprise_details = []
|
||||
|
||||
$timeout ->
|
||||
if $scope.open()
|
||||
$scope.load_shopfront()
|
||||
|
||||
# Toggles shopfront tabs open/closed. Fetches enterprise details from the api, diplays them and adds them
|
||||
# to $scope.enterprise_details, or simply displays the details again if previously fetched
|
||||
$scope.toggle = (event) ->
|
||||
if $scope.open()
|
||||
$scope.toggle_tab(event)
|
||||
return
|
||||
|
||||
if $scope.enterprise_details[$scope.hub.id]
|
||||
$scope.hub = $scope.enterprise_details[$scope.hub.id]
|
||||
$scope.toggle_tab(event)
|
||||
return
|
||||
|
||||
$scope.load_shopfront(event)
|
||||
|
||||
$scope.load_shopfront = (event=null) ->
|
||||
$scope.shopfront_loading = true
|
||||
$scope.toggle_tab(event)
|
||||
|
||||
$http.get("/api/enterprises/" + $scope.hub.id + "/shopfront")
|
||||
.success (data) ->
|
||||
$scope.shopfront_loading = false
|
||||
$scope.hub = data
|
||||
$scope.enterprise_details[$scope.hub.id] = $scope.hub
|
||||
.error (data) ->
|
||||
console.error(data)
|
||||
|
||||
$scope.toggle_tab = (event) ->
|
||||
HashNavigation.toggle $scope.hub.hash if event && !angular.element(event.target).inheritedData('is-link')
|
||||
|
||||
# Returns boolean: pulldown tab is currently open/closed
|
||||
$scope.open = ->
|
||||
HashNavigation.active $scope.hub.hash
|
||||
|
||||
|
||||
# Returns boolean: is this hub the hub that the user is currently "shopping" in?
|
||||
$scope.current = ->
|
||||
$scope.hub.id is CurrentHub.hub.id
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Darkswarm.controller "OffcanvasCtrl", ($scope) ->
|
||||
$scope.menu = $(".left-off-canvas-menu")
|
||||
|
||||
$scope.setOffcanvasMenuHeight = ->
|
||||
$scope.menu.height($(window).height())
|
||||
|
||||
$scope.bind = ->
|
||||
$(window).on("resize", $scope.setOffcanvasMenuHeight)
|
||||
$scope.setOffcanvasMenuHeight()
|
||||
|
||||
$scope.bind()
|
||||
@@ -1,6 +1,39 @@
|
||||
Darkswarm.controller "ProducerNodeCtrl", ($scope, HashNavigation, $anchorScroll) ->
|
||||
$scope.toggle = ->
|
||||
HashNavigation.toggle $scope.producer.hash
|
||||
Darkswarm.controller "ProducerNodeCtrl", ($scope, HashNavigation, $anchorScroll, $http, $timeout) ->
|
||||
$scope.shopfront_loading = false
|
||||
$scope.enterprise_details = []
|
||||
|
||||
$timeout ->
|
||||
if $scope.open()
|
||||
$scope.load_shopfront()
|
||||
|
||||
# Toggles shopfront tabs open/closed. Fetches enterprise details from the api, diplays them and adds them
|
||||
# to $scope.enterprise_details, or simply displays the details again if previously fetched
|
||||
$scope.toggle = (event) ->
|
||||
if $scope.open()
|
||||
$scope.toggle_tab(event)
|
||||
return
|
||||
|
||||
if $scope.enterprise_details[$scope.producer.id]
|
||||
$scope.producer = $scope.enterprise_details[$scope.producer.id]
|
||||
$scope.toggle_tab(event)
|
||||
return
|
||||
|
||||
$scope.load_shopfront(event)
|
||||
|
||||
$scope.load_shopfront = (event=null) ->
|
||||
$scope.shopfront_loading = true
|
||||
$scope.toggle_tab(event)
|
||||
|
||||
$http.get("/api/enterprises/" + $scope.producer.id + "/shopfront")
|
||||
.success (data) ->
|
||||
$scope.shopfront_loading = false
|
||||
$scope.producer = data
|
||||
$scope.enterprise_details[$scope.producer.id] = $scope.producer
|
||||
.error (data) ->
|
||||
console.error(data)
|
||||
|
||||
$scope.toggle_tab = (event) ->
|
||||
HashNavigation.toggle $scope.producer.hash if event && !angular.element(event.target).inheritedData('is-link')
|
||||
|
||||
$scope.open = ->
|
||||
HashNavigation.active($scope.producer.hash)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Darkswarm.controller "AboutUsCtrl", ($scope, CurrentHub) ->
|
||||
$scope.CurrentHub = CurrentHub
|
||||
Darkswarm.controller "AboutUsCtrl", ($scope, Shopfront) ->
|
||||
$scope.shopfront = Shopfront.shopfront
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
Darkswarm.controller "ProducersTabCtrl", ($scope, CurrentHub, Enterprises, EnterpriseModal) ->
|
||||
# Injecting Enterprises so CurrentHub.producers is dereferenced.
|
||||
# We should probably dereference here instead and separate out CurrentHub dereferencing from the Enterprise factory.
|
||||
$scope.CurrentHub = CurrentHub
|
||||
Darkswarm.controller "ProducersTabCtrl", ($scope, Shopfront, EnterpriseModal) ->
|
||||
$scope.shopfront = Shopfront.shopfront
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
Darkswarm.directive "enterpriseModal", ($modal, Enterprises, EnterpriseResource) ->
|
||||
Darkswarm.directive "enterpriseModal", (EnterpriseModal) ->
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
template: "<a ng-transclude></a>"
|
||||
transclude: true
|
||||
link: (scope, elem, attrs, ctrl) ->
|
||||
elem.on "click", (ev) =>
|
||||
ev.stopPropagation()
|
||||
params =
|
||||
id: scope.enterprise.id
|
||||
EnterpriseResource.relatives params, (data) =>
|
||||
Enterprises.addEnterprises data
|
||||
scope.enterprise = Enterprises.enterprises_by_id[scope.enterprise.id]
|
||||
Enterprises.dereferenceEnterprise scope.enterprise
|
||||
scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'enterprise_modal.html', scope: scope)
|
||||
elem.on "click", (event) =>
|
||||
event.stopPropagation()
|
||||
|
||||
scope.modalInstance = EnterpriseModal.open scope.enterprise
|
||||
@@ -13,11 +13,16 @@ Darkswarm.directive "ofnOnHand", ->
|
||||
ngModel.$setDirty = setDirty
|
||||
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
on_hand = parseInt(attr.ofnOnHand)
|
||||
if parseInt(viewValue) > on_hand
|
||||
alert t("js.insufficient_stock", {on_hand: on_hand})
|
||||
viewValue = on_hand
|
||||
available_quantity = scope.available_quantity()
|
||||
if parseInt(viewValue) > available_quantity
|
||||
alert t("js.insufficient_stock", {on_hand: available_quantity})
|
||||
viewValue = available_quantity
|
||||
ngModel.$setViewValue viewValue
|
||||
ngModel.$render()
|
||||
|
||||
viewValue
|
||||
|
||||
scope.available_quantity = ->
|
||||
on_hand = parseInt(attr.ofnOnHand)
|
||||
finalized_quantity = parseInt(attr.finalizedquantity) || 0 # finalizedquantity is optional
|
||||
on_hand + finalized_quantity
|
||||
|
||||
@@ -2,7 +2,14 @@ Darkswarm.directive "ofnPageAlert", ($timeout) ->
|
||||
restrict: 'A'
|
||||
scope: true
|
||||
link: (scope, elem, attrs) ->
|
||||
container_elems = $(".off-canvas-wrap .inner-wrap, .off-canvas-wrap .inner-wrap .fixed, .page-alert")
|
||||
moveSelectors = [".off-canvas-wrap .inner-wrap",
|
||||
".off-canvas-wrap .inner-wrap .fixed",
|
||||
".off-canvas-fixed .top-bar",
|
||||
".off-canvas-fixed ofn-flash",
|
||||
".off-canvas-fixed nav.tab-bar",
|
||||
".off-canvas-fixed .page-alert"]
|
||||
|
||||
container_elems = $(moveSelectors.join(", "))
|
||||
|
||||
# Wait a moment after page load before showing the alert. Otherwise we often miss the
|
||||
# start of the animation.
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, visibleFilter, Matcher, Geo, $rootScope) ->
|
||||
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, Matcher, Geo, $rootScope) ->
|
||||
new class Enterprises
|
||||
enterprises_by_id: {}
|
||||
|
||||
constructor: ->
|
||||
# Populate Enterprises.enterprises from json in page.
|
||||
@enterprises = enterprises
|
||||
|
||||
# Map enterprises to id/object pairs for lookup.
|
||||
for enterprise in enterprises
|
||||
@enterprises_by_id[enterprise.id] = enterprise
|
||||
|
||||
# Replace enterprise and taxons ids with actual objects.
|
||||
@dereferenceEnterprises()
|
||||
@visible_enterprises = visibleFilter @enterprises
|
||||
@producers = @visible_enterprises.filter (enterprise)->
|
||||
|
||||
@producers = @enterprises.filter (enterprise)->
|
||||
enterprise.category in ["producer_hub", "producer_shop", "producer"]
|
||||
@hubs = @visible_enterprises.filter (enterprise)->
|
||||
@hubs = @enterprises.filter (enterprise)->
|
||||
enterprise.category in ["hub", "hub_profile", "producer_hub", "producer_shop"]
|
||||
|
||||
dereferenceEnterprises: ->
|
||||
@@ -22,8 +25,6 @@ Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer,
|
||||
@dereferenceEnterprise enterprise
|
||||
|
||||
dereferenceEnterprise: (enterprise) ->
|
||||
@dereferenceProperty(enterprise, 'hubs', @enterprises_by_id)
|
||||
@dereferenceProperty(enterprise, 'producers', @enterprises_by_id)
|
||||
@dereferenceProperty(enterprise, 'taxons', Taxons.taxons_by_id)
|
||||
@dereferenceProperty(enterprise, 'supplied_taxons', Taxons.taxons_by_id)
|
||||
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
Darkswarm.factory 'Groups', (groups, Enterprises, Dereferencer) ->
|
||||
Darkswarm.factory 'Groups', (groups) ->
|
||||
new class Groups
|
||||
groups: groups
|
||||
groups_by_id: {}
|
||||
constructor: ->
|
||||
for group in @groups
|
||||
@groups_by_id[group.id] = group
|
||||
@dereference()
|
||||
dereference: ->
|
||||
for group in @groups
|
||||
Dereferencer.dereference group.enterprises, Enterprises.enterprises_by_id
|
||||
for enterprise in Enterprises.enterprises
|
||||
Dereferencer.dereference enterprise.groups, @groups_by_id
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal, visibleFilter) ->
|
||||
Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal) ->
|
||||
new class OfnMap
|
||||
constructor: ->
|
||||
@enterprises = @enterprise_markers(Enterprises.enterprises)
|
||||
@@ -6,7 +6,7 @@ Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal, visibleFilter) ->
|
||||
enterprise.latitude != null || enterprise.longitude != null # Remove enterprises w/o lat or long
|
||||
|
||||
enterprise_markers: (enterprises) ->
|
||||
@extend(enterprise) for enterprise in visibleFilter(enterprises)
|
||||
@extend(enterprise) for enterprise in enterprises
|
||||
|
||||
# Adding methods to each enterprise
|
||||
extend: (enterprise) ->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Properties, Cart, Variants) ->
|
||||
Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Properties, Cart, Variants) ->
|
||||
new class Products
|
||||
constructor: ->
|
||||
@update()
|
||||
@@ -31,7 +31,7 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro
|
||||
|
||||
dereference: ->
|
||||
for product in @products
|
||||
product.supplier = Enterprises.enterprises_by_id[product.supplier.id]
|
||||
product.supplier = Shopfront.producers_by_id[product.supplier.id]
|
||||
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
|
||||
|
||||
product.properties = angular.copy(product.properties_with_values)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
Darkswarm.factory 'Shopfront', (shopfront) ->
|
||||
new class Shopfront
|
||||
shopfront: shopfront
|
||||
producers_by_id: {}
|
||||
|
||||
constructor: ->
|
||||
for producer in shopfront.producers
|
||||
@producers_by_id[producer.id] = producer
|
||||
@@ -1,6 +1,6 @@
|
||||
#new-subscription-dialog
|
||||
.text-normal.margin-bottom-30.text-center
|
||||
= t('admin.subscriptions.index.please_select_a_shop')
|
||||
= t('js.admin.subscriptions.new.please_select_a_shop')
|
||||
|
||||
%form{ name: 'new_subscription_form', novalidate: true, ng: { submit: "newSubscription()" }}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
%input.ofn-select2.fullwidth#new_subscription_shop_id{ ng: { model: 'shop_id' }, required: true, name: 'shop_id', data: 'shops' }
|
||||
%div{ ng: { show: "submitted && new_subscription_form.$pristine" } }
|
||||
.error{ ng: { show: "new_subscription_form.shop_id.$error.required" } }
|
||||
= t('admin.subscriptions.index.please_select_a_shop')
|
||||
= t('js.admin.subscriptions.new.please_select_a_shop')
|
||||
|
||||
.text-center
|
||||
%input.button.red.icon-plus{ type: 'submit', value: t('continue') }
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
%input.ofn-select2.fullwidth{ :id => 'rule_type_selector', ng: { model: "ruleType" }, data: "ruleTypes", 'min-search' => "5" }
|
||||
|
||||
.text-center
|
||||
%input.button.red.icon-plus{ type: 'button', value: "Add Rule", ng: { click: 'addRule(tagGroup, ruleType)' } }
|
||||
%input.button.red.icon-plus{ type: 'button', value: "{{ 'js.admin.new_tag_rule_dialog.add_rule' | t }}", ng: { click: 'addRule(tagGroup, ruleType)' } }
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
-# Do not show this for producer shops selling only their own produce,
|
||||
-# Since a shopping link will already have been displayed in hub_details.html.haml
|
||||
.row.active_table_row.pad-top{ "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"}
|
||||
.columns.small-12
|
||||
.columns.small-12.cta-container
|
||||
.row
|
||||
.columns.small-12.fat
|
||||
%div{"ng-if" => "::enterprise.name"}
|
||||
%label{"ng-html" => "::'shop_for_products_html' | t:{enterprise: enterprise.name}"}
|
||||
%label{"ng-bind-html" => "::'shop_for_products_html' | t:{enterprise: enterprise.name}"}
|
||||
%div.show-for-medium-up{"ng-if" => "::!enterprise.name"}
|
||||
|
||||
.row.cta-container
|
||||
.row
|
||||
.columns.small-12
|
||||
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'",
|
||||
"ng-href" => "{{::hub.path}}", "ofn-empties-cart" => "hub",
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
*
|
||||
|
||||
*= require admin/spree_backend
|
||||
*= require admin/spree_auth
|
||||
|
||||
*= require jquery-ui-timepicker-addon
|
||||
*= require shared/textAngular
|
||||
*= require shared/ng-tags-input.min
|
||||
*= require admin/custom
|
||||
|
||||
*= require_self
|
||||
*/
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
/* Custom fix */
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd { width: 25%; }
|
||||
@@ -10,6 +10,7 @@
|
||||
@import 'foundation-icons';
|
||||
|
||||
@import 'base/*';
|
||||
@import 'layout/*';
|
||||
@import '*';
|
||||
@import 'pages/*';
|
||||
@import '../web/all';
|
||||
|
||||
52
app/assets/stylesheets/darkswarm/collapsible.css.scss
Normal file
52
app/assets/stylesheets/darkswarm/collapsible.css.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
// A bit arbitrary, works for it's use at time of implementation
|
||||
$collapsible-max-height: 350px;
|
||||
|
||||
.collapsible-checkbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapsible-label > ::before {
|
||||
border-bottom: 5px solid transparent;
|
||||
border-left: 5px solid currentColor;
|
||||
border-top: 5px solid transparent;
|
||||
|
||||
content: ' ';
|
||||
display: inline-block;
|
||||
|
||||
margin-right: .7rem;
|
||||
transform: translateY(-2px);
|
||||
transition: transform .2s ease-out;
|
||||
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.collapsible-content {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
transition: max-height .25s ease-in-out;
|
||||
}
|
||||
|
||||
.collapsible-checkbox:checked + .collapsible-label + .collapsible-content {
|
||||
max-height: $collapsible-max-height;
|
||||
}
|
||||
|
||||
.collapsible-checkbox:checked + .collapsible-label > ::before {
|
||||
transform: rotate(90deg) translateX(-3px);
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1025px) {
|
||||
// This double class is used to so this rule is more specific than the one in
|
||||
// all.scss
|
||||
.collapsible-label.collapsible-label-md {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.collapsible-label-md > ::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapsible-content-md {
|
||||
max-height: $collapsible-max-height;
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,6 @@ body.embedded {
|
||||
vertical-align: top;
|
||||
|
||||
&.cart {
|
||||
|
||||
div.joyride-tip-guide { // Cart Dropdown
|
||||
top: 75px;
|
||||
overflow: visible;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
@import "variables";
|
||||
|
||||
nav.top-bar {
|
||||
margin-bottom: 0px;
|
||||
|
||||
a.icon {
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
height: $topbar-height;
|
||||
color: white;
|
||||
|
||||
i {
|
||||
font-size: 29px;
|
||||
line-height: $topbar-height;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
line-height: $topbar-height;
|
||||
height: $topbar-height;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body > section[role='main'] {
|
||||
padding: 0px;
|
||||
}
|
||||
17
app/assets/stylesheets/darkswarm/layout/offcanvas.css.scss
Normal file
17
app/assets/stylesheets/darkswarm/layout/offcanvas.css.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
@import "compass/css3/transition";
|
||||
|
||||
.off-canvas-fixed {
|
||||
@include transition(transform 1000ms ease-in-out);
|
||||
}
|
||||
|
||||
.move-right > .off-canvas-fixed {
|
||||
height: 100%;
|
||||
-webkit-transform: translate3d(15.625rem, 0, 0);
|
||||
transform: translate3d(15.625rem, 0, 0);
|
||||
}
|
||||
|
||||
.left-off-canvas-menu {
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
margin-left: -15.625rem;
|
||||
}
|
||||
@@ -1,103 +1,212 @@
|
||||
@import "compass";
|
||||
@import "branding";
|
||||
@import "mixins";
|
||||
@import "typography";
|
||||
@import "variables";
|
||||
@import 'compass';
|
||||
@import 'branding';
|
||||
@import 'mixins';
|
||||
@import 'typography';
|
||||
@import 'variables';
|
||||
|
||||
nav {
|
||||
nav.top-bar {
|
||||
@include textpress;
|
||||
|
||||
text-shadow: none;
|
||||
|
||||
// Create center style for nav ul (foundation provides left and right)
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
margin-bottom: 0;
|
||||
height: $topbar-height;
|
||||
}
|
||||
|
||||
.top-bar-section {
|
||||
// Avoid menu items blocking logo
|
||||
li:not(.has-form), li:not(.has-form) a:not(.button), li:not(.has-form) a:not(.button):hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
ul.center {
|
||||
display: inline-block;
|
||||
|
||||
// By default, we center between the left and right uls, but we want to be centered
|
||||
// relative to the whole page. The difference in width between the other uls is 74px,
|
||||
// so we offset by that amount here.
|
||||
margin-left: -74px;
|
||||
}
|
||||
}
|
||||
|
||||
.joyride-tip-guide .button {
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
// Default overrides - big menu
|
||||
.top-bar-section .has-dropdown > a {
|
||||
padding-right: ($topbar-height / 3) !important;
|
||||
|
||||
i.ofn-i_022-cog, .ofn-i_071-globe {
|
||||
font-size: 24px;
|
||||
line-height: $topbar-height;
|
||||
}
|
||||
|
||||
i.ofn-i_071-globe {
|
||||
color: #666;
|
||||
font-size: 27px
|
||||
}
|
||||
}
|
||||
|
||||
.top-bar-section .has-dropdown > a:after {
|
||||
@media #{$large-only} {
|
||||
.top-bar--menu-item-with-icon span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.top-bar-section ul li > a {
|
||||
font-size: 0.75rem;
|
||||
height: $topbar-height;
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@include transition(all 0.3s ease-in-out);
|
||||
}
|
||||
|
||||
.top-bar-section ul li.ofn-logo > a {
|
||||
display: table-cell;
|
||||
.top-bar--current-hub-prefix,
|
||||
.top-bar--current-hub-name {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nav-branded {
|
||||
color: $brand-colour;
|
||||
.top-bar--current-hub-name {
|
||||
max-width: 10em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
.top-bar-section ul li > a.top-bar--menu-item-with-icon {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.top-bar--menu-item-with-icon i,
|
||||
.top-bar--menu-item-with-icon img {
|
||||
line-height: $topbar-height;
|
||||
}
|
||||
|
||||
.top-bar-section {
|
||||
border-bottom: 1px solid $ofn-grey;
|
||||
|
||||
a.icon {
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
font-weight: 300;
|
||||
height: $topbar-height;
|
||||
line-height: $topbar-height;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-primary {
|
||||
@include headingFont;
|
||||
|
||||
font-size: 0.875rem;
|
||||
font-weight: 300;
|
||||
// Avoid menu items blocking logo
|
||||
li:not(.has-form), li:not(.has-form) a:not(.button), li:not(.has-form) a:not(.button):hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
ul .nav-primary {
|
||||
text-transform: uppercase;
|
||||
li.cart {
|
||||
background-color: #f4704c;
|
||||
|
||||
a span {
|
||||
color: white;
|
||||
}
|
||||
|
||||
i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.count {
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 32px;
|
||||
height: 26px;
|
||||
margin-top: 16px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
background-color: white;
|
||||
border-radius: 20px;
|
||||
color: #f4704c;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: 8px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul.center {
|
||||
display: inline-block;
|
||||
|
||||
// By default, we center between the left and right uls, but we want to be centered
|
||||
// relative to the whole page. The difference in width between the other uls is 74px,
|
||||
// so we offset by that amount here.
|
||||
margin-left: -74px;
|
||||
}
|
||||
|
||||
ul.dropdown {
|
||||
border: 1px solid $smoke;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
ul.right {
|
||||
> li {
|
||||
border-left: 1px solid #ddd;
|
||||
padding: 0 14px;
|
||||
|
||||
@media screen and (max-width: 1450px) {
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
|
||||
li > a {
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #f4704c;
|
||||
display: inline-block;
|
||||
margin-right: 2px;
|
||||
margin-top: -3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: 2px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul li > a {
|
||||
font-size: 16px;
|
||||
height: $topbar-height;
|
||||
}
|
||||
|
||||
ul li.ofn-logo > a {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
ul .nav-primary {
|
||||
@include headingFont;
|
||||
text-transform: uppercase;
|
||||
font-weight: 300;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.joyride-tip-guide .button {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile Menu
|
||||
|
||||
.tab-bar {
|
||||
background-color: white;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 2.8em;
|
||||
z-index: 1;
|
||||
|
||||
.cart-span {
|
||||
background-color: #f4704c;
|
||||
padding: 13px;
|
||||
|
||||
a,
|
||||
span {
|
||||
color: white;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.count {
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
margin-left: 2px;
|
||||
width: 26px;
|
||||
}
|
||||
|
||||
span {
|
||||
background-color: white;
|
||||
border-radius: 20px;
|
||||
color: #f4704c;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
text-align: center;
|
||||
top: -9px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.off-canvas-list li.language-switcher ul li {
|
||||
@@ -114,8 +223,6 @@ nav {
|
||||
}
|
||||
|
||||
.off-canvas-wrap.move-right .tab-bar .menu-icon span {
|
||||
-moz-box-shadow: 0 0px 0 1px #666, 0 7px 0 1px #666, 0 14px 0 1px #666;
|
||||
-webkit-box-shadow: 0 0px 0 1px #666, 0 7px 0 1px #666, 0 14px 0 1px #666;
|
||||
box-shadow: 0 0px 0 1px #666, 0 7px 0 1px #666, 0 14px 0 1px #666;
|
||||
}
|
||||
|
||||
@@ -127,10 +234,19 @@ nav {
|
||||
padding: 9px 0 0 9px;
|
||||
}
|
||||
|
||||
// Leave space for tab bar, in screens smaller than large.
|
||||
[role="main"] {
|
||||
margin-top: 2.8em;
|
||||
|
||||
@media #{$large-up} {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.top-bar .ofn-logo img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
max-height: 51px;
|
||||
max-height: 44px;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
@@ -138,7 +254,7 @@ nav {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.off-canvas-wrap.move-right ul.off-canvas-list {
|
||||
.off-canvas-wrap ul.off-canvas-list {
|
||||
font-size: 0.875rem;
|
||||
|
||||
.li-menu {
|
||||
@@ -158,12 +274,10 @@ nav {
|
||||
background-color: transparent;
|
||||
color: $brand-colour;
|
||||
}
|
||||
|
||||
@include transition(all 0.3s ease-in-out);
|
||||
}
|
||||
}
|
||||
|
||||
.off-canvas-wrap.move-right ul.off-canvas-list i {
|
||||
.off-canvas-wrap ul.off-canvas-list i {
|
||||
font-size: 1.5rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
@@ -172,7 +286,8 @@ nav {
|
||||
|
||||
@media screen and (max-width: 1450px) {
|
||||
nav .top-bar-section {
|
||||
ul li a, .has-dropdown > a {
|
||||
ul li a,
|
||||
.has-dropdown > a {
|
||||
padding: 0 ($topbar-height / 8) !important;
|
||||
}
|
||||
|
||||
@@ -194,10 +309,4 @@ nav {
|
||||
// padding required to placehold for fixed menu bar
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
section.right {
|
||||
.nav-branded {
|
||||
padding: 0 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
@import "animations";
|
||||
@import "compass/css3/transition";
|
||||
|
||||
$page-alert-height: 55px;
|
||||
|
||||
// Basic style \\
|
||||
.page-alert {
|
||||
.alert-box {
|
||||
height: 55px;
|
||||
height: $page-alert-height;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba($dark-grey, 0.35);
|
||||
border-left: none;
|
||||
@@ -45,33 +47,26 @@
|
||||
}
|
||||
|
||||
// Show-hide animation \\
|
||||
.off-canvas-wrap .inner-wrap, .off-canvas-wrap .inner-wrap .fixed, nav.tab-bar {
|
||||
@include transition(all, 1000ms, ease-in-out);
|
||||
|
||||
.off-canvas-wrap .inner-wrap,
|
||||
.off-canvas-fixed .top-bar,
|
||||
.off-canvas-fixed ofn-flash,
|
||||
.off-canvas-fixed nav.tab-bar,
|
||||
.off-canvas-fixed .page-alert {
|
||||
@include transition(all 1000ms ease-in-out);
|
||||
|
||||
&.move-down {
|
||||
margin-top: 55px;
|
||||
|
||||
@include transition(all, 1000ms, ease-in-out);
|
||||
margin-top: $page-alert-height;
|
||||
}
|
||||
}
|
||||
|
||||
.off-canvas-wrap .inner-wrap .page-alert.fixed {
|
||||
top: -55px;
|
||||
z-index: 1;
|
||||
|
||||
// TODO: Compass to disable transition
|
||||
-moz-transition: none;
|
||||
-webkit-transition: none;
|
||||
-o-transition: color 0 ease-in;
|
||||
transition: none;
|
||||
.off-canvas-wrap .page-alert {
|
||||
top: -1 * $page-alert-height;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.off-canvas-wrap.move-right .inner-wrap.move-down {
|
||||
.page-alert {
|
||||
top: -55px * 2;
|
||||
}
|
||||
|
||||
.left-off-canvas-menu {
|
||||
top: -55px;
|
||||
top: -1 * $page-alert-height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import '../base/colors';
|
||||
@import '../collapsible';
|
||||
|
||||
// Styling for login modal to style tabs
|
||||
.reveal-modal.login-modal {
|
||||
@@ -23,3 +24,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1025px) {
|
||||
// make sure styling doesn't get messed up if resizing down and back up
|
||||
.collapsible-menus-container {
|
||||
min-height: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
$brand-colour: #f27052;
|
||||
|
||||
// Topbar
|
||||
$topbar-height: rem-calc(75);
|
||||
$topbar-link-padding: $topbar-height / 3;
|
||||
$topbar-height: rem-calc(64);
|
||||
$topbar-link-padding: $topbar-height / 4;
|
||||
$topbar-arrows: false;
|
||||
|
||||
$topbar-bg: $white;
|
||||
$topbar-bg-color: $topbar-bg;
|
||||
|
||||
@@ -31,7 +31,16 @@ module Admin
|
||||
|
||||
def collection
|
||||
return Schedule.where("1=0") unless json_request?
|
||||
permissions.visible_schedules
|
||||
if params[:enterprise_id]
|
||||
filter_schedules_by_enterprise_id(permissions.visible_schedules, params[:enterprise_id])
|
||||
else
|
||||
permissions.visible_schedules
|
||||
end
|
||||
end
|
||||
|
||||
# Filter schedules by OCs with a given coordinator id
|
||||
def filter_schedules_by_enterprise_id(schedules, enterprise_id)
|
||||
schedules.joins(:order_cycles).where(order_cycles: { coordinator_id: enterprise_id.to_i })
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
|
||||
@@ -5,12 +5,7 @@ module Api
|
||||
before_filter :override_sells, only: [:create, :update]
|
||||
before_filter :override_visible, only: [:create, :update]
|
||||
respond_to :json
|
||||
skip_authorization_check only: [:shopfront, :managed]
|
||||
|
||||
def managed
|
||||
@enterprises = Enterprise.ransack(params[:q]).result.managed_by(current_api_user)
|
||||
render params[:template] || :bulk_index
|
||||
end
|
||||
skip_authorization_check only: [:shopfront]
|
||||
|
||||
def create
|
||||
authorize! :create, Enterprise
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
module Api
|
||||
class OrderCyclesController < Spree::Api::BaseController
|
||||
respond_to :json
|
||||
def managed
|
||||
authorize! :admin, OrderCycle
|
||||
authorize! :read, OrderCycle
|
||||
@order_cycles = OrderCycle.ransack(params[:q]).result.managed_by(current_api_user)
|
||||
render params[:template] || :bulk_index
|
||||
end
|
||||
|
||||
def accessible
|
||||
@order_cycles = if params[:as] == "distributor"
|
||||
OrderCycle.ransack(params[:q]).result.
|
||||
involving_managed_distributors_of(current_api_user).order('updated_at DESC')
|
||||
elsif params[:as] == "producer"
|
||||
OrderCycle.ransack(params[:q]).result.
|
||||
involving_managed_producers_of(current_api_user).order('updated_at DESC')
|
||||
else
|
||||
OrderCycle.ransack(params[:q]).result.accessible_by(current_api_user)
|
||||
end
|
||||
|
||||
render params[:template] || :bulk_index
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,5 @@
|
||||
require 'open_food_network/referer_parser'
|
||||
require 'spree/authentication_helpers'
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
protect_from_forgery
|
||||
@@ -7,6 +8,7 @@ class ApplicationController < ActionController::Base
|
||||
before_filter :set_cache_headers # prevent cart emptying via cache when using back button #1213
|
||||
|
||||
include EnterprisesHelper
|
||||
include Spree::AuthenticationHelpers
|
||||
|
||||
def redirect_to(options = {}, response_status = {})
|
||||
::Rails.logger.error("Redirected by #{begin
|
||||
|
||||
@@ -8,7 +8,6 @@ class CheckoutController < Spree::CheckoutController
|
||||
prepend_before_filter :require_order_cycle
|
||||
prepend_before_filter :require_distributor_chosen
|
||||
|
||||
skip_before_filter :check_registration
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
include OrderCyclesHelper
|
||||
@@ -175,10 +174,6 @@ class CheckoutController < Spree::CheckoutController
|
||||
end
|
||||
end
|
||||
|
||||
def skip_state_validation?
|
||||
true
|
||||
end
|
||||
|
||||
def load_order
|
||||
@order = current_order
|
||||
redirect_to(main_app.shop_path) && return unless @order && @order.checkout_allowed?
|
||||
@@ -206,11 +201,11 @@ class CheckoutController < Spree::CheckoutController
|
||||
def redirect_to_cart_path
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
redirect_to cart_path
|
||||
redirect_to main_app.cart_path
|
||||
end
|
||||
|
||||
format.json do
|
||||
render json: { path: cart_path }, status: :bad_request
|
||||
render json: { path: main_app.cart_path }, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ class EnterprisesController < BaseController
|
||||
layout "darkswarm"
|
||||
helper Spree::ProductsHelper
|
||||
include OrderCyclesHelper
|
||||
include SerializerHelper
|
||||
|
||||
# These prepended filters are in the reverse order of execution
|
||||
prepend_before_filter :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop
|
||||
@@ -14,18 +15,10 @@ class EnterprisesController < BaseController
|
||||
respond_to :js, only: :permalink_checker
|
||||
|
||||
def shop
|
||||
return redirect_to spree.cart_path unless enough_stock?
|
||||
return redirect_to main_app.cart_path unless enough_stock?
|
||||
set_noindex_meta_tag
|
||||
|
||||
enterprises = current_distributor
|
||||
.plus_relatives_and_oc_producers(shop_order_cycles)
|
||||
.activated
|
||||
.includes(address: :state)
|
||||
.all
|
||||
|
||||
enterprises = inject_json_ams('enterprises', enterprises)
|
||||
|
||||
render locals: { enterprises: enterprises }
|
||||
@enterprise = current_distributor
|
||||
end
|
||||
|
||||
def relatives
|
||||
@@ -111,14 +104,4 @@ class EnterprisesController < BaseController
|
||||
def set_noindex_meta_tag
|
||||
@noindex_meta_tag = true unless current_distributor.visible?
|
||||
end
|
||||
|
||||
def inject_json_ams(name, object)
|
||||
options = {
|
||||
each_serializer: Api::EnterpriseSerializer,
|
||||
data: OpenFoodNetwork::EnterpriseInjectionData.new
|
||||
}
|
||||
serializer_instance = ActiveModel::ArraySerializer.new(object, options)
|
||||
|
||||
{ name: name, json: serializer_instance.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
class GroupsController < BaseController
|
||||
layout 'darkswarm'
|
||||
|
||||
def index
|
||||
@groups = EnterpriseGroup.on_front_page.by_position
|
||||
end
|
||||
|
||||
def show
|
||||
enable_embedded_shopfront
|
||||
@hide_menu = true if @shopfront_layout == 'embedded'
|
||||
|
||||
6
app/controllers/metal_decorator.rb
Normal file
6
app/controllers/metal_decorator.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
# For the API
|
||||
ActionController::Metal.class_eval do
|
||||
def spree_current_user
|
||||
@spree_current_user ||= env['warden'].user
|
||||
end
|
||||
end
|
||||
@@ -3,5 +3,14 @@ class ProducersController < BaseController
|
||||
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
def index; end
|
||||
def index
|
||||
@enterprises = Enterprise
|
||||
.activated
|
||||
.visible
|
||||
.is_primary_producer
|
||||
.includes(address: :state)
|
||||
.includes(:properties)
|
||||
.includes(supplied_products: :properties)
|
||||
.all
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,6 +6,8 @@ class ShopsController < BaseController
|
||||
def index
|
||||
@enterprises = Enterprise
|
||||
.activated
|
||||
.visible
|
||||
.is_distributor
|
||||
.includes(address: :state)
|
||||
.includes(:properties)
|
||||
.includes(supplied_products: :properties)
|
||||
|
||||
@@ -47,12 +47,22 @@ Spree::Admin::BaseController.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def model_class
|
||||
const_name = controller_name.classify
|
||||
if Spree.const_defined?(const_name)
|
||||
return "Spree::#{const_name}".constantize
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def active_distributors_not_ready_for_checkout
|
||||
ocs = OrderCycle.managed_by(spree_current_user).active
|
||||
distributors = ocs.map(&:distributors).flatten.uniq
|
||||
Enterprise.where('id IN (?)', distributors).not_ready_for_checkout
|
||||
Enterprise.where('enterprises.id IN (?)', distributors).not_ready_for_checkout
|
||||
end
|
||||
|
||||
def active_distributors_not_ready_for_checkout_message(distributors)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Spree::Admin::Orders::CustomerDetailsController.class_eval do
|
||||
before_filter :check_authorization
|
||||
before_filter :set_guest_checkout_status, only: :update
|
||||
|
||||
def update
|
||||
@@ -25,6 +26,17 @@ Spree::Admin::Orders::CustomerDetailsController.class_eval do
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
load_order
|
||||
session[:access_token] ||= params[:token]
|
||||
|
||||
resource = @order
|
||||
action = params[:action].to_sym
|
||||
action = :edit if action == :show # show route renders :edit for this controller
|
||||
|
||||
authorize! action, resource, session[:access_token]
|
||||
end
|
||||
|
||||
def set_guest_checkout_status
|
||||
registered_user = Spree::User.find_by_email(params[:order][:email])
|
||||
|
||||
|
||||
@@ -33,12 +33,18 @@ Spree::Admin::ProductsController.class_eval do
|
||||
delete_stock_params_and_set_after do
|
||||
super
|
||||
end
|
||||
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
||||
invoke_callbacks(:create, :fails)
|
||||
@object.errors.add(:base, t('spree.admin.products.image_upload_error'))
|
||||
respond_with(@object)
|
||||
end
|
||||
|
||||
def update
|
||||
delete_stock_params_and_set_after do
|
||||
super
|
||||
end
|
||||
|
||||
clear_variants_unit_description if @object.variant_unit == 'items'
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
@@ -154,4 +160,10 @@ Spree::Admin::ProductsController.class_eval do
|
||||
def set_product_master_variant_price_to_zero
|
||||
@product.price = 0 if @product.price.nil?
|
||||
end
|
||||
|
||||
def clear_variants_unit_description
|
||||
@object.variants.each do |variant|
|
||||
variant.update_attribute :unit_description, ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,3 +14,7 @@ module AuthorizeOnLoadResource
|
||||
end
|
||||
|
||||
Spree::Admin::ResourceController.prepend(AuthorizeOnLoadResource)
|
||||
|
||||
Spree::Admin::ResourceController.class_eval do
|
||||
rescue_from CanCan::AccessDenied, :with => :unauthorized
|
||||
end
|
||||
|
||||
131
app/controllers/spree/admin/users_controller.rb
Normal file
131
app/controllers/spree/admin/users_controller.rb
Normal file
@@ -0,0 +1,131 @@
|
||||
module Spree
|
||||
module Admin
|
||||
class UsersController < ResourceController
|
||||
rescue_from Spree::User::DestroyWithOrdersError, with: :user_destroy_with_orders_error
|
||||
|
||||
after_filter :sign_in_if_change_own_password, only: :update
|
||||
|
||||
# http://spreecommerce.com/blog/2010/11/02/json-hijacking-vulnerability/
|
||||
before_filter :check_json_authenticity, only: :index
|
||||
before_filter :load_roles, only: [:edit, :new, :update, :create,
|
||||
:generate_api_key, :clear_api_key]
|
||||
|
||||
def index
|
||||
respond_with(@collection) do |format|
|
||||
format.html
|
||||
format.json { render json: json_data }
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
if params[:user]
|
||||
roles = params[:user].delete("spree_role_ids")
|
||||
end
|
||||
|
||||
@user = Spree::User.new(params[:user])
|
||||
if @user.save
|
||||
|
||||
if roles
|
||||
@user.spree_roles = roles.reject(&:blank?).collect{ |r| Spree::Role.find(r) }
|
||||
end
|
||||
|
||||
flash.now[:success] = Spree.t(:created_successfully)
|
||||
render :edit
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if params[:user]
|
||||
roles = params[:user].delete("spree_role_ids")
|
||||
end
|
||||
|
||||
if @user.update_attributes(params[:user])
|
||||
if roles
|
||||
@user.spree_roles = roles.reject(&:blank?).collect{ |r| Spree::Role.find(r) }
|
||||
end
|
||||
|
||||
flash.now[:success] = Spree.t(:account_updated)
|
||||
end
|
||||
render :edit
|
||||
end
|
||||
|
||||
def generate_api_key
|
||||
if @user.generate_spree_api_key!
|
||||
flash[:success] = Spree.t('api.key_generated')
|
||||
end
|
||||
redirect_to edit_admin_user_path(@user)
|
||||
end
|
||||
|
||||
def clear_api_key
|
||||
if @user.clear_spree_api_key!
|
||||
flash[:success] = Spree.t('api.key_cleared')
|
||||
end
|
||||
redirect_to edit_admin_user_path(@user)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def collection
|
||||
return @collection if @collection.present?
|
||||
if request.xhr? && params[:q].present?
|
||||
# Disabling proper nested include here due to rails 3.1 bug
|
||||
@collection = Spree::User.
|
||||
includes(:bill_address, :ship_address).
|
||||
where("spree_users.email #{LIKE} :search
|
||||
OR (spree_addresses.firstname #{LIKE} :search
|
||||
AND spree_addresses.id = spree_users.bill_address_id)
|
||||
OR (spree_addresses.lastname #{LIKE} :search
|
||||
AND spree_addresses.id = spree_users.bill_address_id)
|
||||
OR (spree_addresses.firstname #{LIKE} :search
|
||||
AND spree_addresses.id = spree_users.ship_address_id)
|
||||
OR (spree_addresses.lastname #{LIKE} :search
|
||||
AND spree_addresses.id = spree_users.ship_address_id)",
|
||||
search: "#{params[:q].strip}%").
|
||||
limit(params[:limit] || 100)
|
||||
else
|
||||
@search = Spree::User.registered.ransack(params[:q])
|
||||
@collection = @search.
|
||||
result.
|
||||
page(params[:page]).
|
||||
per(Spree::Config[:admin_products_per_page])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# handling raise from Spree::Admin::ResourceController#destroy
|
||||
def user_destroy_with_orders_error
|
||||
invoke_callbacks(:destroy, :fails)
|
||||
render status: :forbidden, text: Spree.t(:error_user_destroy_with_orders)
|
||||
end
|
||||
|
||||
# Allow different formats of json data to suit different ajax calls
|
||||
def json_data
|
||||
json_format = params[:json_format] || 'default'
|
||||
case json_format
|
||||
when 'basic'
|
||||
collection.map { |u| { 'id' => u.id, 'name' => u.email } }.to_json
|
||||
else
|
||||
address_fields = [:firstname, :lastname, :address1, :address2, :city,
|
||||
:zipcode, :phone, :state_name, :state_id, :country_id]
|
||||
includes = { only: address_fields, include: { state: { only: :name },
|
||||
country: { only: :name } } }
|
||||
|
||||
collection.to_json(only: [:id, :email], include:
|
||||
{ bill_address: includes, ship_address: includes })
|
||||
end
|
||||
end
|
||||
|
||||
def sign_in_if_change_own_password
|
||||
return unless spree_current_user == @user && @user.password.present?
|
||||
sign_in(@user, event: :authentication, bypass: true)
|
||||
end
|
||||
|
||||
def load_roles
|
||||
@roles = Spree::Role.scoped
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
app/controllers/spree/checkout_controller.rb
Normal file
93
app/controllers/spree/checkout_controller.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
require 'open_food_network/address_finder'
|
||||
|
||||
module Spree
|
||||
class CheckoutController < Spree::StoreController
|
||||
include CheckoutHelper
|
||||
|
||||
ssl_required
|
||||
|
||||
before_filter :load_order
|
||||
|
||||
before_filter :ensure_order_not_completed
|
||||
before_filter :ensure_checkout_allowed
|
||||
before_filter :ensure_sufficient_stock_lines
|
||||
|
||||
before_filter :associate_user
|
||||
before_filter :check_authorization
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
helper 'spree/orders'
|
||||
|
||||
rescue_from Spree::Core::GatewayError, :with => :rescue_from_spree_gateway_error
|
||||
|
||||
def edit
|
||||
flash.keep
|
||||
redirect_to main_app.checkout_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_order
|
||||
@order = current_order
|
||||
redirect_to main_app.cart_path && return unless @order
|
||||
|
||||
if params[:state]
|
||||
redirect_to checkout_state_path(@order.state) if @order.can_go_to_state?(params[:state])
|
||||
@order.state = params[:state]
|
||||
end
|
||||
setup_for_current_state
|
||||
end
|
||||
|
||||
def ensure_checkout_allowed
|
||||
redirect_to main_app.cart_path unless @order.checkout_allowed?
|
||||
end
|
||||
|
||||
def ensure_order_not_completed
|
||||
redirect_to main_app.cart_path if @order.completed?
|
||||
end
|
||||
|
||||
def ensure_sufficient_stock_lines
|
||||
if @order.insufficient_stock_lines.present?
|
||||
flash[:error] = Spree.t(:inventory_error_flash_for_insufficient_quantity)
|
||||
redirect_to main_app.cart_path
|
||||
end
|
||||
end
|
||||
|
||||
def setup_for_current_state
|
||||
method_name = :"before_#{@order.state}"
|
||||
send(method_name) if respond_to?(method_name, true)
|
||||
end
|
||||
|
||||
# Adapted from spree_last_address gem: https://github.com/TylerRick/spree_last_address
|
||||
# Originally, we used a forked version of this gem, but encountered strange errors where
|
||||
# it worked in dev but only intermittently in staging/prod.
|
||||
def before_address
|
||||
associate_user
|
||||
|
||||
finder = OpenFoodNetwork::AddressFinder.new(@order.email)
|
||||
|
||||
@order.bill_address = finder.bill_address
|
||||
@order.ship_address = finder.ship_address
|
||||
end
|
||||
|
||||
def before_delivery
|
||||
return if params[:order].present?
|
||||
|
||||
packages = @order.shipments.map(&:to_package)
|
||||
@differentiator = Spree::Stock::Differentiator.new(@order, packages)
|
||||
end
|
||||
|
||||
def before_payment
|
||||
current_order.payments.destroy_all if request.put?
|
||||
end
|
||||
|
||||
def rescue_from_spree_gateway_error
|
||||
flash[:error] = Spree.t(:spree_gateway_error_flash_for_checkout)
|
||||
render :edit
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize!(:edit, current_order, session[:access_token])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
require 'open_food_network/address_finder'
|
||||
|
||||
Spree::CheckoutController.class_eval do
|
||||
include CheckoutHelper
|
||||
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
def edit
|
||||
flash.keep
|
||||
redirect_to main_app.checkout_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def before_payment
|
||||
current_order.payments.destroy_all if request.put?
|
||||
end
|
||||
|
||||
# Adapted from spree_last_address gem: https://github.com/TylerRick/spree_last_address
|
||||
# Originally, we used a forked version of this gem, but encountered strange errors where
|
||||
# it worked in dev but only intermittently in staging/prod.
|
||||
def before_address
|
||||
associate_user
|
||||
|
||||
finder = OpenFoodNetwork::AddressFinder.new(@order.email)
|
||||
|
||||
@order.bill_address = finder.bill_address
|
||||
@order.ship_address = finder.ship_address
|
||||
end
|
||||
end
|
||||
7
app/controllers/spree/home_controller.rb
Normal file
7
app/controllers/spree/home_controller.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
module Spree
|
||||
class HomeController < Spree::StoreController
|
||||
respond_to :html
|
||||
|
||||
def index; end
|
||||
end
|
||||
end
|
||||
226
app/controllers/spree/orders_controller.rb
Normal file
226
app/controllers/spree/orders_controller.rb
Normal file
@@ -0,0 +1,226 @@
|
||||
require 'spree/core/controller_helpers/order_decorator'
|
||||
require 'spree/core/controller_helpers/auth_decorator'
|
||||
|
||||
module Spree
|
||||
class OrdersController < Spree::StoreController
|
||||
include OrderCyclesHelper
|
||||
layout 'darkswarm'
|
||||
|
||||
ssl_required :show
|
||||
|
||||
before_filter :check_authorization
|
||||
rescue_from ActiveRecord::RecordNotFound, :with => :render_404
|
||||
helper 'spree/products', 'spree/orders'
|
||||
|
||||
respond_to :html
|
||||
respond_to :json
|
||||
|
||||
before_filter :update_distribution, only: :update
|
||||
before_filter :filter_order_params, only: :update
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
prepend_before_filter :require_order_authentication, only: :show
|
||||
prepend_before_filter :require_order_cycle, only: :edit
|
||||
prepend_before_filter :require_distributor_chosen, only: :edit
|
||||
before_filter :check_hub_ready_for_checkout, only: :edit
|
||||
before_filter :check_at_least_one_line_item, only: :update
|
||||
|
||||
def show
|
||||
@order = Spree::Order.find_by_number!(params[:id])
|
||||
end
|
||||
|
||||
def empty
|
||||
if @order = current_order
|
||||
@order.empty!
|
||||
end
|
||||
|
||||
redirect_to main_app.cart_path
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
session[:access_token] ||= params[:token]
|
||||
order = Spree::Order.find_by_number(params[:id]) || current_order
|
||||
|
||||
if order
|
||||
authorize! :edit, order, session[:access_token]
|
||||
else
|
||||
authorize! :create, Spree::Order
|
||||
end
|
||||
end
|
||||
|
||||
# Patching to redirect to shop if order is empty
|
||||
def edit
|
||||
@order = current_order(true)
|
||||
@insufficient_stock_lines = @order.insufficient_stock_lines
|
||||
@unavailable_order_variants = OrderCycleDistributedVariants.
|
||||
new(current_order_cycle, current_distributor).unavailable_order_variants(@order)
|
||||
|
||||
if @order.line_items.empty?
|
||||
redirect_to main_app.shop_path
|
||||
else
|
||||
associate_user
|
||||
|
||||
if @order.insufficient_stock_lines.present? || @unavailable_order_variants.present?
|
||||
flash[:error] = t("spree.orders.error_flash_for_unavailable_items")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@insufficient_stock_lines = []
|
||||
@order = order_to_update
|
||||
unless @order
|
||||
flash[:error] = t(:order_not_found)
|
||||
redirect_to(root_path) && return
|
||||
end
|
||||
|
||||
if @order.update_attributes(params[:order])
|
||||
discard_empty_line_items
|
||||
with_open_adjustments { update_totals_and_taxes }
|
||||
|
||||
if @order == current_order
|
||||
fire_event('spree.order.contents_changed')
|
||||
else
|
||||
@order.update_distribution_charge!
|
||||
end
|
||||
|
||||
respond_with(@order) do |format|
|
||||
format.html do
|
||||
if params.key?(:checkout)
|
||||
@order.next_transition.run_callbacks if @order.cart?
|
||||
redirect_to checkout_state_path(@order.checkout_steps.first)
|
||||
elsif @order.complete?
|
||||
redirect_to order_path(@order)
|
||||
else
|
||||
redirect_to main_app.cart_path
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
# Show order with original values, not newly entered ones
|
||||
@insufficient_stock_lines = @order.insufficient_stock_lines
|
||||
@order.line_items(true)
|
||||
respond_with(@order)
|
||||
end
|
||||
end
|
||||
|
||||
def update_distribution
|
||||
@order = current_order(true)
|
||||
|
||||
if params[:commit] == 'Choose Hub'
|
||||
distributor = Enterprise.is_distributor.find params[:order][:distributor_id]
|
||||
@order.set_distributor! distributor
|
||||
|
||||
flash[:notice] = I18n.t(:order_choosing_hub_notice)
|
||||
redirect_to request.referer
|
||||
|
||||
elsif params[:commit] == 'Choose Order Cycle'
|
||||
@order.empty! # empty cart
|
||||
order_cycle = OrderCycle.active.find params[:order][:order_cycle_id]
|
||||
@order.set_order_cycle! order_cycle
|
||||
|
||||
flash[:notice] = I18n.t(:order_choosing_hub_notice)
|
||||
redirect_to request.referer
|
||||
end
|
||||
end
|
||||
|
||||
def filter_order_params
|
||||
if params[:order] && params[:order][:line_items_attributes]
|
||||
params[:order][:line_items_attributes] =
|
||||
remove_missing_line_items(params[:order][:line_items_attributes])
|
||||
end
|
||||
end
|
||||
|
||||
def remove_missing_line_items(attrs)
|
||||
attrs.select do |_i, line_item|
|
||||
Spree::LineItem.find_by_id(line_item[:id])
|
||||
end
|
||||
end
|
||||
|
||||
def clear
|
||||
@order = current_order(true)
|
||||
@order.empty!
|
||||
@order.set_order_cycle! nil
|
||||
redirect_to main_app.enterprise_path(@order.distributor.id)
|
||||
end
|
||||
|
||||
def order_cycle_expired
|
||||
@order_cycle = OrderCycle.find session[:expired_order_cycle_id]
|
||||
end
|
||||
|
||||
def cancel
|
||||
@order = Spree::Order.find_by_number!(params[:id])
|
||||
authorize! :cancel, @order
|
||||
|
||||
if @order.cancel
|
||||
flash[:success] = I18n.t(:orders_your_order_has_been_cancelled)
|
||||
else
|
||||
flash[:error] = I18n.t(:orders_could_not_cancel)
|
||||
end
|
||||
redirect_to request.referer || order_path(@order)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Updates the various denormalized total attributes of the order and
|
||||
# recalculates the shipment taxes
|
||||
def update_totals_and_taxes
|
||||
@order.updater.update_totals
|
||||
@order.shipment.ensure_correct_adjustment_with_included_tax if @order.shipment
|
||||
end
|
||||
|
||||
# Sets the adjustments to open to perform the block's action and restores
|
||||
# their state to whatever the they had. Note that it does not change any new
|
||||
# adjustments that might get created in the yielded block.
|
||||
def with_open_adjustments
|
||||
previous_states = @order.adjustments.each_with_object({}) do |adjustment, hash|
|
||||
hash[adjustment.id] = adjustment.state
|
||||
end
|
||||
@order.adjustments.each(&:open)
|
||||
|
||||
yield
|
||||
|
||||
@order.adjustments.each do |adjustment|
|
||||
previous_state = previous_states[adjustment.id]
|
||||
adjustment.update_attribute(:state, previous_state) if previous_state
|
||||
end
|
||||
end
|
||||
|
||||
def discard_empty_line_items
|
||||
@order.line_items = @order.line_items.select { |li| li.quantity > 0 }
|
||||
end
|
||||
|
||||
def require_order_authentication
|
||||
return if session[:access_token] || params[:token] || spree_current_user
|
||||
|
||||
flash[:error] = I18n.t("spree.orders.edit.login_to_view_order")
|
||||
require_login_then_redirect_to request.env['PATH_INFO']
|
||||
end
|
||||
|
||||
def order_to_update
|
||||
return @order_to_update if defined? @order_to_update
|
||||
return @order_to_update = current_order unless params[:id]
|
||||
@order_to_update = changeable_order_from_number
|
||||
end
|
||||
|
||||
# If a specific order is requested, return it if it is COMPLETE and
|
||||
# changes are allowed and the user has access. Return nil if not.
|
||||
def changeable_order_from_number
|
||||
order = Spree::Order.complete.find_by_number(params[:id])
|
||||
return nil unless order.andand.changes_allowed? && can?(:update, order)
|
||||
order
|
||||
end
|
||||
|
||||
def check_at_least_one_line_item
|
||||
return unless order_to_update.andand.complete?
|
||||
|
||||
items = params[:order][:line_items_attributes]
|
||||
.andand.select{ |_k, attrs| attrs["quantity"].to_i > 0 }
|
||||
|
||||
if items.empty?
|
||||
flash[:error] = I18n.t(:orders_cannot_remove_the_final_item)
|
||||
redirect_to order_path(order_to_update)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,194 +0,0 @@
|
||||
require 'spree/core/controller_helpers/order_decorator'
|
||||
require 'spree/core/controller_helpers/auth_decorator'
|
||||
|
||||
Spree::OrdersController.class_eval do
|
||||
before_filter :update_distribution, only: :update
|
||||
before_filter :filter_order_params, only: :update
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
prepend_before_filter :require_order_authentication, only: :show
|
||||
prepend_before_filter :require_order_cycle, only: :edit
|
||||
prepend_before_filter :require_distributor_chosen, only: :edit
|
||||
before_filter :check_hub_ready_for_checkout, only: :edit
|
||||
before_filter :check_at_least_one_line_item, only: :update
|
||||
|
||||
include OrderCyclesHelper
|
||||
layout 'darkswarm'
|
||||
|
||||
respond_to :json
|
||||
|
||||
# Patching to redirect to shop if order is empty
|
||||
def edit
|
||||
@order = current_order(true)
|
||||
@insufficient_stock_lines = @order.insufficient_stock_lines
|
||||
@unavailable_order_variants = OrderCycleDistributedVariants.new(current_order_cycle, current_distributor).unavailable_order_variants(@order)
|
||||
|
||||
if @order.line_items.empty?
|
||||
redirect_to main_app.shop_path
|
||||
else
|
||||
associate_user
|
||||
|
||||
if @order.insufficient_stock_lines.present? || @unavailable_order_variants.present?
|
||||
flash[:error] = t("spree.orders.error_flash_for_unavailable_items")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@insufficient_stock_lines = []
|
||||
@order = order_to_update
|
||||
unless @order
|
||||
flash[:error] = t(:order_not_found)
|
||||
redirect_to(root_path) && return
|
||||
end
|
||||
|
||||
if @order.update_attributes(params[:order])
|
||||
discard_empty_line_items
|
||||
with_open_adjustments { update_totals_and_taxes }
|
||||
|
||||
render(:edit) && return unless apply_coupon_code
|
||||
|
||||
if @order == current_order
|
||||
fire_event('spree.order.contents_changed')
|
||||
else
|
||||
@order.update_distribution_charge!
|
||||
end
|
||||
|
||||
respond_with(@order) do |format|
|
||||
format.html do
|
||||
if params.key?(:checkout)
|
||||
@order.next_transition.run_callbacks if @order.cart?
|
||||
redirect_to checkout_state_path(@order.checkout_steps.first)
|
||||
elsif @order.complete?
|
||||
redirect_to order_path(@order)
|
||||
else
|
||||
redirect_to cart_path
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
# Show order with original values, not newly entered ones
|
||||
@insufficient_stock_lines = @order.insufficient_stock_lines
|
||||
@order.line_items(true)
|
||||
respond_with(@order)
|
||||
end
|
||||
end
|
||||
|
||||
def update_distribution
|
||||
@order = current_order(true)
|
||||
|
||||
if params[:commit] == 'Choose Hub'
|
||||
distributor = Enterprise.is_distributor.find params[:order][:distributor_id]
|
||||
@order.set_distributor! distributor
|
||||
|
||||
flash[:notice] = I18n.t(:order_choosing_hub_notice)
|
||||
redirect_to request.referer
|
||||
|
||||
elsif params[:commit] == 'Choose Order Cycle'
|
||||
@order.empty! # empty cart
|
||||
order_cycle = OrderCycle.active.find params[:order][:order_cycle_id]
|
||||
@order.set_order_cycle! order_cycle
|
||||
|
||||
flash[:notice] = I18n.t(:order_choosing_hub_notice)
|
||||
redirect_to request.referer
|
||||
end
|
||||
end
|
||||
|
||||
def filter_order_params
|
||||
if params[:order] && params[:order][:line_items_attributes]
|
||||
params[:order][:line_items_attributes] = remove_missing_line_items(params[:order][:line_items_attributes])
|
||||
end
|
||||
end
|
||||
|
||||
def remove_missing_line_items(attrs)
|
||||
attrs.select do |_i, line_item|
|
||||
Spree::LineItem.find_by_id(line_item[:id])
|
||||
end
|
||||
end
|
||||
|
||||
def clear
|
||||
@order = current_order(true)
|
||||
@order.empty!
|
||||
@order.set_order_cycle! nil
|
||||
redirect_to main_app.enterprise_path(@order.distributor.id)
|
||||
end
|
||||
|
||||
def order_cycle_expired
|
||||
@order_cycle = OrderCycle.find session[:expired_order_cycle_id]
|
||||
end
|
||||
|
||||
def cancel
|
||||
@order = Spree::Order.find_by_number!(params[:id])
|
||||
authorize! :cancel, @order
|
||||
|
||||
if @order.cancel
|
||||
flash[:success] = I18n.t(:orders_your_order_has_been_cancelled)
|
||||
else
|
||||
flash[:error] = I18n.t(:orders_could_not_cancel)
|
||||
end
|
||||
redirect_to request.referer || order_path(@order)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Updates the various denormalized total attributes of the order and
|
||||
# recalculates the shipment taxes
|
||||
def update_totals_and_taxes
|
||||
@order.updater.update_totals
|
||||
@order.shipment.ensure_correct_adjustment_with_included_tax if @order.shipment
|
||||
end
|
||||
|
||||
# Sets the adjustments to open to perform the block's action and restores
|
||||
# their state to whatever the they had. Note that it does not change any new
|
||||
# adjustments that might get created in the yielded block.
|
||||
def with_open_adjustments
|
||||
previous_states = @order.adjustments.each_with_object({}) do |adjustment, hash|
|
||||
hash[adjustment.id] = adjustment.state
|
||||
end
|
||||
@order.adjustments.each(&:open)
|
||||
|
||||
yield
|
||||
|
||||
@order.adjustments.each do |adjustment|
|
||||
previous_state = previous_states[adjustment.id]
|
||||
adjustment.update_attribute(:state, previous_state) if previous_state
|
||||
end
|
||||
end
|
||||
|
||||
def discard_empty_line_items
|
||||
@order.line_items = @order.line_items.select { |li| li.quantity > 0 }
|
||||
end
|
||||
|
||||
def require_order_authentication
|
||||
return if session[:access_token] || params[:token] || spree_current_user
|
||||
|
||||
flash[:error] = I18n.t("spree.orders.edit.login_to_view_order")
|
||||
require_login_then_redirect_to request.env['PATH_INFO']
|
||||
end
|
||||
|
||||
def order_to_update
|
||||
return @order_to_update if defined? @order_to_update
|
||||
return @order_to_update = current_order unless params[:id]
|
||||
@order_to_update = changeable_order_from_number
|
||||
end
|
||||
|
||||
# If a specific order is requested, return it if it is COMPLETE and
|
||||
# changes are allowed and the user has access. Return nil if not.
|
||||
def changeable_order_from_number
|
||||
order = Spree::Order.complete.find_by_number(params[:id])
|
||||
return nil unless order.andand.changes_allowed? && can?(:update, order)
|
||||
order
|
||||
end
|
||||
|
||||
def check_at_least_one_line_item
|
||||
return unless order_to_update.andand.complete?
|
||||
|
||||
items = params[:order][:line_items_attributes]
|
||||
.andand.select{ |_k, attrs| attrs["quantity"].to_i > 0 }
|
||||
|
||||
if items.empty?
|
||||
flash[:error] = I18n.t(:orders_cannot_remove_the_final_item)
|
||||
redirect_to order_path(order_to_update)
|
||||
end
|
||||
end
|
||||
end
|
||||
14
app/controllers/spree/store_controller.rb
Normal file
14
app/controllers/spree/store_controller.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
module Spree
|
||||
class StoreController < Spree::BaseController
|
||||
layout 'darkswarm'
|
||||
|
||||
include Spree::Core::ControllerHelpers::Order
|
||||
|
||||
include I18nHelper
|
||||
before_filter :set_locale
|
||||
|
||||
def unauthorized
|
||||
render 'shared/unauthorized', status: :unauthorized
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
class Spree::StoreController
|
||||
layout 'darkswarm'
|
||||
|
||||
include I18nHelper
|
||||
before_filter :set_locale
|
||||
|
||||
def unauthorized
|
||||
render 'shared/unauthorized', status: :unauthorized
|
||||
end
|
||||
end
|
||||
44
app/controllers/spree/user_passwords_controller.rb
Normal file
44
app/controllers/spree/user_passwords_controller.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
module Spree
|
||||
class UserPasswordsController < Devise::PasswordsController
|
||||
helper 'spree/base', 'spree/store'
|
||||
|
||||
include Spree::Core::ControllerHelpers::Auth
|
||||
include Spree::Core::ControllerHelpers::Common
|
||||
include Spree::Core::ControllerHelpers::Order
|
||||
include Spree::Core::ControllerHelpers::SSL
|
||||
|
||||
ssl_required
|
||||
|
||||
# Overridden due to bug in Devise.
|
||||
# respond_with resource, :location => new_session_path(resource_name)
|
||||
# is generating bad url /session/new.user
|
||||
#
|
||||
# overridden to:
|
||||
# respond_with resource, :location => spree.login_path
|
||||
#
|
||||
def create
|
||||
self.resource = resource_class.send_reset_password_instructions(params[resource_name])
|
||||
|
||||
if resource.errors.empty?
|
||||
set_flash_message(:notice, :send_instructions) if is_navigational_format?
|
||||
respond_with resource, location: spree.login_path
|
||||
else
|
||||
respond_with_navigational(resource) { render :new }
|
||||
end
|
||||
end
|
||||
|
||||
# Devise::PasswordsController allows for blank passwords.
|
||||
# Silly Devise::PasswordsController!
|
||||
# Fixes spree/spree#2190.
|
||||
def update
|
||||
if params[:spree_user][:password].blank?
|
||||
self.resource = resource_class.new
|
||||
resource.reset_password_token = params[:spree_user][:reset_password_token]
|
||||
set_flash_message(:error, :cannot_be_blank)
|
||||
render :edit
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
65
app/controllers/spree/user_registrations_controller.rb
Normal file
65
app/controllers/spree/user_registrations_controller.rb
Normal file
@@ -0,0 +1,65 @@
|
||||
module Spree
|
||||
class UserRegistrationsController < Devise::RegistrationsController
|
||||
helper 'spree/base', 'spree/store'
|
||||
|
||||
include Spree::Core::ControllerHelpers::Auth
|
||||
include Spree::Core::ControllerHelpers::Common
|
||||
include Spree::Core::ControllerHelpers::Order
|
||||
include Spree::Core::ControllerHelpers::SSL
|
||||
|
||||
ssl_required
|
||||
before_filter :check_permissions, only: [:edit, :update]
|
||||
skip_before_filter :require_no_authentication
|
||||
|
||||
# GET /resource/sign_up
|
||||
def new
|
||||
super
|
||||
@user = resource
|
||||
end
|
||||
|
||||
# POST /resource/sign_up
|
||||
def create
|
||||
@user = build_resource(params[:spree_user])
|
||||
if resource.save
|
||||
set_flash_message(:notice, :signed_up)
|
||||
sign_in(:spree_user, @user)
|
||||
session[:spree_user_signup] = true
|
||||
associate_user
|
||||
respond_with resource, location: after_sign_up_path_for(resource)
|
||||
else
|
||||
clean_up_passwords(resource)
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
# GET /resource/edit
|
||||
def edit
|
||||
super
|
||||
end
|
||||
|
||||
# PUT /resource
|
||||
def update
|
||||
super
|
||||
end
|
||||
|
||||
# DELETE /resource
|
||||
def destroy
|
||||
super
|
||||
end
|
||||
|
||||
# GET /resource/cancel
|
||||
# Forces the session data which is usually expired after sign
|
||||
# in to be expired now. This is useful if the user wants to
|
||||
# cancel oauth signing in/up in the middle of the process,
|
||||
# removing all OAuth session data.
|
||||
def cancel
|
||||
super
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def check_permissions
|
||||
authorize!(:create, resource)
|
||||
end
|
||||
end
|
||||
end
|
||||
56
app/controllers/spree/user_sessions_controller.rb
Normal file
56
app/controllers/spree/user_sessions_controller.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
module Spree
|
||||
class UserSessionsController < Devise::SessionsController
|
||||
helper 'spree/base', 'spree/store'
|
||||
|
||||
include Spree::Core::ControllerHelpers::Auth
|
||||
include Spree::Core::ControllerHelpers::Common
|
||||
include Spree::Core::ControllerHelpers::Order
|
||||
include Spree::Core::ControllerHelpers::SSL
|
||||
|
||||
ssl_required :new, :create, :destroy, :update
|
||||
ssl_allowed :login_bar
|
||||
|
||||
before_filter :set_checkout_redirect, only: :create
|
||||
|
||||
def create
|
||||
authenticate_spree_user!
|
||||
|
||||
if spree_user_signed_in?
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = t('devise.success.logged_in_succesfully')
|
||||
redirect_back_or_default(after_sign_in_path_for(spree_current_user))
|
||||
}
|
||||
format.js {
|
||||
render json: { email: spree_current_user.login }, status: :ok
|
||||
}
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash.now[:error] = t('devise.failure.invalid')
|
||||
render :new
|
||||
}
|
||||
format.js {
|
||||
render json: { message: t('devise.failure.invalid') }, status: :unauthorized
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def nav_bar
|
||||
render partial: 'spree/shared/nav_bar'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def accurate_title
|
||||
Spree.t(:login)
|
||||
end
|
||||
|
||||
def redirect_back_or_default(default)
|
||||
redirect_to(session["spree_user_return_to"] || default)
|
||||
session["spree_user_return_to"] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,29 +0,0 @@
|
||||
Spree::UserSessionsController.class_eval do
|
||||
before_filter :set_checkout_redirect, only: :create
|
||||
|
||||
def create
|
||||
authenticate_spree_user!
|
||||
|
||||
if spree_user_signed_in?
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = t(:logged_in_succesfully)
|
||||
redirect_back_or_default(after_sign_in_path_for(spree_current_user))
|
||||
}
|
||||
format.js {
|
||||
render json: { email: spree_current_user.login }, status: :ok
|
||||
}
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash.now[:error] = t('devise.failure.invalid')
|
||||
render :new
|
||||
}
|
||||
format.js {
|
||||
render json: { message: t('devise.failure.invalid') }, status: :unauthorized
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
74
app/controllers/spree/users_controller.rb
Normal file
74
app/controllers/spree/users_controller.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
module Spree
|
||||
class UsersController < Spree::StoreController
|
||||
layout 'darkswarm'
|
||||
ssl_required
|
||||
skip_before_filter :set_current_order, only: :show
|
||||
prepend_before_filter :load_object, only: [:show, :edit, :update]
|
||||
prepend_before_filter :authorize_actions, only: :new
|
||||
|
||||
include Spree::Core::ControllerHelpers
|
||||
include I18nHelper
|
||||
|
||||
before_filter :set_locale
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
# Ignores invoice orders, only order where state: 'complete'
|
||||
def show
|
||||
@orders = @user.orders.where(state: 'complete').order('completed_at desc')
|
||||
@unconfirmed_email = spree_current_user.unconfirmed_email
|
||||
end
|
||||
|
||||
# Endpoint for queries to check if a user is already registered
|
||||
def registered_email
|
||||
user = Spree.user_class.find_by_email params[:email]
|
||||
render json: { registered: user.present? }
|
||||
end
|
||||
|
||||
def create
|
||||
@user = Spree::User.new(params[:user])
|
||||
if @user.save
|
||||
|
||||
if current_order
|
||||
session[:guest_token] = nil
|
||||
end
|
||||
|
||||
redirect_back_or_default(root_url)
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @user.update_attributes(params[:user])
|
||||
if params[:user][:password].present?
|
||||
# this logic needed b/c devise wants to log us out after password changes
|
||||
Spree::User.reset_password_by_token(params[:user])
|
||||
sign_in(@user, event: :authentication,
|
||||
bypass: true)
|
||||
end
|
||||
redirect_to spree.account_url, notice: Spree.t(:account_updated)
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_object
|
||||
@user ||= spree_current_user
|
||||
if @user
|
||||
authorize! params[:action].to_sym, @user
|
||||
else
|
||||
redirect_to spree.login_path
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_actions
|
||||
authorize! params[:action].to_sym, Spree::User.new
|
||||
end
|
||||
|
||||
def accurate_title
|
||||
Spree.t(:my_account)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
Spree::UsersController.class_eval do
|
||||
layout 'darkswarm'
|
||||
include I18nHelper
|
||||
|
||||
before_filter :set_locale
|
||||
before_filter :enable_embedded_shopfront
|
||||
|
||||
# Override of spree_auth_devise default
|
||||
# Ignores invoice orders, only order where state: 'complete'
|
||||
def show
|
||||
@orders = @user.orders.where(state: 'complete').order('completed_at desc')
|
||||
@unconfirmed_email = spree_current_user.unconfirmed_email
|
||||
end
|
||||
|
||||
# Endpoint for queries to check if a user is already registered
|
||||
def registered_email
|
||||
user = Spree.user_class.find_by_email params[:email]
|
||||
render json: { registered: user.present? }
|
||||
end
|
||||
end
|
||||
@@ -8,6 +8,6 @@ module EnterpriseFeesHelper
|
||||
end
|
||||
|
||||
def enterprise_fee_type_options
|
||||
EnterpriseFee::FEE_TYPES.map { |f| [f.capitalize, f] }
|
||||
EnterpriseFee::FEE_TYPES.map { |fee_type| [t("#{fee_type}_fee"), fee_type] }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +1,41 @@
|
||||
require 'open_food_network/enterprise_injection_data'
|
||||
|
||||
module InjectionHelper
|
||||
include SerializerHelper
|
||||
|
||||
def inject_enterprises(enterprises = Enterprise.activated.includes(address: :state).all)
|
||||
inject_json_ams(
|
||||
'enterprises',
|
||||
"enterprises",
|
||||
enterprises,
|
||||
Api::EnterpriseSerializer,
|
||||
enterprise_injection_data
|
||||
)
|
||||
end
|
||||
|
||||
def inject_enterprise_shopfront_list
|
||||
def inject_groups
|
||||
select_only = required_attributes EnterpriseGroup, Api::GroupListSerializer
|
||||
|
||||
inject_json_ams(
|
||||
'enterprises',
|
||||
Enterprise.activated.includes(address: :state).all,
|
||||
"groups",
|
||||
EnterpriseGroup.on_front_page.by_position.select(select_only).includes(address: :state).all,
|
||||
Api::GroupListSerializer
|
||||
)
|
||||
end
|
||||
|
||||
def inject_enterprise_shopfront(enterprise)
|
||||
inject_json_ams(
|
||||
"shopfront",
|
||||
enterprise,
|
||||
Api::EnterpriseShopfrontSerializer
|
||||
)
|
||||
end
|
||||
|
||||
def inject_enterprise_shopfront_list
|
||||
select_only = required_attributes Enterprise, Api::EnterpriseShopfrontListSerializer
|
||||
|
||||
inject_json_ams(
|
||||
"enterprises",
|
||||
Enterprise.activated.visible.select(select_only).includes(address: :state).all,
|
||||
Api::EnterpriseShopfrontListSerializer
|
||||
)
|
||||
end
|
||||
@@ -23,7 +45,12 @@ module InjectionHelper
|
||||
end
|
||||
|
||||
def inject_group_enterprises
|
||||
inject_json_ams "group_enterprises", @group.enterprises.activated.all, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
inject_json_ams(
|
||||
"enterprises",
|
||||
@group.enterprises.activated.all,
|
||||
Api::EnterpriseSerializer,
|
||||
enterprise_injection_data
|
||||
)
|
||||
end
|
||||
|
||||
def inject_current_hub
|
||||
@@ -79,17 +106,17 @@ module InjectionHelper
|
||||
end
|
||||
|
||||
def inject_saved_credit_cards
|
||||
data = if spree_current_user
|
||||
spree_current_user.credit_cards.with_payment_profile.all
|
||||
else
|
||||
[]
|
||||
end
|
||||
data = spree_current_user ? spree_current_user.credit_cards.with_payment_profile.all : []
|
||||
|
||||
inject_json_ams "savedCreditCards", data, Api::CreditCardSerializer
|
||||
end
|
||||
|
||||
def inject_json(name, partial, opts = {})
|
||||
render partial: "json/injection", locals: { name: name, partial: partial }.merge(opts)
|
||||
def inject_current_user
|
||||
inject_json_ams "user", spree_current_user, Api::UserSerializer
|
||||
end
|
||||
|
||||
def inject_rails_flash
|
||||
inject_json_ams "railsFlash", OpenStruct.new(flash.to_hash), Api::RailsFlashSerializer
|
||||
end
|
||||
|
||||
def inject_json_ams(name, data, serializer, opts = {})
|
||||
|
||||
@@ -3,4 +3,13 @@ module SerializerHelper
|
||||
return [] if ids.blank?
|
||||
ids.map { |id| { id: id } }
|
||||
end
|
||||
|
||||
# Returns an array of the fields a serializer needs from it's object
|
||||
# so we can #select only what the serializer will actually use
|
||||
def required_attributes(model, serializer)
|
||||
model_attributes = model.attribute_names
|
||||
serializer_attributes = serializer._attributes.keys.map(&:to_s)
|
||||
|
||||
(serializer_attributes & model_attributes).map { |attr| "#{model.table_name}.#{attr}" }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,27 +2,102 @@ module Spree
|
||||
module Admin
|
||||
module OrdersHelper
|
||||
def order_links(order)
|
||||
@order ||= order
|
||||
links = []
|
||||
links << { name: t(:edit_order), url: edit_admin_order_path(order), icon: 'icon-edit' } unless action_name == "edit"
|
||||
if @order.complete?
|
||||
links << { name: t(:resend_confirmation), url: resend_admin_order_path(order), icon: 'icon-email', method: 'post', confirm: t(:confirm_resend_order_confirmation) }
|
||||
if @order.distributor.can_invoice?
|
||||
links << { name: t(:send_invoice), url: invoice_admin_order_path(order), icon: 'icon-email', confirm: t(:confirm_send_invoice) }
|
||||
else
|
||||
links << { name: t(:send_invoice), url: "#", icon: 'icon-email', confirm: t(:must_have_valid_business_number, enterprise_name: order.distributor.name) }
|
||||
end
|
||||
links << { name: t(:print_invoice), url: print_admin_order_path(order), icon: 'icon-print', target: "_blank" }
|
||||
if Spree::Config.enable_receipt_printing?
|
||||
links << { name: t(:print_ticket), url: print_ticket_admin_order_path(order), icon: 'icon-print', target: "_blank" }
|
||||
links << { name: t(:select_ticket_printer), url: "#{print_ticket_admin_order_path(order)}#select-printer", icon: 'icon-print', target: "_blank" }
|
||||
end
|
||||
end
|
||||
if @order.ready_to_ship?
|
||||
links << { name: t(:ship_order), url: fire_admin_order_path(@order, e: 'ship'), method: 'put', icon: 'icon-truck', confirm: t(:are_you_sure) }
|
||||
end
|
||||
links << { name: t(:cancel_order), url: fire_admin_order_path(@order.number, e: 'cancel'), icon: 'icon-trash', confirm: t(:are_you_sure) } if order.can_cancel?
|
||||
links << edit_order_link unless action_name == "edit"
|
||||
links.concat(complete_order_links) if @order.complete?
|
||||
links << ship_order_link if @order.ready_to_ship?
|
||||
links << cancel_order_link if @order.can_cancel?
|
||||
links
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def complete_order_links
|
||||
[resend_confirmation_link] + invoice_links + ticket_links
|
||||
end
|
||||
|
||||
def invoice_links
|
||||
return [] unless Spree::Config[:enable_invoices?]
|
||||
[send_invoice_link, print_invoice_link]
|
||||
end
|
||||
|
||||
def send_invoice_link
|
||||
if @order.distributor.can_invoice?
|
||||
send_invoice_link_with_url
|
||||
else
|
||||
send_invoice_link_without_url
|
||||
end
|
||||
end
|
||||
|
||||
def ticket_links
|
||||
return [] unless Spree::Config[:enable_receipt_printing?]
|
||||
[print_ticket_link, select_ticket_printer_link]
|
||||
end
|
||||
|
||||
def edit_order_link
|
||||
{ name: t(:edit_order),
|
||||
url: edit_admin_order_path(@order),
|
||||
icon: 'icon-edit' }
|
||||
end
|
||||
|
||||
def resend_confirmation_link
|
||||
{ name: t(:resend_confirmation),
|
||||
url: resend_admin_order_path(@order),
|
||||
icon: 'icon-email',
|
||||
method: 'post',
|
||||
confirm: t(:confirm_resend_order_confirmation) }
|
||||
end
|
||||
|
||||
def send_invoice_link_with_url
|
||||
{ name: t(:send_invoice),
|
||||
url: invoice_admin_order_path(@order),
|
||||
icon: 'icon-email',
|
||||
confirm: t(:confirm_send_invoice) }
|
||||
end
|
||||
|
||||
def send_invoice_link_without_url
|
||||
{ name: t(:send_invoice),
|
||||
url: "#",
|
||||
icon: 'icon-email',
|
||||
confirm: t(:must_have_valid_business_number, enterprise_name: @order.distributor.name) }
|
||||
end
|
||||
|
||||
def print_invoice_link
|
||||
{ name: t(:print_invoice),
|
||||
url: print_admin_order_path(@order),
|
||||
icon: 'icon-print',
|
||||
target: "_blank" }
|
||||
end
|
||||
|
||||
def print_ticket_link
|
||||
{ name: t(:print_ticket),
|
||||
url: print_ticket_admin_order_path(@order),
|
||||
icon: 'icon-print',
|
||||
target: "_blank" }
|
||||
end
|
||||
|
||||
def select_ticket_printer_link
|
||||
{ name: t(:select_ticket_printer),
|
||||
url: "#{print_ticket_admin_order_path(@order)}#select-printer",
|
||||
icon: 'icon-print',
|
||||
target: "_blank" }
|
||||
end
|
||||
|
||||
def ship_order_link
|
||||
{ name: t(:ship_order),
|
||||
url: fire_admin_order_path(@order, e: 'ship'),
|
||||
method: 'put',
|
||||
icon: 'icon-truck',
|
||||
confirm: t(:are_you_sure) }
|
||||
end
|
||||
|
||||
def cancel_order_link
|
||||
{ name: t(:cancel_order),
|
||||
url: fire_admin_order_path(@order.number, e: 'cancel'),
|
||||
icon: 'icon-trash',
|
||||
confirm: t(:are_you_sure) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
47
app/mailers/spree/user_mailer.rb
Normal file
47
app/mailers/spree/user_mailer.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
# This mailer is configured to be the Devise mailer
|
||||
# Some methods here override Devise::Mailer methods
|
||||
module Spree
|
||||
class UserMailer < BaseMailer
|
||||
include I18nHelper
|
||||
|
||||
# Overrides `Devise::Mailer.reset_password_instructions`
|
||||
def reset_password_instructions(user)
|
||||
recipient = user.respond_to?(:id) ? user : Spree.user_class.find(user)
|
||||
@edit_password_reset_url = spree.
|
||||
edit_spree_user_password_url(reset_password_token: recipient.reset_password_token)
|
||||
|
||||
mail(to: recipient.email, from: from_address,
|
||||
subject: Spree::Config[:site_name] + ' ' +
|
||||
I18n.t(:subject, scope: [:devise, :mailer, :reset_password_instructions]))
|
||||
end
|
||||
|
||||
# This is a OFN specific email, not from Devise::Mailer
|
||||
def signup_confirmation(user)
|
||||
@user = user
|
||||
I18n.with_locale valid_locale(@user) do
|
||||
mail(to: user.email, from: from_address,
|
||||
subject: t(:welcome_to) + Spree::Config[:site_name])
|
||||
end
|
||||
end
|
||||
|
||||
# Overrides `Devise::Mailer.confirmation_instructions`
|
||||
def confirmation_instructions(user, _opts)
|
||||
@user = user
|
||||
@instance = Spree::Config[:site_name]
|
||||
@contact = ContentConfig.footer_email
|
||||
|
||||
I18n.with_locale valid_locale(@user) do
|
||||
subject = t('spree.user_mailer.confirmation_instructions.subject')
|
||||
mail(to: confirmation_email_address,
|
||||
from: from_address,
|
||||
subject: subject)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def confirmation_email_address
|
||||
@user.pending_reconfirmation? ? @user.unconfirmed_email : @user.email
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,32 +0,0 @@
|
||||
Spree::UserMailer.class_eval do
|
||||
include I18nHelper
|
||||
|
||||
def signup_confirmation(user)
|
||||
@user = user
|
||||
I18n.with_locale valid_locale(@user) do
|
||||
mail(to: user.email, from: from_address,
|
||||
subject: t(:welcome_to) + Spree::Config[:site_name])
|
||||
end
|
||||
end
|
||||
|
||||
# Overriding `Spree::UserMailer.confirmation_instructions` which is
|
||||
# overriding `Devise::Mailer.confirmation_instructions`.
|
||||
def confirmation_instructions(user, _opts)
|
||||
@user = user
|
||||
@instance = Spree::Config[:site_name]
|
||||
@contact = ContentConfig.footer_email
|
||||
|
||||
I18n.with_locale valid_locale(@user) do
|
||||
subject = t('spree.user_mailer.confirmation_instructions.subject')
|
||||
mail(to: confirmation_email_address,
|
||||
from: from_address,
|
||||
subject: subject)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def confirmation_email_address
|
||||
@user.pending_reconfirmation? ? @user.unconfirmed_email : @user.email
|
||||
end
|
||||
end
|
||||
@@ -48,7 +48,7 @@ class SubscriptionMailer < Spree::BaseMailer
|
||||
|
||||
def send_mail(order)
|
||||
I18n.with_locale valid_locale(order.user) do
|
||||
confirm_email_subject = t('order_mailer.confirm_email.subject')
|
||||
confirm_email_subject = t('spree.order_mailer.confirm_email.subject')
|
||||
subject = "#{Spree::Config[:site_name]} #{confirm_email_subject} ##{order.number}"
|
||||
mail(to: order.email,
|
||||
from: from_address,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
module LineItemBasedAdjustmentHandling
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :adjustments_for_which_source, class_name: "Spree::Adjustment", as: :source,
|
||||
dependent: :destroy
|
||||
end
|
||||
end
|
||||
@@ -116,9 +116,9 @@ class Enterprise < ActiveRecord::Base
|
||||
scope :not_ready_for_checkout, lambda {
|
||||
# When ready_for_checkout is empty, return all rows when there are no enterprises ready for
|
||||
# checkout.
|
||||
ready_enterprises = Enterprise.ready_for_checkout
|
||||
ready_enterprises = Enterprise.ready_for_checkout.select('enterprises.id')
|
||||
if ready_enterprises.present?
|
||||
where("id NOT IN (?)", ready_enterprises)
|
||||
where("enterprises.id NOT IN (?)", ready_enterprises)
|
||||
else
|
||||
where("TRUE")
|
||||
end
|
||||
@@ -165,14 +165,14 @@ class Enterprise < ActiveRecord::Base
|
||||
select('DISTINCT enterprises.*')
|
||||
}
|
||||
|
||||
scope :distributing_products, lambda { |products|
|
||||
scope :distributing_products, lambda { |product_ids|
|
||||
exchanges = joins("
|
||||
INNER JOIN exchanges
|
||||
ON (exchanges.receiver_id = enterprises.id AND exchanges.incoming = 'f')
|
||||
").
|
||||
joins('INNER JOIN exchange_variants ON (exchange_variants.exchange_id = exchanges.id)').
|
||||
joins('INNER JOIN spree_variants ON (spree_variants.id = exchange_variants.variant_id)').
|
||||
where('spree_variants.product_id IN (?)', products).select('DISTINCT enterprises.id')
|
||||
where('spree_variants.product_id IN (?)', product_ids).select('DISTINCT enterprises.id')
|
||||
|
||||
where(id: exchanges)
|
||||
}
|
||||
@@ -322,6 +322,14 @@ class Enterprise < ActiveRecord::Base
|
||||
select('DISTINCT spree_taxons.*')
|
||||
end
|
||||
|
||||
def current_distributed_taxons
|
||||
Spree::Taxon
|
||||
.select("DISTINCT spree_taxons.*")
|
||||
.joins(products: :variants_including_master)
|
||||
.joins("INNER JOIN (#{current_exchange_variants.to_sql}) \
|
||||
AS exchange_variants ON spree_variants.id = exchange_variants.variant_id")
|
||||
end
|
||||
|
||||
# Return all taxons for all supplied products
|
||||
def supplied_taxons
|
||||
Spree::Taxon.
|
||||
@@ -367,6 +375,14 @@ class Enterprise < ActiveRecord::Base
|
||||
|
||||
private
|
||||
|
||||
def current_exchange_variants
|
||||
ExchangeVariant.joins(exchange: :order_cycle)
|
||||
.merge(Exchange.outgoing)
|
||||
.select("DISTINCT exchange_variants.variant_id, exchanges.receiver_id AS enterprise_id")
|
||||
.where("exchanges.receiver_id = ?", id)
|
||||
.merge(OrderCycle.active.with_distributor(id))
|
||||
end
|
||||
|
||||
def name_is_unique
|
||||
dups = Enterprise.where(name: name)
|
||||
dups = dups.where('id != ?', id) unless new_record?
|
||||
@@ -449,7 +465,7 @@ class Enterprise < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def touch_distributors
|
||||
Enterprise.distributing_products(supplied_products).
|
||||
Enterprise.distributing_products(supplied_products.select(:id)).
|
||||
where('enterprises.id != ?', id).
|
||||
find_each(&:touch)
|
||||
end
|
||||
|
||||
@@ -25,8 +25,8 @@ class EnterpriseRelationship < ActiveRecord::Base
|
||||
where('parent_id IN (?) OR child_id IN (?)', enterprises, enterprises)
|
||||
}
|
||||
|
||||
scope :permitting, ->(enterprises) { where('child_id IN (?)', enterprises) }
|
||||
scope :permitted_by, ->(enterprises) { where('parent_id IN (?)', enterprises) }
|
||||
scope :permitting, ->(enterprise_ids) { where('child_id IN (?)', enterprise_ids) }
|
||||
scope :permitted_by, ->(enterprise_ids) { where('parent_id IN (?)', enterprise_ids) }
|
||||
|
||||
scope :with_permission, ->(permission) {
|
||||
joins(:permissions).
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
# Representation of an enterprise being part of an order cycle.
|
||||
#
|
||||
# A producer can be part as supplier. The supplier's products can be selected to
|
||||
# be available in the order cycle (incoming products).
|
||||
#
|
||||
# A selling enterprise can be part as distributor. The order cycle then appears
|
||||
# in its shopfront. Any incoming product can be selected to be shown in the
|
||||
# shopfront (outgoing products). But the set of shown products can be smaller
|
||||
# than all incoming products.
|
||||
class Exchange < ActiveRecord::Base
|
||||
acts_as_taggable
|
||||
|
||||
@@ -26,11 +35,25 @@ class Exchange < ActiveRecord::Base
|
||||
scope :to_enterprise, lambda { |enterprise| where(receiver_id: enterprise) }
|
||||
scope :from_enterprises, lambda { |enterprises| where('exchanges.sender_id IN (?)', enterprises) }
|
||||
scope :to_enterprises, lambda { |enterprises| where('exchanges.receiver_id IN (?)', enterprises) }
|
||||
scope :involving, lambda { |enterprises| where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises).select('DISTINCT exchanges.*') }
|
||||
scope :supplying_to, lambda { |distributor| where('exchanges.incoming OR exchanges.receiver_id = ?', distributor) }
|
||||
scope :with_variant, lambda { |variant| joins(:exchange_variants).where('exchange_variants.variant_id = ?', variant) }
|
||||
scope :with_any_variant, lambda { |variants| joins(:exchange_variants).where('exchange_variants.variant_id IN (?)', variants).select('DISTINCT exchanges.*') }
|
||||
scope :with_product, lambda { |product| joins(:exchange_variants).where('exchange_variants.variant_id IN (?)', product.variants_including_master) }
|
||||
scope :involving, lambda { |enterprises|
|
||||
where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises).
|
||||
select('DISTINCT exchanges.*')
|
||||
}
|
||||
scope :supplying_to, lambda { |distributor|
|
||||
where('exchanges.incoming OR exchanges.receiver_id = ?', distributor)
|
||||
}
|
||||
scope :with_variant, lambda { |variant|
|
||||
joins(:exchange_variants).where('exchange_variants.variant_id = ?', variant)
|
||||
}
|
||||
scope :with_any_variant, lambda { |variant_ids|
|
||||
joins(:exchange_variants).
|
||||
where(exchange_variants: { variant_id: variant_ids }).
|
||||
select('DISTINCT exchanges.*')
|
||||
}
|
||||
scope :with_product, lambda { |product|
|
||||
joins(:exchange_variants).
|
||||
where('exchange_variants.variant_id IN (?)', product.variants_including_master)
|
||||
}
|
||||
scope :by_enterprise_name, -> {
|
||||
joins('INNER JOIN enterprises AS sender ON (sender.id = exchanges.sender_id)').
|
||||
joins('INNER JOIN enterprises AS receiver ON (receiver.id = exchanges.receiver_id)').
|
||||
@@ -49,11 +72,12 @@ class Exchange < ActiveRecord::Base
|
||||
if user.has_spree_role?('admin')
|
||||
scoped
|
||||
else
|
||||
joins('LEFT JOIN enterprises senders ON senders.id = exchanges.sender_id').
|
||||
joins('LEFT JOIN enterprises receivers ON receivers.id = exchanges.receiver_id').
|
||||
joins('LEFT JOIN enterprise_roles sender_roles ON sender_roles.enterprise_id = senders.id').
|
||||
joins('LEFT JOIN enterprise_roles receiver_roles ON receiver_roles.enterprise_id = receivers.id').
|
||||
where('sender_roles.user_id = ? AND receiver_roles.user_id = ?', user.id, user.id)
|
||||
joins("LEFT JOIN enterprises senders ON senders.id = exchanges.sender_id").
|
||||
joins("LEFT JOIN enterprises receivers ON receivers.id = exchanges.receiver_id").
|
||||
joins("LEFT JOIN enterprise_roles sender_roles ON sender_roles.enterprise_id = senders.id").
|
||||
joins("LEFT JOIN enterprise_roles receiver_roles
|
||||
ON receiver_roles.enterprise_id = receivers.id").
|
||||
where("sender_roles.user_id = ? AND receiver_roles.user_id = ?", user.id, user.id)
|
||||
end
|
||||
}
|
||||
|
||||
@@ -75,20 +99,6 @@ class Exchange < ActiveRecord::Base
|
||||
incoming? ? sender : receiver
|
||||
end
|
||||
|
||||
def to_h(core_only = false)
|
||||
h = attributes.merge('variant_ids' => variant_ids.sort, 'enterprise_fee_ids' => enterprise_fee_ids.sort)
|
||||
h.reject! { |k| %w(id order_cycle_id created_at updated_at).include? k } if core_only
|
||||
h
|
||||
end
|
||||
|
||||
def eql?(e)
|
||||
if e.respond_to? :to_h
|
||||
to_h(true) == e.to_h(true)
|
||||
else
|
||||
super e
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_products_cache
|
||||
OpenFoodNetwork::ProductsCache.exchange_changed self
|
||||
end
|
||||
|
||||
@@ -27,17 +27,30 @@ class OrderCycle < ActiveRecord::Base
|
||||
|
||||
preference :product_selection_from_coordinator_inventory_only, :boolean, default: false
|
||||
|
||||
scope :active, lambda { where('order_cycles.orders_open_at <= ? AND order_cycles.orders_close_at >= ?', Time.zone.now, Time.zone.now) }
|
||||
scope :active, lambda {
|
||||
where('order_cycles.orders_open_at <= ? AND order_cycles.orders_close_at >= ?',
|
||||
Time.zone.now,
|
||||
Time.zone.now)
|
||||
}
|
||||
scope :active_or_complete, lambda { where('order_cycles.orders_open_at <= ?', Time.zone.now) }
|
||||
scope :inactive, lambda { where('order_cycles.orders_open_at > ? OR order_cycles.orders_close_at < ?', Time.zone.now, Time.zone.now) }
|
||||
scope :inactive, lambda {
|
||||
where('order_cycles.orders_open_at > ? OR order_cycles.orders_close_at < ?',
|
||||
Time.zone.now,
|
||||
Time.zone.now)
|
||||
}
|
||||
scope :upcoming, lambda { where('order_cycles.orders_open_at > ?', Time.zone.now) }
|
||||
scope :not_closed, lambda { where('order_cycles.orders_close_at > ? OR order_cycles.orders_close_at IS NULL', Time.zone.now) }
|
||||
scope :closed, lambda { where('order_cycles.orders_close_at < ?', Time.zone.now).order("order_cycles.orders_close_at DESC") }
|
||||
scope :not_closed, lambda {
|
||||
where('order_cycles.orders_close_at > ? OR order_cycles.orders_close_at IS NULL', Time.zone.now)
|
||||
}
|
||||
scope :closed, lambda {
|
||||
where('order_cycles.orders_close_at < ?',
|
||||
Time.zone.now).order("order_cycles.orders_close_at DESC")
|
||||
}
|
||||
scope :undated, -> { where('order_cycles.orders_open_at IS NULL OR orders_close_at IS NULL') }
|
||||
scope :dated, -> { where('orders_open_at IS NOT NULL AND orders_close_at IS NOT NULL') }
|
||||
|
||||
scope :soonest_closing, lambda { active.order('order_cycles.orders_close_at ASC') }
|
||||
# TODO This method returns all the closed orders. So maybe we can replace it with :recently_closed.
|
||||
# This scope returns all the closed orders
|
||||
scope :most_recently_closed, lambda { closed.order('order_cycles.orders_close_at DESC') }
|
||||
|
||||
scope :soonest_opening, lambda { upcoming.order('order_cycles.orders_open_at ASC') }
|
||||
@@ -52,7 +65,7 @@ class OrderCycle < ActiveRecord::Base
|
||||
if user.has_spree_role?('admin')
|
||||
scoped
|
||||
else
|
||||
where('coordinator_id IN (?)', user.enterprises)
|
||||
where('coordinator_id IN (?)', user.enterprises.map(&:id))
|
||||
end
|
||||
}
|
||||
|
||||
@@ -62,14 +75,17 @@ class OrderCycle < ActiveRecord::Base
|
||||
scoped
|
||||
else
|
||||
with_exchanging_enterprises_outer.
|
||||
where('order_cycles.coordinator_id IN (?) OR enterprises.id IN (?)', user.enterprises, user.enterprises).
|
||||
where('order_cycles.coordinator_id IN (?) OR enterprises.id IN (?)',
|
||||
user.enterprises.map(&:id),
|
||||
user.enterprises.map(&:id)).
|
||||
select('DISTINCT order_cycles.*')
|
||||
end
|
||||
}
|
||||
|
||||
scope :with_exchanging_enterprises_outer, lambda {
|
||||
joins('LEFT OUTER JOIN exchanges ON (exchanges.order_cycle_id = order_cycles.id)').
|
||||
joins('LEFT OUTER JOIN enterprises ON (enterprises.id = exchanges.sender_id OR enterprises.id = exchanges.receiver_id)')
|
||||
joins("LEFT OUTER JOIN exchanges ON (exchanges.order_cycle_id = order_cycles.id)").
|
||||
joins("LEFT OUTER JOIN enterprises
|
||||
ON (enterprises.id = exchanges.sender_id OR enterprises.id = exchanges.receiver_id)")
|
||||
}
|
||||
|
||||
scope :involving_managed_distributors_of, lambda { |user|
|
||||
@@ -78,7 +94,9 @@ class OrderCycle < ActiveRecord::Base
|
||||
# Order cycles where I managed an enterprise at either end of an outgoing exchange
|
||||
# ie. coordinator or distributor
|
||||
joins(:exchanges).merge(Exchange.outgoing).
|
||||
where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises).
|
||||
where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)',
|
||||
enterprises.pluck(:id),
|
||||
enterprises.pluck(:id)).
|
||||
select('DISTINCT order_cycles.*')
|
||||
}
|
||||
|
||||
@@ -88,7 +106,9 @@ class OrderCycle < ActiveRecord::Base
|
||||
# Order cycles where I managed an enterprise at either end of an incoming exchange
|
||||
# ie. coordinator or producer
|
||||
joins(:exchanges).merge(Exchange.incoming).
|
||||
where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises).
|
||||
where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)',
|
||||
enterprises.pluck(:id),
|
||||
enterprises.pluck(:id)).
|
||||
select('DISTINCT order_cycles.*')
|
||||
}
|
||||
|
||||
@@ -113,7 +133,8 @@ class OrderCycle < ActiveRecord::Base
|
||||
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').
|
||||
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] }
|
||||
]
|
||||
end
|
||||
@@ -123,7 +144,9 @@ class OrderCycle < ActiveRecord::Base
|
||||
oc.name = I18n.t("models.order_cycle.cloned_order_cycle_name", order_cycle: oc.name)
|
||||
oc.orders_open_at = oc.orders_close_at = nil
|
||||
oc.coordinator_fee_ids = coordinator_fee_ids
|
||||
# rubocop:disable Metrics/LineLength
|
||||
oc.preferred_product_selection_from_coordinator_inventory_only = preferred_product_selection_from_coordinator_inventory_only
|
||||
# rubocop:enable Metrics/LineLength
|
||||
oc.save!
|
||||
exchanges.each { |e| e.clone!(oc) }
|
||||
oc.reload
|
||||
@@ -217,7 +240,7 @@ class OrderCycle < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def exchanges_supplying(order)
|
||||
exchanges.supplying_to(order.distributor).with_any_variant(order.variants)
|
||||
exchanges.supplying_to(order.distributor).with_any_variant(order.variants.map(&:id))
|
||||
end
|
||||
|
||||
def coordinated_by?(user)
|
||||
@@ -229,8 +252,12 @@ class OrderCycle < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def items_bought_by_user(user, distributor)
|
||||
# The Spree::Order.complete scope only checks for completed_at date, does not ensure state is "complete"
|
||||
orders = Spree::Order.complete.where(state: "complete", user_id: user, distributor_id: distributor, order_cycle_id: self)
|
||||
# The Spree::Order.complete scope only checks for completed_at date
|
||||
# it does not ensure state is "complete"
|
||||
orders = Spree::Order.complete.where(state: "complete",
|
||||
user_id: user,
|
||||
distributor_id: distributor,
|
||||
order_cycle_id: self)
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
items = Spree::LineItem.joins(:order).merge(orders)
|
||||
items.each { |li| scoper.scope(li.variant) }
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
|
||||
module ProductImport
|
||||
class EntryValidator
|
||||
def initialize(current_user, import_time, spreadsheet_data, editable_enterprises, inventory_permissions, reset_counts, import_settings)
|
||||
SKIP_VALIDATE_ON_UPDATE = [:description].freeze
|
||||
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(current_user, import_time, spreadsheet_data, editable_enterprises,
|
||||
inventory_permissions, reset_counts, import_settings, all_entries)
|
||||
@current_user = current_user
|
||||
@import_time = import_time
|
||||
@spreadsheet_data = spreadsheet_data
|
||||
@@ -12,7 +16,9 @@ module ProductImport
|
||||
@inventory_permissions = inventory_permissions
|
||||
@reset_counts = reset_counts
|
||||
@import_settings = import_settings
|
||||
@all_entries = all_entries
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
def self.non_updatable_fields
|
||||
{
|
||||
@@ -30,6 +36,7 @@ module ProductImport
|
||||
assign_enterprise_field(entry)
|
||||
enterprise_validation(entry)
|
||||
unit_fields_validation(entry)
|
||||
variant_of_product_validation(entry)
|
||||
|
||||
next if entry.enterprise_id.blank?
|
||||
|
||||
@@ -158,6 +165,27 @@ module ProductImport
|
||||
mark_as_invalid(entry, attribute: 'variant_unit_name', error: I18n.t('admin.product_import.model.conditional_blank')) unless entry.variant_unit_name && entry.variant_unit_name.present?
|
||||
end
|
||||
|
||||
def variant_of_product_validation(entry)
|
||||
return if entry.producer.blank? || entry.name.blank?
|
||||
|
||||
validate_unit_type_unchanged(entry)
|
||||
validate_variant_unit_name_unchanged(entry)
|
||||
end
|
||||
|
||||
def validate_unit_type_unchanged(entry)
|
||||
return if entry.unit_type.blank?
|
||||
reference_entry = all_entries_for_product(entry).first
|
||||
return if entry.unit_type.to_s == reference_entry.unit_type.to_s
|
||||
mark_as_not_updatable(entry, "unit_type")
|
||||
end
|
||||
|
||||
def validate_variant_unit_name_unchanged(entry)
|
||||
return if entry.variant_unit_name.blank?
|
||||
reference_entry = all_entries_for_product(entry).first
|
||||
return if entry.variant_unit_name.to_s == reference_entry.variant_unit_name.to_s
|
||||
mark_as_not_updatable(entry, "variant_unit_name")
|
||||
end
|
||||
|
||||
def producer_validation(entry)
|
||||
producer_name = entry.producer
|
||||
|
||||
@@ -288,6 +316,7 @@ module ProductImport
|
||||
def product_field_errors(entry, existing_product)
|
||||
EntryValidator.non_updatable_fields.each do |display_name, attribute|
|
||||
next if attributes_match?(attribute, existing_product, entry) || attributes_blank?(attribute, existing_product, entry)
|
||||
next if ignore_when_updating_product?(attribute)
|
||||
mark_as_invalid(entry, attribute: display_name, error: I18n.t('admin.product_import.model.not_updatable'))
|
||||
end
|
||||
end
|
||||
@@ -298,6 +327,10 @@ module ProductImport
|
||||
existing_product_value == convert_to_trusted_type(entry_value, existing_product_value)
|
||||
end
|
||||
|
||||
def ignore_when_updating_product?(attribute)
|
||||
SKIP_VALIDATE_ON_UPDATE.include? attribute
|
||||
end
|
||||
|
||||
def convert_to_trusted_type(untrusted_attribute, trusted_attribute)
|
||||
case trusted_attribute
|
||||
when Integer
|
||||
@@ -332,6 +365,11 @@ module ProductImport
|
||||
entry.product_validations = options[:product_validations] if options[:product_validations]
|
||||
end
|
||||
|
||||
def mark_as_not_updatable(entry, attribute)
|
||||
mark_as_invalid(entry, attribute: attribute,
|
||||
error: I18n.t("admin.product_import.model.not_updatable"))
|
||||
end
|
||||
|
||||
def import_into_inventory?
|
||||
@import_settings[:settings].andand['import_into'] == 'inventories'
|
||||
end
|
||||
@@ -386,5 +424,19 @@ module ProductImport
|
||||
object.on_demand = object.count_on_hand.blank? if entry.on_demand.blank?
|
||||
entry.on_hand_nil = object.count_on_hand.blank?
|
||||
end
|
||||
|
||||
def all_entries_for_product(entry)
|
||||
all_entries_by_product[entries_by_product_key(entry)]
|
||||
end
|
||||
|
||||
def all_entries_by_product
|
||||
@all_entries_by_product ||= @all_entries.group_by do |entry|
|
||||
entries_by_product_key(entry)
|
||||
end
|
||||
end
|
||||
|
||||
def entries_by_product_key(entry)
|
||||
[entry.producer.to_s, entry.name.to_s]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ module ProductImport
|
||||
include ActiveModel::Conversion
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_reader :updated_ids, :import_settings
|
||||
attr_reader :entries, :updated_ids, :import_settings
|
||||
|
||||
def initialize(file, current_user, import_settings = {})
|
||||
unless file.is_a?(File)
|
||||
@@ -90,10 +90,6 @@ module ProductImport
|
||||
@processor.total_enterprise_products
|
||||
end
|
||||
|
||||
def all_entries
|
||||
@entries
|
||||
end
|
||||
|
||||
def entries_json
|
||||
entries = {}
|
||||
@entries.each do |entry|
|
||||
@@ -184,8 +180,11 @@ module ProductImport
|
||||
end
|
||||
|
||||
@spreadsheet_data = SpreadsheetData.new(@entries, @import_settings)
|
||||
@validator = EntryValidator.new(@current_user, @import_time, @spreadsheet_data, @editable_enterprises, @inventory_permissions, @reset_counts, @import_settings)
|
||||
@processor = EntryProcessor.new(self, @validator, @import_settings, @spreadsheet_data, @editable_enterprises, @import_time, @updated_ids)
|
||||
@validator = EntryValidator.new(@current_user, @import_time, @spreadsheet_data,
|
||||
@editable_enterprises, @inventory_permissions, @reset_counts,
|
||||
@import_settings, build_all_entries)
|
||||
@processor = EntryProcessor.new(self, @validator, @import_settings, @spreadsheet_data,
|
||||
@editable_enterprises, @import_time, @updated_ids)
|
||||
|
||||
@processor.count_existing_items unless staged_import?
|
||||
end
|
||||
@@ -238,28 +237,22 @@ module ProductImport
|
||||
end
|
||||
|
||||
def build_entries_in_range
|
||||
start_line = @import_settings[:start]
|
||||
end_line = @import_settings[:end]
|
||||
# In the JS, start and end are calculated like this:
|
||||
# start = (batchIndex * $scope.batchSize) + 1
|
||||
# end = (batchIndex + 1) * $scope.batchSize
|
||||
start_data_index = @import_settings[:start] - 1
|
||||
end_data_index = @import_settings[:end] - 1
|
||||
|
||||
(start_line..end_line).each do |i|
|
||||
line_number = i + 1
|
||||
row = @sheet.row(line_number)
|
||||
row_data = Hash[[headers, row].transpose]
|
||||
entry = SpreadsheetEntry.new(row_data)
|
||||
entry.line_number = line_number
|
||||
@entries.push entry
|
||||
break if @sheet.last_row == line_number
|
||||
end
|
||||
data_rows = rows[start_data_index..end_data_index]
|
||||
@entries = build_entries_from_rows(data_rows, start_data_index)
|
||||
end
|
||||
|
||||
def build_entries
|
||||
rows.each_with_index do |row, i|
|
||||
row_data = Hash[[headers, row].transpose]
|
||||
entry = SpreadsheetEntry.new(row_data)
|
||||
entry.line_number = i + 2
|
||||
@entries.push entry
|
||||
end
|
||||
@entries
|
||||
@entries = build_entries_from_rows(rows)
|
||||
end
|
||||
|
||||
def build_all_entries
|
||||
build_entries_from_rows(rows)
|
||||
end
|
||||
|
||||
def save_all_valid
|
||||
@@ -272,5 +265,14 @@ module ProductImport
|
||||
return unless @file.path == Rails.root.join('tmp', 'product_import').to_s
|
||||
File.delete(@file)
|
||||
end
|
||||
|
||||
def build_entries_from_rows(rows, offset = 0)
|
||||
rows.each_with_index.inject([]) do |entries, (row, i)|
|
||||
row_data = Hash[[headers, row].transpose]
|
||||
entry = SpreadsheetEntry.new(row_data)
|
||||
entry.line_number = offset + i + 2
|
||||
entries.push entry
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,6 +29,7 @@ Spree::AppConfiguration.class_eval do
|
||||
preference :matomo_site_id, :string, default: nil
|
||||
|
||||
# Invoices & Receipts
|
||||
preference :enable_invoices?, :boolean, default: true
|
||||
preference :invoice_style2?, :boolean, default: false
|
||||
preference :enable_receipt_printing?, :boolean, default: false
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ module Spree
|
||||
[object]
|
||||
elsif object.respond_to? :line_items
|
||||
object.line_items
|
||||
elsif object.order.present?
|
||||
elsif object.respond_to?(:order) && object.order.present?
|
||||
object.order.line_items
|
||||
else
|
||||
[object]
|
||||
|
||||
@@ -15,7 +15,7 @@ Spree::CreditCard.class_eval do
|
||||
belongs_to :user
|
||||
|
||||
after_create :ensure_single_default_card
|
||||
after_save :ensure_single_default_card, if: :is_default_changed?
|
||||
after_save :ensure_single_default_card, if: :default_card_needs_updating?
|
||||
|
||||
# Allows us to use a gateway_payment_profile_id to store Stripe Tokens
|
||||
# Should be able to remove once we reach Spree v2.2.0
|
||||
@@ -31,13 +31,21 @@ Spree::CreditCard.class_eval do
|
||||
|
||||
private
|
||||
|
||||
def reusable?
|
||||
gateway_customer_profile_id.present?
|
||||
end
|
||||
|
||||
def default_missing?
|
||||
!user.credit_cards.exists?(is_default: true)
|
||||
end
|
||||
|
||||
def default_card_needs_updating?
|
||||
is_default_changed? || gateway_customer_profile_id_changed?
|
||||
end
|
||||
|
||||
def ensure_single_default_card
|
||||
return unless user
|
||||
return unless is_default? || default_missing?
|
||||
return unless is_default? || (reusable? && default_missing?)
|
||||
user.credit_cards.update_all(['is_default=(id=?)', id])
|
||||
self.is_default = true
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user