Compare commits

..

1 Commits

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

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,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

@@ -47,7 +47,7 @@ Metrics/BlockNesting:
Exclude:
- 'app/models/spree/payment/processing.rb'
# Offense count: 48
# Offense count: 47
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ClassLength:
Exclude:
@@ -88,7 +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/sets/product_set.rb'
- 'engines/order_management/app/services/order_management/order/updater.rb'
- 'lib/open_food_network/enterprise_fee_calculator.rb'
@@ -99,6 +98,7 @@ 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: 30
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
@@ -129,13 +129,14 @@ Metrics/CyclomaticComplexity:
- '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'
@@ -148,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:
@@ -173,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:
@@ -181,27 +182,6 @@ Metrics/PerceivedComplexity:
- 'app/models/spree/ability.rb'
- 'app/models/spree/order/checkout.rb'
# Offense count: 1
# Configuration parameters: EnforcedStyle, AllowedPatterns.
# SupportedStyles: snake_case, camelCase
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: 3
# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns.
# SupportedStyles: snake_case, camelCase
Naming/VariableName:
Exclude:
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
# Offense count: 1
# Configuration parameters: TransactionMethods.
Rails/TransactionExitStatement:

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

@@ -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

View File

@@ -20,10 +20,6 @@ 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'

View File

