Compare commits

..

1 Commits

Author SHA1 Message Date
Rachel Arnould
b5020cc740 Update en.yml
Removing mentions about UFC Que choisir and changing the link
2024-11-25 13:31:36 +01:00
497 changed files with 6070 additions and 10195 deletions

View File

@@ -6,9 +6,9 @@
# cp .env.development .env.local # cp .env.development .env.local
# Locale for translation. Using a locale other than `en` tests the # Locale for translation. Using a locale other than `en` tests the
# successful fallback to `en`. To see up-to-date text used in production, # successful fallback to `en`. You will also see up-to-date text used
# set another locale in a local env file. # in production
LOCALE="en_TST" LOCALE="en_AU"
VERBOSE_QUERY_LOGS=true VERBOSE_QUERY_LOGS=true
@@ -16,9 +16,8 @@ SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
OFN_REDIS_URL="redis://localhost:6379/1" OFN_REDIS_URL="redis://localhost:6379/1"
OFN_REDIS_JOBS_URL="redis://localhost:6379/2" OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
OFN_REDIS_CABLE_URL="redis://localhost:6379/0"
SITE_URL="localhost:3000" SITE_URL="0.0.0.0:3000"
# Deactivate rack-timeout in development. # Deactivate rack-timeout in development.
# https://github.com/zombocom/rack-timeout#configuring # https://github.com/zombocom/rack-timeout#configuring

View File

@@ -2,7 +2,7 @@
# Override locally with `.env.test.local` # Override locally with `.env.test.local`
# Locale for translation. # Locale for translation.
LOCALE="en_TST" LOCALE="en_TEST"
OFN_REDIS_JOBS_URL="redis://localhost:6379/2" OFN_REDIS_JOBS_URL="redis://localhost:6379/2"

View File

@@ -38,10 +38,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on. # [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs. # Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1). # Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [4] ci_node_total: [8]
# Indexes for parallel jobs (starting from zero). # Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3] ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -66,7 +66,7 @@ jobs:
- name: Set up database - name: Set up database
run: | run: |
bin/rails db:create db:schema:load bin/rake db:create db:schema:load
- name: Run tests - name: Run tests
env: env:
@@ -84,7 +84,7 @@ jobs:
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}" KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}"
run: | run: |
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3) git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
bin/rails assets:precompile knapsack_pro:rspec bin/rake knapsack_pro:rspec
- name: Save SimpleCov file - name: Save SimpleCov file
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -116,10 +116,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on. # [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs. # Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1). # Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [2] ci_node_total: [4]
# Indexes for parallel jobs (starting from zero). # Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1] ci_node_index: [0, 1, 2, 3]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -135,7 +135,7 @@ jobs:
- name: Set up database - name: Set up database
run: | run: |
bin/rails db:create db:schema:load bin/rake db:create db:schema:load
- name: Run tests - name: Run tests
env: env:
@@ -184,10 +184,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on. # [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs. # Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1). # Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [10] ci_node_total: [14]
# Indexes for parallel jobs (starting from zero). # Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -211,7 +211,7 @@ jobs:
- name: Set up database - name: Set up database
run: | run: |
bin/rails db:create db:schema:load bin/rake db:create db:schema:load
- name: Run tests - name: Run tests
@@ -230,7 +230,7 @@ jobs:
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}" KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
run: | run: |
bin/rails assets:precompile knapsack_pro:queue:rspec bin/rake knapsack_pro:queue:rspec
- name: Save SimpleCov file - name: Save SimpleCov file
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -271,10 +271,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on. # [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs. # Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1). # Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [6] ci_node_total: [12]
# Indexes for parallel jobs (starting from zero). # Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5] ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -298,7 +298,7 @@ jobs:
- name: Set up database - name: Set up database
run: | run: |
bin/rails db:create db:schema:load bin/rake db:create db:schema:load
- name: Run tests - name: Run tests
@@ -317,7 +317,7 @@ jobs:
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}" KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}"
run: | run: |
bin/rails assets:precompile knapsack_pro:queue:rspec bin/rake knapsack_pro:queue:rspec
- name: Save SimpleCov file - name: Save SimpleCov file
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -386,7 +386,7 @@ jobs:
- name: Set up database - name: Set up database
run: | run: |
bin/rails db:create db:schema:load bin/rake db:create db:schema:load
- name: Run tests - name: Run tests
@@ -405,7 +405,7 @@ jobs:
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}" KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}"
run: | run: |
bin/rails assets:precompile knapsack_pro:rspec bin/rake knapsack_pro:rspec
- name: Save SimpleCov file - name: Save SimpleCov file
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -437,10 +437,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on. # [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs. # Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1). # Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [3] ci_node_total: [5]
# Indexes for parallel jobs (starting from zero). # Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2] ci_node_index: [0, 1, 2, 3, 4]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -465,7 +465,7 @@ jobs:
- name: Set up database - name: Set up database
run: | run: |
bin/rails db:create db:schema:load bin/rake db:create db:schema:load
- name: Run tests - name: Run tests
env: env:
@@ -482,7 +482,7 @@ jobs:
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true #KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}" KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}"
run: | run: |
bin/rails assets:precompile knapsack_pro:rspec bin/rake knapsack_pro:rspec
- name: Save SimpleCov file - name: Save SimpleCov file
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -553,6 +553,7 @@ jobs:
with: with:
pattern: simplecov-chunk-* pattern: simplecov-chunk-*
path: tmp/simplecov path: tmp/simplecov
merge-multiple: true
- name: collate results from each of the workers - name: collate results from each of the workers
run: bundle exec rake 'simplecov:collate_results[tmp/simplecov]' run: bundle exec rake 'simplecov:collate_results[tmp/simplecov]'

View File

@@ -3,14 +3,10 @@
# These are the rules we agreed upon and we work towards. # These are the rules we agreed upon and we work towards.
AllCops: AllCops:
NewCops: enable NewCops: enable
MigratedSchemaVersion: "20250111000000"
Exclude: Exclude:
- bin/**/* - bin/**/*
- db/**/*
- config/**/* - config/**/*
- db/bad_migrations/*
- db/migrate/201*
- db/migrate/202[0-4]*
- db/schema.rb
- script/**/* - script/**/*
- vendor/**/* - vendor/**/*
- node_modules/**/* - node_modules/**/*

View File

@@ -35,8 +35,15 @@ Lint/EmptyClass:
- 'spec/lib/reports/report_loader_spec.rb' - 'spec/lib/reports/report_loader_spec.rb'
# Offense count: 1 # Offense count: 1
# Configuration parameters: AllowComments.
Lint/EmptyFile:
Exclude:
- 'spec/lib/open_food_network/enterprise_injection_data_spec.rb'
# Offense count: 2
Lint/FloatComparison: Lint/FloatComparison:
Exclude: Exclude:
- 'app/models/product_import/entry_validator.rb'
- 'app/models/spree/gateway/pay_pal_express.rb' - 'app/models/spree/gateway/pay_pal_express.rb'
# Offense count: 1 # Offense count: 1
@@ -49,12 +56,13 @@ Lint/NoReturnInBeginEndBlocks:
Exclude: Exclude:
- 'app/controllers/payment_gateways/stripe_controller.rb' - 'app/controllers/payment_gateways/stripe_controller.rb'
# Offense count: 3 # Offense count: 4
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
Lint/RedundantDirGlobSort: Lint/RedundantDirGlobSort:
Exclude: Exclude:
- 'engines/catalog/spec/spec_helper.rb' - 'engines/catalog/spec/spec_helper.rb'
- 'engines/dfc_provider/spec/spec_helper.rb' - 'engines/dfc_provider/spec/spec_helper.rb'
- 'spec/base_spec_helper.rb'
- 'spec/system_helper.rb' - 'spec/system_helper.rb'
# Offense count: 1 # Offense count: 1
@@ -84,6 +92,7 @@ Metrics/AbcSize:
- 'app/controllers/admin/enterprises_controller.rb' - 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/payment_gateways/paypal_controller.rb' - 'app/controllers/payment_gateways/paypal_controller.rb'
- 'app/controllers/spree/admin/payments_controller.rb' - 'app/controllers/spree/admin/payments_controller.rb'
- 'app/controllers/spree/admin/taxons_controller.rb'
- 'app/controllers/spree/admin/variants_controller.rb' - 'app/controllers/spree/admin/variants_controller.rb'
- 'app/controllers/spree/orders_controller.rb' - 'app/controllers/spree/orders_controller.rb'
- 'app/helpers/spree/admin/navigation_helper.rb' - 'app/helpers/spree/admin/navigation_helper.rb'
@@ -118,7 +127,7 @@ Metrics/BlockNesting:
Exclude: Exclude:
- 'app/models/spree/payment/processing.rb' - 'app/models/spree/payment/processing.rb'
# Offense count: 47 # Offense count: 46
# Configuration parameters: CountComments, Max, CountAsOne. # Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ClassLength: Metrics/ClassLength:
Exclude: Exclude:
@@ -128,7 +137,6 @@ Metrics/ClassLength:
- 'app/controllers/admin/resource_controller.rb' - 'app/controllers/admin/resource_controller.rb'
- 'app/controllers/admin/subscriptions_controller.rb' - 'app/controllers/admin/subscriptions_controller.rb'
- 'app/controllers/application_controller.rb' - 'app/controllers/application_controller.rb'
- 'app/controllers/checkout_controller.rb'
- 'app/controllers/payment_gateways/paypal_controller.rb' - 'app/controllers/payment_gateways/paypal_controller.rb'
- 'app/controllers/spree/admin/orders_controller.rb' - 'app/controllers/spree/admin/orders_controller.rb'
- 'app/controllers/spree/admin/payment_methods_controller.rb' - 'app/controllers/spree/admin/payment_methods_controller.rb'
@@ -160,7 +168,6 @@ Metrics/ClassLength:
- 'app/services/cart_service.rb' - 'app/services/cart_service.rb'
- 'app/services/order_cycles/form_service.rb' - 'app/services/order_cycles/form_service.rb'
- 'app/services/orders/sync_service.rb' - 'app/services/orders/sync_service.rb'
- 'app/services/sets/product_set.rb'
- 'engines/order_management/app/services/order_management/order/updater.rb' - 'engines/order_management/app/services/order_management/order/updater.rb'
- 'lib/open_food_network/enterprise_fee_calculator.rb' - 'lib/open_food_network/enterprise_fee_calculator.rb'
- 'lib/open_food_network/order_cycle_form_applicator.rb' - 'lib/open_food_network/order_cycle_form_applicator.rb'
@@ -171,12 +178,12 @@ Metrics/ClassLength:
- 'lib/reporting/reports/enterprise_fee_summary/scope.rb' - 'lib/reporting/reports/enterprise_fee_summary/scope.rb'
- 'lib/reporting/reports/xero_invoices/base.rb' - 'lib/reporting/reports/xero_invoices/base.rb'
# Offense count: 30 # Offense count: 32
# Configuration parameters: AllowedMethods, AllowedPatterns, Max. # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
Metrics/CyclomaticComplexity: Metrics/CyclomaticComplexity:
Exclude: Exclude:
- 'app/controllers/admin/enterprises_controller.rb' - 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/spree/admin/payments_controller.rb' - 'app/controllers/spree/admin/taxons_controller.rb'
- 'app/controllers/spree/orders_controller.rb' - 'app/controllers/spree/orders_controller.rb'
- 'app/helpers/checkout_helper.rb' - 'app/helpers/checkout_helper.rb'
- 'app/helpers/order_cycles_helper.rb' - 'app/helpers/order_cycles_helper.rb'
@@ -192,6 +199,7 @@ Metrics/CyclomaticComplexity:
- 'app/models/spree/preferences/preferable_class_methods.rb' - 'app/models/spree/preferences/preferable_class_methods.rb'
- 'app/models/spree/return_authorization.rb' - 'app/models/spree/return_authorization.rb'
- 'app/models/spree/tax_rate.rb' - 'app/models/spree/tax_rate.rb'
- 'app/models/spree/variant.rb'
- 'app/models/spree/zone.rb' - 'app/models/spree/zone.rb'
- 'lib/open_food_network/enterprise_issue_validator.rb' - 'lib/open_food_network/enterprise_issue_validator.rb'
- 'lib/reporting/reports/xero_invoices/base.rb' - 'lib/reporting/reports/xero_invoices/base.rb'
@@ -200,12 +208,13 @@ Metrics/CyclomaticComplexity:
- 'lib/spree/localized_number.rb' - 'lib/spree/localized_number.rb'
- 'spec/models/product_importer_spec.rb' - 'spec/models/product_importer_spec.rb'
# Offense count: 23 # Offense count: 24
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength: Metrics/MethodLength:
Exclude: Exclude:
- 'app/controllers/admin/enterprises_controller.rb' - 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/payment_gateways/paypal_controller.rb' - 'app/controllers/payment_gateways/paypal_controller.rb'
- 'app/controllers/spree/admin/taxons_controller.rb'
- 'app/controllers/spree/orders_controller.rb' - 'app/controllers/spree/orders_controller.rb'
- 'app/helpers/spree/admin/navigation_helper.rb' - 'app/helpers/spree/admin/navigation_helper.rb'
- 'app/models/spree/ability.rb' - 'app/models/spree/ability.rb'
@@ -284,17 +293,19 @@ Metrics/ParameterLists:
- 'spec/support/controller_requests_helper.rb' - 'spec/support/controller_requests_helper.rb'
- 'spec/system/admin/reports_spec.rb' - 'spec/system/admin/reports_spec.rb'
# Offense count: 3 # Offense count: 4
# Configuration parameters: AllowedMethods, AllowedPatterns, Max. # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Exclude: Exclude:
- 'app/controllers/spree/admin/taxons_controller.rb'
- 'app/models/enterprise_relationship.rb' - 'app/models/enterprise_relationship.rb'
- 'app/models/spree/ability.rb' - 'app/models/spree/ability.rb'
- 'app/models/spree/order/checkout.rb' - 'app/models/spree/order/checkout.rb'
# Offense count: 7 # Offense count: 8
Naming/AccessorMethodName: Naming/AccessorMethodName:
Exclude: Exclude:
- 'app/controllers/spree/admin/taxonomies_controller.rb'
- 'app/mailers/producer_mailer.rb' - 'app/mailers/producer_mailer.rb'
- 'app/models/spree/order.rb' - 'app/models/spree/order.rb'
- 'app/services/checkout/post_checkout_actions.rb' - 'app/services/checkout/post_checkout_actions.rb'
@@ -326,7 +337,7 @@ Naming/MethodParameterName:
Exclude: Exclude:
- 'app/services/process_payment_intent.rb' - 'app/services/process_payment_intent.rb'
# Offense count: 26 # Offense count: 28
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
# SupportedStyles: snake_case, normalcase, non_integer # SupportedStyles: snake_case, normalcase, non_integer
# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 # AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
@@ -337,11 +348,12 @@ Naming/VariableNumber:
- 'app/models/preference_sections/main_links_section.rb' - 'app/models/preference_sections/main_links_section.rb'
- 'lib/spree/core/controller_helpers/common.rb' - 'lib/spree/core/controller_helpers/common.rb'
- 'spec/controllers/spree/admin/search_controller_spec.rb' - 'spec/controllers/spree/admin/search_controller_spec.rb'
- 'spec/factories/stock_location_factory.rb'
- 'spec/models/spree/stock_item_spec.rb' - 'spec/models/spree/stock_item_spec.rb'
- 'spec/models/spree/tax_rate_spec.rb' - 'spec/models/spree/tax_rate_spec.rb'
- 'spec/requests/api/orders_spec.rb' - 'spec/requests/api/orders_spec.rb'
# Offense count: 144 # Offense count: 142
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: ResponseMethods. # Configuration parameters: ResponseMethods.
# ResponseMethods: response, last_response # ResponseMethods: response, last_response
@@ -545,7 +557,7 @@ RSpecRails/InferredSpecType:
- 'spec/requests/voucher_adjustments_spec.rb' - 'spec/requests/voucher_adjustments_spec.rb'
- 'spec/routing/stripe_spec.rb' - 'spec/routing/stripe_spec.rb'
# Offense count: 21 # Offense count: 22
# Configuration parameters: IgnoreScopes, Include. # Configuration parameters: IgnoreScopes, Include.
# Include: app/models/**/*.rb # Include: app/models/**/*.rb
Rails/InverseOf: Rails/InverseOf:
@@ -560,6 +572,7 @@ Rails/InverseOf:
- 'app/models/spree/price.rb' - 'app/models/spree/price.rb'
- 'app/models/spree/product.rb' - 'app/models/spree/product.rb'
- 'app/models/spree/stock_item.rb' - 'app/models/spree/stock_item.rb'
- 'app/models/spree/taxonomy.rb'
- 'app/models/spree/variant.rb' - 'app/models/spree/variant.rb'
- 'app/models/subscription_line_item.rb' - 'app/models/subscription_line_item.rb'
@@ -584,9 +597,30 @@ Rails/LexicallyScopedActionFilter:
- 'app/controllers/spree/admin/return_authorizations_controller.rb' - 'app/controllers/spree/admin/return_authorizations_controller.rb'
- 'app/controllers/spree/admin/search_controller.rb' - 'app/controllers/spree/admin/search_controller.rb'
- 'app/controllers/spree/admin/shipping_methods_controller.rb' - 'app/controllers/spree/admin/shipping_methods_controller.rb'
- 'app/controllers/spree/admin/users_controller.rb'
- 'app/controllers/spree/admin/zones_controller.rb' - 'app/controllers/spree/admin/zones_controller.rb'
- 'app/controllers/spree/users_controller.rb' - 'app/controllers/spree/users_controller.rb'
# Offense count: 56
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Include.
# Include: spec/controllers/**/*.rb, spec/requests/**/*.rb, test/controllers/**/*.rb, test/integration/**/*.rb
Rails/ResponseParsedBody:
Exclude:
- 'spec/controllers/admin/bulk_line_items_controller_spec.rb'
- 'spec/controllers/admin/customers_controller_spec.rb'
- 'spec/controllers/admin/order_cycles_controller_spec.rb'
- 'spec/controllers/admin/proxy_orders_controller_spec.rb'
- 'spec/controllers/admin/schedules_controller_spec.rb'
- 'spec/controllers/admin/stripe_accounts_controller_spec.rb'
- 'spec/controllers/admin/subscription_line_items_controller_spec.rb'
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
- 'spec/controllers/cart_controller_spec.rb'
- 'spec/controllers/line_items_controller_spec.rb'
- 'spec/controllers/spree/admin/search_controller_spec.rb'
- 'spec/controllers/spree/credit_cards_controller_spec.rb'
- 'spec/controllers/user_registrations_controller_spec.rb'
# Offense count: 7 # Offense count: 7
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.
@@ -686,7 +720,7 @@ Style/GlobalStdStream:
- 'lib/tasks/subscriptions/debug.rake' - 'lib/tasks/subscriptions/debug.rake'
- 'lib/tasks/subscriptions/test.rake' - 'lib/tasks/subscriptions/test.rake'
# Offense count: 10 # Offense count: 12
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowSplatArgument. # Configuration parameters: AllowSplatArgument.
Style/HashConversion: Style/HashConversion:
@@ -694,7 +728,9 @@ Style/HashConversion:
- 'app/controllers/admin/column_preferences_controller.rb' - 'app/controllers/admin/column_preferences_controller.rb'
- 'app/controllers/admin/variant_overrides_controller.rb' - 'app/controllers/admin/variant_overrides_controller.rb'
- 'app/controllers/spree/admin/products_controller.rb' - 'app/controllers/spree/admin/products_controller.rb'
- 'app/models/order_cycle.rb'
- 'app/models/product_import/product_importer.rb' - 'app/models/product_import/product_importer.rb'
- 'app/models/spree/shipping_method.rb'
- 'app/serializers/api/admin/exchange_serializer.rb' - 'app/serializers/api/admin/exchange_serializer.rb'
- 'app/services/variants_stock_levels.rb' - 'app/services/variants_stock_levels.rb'
- 'spec/controllers/admin/inventory_items_controller_spec.rb' - 'spec/controllers/admin/inventory_items_controller_spec.rb'

