Compare commits

..

1 Commits

Author SHA1 Message Date
Ahmed Ejaz
7cab04553a add test data 2025-09-18 00:51:31 +05:00
646 changed files with 47397 additions and 35439 deletions

56
.codeclimate.yml Normal file
View File

@@ -0,0 +1,56 @@
version: "2"
plugins:
rubocop:
enabled: true
channel: "rubocop-1-12"
config:
file: ".rubocop.yml"
scss-lint:
enabled: true
checks:
ImportantRule:
enabled: false
VendorPrefix:
enabled: false
LeadingZero:
enabled: false
PropertySortOrder:
enabled: false
StringQuotes:
enabled: false
DeclarationOrder:
enabled: false
NestingDepth:
enabled: false
duplication:
enabled: true
exclude_patterns:
- "db/**"
- "config/initializers/active_record_postgresql_referential_integrity_patch.rb"
checks:
argument-count:
enabled: false
complex-logic:
enabled: false
file-lines:
enabled: false
method-complexity:
enabled: false
method-count:
enabled: false
method-lines:
enabled: false
nested-control-flow:
enabled: false
return-statements:
enabled: false
similar-code:
enabled: false
identical-code:
enabled: false
exclude_patterns:
- "spec/**/*"
- "vendor/**/*"
- "app/assets/javascripts/shared/*"
- "app/assets/javascripts/jquery-migrate-1.0.0.js"

View File

@@ -20,6 +20,7 @@ STRIPE_INSTANCE_SECRET_KEY="bogus_key"
STRIPE_CUSTOMER="bogus_customer"
STRIPE_ACCOUNT="bogus_account"
STRIPE_CLIENT_ID="bogus_client_id"
STRIPE_PUBLIC_TEST_API_KEY="bogus_stripe_publishable_key"
SITE_URL="test.host"

View File

@@ -4,28 +4,7 @@
# Most of the configuration here is not used for security updates though.
version: 2
multi-ecosystem-groups:
turbo_power:
schedule:
interval: "daily"
updates:
- package-ecosystem: "bundler"
directory: "/"
patterns: ["turbo_power"]
multi-ecosystem-group: "turbo_power"
# Only specific requirements are specified in Gemfile, so don't touch it.
versioning-strategy: lockfile-only
- package-ecosystem: "npm"
directory: "/"
patterns: ["turbo_power"]
multi-ecosystem-group: "turbo_power"
# Only specific requirements are specified in package.json, so don't touch it.
versioning-strategy: lockfile-only
- package-ecosystem: "bundler"
directory: "/"
@@ -40,5 +19,5 @@ updates:
schedule:
interval: "daily"
# Only specific requirements are specified in package.json, so don't touch it.
versioning-strategy: lockfile-only
# All versions are specified in package.json, so please update them.
versioning-strategy: increase

13
.github/pull_request.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"pull_request": {
"number": 13490,
"title": "Bump rails from 7.0.4 to 7.0.8",
"id": 99999999,
"user": { "login": "dependabot[bot]" }
},
"repository": {
"owner": { "login": "openfoodnetwork" },
"name": "openfoodnetwork"
},
"sender": { "login": "dependabot[bot]" }
}

View File

@@ -1,15 +0,0 @@
{
"pull_request": {
"number": 13545,
"title": "Bump test from 7.0.4 to 7.0.8",
"user": {
"login": "dependabot[bot]"
}
},
"repository": {
"owner": {
"login": "openfoodfoundation"
},
"name": "openfoodnetwork"
}
}

View File

@@ -1,10 +1,10 @@
name: Linters
on: [pull_request]
on: [push, pull_request]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
lint:
name: reviewdog
name: prettier and rubocop
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -21,10 +21,21 @@ jobs:
- run: git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
- uses: reviewdog/action-setup@v1
- name: prettier
uses: EPMatt/reviewdog-action-prettier@v1
with:
reviewdog_version: v0.21.0
github_token: ${{ secrets.github_token }}
reporter: github-pr-check
level: error
fail_on_error: true
- run: ./script/reviewdog.sh
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.github_token }}
- name: rubocop
uses: reviewdog/action-rubocop@v2
with:
rubocop_version: gemfile
rubocop_extensions: rubocop-rails:gemfile rubocop-rspec:gemfile
reporter: github-pr-check
level: error
filter_mode: nofilter
use_bundler: true
fail_level: any

View File

@@ -1,10 +1,7 @@
name: Auto-move Dependabot PRs to Code Review
permissions:
contents: read
pull-requests: read
on:
pull_request_target:
pull_request:
types: [opened]
jobs:
@@ -12,24 +9,15 @@ jobs:
runs-on: ubuntu-latest
if: github.event.pull_request.user.login == 'dependabot[bot]' || startsWith(github.event.pull_request.title, 'Bump')
steps:
- name: Generate GitHub App Token
id: app-token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.DEPENDABOT_PR_APP_ID }}
private_key: ${{ secrets.DEPENDABOT_PR_APP_PRIVATE_KEY }}
installation_retrieval_mode: id
installation_retrieval_payload: ${{ secrets.DEPENDABOT_PR_APP_INSTALLATION_ID }}
- name: Move PR to Code Review in Project v2
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const projectNumber = 8; // for "OFN Delivery board"
const org = "openfoodfoundation";
const repo = context.repo.repo;
const prNumber = context.payload.pull_request.number;
const org = context.repo.owner;
const repo = context.repo.repo;
const prNumber = context.payload.pull_request.number;
const statusFieldName = "Status";
const statusValue = "Code review 🔎";

1
.gitignore vendored
View File

@@ -59,4 +59,3 @@ yarn-debug.log*
/config/credentials.yml.enc
/config/master.key
.secrets

View File

@@ -2,12 +2,19 @@
# frameworks such as Jekyll/Middleman
skip_frontmatter: false
inherits_from: .haml-lint_todo.yml
linters:
AltText:
enabled: false
ClassAttributeWithStaticValue:
enabled: true
ClassesBeforeIds:
enabled: true
ConsecutiveComments:
enabled: true
ConsecutiveSilentScripts:
enabled: true
max_consecutive: 2
@@ -25,6 +32,7 @@ linters:
enabled: true
LineLength:
enabled: true
max: 80
MultilinePipe:
@@ -39,11 +47,24 @@ linters:
RuboCop:
enabled: false
RubyComments:
enabled: true
SpaceBeforeScript:
enabled: true
SpaceInsideHashAttributes:
enabled: true
style: no_space
TagName:
enabled: true
TrailingWhitespace:
enabled: true
UnnecessaryInterpolation:
enabled: true
UnnecessaryStringOutput:
enabled: true

View File

@@ -1,153 +0,0 @@
# This configuration was generated by
# `haml-lint --auto-gen-config`
# on 2025-10-30 09:19:50 +0100 using Haml-Lint version 0.66.0.
# The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of Haml-Lint, may require this file to be generated again.
linters:
# Offense count: 35
ClassAttributeWithStaticValue:
enabled: false
# Offense count: 77
ClassesBeforeIds:
enabled: false
# Offense count: 18
ConsecutiveComments:
enabled: false
# Offense count: 22
ConsecutiveSilentScripts:
exclude:
- "app/views/admin/contents/_fieldset.html.haml"
- "app/views/admin/enterprises/form/_tag_rules.html.haml"
- "app/views/admin/order_cycles/edit.html.haml"
- "app/views/admin/products_v3/product_preview.turbo_stream.haml"
- "app/views/admin/reports/_date_range_form.html.haml"
- "app/views/checkout/_details.html.haml"
- "app/views/checkout/_payment.html.haml"
- "app/views/spree/admin/adjustments/_adjustments_table.html.haml"
- "app/views/spree/admin/orders/customer_details/_address_form.html.haml"
- "app/views/spree/admin/tax_categories/index.html.haml"
- "app/views/spree/admin/users/index.html.haml"
# Offense count: 14
FinalNewline:
exclude:
- "app/assets/javascripts/templates/shared/question_mark_with_tooltip.html.haml"
- "app/views/admin/enterprises/form/_social.html.haml"
- "app/views/admin/json/_injection_ams.html.haml"
- "app/views/admin/order_cycles/_date_time_warning_modal_content.html.haml"
- "app/views/admin/order_cycles/edit.html.haml"
- "app/views/admin/product_import/_ams_data.html.haml"
- "app/views/admin/reports/_row_group.haml"
- "app/views/admin/reports/filters/_enterprise_fee_summary.html.haml"
- "app/views/admin/reports/filters/_users_and_enterprises.html.haml"
- "app/views/shop/_blocked_cookies.html.haml"
- "app/views/spree/admin/orders/_invoice/_order_note.html.haml"
- "app/views/spree/admin/orders/invoice4.html.haml"
- "app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml"
- "app/views/spree/admin/users/_email_confirmation.html.haml"
# Offense count: 130
IdNames:
enabled: false
# Offense count: 5
Indentation:
exclude:
- "app/views/admin/products_v3/clone.turbo_stream.haml"
- "app/views/admin/products_v3/destroy_product_variant.turbo_stream.haml"
- "app/views/spree/admin/taxons/destroy_taxon.turbo_stream.haml"
# Offense count: 191
InlineStyles:
enabled: false
# Offense count: 589
InstanceVariables:
enabled: false
# Offense count: 2
LeadingCommentSpace:
exclude:
- "app/views/admin/reports/_row_group.haml"
# Offense count: 2331
LineLength:
enabled: false
# Offense count: 1
MultilinePipe:
exclude:
- "app/views/admin/reports/_rendering_options.html.haml"
# Offense count: 2
MultilineScript:
exclude:
- "app/views/admin/products_v3/product_preview.turbo_stream.haml"
- "app/views/checkout/_voucher_section.html.haml"
# Offense count: 2
RepeatedId:
exclude:
- "app/assets/javascripts/templates/admin/save_bar.html.haml"
# Offense count: 24
RubyComments:
enabled: false
# Offense count: 104
SpaceBeforeScript:
enabled: false
# Offense count: 3345
SpaceInsideHashAttributes:
enabled: false
# Offense count: 22
TrailingEmptyLines:
enabled: false
# Offense count: 73
TrailingWhitespace:
enabled: false
# Offense count: 13
UnnecessaryInterpolation:
exclude:
- "app/components/example_component/example_component.html.haml"
- "app/views/admin/product_import/_entries_table.html.haml"
- "app/views/admin/product_import/import.html.haml"
- "app/views/admin/variant_overrides/_filters.html.haml"
- "app/views/registration/steps/_introduction.html.haml"
- "app/views/spree/order_mailer/_shipping.html.haml"
- "app/views/spree/order_mailer/invoice_email.html.haml"
- "app/views/spree/shared/_shipment_delivery_details.html.haml"
- "app/views/spree/shared/_shipment_pickup_details.html.haml"
# Offense count: 68
UnnecessaryStringOutput:
enabled: false
# Offense count: 14
ViewLength:
exclude:
- "app/assets/javascripts/templates/admin/panels/enterprise_package.html.haml"
- "app/views/admin/customers/index.html.haml"
- "app/views/admin/enterprises/_new_form.html.haml"
- "app/views/admin/enterprises/form/_shop_preferences.html.haml"
- "app/views/admin/product_import/_import_review.html.haml"
- "app/views/admin/products_v3/product_preview.turbo_stream.haml"
- "app/views/checkout/_details.html.haml"
- "app/views/groups/show.html.haml"
- "app/views/producer_mailer/order_cycle_report.html.haml"
- "app/views/shared/_footer.html.haml"
- "app/views/spree/admin/orders/bulk_management.html.haml"
- "app/views/spree/admin/orders/invoice4.html.haml"
- "app/views/spree/admin/products/new.html.haml"
- "app/views/spree/admin/variants/_form.html.haml"