@@ -200,7 +200,7 @@ GEM
msgpack (~> 1.2)
bugsnag (6.26.4)
concurrent-ruby (~> 1.0)
builder (3.3.0)
builder (3.2.4)
bullet (8.0.8)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
@@ -228,7 +228,7 @@ GEM
marcel (~> 1.0)
nokogiri (~> 1.10, >= 1.10.4)
rubyzip (>= 1.3.0, < 3)
cgi (0.5.0)
cgi (0.3.7)
childprocess (5.0.0)
choice (0.2.0)
chronic (0.10.2)
@@ -244,7 +244,7 @@ GEM
matrix
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.3.5)
connection_pool (2.5.4)
connection_pool (2.5.3)
cookiejar (0.3.4)
crack (1.0.0)
bigdecimal
@@ -281,9 +281,9 @@ GEM
devise (>= 4.9.0)
devise-token_authenticatable (1.1.0)
devise (>= 4.0.0, < 5.0.0)
diff-lcs (1.6.2)
digest (3.2.0)
docile (1.4.1)
diff-lcs (1.5.1)
digest (3.1.1)
docile (1.4.0)
dotenv (3.1.2)
drb (2.2.3)
em-http-request (1.1.7)
@@ -299,9 +299,7 @@ GEM
eventmachine (>= 1.0.0.beta.1)
email_validator (2.2.4)
activemodel
erb (4.0.4)
cgi (>= 0.3.3)
erubi (1.13.1)
erubi (1.12.0)
et-orbi (1.3.0)
tzinfo
eventmachine (1.2.7)
@@ -326,7 +324,7 @@ GEM
websocket-driver (>= 0.6, < 0.8)
ffaker (2.23.0)
ffi (1.16.3)
flipper (1.3.6)
flipper (1.3.0)
concurrent-ruby (< 2)
flipper-active_record (1.3.0)
activerecord (>= 4.2, < 8)
@@ -403,11 +401,10 @@ GEM
activerecord (>= 3.0)
invisible_captcha (2.3.0)
rails (>= 5.2)
io-console (0.8.1)
io-console (0.7.2)
ipaddress (0.8.3)
irb (1.15.2)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
irb (1.12.0)
rdoc
reline (>= 0.4.2)
jmespath (1.6.2)
jquery-rails (4.4.0)
@@ -466,10 +463,9 @@ GEM
marcel (1.0.4)
matrix (0.4.2)
method_source (1.1.0)
mime-types (3.7.0)
logger
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2025.0924)
mime-types (3.5.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2023.1205)
mimemagic (0.4.3)
nokogiri (~> 1)
rake
@@ -496,9 +492,9 @@ GEM
timeout
net-smtp (0.5.0)
net-protocol
newrelic_rpm (9.22.0)
newrelic_rpm (9.9.0)
nio4r (2.7.1)
nokogiri (1.18.10)
nokogiri (1.18.9)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri-html5-inference (0.3.0)
@@ -542,7 +538,7 @@ GEM
parallel (1.24.0)
paranoia (2.6.3)
activerecord (>= 5.1, < 7.2)
parser (3.3.9.0)
parser (3.3.8.0)
ast (~> 2.4.1)
racc
paypal-sdk-core (0.3.4)
@@ -557,15 +553,11 @@ GEM
ruby-rc4
ttfunk
pg (1.2.3)
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
private_address_check (0.5.0)
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
psych (5.2.6)
date
psych (5.1.2)
stringio
public_suffix (5.0.5)
puffing-billy (4.0.2)
@@ -583,7 +575,7 @@ GEM
railties (>= 4.2)
raabro (1.4.0)
racc (1.8.1)
rack (2.2.20)
rack (2.2.14)
rack-mini-profiler (2.3.4)
rack (>= 1.2.0)
rack-oauth2 (2.2.1)
@@ -601,7 +593,7 @@ GEM
rack-rewrite (1.5.1)
rack-session (1.0.2)
rack (< 3)
rack-test (2.2.0)
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.7.0)
rackup (1.0.1)
@@ -625,7 +617,7 @@ GEM
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.3.0)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
@@ -634,10 +626,10 @@ GEM
activesupport (>= 4.2)
choice (~> 0.2.0)
ruby-graphviz (~> 1.2)
rails-html-sanitizer (1.6.2)
rails-html-sanitizer (1.6.1)
loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails-i18n (7.0.10)
rails-i18n (7.0.9)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
rails_safe_tasks (1.0.0)
@@ -650,7 +642,7 @@ GEM
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.3.0)
rake (13.2.1)
ransack (4.1.1)
activerecord (>= 6.1.5)
activesupport (>= 6.1.5)
@@ -662,17 +654,15 @@ GEM
bcp47_spec (~> 0.2)
bigdecimal (~> 3.1, >= 3.1.5)
link_header (~> 0.0, >= 0.0.8)
rdoc (6.15.0)
erb
rdoc (6.6.3.1)
psych (>= 4.0.0)
tsort
redcarpet (3.6.0)
redis (5.4.1)
redis (5.2.0)
redis-client (>= 0.22.0)
redis-client (0.26.1)
redis-client (0.22.1)
connection_pool
regexp_parser (2.9.2)
reline (0.6.2)
reline (0.5.0)
io-console (~> 0.5)
request_store (1.5.1)
rack (>= 1.4)
@@ -697,18 +687,18 @@ GEM
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.5)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
rspec-expectations (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.5)
rspec-mocks (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (7.1.1)
actionpack (>= 7.0)
activesupport (>= 7.0)
railties (>= 7.0)
rspec-rails (6.1.2)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
@@ -718,22 +708,22 @@ GEM
rspec-sql (0.0.3)
activesupport
rspec
rspec-support (3.13.6)
rswag (2.16.0)
rswag-api (= 2.16.0)
rswag-specs (= 2.16.0)
rswag-ui (= 2.16.0)
rswag-api (2.16.0)
activesupport (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rswag-specs (2.16.0)
activesupport (>= 5.2, < 8.1)
json-schema (>= 2.2, < 6.0)
railties (>= 5.2, < 8.1)
rspec-support (3.13.1)
rswag (2.13.0)
rswag-api (= 2.13.0)
rswag-specs (= 2.13.0)
rswag-ui (= 2.13.0)
rswag-api (2.13.0)
activesupport (>= 3.1, < 7.2)
railties (>= 3.1, < 7.2)
rswag-specs (2.13.0)
activesupport (>= 3.1, < 7.2)
json-schema (>= 2.2, < 5.0)
railties (>= 3.1, < 7.2)
rspec-core (>= 2.14)
rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rswag-ui (2.13.0)
actionpack (>= 3.1, < 7.2)
railties (>= 3.1, < 7.2)
rubocop (1.64.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
@@ -801,7 +791,7 @@ GEM
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.2)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
spreadsheet_architect (5.0.0)
caxlsx (>= 3.3.0, < 4)
@@ -811,8 +801,7 @@ GEM
spring (>= 0.9.1)
spring-commands-rubocop (0.4.0)
spring (>= 1.0)
sprockets (3.7.5)
base64
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.4.2)
@@ -839,7 +828,7 @@ GEM
railties (>= 5.2)
redis (>= 4.0, < 6.0)
stringex (2.8.6)
stringio (3.1.7)
stringio (3.1.0)
stripe (11.1.0)
strscan (3.1.2)
swd (2.0.3)
@@ -854,7 +843,6 @@ GEM
thread-local (1.1.0)
tilt (2.3.0)
timeout (0.4.3)
tsort (0.2.0)
ttfunk (1.8.0)
bigdecimal (~> 3.1)
turbo-rails (2.0.5)
@@ -865,7 +853,7 @@ GEM
turbo-rails (>= 1.3.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
undercover (0.8.1)
undercover (0.7.4)
base64
bigdecimal
imagen (>= 0.2.0)
@@ -915,7 +903,7 @@ GEM
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
webrick (1.9.1)
webrick (1.8.2)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -927,7 +915,7 @@ GEM
xml-simple (1.1.8)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.18)
zeitwerk (2.6.15)
PLATFORMS
ruby
@@ -1055,7 +1043,6 @@ DEPENDENCIES
spring
spring-commands-rspec
spring-commands-rubocop
sprockets (~> 3.7)
state_machines-activerecord
stimulus_reflex
stimulus_reflex_testing!

View File

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

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

@@ -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

@@ -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

@@ -1,26 +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: {})
@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
end
attr_reader :name, :tags, :placeholder, :only_one, :aria_label_option, :hidden_field_data_options
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,19 +1,19 @@
%div{ "data-controller": "tag-list-input", "data-tag-list-input-only-one-value": "#{only_one}" }
%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#addTag keyup->tag-list-input#filterInput blur->tag-list-input#addTag", "data-tag-list-input-target": "newTag", **aria_label_option, style: "display: #{display};"
= 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