View File

@@ -1,32 +1,92 @@
FROM ruby:3.1.4-alpine3.19 AS base FROM ubuntu:20.04
ENV LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
TZ=Europe/London \
RAILS_ROOT=/usr/src/app
RUN apk --no-cache upgrade && \
apk add --no-cache tzdata postgresql-client imagemagick imagemagick-jpeg && \
apk add --no-cache --virtual wkhtmltopdf
WORKDIR $RAILS_ROOT ENV TZ Europe/London
# Development dependencies RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
FROM base AS development-base
RUN apk add --no-cache --virtual .build-deps \
build-base postgresql-dev git nodejs yarn && \
apk add --no-cache --virtual .dev-utils \
bash curl less vim chromium-chromedriver zlib-dev openssl-dev \
readline-dev yaml-dev sqlite-dev libxml2-dev libxslt-dev libffi-dev vips-dev && \
curl -o /usr/local/bin/wait-for-it https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh && \
chmod +x /usr/local/bin/wait-for-it
# Install yarn dependencies separately for caching RUN echo "deb http://security.ubuntu.com/ubuntu bionic-security main" >> /etc/apt/sources.list
FROM development-base AS yarn-dependencies
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# Install Ruby gems # Install all the requirements
FROM development-base RUN apt-get update && apt-get install -y \
COPY . $RAILS_ROOT curl \
COPY Gemfile Gemfile.lock ./ git \
RUN bundle install --jobs "$(nproc)" build-essential \
COPY --from=yarn-dependencies $RAILS_ROOT/node_modules ./node_modules software-properties-common \
wget \
zlib1g-dev \
libreadline-dev \
libyaml-dev \
libffi-dev \
libxml2-dev \
libxslt1-dev \
wait-for-it \
imagemagick \
unzip \
libjemalloc-dev \
libssl-dev \
ca-certificates \
gnupg
# Setup ENV variables
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:/usr/local/src/nodenv/shims:/usr/local/src/nodenv/bin:$PATH
ENV RBENV_ROOT /usr/local/src/rbenv
ENV NODENV_ROOT /usr/local/src/nodenv
ENV CONFIGURE_OPTS --disable-install-doc
ENV BUNDLE_PATH /bundles
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so
WORKDIR /usr/src/app
# trim spaces and line return from .ruby-version file
COPY .ruby-version .ruby-version.raw
RUN cat .ruby-version.raw | tr -d '\r\t ' > .ruby-version
# Install Rbenv & Ruby
RUN git clone --depth 1 https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
git clone --depth 1 https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh && \
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install $(cat .ruby-version) && \
rbenv global $(cat .ruby-version)
# Install Postgres
RUN sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main' >> /etc/apt/sources.list.d/pgdg.list" && \
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null && \
apt-get update && \
apt-get install -yqq --no-install-recommends postgresql-client-10 libpq-dev
# trim spaces and line return from .node-version file
COPY .node-version .node-version.raw
RUN cat .node-version.raw | tr -d '\r\t ' > .node-version
# Install Node and Yarn with Nodenv
RUN git clone --depth 1 https://github.com/nodenv/nodenv.git ${NODENV_ROOT} && \
git clone --depth 1 https://github.com/nodenv/node-build.git ${NODENV_ROOT}/plugins/node-build && \
git clone --depth 1 https://github.com/pine/nodenv-yarn-install.git ${NODENV_ROOT}/plugins/nodenv-yarn-install && \
git clone --depth 1 https://github.com/nodenv/nodenv-package-rehash.git ${NODENV_ROOT}/plugins/nodenv-package-rehash && \
echo 'eval "$(nodenv init -)"' >> /etc/profile.d/nodenv.sh && \
nodenv install $(cat .node-version) && \
nodenv global $(cat .node-version)
# Install Chrome
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
sh -c "echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google-chrome.list" && \
apt-get update && \
apt-get install -fy google-chrome-stable
# Install Chromedriver
RUN wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip && \
unzip chromedriver_linux64.zip -d /usr/bin && \
chmod u+x /usr/bin/chromedriver
# Copy code and install app dependencies
COPY . /usr/src/app/
# Install Bundler
RUN ./script/install-bundler
# Install front-end dependencies
RUN yarn install
# Run bundler install in parallel with the amount of available CPUs
RUN bundle install --jobs="$(nproc)"

View File

@@ -1,92 +0,0 @@
FROM ubuntu:20.04
ENV TZ Europe/London
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN echo "deb http://security.ubuntu.com/ubuntu bionic-security main" >> /etc/apt/sources.list
# Install all the requirements
RUN apt-get update && apt-get install -y \
curl \
git \
build-essential \
software-properties-common \
wget \
zlib1g-dev \
libreadline-dev \
libyaml-dev \
libffi-dev \
libxml2-dev \
libxslt1-dev \
wait-for-it \
imagemagick \
unzip \
libjemalloc-dev \
libssl-dev \
ca-certificates \
gnupg
# Setup ENV variables
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:/usr/local/src/nodenv/shims:/usr/local/src/nodenv/bin:$PATH
ENV RBENV_ROOT /usr/local/src/rbenv
ENV NODENV_ROOT /usr/local/src/nodenv
ENV CONFIGURE_OPTS --disable-install-doc
ENV BUNDLE_PATH /bundles
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so
WORKDIR /usr/src/app
# trim spaces and line return from .ruby-version file
COPY .ruby-version .ruby-version.raw
RUN cat .ruby-version.raw | tr -d '\r\t ' > .ruby-version
# Install Rbenv & Ruby
RUN git clone --depth 1 https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
git clone --depth 1 https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh && \
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install $(cat .ruby-version) && \
rbenv global $(cat .ruby-version)
# Install Postgres
RUN sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main' >> /etc/apt/sources.list.d/pgdg.list" && \
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null && \
apt-get update && \
apt-get install -yqq --no-install-recommends postgresql-client-10 libpq-dev
# trim spaces and line return from .node-version file
COPY .node-version .node-version.raw
RUN cat .node-version.raw | tr -d '\r\t ' > .node-version
# Install Node and Yarn with Nodenv
RUN git clone --depth 1 https://github.com/nodenv/nodenv.git ${NODENV_ROOT} && \
git clone --depth 1 https://github.com/nodenv/node-build.git ${NODENV_ROOT}/plugins/node-build && \
git clone --depth 1 https://github.com/pine/nodenv-yarn-install.git ${NODENV_ROOT}/plugins/nodenv-yarn-install && \
git clone --depth 1 https://github.com/nodenv/nodenv-package-rehash.git ${NODENV_ROOT}/plugins/nodenv-package-rehash && \
echo 'eval "$(nodenv init -)"' >> /etc/profile.d/nodenv.sh && \
nodenv install $(cat .node-version) && \
nodenv global $(cat .node-version)
# Install Chrome
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
sh -c "echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google-chrome.list" && \
apt-get update && \
apt-get install -fy google-chrome-stable
# Install Chromedriver
RUN wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip && \
unzip chromedriver_linux64.zip -d /usr/bin && \
chmod u+x /usr/bin/chromedriver
# Copy code and install app dependencies
COPY . /usr/src/app/
# Install Bundler
RUN ./script/install-bundler
# Install front-end dependencies
RUN yarn install
# Run bundler install in parallel with the amount of available CPUs
RUN bundle install --jobs="$(nproc)"

View File

@@ -86,7 +86,7 @@ gem "active_model_serializers", "0.8.4"
gem 'activerecord-session_store' gem 'activerecord-session_store'
gem 'acts-as-taggable-on' gem 'acts-as-taggable-on'
gem 'angularjs-file-upload-rails', '~> 2.4.1' gem 'angularjs-file-upload-rails', '~> 2.4.1'
gem 'bigdecimal' gem 'bigdecimal', '3.0.2'
gem 'bootsnap', require: false gem 'bootsnap', require: false
gem 'geocoder' gem 'geocoder'
gem 'gmaps4rails' gem 'gmaps4rails'

View File

@@ -180,7 +180,7 @@ GEM
base64 (0.2.0) base64 (0.2.0)
bcp47_spec (0.2.1) bcp47_spec (0.2.1)
bcrypt (3.1.20) bcrypt (3.1.20)
bigdecimal (3.1.8) bigdecimal (3.0.2)
bindata (2.5.0) bindata (2.5.0)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.18.3) bootsnap (1.18.3)
@@ -191,7 +191,7 @@ GEM
bullet (7.1.6) bullet (7.1.6)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.11) uniform_notifier (~> 1.11)
cable_ready (5.0.6) cable_ready (5.0.5)
actionpack (>= 5.2) actionpack (>= 5.2)
actionview (>= 5.2) actionview (>= 5.2)
activesupport (>= 5.2) activesupport (>= 5.2)
@@ -246,7 +246,7 @@ GEM
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
datafoodconsortium-connector (1.1.0) datafoodconsortium-connector (1.0.0.pre.alpha.13)
virtual_assembly-semantizer (~> 1.0, >= 1.0.5) virtual_assembly-semantizer (~> 1.0, >= 1.0.5)
date (3.3.4) date (3.3.4)
debug (1.9.2) debug (1.9.2)
@@ -522,7 +522,7 @@ GEM
railties (>= 4.2) railties (>= 4.2)
raabro (1.4.0) raabro (1.4.0)
racc (1.8.0) racc (1.8.0)
rack (2.2.11) rack (2.2.9)
rack-mini-profiler (2.3.4) rack-mini-profiler (2.3.4)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-oauth2 (2.2.1) rack-oauth2 (2.2.1)
@@ -593,9 +593,8 @@ GEM
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rdf (3.3.2) rdf (3.3.1)
bcp47_spec (~> 0.2) bcp47_spec (~> 0.2)
bigdecimal (~> 3.1, >= 3.1.5)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
rdoc (6.6.3.1) rdoc (6.6.3.1)
psych (>= 4.0.0) psych (>= 4.0.0)
@@ -612,8 +611,8 @@ GEM
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
rexml (3.2.9) rexml (3.2.8)
strscan strscan (>= 3.0.9)
roadie (5.2.1) roadie (5.2.1)
css_parser (~> 1.4) css_parser (~> 1.4)
nokogiri (~> 1.15) nokogiri (~> 1.15)
@@ -684,10 +683,10 @@ GEM
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-factory_bot (2.25.1) rubocop-factory_bot (2.25.1)
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-rails (2.28.0) rubocop-rails (2.24.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.52.0, < 2.0) rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (2.29.2) rubocop-rspec (2.29.2)
rubocop (~> 1.40) rubocop (~> 1.40)
@@ -756,21 +755,21 @@ GEM
state_machines-activerecord (0.9.0) state_machines-activerecord (0.9.0)
activerecord (>= 6.0) activerecord (>= 6.0)
state_machines-activemodel (>= 0.9.0) state_machines-activemodel (>= 0.9.0)
stimulus_reflex (3.5.3) stimulus_reflex (3.5.1)
actioncable (>= 5.2) actioncable (>= 5.2, < 8)
actionpack (>= 5.2) actionpack (>= 5.2, < 8)
actionview (>= 5.2) actionview (>= 5.2, < 8)
activesupport (>= 5.2) activesupport (>= 5.2, < 8)
cable_ready (~> 5.0) cable_ready (~> 5.0)
nokogiri (~> 1.0) nokogiri (~> 1.0)
nokogiri-html5-inference (~> 0.3) nokogiri-html5-inference (~> 0.3)
rack (>= 2, < 4) rack (>= 2, < 4)
railties (>= 5.2) railties (>= 5.2, < 8)
redis (>= 4.0, < 6.0) redis (>= 4.0, < 6.0)
stringex (2.8.6) stringex (2.8.6)
stringio (3.1.0) stringio (3.1.0)
stripe (11.1.0) stripe (11.1.0)
strscan (3.1.2) strscan (3.1.0)
swd (2.0.3) swd (2.0.3)
activesupport (>= 3) activesupport (>= 3)
attr_required (>= 0.0.5) attr_required (>= 0.0.5)
@@ -866,7 +865,7 @@ DEPENDENCIES
angularjs-rails (= 1.8.0) angularjs-rails (= 1.8.0)
arel-helpers (~> 2.12) arel-helpers (~> 2.12)
aws-sdk-s3 aws-sdk-s3
bigdecimal bigdecimal (= 3.0.2)
bootsnap bootsnap
bugsnag bugsnag
bullet bullet

