mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-13 18:46:49 +00:00
Compare commits
313 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a61357be8 | ||
|
|
b2eeb5d687 | ||
|
|
ffeca41ef4 | ||
|
|
68c0936766 | ||
|
|
23b6267e30 | ||
|
|
6872ccfb72 | ||
|
|
b1b791e4ee | ||
|
|
1779b759f7 | ||
|
|
3ebbf5a55d | ||
|
|
83ce2ac3dc | ||
|
|
aaf850a095 | ||
|
|
3e9bf8aa1f | ||
|
|
95832c96ac | ||
|
|
8a700307d2 | ||
|
|
1756ddd0e9 | ||
|
|
fe71781d70 | ||
|
|
ec65951ef3 | ||
|
|
633c7737e4 | ||
|
|
f42bf72b32 | ||
|
|
8ab77b077b | ||
|
|
9b03833df0 | ||
|
|
fada30435f | ||
|
|
397729ed3d | ||
|
|
88312b8e4a | ||
|
|
9be3ff90f7 | ||
|
|
e66dea7e03 | ||
|
|
4c5ed2fa45 | ||
|
|
40c0e69f11 | ||
|
|
f3837fb6af | ||
|
|
db7b7bbde2 | ||
|
|
91da0114e3 | ||
|
|
ee301c5e2f | ||
|
|
4a67ffab25 | ||
|
|
4d060815d0 | ||
|
|
f91bd03c25 | ||
|
|
7de4ec2a90 | ||
|
|
812d8cac4a | ||
|
|
d1faed282f | ||
|
|
26195314eb | ||
|
|
912483660f | ||
|
|
d12a7c2c76 | ||
|
|
101cff02c6 | ||
|
|
5ce2af454e | ||
|
|
951787d456 | ||
|
|
b35743b6e4 | ||
|
|
4fc30ba50e | ||
|
|
97a401a307 | ||
|
|
232e3b8262 | ||
|
|
b3b66d5825 | ||
|
|
1b532f995c | ||
|
|
f5544494f0 | ||
|
|
2de32b54ba | ||
|
|
b6e2dadbb3 | ||
|
|
473e635d54 | ||
|
|
3e11cdb5c1 | ||
|
|
842e8da2c3 | ||
|
|
24f8e6d2ec | ||
|
|
568d58405d | ||
|
|
b23489fa40 | ||
|
|
503658f930 | ||
|
|
24bf5f0fea | ||
|
|
9d86249bcb | ||
|
|
3e37c8a3f1 | ||
|
|
691d7d735b | ||
|
|
6d1dd76590 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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
|
||||
@@ -429,7 +426,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
|
||||
@@ -493,6 +489,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 +566,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 +595,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 +640,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
|
||||
9
Gemfile
9
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,7 +20,9 @@ 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'
|
||||
@@ -122,7 +125,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'
|
||||
|
||||
34
Gemfile.lock
34
Gemfile.lock
@@ -34,13 +34,6 @@ GIT
|
||||
revision: 46d6f8f5fd434105b0c69958276d1727a3066a97
|
||||
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)
|
||||
@@ -83,8 +74,6 @@ GIT
|
||||
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
|
||||
@@ -160,6 +149,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)
|
||||
@@ -279,7 +270,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 +447,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 +483,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 +505,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)
|
||||
@@ -704,7 +695,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 +720,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 +776,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)
|
||||
@@ -817,7 +809,7 @@ DEPENDENCIES
|
||||
foundation-icons-sass-rails
|
||||
foundation-rails
|
||||
foundation_rails_helper!
|
||||
fuubar (~> 2.4.0)
|
||||
fuubar (~> 2.4.1)
|
||||
geocoder
|
||||
gmaps4rails
|
||||
guard
|
||||
@@ -826,7 +818,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 +860,10 @@ DEPENDENCIES
|
||||
simplecov
|
||||
skylight (< 2.0)
|
||||
spinjs-rails
|
||||
spree!
|
||||
spree_api!
|
||||
spree_auth_devise!
|
||||
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 |
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -4,102 +4,205 @@
|
||||
@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 {
|
||||
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;
|
||||
|
||||
.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 {
|
||||
list-style-type: none;
|
||||
padding-left: 0.5em;
|
||||
@@ -114,8 +217,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;
|
||||
}
|
||||
|
||||
@@ -194,10 +295,4 @@ nav {
|
||||
// padding required to placehold for fixed menu bar
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
section.right {
|
||||
.nav-branded {
|
||||
padding: 0 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -175,10 +175,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 +202,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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -52,7 +52,7 @@ Spree::Admin::BaseController.class_eval do
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
@@ -7,7 +7,7 @@ Spree::UserSessionsController.class_eval do
|
||||
if spree_user_signed_in?
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = t(:logged_in_succesfully)
|
||||
flash[:success] = t('devise.success.logged_in_succesfully')
|
||||
redirect_back_or_default(after_sign_in_path_for(spree_current_user))
|
||||
}
|
||||
format.js {
|
||||
|
||||
@@ -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,11 +106,7 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,9 @@
|
||||
|
||||
module ProductImport
|
||||
class EntryValidator
|
||||
def initialize(current_user, import_time, spreadsheet_data, editable_enterprises, inventory_permissions, reset_counts, import_settings)
|
||||
# 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 +14,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 +34,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 +163,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
|
||||
|
||||
@@ -332,6 +358,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 +417,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]
|
||||
|
||||
@@ -31,13 +31,17 @@ 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 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
|
||||
|
||||
@@ -166,7 +166,23 @@ Spree::Order.class_eval do
|
||||
shipments.each do |shipment|
|
||||
next if shipment.shipped?
|
||||
update_adjustment! shipment.adjustment if shipment.adjustment
|
||||
shipment.save # updates included tax
|
||||
save_or_rescue_shipment(shipment)
|
||||
end
|
||||
end
|
||||
|
||||
def save_or_rescue_shipment(shipment)
|
||||
shipment.save # updates included tax
|
||||
rescue ActiveRecord::RecordNotUnique => error
|
||||
# This error was seen in production on `shipment.save` above.
|
||||
# It caused lost payments and duplicate payments due to database rollbacks.
|
||||
# While we don't understand the cause of this error yet, we rescue here
|
||||
# because an outdated shipping fee is not as bad as a lost payment.
|
||||
# And the shipping fee is already up-to-date when this error occurs.
|
||||
# https://github.com/openfoodfoundation/openfoodnetwork/issues/3924
|
||||
Bugsnag.notify(error) do |report|
|
||||
report.add_tab(:order, attributes)
|
||||
report.add_tab(:shipment, shipment.attributes)
|
||||
report.add_tab(:shipment_in_db, Spree::Shipment.find_by_id(shipment.id).attributes)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ Spree::Product.class_eval do
|
||||
if user.has_spree_role?('admin')
|
||||
scoped
|
||||
else
|
||||
where('supplier_id IN (?)', user.enterprises)
|
||||
where('supplier_id IN (?)', user.enterprises.select("enterprises.id"))
|
||||
end
|
||||
}
|
||||
|
||||
@@ -189,7 +189,9 @@ Spree::Product.class_eval do
|
||||
OpenFoodNetwork::ProductsCache.product_deleted(self) do
|
||||
touch_distributors
|
||||
|
||||
ExchangeVariant.where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted).destroy_all
|
||||
ExchangeVariant.
|
||||
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted.
|
||||
select(:id)).destroy_all
|
||||
|
||||
destroy_without_delete_from_order_cycles
|
||||
end
|
||||
@@ -216,7 +218,7 @@ Spree::Product.class_eval do
|
||||
end
|
||||
|
||||
def touch_distributors
|
||||
Enterprise.distributing_products(self).each(&:touch)
|
||||
Enterprise.distributing_products(id).each(&:touch)
|
||||
end
|
||||
|
||||
def add_primary_taxon_to_taxons
|
||||
@@ -264,6 +266,7 @@ Spree::Product.class_eval do
|
||||
raise
|
||||
end
|
||||
|
||||
# Spree creates a permalink already but our implementation fixes an edge case.
|
||||
def sanitize_permalink
|
||||
if permalink.blank? || permalink_changed?
|
||||
requested = permalink.presence || permalink_was.presence || name.presence || 'product'
|
||||
|
||||
@@ -53,6 +53,13 @@ Spree::ShippingMethod.class_eval do
|
||||
spree_calculators.send model_name_without_spree_namespace
|
||||
end
|
||||
|
||||
# This is bypassing the validation of shipping method zones on checkout
|
||||
# It allows checkout using shipping methods without zones (see issue #3928 for details)
|
||||
# and it allows checkout with addresses outside of the zones of the selected shipping method
|
||||
def include?(address)
|
||||
address.present?
|
||||
end
|
||||
|
||||
def has_distributor?(distributor)
|
||||
distributors.include?(distributor)
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#
|
||||
module Stock
|
||||
class Package < Spree::Stock::Package
|
||||
# Returns all exsiting shipping categories.
|
||||
# Returns all existing shipping categories.
|
||||
# It does not filter by the shipping categories of the products in the order.
|
||||
# It allows checkout of products with categories that are not the shipping methods categories
|
||||
# It disables the matching of product shipping category with shipping method's category
|
||||
|
||||
@@ -14,7 +14,7 @@ module Api
|
||||
:long_description, :website, :instagram, :linkedin, :twitter,
|
||||
:facebook, :is_primary_producer, :is_distributor, :phone, :visible,
|
||||
:email_address, :hash, :logo, :promo_image, :path, :pickup, :delivery,
|
||||
:icon, :icon_font, :producer_icon_font, :category, :producers, :hubs
|
||||
:icon, :icon_font, :producer_icon_font, :category
|
||||
|
||||
attributes :taxons, :supplied_taxons
|
||||
|
||||
@@ -53,16 +53,6 @@ module Api
|
||||
enterprise_shop_path(enterprise)
|
||||
end
|
||||
|
||||
def producers
|
||||
relatives = data.relatives[enterprise.id]
|
||||
ids_to_objs(relatives.andand[:producers])
|
||||
end
|
||||
|
||||
def hubs
|
||||
relatives = data.relatives[enterprise.id]
|
||||
ids_to_objs(relatives.andand[:distributors])
|
||||
end
|
||||
|
||||
def taxons
|
||||
if active
|
||||
ids_to_objs data.current_distributed_taxons[enterprise.id]
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
module Api
|
||||
class EnterpriseShopfrontListSerializer < ActiveModel::Serializer
|
||||
attributes :name, :id, :latitude, :longitude, :is_primary_producer, :is_distributor,
|
||||
:visible, :path, :icon, :icon_font, :producer_icon_font
|
||||
:path, :icon, :icon_font, :producer_icon_font, :address_id, :sells,
|
||||
:permalink
|
||||
|
||||
has_one :address, serializer: Api::AddressSerializer
|
||||
|
||||
|
||||
@@ -51,7 +51,10 @@ module Api
|
||||
|
||||
def producers
|
||||
ActiveModel::ArraySerializer.new(
|
||||
enterprise.suppliers, each_serializer: Api::EnterpriseThinSerializer
|
||||
enterprise.plus_relatives_and_oc_producers(
|
||||
OrderCycle.not_closed.with_distributor(enterprise)
|
||||
),
|
||||
each_serializer: Api::EnterpriseThinSerializer
|
||||
)
|
||||
end
|
||||
|
||||
@@ -62,8 +65,10 @@ module Api
|
||||
end
|
||||
|
||||
def taxons
|
||||
taxons = active ? enterprise.current_distributed_taxons : enterprise.distributed_taxons
|
||||
|
||||
ActiveModel::ArraySerializer.new(
|
||||
enterprise.distributed_taxons, each_serializer: Api::TaxonSerializer
|
||||
taxons, each_serializer: Api::TaxonSerializer
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
16
app/serializers/api/group_list_serializer.rb
Normal file
16
app/serializers/api/group_list_serializer.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
module Api
|
||||
class GroupListSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :permalink, :email, :website, :facebook, :instagram,
|
||||
:linkedin, :twitter, :enterprises, :state, :address_id
|
||||
|
||||
def state
|
||||
object.address.state.abbr
|
||||
end
|
||||
|
||||
def enterprises
|
||||
ActiveModel::ArraySerializer.new(
|
||||
object.enterprises, each_serializer: Api::EnterpriseThinSerializer
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/services/exchange_variant_bulk_updater.rb
Normal file
36
app/services/exchange_variant_bulk_updater.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class ExchangeVariantBulkUpdater
|
||||
def initialize(exchange)
|
||||
@exchange = exchange
|
||||
end
|
||||
|
||||
def update!(variant_ids)
|
||||
sanitized_variant_ids = variant_ids.map(&:to_i).uniq
|
||||
existing_variant_ids = @exchange.variant_ids
|
||||
|
||||
disassociate_variants!(existing_variant_ids - sanitized_variant_ids)
|
||||
associate_variants!(sanitized_variant_ids - existing_variant_ids)
|
||||
|
||||
uncache_variant_associations
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def disassociate_variants!(variant_ids)
|
||||
return if variant_ids.blank?
|
||||
@exchange.exchange_variants.where(variant_id: variant_ids).delete_all
|
||||
end
|
||||
|
||||
def associate_variants!(variant_ids)
|
||||
return if variant_ids.blank?
|
||||
|
||||
new_exchange_variants = variant_ids.map do |variant_id|
|
||||
ExchangeVariant.new(exchange_id: @exchange.id, variant_id: variant_id)
|
||||
end
|
||||
ExchangeVariant.import!(new_exchange_variants)
|
||||
end
|
||||
|
||||
def uncache_variant_associations
|
||||
@exchange.exchange_variants.reset
|
||||
@exchange.variants.proxy_association.reset
|
||||
end
|
||||
end
|
||||
@@ -48,7 +48,7 @@ class OrderFactory
|
||||
attrs[:line_items].each do |li|
|
||||
next unless variant = Spree::Variant.find_by_id(li[:variant_id])
|
||||
scoper.scope(variant)
|
||||
li[:quantity] = stock_limited_quantity(variant.on_hand, li[:quantity])
|
||||
li[:quantity] = stock_limited_quantity(variant.on_demand, variant.on_hand, li[:quantity])
|
||||
li[:price] = variant.price
|
||||
build_item_from(li)
|
||||
end
|
||||
@@ -81,9 +81,9 @@ class OrderFactory
|
||||
@order.payments.create(payment_method_id: attrs[:payment_method_id], amount: @order.reload.total)
|
||||
end
|
||||
|
||||
def stock_limited_quantity(stock, requested)
|
||||
return requested if opts[:skip_stock_check]
|
||||
[stock, requested].min
|
||||
def stock_limited_quantity(variant_on_demand, variant_on_hand, requested)
|
||||
return requested if opts[:skip_stock_check] || variant_on_demand
|
||||
[variant_on_hand, requested].min
|
||||
end
|
||||
|
||||
def scoper
|
||||
|
||||
@@ -13,6 +13,6 @@ class UploadSanitizer
|
||||
private
|
||||
|
||||
def strip_bom_character
|
||||
@data.gsub("\xEF\xBB\xBF".force_encoding("UTF-8"), '')
|
||||
@data.scrub.gsub("\xEF\xBB\xBF".force_encoding("UTF-8"), '')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
%ofn-select{ :id => angular_id(:enterprise_id), data: 'enterprises', include_blank: true, ng: { model: 'enterprise_fee.enterprise_id' } }
|
||||
%input{ type: "hidden", name: angular_name(:enterprise_id), ng: { value: "enterprise_fee.enterprise_id" } }
|
||||
%td= f.ng_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type'
|
||||
%td= f.ng_text_field :name, { placeholder: 'e.g. packing fee' }
|
||||
%td= f.ng_text_field :name, { placeholder: t('.name_placeholder') }
|
||||
%td
|
||||
%ofn-select{ :id => angular_id(:tax_category_id), data: 'tax_categories', include_blank: true, ng: { model: 'enterprise_fee.tax_category_id' } }
|
||||
%input{ type: "hidden", name: angular_name(:tax_category_id), 'watch-tax-category' => true }
|
||||
|
||||
@@ -71,3 +71,10 @@
|
||||
.eight.columns.omega
|
||||
= surround spree.root_url, "/shop" do
|
||||
{{Enterprise.permalink}}
|
||||
.row
|
||||
.three.columns.alpha
|
||||
= f.label :id, t('.ofn_uid')
|
||||
%div{'ofn-with-tip' => t('.ofn_uid_tip')}
|
||||
%a= t('admin.whats_this')
|
||||
.six.columns
|
||||
{{Enterprise.id}}
|
||||
|
||||
@@ -77,16 +77,15 @@
|
||||
= f.radio_button :allow_order_changes, true, "ng-model" => "Enterprise.allow_order_changes", "ng-value" => "true"
|
||||
= f.label :allow_order_changes, t('.allow_order_changes_true'), value: :true
|
||||
|
||||
- if spree_current_user.admin?
|
||||
.row
|
||||
.alpha.eleven.columns
|
||||
.three.columns.alpha
|
||||
%label= t '.enable_subscriptions'
|
||||
%div{'ofn-with-tip' => t('.enable_subscriptions_tip')}
|
||||
%a= t 'admin.whats_this'
|
||||
.three.columns
|
||||
= f.radio_button :enable_subscriptions, true
|
||||
= f.label :enable_subscriptions, t('.enable_subscriptions_true'), value: :true
|
||||
.five.columns.omega
|
||||
= f.radio_button :enable_subscriptions, false
|
||||
= f.label :enable_subscriptions, t('.enable_subscriptions_false'), value: :false
|
||||
.row
|
||||
.alpha.eleven.columns
|
||||
.three.columns.alpha
|
||||
%label= t '.enable_subscriptions'
|
||||
%div{'ofn-with-tip' => t('.enable_subscriptions_tip')}
|
||||
%a= t 'admin.whats_this'
|
||||
.three.columns
|
||||
= f.radio_button :enable_subscriptions, true
|
||||
= f.label :enable_subscriptions, t('.enable_subscriptions_true'), value: :true
|
||||
.five.columns.omega
|
||||
= f.radio_button :enable_subscriptions, false
|
||||
= f.label :enable_subscriptions, t('.enable_subscriptions_false'), value: :false
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
= form_tag main_app.admin_invoice_settings_path, :method => :put do
|
||||
|
||||
.field.align-center
|
||||
= hidden_field_tag 'preferences[enable_invoices?]', '0'
|
||||
= check_box_tag 'preferences[enable_invoices?]', '1', Spree::Config[:enable_invoices?]
|
||||
= label_tag nil, t('.enable_invoices?')
|
||||
|
||||
.field.align-center
|
||||
= hidden_field_tag 'preferences[invoice_style2?]', '0'
|
||||
= check_box_tag 'preferences[invoice_style2?]', '1', Spree::Config[:invoice_style2?]
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
= select_tag 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', nil, {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', 'ng-model' => 'enterprise_fee.id', 'ng-options' => 'enterprise_fee.id as enterprise_fee.name for enterprise_fee in enterpriseFeesForEnterprise(enterprise_fee.enterprise_id)'}
|
||||
|
||||
= link_to 'Remove', '#', {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_remove', 'ng-click' => 'removeExchangeFee($event, exchange, $index)'}
|
||||
= link_to t('.remove'), '#', {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_remove', 'ng-click' => 'removeExchangeFee($event, exchange, $index)'}
|
||||
|
||||
= f.submit t('.add_fee'), 'ng-click' => 'addExchangeFee($event, exchange)', 'ng-hide' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
|
||||
%td.actions
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
= t :edit_order_cycle
|
||||
|
||||
- ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl'
|
||||
= admin_inject_order_cycle_instance
|
||||
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f|
|
||||
|
||||
%save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
%p.red
|
||||
{{ exception }}
|
||||
|
||||
= form_tag false, {class: 'product-import', name: 'importForm', 'ng-show' => 'step == "results"'} do
|
||||
= form_tag main_app.admin_product_import_path, {class: 'product-import', name: 'importForm', 'ng-show' => 'step == "results"'} do
|
||||
|
||||
= render 'import_review' if @importer.table_headings
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%fieldset.no-border-bottom
|
||||
%legend{ align: 'center'}= t("admin.subscriptions.form.details")
|
||||
%legend{ align: 'center'}= t(".details")
|
||||
.row
|
||||
.seven.columns.alpha.field
|
||||
%label{ for: 'customer_id'}= t('admin.customer')
|
||||
@@ -34,11 +34,11 @@
|
||||
.row
|
||||
.seven.columns.alpha.field
|
||||
%label{ for: 'begins_at'}= t('admin.begins_at')
|
||||
%input.fullwidth#begins_at{ name: 'begins_at', type: 'text', placeholder: 'Select A Date', datepicker: 'subscription.begins_at', required: true, ng: { model: 'subscription.begins_at' } }
|
||||
%input.fullwidth#begins_at{ name: 'begins_at', type: 'text', placeholder: "#{t('.begins_at_placeholder')}", datepicker: 'subscription.begins_at', required: true, ng: { model: 'subscription.begins_at' } }
|
||||
.error{ ng: { show: 'subscription_form.$submitted && subscription_details_form.begins_at.$error.required' } }= t(:error_required)
|
||||
.error{ ng: { repeat: 'error in errors.begins_at', show: 'subscription_details_form.begins_at.$pristine' } } {{ error }}
|
||||
.two.columns
|
||||
.seven.columns.omega.field
|
||||
%label{ for: 'ends_at'}= t('admin.ends_at')
|
||||
%input.fullwidth#ends_at{ name: 'ends_at', type: 'text', placeholder: 'Optional', datepicker: 'subscription.begins_at', ng: { model: 'subscription.ends_at' } }
|
||||
%input.fullwidth#ends_at{ name: 'ends_at', type: 'text', placeholder: "#{t('.ends_at_placeholder')}", datepicker: 'subscription.begins_at', ng: { model: 'subscription.ends_at' } }
|
||||
.error{ ng: { repeat: 'error in errors.ends_at', show: 'subscription_details_form.ends_at.$pristine' } } {{ error }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
.filter_select.five.columns.alpha
|
||||
%label{ :for => 'query', ng: {class: '{disabled: !shop_id}'} }=t('admin.quick_search')
|
||||
%br
|
||||
%input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!shop_id'}, :placeholder => "Search by email..." }
|
||||
%input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!shop_id'}, :placeholder => "#{t('.query_placeholder')}" }
|
||||
.filter_select.four.columns
|
||||
%label{ :for => 'shop_id', ng: { bind: "shop_id ? '#{t('admin.shop')}' : '#{t('admin.variant_overrides.index.select_a_shop')}'" } }
|
||||
%br
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
= render 'products'
|
||||
%a.button.update.fullwidth{ ng: { class: "{disabled: saved() && !saving, saving: saving}", click: "save()" } }
|
||||
%span{ ng: {hide: "saved() || saving" } }
|
||||
SAVE
|
||||
= t('.save')
|
||||
%i.icon-save
|
||||
%span{ ng: {show: "saved() && !saving" } }
|
||||
SAVED
|
||||
= t('.saved')
|
||||
%i.icon-ok-sign
|
||||
%span{ ng: {show: "saving" } }
|
||||
SAVING
|
||||
= t('.saving')
|
||||
%i.icon-refresh
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%fieldset.no-border-bottom
|
||||
%legend{ align: 'center'}= t("admin.subscriptions.form.review")
|
||||
%legend{ align: 'center'}= t(".details")
|
||||
.row
|
||||
.eight.columns.alpha
|
||||
.row
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
%p.alert-box.info
|
||||
= t '.message_html', cart: link_to(t('.cart'), "#{cart_path}#bought-products")
|
||||
= t '.message_html', cart: link_to(t('.cart'), "#{main_app.cart_path}#bought-products")
|
||||
|
||||
@@ -30,6 +30,6 @@
|
||||
%td.text-right {{ Checkout.cartTotal() | localizeCurrency }}
|
||||
|
||||
//= f.submit "Purchase", class: "button", "ofn-focus" => "accordion['payment']"
|
||||
%a.button.secondary{href: cart_url}
|
||||
%a.button.secondary{href: main_app.cart_url}
|
||||
%i.ofn-i_008-caret-left
|
||||
= t :checkout_back_to_cart
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
- content_for(:image) do
|
||||
= current_distributor.logo.url
|
||||
|
||||
= render partial: 'json/injection_ams', locals: enterprises
|
||||
= inject_enterprise_shopfront(@enterprise)
|
||||
|
||||
%shop.darkswarm
|
||||
- if @shopfront_layout == 'embedded'
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
- content_for(:title) do
|
||||
= t :groups_title
|
||||
|
||||
= inject_enterprises
|
||||
|
||||
:javascript
|
||||
angular.module('Darkswarm').value('groups', #{render partial: "json/groups", object: @groups})
|
||||
= inject_groups
|
||||
|
||||
#groups.pad-top.footer-pad{"ng-controller" => "GroupsCtrl"}
|
||||
.row
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
- content_for(:image) do
|
||||
= @group.logo.url
|
||||
|
||||
-# inject all enterprises as "enterprises"
|
||||
-# it could be more efficient to inject only the enterprises that are related to the group
|
||||
= inject_enterprises
|
||||
|
||||
-# inject enterprises in this group
|
||||
-# further hubs and producers of these enterprises can't be resolved within this small subset
|
||||
= inject_group_enterprises
|
||||
|
||||
#group-page.row.pad-top.footer-pad{"ng-controller" => "GroupPageCtrl"}
|
||||
@@ -68,8 +62,8 @@
|
||||
.small-12.columns
|
||||
.active_table
|
||||
%producer.active_table_node.row.animate-repeat{id: "{{producer.path}}",
|
||||
"ng-repeat" => "producer in filteredEnterprises = (group_producers | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties:'supplied_properties')",
|
||||
"ng-controller" => "GroupEnterpriseNodeCtrl",
|
||||
"ng-repeat" => "producer in filteredEnterprises = (Enterprises.producers | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties:'supplied_properties')",
|
||||
"ng-controller" => "ProducerNodeCtrl",
|
||||
"ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}",
|
||||
id: "{{producer.hash}}"}
|
||||
|
||||
@@ -89,15 +83,15 @@
|
||||
= t :groups_hubs
|
||||
|
||||
= render "shared/components/enterprise_search"
|
||||
= render "shops/filters", resource: "group_hubs", property_filters: "| searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles"
|
||||
= render "shops/filters", resource: "Enterprises.hubs", property_filters: "| searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes"
|
||||
|
||||
.row
|
||||
.small-12.columns
|
||||
.active_table
|
||||
%hub.active_table_node.row.animate-repeat{id: "{{hub.hash}}",
|
||||
"ng-repeat" => "hub in filteredEnterprises = (group_hubs | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | properties:activeProperties:'distributed_properties' | orderBy:['-active', '+orders_close_at'])",
|
||||
"ng-repeat" => "hub in filteredEnterprises = (Enterprises.hubs | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | properties:activeProperties:'distributed_properties' | orderBy:['-active', '+orders_close_at'])",
|
||||
"ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
|
||||
"ng-controller" => "GroupEnterpriseNodeCtrl"}
|
||||
"ng-controller" => "HubNodeCtrl"}
|
||||
.small-12.columns
|
||||
= render 'shops/skinny'
|
||||
= render 'shops/fat'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user