@@ -2,18 +2,17 @@ import { Controller } from "stimulus";
export default class extends Controller {
static targets = ["tagList", "newTag", "template", "list"];
static values = { onlyOne: Boolean };
addTag(event) {
// prevent hotkey form submitting the form (default action for "enter" key)
event.preventDefault();
const newTagName = this.newTagTarget.value.trim().replaceAll(" ", "-");
// 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) {
@@ -23,13 +22,7 @@ export default class extends Controller {
}
// 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);
@@ -39,11 +32,6 @@ export default class extends Controller {
// Clear new tag value
this.newTagTarget.value = "";
// hide tag input if limited to one tag
if (this.tagListTarget.value.split(",").length == 1 && this.onlyOneValue == true) {
this.newTagTarget.style.display = "none";
}
}
removeTag(event) {
@@ -52,18 +40,13 @@ export default class extends Controller {
// 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.newTagTarget.style.display = "block";
}
}
filterInput(event) {

View File

@@ -1,59 +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",
}
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

@@ -83,7 +83,6 @@ module Admin
format.turbo_stream
end
else
load_tag_rule_types
respond_with(@object) do |format|
format.json {
render json: { errors: @object.errors.messages }, status: :unprocessable_entity
@@ -148,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
@@ -392,15 +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') }
]
return unless helpers.feature?(:inventory, @object)
@tag_rule_types.prepend([t(".form.tag_rules.show_hide_variants"), "FilterProducts"])
@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.feature?(:inventory, *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.feature?(:inventory, *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

@@ -123,13 +123,6 @@ 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 include backorderable_priority (on-demand) for proper ordering
if @q[:s] == 'on_hand asc'
@q[:s] = ['backorderable_priority asc', @q[:s]]
elsif @q[:s] == 'on_hand desc'
@q[:s] = ['backorderable_priority desc', @q[:s]]
end
end
def producers
@@ -162,27 +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.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

@@ -22,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
@@ -59,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,45 +1,12 @@
# 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]
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
respond_override destroy: { json: {
success: lambda { head :no_content }
} }
def map_by_tag
respond_to do |format|
@@ -72,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}
end
end
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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -1,35 +0,0 @@
# frozen_string_literal: true
module ProductSortByStocks
extend ActiveSupport::Concern
included do
@on_hand_sql = Arel.sql("(
SELECT COALESCE(SUM(si.count_on_hand), 0)
FROM spree_variants v
JOIN spree_stock_items si ON si.variant_id = v.id
WHERE v.product_id = spree_products.id
GROUP BY v.product_id
)")
@backorderable_priority_sql = Arel.sql("(
SELECT BOOL_OR(si.backorderable)
FROM spree_variants v
JOIN spree_stock_items si ON si.variant_id = v.id
WHERE v.product_id = spree_products.id
GROUP BY v.product_id
)")
class << self
attr_reader :on_hand_sql, :backorderable_priority_sql
end
ransacker :on_hand do
@on_hand_sql
end
ransacker :backorderable_priority do
@backorderable_priority_sql
end
end
end

View File

@@ -193,7 +193,7 @@ module ProductImport
order('is_primary_producer ASC, name').
map { |e| @editable_enterprises[e.name] = e.id }
return unless OpenFoodNetwork::FeatureToggle.enabled?(:inventory, *@current_user.enterprises)
return unless OpenFoodNetwork::FeatureToggle.enabled?(:inventory, @current_user.enterprises)
@inventory_permissions = permissions.variant_override_enterprises_per_hub
end

View File

@@ -142,7 +142,6 @@ module Spree
can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty
can :new, TagRule
can [:admin, :map_by_tag, :destroy], TagRule do |tag_rule|
user.enterprises.include? tag_rule.enterprise
end
@@ -150,7 +149,7 @@ module Spree
can [:admin, :index, :create], Enterprise
can [:read, :edit, :update,
:remove_logo, :remove_promo_image, :remove_terms_and_conditions,
:bulk_update, :resend_confirmation, :new_tag_rule_group], Enterprise do |enterprise|
:bulk_update, :resend_confirmation], Enterprise do |enterprise|
OpenFoodNetwork::Permissions.new(user).editable_enterprises.include? enterprise
end
can [:welcome, :register], Enterprise do |enterprise|
@@ -214,7 +213,7 @@ module Spree
managed_product_enterprises.include? variant.supplier
end
if OpenFoodNetwork::FeatureToggle.enabled?(:inventory, *user.enterprises)
if OpenFoodNetwork::FeatureToggle.enabled?(:inventory, user.enterprises)
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
next false unless vo.hub.present? && vo.variant&.supplier.present?

View File

@@ -57,7 +57,7 @@ module Spree
scope :failed, -> { with_state('failed') }
scope :valid, -> { where.not(state: %w(failed invalid)) }
scope :void, -> { with_state('void') }
scope :authorization_action_required, -> { where.not(redirect_auth_url: nil) }
scope :authorization_action_required, -> { where.not(cvv_response_message: nil) }
scope :requires_authorization, -> { with_state("requires_authorization") }
scope :with_payment_intent, ->(code) { where(response_code: code) }
@@ -164,7 +164,7 @@ module Spree
end
def clear_authorization_url
update_attribute(:redirect_auth_url, nil)
update_attribute(:cvv_response_message, nil)
end
private

View File

@@ -241,8 +241,7 @@ module Spree
if response.cvv_result
self.cvv_response_code = response.cvv_result['code']
self.cvv_response_message = response.cvv_result['message']
self.redirect_auth_url = response.cvv_result['redirect_auth_url']
if redirect_auth_url.present?
if cvv_response_message.present?
return require_authorization!
end
end

View File

@@ -19,7 +19,6 @@ require 'open_food_network/property_merge'
module Spree
class Product < ApplicationRecord
include LogDestroyPerformer
include ProductSortByStocks
self.belongs_to_required_by_default = false
# These columns have been moved to variant. Currently this is only for documentation purposes,
@@ -31,7 +30,7 @@ module Spree
acts_as_paranoid
searchable_attributes :meta_keywords, :sku, :on_hand, :backorderable_priority
searchable_attributes :meta_keywords, :sku
searchable_associations :properties, :variants
searchable_scopes :active, :with_properties

View File

@@ -19,9 +19,4 @@ class TagRule < ApplicationRecord
end
end
end
# The following method must be overriden in a concrete tagRule
def tags
raise NotImplementedError, 'please use concrete TagRule'
end
end

View File

@@ -14,10 +14,6 @@ class TagRule::FilterOrderCycles < TagRule
preferred_matched_order_cycles_visibility != "visible"
end
def tags
preferred_exchange_tags
end
private
def exchange_for(order_cycle)

View File

@@ -13,8 +13,4 @@ class TagRule::FilterPaymentMethods < TagRule
def reject_matched?
preferred_matched_payment_methods_visibility != "visible"
end
def tags
preferred_payment_method_tags
end
end

View File

@@ -18,9 +18,5 @@ class TagRule
def reject_matched?
preferred_matched_variants_visibility != "visible"
end
def tags
preferred_variant_tags
end
end
end

View File

@@ -13,8 +13,4 @@ class TagRule::FilterShippingMethods < TagRule
preferred_tags = preferred_shipping_method_tags.split(",")
shipping_method_tags.intersect?(preferred_tags)
end
def tags
preferred_shipping_method_tags
end
end

View File

@@ -10,8 +10,8 @@ module Api
:preferred_shopfront_taxon_order, :preferred_shopfront_producer_order,
:preferred_shopfront_order_cycle_order, :show_customer_names_to_suppliers,
:show_customer_contacts_to_suppliers,
:preferred_shopfront_product_sorting_method, :owner, :contact, :users,
:require_login, :allow_guest_orders, :allow_order_changes,
:preferred_shopfront_product_sorting_method, :owner, :contact, :users, :tag_groups,
:default_tag_group, :require_login, :allow_guest_orders, :allow_order_changes,
:logo, :promo_image, :terms_and_conditions,
:terms_and_conditions_file_name, :terms_and_conditions_updated_at,
:preferred_invoice_order_by_supplier, :preferred_product_low_stock_display,
@@ -50,8 +50,41 @@ module Api
object.terms_and_conditions_blob&.created_at&.to_s
end
def tag_groups
prioritized_tag_rules.each_with_object([]) do |tag_rule, tag_groups|
tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.
split(",").
map{ |t| { text: t } })
if tag_group[:rules].blank?
tag_groups << tag_group
tag_group[:position] = tag_groups.count
end
tag_group[:rules] << Api::Admin::TagRuleSerializer.new(tag_rule).serializable_hash
end
end
def default_tag_group
default_rules = object.tag_rules.select(&:is_default)
serialized_rules =
ActiveModel::ArraySerializer.new(default_rules,
each_serializer: Api::Admin::TagRuleSerializer)
{ tags: [], rules: serialized_rules }
end
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
private
def prioritized_tag_rules
object.tag_rules.prioritised.reject(&:is_default)
end
# Returns a hash of URLs for specified versions of an attachment.
#
# Example result:

View File

@@ -2,7 +2,7 @@
module Api
class PaymentSerializer < ActiveModel::Serializer
attributes :amount, :updated_at, :payment_method, :state, :redirect_auth_url
attributes :amount, :updated_at, :payment_method, :state, :cvv_response_message
def payment_method
object.payment_method.try(:name)

View File

@@ -40,7 +40,7 @@ module Checkout
# Stripe::AuthorizeResponsePatcher patches the Stripe authorization response
# so that this field stores the redirect URL. It also verifies that it is a Stripe URL.
def stripe_payment_url(payment)
payment.redirect_auth_url
payment.cvv_response_message
end
end
end

View File

@@ -5,7 +5,7 @@
# /checkout; for admin payments and subscription payemnts it's the order url.
#
# This class confirms that the payment intent matches what's in our database,
# marks the payment as complete, and removes the redirect_auth_url field,
# marks the payment as complete, and removes the cvv_response_message field,
# which we use to indicate that authorization is required. It also completes the
# Order, if appropriate.

View File

@@ -4,13 +4,6 @@
# Tag rules exists in the context of enterprise, customer, and variant_overrides,
# and are applied to variant_overrides only. Returns a Spree::Variant AR object.
# The filtering is somewhat not intuitive when they are conflicting rules in play:
# * When a variant is hidden by a default rule, the order of customer related rules doesn't matter
# ( despite the use of `TagRule::FilterProducts.prioritised` ). It will apply the "show rule"
# if any
# * When there is no default rule, the order of customer related rules doesn't matter, it will
# apply the "hide rule" if any
#
class ProductTagRulesFilterer
def initialize(distributor, customer, variants_relation)
@distributor = distributor

View File

@@ -1,25 +1,29 @@
.row
= render partial: "admin/json/injection_ams", locals: { ngModule: "admin.tagRules", name: "ruleTypes", json: @tag_rule_types.to_json }
.row{ "ng-app" => "admin.tagRules", "ng-controller": "TagRulesCtrl" }
.eleven.columns.alpha.omega
%div{ "data-turbo": true }
- current_group_index = 0
- # We use a high enough index increment so that the default tag rule should not overlap with the tag rules
- # Rails will deal with non continous numbered tag_rules_attributes just fine, it saves us from having to manage the index state in javascript
- current_rule_index = 1000
- rules = @enterprise.tag_rules.prioritised.reject(&:is_default)
- if rules.empty?
.no_tags
= t('.no_tags_yet')
%ofn-sortable{ axis: "y", handle: ".header", items: '.customer_tag', position: "tagGroup.position", "after-sort": "updateRuleCounts()" }
.no_tags{ "ng-show": "tagGroups.length == 0" }
= t('.no_tags_yet')
= render 'admin/enterprises/form/tag_rules/default_rules'
-# = render 'customer_tags'
.customer_tag{ id: "tg_{{tagGroup.position}}", "ng-repeat": "tagGroup in tagGroups" }
.header
%table
%colgroup
%col{width: '35%'}
%col{width: '65%'}
%tr
%td
%h5
= t('.for_customers_tagged')
%td
%tags-with-translation{ object: "tagGroup", max: 1, "on-tag-added": "updateTagsRulesFor(tagGroup)", "on-tag-removed": "updateTagsRulesFor(tagGroup)" }
= render 'admin/enterprises/form/tag_rules/default_rules', f:, current_rule_index:
#customer-tag-rule
- tag_groups(rules).each_with_index do |group, group_index|
- current_group_index = group_index + 1
= render TagRuleGroupFormComponent.new(group:, index: group_index, customer_rule_index: current_rule_index, tag_rule_types: @tag_rule_types)
- # Same as above, We use a high enough increcment so that the previous tag rule group does not overlap with the next tag rule group
- current_rule_index += 1000
.add_tag{ "data-controller": "tag-rule-group" }
= hidden_field_tag "group_index", current_group_index, { "data-tag-rule-group-target": "index" }
= hidden_field_tag "customer_rule_index", current_rule_index, { "data-tag-rule-group-target": "customerRuleIndex" }
%input.button{ type: 'button', value: t('.add_new_tag'), "data-action": "click->tag-rule-group#add" }
.no_rules{ "ng-show": "tagGroup.rules.length == 0" }
= t('.no_rules_yet')
.tag_rule{ "ng-repeat": "rule in tagGroup.rules" }
.add_rule.text-center
%input.button.icon-plus{ type: 'button', value: t('.add_new_rule'), "add-new-rule-to" => "addNewRuleTo", "tag-group" => "tagGroup", "new-tag-rule-dialog" => true }
.add_tag
%input.button.red.icon-plus{ type: 'button', value: t('.add_new_tag'), "ng-click": 'addNewTag()' }

View File

@@ -8,23 +8,12 @@
%h5
= t('.by_default')
%i.text-big.icon-question-sign{ "data-controller": "help-modal-link", "data-action": "click->help-modal-link#open", "data-help-modal-link-target-value": "tag_rule_help_modal" }
#default-tag-rule
- default_rules = @enterprise.tag_rules.select(&:is_default)
- current_rule_index = 0
- if default_rules.empty?
.no_rules
= t('.no_rules_yet')
- else
- default_rules.each_with_index do |default_rule, index|
- current_rule_index = index + 1
= render TagRuleFormComponent.new(rule: default_rule, index: index)
%hr
.no_rules{ "ng-show": "defaultTagGroup.rules.length == 0" }
= t('.no_rules_yet')
.tag_rule{ "ng-repeat": "rule in defaultTagGroup.rules" }
.add_rule.text-center
%input.button.icon-plus{ type: 'button', value: t('.add_new_button'), "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "tag_rule_add_new_default_rule" }
%input.button.icon-plus{ type: 'button', value: t('.add_new_button'), "add-new-rule-to" => "addNewRuleTo", "tag-group" => "defaultTagGroup", "new-tag-rule-dialog" => true }
= render AddTagRuleModalComponent.new(id: "tag_rule_add_new_default_rule", tag_rule_types: @tag_rule_types, current_index: current_rule_index, div_id: "default-tag-rule", is_default: true)
= render HelpModalComponent.new(id: "tag_rule_help_modal") do
#tag-rule-help
.margin-bottom-30.text-center

View File

@@ -1,4 +0,0 @@
= turbo_stream.append "customer-tag-rule" do
= render TagRuleGroupFormComponent.new(group: @group, index: @index, customer_rule_index: @customer_rule_index, tag_rule_types: @tag_rule_types)
= turbo_stream.remove_all ".no_tags"

View File

@@ -7,7 +7,7 @@
%td.receival-details
= text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => t('.receival_instructions_placeholder'), 'ng-model' => 'exchange.receival_instructions'
- if type == 'distributor'
%td.tags.panel-toggle.text-center{ name: "tags", id: "tags", "ng-if": 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' }
%td.tags.panel-toggle.text-center{ name: "tags", "ng-if": 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' }
{{ exchange.tags.length }}
%td.collection-details
= text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'ng-init' => 'setPickupTimeFieldDirty($index, exchange.pickup_time)', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'required' => 'required', 'placeholder' => t('.pickup_time_placeholder'), 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator', 'maxlength' => 35
@@ -36,5 +36,5 @@
- if type == 'distributor'
%tr.panel-row{ object: "exchange",
panels: "{products: 'exchange_products_distributed', tags: 'exchange_tags'}",
locals: "$index,exchangeTotalVariants,order_cycle,exchange,enterprises,setExchangeVariants,incomingExchangeVariantsFor,variantSuppliedToOrderCycle,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading,order_cycle_form",
locals: "$index,exchangeTotalVariants,order_cycle,exchange,enterprises,setExchangeVariants,incomingExchangeVariantsFor,variantSuppliedToOrderCycle,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading",
colspan: 5 }

View File

@@ -5,7 +5,7 @@
%h6= t('admin.product_import.index.choose_import_type')
%br
- options = { "#{t('admin.product_import.index.product_list')}" => :product_list }
- options = options.merge("#{t('admin.product_import.index.inventories')}" => :inventories) if feature?(:inventory, *spree_current_user.enterprises)
- options = options.merge("#{t('admin.product_import.index.inventories')}" => :inventories) if feature?(:inventory, spree_current_user.enterprises)
= select_tag "settings[import_into]",
options_for_select(options),
{ "data-controller": "tom-select", class: "primary inline no-search", "ng-model": "settings.import_into" }

View File

@@ -5,7 +5,7 @@
%i.icon-external-link
= t('admin.product_import.index.product_list_template')
- if feature?(:inventory, *spree_current_user.enterprises)
- if feature?(:inventory, spree_current_user.enterprises)
%a.download{href: '/inventory_template.csv'}
%i.icon-external-link
= t('admin.product_import.index.inventory_template')

View File

@@ -24,7 +24,7 @@
%td.col-inherits_properties.align-left
.content= product.inherits_properties ? 'YES' : 'NO' #TODO: consider using https://github.com/RST-J/human_attribute_values, else use I18n.t (also below)
%td.align-right
= render(VerticalEllipsisMenuComponent.new) do
= render(VerticalEllipsisMenu::Component.new) do
= link_to t('admin.products_page.actions.edit'), edit_admin_product_path(product), 'data-turbo': false
= link_to t('admin.products_page.actions.clone'), admin_clone_product_path(product), 'data-turbo-method': :post
%a{ "data-controller": "modal-link", "data-action": "click->modal-link#setModalDataSetOnConfirm click->modal-link#open",

View File

@@ -7,7 +7,7 @@
= render partial: 'product_row', locals: { f: product_form, product:, product_index: }
- product.variants.each_with_index do |variant, variant_index|
= form.fields_for("products][#{product_index}][variants_attributes", variant, index: variant_index) do |variant_form|
= form.fields_for("products][#{product_index}][variants_attributes][", variant, index: variant_index) do |variant_form|
%tr.condensed{ id: dom_id(variant), 'data-controller': "variant", 'class': "nested-form-wrapper", 'data-new-record': variant.new_record? ? "true" : false }
= render partial: 'variant_row', locals: { variant:, f: variant_form, category_options:, tax_category_options:, producer_options: }

View File

@@ -59,8 +59,7 @@
%th.align-left.col-unit_scale.with-input= t('admin.products_page.columns.unit_scale')
%th.align-left.col-unit.with-input= t('admin.products_page.columns.unit')
%th.align-left.col-price.with-input= t('admin.products_page.columns.price')
= render partial: 'spree/admin/shared/stimulus_sortable_header',
locals: { column: :on_hand, sorted: params.dig(:q, :s), default: 'name asc' }
%th.align-left.col-on_hand.with-input= t('admin.products_page.columns.on_hand')
%th.align-left.col-producer= t('admin.products_page.columns.producer')
%th.align-left.col-category= t('admin.products_page.columns.category')
%th.align-left.col-tax_category= t('admin.products_page.columns.tax_category')

View File

@@ -80,11 +80,11 @@
= error_message_on variant, :tax_category
- if feature?(:variant_tag, spree_current_user)
%td.col-tags.field.naked_inputs
= render TagListInputComponent.new(name: f.field_name(:tag_list), tags: variant.tag_list, placeholder: t('.add_a_tag'), aria_label: t('admin.products_page.columns.tags'))
= render TagListInputComponent.new(form: f, method: "tag_list", tags: variant.tag_list, placeholder: t('.add_a_tag'), aria_label: t('admin.products_page.columns.tags'))
%td.col-inherits_properties.align-left
-# empty
%td.align-right
= render(VerticalEllipsisMenuComponent.new) do
= render(VerticalEllipsisMenu::Component.new) do
- if variant.persisted?
= link_to t('admin.products_page.actions.edit'), edit_admin_product_variant_path(variant.product, variant)
- if variant.product.variants.size > 1

View File

@@ -1,23 +1,19 @@
- if @report_subtypes.present? && @report_subtypes.count > 1
%input{type: 'hidden', name: 'report_subtype', value: @report_subtype}
.row.rendering-options{ "data-controller": "csv-select metadata-toggle" }
.row.rendering-options{ "data-controller": "csv-select" }
.alpha.two.columns
= label_tag :report_format, t(".generate_report")
.omega.fourteen.columns{ style: "margin-bottom: 1.5em;" }
= select_tag :report_format, grouped_options_for_select({ |
t('.formatted_data') => { t('.on_screen') => '', "PDF" => 'pdf', t('.spreadsheet') => 'xlsx' }, |
t('.raw_data') => { "CSV" => 'csv' }, |
}), { "data-csv-select-target": "reportType", "data-metadata-toggle-target": "reportType", "data-action": "csv-select#handleSelectChange metadata-toggle#handleSelectChange" }
}), { "data-csv-select-target": "reportType", "data-action": "csv-select#handleSelectChange" }
- if @report.header_option? || @report.summary_row_option? || @report.metadata_option?
- if @report.header_option? || @report.summary_row_option?
.row
.alpha.two.columns= label_tag nil, t(".display")
.omega.fourteen.columns
- if @report.metadata_option?
%span.inline-checkbox{ style: "margin-right: 1rem;" }
= check_box_tag :display_metadata_rows, true, @rendering_options.options[:display_metadata_rows], { "disabled": "true", "data-metadata-toggle-target": "checkbox" }
= label_tag :display_metadata_rows, t(".metadata_rows"), {"class": "disabled", "data-metadata-toggle-target": "label" }
- if @report.header_option?
%span.inline-checkbox{ style: "margin-right: 1rem;" }
= check_box_tag :display_header_row, true, @rendering_options.options[:display_header_row]

View File

@@ -1,4 +0,0 @@
- unless flash[:error]
= turbo_stream.remove "tr_#{@index}"
= turbo_stream.append "flashes" do
= render(partial: 'admin/shared/flashes', locals: { flashes: flash })

View File

@@ -1,10 +0,0 @@
- if flash[:error]
= turbo_stream.append "flashes" do
= render(partial: 'admin/shared/flashes', locals: { flashes: flash })
- else
= turbo_stream.append @div_id do
= render(TagRuleFormComponent.new(rule: @default_rule,
index: @index,
customer_tags: @customer_tags,
hidden_field_customer_tag_options: { "data-tag-rule-group-form-target": "ruleCustomerTag" }))
= turbo_stream.remove_all ".no_rules"

View File

@@ -11,7 +11,7 @@
= @order.shipping_method.name
%em.fees= payment_or_shipping_price(@order.shipping_method, @order)
.two-columns
= render "delivery_details" if @order.shipping_method.delivery?
= render "delivery_details" if @order.shipping_method.delivery? || feature?(:hub_address)
- if @order.shipping_method.description.present?
%div
.summary-subtitle

View File

@@ -13,10 +13,10 @@
%form{ name: 'about', novalidate: true, "ng-controller": "RegistrationFormCtrl", "ng-submit": "selectIfValid('images', about)" }
.row
.small-12.columns
.alert-box.info{ "data-controller": "toggle-control", "data-toggle-control-target": "content", style: "display: block;" }
.alert-box.info{ "ofn-inline-alert": true, "ng-show": "visible" }
%h6{ "ng-bind" => "'registration.steps.about.success' | t:{enterprise: enterprise.name}" }
%span= t(".registration_exit_message")
%a.close{ "data-action": "toggle-control#toggleDisplay" } &times;
%a.close{ "ng-click": "close()" } &times;
.small-12.large-8.columns
.row

View File

@@ -1,4 +1,4 @@
.text-center.page-alert.fixed{ "data-controller" => "page-alert" }
.text-center.page-alert.fixed{ "ofn-page-alert" => true }
.alert-box
= render 'shared/page_alert'
%a.close{ "data-action" => "page-alert#close" } &times;
%a.close{ "ng-click": "close()" } &times;

View File

@@ -1,7 +1,7 @@
.closed-shop-header
.row
.small-12.columns
.content
.content{ "darker-background" => true }
%h4
.warning-sign
.rectangle

View File

@@ -1,4 +1,4 @@
.content
.content{ "darker-background" => true }
.row.footer-pad
.small-12.columns{ "data-controller": "login-modal" }
%strong

View File

@@ -1,4 +1,4 @@
.content.footer-pad{ "ng-show" => "order_cycle.order_cycle_id == null" }
.content.footer-pad{ "darker-background" => true, "ng-show" => "order_cycle.order_cycle_id == null" }
.row
.small-12.columns
.select-oc-message

View File

@@ -10,8 +10,8 @@
.columns.large-4.show-for-large-up
= render partial: "shopping_shared/order_cycles"
- shop_tabs.each do |tab|
%div{id: "#{tab[:name]}_panel", "data-tabs-and-panels-target": "panel #{'default' if tab[:default]} #{'shop' if tab[:shop]}" }
.page-view{ class: shop_tab_class(tab[:name]) }
%div{id: "#{tab[:name]}_panel", "data-tabs-and-panels-target": "panel #{'default' if tab[:default]} #{'shop' if tab[:shop]}" }
.page-view
- if tab[:custom]
= render "shopping_shared/tabs/custom"
- else

View File

@@ -2,5 +2,5 @@
%ul#sub_nav.inline-menu
= tab :products, :products_v3, url: admin_products_path
= tab :properties
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory' if feature?(:inventory, *spree_current_user.enterprises)
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory' if feature?(:inventory, spree_current_user.enterprises)
= tab :import, url: main_app.admin_product_import_path, match_path: '/product_import'

View File

@@ -1,3 +1,5 @@
.row.links
= link_to_or_disabled t(:orders_edit_continue), current_shop_products_path, class: "continue-shopping button secondary", disabled: @insufficient_stock_lines.any?
= link_to_or_disabled t(:orders_edit_checkout), main_app.checkout_path, class: "button primary right", disabled: @insufficient_stock_lines.any?, id: "checkout-link"
%a.continue-shopping.button.secondary{href: current_shop_products_path, "ng-disabled" => "#{@insufficient_stock_lines.any?}", "disable-dynamically" => true}
= t :orders_edit_continue
%a#checkout-link.button.primary.right{href: main_app.checkout_path, "ng-disabled" => "#{@insufficient_stock_lines.any?}", "disable-dynamically" => true}
= t :orders_edit_checkout

View File

@@ -42,9 +42,9 @@
.row
.columns.large-12
- if order.changes_allowed?
.alert-box.order-summary{ "data-controller": "toggle-control", "data-toggle-control-target": "content", style: "display: block;" }
.alert-box.order-summary{ "ofn-inline-alert" => true, "ng-show" => "visible" }
= t(:orders_changeable_orders_alert_html, oc_close: l(order.order_cycle.orders_close_at, format: "%b %d, %Y %H:%M"))
%a.close{ "data-action": "toggle-control#toggleDisplay" } &times;
%a.close{ "ng-click" => "close()" } &times;
= form_for order, url: main_app.order_path(order), html: {id: 'update-order', name: 'update_order_form' } do |order_form|
- if order.changes_allowed?

View File

@@ -16,7 +16,7 @@
%td
= payment.updated_at.strftime("%Y-%m-%d")
%td
%a{ href: "#{payment.redirect_auth_url}" }
%a{ href: "#{payment.cvv_response_message}" }
%button.x-small
= t(".authorise")
%td.text-right

View File

@@ -4,9 +4,9 @@ export default class extends Controller {
static targets = ["reportType", "checkbox", "label"];
handleSelectChange() {
this.reportTypeTarget.value == "csv" ?
this.disableField():
this.enableField();
this.reportTypeTarget.value == "csv"
? this.disableField()
: this.enableField();
}
disableField() {

View File

@@ -1,13 +0,0 @@
import { Controller } from "stimulus";
export default class extends Controller {
static values = { index: Number };
delete(e) {
// prevent default link action
e.preventDefault();
if (confirm(I18n.t("admin.tag_rules.confirm_delete")) == true) {
document.getElementById(`tr_${this.indexValue}`).remove();
}
}
}

View File

@@ -6,37 +6,14 @@ import StimulusReflex from "stimulus_reflex";
import consumer from "../channels/consumer";
import controller from "../controllers/application_controller";
import CableReady from "cable_ready";
import RailsNestedForm from "@stimulus-components/rails-nested-form/dist/stimulus-rails-nested-form.umd.js"; // the default module entry point is broken
import RailsNestedForm from '@stimulus-components/rails-nested-form/dist/stimulus-rails-nested-form.umd.js' // the default module entry point is broken
const application = Application.start();
const context = require.context("controllers", true, /_controller\.js$/);
application.load(definitionsFromContext(context));
// Load component controller, but generate a shorter controller name than "definitionsFromContext" would
// - for controller in a component subdirectory, get rid of the component folder and use
// the controller name, ie:
// ./tag_rule_group_form_component/tag_rule_group_form_controller.js -> tag-rule-group-form
// - for controller that don't match the pattern above, replace "_" by "-" and "/" by "--", ie:
// ./vertical_ellipsis_menu/component_controller.js -> vertical-ellipsis-menu--component
//
const contextComponents = require.context("../../components", true, /_controller\.js$/);
contextComponents.keys().forEach((path) => {
const module = contextComponents(path);
// Check whether a module has the default export defined
if (!module.default) return;
const identifier = path
.replace(/^\.\//, "")
.replace(/^\w+_component\//, "")
.replace(/_controller\.js$/, "")
.replace(/\//g, "--")
.replace(/_/g, "-");
application.register(identifier, module.default);
});
application.register("nested-form", RailsNestedForm);
application.load(definitionsFromContext(context).concat(definitionsFromContext(contextComponents)));
application.register('nested-form', RailsNestedForm);
application.consumer = consumer;
StimulusReflex.initialize(application, { controller, isolate: true });

View File

@@ -1,30 +0,0 @@
import { Controller } from "stimulus";
export default class extends Controller {
static targets = ["reportType", "checkbox", "label"];
handleSelectChange() {
this.reportTypeTarget.value == "csv" ?
this.enableField():
this.disableField();
}
disableField() {
if (this.hasCheckboxTarget) {
this.checkboxTarget.checked = false;
this.checkboxTarget.disabled = true;
}
if (this.hasLabelTarget) {
this.labelTarget.classList.add("disabled");
}
}
enableField() {
if (this.hasCheckboxTarget) {
this.checkboxTarget.disabled = false;
}
if (this.hasLabelTarget) {
this.labelTarget.classList.remove("disabled");
}
}
}

View File

@@ -1,34 +0,0 @@
import { Controller } from "stimulus";
export default class extends Controller {
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"];
connect() {
// Wait a moment after page load before showing the alert. Otherwise we often miss the
// start of the animation.
setTimeout(this.#show, 1000);
}
close() {
this.#moveElements().forEach((element) => {
element.classList.remove("move-up");
});
}
// private
#moveElements() {
return document.querySelectorAll(this.moveSelectors.join(","));
}
#show = () => {
this.#moveElements().forEach((element) => {
element.classList.add("move-up");
});
};
}

View File

@@ -1,36 +0,0 @@
import { Controller } from "stimulus";
import showHttpError from "../../webpacker/js/services/show_http_error";
export default class extends Controller {
static targets = ["index", "customerRuleIndex"];
add(e) {
e.preventDefault();
const index = this.indexTarget.value;
const customerRuleIndex = this.customerRuleIndexTarget.value;
// fetch from backend
const params = new URLSearchParams();
params.append("index", index);
params.append("customer_rule_index", customerRuleIndex);
fetch(`new_tag_rule_group?${params}`, {
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.indexTarget.value = parseInt(index) + 1;
})
.catch((error) => console.error(error));
}
}

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