View File

@@ -1,5 +0,0 @@
# Foreman Procfile for Docker env. Start all dev server processes with: `bundle exec foreman start -f Procfile.docker`
webpack: WEBPACKER_DEV_SERVER_HOST=0.0.0.0 ./bin/webpack-dev-server
sidekiq: DEV_CACHING=true bundle exec sidekiq -q mailers -q default
rails: WEBPACKER_DEV_SERVER_HOST=0.0.0.0 DEV_CACHING=true bundle exec rails s -p 3000 -b 0.0.0.0

View File

@@ -67,5 +67,8 @@
// foundation // foundation
//= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js //= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js
// LocalStorage
//= require ../shared/angular-local-storage.js
// requires the rest of the JS code in this folder // requires the rest of the JS code in this folder
//= require_tree . //= require_tree .

View File

@@ -307,6 +307,9 @@ filterSubmitProducts = (productsToFilter) ->
variantHasUpdatableProperty = result.hasUpdatableProperty variantHasUpdatableProperty = result.hasUpdatableProperty
filteredVariants.push filteredVariant if variantHasUpdatableProperty filteredVariants.push filteredVariant if variantHasUpdatableProperty
if product.hasOwnProperty("sku")
filteredProduct.sku = product.sku
hasUpdatableProperty = true
if product.hasOwnProperty("name") if product.hasOwnProperty("name")
filteredProduct.name = product.name filteredProduct.name = product.name
hasUpdatableProperty = true hasUpdatableProperty = true

View File

@@ -0,0 +1,29 @@
angular.module("admin.indexUtils").factory 'KeyValueMapStore', (localStorageService)->
new class KeyValueMapStore
localStorageKey: ''
storableKeys: []
constructor: ->
localStorageService.setStorageType("sessionStorage")
getStoredKeyValueMap: ->
localStorageService.get(@localStorageKey) || {}
setStoredValues: (source) ->
keyValueMap = {}
for key in @storableKeys
keyValueMap[key] = source[key]
localStorageService.set(@localStorageKey, keyValueMap)
restoreValues: (target) ->
storedKeyValueMap = @getStoredKeyValueMap()
return false if _.isEmpty(storedKeyValueMap)
for k,v of storedKeyValueMap
target[k] = v
return true
clearKeyValueMap: () ->
localStorageService.remove(@localStorageKey)

View File

@@ -187,17 +187,18 @@ addVariantFromStockLocation = function() {
$('#stock_details').hide(); $('#stock_details').hide();
var variant_id = $('input.variant_autocomplete').val(); var variant_id = $('input.variant_autocomplete').val();
var quantity = $("input.quantity").val(); var stock_location_id = $(this).data('stock-location-id');
var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();
var shipment = _.find(shipments, function(shipment){ var shipment = _.find(shipments, function(shipment){
return shipment.state == 'ready' || shipment.state == 'pending'; return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
}); });
if(shipment==undefined){ if(shipment==undefined){
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: Spree.url(Spree.routes.orders_api + "/" + order_number + "/shipments.json"), url: Spree.url(Spree.routes.orders_api + "/" + order_number + "/shipments.json"),
data: { variant_id: variant_id, quantity: quantity } data: { variant_id: variant_id, quantity: quantity, stock_location_id: stock_location_id }
}).done(function( msg ) { }).done(function( msg ) {
window.location.reload(); window.location.reload();
}).error(function( msg ) { }).error(function( msg ) {

View File

@@ -17,6 +17,7 @@
#= require angular-google-maps.min.js #= require angular-google-maps.min.js
#= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js #= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js
#= require ../shared/ng-infinite-scroll.min.js #= require ../shared/ng-infinite-scroll.min.js
#= require ../shared/angular-local-storage.js
#= require ../shared/angular-slideables.js #= require ../shared/angular-slideables.js
#= require ../shared/shared #= require ../shared/shared
#= require_tree ../shared/directives #= require_tree ../shared/directives

View File

@@ -1,6 +1,7 @@
angular.module("Darkswarm", [ angular.module("Darkswarm", [
'ngResource', 'ngResource',
'mm.foundation', 'mm.foundation',
'LocalStorageModule',
'infinite-scroll', 'infinite-scroll',
'angular-flash.service', 'angular-flash.service',
'templates', 'templates',

View File

@@ -0,0 +1,10 @@
angular.module('Darkswarm').directive "ofnDisableScroll", ()->
# Stops scrolling from incrementing or decrementing input value
# Useful for number inputs
restrict: 'A'
link: (scope, element, attrs)->
element.bind 'focus', ->
element.bind 'mousewheel', (e)->
e.preventDefault()
element.bind 'blur', ->
element.unbind 'mousewheel'

View File

@@ -0,0 +1,5 @@
angular.module('Darkswarm').directive "integer", ->
restrict: 'A'
link: (scope, elem, attr) ->
elem.bind 'input', ->
elem.val Math.round(elem.val())

View File

@@ -20,13 +20,10 @@ angular.module('Darkswarm').directive 'mapSearch', ($timeout, Search) ->
$timeout => $timeout =>
map = ctrl.getMap() map = ctrl.getMap()
if !map searchBox = scope.createSearchBox map
alert(t('gmap_load_failure')) scope.bindSearchResponse map, searchBox
else scope.biasResults map, searchBox
searchBox = scope.createSearchBox map scope.performUrlSearch map
scope.bindSearchResponse map, searchBox
scope.biasResults map, searchBox
scope.performUrlSearch map
scope.createSearchBox = (map) -> scope.createSearchBox = (map) ->
map.controls[google.maps.ControlPosition.TOP_LEFT].push scope.input map.controls[google.maps.ControlPosition.TOP_LEFT].push scope.input

View File

@@ -13,8 +13,6 @@ angular.module('Darkswarm').directive 'ofnOpenStreetMap', ($window, MapCentreCal
buildMarker = (enterprise, latlng, title) -> buildMarker = (enterprise, latlng, title) ->
icon = L.icon icon = L.icon
iconAnchor: [14, 33]
iconSize: [28, 33]
iconUrl: enterprise.icon iconUrl: enterprise.icon
marker = L.marker latlng, marker = L.marker latlng,
draggable: true, draggable: true,

View File

@@ -0,0 +1,14 @@
angular.module('Darkswarm').directive "renderSvg", ()->
# Magical directive that'll render SVGs from URLs
# If only there were a neater way of doing this
restrict: 'E'
priority: 99
template: "<svg-wrapper></svg-wrapper>"
# Fetch SVG via ajax, inject into page using DOM
link: (scope, elem, attr)->
if /.svg/.test attr.path # Only do this if we've got an svg
$.ajax
url: attr.path
success: (html)->
elem.html($(html).find("svg"))

View File

@@ -0,0 +1,9 @@
angular.module('Darkswarm').directive "ofnScrollTo", ($location, $anchorScroll)->
# Onclick sets $location.hash to attrs.ofnScrollTo
# Then triggers anchorScroll
restrict: 'A'
link: (scope, element, attrs)->
element.bind 'click', (ev)->
ev.stopPropagation()
$location.hash attrs.ofnScrollTo
$anchorScroll()

View File

@@ -1,4 +1,4 @@
angular.module('Darkswarm').factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, Messages) -> angular.module('Darkswarm').factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService, Messages) ->
# Handles syncing of current cart/order state to server # Handles syncing of current cart/order state to server
new class Cart new class Cart
dirty: false dirty: false
@@ -113,6 +113,7 @@ angular.module('Darkswarm').factory 'Cart', (CurrentOrder, Variants, $timeout, $
clear: -> clear: ->
@line_items = [] @line_items = []
localStorageService.clearAll() # One day this will have to be moar GRANULAR
isOnlyItemInOrder: (id) => isOnlyItemInOrder: (id) =>
deletedItem = @line_items_finalised.find((item) -> item.id == id) deletedItem = @line_items_finalised.find((item) -> item.id == id)

View File

@@ -0,0 +1,546 @@
/**
* An Angular module that gives you access to the browsers local storage
* @version v0.5.0 - 2016-08-29
* @link https://github.com/grevory/angular-local-storage
* @author grevory <greg@gregpike.ca>
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
(function (window, angular) {
var isDefined = angular.isDefined,
isUndefined = angular.isUndefined,
isNumber = angular.isNumber,
isObject = angular.isObject,
isArray = angular.isArray,
extend = angular.extend,
toJson = angular.toJson;
angular
.module('LocalStorageModule', [])
.provider('localStorageService', function() {
// You should set a prefix to avoid overwriting any local storage variables from the rest of your app
// e.g. localStorageServiceProvider.setPrefix('yourAppName');
// With provider you can use config as this:
// myApp.config(function (localStorageServiceProvider) {
// localStorageServiceProvider.prefix = 'yourAppName';
// });
this.prefix = 'ls';
// You could change web storage type localstorage or sessionStorage
this.storageType = 'localStorage';
// Cookie options (usually in case of fallback)
// expiry = Number of days before cookies expire // 0 = Does not expire
// path = The web path the cookie represents
// secure = Wether the cookies should be secure (i.e only sent on HTTPS requests)
this.cookie = {
expiry: 30,
path: '/',
secure: false
};
// Decides wether we should default to cookies if localstorage is not supported.
this.defaultToCookie = true;
// Send signals for each of the following actions?
this.notify = {
setItem: true,
removeItem: false
};
// Setter for the prefix
this.setPrefix = function(prefix) {
this.prefix = prefix;
return this;
};
// Setter for the storageType
this.setStorageType = function(storageType) {
this.storageType = storageType;
return this;
};
// Setter for defaultToCookie value, default is true.
this.setDefaultToCookie = function (shouldDefault) {
this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value.
return this;
};
// Setter for cookie config
this.setStorageCookie = function(exp, path, secure) {
this.cookie.expiry = exp;
this.cookie.path = path;
this.cookie.secure = secure;
return this;
};
// Setter for cookie domain
this.setStorageCookieDomain = function(domain) {
this.cookie.domain = domain;
return this;
};
// Setter for notification config
// itemSet & itemRemove should be booleans
this.setNotify = function(itemSet, itemRemove) {
this.notify = {
setItem: itemSet,
removeItem: itemRemove
};
return this;
};
this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) {
var self = this;
var prefix = self.prefix;
var cookie = self.cookie;
var notify = self.notify;
var storageType = self.storageType;
var webStorage;
// When Angular's $document is not available
if (!$document) {
$document = document;
} else if ($document[0]) {
$document = $document[0];
}
// If there is a prefix set in the config lets use that with an appended period for readability
if (prefix.substr(-1) !== '.') {
prefix = !!prefix ? prefix + '.' : '';
}
var deriveQualifiedKey = function(key) {
return prefix + key;
};
// Removes prefix from the key.
var underiveQualifiedKey = function (key) {
return key.replace(new RegExp('^' + prefix, 'g'), '');
};
// Check if the key is within our prefix namespace.
var isKeyPrefixOurs = function (key) {
return key.indexOf(prefix) === 0;
};
// Checks the browser to see if local storage is supported
var checkSupport = function () {
try {
var supported = (storageType in $window && $window[storageType] !== null);
// When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage
// is available, but trying to call .setItem throws an exception.
//
// "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage
// that exceeded the quota."
var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7));
if (supported) {
webStorage = $window[storageType];
webStorage.setItem(key, '');
webStorage.removeItem(key);
}
return supported;
} catch (e) {
// Only change storageType to cookies if defaulting is enabled.
if (self.defaultToCookie)
storageType = 'cookie';
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return false;
}
};
var browserSupportsLocalStorage = checkSupport();
// Directly adds a value to local storage
// If local storage is not available in the browser use cookies
// Example use: localStorageService.add('library','angular');
var addToLocalStorage = function (key, value, type) {
setStorageType(type);
// Let's convert undefined values to null to get the value consistent
if (isUndefined(value)) {
value = null;
} else {
value = toJson(value);
}
// If this browser does not support local storage use cookies
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
}
if (notify.setItem) {
$rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'});
}
return addToCookies(key, value);
}
try {
if (webStorage) {
webStorage.setItem(deriveQualifiedKey(key), value);
}
if (notify.setItem) {
$rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType});
}
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return addToCookies(key, value);
}
return true;
};
// Directly get a value from local storage
// Example use: localStorageService.get('library'); // returns 'angular'
var getFromLocalStorage = function (key, type) {
setStorageType(type);
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
}
return getFromCookies(key);
}
var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null;
// angular.toJson will convert null to 'null', so a proper conversion is needed
// FIXME not a perfect solution, since a valid 'null' string can't be stored
if (!item || item === 'null') {
return null;
}
try {
return JSON.parse(item);
} catch (e) {
return item;
}
};
// Remove an item from local storage
// Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular'
//
// This is var-arg removal, check the last argument to see if it is a storageType
// and set type accordingly before removing.
//
var removeFromLocalStorage = function () {
// can't pop on arguments, so we do this
var consumed = 0;
if (arguments.length >= 1 &&
(arguments[arguments.length - 1] === 'localStorage' ||
arguments[arguments.length - 1] === 'sessionStorage')) {
consumed = 1;
setStorageType(arguments[arguments.length - 1]);
}
var i, key;
for (i = 0; i < arguments.length - consumed; i++) {
key = arguments[i];
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
}
if (notify.removeItem) {
$rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'});
}
removeFromCookies(key);
}
else {
try {
webStorage.removeItem(deriveQualifiedKey(key));
if (notify.removeItem) {
$rootScope.$broadcast('LocalStorageModule.notification.removeitem', {
key: key,
storageType: self.storageType
});
}
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
removeFromCookies(key);
}
}
}
};
// Return array of keys for local storage
// Example use: var keys = localStorageService.keys()
var getKeysForLocalStorage = function (type) {
setStorageType(type);
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
return [];
}
var prefixLength = prefix.length;
var keys = [];
for (var key in webStorage) {
// Only return keys that are for this app
if (key.substr(0, prefixLength) === prefix) {
try {
keys.push(key.substr(prefixLength));
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.Description);
return [];
}
}
}
return keys;
};
// Remove all data for this app from local storage
// Also optionally takes a regular expression string and removes the matching key-value pairs
// Example use: localStorageService.clearAll();
// Should be used mostly for development purposes
var clearAllFromLocalStorage = function (regularExpression, type) {
setStorageType(type);
// Setting both regular expressions independently
// Empty strings result in catchall RegExp
var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp();
var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp();
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
}
return clearAllFromCookies();
}
if (!browserSupportsLocalStorage && !self.defaultToCookie)
return false;
var prefixLength = prefix.length;
for (var key in webStorage) {
// Only remove items that are for this app and match the regular expression
if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) {
try {
removeFromLocalStorage(key.substr(prefixLength));
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return clearAllFromCookies();
}
}
}
return true;
};
// Checks the browser to see if cookies are supported
var browserSupportsCookies = (function() {
try {
return $window.navigator.cookieEnabled ||
("cookie" in $document && ($document.cookie.length > 0 ||
($document.cookie = "test").indexOf.call($document.cookie, "test") > -1));
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return false;
}
}());
// Directly adds a value to cookies
// Typically used as a fallback if local storage is not available in the browser
// Example use: localStorageService.cookie.add('library','angular');
var addToCookies = function (key, value, daysToExpiry, secure) {
if (isUndefined(value)) {
return false;
} else if(isArray(value) || isObject(value)) {
value = toJson(value);
}
if (!browserSupportsCookies) {
$rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
return false;
}
try {
var expiry = '',
expiryDate = new Date(),
cookieDomain = '';
if (value === null) {
// Mark that the cookie has expired one day ago
expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000));
expiry = "; expires=" + expiryDate.toGMTString();
value = '';
} else if (isNumber(daysToExpiry) && daysToExpiry !== 0) {
expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000));
expiry = "; expires=" + expiryDate.toGMTString();
} else if (cookie.expiry !== 0) {
expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000));
expiry = "; expires=" + expiryDate.toGMTString();
}
if (!!key) {
var cookiePath = "; path=" + cookie.path;
if (cookie.domain) {
cookieDomain = "; domain=" + cookie.domain;
}
/* Providing the secure parameter always takes precedence over config
* (allows developer to mix and match secure + non-secure) */
if (typeof secure === 'boolean') {
if (secure === true) {
/* We've explicitly specified secure,
* add the secure attribute to the cookie (after domain) */
cookieDomain += "; secure";
}
// else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says
}
else if (cookie.secure === true) {
// secure parameter wasn't specified, get default from config
cookieDomain += "; secure";
}
$document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain;
}
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return false;
}
return true;
};
// Directly get a value from a cookie
// Example use: localStorageService.cookie.get('library'); // returns 'angular'
var getFromCookies = function (key) {
if (!browserSupportsCookies) {
$rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
return false;
}
var cookies = $document.cookie && $document.cookie.split(';') || [];
for(var i=0; i < cookies.length; i++) {
var thisCookie = cookies[i];
while (thisCookie.charAt(0) === ' ') {
thisCookie = thisCookie.substring(1,thisCookie.length);
}
if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) {
var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length));
try {
return JSON.parse(storedValues);
} catch(e) {
return storedValues;
}
}
}
return null;
};
var removeFromCookies = function (key) {
addToCookies(key,null);
};
var clearAllFromCookies = function () {
var thisCookie = null;
var prefixLength = prefix.length;
var cookies = $document.cookie.split(';');
for(var i = 0; i < cookies.length; i++) {
thisCookie = cookies[i];
while (thisCookie.charAt(0) === ' ') {
thisCookie = thisCookie.substring(1, thisCookie.length);
}
var key = thisCookie.substring(prefixLength, thisCookie.indexOf('='));
removeFromCookies(key);
}
};
var getStorageType = function() {
return storageType;
};
var setStorageType = function(type) {
if (type && storageType !== type) {
storageType = type;
browserSupportsLocalStorage = checkSupport();
}
return browserSupportsLocalStorage;
};
// Add a listener on scope variable to save its changes to local storage
// Return a function which when called cancels binding
var bindToScope = function(scope, key, def, lsKey, type) {
lsKey = lsKey || key;
var value = getFromLocalStorage(lsKey, type);
if (value === null && isDefined(def)) {
value = def;
} else if (isObject(value) && isObject(def)) {
value = extend(value, def);
}
$parse(key).assign(scope, value);
return scope.$watch(key, function(newVal) {
addToLocalStorage(lsKey, newVal, type);
}, isObject(scope[key]));
};
// Add listener to local storage, for update callbacks.
if (browserSupportsLocalStorage) {
if ($window.addEventListener) {
$window.addEventListener("storage", handleStorageChangeCallback, false);
$rootScope.$on('$destroy', function() {
$window.removeEventListener("storage", handleStorageChangeCallback);
});
} else if($window.attachEvent){
// attachEvent and detachEvent are proprietary to IE v6-10
$window.attachEvent("onstorage", handleStorageChangeCallback);
$rootScope.$on('$destroy', function() {
$window.detachEvent("onstorage", handleStorageChangeCallback);
});
}
}
// Callback handler for storage changed.
function handleStorageChangeCallback(e) {
if (!e) { e = $window.event; }
if (notify.setItem) {
if (isKeyPrefixOurs(e.key)) {
var key = underiveQualifiedKey(e.key);
// Use timeout, to avoid using $rootScope.$apply.
$timeout(function () {
$rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType });
});
}
}
}
// Return localStorageService.length
// ignore keys that not owned
var lengthOfLocalStorage = function(type) {
setStorageType(type);
var count = 0;
var storage = $window[storageType];
for(var i = 0; i < storage.length; i++) {
if(storage.key(i).indexOf(prefix) === 0 ) {
count++;
}
}
return count;
};
return {
isSupported: browserSupportsLocalStorage,
getStorageType: getStorageType,
setStorageType: setStorageType,
set: addToLocalStorage,
add: addToLocalStorage, //DEPRECATED
get: getFromLocalStorage,
keys: getKeysForLocalStorage,
remove: removeFromLocalStorage,
clearAll: clearAllFromLocalStorage,
bind: bindToScope,
deriveKey: deriveQualifiedKey,
underiveKey: underiveQualifiedKey,
length: lengthOfLocalStorage,
defaultToCookie: this.defaultToCookie,
cookie: {
isSupported: browserSupportsCookies,
set: addToCookies,
add: addToCookies, //DEPRECATED
get: getFromCookies,
remove: removeFromCookies,
clearAll: clearAllFromCookies
}
};
}];
});
})(window, window.angular);