6
.hound.yml Normal file
View File

@@ -0,0 +1,6 @@
rubocop:
config_file: .rubocop_styleguide.yml
scss:
config_file: .scss-lint.yml
haml:
config_file: .haml-lint.yml

View File

@@ -1 +1 @@
24.10.0
17.9.1

View File

@@ -4,7 +4,7 @@
#
# The configuration is split into three files. Look into those files for more details.
#
plugins:
require:
- rubocop-capybara
- rubocop-factory_bot
- rubocop-rails

View File

@@ -94,7 +94,7 @@ Metrics/PerceivedComplexity:
Enabled: true
Max: 14 # default 8
Naming/PredicatePrefix:
Naming/PredicateName:
Enabled: false
Naming/VariableNumber:

View File

@@ -1,44 +1,11 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 1400 --no-auto-gen-timestamp`
# using RuboCop version 1.81.7.
# using RuboCop version 1.64.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: RequireParenthesesForMethodChains.
Lint/AmbiguousRange:
Exclude:
- 'app/models/concerns/permalink_generator.rb'
# Offense count: 6
Lint/CopDirectiveSyntax:
Exclude:
- 'app/services/orders/bulk_cancel_service.rb'
- 'lib/tasks/simplecov.rake'
- 'spec/models/database_spec.rb'
- 'spec/system/admin/bulk_order_management_spec.rb'
- 'spec/system/admin/enterprise_relationships_spec.rb'
# Offense count: 2
Lint/DuplicateMethods:
Exclude:
- 'app/models/spree/order.rb'
- 'engines/order_management/app/services/order_management/subscriptions/form.rb'
# Offense count: 3
Lint/UselessConstantScoping:
Exclude:
- 'app/services/weights_and_measures.rb'
- 'lib/reporting/report_metadata_builder.rb'
# Offense count: 1
Lint/UselessOr:
Exclude:
- 'app/models/product_import/entry_validator.rb'
# Offense count: 24
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
Metrics/AbcSize:
@@ -75,12 +42,12 @@ Metrics/BlockLength:
- 'lib/tasks/data.rake'
# Offense count: 1
# Configuration parameters: CountBlocks, CountModifierForms, Max.
# Configuration parameters: CountBlocks, Max.
Metrics/BlockNesting:
Exclude:
- 'app/models/spree/payment/processing.rb'
# Offense count: 49
# Offense count: 47
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ClassLength:
Exclude:
@@ -121,8 +88,6 @@ Metrics/ClassLength:
- 'app/services/cart_service.rb'
- 'app/services/order_cycles/form_service.rb'
- 'app/services/orders/sync_service.rb'
- 'app/services/permissions/order.rb'
- 'app/services/products_renderer.rb'
- 'app/services/sets/product_set.rb'
- 'engines/order_management/app/services/order_management/order/updater.rb'
- 'lib/open_food_network/enterprise_fee_calculator.rb'
@@ -133,8 +98,9 @@ Metrics/ClassLength:
- 'lib/reporting/reports/enterprise_fee_summary/enterprise_fees_with_tax_report_by_producer.rb'
- 'lib/reporting/reports/enterprise_fee_summary/scope.rb'
- 'lib/reporting/reports/xero_invoices/base.rb'
- 'app/services/permissions/order.rb'
# Offense count: 37
# Offense count: 30
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
Metrics/CyclomaticComplexity:
Exclude:
@@ -157,24 +123,20 @@ Metrics/CyclomaticComplexity:
- 'app/models/spree/tax_rate.rb'
- 'app/models/spree/zone.rb'
- 'lib/open_food_network/enterprise_issue_validator.rb'
- 'lib/reporting/reports/orders_and_fulfillment/order_cycle_customer_totals.rb'
- 'lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb'
- 'lib/reporting/reports/payments/itemised_payment_totals.rb'
- 'lib/reporting/reports/payments/payment_totals.rb'
- 'lib/reporting/reports/sales_tax/sales_tax_totals_by_producer.rb'
- 'lib/reporting/reports/xero_invoices/base.rb'
- 'lib/spree/core/controller_helpers/order.rb'
- 'lib/spree/core/controller_helpers/respond_with.rb'
- 'lib/spree/localized_number.rb'
- 'spec/models/product_importer_spec.rb'
# Offense count: 22
# Offense count: 23
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Exclude:
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/payment_gateways/paypal_controller.rb'
- 'app/controllers/spree/orders_controller.rb'
- 'app/helpers/spree/admin/navigation_helper.rb'
- 'app/models/spree/ability.rb'
- 'app/models/spree/gateway/pay_pal_express.rb'
- 'app/models/spree/order/checkout.rb'
@@ -187,7 +149,7 @@ Metrics/MethodLength:
- 'lib/spree/localized_number.rb'
- 'lib/tasks/sample_data/product_factory.rb'
# Offense count: 10
# Offense count: 47
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ModuleLength:
Exclude:
@@ -212,7 +174,7 @@ Metrics/ParameterLists:
- 'spec/support/controller_requests_helper.rb'
- 'spec/system/admin/reports_spec.rb'
# Offense count: 4
# Offense count: 3
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
Metrics/PerceivedComplexity:
Exclude:
@@ -220,165 +182,16 @@ Metrics/PerceivedComplexity:
- 'app/models/spree/ability.rb'
- 'app/models/spree/order/checkout.rb'
# Offense count: 1
# Configuration parameters: EnforcedStyle, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns.
# SupportedStyles: snake_case, camelCase
# ForbiddenIdentifiers: __id__, __send__
Naming/MethodName:
Exclude:
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
# Offense count: 1
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
Naming/MethodParameterName:
Exclude:
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
# Offense count: 60
# Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
# AllowedMethods: call
# WaywardPredicates: nonzero?
Naming/PredicateMethod:
Exclude:
- 'app/controllers/admin/product_import_controller.rb'
- 'app/controllers/api/v0/order_cycles_controller.rb'
- 'app/controllers/spree/admin/overview_controller.rb'
- 'app/controllers/spree/admin/payments_controller.rb'
- 'app/controllers/voucher_adjustments_controller.rb'
- 'app/forms/enterprise_fees_bulk_update.rb'
- 'app/forms/schedule_form.rb'
- 'app/helpers/spree/orders_helper.rb'
- 'app/models/concerns/variant_stock.rb'
- 'app/models/enterprise.rb'
- 'app/models/enterprise_fee.rb'
- 'app/models/invoice/data_presenter.rb'
- 'app/models/order_cycle.rb'
- 'app/models/product_import/entry_processor.rb'
- 'app/models/product_import/entry_validator.rb'
- 'app/models/spree/order.rb'
- 'app/models/spree/order_contents.rb'
- 'app/models/spree/payment.rb'
- 'app/models/spree/payment/processing.rb'
- 'app/models/spree/state_change.rb'
- 'app/models/spree/user.rb'
- 'app/reflexes/admin/orders_reflex.rb'
- 'app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb'
- 'app/serializers/api/admin/index_enterprise_serializer.rb'
- 'app/serializers/api/admin/index_order_cycle_serializer.rb'
- 'app/serializers/api/admin/order_cycle_serializer.rb'
- 'app/serializers/api/admin/order_serializer.rb'
- 'app/serializers/api/admin/schedule_serializer.rb'
- 'app/serializers/api/admin/subscription_line_item_serializer.rb'
- 'app/serializers/api/admin/user_serializer.rb'
- 'app/serializers/api/admin/variant_serializer.rb'
- 'app/serializers/api/enterprise_shopfront_serializer.rb'
- 'app/serializers/api/enterprise_thin_serializer.rb'
- 'app/serializers/api/order_serializer.rb'
- 'app/serializers/api/uncached_enterprise_serializer.rb'
- 'app/services/cart_service.rb'
- 'app/services/orders/fetch_adjustments_service.rb'
- 'app/services/orders/workflow_service.rb'
- 'app/services/sets/model_set.rb'
- 'app/services/sets/order_cycle_set.rb'
- 'app/services/sets/product_set.rb'
- 'engines/dfc_provider/app/controllers/dfc_provider/addresses_controller.rb'
- 'lib/open_food_network/order_cycle_form_applicator.rb'
- 'lib/open_food_network/order_cycle_permissions.rb'
- 'lib/reporting/reports/enterprise_fee_summary/enterprise_fees_with_tax_report_by_order.rb'
- 'lib/reporting/reports/enterprise_fee_summary/enterprise_fees_with_tax_report_by_producer.rb'
- 'lib/tasks/data/check_invalid_address_used.rake'
# Offense count: 3
# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns.
# SupportedStyles: snake_case, camelCase
Naming/VariableName:
Exclude:
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
# Offense count: 6
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/FindByOrAssignmentMemoization:
Exclude:
- 'app/controllers/admin/customers_controller.rb'
- 'app/controllers/admin/resource_controller.rb'
- 'app/controllers/api/v0/enterprise_fees_controller.rb'
- 'app/controllers/api/v0/order_cycles_controller.rb'
- 'lib/stripe/account_connector.rb'
# Offense count: 32
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/OrderArguments:
Exclude:
- 'app/controllers/admin/enterprise_fees_controller.rb'
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/admin/order_cycles_controller.rb'
- 'app/controllers/admin/product_import_controller.rb'
- 'app/controllers/api/v0/states_controller.rb'
- 'app/controllers/spree/admin/overview_controller.rb'
- 'app/controllers/spree/admin/products_controller.rb'
- 'app/helpers/enterprises_helper.rb'
- 'app/models/enterprise.rb'
- 'app/models/enterprise_group.rb'
- 'app/models/enterprise_relationship_permission.rb'
- 'app/models/order_cycle.rb'
- 'app/models/product_import/product_importer.rb'
- 'app/models/schedule.rb'
- 'app/models/spree/country.rb'
- 'app/models/spree/order.rb'
- 'app/models/spree/shipping_rate.rb'
- 'app/models/spree/user.rb'
- 'app/models/spree/zone.rb'
- 'app/models/subscription_line_item.rb'
- 'app/models/tag_rule.rb'
- 'app/models/variant_override.rb'
- 'lib/open_food_network/address_finder.rb'
- 'spec/services/orders/generate_invoice_service_spec.rb'
- 'spec/system/admin/order_cycles/simple_spec.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
Rails/Presence:
Exclude:
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/models/spree/product.rb'
# Offense count: 6
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Severity.
Rails/RedirectBackOrTo:
Exclude:
- 'app/controllers/admin/order_cycles_controller.rb'
- 'app/controllers/locales_controller.rb'
- 'app/controllers/spree/admin/invoices_controller.rb'
- 'app/controllers/spree/admin/orders_controller.rb'
- 'app/controllers/spree/admin/return_authorizations_controller.rb'
# Offense count: 1
# Configuration parameters: TransactionMethods.
Rails/TransactionExitStatement:
Exclude:
- 'app/services/place_proxy_order.rb'
# Offense count: 3
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/ArrayIntersect:
Exclude:
- 'app/models/spree/ability.rb'
- 'app/models/spree/variant.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/BitwisePredicate:
Exclude:
- 'app/helpers/admin/enterprises_helper.rb'
# Offense count: 23
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: nested, compact
# SupportedStylesForClasses: ~, nested, compact
# SupportedStylesForModules: ~, nested, compact
Style/ClassAndModuleChildren:
Exclude:
- 'app/models/calculator/flat_percent_per_item.rb'
@@ -404,33 +217,13 @@ Style/ClassAndModuleChildren:
- 'lib/open_food_network/locking.rb'
- 'spec/models/spree/payment_method_spec.rb'
# Offense count: 14
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/CollectionQuerying:
Exclude:
- 'app/controllers/spree/credit_cards_controller.rb'
- 'app/models/product_import/product_importer.rb'
- 'app/models/product_import/spreadsheet_entry.rb'
- 'app/models/spree/order.rb'
- 'app/models/spree/order_inventory.rb'
- 'app/models/spree/payment_method.rb'
- 'app/models/spree/user.rb'
- 'app/models/stripe_account.rb'
- 'app/services/order_cycles/warning_service.rb'
- 'lib/reporting/report_renderer.rb'
- 'lib/tasks/sample_data.rake'
# Offense count: 2
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/HashSlice:
Exclude:
- 'app/services/product_filters.rb'
- 'lib/reporting/report_row_builder.rb'
# Offense count: 1
# Offense count: 4
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/MapToHash:
Exclude:
- 'lib/reporting/report_query_template.rb'
- 'lib/reporting/report_row_builder.rb'
- 'lib/reporting/reports/enterprise_fee_summary/fee_summary.rb'
- 'lib/tasks/sample_data/user_factory.rb'
# Offense count: 38
@@ -461,22 +254,3 @@ Style/OptionalBooleanParameter:
- 'engines/order_management/app/services/order_management/stock/estimator.rb'
- 'lib/spree/core/controller_helpers/order.rb'
- 'spec/support/request/web_helper.rb'
# Offense count: 3
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/RedundantFormat:
Exclude:
- 'spec/models/product_importer_spec.rb'
- 'spec/requests/checkout/stripe_sca_spec.rb'
- 'spec/system/consumer/account/cards_spec.rb'
# Offense count: 8
# Configuration parameters: Max.
Style/SafeNavigationChainLength:
Exclude:
- 'app/controllers/concerns/extra_fields.rb'
- 'app/services/customer_syncer.rb'
- 'app/services/fdc_offer_broker.rb'
- 'engines/dfc_provider/app/services/dfc_catalog.rb'
- 'engines/dfc_provider/app/services/image_builder.rb'
- 'engines/dfc_provider/app/services/quantitative_value_builder.rb'

View File

@@ -1 +1 @@
3.2.9
3.1.4

19
.scss-lint.yml Normal file
View File

@@ -0,0 +1,19 @@
scss_files: 'app/assets/stylesheets/**/*.css.scss'
exclude: 'app/assets/stylesheets/shared/**'
linters:
ImportantRule:
enabled: false
VendorPrefix:
enabled: false
LeadingZero:
enabled: false
PropertySortOrder:
enabled: false
StringQuotes:
enabled: false
DeclarationOrder:
enabled: false
NestingDepth:
enabled: false

View File

@@ -1,4 +0,0 @@
# .secrets file define github secrets value locally
DEPENDABOT_PR_APP_ID=123456
DEPENDABOT_PR_APP_INSTALLATION_ID=123456
DEPENDABOT_PR_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n....\n-----END RSA PRIVATE KEY-----"

View File

@@ -1,4 +1,4 @@
FROM ruby:3.2.9-alpine3.19 AS base
FROM ruby:3.1.4-alpine3.19 AS base
ENV LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
TZ=Europe/London \

View File

@@ -73,7 +73,7 @@ To login as the default user, use:
email: ofn@example.com
password: ofn123
See [Locale and sample data] about loading data.
Seee [Locale and sample data] about loading data.
### Testing

22
Gemfile
View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
source 'https://gem.coop'
source 'https://rubygems.org'
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
ruby File.read('.ruby-version').chomp
@@ -14,19 +14,16 @@ gem "active_storage_validations"
gem "aws-sdk-s3", require: false
gem "image_processing"
gem 'activemerchant'
gem 'angular-rails-templates'
gem 'activemerchant', '>= 1.78.0'
gem 'angular-rails-templates', '>= 0.3.0'
gem 'ransack', '~> 4.1.0'
gem 'responders'
gem 'webpacker', '~> 5'
# Indirect dependency but we access it directly in JS specs.
# It turns out to be hard to upgrade but please do if you can.
gem 'sprockets', '~> 3.7'
gem 'i18n'
gem 'i18n-js', '~> 3.9.0'
gem 'rails-i18n'
gem 'rails_safe_tasks', '~> 1.0'
gem "activerecord-import"
gem "db2fog", github: "openfoodfoundation/db2fog", branch: "rails-7"
@@ -43,7 +40,7 @@ gem 'web', path: './engines/web'
gem "activerecord-postgresql-adapter"
gem "arel-helpers", "~> 2.12"
gem "pg"
gem "pg", "~> 1.2.3"
gem 'acts_as_list', '1.0.4'
gem 'cancancan', '~> 1.15.0'
@@ -57,7 +54,7 @@ gem 'state_machines-activerecord'
gem 'stringex', '~> 2.8.5', require: false
gem 'paypal-sdk-merchant', '1.117.2'
gem 'stripe', '~> 13'
gem 'stripe'
gem 'devise'
gem 'devise-encryptable'
@@ -185,19 +182,16 @@ group :test do
end
group :development do
gem 'debugger-linecache'
gem 'foreman'
gem 'haml_lint', require: false
gem 'i18n-tasks'
gem 'listen'
gem 'pry'
gem 'pry', '~> 0.13.0'
gem 'query_count'
gem 'rails-erd'
gem 'rubocop'
gem 'rubocop-capybara'
gem 'rubocop-factory_bot'
gem 'rubocop-rails'
gem 'rubocop-rspec'
gem 'rubocop-rspec_rails'
gem 'spring'
gem 'spring-commands-rspec'
gem 'spring-commands-rubocop'

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,6 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filt
$scope.confirmRefresh = (event) ->
event.preventDefault() unless pendingChanges.unsavedCount() == 0 || confirm(t("unsaved_changes_warning"))
$scope.hasUnsavedChanges = ->
pendingChanges.yes()
$scope.$watch "shop_id", ->
if $scope.shop_id?
CurrentShop.shop = $filter('filter')($scope.shops, {id: parseInt($scope.shop_id)}, true)[0]

View File

@@ -6,6 +6,7 @@ angular.module("admin.enterprises", [
"admin.side_menu",
"admin.taxons",
'admin.indexUtils',
'admin.tagRules',
'admin.dropdown',
'ngSanitize']
)

View File

@@ -4,16 +4,15 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
type: "@objForUpdate"
attr: "@attrForUpdate"
link: (scope, element, attrs) ->
scope.savedValue = scope.object()[scope.attr] || ""
scope.savedValue = scope.object()[scope.attr]
scope.$watch "object().#{scope.attr}", (value) ->
strValue = value || ""
if strValue == scope.savedValue
if value == scope.savedValue
pendingChanges.remove(scope.object().id, scope.attr)
scope.clear()
else
scope.pending()
addPendingChange(scope.attr, strValue)
addPendingChange(scope.attr, value ? "")
scope.reset = (value) ->
scope.savedValue = value

View File

@@ -16,10 +16,7 @@ angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, Sta
remove: (id, attr) =>
if @pendingChanges.hasOwnProperty("#{id}")
delete @pendingChanges["#{id}"]["#{attr}"]
if @changeCount( @pendingChanges["#{id}"] ) < 1
delete @pendingChanges["#{id}"]
StatusMessage.clear()
delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1
submitAll: (form=null) =>
all = []
@@ -50,8 +47,5 @@ angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, Sta
unsavedCount: ->
Object.keys(@pendingChanges).length
yes: ->
@unsavedCount() > 0
changeCount: (objectChanges) ->
Object.keys(objectChanges).length

View File

@@ -1,4 +1,4 @@
angular.module("admin.indexUtils").factory "switchClass", ($timeout, StatusMessage) ->
angular.module("admin.indexUtils").factory "switchClass", ($timeout) ->
return (element, classToAdd, removeClasses, timeout) ->
$timeout.cancel element.timeout if element.timeout
element.removeClass className for className in removeClasses
@@ -7,6 +7,4 @@ angular.module("admin.indexUtils").factory "switchClass", ($timeout, StatusMessa
if timeout && intRegex.test(timeout)
element.timeout = $timeout(->
element.removeClass classToAdd
StatusMessage.clear()
, timeout, true)
element

View File

@@ -0,0 +1,58 @@
angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, $filter, enterprise) ->
$scope.tagGroups = enterprise.tag_groups
$scope.defaultTagGroup = enterprise.default_tag_group
$scope.visibilityOptions = [ { id: "visible", name: t('js.tag_rules.visible') }, { id: "hidden", name: t('js.tag_rules.not_visible') } ]
$scope.updateRuleCounts = ->
index = $scope.defaultTagGroup.rules.length
for tagGroup in $filter('orderBy')($scope.tagGroups, 'position')
tagGroup.startIndex = index
index = index + tagGroup.rules.length
$scope.updateRuleCounts()
$scope.updateTagsRulesFor = (tagGroup) ->
for tagRule in tagGroup.rules
tagRule.preferred_customer_tags = (tag.text for tag in tagGroup.tags).join(",")
$scope.addNewRuleTo = (tagGroup, ruleType) ->
newRule =
id: null
is_default: tagGroup == $scope.defaultTagGroup
preferred_customer_tags: (tag.text for tag in tagGroup.tags).join(",")
type: "TagRule::#{ruleType}"
switch ruleType
when "FilterShippingMethods"
newRule.peferred_shipping_method_tags = []
newRule.preferred_matched_shipping_methods_visibility = "visible"
when "FilterPaymentMethods"
newRule.peferred_payment_method_tags = []
newRule.preferred_matched_payment_methods_visibility = "visible"
when "FilterProducts"
newRule.peferred_variant_tags = []
newRule.preferred_matched_variants_visibility = "visible"
when "FilterOrderCycles"
newRule.peferred_exchange_tags = []
newRule.preferred_matched_order_cycles_visibility = "visible"
tagGroup.rules.push(newRule)
$scope.updateRuleCounts()
$scope.addNewTag = ->
$scope.tagGroups.push { tags: [], rules: [], position: $scope.tagGroups.length + 1 }
$scope.deleteTagRule = (tagGroup, tagRule) ->
index = tagGroup.rules.indexOf(tagRule)
return unless index >= 0
if tagRule.id is null
tagGroup.rules.splice(index, 1)
$scope.updateRuleCounts()
else
if confirm("Are you sure?")
$http
method: "DELETE"
url: "/admin/enterprises/#{enterprise.id}/tag_rules/#{tagRule.id}.json"
.then ->
tagGroup.rules.splice(index, 1)
$scope.updateRuleCounts()
$scope.enterprise_form.$setDirty()

View File

@@ -0,0 +1,11 @@
angular.module("admin.tagRules").directive "invertNumber", ->
restrict: "A"
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
ngModel.$parsers.push (viewValue) ->
return -parseInt(viewValue) unless isNaN(parseInt(viewValue))
viewValue
ngModel.$formatters.push (modelValue) ->
return -parseInt(modelValue) unless isNaN(parseInt(modelValue))
modelValue

View File

@@ -0,0 +1,26 @@
angular.module("admin.tagRules").directive 'newTagRuleDialog', ($rootScope, $compile, $templateCache, DialogDefaults, ruleTypes) ->
restrict: 'A'
scope:
tagGroup: '='
addNewRuleTo: '='
link: (scope, element, attr) ->
# Compile modal template
template = $compile($templateCache.get('admin/new_tag_rule_dialog.html'))(scope)
scope.ruleTypes = ruleTypes
scope.ruleType = scope.ruleTypes[0].id
# Set Dialog options
template.dialog(DialogDefaults)
# Link opening of dialog to click event on element
element.bind 'click', (e) ->
template.dialog('open')
$rootScope.$evalAsync()
scope.addRule = (tagGroup, ruleType) ->
scope.addNewRuleTo(tagGroup, ruleType)
template.dialog('close')
$rootScope.$evalAsync()
return

View File

@@ -0,0 +1,41 @@
angular.module("admin.tagRules").directive "tagRule", ->
restrict: "C"
templateUrl: "admin/tag_rules/tag_rule.html"
link: (scope, element, attrs) ->
scope.opt =
"TagRule::FilterShippingMethods":
textTop: t('js.admin.tag_rules.shipping_method_tagged_top')
textBottom: t('js.admin.tag_rules.shipping_method_tagged_bottom')
taggable: "shipping_method"
tagsAttr: "shipping_method_tags"
tagListAttr: "preferred_shipping_method_tags"
inputTemplate: "admin/tag_rules/filter_shipping_methods_input.html"
tagListFor: (rule) ->
rule.preferred_shipping_method_tags
"TagRule::FilterPaymentMethods":
textTop: t('js.admin.tag_rules.payment_method_tagged_top')
textBottom: t('js.admin.tag_rules.payment_method_tagged_bottom')
taggable: "payment_method"
tagsAttr: "payment_method_tags"
tagListAttr: "preferred_payment_method_tags"
inputTemplate: "admin/tag_rules/filter_payment_methods_input.html"
tagListFor: (rule) ->
rule.preferred_payment_method_tags
"TagRule::FilterOrderCycles":
textTop: t('js.admin.tag_rules.order_cycle_tagged_top')
textBottom: t('js.admin.tag_rules.order_cycle_tagged_bottom')
taggable: "exchange"
tagsAttr: "exchange_tags"
tagListAttr: "preferred_exchange_tags"
inputTemplate: "admin/tag_rules/filter_order_cycles_input.html"
tagListFor: (rule) ->
rule.preferred_exchange_tags
"TagRule::FilterProducts":
textTop: t('js.admin.tag_rules.inventory_tagged_top')
textBottom: t('js.admin.tag_rules.inventory_tagged_bottom')
taggable: "variant"
tagsAttr: "variant_tags"
tagListAttr: "preferred_variant_tags"
inputTemplate: "admin/tag_rules/filter_products_input.html"
tagListFor: (rule) ->
rule.preferred_variant_tags

View File

@@ -1,6 +1,6 @@
angular.module('Darkswarm').directive "activeTableHubLink", (CurrentHub, CurrentOrder) ->
# Change the text of the hub link based on CurrentHub
# To be used with ofnChangeHub
# To be used with ofnEmptiesCart
# Takes "change" and "shop" as text string attributes
restrict: "A"
scope:

View File

@@ -0,0 +1,11 @@
angular.module('Darkswarm').directive "darkerBackground", ->
restrict: "A"
link: (scope, elm, attr)->
toggleClass = (value) ->
elm.closest('.page-view').toggleClass("with-darker-background", value)
toggleClass(true)
# if an OrderCycle is selected, disable darker background
scope.$watch 'order_cycle.order_cycle_id', (newvalue, oldvalue) ->
toggleClass(false) if newvalue

View File

@@ -0,0 +1,14 @@
# Allows disabling of link buttons via disabled attribute.
# This is normally ignored, ie the link appears disabled but is still clickable.
angular.module('Darkswarm').directive "disableDynamically", ->
restrict: 'A'
link: (scope, element, attrs) ->
element.on 'click', (e) ->
if attrs.disabled
e.preventDefault()
return
scope.$on "$destroy", ->
element.off("click")

View File

@@ -0,0 +1,7 @@
angular.module('Darkswarm').directive "ofnInlineAlert", ->
restrict: 'A'
scope: true
link: (scope, elem, attrs) ->
scope.visible = true
scope.close = ->
scope.visible = false

View File

@@ -0,0 +1,21 @@
angular.module('Darkswarm').directive "ofnPageAlert", ($timeout) ->
restrict: 'A'
scope: true
link: (scope, elem, attrs) ->
moveSelectors = [".off-canvas-wrap .inner-wrap",
".off-canvas-wrap .inner-wrap .fixed",
".off-canvas-fixed .top-bar",
".off-canvas-fixed ofn-flash",
".off-canvas-fixed nav.tab-bar",
".off-canvas-fixed .page-alert"]
container_elems = $(moveSelectors.join(", "))
# Wait a moment after page load before showing the alert. Otherwise we often miss the
# start of the animation.
$timeout ->
container_elems.addClass("move-up")
, 1000
scope.close = ->
container_elems.removeClass("move-up")

View File

@@ -0,0 +1,10 @@
#new-tag-rule-dialog
.text-normal.margin-bottom-30.text-center
{{ 'js.admin.new_tag_rule_dialog.select_rule_type' | t }}
.text-center.margin-bottom-30
-# %select.fullwidth{ 'select2-min-search' => 5, 'ng-model' => 'newRuleType', 'ng-options' => 'ruleType.id as ruleType.name for ruleType in availableRuleTypes' }
%input.ofn-select2.fullwidth{ id: 'rule_type_selector', data: "ruleTypes", "min-search": "5", "ng-model": "ruleType" }
.text-center
%input.button.red.icon-plus{ type: 'button', value: "{{ 'js.admin.new_tag_rule_dialog.add_rule' | t }}", "ng-click": 'addRule(tagGroup, ruleType)' }

View File

@@ -3,4 +3,4 @@
%span.text-normal
{{ 'admin.tags' | t }}
%br
%tags-with-translation.fullwidth{ object: 'object', form: 'order_cycle_form', id: 'tags_with_translation'}
%tags-with-translation.fullwidth{ object: 'object' }

View File

@@ -0,0 +1,3 @@
%div
%input{ type: "number", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_preferred_flat_percent", min: -100, max: 100, "invert-number": true, "ng-model": "rule.calculator.preferred_flat_percent" }
%span.text-normal %

View File

@@ -0,0 +1,5 @@
%div
%input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_order_cycles_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_order_cycles_visibility]", data: 'visibilityOptions', "min-search": 5, "ng-model": "rule.preferred_matched_order_cycles_visibility", "ng-if": "!rule.is_default" }
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_order_cycles_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_order_cycles_visibility]", "ng-value": "'hidden'", "ng-if": "rule.is_default" }
%span.text-normal{ "ng-if": "rule.is_default" }
=t(:not_visible)

View File

@@ -0,0 +1,5 @@
%div
%input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_payment_methods_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_payment_methods_visibility]", data: 'visibilityOptions', "min-search": 5, "ng-model": "rule.preferred_matched_payment_methods_visibility", "ng-if": "!rule.is_default" }
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_payment_methods_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_payment_methods_visibility]", "ng-value": "'hidden'", "ng-if": "rule.is_default" }
%span.text-normal{ "ng-if": "rule.is_default" }
= t(:not_visible)

View File

@@ -0,0 +1,5 @@
%div
%input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_variants_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_variants_visibility]", data: 'visibilityOptions', "min-search": 5, "ng-model": "rule.preferred_matched_variants_visibility", "ng-if": "!rule.is_default" }
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_variants_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_variants_visibility]", "ng-value": "'hidden'", "ng-if": "rule.is_default" }
%span.text-normal{ "ng-if": "rule.is_default" }
= t(:not_visible)

View File

@@ -0,0 +1,6 @@
%div
%input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_shipping_methods_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_shipping_methods_visibility]", data: 'visibilityOptions', "min-search": 5, "ng-model": "rule.preferred_matched_shipping_methods_visibility", "ng-if": "!rule.is_default" }
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_shipping_methods_visibility", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_shipping_methods_visibility]", "ng-value": "'hidden'", "ng-if": "rule.is_default" }
%span.text-normal{ "ng-if": "rule.is_default" }
= t(:not_visible)

View File

@@ -0,0 +1,32 @@
%div{ id: "tr_{{tagGroup.startIndex + $index}}" }
%table
%colgroup
%col.text{ width: "35%" }
%col.inputs{ width: "55%" }
%col.actions{ width: "10%" }
%tr
%td
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]", "ng-value": "rule.id" }
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]", "ng-value": "rule.type" }
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_priority", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][priority]", "ng-value": "tagGroup.startIndex + $index" }
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_is_default", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][is_default]", "ng-value": "rule.is_default" }
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]", "ng-value": "rule.preferred_customer_tags" }
%input{ type: "hidden", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_{{opt[rule.type].taggable}}_tags", name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_{{opt[rule.type].taggable}}_tags]", "ng-value": "opt[rule.type].tagListFor(rule)" }
%span.text-normal {{ opt[rule.type].textTop }}
%td
%tags-with-translation{ object: "rule", max: 1, "tags-attr" => "{{opt[rule.type].tagsAttr}}", "tag-list-attr" => "{{opt[rule.type].tagListAttr}}" }
%td.actions{ rowspan: 2 }
%a{ class: "delete-tag-rule icon-trash no-text", "ng-click": "deleteTagRule(tagGroup || defaultTagGroup, rule)" }
%tr
%td
%span.text-normal {{ opt[rule.type].textBottom }}
%td
%div{ "ng-include": "opt[rule.type].inputTemplate" }
%hr

View File

@@ -12,7 +12,7 @@
.row
.columns.small-12
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'",
"ng-href" => "{{::hub.path}}#/shop_panel",
"ng-href" => "{{::hub.path}}#/shop_panel", "ofn-empties-cart" => "hub",
"ng-class" => "::{primary: hub.active, secondary: !hub.active}",
"ng-click" => "$close()",
"ofn-change-hub" => "hub"}

View File

@@ -1,21 +0,0 @@
# frozen_string_literal: true
class AddTagRuleModalComponent < ModalComponent
def initialize(id:, tag_rule_types:, current_index:, div_id:, is_default: false,
customer_tag: "", hidden_field_customer_tag_options: {} )
super
@close_button = false
@modal_class = "tiny"
@tag_rule_types = tag_rule_types
@current_index = current_index
@div_id = div_id
@is_default = is_default
@customer_tag = customer_tag
@hidden_field_customer_tag_options = hidden_field_customer_tag_options
end
attr_reader :tag_rule_types, :current_index, :div_id, :is_default, :customer_tag,
:hidden_field_customer_tag_options
end

View File

@@ -1,32 +0,0 @@
-# as far as I can tell we can't pass content to the parent template while rendering ie: something like :
-# = render_parent do
-# .something
-# my content
-# Workarount is to copy the ModalComponent template
%div{ id: @id, "data-controller": @data_controller, "data-action": @data_action, "data-modal-instant-value": @instant, **@options }
.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 }
#new-tag-rule-dialog{ "data-controller": "add-tag-rule-modal",
"data-add-tag-rule-modal-index-value": current_index }
-# Ideally we would use event to communicate the update of customer tag, but we would need
-# the element with "data-controller": "add-tag-rule-modal"
-# to be parent of the element with "data-controller": "tag-rule-group-form"
-# so it could respond to event generated by "tag-rule-group-form".
-# Here we are in the opposite situation so we use a hidden field to store the value of
-# the customer tag, so it can be updated by "tag-rule-group-form"
= hidden_field_tag "customer_tag", customer_tag, { "data-add-tag-rule-modal-target": "ruleCustomerTag" }.merge(hidden_field_customer_tag_options)
.text-normal.margin-bottom-30.text-center
= t('components.add_tag_rule_modal.select_rule_type')
.text-center.margin-bottom-30
= select_tag :rule_type_selector, options_for_select(tag_rule_types), { "data-controller": "tom-select", "data-add-tag-rule-modal-target": "rule", class: "primary no-search" }
.text-center
%input.button.red.icon-plus{ type: 'button',
value: "#{t('components.add_tag_rule_modal.add_rule')}",
"data-action": "click->add-tag-rule-modal#add click->modal#close",
"data-add-tag-rule-modal-div-id-param": div_id,
"data-add-tag-rule-modal-is-default-param": "#{is_default}"}
- if close_button?
.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

@@ -1,46 +0,0 @@
import { Controller } from "stimulus";
import showHttpError from "../../webpacker/js/services/show_http_error";
export default class extends Controller {
static targets = ["rule", "ruleCustomerTag"];
static values = { index: Number };
add({ params }) {
const rule_type = this.ruleTarget.value;
const index = this.indexValue;
const divId = params["divId"];
const isDefault = params["isDefault"];
const customerTags = this.hasRuleCustomerTagTarget
? this.ruleCustomerTagTarget.value
: undefined;
const urlParams = new URLSearchParams();
urlParams.append("rule_type", rule_type);
urlParams.append("index", index);
urlParams.append("div_id", divId);
urlParams.append("is_default", isDefault);
if (customerTags != undefined) {
urlParams.append("customer_tags", customerTags);
}
// fetch from backend
fetch(`tag_rules/new?${urlParams}`, {
method: "GET",
headers: {
Accept: "text/vnd.turbo-stream.html",
},
})
.then((response) => {
if (!response.ok) {
showHttpError(response.status);
throw response;
}
return response.text();
})
.then((html) => {
Turbo.renderStreamMessage(html);
this.indexValue = parseInt(index) + 1;
})
.catch((error) => console.error(error));
}
}

View File

@@ -1,5 +1,4 @@
// This controller will be called "example", ie "js-file-name" minus the "_controller.js"
// see controller/index.js for more info
// This controller will be called "example-component--example", ie "component-subdirectory--js-file-name"
import { Controller } from "stimulus";
export default class extends Controller {}

View File

@@ -30,7 +30,7 @@ class SearchableDropdownComponent < ViewComponent::Base
:aria_label, :other_attrs
def classes
"fullwidth #{'no-input' if remove_search_plugin?}"
"fullwidth #{remove_search_plugin? ? 'no-input' : ''}"
end
def data

View File

@@ -1,29 +1,16 @@
# frozen_string_literal: true
class TagListInputComponent < ViewComponent::Base
def initialize(name:, tags:,
# method in a "hidden_field" form helper and is the method used to get a list of tag on the model
def initialize(form:, method:, tags:,
placeholder: I18n.t("components.tag_list_input.default_placeholder"),
only_one: false,
aria_label: nil,
hidden_field_data_options: {},
autocomplete_url: "")
@name = name
aria_label: nil)
@f = form
@method = method
@tags = tags
@placeholder = placeholder
@only_one = only_one
@aria_label_option = aria_label ? { 'aria-label': aria_label } : {}
@hidden_field_data_options = hidden_field_data_options
@autocomplete_url = autocomplete_url
end
attr_reader :name, :tags, :placeholder, :only_one, :aria_label_option,
:hidden_field_data_options, :autocomplete_url
private
def display
return "none" if tags.length >= 1 && only_one == true
"block"
end
attr_reader :f, :method, :tags, :placeholder, :aria_label_option
end

View File

@@ -1,27 +1,19 @@
%div{ "data-controller": "tag-list-input", "data-tag-list-input-only-one-value": "#{only_one}", "data-tag-list-input-url-value": autocomplete_url, "data-action": "autocomplete.change->tag-list-input#addTag" }
%div{ "data-controller": "tag-list-input-component--tag-list-input" }
.tags-input
.tags
- # We use display:none instead of hidden field, so changes to the value can be picked up by the bulkFormController
= text_field_tag name, tags.join(","), {"data-tag-list-input-target": "tagList", "style": "display: none"}.merge(hidden_field_data_options)
%ul.tag-list{"data-tag-list-input-target": "list"}
%template{"data-tag-list-input-target": "template"}
= f.text_field method.to_sym, value: tags.join(","), "data-tag-list-input-component--tag-list-input-target": "tagList", "style": "display: none"
%ul.tag-list{"data-tag-list-input-component--tag-list-input-target": "list"}
%template{"data-tag-list-input-component--tag-list-input-target": "template"}
%li.tag-item
.tag-template
%span
%a.remove-button{ "data-action": "click->tag-list-input#removeTag" }
%a.remove-button{ "data-action": "click->tag-list-input-component--tag-list-input#removeTag" }
×
- tags.each do |tag|
%li.tag-item
.tag-template
%span=tag
%a.remove-button{ "data-action": "click->tag-list-input#removeTag" }
%a.remove-button{ "data-action": "click->tag-list-input-component--tag-list-input#removeTag" }
×
= text_field_tag("variant_add_tag",
nil,
{ class: "input",
placeholder: placeholder,
"data-action": "keydown.enter->tag-list-input#keyboardAddTag keyup->tag-list-input#filterInput blur->tag-list-input#onBlur focus->tag-list-input#onInputChange",
"data-tag-list-input-target": "input",
**aria_label_option,
style: "display: #{display};"})
%ul.suggestion-list{ "data-tag-list-input-target": "results" , hidden: true }
= text_field_tag "variant_add_tag_#{f.object.id}".to_sym, nil, class: "input", placeholder: placeholder, "data-action": "keydown.enter->tag-list-input-component--tag-list-input#addTag keyup->tag-list-input-component--tag-list-input#filterInput", "data-tag-list-input-component--tag-list-input-target": "newTag", **aria_label_option

View File

@@ -67,35 +67,4 @@
font-size: 14px;
}
}
ul.suggestion-list {
margin-top: 5px;
padding: 5px 0;
z-index: $tag-drop-down-z-index;
width: fit-content;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.2);
box-shadow: $shadow-dropdown;
list-style-type: none;
max-height: 280px;
overflow-y: auto;
position: relative;
li.suggestion-item {
padding: 5px 10px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #000;
background-color: #fff;
width: stretch;
&.active,
&:hover {
color: #fff;
background-color: $color-link-visited;
}
}
}
}

View File

@@ -1,44 +1,28 @@
import { Autocomplete } from "stimulus-autocomplete";
import { Controller } from "stimulus";
// Extend the stimulus-autocomplete controller, so we can load tag with existing rules
// The autocomplete functionality is only loaded if the url value is set
// For more informatioon on "stimulus-autocomplete", see:
// https://github.com/afcapel/stimulus-autocomplete/tree/main
//
export default class extends Autocomplete {
static targets = ["tagList", "input", "template", "list"];
static values = { onlyOne: Boolean };
connect() {
// Don't start autocomplete controller if we don't have an url
if (this.urlValue.length == 0) return;
super.connect();
}
export default class extends Controller {
static targets = ["tagList", "newTag", "template", "list"];
addTag(event) {
const newTagName = this.inputTarget.value.trim().replaceAll(" ", "-");
// prevent hotkey form submitting the form (default action for "enter" key)
event.preventDefault();
// Check if tag already exist
const newTagName = this.newTagTarget.value.trim();
if (newTagName.length == 0) {
return;
}
// Check if tag already exist
const tags = this.tagListTarget.value.split(",");
const index = tags.indexOf(newTagName);
if (index != -1) {
// highlight the value in red
this.inputTarget.classList.add("tag-error");
this.newTagTarget.classList.add("tag-error");
return;
}
// add to tagList
if (this.tagListTarget.value == "") {
this.tagListTarget.value = newTagName;
} else {
this.tagListTarget.value = this.tagListTarget.value.concat(`,${newTagName}`);
}
// manualy dispatch an Input event so the change can get picked up by other controllers
this.tagListTarget.dispatchEvent(new InputEvent("input"));
this.tagListTarget.value = this.tagListTarget.value.concat(`,${newTagName}`);
// Create new li component with value
const newTagElement = this.templateTarget.content.cloneNode(true);
@@ -47,21 +31,7 @@ export default class extends Autocomplete {
this.listTarget.appendChild(newTagElement);
// Clear new tag value
this.inputTarget.value = "";
// hide tag input if limited to one tag
if (this.tagListTarget.value.split(",").length == 1 && this.onlyOneValue == true) {
this.inputTarget.style.display = "none";
}
}
keyboardAddTag(event) {
// prevent hotkey form submitting the form (default action for "enter" key)
if (event) {
event.preventDefault();
}
this.addTag();
this.newTagTarget.value = "";
}
removeTag(event) {
@@ -70,24 +40,19 @@ export default class extends Autocomplete {
// Remove tag from list
const tags = this.tagListTarget.value.split(",");
this.tagListTarget.value = tags.filter((tag) => tag != tagName).join(",");
this.tagListTarget.value = tags.filter(tag => tag != tagName).join(",");
// manualy dispatch an Input event so the change gets picked up by the bulk form controller
this.tagListTarget.dispatchEvent(new InputEvent("input"));
// Remove HTML element from the list
event.srcElement.parentElement.parentElement.remove();
// Make sure the tag input is displayed
if (this.tagListTarget.value.length == 0) {
this.inputTarget.style.display = "block";
}
}
filterInput(event) {
// clear error class if key is not enter
if (event.key !== "Enter") {
this.inputTarget.classList.remove("tag-error");
this.newTagTarget.classList.remove("tag-error");
}
// Strip comma from tag name
@@ -95,53 +60,4 @@ export default class extends Autocomplete {
event.srcElement.value = event.srcElement.value.replace(",", "");
}
}
// Add tag if we don't have an autocomplete list open
onBlur() {
// check if we have any autocomplete results
if (this.resultsTarget.childElementCount == 0) this.addTag();
}
// Override original to add tag filtering
replaceResults(html) {
const filteredHtml = this.#filterResults(html);
// Don't show result if we don't have anything to show
if (filteredHtml.length == 0) return;
super.replaceResults(filteredHtml);
}
// Override original to all empty query, which will return all existing tags
onInputChange = () => {
if (this.urlValue.length == 0) return;
if (this.hasHiddenTarget) this.hiddenTarget.value = "";
const query = this.inputTarget.value.trim();
if (query.length >= this.minLengthValue) {
this.fetchResults(query);
} else {
this.hideAndRemoveOptions();
}
};
//private
#filterResults(html) {
const existingTags = this.tagListTarget.value.split(",");
// Parse the HTML
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const lis = doc.getElementsByTagName("li");
// Filter
let filteredHtml = "";
for (let li of lis) {
if (!existingTags.includes(li.dataset.autocompleteValue)) {
filteredHtml += li.outerHTML;
}
}
return filteredHtml;
}
}

View File

@@ -1,66 +0,0 @@
# frozen_string_literal: true
class TagRuleFormComponent < ViewComponent::Base
def initialize(rule:, index:, customer_tags: "",
hidden_field_customer_tag_options: {})
@rule = rule
@index = index
@customer_tags = customer_tags
@hidden_field_customer_tag_options = hidden_field_customer_tag_options
end
attr_reader :rule, :index, :customer_tags, :hidden_field_customer_tag_options
private
def element_name(name)
"enterprise[tag_rules_attributes][#{index}][#{name}]"
end
def rule_data # rubocop:disable Metrics/MethodLength
case rule.type
when "TagRule::FilterShippingMethods"
{
text_top: t('components.tag_rule_form.tag_rules.shipping_method_tagged_top'),
text_bottom: t('components.tag_rule_form.tag_rules.shipping_method_tagged_bottom'),
taggable: "shipping_method",
visibility_field: "preferred_matched_shipping_methods_visibility",
}
when "TagRule::FilterPaymentMethods"
{
text_top: t('components.tag_rule_form.tag_rules.payment_method_tagged_top'),
text_bottom: t('components.tag_rule_form.tag_rules.payment_method_tagged_bottom'),
taggable: "payment_method",
visibility_field: "preferred_matched_payment_methods_visibility",
}
when "TagRule::FilterOrderCycles"
{
text_top: t('components.tag_rule_form.tag_rules.order_cycle_tagged_top'),
text_bottom: t('components.tag_rule_form.tag_rules.order_cycle_tagged_bottom'),
taggable: "exchange",
visibility_field: "preferred_matched_order_cycles_visibility",
}
when "TagRule::FilterProducts"
{
text_top: t('components.tag_rule_form.tag_rules.inventory_tagged_top'),
text_bottom: t('components.tag_rule_form.tag_rules.inventory_tagged_bottom'),
taggable: "variant",
visibility_field: "preferred_matched_variants_visibility",
}
when "TagRule::FilterVariants"
{
text_top: t('components.tag_rule_form.tag_rules.variant_tagged_top'),
text_bottom: t('components.tag_rule_form.tag_rules.variant_tagged_bottom'),
taggable: "variant",
visibility_field: "preferred_matched_variants_visibility",
}
end
end
def visibility_options
[
[t('components.tag_rule_form.tag_rules.visible'), "visible"],
[t('components.tag_rule_form.tag_rules.not_visible'), "hidden"]
]
end
end

View File

@@ -1,35 +0,0 @@
%div{ id: "tr_#{index}" }
%table
%colgroup
%col.text{ width: "35%" }
%col.inputs{ width: "55%" }
%col.actions{ width: "10%" }
%tr
%td
= hidden_field_tag element_name("id"), rule.id
= hidden_field_tag element_name("type"), rule.type
= hidden_field_tag element_name("priority"), index
= hidden_field_tag element_name("is_default"), rule.is_default
= hidden_field_tag element_name("preferred_customer_tags"), customer_tags, hidden_field_customer_tag_options
%span.text-normal
= rule_data[:text_top]
%td
= render TagListInputComponent.new(name: element_name("preferred_#{rule_data[:taggable]}_tags"), tags: rule.tags.split(","), only_one: true)
%td.actions{ rowspan: 2 , "data-controller": "delete-tag-rule", "data-delete-tag-rule-index-value": index }
- if rule.new_record?
= link_to("", "#", { "data-action": "click->delete-tag-rule#delete" ,class: "delete-tag-rule icon-trash no-text"})
- else
= link_to("", "#{admin_enterprise_tag_rule_url(rule.enterprise_id, rule.id)}?index=#{index}", { "data-turbo-method": "delete", "data-turbo-confirm": t("admin.tag_rules.confirm_delete"), class: "delete-tag-rule icon-trash no-text" })
%tr
%td
%span.text-normal
= rule_data[:text_bottom]
%td
%div
%div
- if rule.is_default
= hidden_field_tag "enterprise[tag_rules_attributes][#{index}][#{rule_data[:visibility_field]}]", "hidden"
%span.text-normal
= t(:not_visible)
- else
= select_tag "enterprise[tag_rules_attributes][#{index}][#{rule_data[:visibility_field]}]", options_for_select(visibility_options, rule.public_send(rule_data[:visibility_field].to_sym) ), { "data-controller": "tom-select", class: "primary no-search" }

View File

@@ -1,26 +0,0 @@
# frozen_string_literal: true
class TagRuleGroupFormComponent < ViewComponent::Base
def initialize(group:, index:, customer_rule_index:, tag_rule_types:)
@group = group
@index = index
@customer_rule_index = customer_rule_index
@tag_rule_types = tag_rule_types
end
attr_reader :group, :index, :customer_rule_index, :tag_rule_types
private
def form_id
"tg_#{index}"
end
def customer_tag_rule_div_id
"new-customer-tag-rule-#{index}"
end
def tag_list_input_name
"group[#{index}][preferred_customer_tags]"
end
end

View File

@@ -1,39 +0,0 @@
%div{ id: form_id, "data-controller": "tag-rule-group-form" }
- rule_index = customer_rule_index
.customer_tag
.header
%table
%colgroup
%col{width: '35%'}
%col{width: '65%'}
%tr
%td
%h5
= t('components.tag_rule_group_form.for_customers_tagged')
%td
= render(TagListInputComponent.new(name: tag_list_input_name,
tags: group[:tags],
only_one: true,
hidden_field_data_options: { "data-action": "input->tag-rule-group-form#updatePreferredCustomerTag", "data-tag-rule-group-form-target": "customerTag" }))
%div{ id: customer_tag_rule_div_id }
- if group[:rules].empty?
.no_rules
= t('components.tag_rule_group_form.no_rules_yet')
- else
- group[:rules].each do |rule|
- rule_index += 1
= render(TagRuleFormComponent.new(rule: rule,
index: rule_index,
customer_tags: group[:tags],
hidden_field_customer_tag_options: { "data-tag-rule-group-form-target": "ruleCustomerTag" }))
%hr
.add_rule.text-center
%input.button{ type: 'button', value: t('components.tag_rule_group_form.add_new_rule'), "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "tag_rule_add_new_rule_#{index}" }
= render AddTagRuleModalComponent.new(id: "tag_rule_add_new_rule_#{index}",
tag_rule_types: tag_rule_types,
current_index: (rule_index + 1),
div_id: customer_tag_rule_div_id,
customer_tag: group[:tags],
hidden_field_customer_tag_options: { "data-tag-rule-group-form-target": "ruleCustomerTag" })

View File

@@ -1,11 +0,0 @@
import { Controller } from "stimulus";
export default class extends Controller {
static targets = ["customerTag", "ruleCustomerTag"];
updatePreferredCustomerTag() {
const customerTag = this.customerTagTarget.value;
this.ruleCustomerTagTargets.forEach((element) => (element.value = customerTag));
}
}

View File

@@ -1,4 +1,4 @@
.vertical-ellipsis-menu{ "data-controller": "vertical-ellipsis-menu" }
%i.fa.fa-ellipsis-v{ "data-action": "click->vertical-ellipsis-menu#toggle" }
.vertical-ellipsis-menu-content{ "data-vertical-ellipsis-menu-target": "content" }
.vertical-ellipsis-menu{ "data-controller": "vertical-ellipsis-menu--component" }
%i.fa.fa-ellipsis-v{ "data-action": "click->vertical-ellipsis-menu--component#toggle" }
.vertical-ellipsis-menu-content{ "data-vertical-ellipsis-menu--component-target": "content" }
= content

View File

@@ -0,0 +1,6 @@
# frozen_string_literal: true
module VerticalEllipsisMenu
class Component < ViewComponent::Base
end
end

View File

@@ -1,4 +0,0 @@
# frozen_string_literal: true
class VerticalEllipsisMenuComponent < ViewComponent::Base
end

View File

@@ -3,7 +3,6 @@
module Admin
class BulkLineItemsController < Spree::Admin::BaseController
include PaginationData
# GET /admin/bulk_line_items.json
#
def index

View File

@@ -51,18 +51,6 @@ module Admin
end
end
# copy of Admin::ResourceController without flash notice
def update
if @object.update(permitted_resource_params)
respond_with(@object) do |format|
format.html { redirect_to location_after_save }
format.js { render layout: false }
end
else
respond_with(@object)
end
end
# copy of Admin::ResourceController without flash notice
def destroy
if @object.destroy

View File

@@ -15,8 +15,8 @@ module Admin
def index
# Fetch DFC catalog JSON for preview
@catalog_url = params.require(:catalog_url).strip
@catalog_data = api.call(@catalog_url)
catalog = DfcCatalog.from_json(@catalog_data)
@catalog_json = api.call(@catalog_url)
catalog = DfcCatalog.from_json(@catalog_json)
# Render table and let user decide which ones to import.
@items = list_products(catalog)

View File

@@ -51,7 +51,6 @@ module Admin
load_tag_rule_types
load_tag_rules
return unless params[:stimulus]
@enterprise.is_primary_producer = params[:is_primary_producer]
@@ -84,8 +83,6 @@ module Admin
format.turbo_stream
end
else
load_tag_rule_types
load_tag_rules
respond_with(@object) do |format|
format.json {
render json: { errors: @object.errors.messages }, status: :unprocessable_entity
@@ -150,18 +147,6 @@ module Admin
end
end
def new_tag_rule_group
load_tag_rule_types
@index = params[:index]
@customer_rule_index = params[:customer_rule_index].to_i
@group = { tags: [], rules: [] }
respond_to do |format|
format.turbo_stream
end
end
protected
def delete_custom_tab
@@ -394,32 +379,16 @@ module Admin
end
def load_tag_rule_types
# Load rule types
@tag_rule_types = [
[t(".form.tag_rules.show_hide_shipping"), "FilterShippingMethods"],
[t(".form.tag_rules.show_hide_payment"), "FilterPaymentMethods"],
[t(".form.tag_rules.show_hide_order_cycles"), "FilterOrderCycles"]
{ id: "FilterShippingMethods", name: t('js.tag_rules.show_hide_shipping') },
{ id: "FilterPaymentMethods", name: t('js.tag_rules.show_hide_payment') },
{ id: "FilterOrderCycles", name: t('js.tag_rules.show_hide_order_cycles') }
]
if helpers.feature?(:variant_tag, @object)
@tag_rule_types.prepend([t(".form.tag_rules.show_hide_variants"), "FilterVariants"])
elsif helpers.feature?(:inventory, @object)
@tag_rule_types.prepend([t(".form.tag_rules.show_hide_variants"), "FilterProducts"])
end
end
return unless helpers.feature?(:inventory, @object)
def load_tag_rules
if helpers.feature?(:variant_tag, @object)
@default_rules = @enterprise.tag_rules.exclude_inventory.select(&:is_default)
@rules = @enterprise.tag_rules.exclude_inventory.prioritised.reject(&:is_default)
elsif helpers.feature?(:inventory, @object)
@default_rules = @enterprise.tag_rules.exclude_variant.select(&:is_default)
@rules = @enterprise.tag_rules.exclude_variant.prioritised.reject(&:is_default)
else
@default_rules =
@enterprise.tag_rules.exclude_inventory.exclude_variant.select(&:is_default)
@rules =
@enterprise.tag_rules.exclude_inventory.exclude_variant.prioritised.reject(&:is_default)
end
@tag_rule_types.prepend({ id: "FilterProducts", name: t('js.tag_rules.show_hide_variants') })
end
def setup_property

View File

@@ -49,7 +49,7 @@ module Admin
errors: @importer.errors.full_messages
}
if helpers.inventory_enabled?(spree_current_user.enterprises)
if helpers.feature?(:inventory, spree_current_user.enterprises)
json[:results][:inventory_created] = @importer.inventory_created_count
json[:results][:inventory_updated] = @importer.inventory_updated_count
end
@@ -175,7 +175,7 @@ module Admin
# Return an error if trying to import into inventories when inventory is disable
def can_import_into_inventories?
return true if helpers.inventory_enabled?(spree_current_user.enterprises) ||
return true if helpers.feature?(:inventory, spree_current_user.enterprises) ||
params.dig(:settings, "import_into") != 'inventories'
redirect_to admin_product_import_url, notice: I18n.t(:product_import_inventory_disable)

View File

@@ -11,8 +11,7 @@ module Admin
def index
fetch_products
render "index",
locals: { producer_options:, categories:, tax_category_options:, available_tags:,
flash:, allowed_producers: }
locals: { producers:, categories:, tax_category_options:, available_tags:, flash: }
session[:products_return_to_url] = request.url
end
@@ -33,8 +32,7 @@ module Admin
render "index", status: :unprocessable_entity,
locals: {
producer_options:, categories:, tax_category_options:, available_tags:,
allowed_producers:, flash:
producers:, categories:, tax_category_options:, available_tags:, flash:
}
end
end
@@ -80,29 +78,27 @@ module Admin
end
def clone
product = Spree::Product.find(params[:id])
authorize! :clone, product
@product = Spree::Product.find(params[:id])
authorize! :clone, @product
status = :ok
begin
cloned_product = product.duplicate
@cloned_product = @product.duplicate
flash.now[:success] = t('.success')
product_index = "-#{cloned_product.id}"
@product_index = "-#{@cloned_product.id}"
@producer_options = producers
@category_options = categories
@tax_category_options = tax_category_options
rescue ActiveRecord::ActiveRecordError => e
flash.now[:error] = clone_error_message(e)
status = :unprocessable_entity
product_index = "-1" # Create a unique enough index
@product_index = "-1" # Create a unique enough index
end
respond_with do |format|
format.turbo_stream {
render :clone, status:,
locals: { product:, cloned_product:, product_index:, producer_options:,
category_options: categories, tax_category_options:,
allowed_producers: }
}
format.turbo_stream { render :clone, status: }
end
end
@@ -127,32 +123,12 @@ module Admin
@page = params[:page].presence || 1
@per_page = params[:per_page].presence || 15
@q = params.permit(q: {})[:q] || { s: 'name asc' }
# Transform on_hand sorting to properly handle On-Demand products:
# - On-Demand products should ignore on_hand completely and sort alphabetically.
# - Non-On-Demand products should continue sorting by on_hand as usual.
if @q[:s] == 'on_hand asc'
@q[:s] = [
'backorderable_priority asc',
'backorderable_name asc',
@q[:s]
]
elsif @q[:s] == 'on_hand desc'
@q[:s] = [
'backorderable_priority desc',
'backorderable_name asc',
@q[:s]
]
end
end
def allowed_producers
OpenFoodNetwork::Permissions.new(spree_current_user)
def producers
producers = OpenFoodNetwork::Permissions.new(spree_current_user)
.managed_product_enterprises.is_primary_producer.by_name
end
def producer_options
allowed_producers.map { |p| [p.name, p.id] }
producers.map { |p| [p.name, p.id] }
end
def categories
@@ -179,28 +155,8 @@ module Admin
product_query = OpenFoodNetwork::Permissions.new(spree_current_user)
.editable_products.merge(product_scope_with_includes).ransack(ransack_query).result
# Postgres requires ORDER BY expressions to appear in the SELECT list when using DISTINCT.
# When the current ransack sort uses the computed stock columns, include them in the select
# so the generated COUNT/DISTINCT query is valid.
sort_columns = Array(@q && @q[:s]).flatten
if sort_columns.any? { |s|
s.to_s.include?('on_hand') || s.to_s.include?('backorderable_priority')
}
product_query = product_query.select(
Arel.sql('spree_products.*'),
Spree::Product.backorderable_priority_sql,
Spree::Product.backorderable_name_sql,
Spree::Product.on_hand_sql
)
end
@pagy, @products = pagy(
product_query.order(:name),
limit: @per_page,
page: @page,
size: [1, 2, 2, 1]
)
@pagy, @products = pagy(product_query.order(:name), limit: @per_page, page: @page,
size: [1, 2, 2, 1])
end
def product_scope

View File

@@ -4,7 +4,6 @@ module Admin
class ReportsController < Spree::Admin::BaseController
include ActiveStorage::SetCurrent
include ReportsActions
helper ReportsHelper
before_action :authorize_report, only: [:show, :create]
@@ -23,12 +22,14 @@ module Admin
def show
@report = report_class.new(spree_current_user, params, render: false)
@rendering_options = rendering_options
show_report
end
def create
@report = report_class.new(spree_current_user, params, render: true)
update_rendering_options
render_in_background
end
@@ -60,9 +61,7 @@ module Admin
@blob = ReportBlob.create_for_upload_later!(report_filename)
ReportJob.perform_later(
report_class:,
user: spree_current_user,
params:,
report_class:, user: spree_current_user, params:,
format: report_format,
blob: @blob,
channel: ScopedChannel.for_id(params[:uuid]),

View File

@@ -1,47 +1,13 @@
# frozen_string_literal: true
module Admin
class TagRulesController < Spree::Admin::BaseController
class TagRulesController < Admin::ResourceController
respond_to :json
def new
@index = params[:index]
@div_id = params[:div_id]
is_default = params[:is_default]
@customer_tags = params[:customer_tags]
respond_override destroy: { json: {
success: lambda { head :no_content }
} }
status = :ok
if permitted_tag_rule_type.include?(params[:rule_type])
@default_rule = "TagRule::#{params[:rule_type]}".constantize.new(is_default:)
else
flash.now[:error] = t(".not_supported_type")
status = :internal_server_error
end
respond_with do |format|
format.turbo_stream { render :new, status: }
end
end
def destroy
@rule = TagRule.find(params[:id])
@index = params[:index]
authorize! :destroy, @rule
status = :ok
if @rule.destroy
flash[:success] = Spree.t(:successfully_removed, resource: "Tag Rule")
else
flash.now[:error] = t(".destroy_error")
status = :internal_server_error
end
respond_to do |format|
format.turbo_stream { render :destroy, status: }
end
end
# Used by the tag input autocomplete
def map_by_tag
respond_to do |format|
format.json do
@@ -51,26 +17,6 @@ module Admin
end
end
# Use to populate autocomplete with available rule for the given tag/enterprise
def variant_tag_rules
tag_rules =
TagRule.matching_variant_tag_rules_by_enterprises(params[:enterprise_id], params[:q])
@formatted_tag_rules = tag_rules.each_with_object({}) do |rule, mapping|
rule.preferred_variant_tags.split(",").each do |tag|
if mapping[tag]
mapping[tag][:rules] += 1
else
mapping[tag] = { tag:, rules: 1 }
end
end
end.values
respond_with do |format|
format.html { render :variant_tag_rules, layout: false }
end
end
private
def collection_actions
@@ -93,13 +39,5 @@ module Admin
Enterprise.managed_by(spree_current_user)
end
end
def model_class
TagRule
end
def permitted_tag_rule_type
%w{FilterOrderCycles FilterPaymentMethods FilterProducts FilterShippingMethods FilterVariants}
end
end
end

View File

@@ -44,8 +44,6 @@ module Admin
def load_data
@hubs = OpenFoodNetwork::Permissions.new(spree_current_user).
variant_override_hubs.by_name
# Only display the ones with inventory enabled
@hubs = @hubs.select { |p| helpers.feature?(:inventory, p) }
# Used in JS to look up the name of the producer of each product
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).

View File

@@ -43,7 +43,7 @@ module Api
@enterprise = Enterprise.find_by(permalink: params[:id]) || Enterprise.find(params[:id])
authorize! :update, @enterprise
if params[:logo] && @enterprise.update!(logo: params[:logo])
if params[:logo] && @enterprise.update( logo: params[:logo] )
render(html: @enterprise.logo_url(:medium), status: :ok)
elsif params[:promo] && @enterprise.update!( promo_image: params[:promo] )
render(html: @enterprise.promo_image_url(:medium), status: :ok)

View File

@@ -7,7 +7,6 @@ module Api
module V0
class ExchangeProductsController < Api::V0::BaseController
include PaginationData
DEFAULT_PER_PAGE = 100
skip_authorization_check only: [:index]

View File

@@ -23,8 +23,7 @@ module Api
order_cycle,
customer,
search_params,
inventory_enabled:,
variant_tag_enabled:
inventory_enabled:
).products_json
render plain: products
@@ -97,17 +96,13 @@ module Api
def distributed_products
OrderCycles::DistributedProductsService.new(
distributor, order_cycle, customer, inventory_enabled:, variant_tag_enabled:,
distributor, order_cycle, customer, inventory_enabled:
).products_relation.pluck(:id)
end
def inventory_enabled
OpenFoodNetwork::FeatureToggle.enabled?(:inventory, distributor)
end
def variant_tag_enabled
OpenFoodNetwork::FeatureToggle.enabled?(:variant_tag, distributor)
end
end
end
end

View File

@@ -7,7 +7,6 @@ module Api
module V0
class ProductsController < Api::V0::BaseController
include PaginationData
respond_to :json
DEFAULT_PER_PAGE = 15

View File

@@ -8,10 +8,6 @@ module Api
include AddressTransformation
include ExtraFields
wrap_parameters :customer, include:
Customer.attribute_names +
[:billing_address, :shipping_address]
skip_authorization_check only: :index
before_action :authorize_action, only: [:show, :update, :destroy]
@@ -92,8 +88,7 @@ module Api
attributes = params.require(:customer).permit(
:email, :enterprise_id,
:code, :first_name, :last_name,
:billing_address,
shipping_address: [
:billing_address, shipping_address: [
:phone, :latitude, :longitude,
:first_name, :last_name,
:street_address_1, :street_address_2,

View File

@@ -4,7 +4,7 @@ module ManagerInvitations
extend ActiveSupport::Concern
def create_new_manager(email, enterprise)
password = SecureRandom.base58(64)
password = Devise.friendly_token
new_user = Spree::User.create(email:, unconfirmed_email: email, password:)
new_user.reset_password_token = Devise.friendly_token
# Same time as used in Devise's lib/devise/models/recoverable.rb.

View File

@@ -20,43 +20,25 @@ module OrderStockCheck
@updated_variants = check_stock_service.update_line_items
end
def check_order_cycle_expiry(should_empty_order: true)
def check_order_cycle_expiry
return unless current_order_cycle&.closed?
Alert.raise_with_record("Notice: order cycle closed during checkout completion", current_order)
current_order.empty!
current_order.assign_order_cycle! nil
handle_closed_order_cycle if should_empty_order
flash[:info] = build_order_cycle_message(should_empty_order)
redirect_to_shop_page(should_empty_order)
flash[:info] = I18n.t('order_cycle_closed')
respond_to do |format|
format.cable_ready {
render status: :see_other, cable_ready: cable_car.redirect_to(url: main_app.shop_path)
}
format.json { render json: { path: main_app.shop_path }, status: :see_other }
format.html { redirect_to main_app.shop_path, status: :see_other }
end
end
private
def handle_closed_order_cycle
current_order.empty!
current_order.assign_order_cycle!(nil)
end
def build_order_cycle_message(should_empty_order)
# If order is not emptied, we assume user will contact support for next steps
key = should_empty_order ? 'order_cycle_closed' : 'order_cycle_closed_next_steps'
I18n.t(key, order_number: current_order.number)
end
def redirect_to_shop_page(should_empty_order)
# If order is not emptied, redirect to shops page because shop page empties the order by default
redirect_url = should_empty_order ? main_app.shop_path : main_app.shops_path
respond_to do |format|
format.cable_ready {
render status: :see_other, cable_ready: cable_car.redirect_to(url: redirect_url)
}
format.json { render json: { path: redirect_url }, status: :see_other }
format.html { redirect_to redirect_url, status: :see_other }
end
end
def check_stock_service
@check_stock_service ||= Orders::CheckStockService.new(order: @order)
end

View File

@@ -84,7 +84,6 @@ module ReportsActions
else
params[:fields_to_show]
end,
display_metadata_rows: false,
display_summary_row: request.get?,
display_header_row: false
}
@@ -95,7 +94,6 @@ module ReportsActions
rendering_options.update(
options: {
fields_to_show: params[:fields_to_show],
display_metadata_rows: params[:display_metadata_rows].present?,
display_summary_row: params[:display_summary_row].present?,
display_header_row: params[:display_header_row].present?
}

View File

@@ -8,9 +8,7 @@ module PaymentGateways
before_action :destroy_orphaned_paypal_payments, only: :confirm
before_action :load_checkout_order, only: [:express, :confirm]
before_action :handle_insufficient_stock, only: [:express, :confirm]
before_action -> { check_order_cycle_expiry(should_empty_order: false) }, only: [
:express, :confirm
]
before_action :check_order_cycle_expiry, only: [:express, :confirm]
before_action :permit_parameters!
after_action :reset_order_when_complete, only: :confirm

View File

@@ -7,7 +7,7 @@ module PaymentGateways
before_action :load_checkout_order, only: :confirm
before_action :validate_payment_intent, only: :confirm
before_action -> { check_order_cycle_expiry(should_empty_order: false) }, only: :confirm
before_action :check_order_cycle_expiry, only: :confirm
def confirm
validate_stock

View File

@@ -9,7 +9,11 @@ class PaymentsController < BaseController
@payment = Spree::Payment.find(params[:id])
authorize! :show, @payment.order
redirect_to(@payment.redirect_auth_url || order_url(@payment.order))
if (url = @payment.cvv_response_message)
redirect_to url
else
redirect_to order_url(@payment.order)
end
end
private

View File

@@ -6,7 +6,6 @@ module Spree
module Admin
class OrdersController < Spree::Admin::BaseController
include OpenFoodNetwork::SpreeApiKeyLoader
helper CheckoutHelper
before_action :load_order, only: [:edit, :update, :fire, :resend, :invoice, :print]

View File

@@ -10,12 +10,6 @@ module Spree
respond_to :html
PAYMENT_METHODS = %w{
Spree::PaymentMethod::Check
Spree::Gateway::PayPalExpress
Spree::Gateway::StripeSCA
}.index_with(&:constantize).freeze
def create
force_environment
@@ -95,9 +89,8 @@ module Spree
@payment_method = PaymentMethod.find(params[:pm_id])
end
else
@payment_method = PAYMENT_METHODS.fetch(params[:provider_type], PaymentMethod).new
@payment_method = params[:provider_type].constantize.new
end
render partial: 'provider_settings'
end

View File

@@ -10,7 +10,6 @@ module Spree
include OpenFoodNetwork::SpreeApiKeyLoader
include OrderCyclesHelper
include EnterprisesHelper
helper ::Admin::ProductsHelper
helper Spree::Admin::TaxCategoriesHelper

View File

@@ -14,7 +14,6 @@ module Spree
include Spree::Core::ControllerHelpers::Order
include I18nHelper
before_action :set_locale
# Devise::PasswordsController allows for blank passwords.

View File

@@ -1,13 +0,0 @@
# frozen_string_literal: true
class WellKnownController < ApplicationController
layout nil
def dfc
base = "https://github.com/datafoodconsortium/taxonomies/releases/latest/download/scopes.rdf#"
render json: {
"#{base}ReadEnterprise" => "/api/dfc/enterprises/",
"#{base}ReadProducts" => "/api/dfc/supplied_products/",
}
end
end

View File

@@ -36,7 +36,9 @@ class ScheduleForm
false unless @schedule.update(permitted_resource_params)
end
delegate :order_cycle_ids, to: :@schedule
def order_cycle_ids
@schedule.order_cycle_ids
end
private

View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Admin
module EnterprisesHelper # rubocop:disable Metrics/ModuleLength
module EnterprisesHelper
def add_check_if_single(count)
if count == 1
{ checked: true }
@@ -28,7 +28,7 @@ module Admin
show_connected_apps = can?(:manage_connected_apps, enterprise) &&
(connected_apps_enabled(enterprise).present? ||
dfc_platforms_available?)
show_inventory_settings = feature?(:inventory, *spree_current_user.enterprises) && is_shop
show_inventory_settings = feature?(:inventory, spree_current_user.enterprises) && is_shop
show_options = {
show_properties:,
@@ -50,7 +50,7 @@ module Admin
end
def dfc_platforms_available?
ApiUser::PLATFORMS.keys.any? do |id|
DfcProvider::PlatformsController::PLATFORM_IDS.keys.any? do |id|
feature?(id, spree_current_user)
end
end
@@ -76,30 +76,8 @@ module Admin
Enterprise::SELLS.map { |s| [I18n.t(s, scope:), s] }
end
# Group tag rules per rule.preferred_customer_tags
def tag_groups(tag_rules)
tag_rules.each_with_object([]) do |tag_rule, tag_groups|
tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.split(","))
if tag_group[:rules].blank?
tag_groups << tag_group
tag_group[:position] = tag_groups.count
end
tag_group[:rules] << tag_rule
end
end
private
def find_match(tag_groups, tags)
tag_groups.each do |tag_group|
return tag_group if tag_group[:tags].length == tags.length &&
(tag_group[:tags] & tags) == tag_group[:tags]
end
{ tags:, rules: [] }
end
def build_enterprise_side_menu_items(is_shop:, show_options: ) # rubocop:disable Metrics/MethodLength
[
{ name: 'primary_details', icon_class: "icon-home", show: true, selected: 'selected' },

View File

@@ -41,11 +41,5 @@ module Admin
def hide_producer_column?(producer_options)
spree_current_user.column_preferences.bulk_edit_product.empty? && producer_options.one?
end
# check if the user is in the "admins" group or if it's enabled for any of
# the enterprises the user manages
def variant_tag_enabled?(user)
feature?(:variant_tag, user) || feature?(:variant_tag, *user.enterprises)
end
end
end

View File

@@ -52,9 +52,9 @@ module ApplicationHelper
# Pass URL helper calls on to spree where applicable so that we don't need to use
# spree.foo_path in any view rendered from non-spree-namespaced controllers.
def method_missing(method, *, &)
def method_missing(method, *args, &)
if method.to_s.end_with?('_path', '_url') && spree.respond_to?(method)
spree.public_send(method, *)
spree.public_send(method, *args)
else
super
end

View File

@@ -120,7 +120,7 @@ module InjectionHelper
def inject_enterprise_attributes(enterprise_attributes)
render partial: "json/injection_ams",
locals: { name: 'enterpriseAttributes', json: enterprise_attributes.to_json }
locals: { name: 'enterpriseAttributes', json: enterprise_attributes.to_json.to_s }
end
def inject_saved_credit_cards

View File

@@ -1,29 +1,6 @@
# frozen_string_literal: true
module LinkHelper
def link_to_or_disabled(name = nil, options = nil, html_options = nil, &block)
html_options, options, name = options, name, block if block_given?
html_options ||= {}
if !!html_options.delete(:disabled)
# https://www.scottohara.me/blog/2021/05/28/disabled-links.html
html_options.merge!(
'aria-disabled': true,
class: (html_options[:class].to_s.split + ["disabled"]).uniq.join(" "),
role: "link"
)
if block_given?
content_tag("a", name, **html_options, &block)
else
content_tag("a", name, **html_options)
end
elsif block_given?
link_to options, html_options, &block
else
link_to name, options, html_options
end
end
def link_to_service(baseurl, name, html_options = {}, &)
return if name.blank?

View File

@@ -44,7 +44,9 @@ module ReportsHelper
.pluck(:name, :id)
end
delegate :currency_symbol, to: :'Spree::Money'
def currency_symbol
Spree::Money.currency_symbol
end
def enterprise_fee_owner_ids(orders)
EnterpriseFee.where(id: enterprise_fee_ids(orders))

View File

@@ -62,12 +62,6 @@ module ShopHelper
true
end
def shop_tab_class(tab)
return unless (tab == "home" && show_home_tab?) || current_order(false)&.order_cycle.nil?
"with-darker-background"
end
private
def show_groups_tabs?

View File

@@ -147,10 +147,6 @@ module Spree
dom_id(record, 'spree')
end
def inventory_enabled?(enterprises)
!feature?(:variant_tag, *enterprises) && feature?(:inventory, *enterprises)
end
private
def attribute_name_for(field_name)

View File

@@ -120,7 +120,7 @@ module Spree
end
def cancel_event_link(order)
event_label = I18n.t("cancel_order", scope: "actions")
event_label = I18n.t("cancel", scope: "actions")
button_link_to(event_label,
fire_admin_order_url(order, e: "cancel"),
method: :put, icon: "icon-cancel", form_id: "cancel_order_form")

View File

@@ -10,7 +10,9 @@ module TermsAndConditionsHelper
TermsOfService.required?(distributor)
end
delegate :platform_terms_required?, to: :TermsOfService
def platform_terms_required?
TermsOfService.platform_terms_required?
end
def distributor_terms_required?
TermsOfService.distributor_terms_required?(current_order.distributor)

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