View File

@@ -1,4 +1,5 @@
window.OFNShared = angular.module("OFNShared", [ window.OFNShared = angular.module("OFNShared", [
"mm.foundation", "mm.foundation",
"LocalStorageModule"
]).config ($httpProvider) -> ]).config ($httpProvider) ->
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*" $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"

View File

@@ -1,4 +1,3 @@
// please update the modal html in app/components/out_of_stock_modal_component/out_of_stock_modal_component.html.haml if updating this template
%a.close-reveal-modal{"ng-click" => "$close()"} %a.close-reveal-modal{"ng-click" => "$close()"}
%i.ofn-i_009-close %i.ofn-i_009-close
@@ -13,4 +12,4 @@
%span{'ng-if' => "v.on_hand == 0"} %span{'ng-if' => "v.on_hand == 0"}
{{ 'js.out_of_stock.now_out_of_stock' | t }} {{ 'js.out_of_stock.now_out_of_stock' | t }}
%span{'ng-if' => "v.on_hand > 0"} %span{'ng-if' => "v.on_hand > 0"}
{{ 'js.out_of_stock.only_n_remaining' | t:{ num: v.on_hand } }} {{ 'js.out_of_stock.only_n_remainging' | t:{ num: v.on_hand } }}

View File

@@ -1,10 +0,0 @@
# frozen_string_literal: true
class OutOfStockModalComponent < ModalComponent
def initialize(id:, variants: [], redirect: false)
super(id:, modal_class: "medium", instant: true)
@variants = variants
@redirect = redirect
end
end

View File

@@ -1,26 +0,0 @@
%div{ id: @id, "data-controller": "modal out-of-stock-modal", "data-action": "keyup@document->modal#closeIfEscapeKey modal:closing->out-of-stock-modal#redirect", "data-modal-instant-value": @instant, "data-out-of-stock-modal-redirect-value": @redirect }
.reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#close" }
.reveal-modal.fade.modal-component{ "data-modal-target": "modal", class: @modal_class }
- # please update app/assets/javascripts/templates/out_of_stock.html.haml if updating this view
%a.close-reveal-modal{"data-action": "click->modal#close" }
%i.ofn-i_009-close
%h3
= t("js.out_of_stock.reduced_stock_available")
%p
= t("js.out_of_stock.out_of_stock_text")
- @variants.each do |variant|
- if variant.on_hand == 0
%p
%em
= "#{variant.name_to_display} - #{variant.unit_to_display}"
%span
= t("js.out_of_stock.now_out_of_stock")
- if variant.on_hand > 0
%p
%em
= "#{variant.name_to_display} - #{variant.unit_to_display}"
%span
= t("js.out_of_stock.only_n_remaining", num: variant.on_hand)
.text-center
%input{ class: "button icon-plus #{close_button_class}", type: 'button', value: t('js.admin.modals.close'), "data-action": "click->modal#close" }

View File

@@ -44,9 +44,9 @@ module Admin
create_connected_app create_connected_app
jwt_service = Vine::JwtService.new(secret: connected_app_params[:vine_secret]) jwt_service = VineJwtService.new(secret: connected_app_params[:vine_secret])
vine_api = Vine::ApiService.new(api_key: connected_app_params[:vine_api_key], vine_api = VineApiService.new(api_key: connected_app_params[:vine_api_key],
jwt_generator: jwt_service) jwt_generator: jwt_service)
if !@app.connect(api_key: connected_app_params[:vine_api_key], if !@app.connect(api_key: connected_app_params[:vine_api_key],
secret: connected_app_params[:vine_secret], vine_api:) secret: connected_app_params[:vine_secret], vine_api:)
@@ -77,7 +77,7 @@ module Admin
def log_and_notify_exception(exception) def log_and_notify_exception(exception)
Rails.logger.error exception.inspect Rails.logger.error exception.inspect
Alert.raise(exception) Bugsnag.notify(exception)
end end
def vine_params_empty? def vine_params_empty?

View File

@@ -5,74 +5,50 @@ require "private_address_check/tcpsocket_ext"
module Admin module Admin
class DfcProductImportsController < Spree::Admin::BaseController class DfcProductImportsController < Spree::Admin::BaseController
before_action :load_enterprise
# Define model class for `can?` permissions: # Define model class for `can?` permissions:
def model_class def model_class
self.class self.class
end end
def index def index
# Fetch DFC catalog JSON for preview # The plan:
api = DfcRequest.new(spree_current_user) #
@catalog_url = params.require(:catalog_url) # * Fetch DFC catalog as JSON from URL.
@catalog_json = api.call(@catalog_url) enterprise = OpenFoodNetwork::Permissions.new(spree_current_user)
graph = DfcIo.import(@catalog_json) .managed_product_enterprises.is_primary_producer
catalog = DfcCatalog.new(graph) .find(params.require(:enterprise_id))
# Render table and let user decide which ones to import. catalog_url = params.require(:catalog_url)
@items = catalog.products.map do |subject|
[
subject,
@enterprise.supplied_variants.linked_to(subject.semanticId)&.product
]
end
rescue Faraday::Error,
Addressable::URI::InvalidURIError,
ActionController::ParameterMissing => e
flash[:error] = e.message
redirect_to admin_product_import_path
rescue Rack::OAuth2::Client::Error
oidc_settings_link = helpers.link_to(
t('spree.admin.tab.oidc_settings'),
admin_oidc_settings_path
)
flash[:error] = t(".connection_invalid_html", oidc_settings_link:)
redirect_to admin_product_import_path
end
def import json_catalog = fetch_catalog(catalog_url)
ids = params.require(:semanticIds) graph = DfcIo.import(json_catalog)
# Load DFC catalog JSON # * First step: import all products for given enterprise.
graph = DfcIo.import(params.require(:catalog_json)) # * Second step: render table and let user decide which ones to import.
catalog = DfcCatalog.new(graph) imported = graph.map do |subject|
catalog.apply_wholesale_values! next unless subject.is_a? DataFoodConsortium::Connector::SuppliedProduct
# Import all selected products for given enterprise. existing_variant = enterprise.supplied_variants.linked_to(subject.semanticId)
imported = ids.map do |semantic_id|
subject = catalog.item(semantic_id)
existing_variant = @enterprise.supplied_variants.linked_to(semantic_id)
if existing_variant if existing_variant
SuppliedProductImporter.update_product(subject, existing_variant) SuppliedProductBuilder.update_product(subject, existing_variant)
else else
SuppliedProductImporter.store_product(subject, @enterprise) SuppliedProductBuilder.store_product(subject, enterprise)
end end
end end
@count = imported.compact.count @count = imported.compact.count
rescue ActionController::ParameterMissing => e rescue Faraday::Error,
Addressable::URI::InvalidURIError,
ActionController::ParameterMissing => e
flash[:error] = e.message flash[:error] = e.message
redirect_to admin_product_import_path redirect_to admin_product_import_path
end end
private private
def load_enterprise def fetch_catalog(url)
@enterprise = OpenFoodNetwork::Permissions.new(spree_current_user) DfcRequest.new(spree_current_user).call(url)
.managed_product_enterprises.is_primary_producer
.find(params.require(:enterprise_id))
end end
end end
end end

View File

@@ -30,13 +30,13 @@ module Admin
def validate_data def validate_data
return unless process_data('validate') return unless process_data('validate')
render json: @importer.import_results render json: @importer.import_results, response: 200
end end
def save_data def save_data
return unless process_data('save') return unless process_data('save')
render json: @importer.save_results render json: @importer.save_results, response: 200
end end
def reset_absent_products def reset_absent_products
@@ -76,7 +76,7 @@ module Admin
begin begin
@importer.public_send("#{method}_entries") @importer.public_send("#{method}_entries")
rescue StandardError => e rescue StandardError => e
render plain: e.message, status: :internal_server_error render json: e.message, response: 500
return false return false
end end

View File

@@ -144,7 +144,7 @@ module Admin
def product_scope def product_scope
user = spree_current_user user = spree_current_user
scope = if user.admin? || user.enterprises.present? scope = if user.has_spree_role?("admin") || user.enterprises.present?
Spree::Product Spree::Product
else else
Spree::Product.active Spree::Product.active

View File

@@ -71,12 +71,6 @@ module Admin
def load_collection def load_collection
collection_hash = Hash[variant_overrides_params.each_with_index.map { |vo, i| [i, vo] }] collection_hash = Hash[variant_overrides_params.each_with_index.map { |vo, i| [i, vo] }]
# Reset count_on_hand when switching to producer settings:
collection_hash.each_value do |vo|
vo["count_on_hand"] = nil if vo.fetch("on_demand", :unchanged).nil?
end
@vo_set = Sets::VariantOverrideSet.new(@variant_overrides, @vo_set = Sets::VariantOverrideSet.new(@variant_overrides,
collection_attributes: collection_hash) collection_attributes: collection_hash)
end end

View File

@@ -66,7 +66,7 @@ module Api
end end
def error_during_processing(exception) def error_during_processing(exception)
Alert.raise(exception) Bugsnag.notify(exception)
render(json: { exception: exception.message }, render(json: { exception: exception.message },
status: :unprocessable_entity) && return status: :unprocessable_entity) && return

View File

@@ -14,7 +14,7 @@ module Api
def create def create
variant = scoped_variant(params[:variant_id]) variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i quantity = params[:quantity].to_i
@shipment = @order.shipment || @order.shipments.create @shipment = get_or_create_shipment(params[:stock_location_id])
@order.contents.add(variant, quantity, @shipment) @order.contents.add(variant, quantity, @shipment)
@@ -24,7 +24,6 @@ module Api
Orders::WorkflowService.new(@order).advance_to_payment if @order.line_items.any? Orders::WorkflowService.new(@order).advance_to_payment if @order.line_items.any?
@order.recreate_all_fees! @order.recreate_all_fees!
AmendBackorderJob.perform_later(@order) if @order.completed?
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end end
@@ -74,7 +73,6 @@ module Api
@order.contents.add(variant, quantity, @shipment) @order.contents.add(variant, quantity, @shipment)
@order.recreate_all_fees! @order.recreate_all_fees!
AmendBackorderJob.perform_later(@order) if @order.completed?
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end end
@@ -88,7 +86,6 @@ module Api
@shipment.reload if @shipment.persisted? @shipment.reload if @shipment.persisted?
@order.recreate_all_fees! @order.recreate_all_fees!
AmendBackorderJob.perform_later(@order) if @order.completed?
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end end
@@ -116,6 +113,10 @@ module Api
variant variant
end end
def get_or_create_shipment(stock_location_id)
@order.shipment || @order.shipments.create(stock_location_id:)
end
def shipment_params def shipment_params
return {} unless params.has_key? :shipment return {} unless params.has_key? :shipment

View File

@@ -55,14 +55,14 @@ module Api
def scope def scope
if @product if @product
variants = if current_api_user.admin? || params[:show_deleted] variants = if current_api_user.has_spree_role?("admin") || params[:show_deleted]
@product.variants.with_deleted @product.variants.with_deleted
else else
@product.variants @product.variants
end end
else else
variants = Spree::Variant.where(nil) variants = Spree::Variant.where(nil)
if current_api_user.admin? if current_api_user.has_spree_role?("admin")
unless params[:show_deleted] unless params[:show_deleted]
variants = Spree::Variant.active variants = Spree::Variant.active
end end

View File

@@ -52,7 +52,7 @@ module Api
end end
def error_during_processing(exception) def error_during_processing(exception)
Alert.raise(exception) Bugsnag.notify(exception)
if Rails.env.development? || Rails.env.test? if Rails.env.development? || Rails.env.test?
render status: :unprocessable_entity, render status: :unprocessable_entity,

View File

@@ -11,7 +11,7 @@ class CartController < BaseController
order.cap_quantity_at_stock! order.cap_quantity_at_stock!
order.recreate_all_fees! order.recreate_all_fees!
StockSyncJob.sync_linked_catalogs_later(order) StockSyncJob.sync_linked_catalogs(order)
render json: { error: false, stock_levels: stock_levels(order) }, status: :ok render json: { error: false, stock_levels: stock_levels(order) }, status: :ok
else else

View File

@@ -26,7 +26,6 @@ class CheckoutController < BaseController
if params[:step].blank? if params[:step].blank?
redirect_to_step_based_on_order redirect_to_step_based_on_order
else else
handle_insufficient_stock if details_step?
update_order_state update_order_state
check_step check_step
end end
@@ -37,9 +36,6 @@ class CheckoutController < BaseController
end end
def update def update
return render cable_ready: cable_car.redirect_to(url: checkout_step_path(:details)) \
unless sufficient_stock?
if confirm_order || update_order if confirm_order || update_order
return if performed? return if performed?
@@ -82,21 +78,8 @@ class CheckoutController < BaseController
return true if redirect_to_payment_gateway return true if redirect_to_payment_gateway
# Redeem VINE voucher
vine_voucher_redeemer = Vine::VoucherRedeemerService.new(order: @order)
unless vine_voucher_redeemer.redeem
# rubocop:disable Rails/DeprecatedActiveModelErrorsMethods
flash[:error] = if vine_voucher_redeemer.errors.keys.include?(:redeeming_failed)
vine_voucher_redeemer.errors[:redeeming_failed]
else
I18n.t('checkout.errors.voucher_redeeming_error')
end
return false
# rubocop:enable Rails/DeprecatedActiveModelErrorsMethods
end
@order.process_payments! @order.process_payments!
@order.confirm! @order.confirm!
BackorderJob.check_stock(@order)
order_completion_reset @order order_completion_reset @order
end end

View File

@@ -25,6 +25,7 @@ module CheckoutCallbacks
before_action :ensure_order_not_completed before_action :ensure_order_not_completed
before_action :ensure_checkout_allowed before_action :ensure_checkout_allowed
before_action :handle_insufficient_stock
before_action :check_authorization before_action :check_authorization
end end

View File

@@ -50,7 +50,9 @@ module OrderCompletion
end end
def order_invalid! def order_invalid!
Alert.raise_with_record("Notice: invalid order loaded during checkout", @order) Bugsnag.notify("Notice: invalid order loaded during checkout") do |payload|
payload.add_metadata :order, :order, @order
end
flash[:error] = t('checkout.order_not_loaded') flash[:error] = t('checkout.order_not_loaded')
redirect_to main_app.shop_path redirect_to main_app.shop_path
@@ -90,7 +92,9 @@ module OrderCompletion
end end
def notify_failure(error = RuntimeError.new(order_processing_error)) def notify_failure(error = RuntimeError.new(order_processing_error))
Alert.raise_with_record(error, @order) Bugsnag.notify(error) do |payload|
payload.add_metadata :order, @order
end
flash[:error] = order_processing_error if flash.blank? flash[:error] = order_processing_error if flash.blank?
end end

View File

@@ -4,26 +4,25 @@ module OrderStockCheck
include CablecarResponses include CablecarResponses
extend ActiveSupport::Concern extend ActiveSupport::Concern
delegate :sufficient_stock?, to: :check_stock_service
def valid_order_line_items? def valid_order_line_items?
OrderCycles::DistributedVariantsService.new(@order.order_cycle, @order.distributor). @order.insufficient_stock_lines.empty? &&
distributes_order_variants?(@order) OrderCycles::DistributedVariantsService.new(@order.order_cycle, @order.distributor).
distributes_order_variants?(@order)
end end
def handle_insufficient_stock def handle_insufficient_stock
@any_out_of_stock = false
return if sufficient_stock? return if sufficient_stock?
@any_out_of_stock = true flash[:error] = Spree.t(:inventory_error_flash_for_insufficient_quantity)
@updated_variants = check_stock_service.update_line_items redirect_to main_app.cart_path
end end
def check_order_cycle_expiry def check_order_cycle_expiry
return unless current_order_cycle&.closed? return unless current_order_cycle&.closed?
Alert.raise_with_record("Notice: order cycle closed during checkout completion", current_order) Bugsnag.notify("Notice: order cycle closed during checkout completion") do |payload|
payload.add_metadata :order, :order, current_order
end
current_order.empty! current_order.empty!
current_order.set_order_cycle! nil current_order.set_order_cycle! nil
@@ -39,7 +38,7 @@ module OrderStockCheck
private private
def check_stock_service def sufficient_stock?
@check_stock_service ||= Orders::CheckStockService.new(order: @order) @sufficient_stock ||= @order.insufficient_stock_lines.blank?
end end
end end

View File

@@ -4,6 +4,11 @@ class ErrorsController < ApplicationController
layout "errors" layout "errors"
def not_found def not_found
Bugsnag.notify("404") do |event|
event.severity = "info"
event.add_metadata(:request, :env, request.env)
end
render status: :not_found, formats: :html render status: :not_found, formats: :html
end end

View File

@@ -2,13 +2,8 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def openid_connect def openid_connect
ActiveRecord::Base.transaction do OidcAccount.link(spree_current_user, request.env["omniauth.auth"])
OidcAccount.link(spree_current_user, request.env["omniauth.auth"])
end
redirect_to admin_oidc_settings_path
rescue ActiveRecord::RecordNotUnique
flash[:error] = t("devise.oidc.record_not_unique", uid: request.env["omniauth.auth"].uid)
redirect_to admin_oidc_settings_path redirect_to admin_oidc_settings_path
end end

View File

@@ -14,8 +14,6 @@ module PaymentGateways
after_action :reset_order_when_complete, only: :confirm after_action :reset_order_when_complete, only: :confirm
def express def express
return redirect_to order_failed_route if @any_out_of_stock == true
pp_request = provider.build_set_express_checkout( pp_request = provider.build_set_express_checkout(
express_checkout_request_details(@order) express_checkout_request_details(@order)
) )
@@ -43,8 +41,6 @@ module PaymentGateways
end end
def confirm def confirm
return redirect_to order_failed_route if @any_out_of_stock == true
# At this point the user has come back from the Paypal form, and we get one # At this point the user has come back from the Paypal form, and we get one
# last chance to interact with the payment process before the money moves... # last chance to interact with the payment process before the money moves...

View File

@@ -8,12 +8,9 @@ module PaymentGateways
before_action :load_checkout_order, only: :confirm before_action :load_checkout_order, only: :confirm
before_action :validate_payment_intent, only: :confirm before_action :validate_payment_intent, only: :confirm
before_action :check_order_cycle_expiry, only: :confirm before_action :check_order_cycle_expiry, only: :confirm
before_action :validate_stock, only: :confirm
def confirm def confirm
validate_stock
return redirect_to order_failed_route if @any_out_of_stock == true
process_payment_completion! process_payment_completion!
end end

View File

@@ -69,8 +69,7 @@ module Spree
@order.send_cancellation_email = params[:send_cancellation_email] != "false" @order.send_cancellation_email = params[:send_cancellation_email] != "false"
@order.restock_items = params.fetch(:restock_items, "true") == "true" @order.restock_items = params.fetch(:restock_items, "true") == "true"
if allowed_events.include?(event) && @order.public_send(event.to_s) if @order.public_send(event.to_s)
AmendBackorderJob.perform_later(@order) if @order.completed?
flash[:success] = Spree.t(:order_updated) flash[:success] = Spree.t(:order_updated)
else else
flash[:error] = Spree.t(:cannot_perform_operation) flash[:error] = Spree.t(:cannot_perform_operation)
@@ -198,10 +197,6 @@ module Spree
ocs.closed + ocs.closed +
ocs.undated ocs.undated
end end
def allowed_events
%w{cancel resume}
end
end end
end end
end end

View File

@@ -24,12 +24,9 @@ module Spree
end end
def create def create
# Try to redeem VINE voucher first as we don't want to create a payment and complete
# the order if it fails
return redirect_to spree.admin_order_payments_path(@order) unless redeem_vine_voucher
@payment = @order.payments.build(object_params) @payment = @order.payments.build(object_params)
load_payment_source load_payment_source
begin begin
unless @payment.save unless @payment.save
redirect_to spree.admin_order_payments_path(@order) redirect_to spree.admin_order_payments_path(@order)
@@ -54,10 +51,6 @@ module Spree
event = params[:e] event = params[:e]
return unless event && @payment.payment_source return unless event && @payment.payment_source
# capture_and_complete_order will complete the order, so we want to try to redeem VINE
# voucher first and exit if it fails
return if event == "capture_and_complete_order" && !redeem_vine_voucher
# Because we have a transition method also called void, we do this to avoid conflicts. # Because we have a transition method also called void, we do this to avoid conflicts.
event = "void_transaction" if event == "void" event = "void_transaction" if event == "void"
if allowed_events.include?(event) && @payment.public_send("#{event}!") if allowed_events.include?(event) && @payment.public_send("#{event}!")
@@ -67,7 +60,7 @@ module Spree
end end
rescue StandardError => e rescue StandardError => e
logger.error e.message logger.error e.message
Alert.raise(e) Bugsnag.notify(e)
flash[:error] = e.message flash[:error] = e.message
ensure ensure
redirect_to request.referer redirect_to request.referer
@@ -189,22 +182,6 @@ module Spree
%w{capture void_transaction credit refund resend_authorization_email %w{capture void_transaction credit refund resend_authorization_email
capture_and_complete_order} capture_and_complete_order}
end end
def redeem_vine_voucher
vine_voucher_redeemer = Vine::VoucherRedeemerService.new(order: @order)
if vine_voucher_redeemer.redeem == false
# rubocop:disable Rails/DeprecatedActiveModelErrorsMethods
flash[:error] = if vine_voucher_redeemer.errors.keys.include?(:redeeming_failed)
vine_voucher_redeemer.errors[:redeeming_failed]
else
I18n.t('checkout.errors.voucher_redeeming_error')
end
# rubocop:enable Rails/DeprecatedActiveModelErrorsMethods
return false
end
true
end
end end
end end
end end

View File

@@ -11,7 +11,6 @@ module Spree
include OrderCyclesHelper include OrderCyclesHelper
include EnterprisesHelper include EnterprisesHelper
helper ::Admin::ProductsHelper helper ::Admin::ProductsHelper
helper Spree::Admin::TaxCategoriesHelper
before_action :load_data before_action :load_data
before_action :load_producers, only: [:index, :new] before_action :load_producers, only: [:index, :new]
@@ -214,7 +213,7 @@ module Spree
end end
def notify_bugsnag(error, product, variant) def notify_bugsnag(error, product, variant)
Alert.raise(error) do |report| Bugsnag.notify(error) do |report|
report.add_metadata(:product, report.add_metadata(:product,
{ product: product.attributes, variant: variant.attributes }) { product: product.attributes, variant: variant.attributes })
report.add_metadata(:product, :product_error, product.errors.first) unless product.valid? report.add_metadata(:product, :product_error, product.errors.first) unless product.valid?

View File

@@ -11,6 +11,8 @@ module Spree
# http://spreecommerce.com/blog/2010/11/02/json-hijacking-vulnerability/ # http://spreecommerce.com/blog/2010/11/02/json-hijacking-vulnerability/
before_action :check_json_authenticity, only: :index before_action :check_json_authenticity, only: :index
before_action :load_roles, only: [:edit, :new, :update, :create,
:generate_api_key, :clear_api_key]
def index def index
respond_with(@collection) do |format| respond_with(@collection) do |format|
@@ -20,9 +22,17 @@ module Spree
end end
def create def create
if params[:user]
roles = params[:user].delete("spree_role_ids")
end
@user = Spree::User.new(user_params) @user = Spree::User.new(user_params)
if @user.save if @user.save
if roles
@user.spree_roles = roles.compact_blank.collect{ |r| Spree::Role.find(r) }
end
flash[:success] = Spree.t(:created_successfully) flash[:success] = Spree.t(:created_successfully)
redirect_to edit_admin_user_path(@user) redirect_to edit_admin_user_path(@user)
else else
@@ -31,7 +41,15 @@ module Spree
end end
def update def update
if params[:user]
roles = params[:user].delete("spree_role_ids")
end
if @user.update(user_params) if @user.update(user_params)
if roles
@user.spree_roles = roles.compact_blank.collect{ |r| Spree::Role.find(r) }
end
flash[:success] = update_message flash[:success] = update_message
redirect_to edit_admin_user_path(@user) redirect_to edit_admin_user_path(@user)
else else
@@ -105,6 +123,10 @@ module Spree
sign_in(@user, event: :authentication, bypass: true) sign_in(@user, event: :authentication, bypass: true)
end end
def load_roles
@roles = Spree::Role.where(nil)
end
def new_email_unconfirmed? def new_email_unconfirmed?
params[:user][:email] != @user.email params[:user][:email] != @user.email
end end
@@ -115,7 +137,7 @@ module Spree
def user_params def user_params
::PermittedAttributes::User.new(params).call( ::PermittedAttributes::User.new(params).call(
%i[admin enterprise_limit show_api_key_view] %i[enterprise_limit show_api_key_view]
) )
end end
end end

View File

@@ -2,6 +2,7 @@
module Spree module Spree
class ApiKeysController < ::BaseController class ApiKeysController < ::BaseController
include Spree::Core::ControllerHelpers
include I18nHelper include I18nHelper
prepend_before_action :load_object prepend_before_action :load_object

View File

@@ -70,15 +70,14 @@ module Spree
@order.recreate_all_fees! # Enterprise fees on line items and on the order itself @order.recreate_all_fees! # Enterprise fees on line items and on the order itself
# Re apply the voucher # Re apply the voucher
OrderManagement::Order::Updater.new(@order).update_voucher VoucherAdjustmentsService.new(@order).update
@order.update_totals_and_states
if @order.complete? if @order.complete?
@order.update_payment_fees! @order.update_payment_fees!
@order.create_tax_charge! @order.create_tax_charge!
end end
AmendBackorderJob.perform_later(@order) if @order.completed?
respond_with(@order) do |format| respond_with(@order) do |format|
format.html do format.html do
if params.key?(:checkout) if params.key?(:checkout)

View File

@@ -2,6 +2,7 @@
module Spree module Spree
class UsersController < ::BaseController class UsersController < ::BaseController
include Spree::Core::ControllerHelpers
include I18nHelper include I18nHelper
include CablecarResponses include CablecarResponses

View File

@@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'open_food_network/error_logger'
require "spree/core/controller_helpers/auth" require "spree/core/controller_helpers/auth"
require "spree/core/controller_helpers/common" require "spree/core/controller_helpers/common"
require "spree/core/controller_helpers/order" require "spree/core/controller_helpers/order"
@@ -31,12 +32,12 @@ class UserRegistrationsController < Devise::RegistrationsController
associate_user associate_user
respond_to do |format| respond_to do |format|
format.json do format.js do
render json: { email: @user.email } render json: { email: @user.email }
end end
end end
rescue StandardError => e rescue StandardError => e
Alert.raise(e) OpenFoodNetwork::ErrorLogger.notify(e)
render_error(message: I18n.t('unknown_error', scope: I18N_SCOPE)) render_error(message: I18n.t('unknown_error', scope: I18N_SCOPE))
end end
@@ -51,7 +52,7 @@ class UserRegistrationsController < Devise::RegistrationsController
def render_error(errors = {}) def render_error(errors = {})
clean_up_passwords(resource) clean_up_passwords(resource)
respond_to do |format| respond_to do |format|
format.json do format.js do
render json: errors, status: :unauthorized render json: errors, status: :unauthorized
end end
end end

View File

@@ -4,16 +4,7 @@ class VoucherAdjustmentsController < BaseController
before_action :set_order before_action :set_order
def create def create
if voucher_params[:voucher_code].blank? if add_voucher
@order.errors.add(:voucher_code, I18n.t('checkout.errors.voucher_not_found'))
return render_error
end
voucher = load_voucher
return render_error unless valid_voucher?(voucher)
if add_voucher_to_order(voucher)
update_payment_section update_payment_section
elsif @order.errors.present? elsif @order.errors.present?
render_error render_error
@@ -39,28 +30,19 @@ class VoucherAdjustmentsController < BaseController
@order = current_order @order = current_order
end end
def valid_voucher?(voucher) def add_voucher
return false if @order.errors.present? if voucher_params[:voucher_code].blank?
@order.errors.add(:voucher_code, I18n.t('checkout.errors.voucher_not_found'))
return false
end
voucher = Voucher.find_by(code: voucher_params[:voucher_code], enterprise: @order.distributor)
if voucher.nil? if voucher.nil?
@order.errors.add(:voucher_code, I18n.t('checkout.errors.voucher_not_found')) @order.errors.add(:voucher_code, I18n.t('checkout.errors.voucher_not_found'))
return false return false
end end
if !voucher.valid?
@order.errors.add(
:voucher_code,
I18n.t(
'checkout.errors.create_voucher_error', error: voucher.errors.full_messages.to_sentence
)
)
return false
end
true
end
def add_voucher_to_order(voucher)
adjustment = voucher.create_adjustment(voucher.code, @order) adjustment = voucher.create_adjustment(voucher.code, @order)
unless adjustment.persisted? unless adjustment.persisted?
@@ -69,38 +51,14 @@ class VoucherAdjustmentsController < BaseController
return false return false
end end
# calculate_voucher_adjustment
clear_payments clear_payments
OrderManagement::Order::Updater.new(@order).update_voucher VoucherAdjustmentsService.new(@order).update
@order.update_totals_and_states
true true
end end
def load_voucher
voucher = Voucher.find_by(code: voucher_params[:voucher_code],
enterprise: @order.distributor)
return voucher unless voucher.nil? || voucher.is_a?(Vouchers::Vine)
vine_voucher
end
def vine_voucher
vine_voucher_validator = Vine::VoucherValidatorService.new(
voucher_code: voucher_params[:voucher_code], enterprise: @order.distributor
)
voucher = vine_voucher_validator.validate
return nil if vine_voucher_validator.errors[:not_found_voucher].present?
if vine_voucher_validator.errors.present?
@order.errors.add(:voucher_code, I18n.t('checkout.errors.add_voucher_error'))
return nil
end
voucher
end
def update_payment_section def update_payment_section
render cable_ready: cable_car.replace( render cable_ready: cable_car.replace(
selector: "#checkout-payment-methods", selector: "#checkout-payment-methods",

View File

@@ -14,7 +14,6 @@ module Admin
# e.g producer_options = [['producer name', id]] # e.g producer_options = [['producer name', id]]
product.variants.build do |new_variant| product.variants.build do |new_variant|
new_variant.supplier_id = producer_options.first.second if producer_options.one? new_variant.supplier_id = producer_options.first.second if producer_options.one?
new_variant.tax_category_id = product.variants.first.tax_category_id
end end
end end

View File

@@ -6,7 +6,7 @@ module SharedHelper
end end
def admin_user? def admin_user?
spree_current_user&.admin? spree_current_user&.has_spree_role? 'admin'
end end
def current_shop_products_path def current_shop_products_path

View File

@@ -1,20 +0,0 @@
# frozen_string_literal: true
module Spree
module Admin
module TaxCategoriesHelper
def tax_category_dropdown_options(require_tax_category)
if require_tax_category
{
include_blank: false,
selected: Spree::TaxCategory.find_by(is_default: true)&.id
}
else
{
include_blank: t(:none),
}
end
end
end
end
end

View File

@@ -3,7 +3,7 @@
module Spree module Spree
module BaseHelper module BaseHelper
def available_countries def available_countries
checkout_zone = Zone.find_by(name: ENV.fetch("CHECKOUT_ZONE", nil)) checkout_zone = Zone.find_by(name: Spree::Config[:checkout_zone])
countries = if checkout_zone && checkout_zone.kind == 'country' countries = if checkout_zone && checkout_zone.kind == 'country'
checkout_zone.countries checkout_zone.countries

View File

@@ -1,38 +1,98 @@
# frozen_string_literal: true # frozen_string_literal: true
# When orders are created, adjusted or cancelled, we need to amend # When orders are cancelled, we need to amend
# an existing backorder as well. # an existing backorder as well.
# We're not dealing with line item changes just yet.
class AmendBackorderJob < ApplicationJob class AmendBackorderJob < ApplicationJob
sidekiq_options retry: 0 sidekiq_options retry: 0
def self.schedule_bulk_update_for(orders)
# We can have one backorder per order cycle and distributor.
groups = orders.group_by { |order| [order.order_cycle, order.distributor] }
groups.each_value do |orders_with_same_backorder|
# We need to trigger only one update per backorder.
perform_later(orders_with_same_backorder.first)
end
end
def perform(order) def perform(order)
OrderLocker.lock_order_and_variants(order) do OrderLocker.lock_order_and_variants(order) do
amend_backorder(order) amend_backorder(order)
end end
end end
# The following is a mix of the BackorderJob and the CompleteBackorderJob.
# TODO: Move the common code into a re-usable service class.
def amend_backorder(order) def amend_backorder(order)
backorder = BackorderUpdater.new.amend_backorder(order) order_cycle = order.order_cycle
distributor = order.distributor
user = distributor.owner
items = backorderable_items(order)
if backorder return if items.empty?
user = order.distributor.owner
urls = nil # Not needed to send order. The backorder id is the URL.
FdcBackorderer.new(user, urls).send_order(backorder)
elsif !order.order_cycle.closed?
# We don't have an order to amend but the order cycle is or will open. # We are assuming that all variants are linked to the same wholesale
# We can assume that this job was triggered by an admin creating a new # shop and its catalog:
# order or adding backorderable items to an order. reference_link = items[0].variant.semantic_links[0].semantic_id
BackorderJob.new.place_backorder(order) urls = FdcUrlBuilder.new(reference_link)
orderer = FdcBackorderer.new(user, urls)
backorder = orderer.find_open_order(order)
variants = order_cycle.variants_distributed_by(distributor)
adjust_quantities(order_cycle, user, backorder, urls, variants)
FdcBackorderer.new(user, urls).send_order(backorder)
end
# Check if we have enough stock to reduce the backorder.
#
# Our local stock can increase when users cancel their orders.
# But stock levels could also have been adjusted manually. So we review all
# quantities before finalising the order.
def adjust_quantities(order_cycle, user, order, urls, variants)
broker = FdcOfferBroker.new(user, urls)
order.lines.each do |line|
line.quantity = line.quantity.to_i
wholesale_product_id = line.offer.offeredItem.semanticId
transformation = broker.wholesale_to_retail(wholesale_product_id)
linked_variant = variants.linked_to(transformation.retail_product_id)
# Assumption: If a transformation is present then we only sell the retail
# variant. If that can't be found, it was deleted and we'll ignore that
# for now.
next if linked_variant.nil?
# Find all line items for this order cycle
# Update quantity accordingly
if linked_variant.on_demand
release_superfluous_stock(line, linked_variant, transformation)
else
aggregate_final_quantities(order_cycle, line, linked_variant, transformation)
end
end
# Clean up empty lines:
order.lines.reject! { |line| line.quantity.zero? }
end
# We look at all linked variants.
def backorderable_items(order)
order.line_items.select do |item|
# TODO: scope variants to hub.
# We are only supporting producer stock at the moment.
item.variant.semantic_links.present?
end end
end end
def release_superfluous_stock(line, linked_variant, transformation)
# Note that a division of integers dismisses the remainder, like `floor`:
wholesale_items_contained_in_stock = linked_variant.on_hand / transformation.factor
# But maybe we didn't actually order that much:
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
line.quantity -= deductable_quantity
retail_stock_changes = deductable_quantity * transformation.factor
linked_variant.on_hand -= retail_stock_changes
end
def aggregate_final_quantities(order_cycle, line, variant, transformation)
orders = order_cycle.orders.invoiceable
quantity = Spree::LineItem.where(order: orders, variant:).sum(:quantity)
wholesale_quantity = (quantity.to_f / transformation.factor).ceil
line.quantity = wholesale_quantity
end
end end

View File

@@ -19,7 +19,9 @@ class BackorderJob < ApplicationJob
rescue StandardError => e rescue StandardError => e
# Errors here shouldn't affect the checkout. So let's report them # Errors here shouldn't affect the checkout. So let's report them
# separately: # separately:
Alert.raise_with_record(e, order) Bugsnag.notify(e) do |payload|
payload.add_metadata(:order, :order, order)
end
end end
def perform(order) def perform(order)
@@ -117,8 +119,7 @@ class BackorderJob < ApplicationJob
end end
def load_broker(user, urls) def load_broker(user, urls)
catalog = DfcCatalog.load(user, urls.catalog_url) FdcOfferBroker.new(user, urls)
FdcOfferBroker.new(catalog)
end end
def place_order(user, order, orderer, backorder) def place_order(user, order, orderer, backorder)

View File

@@ -24,7 +24,8 @@ class CompleteBackorderJob < ApplicationJob
urls = FdcUrlBuilder.new(order.lines[0].offer.offeredItem.semanticId) urls = FdcUrlBuilder.new(order.lines[0].offer.offeredItem.semanticId)
BackorderUpdater.new.update(order, user, distributor, order_cycle) variants = order_cycle.variants_distributed_by(distributor)
adjust_quantities(order_cycle, user, order, urls, variants)
FdcBackorderer.new(user, urls).complete_order(order) FdcBackorderer.new(user, urls).complete_order(order)
@@ -35,4 +36,55 @@ class CompleteBackorderJob < ApplicationJob
raise raise
end end
# Check if we have enough stock to reduce the backorder.
#
# Our local stock can increase when users cancel their orders.
# But stock levels could also have been adjusted manually. So we review all
# quantities before finalising the order.
def adjust_quantities(order_cycle, user, order, urls, variants)
broker = FdcOfferBroker.new(user, urls)
order.lines.each do |line|
line.quantity = line.quantity.to_i
wholesale_product_id = line.offer.offeredItem.semanticId
transformation = broker.wholesale_to_retail(wholesale_product_id)
linked_variant = variants.linked_to(transformation.retail_product_id)
# Assumption: If a transformation is present then we only sell the retail
# variant. If that can't be found, it was deleted and we'll ignore that
# for now.
next if linked_variant.nil?
# Find all line items for this order cycle
# Update quantity accordingly
if linked_variant.on_demand
release_superfluous_stock(line, linked_variant, transformation)
else
aggregate_final_quantities(order_cycle, line, linked_variant, transformation)
end
end
# Clean up empty lines:
order.lines.reject! { |line| line.quantity.zero? }
end
def release_superfluous_stock(line, linked_variant, transformation)
# Note that a division of integers dismisses the remainder, like `floor`:
wholesale_items_contained_in_stock = linked_variant.on_hand / transformation.factor
# But maybe we didn't actually order that much:
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
line.quantity -= deductable_quantity
retail_stock_changes = deductable_quantity * transformation.factor
linked_variant.on_hand -= retail_stock_changes
end
def aggregate_final_quantities(order_cycle, line, variant, transformation)
orders = order_cycle.orders.invoiceable
quantity = Spree::LineItem.where(order: orders, variant:).sum(:quantity)
wholesale_quantity = (quantity.to_f / transformation.factor).ceil
line.quantity = wholesale_quantity
end
end end

View File

@@ -22,8 +22,11 @@ class ReportJob < ApplicationJob
broadcast_result(channel, format, blob) if channel broadcast_result(channel, format, blob) if channel
rescue StandardError => e rescue StandardError => e
Alert.raise(e, { report: { report_class:, user:, params:, format: } }) Bugsnag.notify(e) do |payload|
Rails.logger.error(e.message) payload.add_metadata :report, {
report_class:, user:, params:, format:
}
end
broadcast_error(channel) broadcast_error(channel)
end end

View File

@@ -8,12 +8,30 @@ class StockSyncJob < ApplicationJob
# product. These variants are rare though and we check first before we # product. These variants are rare though and we check first before we
# enqueue a new job. That should save some time loading the order with # enqueue a new job. That should save some time loading the order with
# all the stock data to make this decision. # all the stock data to make this decision.
def self.sync_linked_catalogs_later(order) def self.sync_linked_catalogs(order)
sync_catalogs_by_perform_method(order, :perform_later) user = order.distributor.owner
catalog_ids(order).each do |catalog_id|
perform_later(user, catalog_id)
end
rescue StandardError => e
# Errors here shouldn't affect the shopping. So let's report them
# separately:
Bugsnag.notify(e) do |payload|
payload.add_metadata(:order, :order, order)
end
end end
def self.sync_linked_catalogs_now(order) def self.sync_linked_catalogs_now(order)
sync_catalogs_by_perform_method(order, :perform_now) user = order.distributor.owner
catalog_ids(order).each do |catalog_id|
perform_now(user, catalog_id)
end
rescue StandardError => e
# Errors here shouldn't affect the shopping. So let's report them
# separately:
Bugsnag.notify(e) do |payload|
payload.add_metadata(:order, :order, order)
end
end end
def self.catalog_ids(order) def self.catalog_ids(order)
@@ -26,10 +44,7 @@ class StockSyncJob < ApplicationJob
end end
def perform(user, catalog_id) def perform(user, catalog_id)
catalog = DfcCatalog.load(user, catalog_id) products = load_products(user, catalog_id)
catalog.apply_wholesale_values!
products = catalog.products
products_by_id = products.index_by(&:semanticId) products_by_id = products.index_by(&:semanticId)
product_ids = products_by_id.keys product_ids = products_by_id.keys
variants = linked_variants(user.enterprises, product_ids) variants = linked_variants(user.enterprises, product_ids)
@@ -47,23 +62,18 @@ class StockSyncJob < ApplicationJob
end end
end end
def load_products(user, catalog_id)
json_catalog = DfcRequest.new(user).call(catalog_id)
graph = DfcIo.import(json_catalog)
graph.select do |subject|
subject.is_a? DataFoodConsortium::Connector::SuppliedProduct
end
end
def linked_variants(enterprises, product_ids) def linked_variants(enterprises, product_ids)
Spree::Variant.where(supplier: enterprises) Spree::Variant.where(supplier: enterprises)
.includes(:semantic_links).references(:semantic_links) .includes(:semantic_links).references(:semantic_links)
.where(semantic_links: { semantic_id: product_ids }) .where(semantic_links: { semantic_id: product_ids })
end end
def self.sync_catalogs_by_perform_method(order, perform_method)
distributor = order.distributor
return unless distributor
user = distributor.owner
catalog_ids(order).each do |catalog_id|
public_send(perform_method, user, catalog_id)
end
rescue StandardError => e
# Errors here shouldn't affect the shopping. So let's report them
# separately:
Alert.raise_with_record(e, order)
end
end end

View File

@@ -55,7 +55,9 @@ class SubscriptionConfirmJob < ApplicationJob
if order.errors.any? if order.errors.any?
send_failed_payment_email(order) send_failed_payment_email(order)
else else
Alert.raise_with_record(e, order) Bugsnag.notify(e) do |payload|
payload.add_metadata :order, :order, order
end
send_failed_payment_email(order, e.message) send_failed_payment_email(order, e.message)
end end
end end
@@ -106,6 +108,8 @@ class SubscriptionConfirmJob < ApplicationJob
record_and_log_error(:failed_payment, order, error_message) record_and_log_error(:failed_payment, order, error_message)
SubscriptionMailer.failed_payment_email(order).deliver_now SubscriptionMailer.failed_payment_email(order).deliver_now
rescue StandardError => e rescue StandardError => e
Alert.raise(e, { subscription_data: { order:, error_message: } }) Bugsnag.notify(e) do |payload|
payload.add_metadata :subscription_data, { order:, error_message: }
end
end end
end end

View File

@@ -18,7 +18,6 @@ class ApplicationMailer < ActionMailer::Base
def roadie_options def roadie_options
# This lets us specify assets using relative paths in email templates # This lets us specify assets using relative paths in email templates
url = URI(main_app.root_url) super.merge(url_options: { host: URI(main_app.root_url).host })
super.merge(url_options: { host: url.host, port: url.port })
end end
end end

View File

@@ -23,8 +23,7 @@ class EnterpriseMailer < ApplicationMailer
I18n.with_locale valid_locale(@enterprise.owner) do I18n.with_locale valid_locale(@enterprise.owner) do
subject = t('enterprise_mailer.invite_manager.subject', enterprise: @enterprise.name) subject = t('enterprise_mailer.invite_manager.subject', enterprise: @enterprise.name)
mail(to: user.email, mail(to: user.email,
subject:, subject:)
reply_to: @enterprise.contact.email)
end end
end end

View File

@@ -5,13 +5,10 @@ class PaymentMailer < ApplicationMailer
def authorize_payment(payment) def authorize_payment(payment)
@payment = payment @payment = payment
@order = @payment.order
subject = I18n.t('spree.payment_mailer.authorize_payment.subject', subject = I18n.t('spree.payment_mailer.authorize_payment.subject',
distributor: @order.distributor.name) distributor: @payment.order.distributor.name)
I18n.with_locale valid_locale(@order.user) do I18n.with_locale valid_locale(@payment.order.user) do
mail(to: @order.email, mail(to: payment.order.email, subject:)
subject:,
reply_to: @order.distributor.contact.email)
end end
end end

View File

@@ -79,19 +79,14 @@ class ProducerMailer < ApplicationMailer
def set_customer_data(line_items) def set_customer_data(line_items)
return unless @coordinator.show_customer_names_to_suppliers? return unless @coordinator.show_customer_names_to_suppliers?
@display_business_name = false
line_items.map do |line_item| line_items.map do |line_item|
customer_code = line_item.order.customer&.code
@display_business_name = true if customer_code.present?
{ {
sku: line_item.variant.sku, sku: line_item.variant.sku,
supplier_name: line_item.variant.supplier.name, supplier_name: line_item.variant.supplier.name,
product_and_full_name: line_item.product_and_full_name, product_and_full_name: line_item.product_and_full_name,
quantity: line_item.quantity, quantity: line_item.quantity,
first_name: line_item.order.billing_address.first_name, first_name: line_item.order.billing_address.first_name,
last_name: line_item.order.billing_address.last_name, last_name: line_item.order.billing_address.last_name
business_name: customer_code,
} }
end.sort_by { |line_item| [line_item[:last_name].downcase, line_item[:first_name].downcase] } end.sort_by { |line_item| [line_item[:last_name].downcase, line_item[:first_name].downcase] }
end end

View File

@@ -13,8 +13,7 @@ module Spree
@order = find_order(order_or_order_id) @order = find_order(order_or_order_id)
I18n.with_locale valid_locale(@order.user) do I18n.with_locale valid_locale(@order.user) do
mail(to: @order.email, mail(to: @order.email,
subject: mail_subject(t('spree.order_mailer.cancel_email.subject'), resend), subject: mail_subject(t('spree.order_mailer.cancel_email.subject'), resend))
reply_to: @order.distributor.contact.email)
end end
end end

View File

@@ -5,11 +5,8 @@ module Spree
def shipped_email(shipment, delivery:) def shipped_email(shipment, delivery:)
@shipment = shipment.respond_to?(:id) ? shipment : Spree::Shipment.find(shipment) @shipment = shipment.respond_to?(:id) ? shipment : Spree::Shipment.find(shipment)
@delivery = delivery @delivery = delivery
@order = @shipment.order
subject = base_subject subject = base_subject
mail(to: @order.email, mail(to: @shipment.order.email, subject:)
subject:,
reply_to: @order.distributor.contact.email)
end end
private private

View File

@@ -28,7 +28,7 @@ module Calculator
# In theory it should never be called any more after this has been deployed. # In theory it should never be called any more after this has been deployed.
# If the message below doesn't show up in Bugsnag, we can safely delete this method and all # If the message below doesn't show up in Bugsnag, we can safely delete this method and all
# the related methods below it. # the related methods below it.
Alert.raise("Calculator::DefaultTax was called with legacy tax calculations") Bugsnag.notify("Calculator::DefaultTax was called with legacy tax calculations")
calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(order.distributor, calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(order.distributor,
order.order_cycle) order.order_cycle)

View File

@@ -67,18 +67,6 @@ module VariantStock
end end
end end
def on_demand_desired_or_current
return on_demand_desired if new_record?
on_demand
end
def on_hand_desired_or_current
return on_hand_desired if new_record?
on_hand
end
# Moving Spree::Stock::Quantifier.can_supply? to the variant enables us # Moving Spree::Stock::Quantifier.can_supply? to the variant enables us
# to override this behaviour for variant overrides # to override this behaviour for variant overrides
# We can have this responsibility here in the variant because there is # We can have this responsibility here in the variant because there is
@@ -90,6 +78,11 @@ module VariantStock
on_demand || total_on_hand >= quantity on_demand || total_on_hand >= quantity
end end
# Moving Spree::StockLocation.fill_status to the variant enables us
# to override this behaviour for variant overrides
# We can have this responsibility here in the variant because there is
# only one stock item per variant
#
# Here we depend only on variant.total_on_hand and variant.on_demand. # Here we depend only on variant.total_on_hand and variant.on_demand.
# This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand. # This way, variant_overrides only need to override variant.total_on_hand and variant.on_demand.
def fill_status(quantity) def fill_status(quantity)
@@ -114,15 +107,13 @@ module VariantStock
raise_error_if_no_stock_item_available raise_error_if_no_stock_item_available
# Creates a stock movement: it updates stock_item.count_on_hand and fills backorders # Creates a stock movement: it updates stock_item.count_on_hand and fills backorders
#
# This is the original Spree::StockLocation#move,
# except that we raise an error if the stock item is missing,
# because, unlike Spree, we should always have exactly one stock item per variant.
stock_item.stock_movements.create!(quantity:, originator:) stock_item.stock_movements.create!(quantity:, originator:)
end end
# There shouldn't be any other stock items, because we should
# have only one stock location.
def stock_item
stock_items.first
end
private private
# Persists the single stock item associated to this variant. As defined in # Persists the single stock item associated to this variant. As defined in
@@ -148,4 +139,10 @@ module VariantStock
def overwrite_stock_levels(new_level) def overwrite_stock_levels(new_level)
stock_item.adjust_count_on_hand(new_level.to_i - stock_item.count_on_hand) stock_item.adjust_count_on_hand(new_level.to_i - stock_item.count_on_hand)
end end
# There shouldn't be any other stock items, because we should
# have only one stock location.
def stock_item
stock_items.first
end
end end

View File

@@ -1,31 +0,0 @@
# frozen_string_literal: true
require "active_support/concern"
module Vouchers
module FlatRatable
extend ActiveSupport::Concern
included do
validates :amount,
presence: true,
numericality: { greater_than: 0 }
end
def display_value
Spree::Money.new(amount)
end
# We limit adjustment to the maximum amount needed to cover the order, ie if the voucher
# covers more than the order.total we only need to create an adjustment covering the order.total
def compute_amount(order)
-amount.clamp(0, order.pre_discount_total)
end
def rate(order)
amount = compute_amount(order)
amount / order.pre_discount_total
end
end
end

View File

@@ -218,7 +218,7 @@ class Enterprise < ApplicationRecord
} }
scope :managed_by, lambda { |user| scope :managed_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
joins(:enterprise_roles).where(enterprise_roles: { user_id: user.id }) joins(:enterprise_roles).where(enterprise_roles: { user_id: user.id })
@@ -481,7 +481,7 @@ class Enterprise < ApplicationRecord
image_variant_url_for(image.variant(name)) image_variant_url_for(image.variant(name))
rescue StandardError => e rescue StandardError => e
Alert.raise "Enterprise ##{id} #{image.try(:name)} error: #{e.message}" Bugsnag.notify "Enterprise ##{id} #{image.try(:name)} error: #{e.message}"
Rails.logger.error(e.message) Rails.logger.error(e.message)
nil nil

View File

@@ -28,7 +28,7 @@ class EnterpriseFee < ApplicationRecord
scope :for_enterprises, lambda { |enterprises| where(enterprise_id: enterprises) } scope :for_enterprises, lambda { |enterprises| where(enterprise_id: enterprises) }
scope :managed_by, lambda { |user| scope :managed_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
where(enterprise_id: user.enterprises.select(&:id)) where(enterprise_id: user.enterprises.select(&:id))

View File

@@ -38,7 +38,7 @@ class EnterpriseGroup < ApplicationRecord
scope :by_position, -> { order('position ASC') } scope :by_position, -> { order('position ASC') }
scope :on_front_page, -> { where(on_front_page: true) } scope :on_front_page, -> { where(on_front_page: true) }
scope :managed_by, lambda { |user| scope :managed_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
where(owner_id: user.id) where(owner_id: user.id)

View File

@@ -75,7 +75,7 @@ class Exchange < ApplicationRecord
} }
scope :managed_by, lambda { |user| scope :managed_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
joins("LEFT JOIN enterprises senders ON senders.id = exchanges.sender_id"). joins("LEFT JOIN enterprises senders ON senders.id = exchanges.sender_id").

View File

@@ -32,6 +32,6 @@ class Invoice < ApplicationRecord
end end
def previous_invoice def previous_invoice
order.invoices.where(id: ...id).first order.invoices.where("id < ?", id).first
end end
end end

View File

@@ -53,7 +53,7 @@ class OrderCycle < ApplicationRecord
Time.zone.now, Time.zone.now,
Time.zone.now) Time.zone.now)
} }
scope :active_or_complete, lambda { where(order_cycles: { orders_open_at: ..Time.zone.now }) } scope :active_or_complete, lambda { where('order_cycles.orders_open_at <= ?', Time.zone.now) }
scope :inactive, lambda { scope :inactive, lambda {
where('order_cycles.orders_open_at > ? OR order_cycles.orders_close_at < ?', where('order_cycles.orders_open_at > ? OR order_cycles.orders_close_at < ?',
Time.zone.now, Time.zone.now,
@@ -64,8 +64,8 @@ class OrderCycle < ApplicationRecord
where('order_cycles.orders_close_at > ? OR order_cycles.orders_close_at IS NULL', Time.zone.now) where('order_cycles.orders_close_at > ? OR order_cycles.orders_close_at IS NULL', Time.zone.now)
} }
scope :closed, lambda { scope :closed, lambda {
where(order_cycles: { orders_close_at: ...Time.zone.now }) where('order_cycles.orders_close_at < ?',
.order("order_cycles.orders_close_at DESC") Time.zone.now).order("order_cycles.orders_close_at DESC")
} }
scope :unprocessed, -> { where(processed_at: nil) } scope :unprocessed, -> { where(processed_at: nil) }
scope :undated, -> { where('order_cycles.orders_open_at IS NULL OR orders_close_at IS NULL') } scope :undated, -> { where('order_cycles.orders_open_at IS NULL OR orders_close_at IS NULL') }
@@ -84,7 +84,7 @@ class OrderCycle < ApplicationRecord
} }
scope :managed_by, lambda { |user| scope :managed_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
where(coordinator_id: user.enterprises.to_a) where(coordinator_id: user.enterprises.to_a)
@@ -93,7 +93,7 @@ class OrderCycle < ApplicationRecord
# Return order cycles that user coordinates, sends to or receives from # Return order cycles that user coordinates, sends to or receives from
scope :visible_by, lambda { |user| scope :visible_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
with_exchanging_enterprises_outer. with_exchanging_enterprises_outer.

View File

@@ -79,9 +79,10 @@ module ProductImport
if entry.attributes['on_hand'].present? if entry.attributes['on_hand'].present?
new_variant.on_hand = entry.attributes['on_hand'] new_variant.on_hand = entry.attributes['on_hand']
end end
check_on_hand_nil(entry, new_variant)
end end
check_on_hand_nil(entry, new_variant)
if new_variant.valid? if new_variant.valid?
entry.product_object = new_variant entry.product_object = new_variant
entry.validates_as = 'new_variant' unless entry.errors? entry.validates_as = 'new_variant' unless entry.errors?
@@ -160,7 +161,7 @@ module ProductImport
end end
def unit_fields_validation(entry) def unit_fields_validation(entry)
unit_types = ['mg', 'g', 'kg', 'oz', 'lb', 't', 'ml', 'cl', 'dl', 'l', 'kl', 'gal', ''] unit_types = ['g', 'oz', 'lb', 'kg', 't', 'ml', 'l', 'kl', '']
if entry.units.blank? if entry.units.blank?
mark_as_invalid(entry, attribute: 'units', mark_as_invalid(entry, attribute: 'units',
@@ -296,7 +297,7 @@ module ProductImport
unscaled_units = entry.unscaled_units.to_f || 0 unscaled_units = entry.unscaled_units.to_f || 0
entry.unit_value = unscaled_units * unit_scale unless unit_scale.nil? entry.unit_value = unscaled_units * unit_scale unless unit_scale.nil?
if entry.match_variant?(existing_variant) if entry.match_inventory_variant?(existing_variant)
variant_override = create_inventory_item(entry, existing_variant) variant_override = create_inventory_item(entry, existing_variant)
return validate_inventory_item(entry, variant_override) return validate_inventory_item(entry, variant_override)
end end

View File

@@ -85,6 +85,10 @@ module ProductImport
end end
def match_variant?(variant) def match_variant?(variant)
match_display_name?(variant) && variant.unit_value.to_d == unscaled_units.to_d
end
def match_inventory_variant?(variant)
match_display_name?(variant) && variant.unit_value.to_d == unit_value.to_d match_display_name?(variant) && variant.unit_value.to_d == unit_value.to_d
end end

View File

@@ -32,18 +32,14 @@ module ProductImport
def unit_scales def unit_scales
{ {
'mg' => { scale: 0.001, unit: 'weight' },
'g' => { scale: 1, unit: 'weight' }, 'g' => { scale: 1, unit: 'weight' },
'kg' => { scale: 1000, unit: 'weight' }, 'kg' => { scale: 1000, unit: 'weight' },
'oz' => { scale: 28.35, unit: 'weight' }, 'oz' => { scale: 28.35, unit: 'weight' },
'lb' => { scale: 453.6, unit: 'weight' }, 'lb' => { scale: 453.6, unit: 'weight' },
't' => { scale: 1_000_000, unit: 'weight' }, 't' => { scale: 1_000_000, unit: 'weight' },
'ml' => { scale: 0.001, unit: 'volume' }, 'ml' => { scale: 0.001, unit: 'volume' },
'cl' => { scale: 0.01, unit: 'volume' },
'dl' => { scale: 0.1, unit: 'volume' },
'l' => { scale: 1, unit: 'volume' }, 'l' => { scale: 1, unit: 'volume' },
'kl' => { scale: 1000, unit: 'volume' }, 'kl' => { scale: 1000, unit: 'volume' }
'gal' => { scale: 4.54609, unit: 'volume' },
} }
end end

View File

@@ -18,7 +18,7 @@ module Spree
user ||= Spree::User.new user ||= Spree::User.new
if user.try(:admin?) if user.respond_to?(:has_spree_role?) && user.has_spree_role?('admin')
can :manage, :all can :manage, :all
else else
can [:index, :read], Country can [:index, :read], Country
@@ -238,7 +238,7 @@ module Spree
can [:admin, :index, :guide, :import, :save, :save_data, can [:admin, :index, :guide, :import, :save, :save_data,
:validate_data, :reset_absent_products], ProductImport::ProductImporter :validate_data, :reset_absent_products], ProductImport::ProductImporter
can [:admin, :index, :import], ::Admin::DfcProductImportsController can [:admin, :index], ::Admin::DfcProductImportsController
# Reports page # Reports page
can [:admin, :index, :show, :create], ::Admin::ReportsController can [:admin, :index, :show, :create], ::Admin::ReportsController

View File

@@ -126,7 +126,7 @@ module Spree
end end
def address_and_city def address_and_city
[address1, address2, city].compact_blank.join(' ') [address1, address2, city].select(&:present?).join(' ')
end end
private private
@@ -176,7 +176,7 @@ module Spree
end end
def render_address(parts) def render_address(parts)
parts.compact_blank.join(', ') parts.select(&:present?).join(', ')
end end
end end
end end

View File

@@ -33,10 +33,14 @@ module Spree
preference :allow_backorder_shipping, :boolean, default: false preference :allow_backorder_shipping, :boolean, default: false
preference :allow_checkout_on_gateway_error, :boolean, default: false preference :allow_checkout_on_gateway_error, :boolean, default: false
preference :allow_guest_checkout, :boolean, default: true preference :allow_guest_checkout, :boolean, default: true
# Replace with the name of a zone if you would like to limit the countries
preference :checkout_zone, :string, default: nil
preference :currency, :string, default: "USD"
preference :currency_decimal_mark, :string, default: "." preference :currency_decimal_mark, :string, default: "."
preference :currency_symbol_position, :string, default: "before" preference :currency_symbol_position, :string, default: "before"
preference :currency_thousands_separator, :string, default: "," preference :currency_thousands_separator, :string, default: ","
preference :display_currency, :boolean, default: false preference :display_currency, :boolean, default: false
preference :default_country_id, :integer
preference :default_meta_description, :string, default: 'OFN demo site' preference :default_meta_description, :string, default: 'OFN demo site'
preference :default_meta_keywords, :string, default: 'ofn, demo' preference :default_meta_keywords, :string, default: 'ofn, demo'
preference :default_seo_title, :string, default: '' preference :default_seo_title, :string, default: ''

View File

@@ -34,7 +34,7 @@ module Spree
image_variant_url_for(variant(size)) image_variant_url_for(variant(size))
rescue StandardError => e rescue StandardError => e
Alert.raise "Product ##{viewable_id} Image ##{id} error: #{e.message}" Bugsnag.notify "Product ##{viewable_id} Image ##{id} error: #{e.message}"
Rails.logger.error(e.message) Rails.logger.error(e.message)
self.class.default_image_url(size) self.class.default_image_url(size)

View File

@@ -35,6 +35,18 @@ module Spree
end end
end end
# This was refactored from a simpler query because the previous implementation
# lead to issues once users tried to modify the objects returned. That's due
# to ActiveRecord `joins(shipment: :stock_location)` only return readonly
# objects
#
# Returns an array of backordered inventory units as per a given stock item
def self.backordered_for_stock_item(stock_item)
backordered_per_variant(stock_item).select do |unit|
unit.shipment.stock_location == stock_item.stock_location
end
end
def self.finalize_units!(inventory_units) def self.finalize_units!(inventory_units)
inventory_units.map do |iu| inventory_units.map do |iu|
iu.update_columns( iu.update_columns(
@@ -45,7 +57,8 @@ module Spree
end end
def find_stock_item def find_stock_item
Spree::StockItem.find_by(variant_id:) Spree::StockItem.find_by(stock_location_id: shipment.stock_location_id,
variant_id:)
end end
private private

View File

@@ -55,7 +55,7 @@ module Spree
# -- Scopes # -- Scopes
scope :managed_by, lambda { |user| scope :managed_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
# Find line items that are from orders distributed by the user or supplied by the user # Find line items that are from orders distributed by the user or supplied by the user

View File

@@ -106,6 +106,7 @@ module Spree
before_validation :clone_billing_address, if: :use_billing? before_validation :clone_billing_address, if: :use_billing?
before_validation :ensure_customer before_validation :ensure_customer
before_save :update_shipping_fees!, if: :complete?
before_save :update_payment_fees!, if: :complete? before_save :update_payment_fees!, if: :complete?
before_create :link_by_email before_create :link_by_email
@@ -124,7 +125,7 @@ module Spree
} }
scope :managed_by, lambda { |user| scope :managed_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
# Find orders that are distributed by the user or have products supplied by the user # Find orders that are distributed by the user or have products supplied by the user
@@ -139,7 +140,7 @@ module Spree
} }
scope :distributed_by_user, lambda { |user| scope :distributed_by_user, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
where(spree_orders: { distributor_id: user.enterprises.select(&:id) }) where(spree_orders: { distributor_id: user.enterprises.select(&:id) })
@@ -391,6 +392,8 @@ module Spree
deliver_order_confirmation_email deliver_order_confirmation_email
BackorderJob.check_stock(self)
state_changes.create( state_changes.create(
previous_state: 'cart', previous_state: 'cart',
next_state: 'complete', next_state: 'complete',
@@ -532,7 +535,7 @@ module Spree
# because an outdated shipping fee is not as bad as a lost payment. # 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. # And the shipping fee is already up-to-date when this error occurs.
# https://github.com/openfoodfoundation/openfoodnetwork/issues/3924 # https://github.com/openfoodfoundation/openfoodnetwork/issues/3924
Alert.raise(e) do |report| Bugsnag.notify(e) do |report|
report.add_metadata(:order, attributes) report.add_metadata(:order, attributes)
report.add_metadata(:shipment, shipment.attributes) report.add_metadata(:shipment, shipment.attributes)
report.add_metadata(:shipment_in_db, Spree::Shipment.find_by(id: shipment.id).attributes) report.add_metadata(:shipment_in_db, Spree::Shipment.find_by(id: shipment.id).attributes)
@@ -671,6 +674,10 @@ module Spree
break if payment_total >= total break if payment_total >= total
yield payment yield payment
if payment.completed?
self.payment_total += payment.amount
end
end end
end end

View File

@@ -142,6 +142,8 @@ module Spree
OrderMailer.cancel_email(id).deliver_later if send_cancellation_email OrderMailer.cancel_email(id).deliver_later if send_cancellation_email
update(payment_state: updater.update_payment_state) update(payment_state: updater.update_payment_state)
AmendBackorderJob.perform_later(self)
end end
def after_resume def after_resume

View File

@@ -38,7 +38,6 @@ module Spree
line_item.price = variant.price line_item.price = variant.price
order.line_items << line_item order.line_items << line_item
end end
update_shipment
order.reload order.reload
line_item line_item

View File

@@ -12,8 +12,8 @@ module Spree
# have inventory assigned via +order.create_proposed_shipment+) or when # have inventory assigned via +order.create_proposed_shipment+) or when
# shipment is explicitly passed # shipment is explicitly passed
# #
# In case shipment is passed stock should only be adjusted # In case shipment is passed the stock location should only unstock or
# if the order is completed. That is so because stock items # restock items if the order is completed. That is so because stock items
# are always unstocked when the order is completed through +shipment.finalize+ # are always unstocked when the order is completed through +shipment.finalize+
def verify(line_item, shipment = nil) def verify(line_item, shipment = nil)
if order.completed? || shipment.present? if order.completed? || shipment.present?
@@ -60,24 +60,27 @@ module Spree
# Returns either one of the shipment: # Returns either one of the shipment:
# #
# first unshipped that already includes this variant # first unshipped that already includes this variant
# first unshipped that's leaving from a stock_location that stocks this variant
def determine_target_shipment(variant) def determine_target_shipment(variant)
target_shipment = order.shipments.detect do |shipment| target_shipment = order.shipments.detect do |shipment|
(shipment.ready? || shipment.pending?) && shipment.contains?(variant) (shipment.ready? || shipment.pending?) && shipment.contains?(variant)
end end
target_shipment || order.shipments.detect do |shipment| target_shipment || order.shipments.detect do |shipment|
shipment.ready? || shipment.pending? (shipment.ready? || shipment.pending?) &&
variant.stock_location_ids.include?(shipment.stock_location_id)
end end
end end
def add_to_shipment(shipment, variant, quantity) def add_to_shipment(shipment, variant, quantity)
on_hand, back_order = variant.fill_status(quantity) on_hand, back_order = shipment.stock_location.fill_status(variant, quantity)
on_hand.times { shipment.set_up_inventory('on_hand', variant, order) } on_hand.times { shipment.set_up_inventory('on_hand', variant, order) }
back_order.times { shipment.set_up_inventory('backordered', variant, order) } back_order.times { shipment.set_up_inventory('backordered', variant, order) }
# adding to this shipment, and removing from stock_location
if order.completed? if order.completed?
variant.move(-quantity, shipment) shipment.stock_location.unstock(variant, quantity, shipment)
end end
quantity quantity
@@ -100,8 +103,9 @@ module Spree
end end
shipment.destroy if shipment.inventory_units.reload.count == 0 shipment.destroy if shipment.inventory_units.reload.count == 0
# removing this from shipment, and adding to stock_location
if order.completed? && restock_item if order.completed? && restock_item
variant.move(removed_quantity, shipment) shipment.stock_location.restock variant, removed_quantity, shipment
end end
removed_quantity removed_quantity

View File

@@ -36,7 +36,6 @@ module Spree
searchable_scopes :active, :with_properties searchable_scopes :active, :with_properties
has_one :image, class_name: "Spree::Image", as: :viewable, dependent: :destroy has_one :image, class_name: "Spree::Image", as: :viewable, dependent: :destroy
has_one :semantic_link, as: :subject, dependent: :delete
has_many :product_properties, dependent: :destroy has_many :product_properties, dependent: :destroy
has_many :properties, through: :product_properties has_many :properties, through: :product_properties
@@ -169,7 +168,7 @@ module Spree
scope :by_name, -> { order('spree_products.name') } scope :by_name, -> { order('spree_products.name') }
scope :managed_by, lambda { |user| scope :managed_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
in_supplier(user.enterprises) in_supplier(user.enterprises)

8
app/models/spree/role.rb Normal file
View File

@@ -0,0 +1,8 @@
# frozen_string_literal: true
module Spree
class Role < ApplicationRecord
has_and_belongs_to_many :users, join_table: 'spree_roles_users',
class_name: "Spree::User"
end
end

View File

@@ -5,10 +5,10 @@ require 'ostruct'
module Spree module Spree
class Shipment < ApplicationRecord class Shipment < ApplicationRecord
self.belongs_to_required_by_default = false self.belongs_to_required_by_default = false
self.ignored_columns += [:stock_location_id]
belongs_to :order, class_name: 'Spree::Order' belongs_to :order, class_name: 'Spree::Order'
belongs_to :address, class_name: 'Spree::Address' belongs_to :address, class_name: 'Spree::Address'
belongs_to :stock_location, class_name: 'Spree::StockLocation'
has_many :shipping_rates, dependent: :delete_all has_many :shipping_rates, dependent: :delete_all
has_many :shipping_methods, through: :shipping_rates has_many :shipping_methods, through: :shipping_rates
@@ -257,7 +257,7 @@ module Spree
end end
def to_package def to_package
package = OrderManagement::Stock::Package.new(order) package = OrderManagement::Stock::Package.new(stock_location, order)
grouped_inventory_units = inventory_units.includes(:variant).group_by do |iu| grouped_inventory_units = inventory_units.includes(:variant).group_by do |iu|
[iu.variant, iu.state_name] [iu.variant, iu.state_name]
end end
@@ -313,11 +313,11 @@ module Spree
end end
def manifest_unstock(item) def manifest_unstock(item)
item.variant.move(-1 * item.quantity, self) stock_location.unstock item.variant, item.quantity, self
end end
def manifest_restock(item) def manifest_restock(item)
item.variant.move(item.quantity, self) stock_location.restock item.variant, item.quantity, self
end end
def generate_shipment_number def generate_shipment_number

View File

@@ -37,7 +37,7 @@ module Spree
after_save :touch_distributors after_save :touch_distributors
scope :managed_by, lambda { |user| scope :managed_by, lambda { |user|
if user.admin? if user.has_spree_role?('admin')
where(nil) where(nil)
else else
joins(:distributors). joins(:distributors).

View File

@@ -2,21 +2,20 @@
module Spree module Spree
class StockItem < ApplicationRecord class StockItem < ApplicationRecord
self.ignored_columns += [:stock_location_id]
acts_as_paranoid acts_as_paranoid
belongs_to :stock_location, class_name: 'Spree::StockLocation', inverse_of: :stock_items
belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant' belongs_to :variant, -> { with_deleted }, class_name: 'Spree::Variant'
has_many :stock_movements, dependent: :destroy has_many :stock_movements, dependent: :destroy
validates :variant_id, uniqueness: { scope: [:deleted_at] } validates :variant_id, uniqueness: { scope: [:stock_location_id, :deleted_at] }
validates :count_on_hand, numericality: { greater_than_or_equal_to: 0, unless: :backorderable? } validates :count_on_hand, numericality: { greater_than_or_equal_to: 0, unless: :backorderable? }
delegate :weight, to: :variant delegate :weight, to: :variant
delegate :name, to: :variant, prefix: true delegate :name, to: :variant, prefix: true
def backordered_inventory_units def backordered_inventory_units
Spree::InventoryUnit.backordered_per_variant(self) Spree::InventoryUnit.backordered_for_stock_item(self)
end end
def adjust_count_on_hand(value) def adjust_count_on_hand(value)

View File

@@ -0,0 +1,57 @@
# frozen_string_literal: true
module Spree
class StockLocation < ApplicationRecord
self.belongs_to_required_by_default = false
self.ignored_columns += [:backorderable_default, :active]
has_many :stock_items, dependent: :delete_all, inverse_of: :stock_location
has_many :stock_movements, through: :stock_items
belongs_to :state, class_name: 'Spree::State'
belongs_to :country, class_name: 'Spree::Country'
validates :name, presence: true
after_create :create_stock_items
# Wrapper for creating a new stock item respecting the backorderable config
def stock_item(variant)
stock_items.where(variant_id: variant).order(:id).first
end
def stock_item_or_create(variant)
stock_item(variant) || stock_items.create(variant:)
end
def count_on_hand(variant)
stock_item(variant).try(:count_on_hand)
end
def backorderable?(variant)
stock_item(variant).try(:backorderable?)
end
def restock(variant, quantity, originator = nil)
move(variant, quantity, originator)
end
def unstock(variant, quantity, originator = nil)
move(variant, -quantity, originator)
end
def move(variant, quantity, originator = nil)
variant.move(quantity, originator)
end
def fill_status(variant, quantity)
variant.fill_status(quantity)
end
private
def create_stock_items
Variant.find_each { |variant| stock_items.create!(variant:) }
end
end
end

View File

@@ -105,7 +105,16 @@ module Spree
if default_zone_or_zone_match?(item.order) if default_zone_or_zone_match?(item.order)
calculator.compute(item) calculator.compute(item)
else else
# In this case, it's a refund (for instance offering a manual discount via an adjustment) # Tax refund should not be possible with the way our production server are configured
Bugsnag.notify(
"Notice: Tax refund should not be possible, please check the default zone and " \
"the tax rate zone configuration"
) do |payload|
payload.add_metadata :order_tax_zone, item.order.tax_zone
payload.add_metadata :tax_rate_zone, zone
payload.add_metadata :default_zone, Zone.default_tax
end
# In this case, it's a refund.
calculator.compute(item) * - 1 calculator.compute(item) * - 1
end end
else else

Some files were not shown because too many files have changed in this diff Show More