mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-17 19:26:48 +00:00
Compare commits
248 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
917079931e | ||
|
|
46e54f48c9 | ||
|
|
059dceb304 | ||
|
|
f0abe650f6 | ||
|
|
282df9859e | ||
|
|
3474c60f4c | ||
|
|
503148b13b | ||
|
|
8442c7d334 | ||
|
|
b14cd08990 | ||
|
|
50ebfe412c | ||
|
|
e59ab6b2d9 | ||
|
|
417d39f684 | ||
|
|
35169f66dc | ||
|
|
64568f4aa4 | ||
|
|
734aebbaaa | ||
|
|
8a2be468fc | ||
|
|
feb429fee7 | ||
|
|
b75101f24f | ||
|
|
1e71db9315 | ||
|
|
82b742608d | ||
|
|
49aa9e0768 | ||
|
|
a85cfab506 | ||
|
|
e2e3aa9281 | ||
|
|
6bd0f2c088 | ||
|
|
ab2968ffd2 | ||
|
|
83bf19084b | ||
|
|
40a59c988b | ||
|
|
43d983cac2 | ||
|
|
ad3e772944 | ||
|
|
6a438a07fe | ||
|
|
ea238829a8 | ||
|
|
91fddeaa8b | ||
|
|
0de8a90b14 | ||
|
|
9fe128d494 | ||
|
|
193e17b625 | ||
|
|
6ad03e6d5c | ||
|
|
1f55ff4b72 | ||
|
|
be13d43e0c | ||
|
|
af7b663334 | ||
|
|
da24638079 | ||
|
|
8ab1cbe600 | ||
|
|
cad0245510 | ||
|
|
93edf4e3ad | ||
|
|
caa2764317 | ||
|
|
4f1e6382c9 | ||
|
|
54d33ca103 | ||
|
|
787205dcca | ||
|
|
fcb0996a76 | ||
|
|
76d874d5f9 | ||
|
|
81711e4c43 | ||
|
|
e64f60a166 | ||
|
|
24bc56781b | ||
|
|
6757c8df74 | ||
|
|
647a384561 | ||
|
|
ec828c335d | ||
|
|
6d03a8ddf3 | ||
|
|
05878fcbb8 | ||
|
|
fd8973862e | ||
|
|
40c77948b9 | ||
|
|
a95aa1b3e9 | ||
|
|
706eb737b1 | ||
|
|
c31758d347 | ||
|
|
6139ba3015 | ||
|
|
256d5ba61c | ||
|
|
cb42e7e119 | ||
|
|
566d310880 | ||
|
|
5cdce35ee8 | ||
|
|
ffe3f12a23 | ||
|
|
bd48a982fb | ||
|
|
5d732d80a6 | ||
|
|
254e11aa36 | ||
|
|
4223b36bc3 | ||
|
|
fcea437d7e | ||
|
|
8716b75d3d | ||
|
|
e055b8b16c | ||
|
|
f5875e4c0b | ||
|
|
56d23c172c | ||
|
|
19bb40d1d3 | ||
|
|
4169a956c9 | ||
|
|
d54dbdfe2d | ||
|
|
fed2ae9a93 | ||
|
|
f00b2f0397 | ||
|
|
c101c4e42f | ||
|
|
11ba33d7f4 | ||
|
|
8e663dac3f | ||
|
|
df0795acf1 | ||
|
|
7e1af9e04b | ||
|
|
4805adec42 | ||
|
|
7939bf8038 | ||
|
|
d4e0b2ab51 | ||
|
|
1014a50aff | ||
|
|
0afbdf157e | ||
|
|
5012c52438 | ||
|
|
615a81c55d | ||
|
|
99b31d05cb | ||
|
|
1a72b5b227 | ||
|
|
a1aea54405 | ||
|
|
e01354e863 | ||
|
|
ffe4603f2f | ||
|
|
ebc794194f | ||
|
|
287f65ec8e | ||
|
|
1288592d58 | ||
|
|
0836d844a6 | ||
|
|
96355a1ed4 | ||
|
|
ce44f19b4a | ||
|
|
6c214543ad | ||
|
|
8b1713d169 | ||
|
|
587ce5ad9d | ||
|
|
f51705cb57 | ||
|
|
55df9416cc | ||
|
|
0376c04ad5 | ||
|
|
2709479bf2 | ||
|
|
c5fc621aa4 | ||
|
|
bfd0e7f784 | ||
|
|
ec3c157f1e | ||
|
|
32aacbd2b0 | ||
|
|
655dc92246 | ||
|
|
fece8beef5 | ||
|
|
53e3621e04 | ||
|
|
1949839056 | ||
|
|
00a0006ff2 | ||
|
|
325f9aa6f3 | ||
|
|
95ec5c3c58 | ||
|
|
ef87cdb167 | ||
|
|
65d4596f3b | ||
|
|
a9e295bc11 | ||
|
|
ac8caf7710 | ||
|
|
8a1e61fd60 | ||
|
|
baf38b6b30 | ||
|
|
3507405dae | ||
|
|
c25fe6ae57 | ||
|
|
7bcf3206d8 | ||
|
|
e808c7fb2b | ||
|
|
df81e8ed35 | ||
|
|
e9d7a0b099 | ||
|
|
da7bbcf82f | ||
|
|
1742d2807f | ||
|
|
d3c5e2365a | ||
|
|
27e53f9dcc | ||
|
|
5d0f55b8c3 | ||
|
|
9d89b4726b | ||
|
|
9b37eacb8d | ||
|
|
bbe22bbfeb | ||
|
|
85d5e2ee70 | ||
|
|
9a4051f37b | ||
|
|
05ed4639b2 | ||
|
|
2bcf84d9a9 | ||
|
|
99acf752f4 | ||
|
|
5086f2d8b5 | ||
|
|
0b46c41ffd | ||
|
|
4fe3f60009 | ||
|
|
dfea0cd805 | ||
|
|
0f04b2fb10 | ||
|
|
146296d0b2 | ||
|
|
a6efad73a8 | ||
|
|
7f9bbd23e5 | ||
|
|
29d63b0f0f | ||
|
|
25e9fd22d8 | ||
|
|
5caeb160ef | ||
|
|
1310965975 | ||
|
|
0c43dd4222 | ||
|
|
bb427db66a | ||
|
|
1165b00600 | ||
|
|
2d45952611 | ||
|
|
17eb8d5cd8 | ||
|
|
c8ca993fa9 | ||
|
|
de20dd949b | ||
|
|
4c9507caa3 | ||
|
|
f1e6f8bb66 | ||
|
|
918d4401ff | ||
|
|
4925e2088d | ||
|
|
bba59c1ffd | ||
|
|
f3e7ba0462 | ||
|
|
23fc77351e | ||
|
|
3bd5ae2eec | ||
|
|
0123d6fb2e | ||
|
|
e0d7252fe3 | ||
|
|
24defac470 | ||
|
|
46696dfa17 | ||
|
|
1af811cf51 | ||
|
|
1850f298a6 | ||
|
|
ae3fa00429 | ||
|
|
80ade22bd6 | ||
|
|
4fb458afe0 | ||
|
|
c25750387f | ||
|
|
5b7fbc875a | ||
|
|
e1976c6cc2 | ||
|
|
91daec4806 | ||
|
|
bdc42deeb6 | ||
|
|
a66fec0b26 | ||
|
|
c52c2ebfe1 | ||
|
|
d8354298f5 | ||
|
|
2baf7c0250 | ||
|
|
0986971473 | ||
|
|
29aa3a8059 | ||
|
|
e09745179f | ||
|
|
49c6a22fde | ||
|
|
a37b0eb698 | ||
|
|
2e36c699f6 | ||
|
|
cb4e7d6fe3 | ||
|
|
94d560d341 | ||
|
|
eea227bc22 | ||
|
|
6d6f8735e3 | ||
|
|
aec9a960e2 | ||
|
|
f1713b11a6 | ||
|
|
c86f7f9d50 | ||
|
|
5e933af079 | ||
|
|
f7c47fecc4 | ||
|
|
7d1cb0c957 | ||
|
|
7f6780b5e9 | ||
|
|
dfcd9391d9 | ||
|
|
6dd4bb3e3b | ||
|
|
cda2408c69 | ||
|
|
eb22bff908 | ||
|
|
9281cd1a62 | ||
|
|
b99d985b75 | ||
|
|
ecfa47cd78 | ||
|
|
10898fdcfc | ||
|
|
718e6765e1 | ||
|
|
686fe8c028 | ||
|
|
cb0a30ef52 | ||
|
|
5c3acbbcaf | ||
|
|
31d49ee99e | ||
|
|
1e08f2713e | ||
|
|
f0fd3bb73c | ||
|
|
6d22652dfa | ||
|
|
2ab6bcb2e4 | ||
|
|
ca612282f9 | ||
|
|
823614c214 | ||
|
|
cc1fa7f563 | ||
|
|
8955972b05 | ||
|
|
5fe5804b56 | ||
|
|
d60d29b685 | ||
|
|
ac347b9c8e | ||
|
|
8a8a178683 | ||
|
|
5c136f8baa | ||
|
|
c372bf746a | ||
|
|
79a22aefc3 | ||
|
|
17fe492bf4 | ||
|
|
47faedc295 | ||
|
|
ce8a2b3251 | ||
|
|
ac2f59bdb2 | ||
|
|
ced3408aaa | ||
|
|
02c0c6aa5e | ||
|
|
9d12e55bd7 | ||
|
|
b3570991f4 | ||
|
|
e2aca63fff | ||
|
|
e537bda9b7 |
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -216,9 +216,9 @@ jobs:
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
name: failed-admin-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
@@ -294,9 +294,9 @@ jobs:
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
name: failed-consumer-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
@@ -371,15 +371,6 @@ jobs:
|
||||
run: |
|
||||
bin/rake knapsack_pro:rspec
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
test_the_rest:
|
||||
runs-on: ubuntu-22.04
|
||||
services:
|
||||
|
||||
@@ -12,22 +12,6 @@ Layout/EmptyLines:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented, indented_relative_to_receiver
|
||||
Layout/MultilineMethodCallIndentation:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: aligned, indented
|
||||
Layout/MultilineOperationIndentation:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 16
|
||||
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
||||
Lint/EmptyBlock:
|
||||
@@ -101,7 +85,7 @@ Lint/UselessMethodDefinition:
|
||||
Exclude:
|
||||
- 'app/models/spree/gateway.rb'
|
||||
|
||||
# Offense count: 23
|
||||
# Offense count: 24
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
||||
Metrics/AbcSize:
|
||||
Exclude:
|
||||
@@ -114,6 +98,7 @@ Metrics/AbcSize:
|
||||
- 'app/helpers/spree/admin/navigation_helper.rb'
|
||||
- 'app/models/enterprise_group.rb'
|
||||
- 'app/models/enterprise_relationship.rb'
|
||||
- 'app/models/product_import/entry_processor.rb'
|
||||
- 'app/models/spree/ability.rb'
|
||||
- 'app/models/spree/address.rb'
|
||||
- 'app/models/spree/order/checkout.rb'
|
||||
@@ -142,7 +127,7 @@ Metrics/BlockNesting:
|
||||
Exclude:
|
||||
- 'app/models/spree/payment/processing.rb'
|
||||
|
||||
# Offense count: 47
|
||||
# Offense count: 46
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ClassLength:
|
||||
Exclude:
|
||||
@@ -178,7 +163,6 @@ Metrics/ClassLength:
|
||||
- 'app/models/spree/variant.rb'
|
||||
- 'app/models/spree/zone.rb'
|
||||
- 'app/reflexes/admin/orders_reflex.rb'
|
||||
- 'app/reflexes/products_reflex.rb'
|
||||
- 'app/serializers/api/cached_enterprise_serializer.rb'
|
||||
- 'app/serializers/api/enterprise_shopfront_serializer.rb'
|
||||
- 'app/services/cart_service.rb'
|
||||
@@ -408,7 +392,6 @@ RSpecRails/HaveHttpStatus:
|
||||
- 'spec/controllers/stripe/webhooks_controller_spec.rb'
|
||||
- 'spec/controllers/user_passwords_controller_spec.rb'
|
||||
- 'spec/controllers/user_registrations_controller_spec.rb'
|
||||
- 'spec/requests/admin/images_spec.rb'
|
||||
- 'spec/requests/api/routes_spec.rb'
|
||||
- 'spec/requests/checkout/stripe_sca_spec.rb'
|
||||
- 'spec/requests/home_controller_spec.rb'
|
||||
@@ -638,20 +621,6 @@ Rails/ResponseParsedBody:
|
||||
- 'spec/controllers/spree/credit_cards_controller_spec.rb'
|
||||
- 'spec/controllers/user_registrations_controller_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/RootPathnameMethods:
|
||||
Exclude:
|
||||
- 'spec/lib/reports/orders_and_fulfillment/order_cycle_customer_totals_report_spec.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Configuration parameters: ForbiddenMethods, AllowedMethods.
|
||||
# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all
|
||||
Rails/SkipsModelValidations:
|
||||
Exclude:
|
||||
- 'app/models/variant_override.rb'
|
||||
- 'spec/models/spree/line_item_spec.rb'
|
||||
|
||||
# Offense count: 7
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
@@ -733,7 +702,7 @@ Style/ClassAndModuleChildren:
|
||||
- 'lib/open_food_network/locking.rb'
|
||||
- 'spec/models/spree/payment_method_spec.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: always, always_true, never
|
||||
@@ -880,39 +849,6 @@ Style/ReturnNilInPredicateMethodDefinition:
|
||||
- 'app/serializers/api/admin/customer_serializer.rb'
|
||||
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
|
||||
|
||||
# Offense count: 207
|
||||
Style/Send:
|
||||
Exclude:
|
||||
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
|
||||
- 'spec/controllers/payment_gateways/paypal_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/base_controller_spec.rb'
|
||||
- 'spec/controllers/spree/orders_controller_spec.rb'
|
||||
- 'spec/helpers/order_cycles_helper_spec.rb'
|
||||
- 'spec/jobs/subscription_confirm_job_spec.rb'
|
||||
- 'spec/jobs/subscription_placement_job_spec.rb'
|
||||
- 'spec/lib/open_food_network/address_finder_spec.rb'
|
||||
- 'spec/lib/open_food_network/enterprise_fee_applicator_spec.rb'
|
||||
- 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb'
|
||||
- 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb'
|
||||
- 'spec/lib/open_food_network/permissions_spec.rb'
|
||||
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
|
||||
- 'spec/lib/reports/xero_invoices_report_spec.rb'
|
||||
- 'spec/lib/stripe/webhook_handler_spec.rb'
|
||||
- 'spec/models/calculator/weight_spec.rb'
|
||||
- 'spec/models/enterprise_spec.rb'
|
||||
- 'spec/models/exchange_spec.rb'
|
||||
- 'spec/models/spree/order_inventory_spec.rb'
|
||||
- 'spec/models/spree/payment_spec.rb'
|
||||
- 'spec/models/spree/return_authorization_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_order_cycles_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_payment_methods_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_products_spec.rb'
|
||||
- 'spec/models/tag_rule/filter_shipping_methods_spec.rb'
|
||||
- 'spec/services/cart_service_spec.rb'
|
||||
- 'spec/services/products_renderer_spec.rb'
|
||||
- 'spec/services/variant_units/option_value_namer_spec.rb'
|
||||
- 'spec/support/localized_number_helper.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Style/SlicingWithRange:
|
||||
|
||||
1
Gemfile
1
Gemfile
@@ -16,7 +16,6 @@ gem "image_processing"
|
||||
|
||||
gem 'activemerchant', '>= 1.78.0'
|
||||
gem 'angular-rails-templates', '>= 0.3.0'
|
||||
gem 'awesome_nested_set'
|
||||
gem 'ransack', '~> 4.1.0'
|
||||
gem 'responders'
|
||||
gem 'webpacker', '~> 5'
|
||||
|
||||
@@ -161,8 +161,6 @@ GEM
|
||||
activerecord (>= 3.1.0, < 8)
|
||||
ast (2.4.2)
|
||||
attr_required (1.0.2)
|
||||
awesome_nested_set (3.6.0)
|
||||
activerecord (>= 4.0.0, < 7.2)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.929.0)
|
||||
aws-sdk-core (3.196.1)
|
||||
@@ -358,7 +356,7 @@ GEM
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
immigrant (0.3.6)
|
||||
activerecord (>= 3.0)
|
||||
invisible_captcha (2.2.0)
|
||||
invisible_captcha (2.3.0)
|
||||
rails (>= 5.2)
|
||||
io-console (0.7.2)
|
||||
ipaddress (0.8.3)
|
||||
@@ -417,7 +415,7 @@ GEM
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.2)
|
||||
marcel (1.0.4)
|
||||
matrix (0.4.2)
|
||||
method_source (1.1.0)
|
||||
mime-types (3.5.2)
|
||||
@@ -449,7 +447,7 @@ GEM
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
newrelic_rpm (9.9.0)
|
||||
nio4r (2.7.0)
|
||||
nio4r (2.7.1)
|
||||
nokogiri (1.16.5)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
@@ -863,7 +861,6 @@ DEPENDENCIES
|
||||
angularjs-file-upload-rails (~> 2.4.1)
|
||||
angularjs-rails (= 1.8.0)
|
||||
arel-helpers (~> 2.12)
|
||||
awesome_nested_set
|
||||
aws-sdk-s3
|
||||
bigdecimal (= 3.0.2)
|
||||
bootsnap
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
//= require jquery.ui.all
|
||||
//= require jquery.powertip
|
||||
//= require jquery.cookie
|
||||
//= require jquery.jstree/jquery.jstree
|
||||
//= require jquery.vAlign
|
||||
//= require angular
|
||||
//= require angular-resource
|
||||
|
||||
@@ -126,8 +126,11 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
DisplayProperties.setShowVariants 0, showVariants
|
||||
|
||||
$scope.addVariant = (product) ->
|
||||
# Set new variant category to same as last product variant category to keep compactibility with deleted variant callback to set new variant category
|
||||
newVariantId = $scope.nextVariantId();
|
||||
newVariantCategoryId = product.variants[product.variants.length - 1]?.category_id
|
||||
product.variants.push
|
||||
id: $scope.nextVariantId()
|
||||
id: newVariantId
|
||||
unit_value: null
|
||||
unit_description: null
|
||||
on_demand: false
|
||||
@@ -136,8 +139,9 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
on_hand: null
|
||||
price: null
|
||||
tax_category_id: null
|
||||
category_id: null
|
||||
category_id: newVariantCategoryId
|
||||
DisplayProperties.setShowVariants product.id, true
|
||||
DirtyProducts.addVariantProperty(product.id, newVariantId, 'category_id', newVariantCategoryId)
|
||||
|
||||
|
||||
$scope.nextVariantId = ->
|
||||
|
||||
@@ -3,6 +3,7 @@ angular.module('admin.orderCycles')
|
||||
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
|
||||
|
||||
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
$scope.order_cycle_id = order_cycle_id
|
||||
$scope.order_cycle = OrderCycle.load(order_cycle_id)
|
||||
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
|
||||
@@ -18,6 +19,8 @@ angular.module('admin.orderCycles')
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
|
||||
$scope.order_cycle?.confirm = $($event.target).data('confirm');
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
OrderCycle.update(destination, $scope.order_cycle_form)
|
||||
|
||||
@@ -25,4 +28,4 @@ angular.module('admin.orderCycles')
|
||||
if $scope.order_cycle_form?.$dirty
|
||||
t('admin.unsaved_confirm_leave')
|
||||
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, OrderCycle, ExchangeProduct, ocInstance) ->
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, EnterpriseFee, OrderCycle, ExchangeProduct, ocInstance) ->
|
||||
$controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})
|
||||
|
||||
$scope.view = 'incoming'
|
||||
|
||||
# NB: weirdly at this next line $scope.order_cycle.id comes out undefined so we use $scope.order_cycle_id instead
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: $scope.order_cycle_id, per_item: true)
|
||||
$scope.exchangeTotalVariants = (exchange) ->
|
||||
return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]?
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
|
||||
$scope.order_cycle?.confirm = $($event.target).data('confirm');
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
OrderCycle.mirrorIncomingToOutgoingProducts()
|
||||
OrderCycle.update(destination, $scope.order_cycle_form) if OrderCycle.confirmNoDistributors()
|
||||
|
||||
@@ -6,6 +6,8 @@ angular.module('admin.orderCycles').factory('EnterpriseFee', ($resource) ->
|
||||
params:
|
||||
order_cycle_id: '@order_cycle_id'
|
||||
coordinator_id: '@coordinator_id'
|
||||
per_item: '@per_item'
|
||||
per_order: '@per_order'
|
||||
})
|
||||
|
||||
{
|
||||
|
||||
@@ -161,7 +161,11 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
|
||||
StatusMessage.display('failure', t('js.order_cycles.create_failure'))
|
||||
|
||||
update: (destination, form) ->
|
||||
oc = new OrderCycleResource({order_cycle: this.dataForSubmit()})
|
||||
oc = new OrderCycleResource({
|
||||
order_cycle: this.dataForSubmit(),
|
||||
confirm: this.order_cycle.confirm,
|
||||
trigger_action: this.order_cycle.trigger_action
|
||||
})
|
||||
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
|
||||
form.$setPristine() if form
|
||||
if destination?
|
||||
@@ -171,6 +175,8 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
|
||||
, (response) ->
|
||||
if response.data.errors?
|
||||
StatusMessage.display('failure', response.data.errors[0])
|
||||
else if (response.data.trigger_action)
|
||||
StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action)
|
||||
else
|
||||
StatusMessage.display('failure', t('js.order_cycles.update_failure'))
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
root = exports ? this
|
||||
|
||||
root.taxon_tree_menu = (obj, context) ->
|
||||
|
||||
base_url = Spree.url(Spree.routes.taxonomy_taxons)
|
||||
admin_base_url = Spree.url(Spree.routes.admin_taxonomy_taxons)
|
||||
edit_url = Spree.url(Spree.routes.admin_taxonomy_taxons + '/' + obj.attr("id") + "/edit");
|
||||
|
||||
create:
|
||||
label: "<i class='icon-plus'></i> " + Spree.translations.add,
|
||||
action: (obj) -> context.create(obj)
|
||||
rename:
|
||||
label: "<i class='icon-pencil'></i> " + Spree.translations.rename,
|
||||
action: (obj) -> context.rename(obj)
|
||||
remove:
|
||||
label: "<i class='icon-trash'></i> " + Spree.translations.remove,
|
||||
action: (obj) -> context.remove(obj)
|
||||
edit:
|
||||
separator_before: true,
|
||||
label: "<i class='icon-edit'></i> " + Spree.translations.edit,
|
||||
action: (obj) -> window.location = edit_url.toString()
|
||||
@@ -1,139 +0,0 @@
|
||||
handle_ajax_error = (XMLHttpRequest, textStatus, errorThrown) ->
|
||||
$.jstree.rollback(last_rollback)
|
||||
$("#ajax_error").show().html("<strong>" + server_error + "</strong><br />" + taxonomy_tree_error)
|
||||
|
||||
handle_move = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
position = data.rslt.cp
|
||||
node = data.rslt.o
|
||||
new_parent = data.rslt.np
|
||||
|
||||
url = new URL(Spree.routes.admin_taxonomy_taxons)
|
||||
url.pathname = url.pathname + '/' + node.attr("id")
|
||||
data = {
|
||||
_method: "put",
|
||||
"taxon[position]": position,
|
||||
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
|
||||
}
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url.toString(),
|
||||
data: data,
|
||||
error: handle_ajax_error
|
||||
|
||||
true
|
||||
|
||||
handle_create = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
name = data.rslt.name
|
||||
position = data.rslt.position
|
||||
new_parent = data.rslt.parent
|
||||
|
||||
data = {
|
||||
"taxon[name]": name,
|
||||
"taxon[position]": position
|
||||
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
|
||||
}
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: base_url.toString(),
|
||||
data: data,
|
||||
error: handle_ajax_error,
|
||||
success: (data,result) ->
|
||||
node.attr('id', data.id)
|
||||
|
||||
handle_rename = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
name = data.rslt.new_name
|
||||
# change the name inside the main input field as well if taxon is the root one
|
||||
document.getElementById("taxonomy_name").value = name if node.parents("[id]").attr("id") == "taxonomy_tree"
|
||||
|
||||
url = new URL(base_url)
|
||||
url.pathname = url.pathname + '/' + node.attr("id")
|
||||
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url.toString(),
|
||||
data: {_method: "put", "taxon[name]": name },
|
||||
error: handle_ajax_error
|
||||
|
||||
handle_delete = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
delete_url = new URL(base_url)
|
||||
delete_url.pathname = delete_url.pathname + '/' + node.attr("id")
|
||||
if confirm(Spree.translations.are_you_sure_delete)
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: delete_url.toString(),
|
||||
data: {_method: "delete"},
|
||||
error: handle_ajax_error
|
||||
else
|
||||
$.jstree.rollback(last_rollback)
|
||||
last_rollback = null
|
||||
|
||||
root = exports ? this
|
||||
root.setup_taxonomy_tree = (taxonomy_id) ->
|
||||
if taxonomy_id != undefined
|
||||
# this is defined within admin/taxonomies/edit
|
||||
root.base_url = Spree.url(Spree.routes.taxonomy_taxons)
|
||||
|
||||
$.ajax
|
||||
url: base_url.pathname.replace("/taxons", "/jstree"),
|
||||
success: (taxonomy) ->
|
||||
last_rollback = null
|
||||
|
||||
conf =
|
||||
json_data:
|
||||
data: taxonomy,
|
||||
ajax:
|
||||
url: (e) ->
|
||||
base_url.pathname + '/' + e.attr('id') + '/jstree'
|
||||
themes:
|
||||
theme: "apple",
|
||||
url: "/assets/jquery.jstree/themes/apple/style.css"
|
||||
strings:
|
||||
new_node: new_taxon,
|
||||
loading: Spree.translations.loading + "..."
|
||||
crrm:
|
||||
move:
|
||||
check_move: (m) ->
|
||||
position = m.cp
|
||||
node = m.o
|
||||
new_parent = m.np
|
||||
|
||||
# no parent or cant drag and drop
|
||||
if !new_parent || node.attr("rel") == "root"
|
||||
return false
|
||||
|
||||
# can't drop before root
|
||||
if new_parent.attr("id") == "taxonomy_tree" && position == 0
|
||||
return false
|
||||
|
||||
true
|
||||
contextmenu:
|
||||
items: (obj) ->
|
||||
taxon_tree_menu(obj, this)
|
||||
plugins: ["themes", "json_data", "dnd", "crrm", "contextmenu"]
|
||||
|
||||
$("#taxonomy_tree").jstree(conf)
|
||||
.bind("move_node.jstree", handle_move)
|
||||
.bind("remove.jstree", handle_delete)
|
||||
.bind("create.jstree", handle_create)
|
||||
.bind("rename.jstree", handle_rename)
|
||||
.bind "loaded.jstree", ->
|
||||
$(this).jstree("core").toggle_node($('.jstree-icon').first())
|
||||
|
||||
$("#taxonomy_tree a").on "dblclick", (e) ->
|
||||
$("#taxonomy_tree").jstree("rename", this)
|
||||
|
||||
# surpress form submit on enter/return
|
||||
$(document).keypress (e) ->
|
||||
if e.keyCode == 13
|
||||
e.preventDefault()
|
||||
@@ -10,7 +10,9 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
||||
|
||||
statusMessage:
|
||||
text: ""
|
||||
style: {}
|
||||
style: {},
|
||||
type: null,
|
||||
actionName: null
|
||||
|
||||
invalidMessage: ""
|
||||
|
||||
@@ -23,11 +25,15 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
||||
active: ->
|
||||
@statusMessage.text != ''
|
||||
|
||||
display: (type, text) ->
|
||||
display: (type, text, actionName = null) ->
|
||||
@statusMessage.text = text
|
||||
@statusMessage.type = type
|
||||
@statusMessage.actionName = actionName
|
||||
@statusMessage.style = @types[type].style
|
||||
null
|
||||
|
||||
clear: ->
|
||||
@statusMessage.text = ''
|
||||
@statusMessage.style = {}
|
||||
@statusMessage.type = null
|
||||
@statusMessage.actionName = null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#save-bar.animate-show{ "ng-show": 'dirty || persist || StatusMessage.active()' }
|
||||
.container
|
||||
.seven.columns.alpha
|
||||
%h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style' }
|
||||
%h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style', data: { 'order-cycle-form-target': 'statusMessage' }, "ng-attr-data-type": "{{StatusMessage.statusMessage.type}}", "ng-attr-data-action-name": "{{StatusMessage.statusMessage.actionName}}" }
|
||||
{{ StatusMessage.statusMessage.text || " " }}
|
||||
%h5#status-message{ style: 'color: #C85136', "ng-show": "StatusMessage.invalidMessage !== ''" }
|
||||
{{ StatusMessage.invalidMessage || " " }}
|
||||
|
||||
@@ -24,6 +24,19 @@
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* prevent arrow on selected admin menu item appearing above modal */
|
||||
|
||||
22
app/controllers/admin/connected_app_settings_controller.rb
Normal file
22
app/controllers/admin/connected_app_settings_controller.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ConnectedAppSettingsController < Spree::Admin::BaseController
|
||||
def update
|
||||
Spree::Config.set(connected_apps_enabled:)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = t(:successfully_updated, resource: t('.resource'))
|
||||
redirect_to main_app.edit_admin_connected_app_settings_path
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def connected_apps_enabled
|
||||
params.require(:preferences).require(:connected_apps_enabled).compact_blank.join(",")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,12 +5,12 @@ module Admin
|
||||
def create
|
||||
authorize! :admin, enterprise
|
||||
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id)
|
||||
attributes = {}
|
||||
attributes[:type] = connected_app_params[:type] if connected_app_params[:type]
|
||||
|
||||
ConnectAppJob.perform_later(
|
||||
app, spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request),
|
||||
)
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id, **attributes)
|
||||
app.connect(api_key: spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request))
|
||||
|
||||
render_panel
|
||||
end
|
||||
@@ -18,15 +18,9 @@ module Admin
|
||||
def destroy
|
||||
authorize! :admin, enterprise
|
||||
|
||||
app = enterprise.connected_apps.first
|
||||
app = enterprise.connected_apps.find(params.require(:id))
|
||||
app.destroy
|
||||
|
||||
WebhookDeliveryJob.perform_later(
|
||||
app.data["destroy"],
|
||||
"disconnect-app",
|
||||
nil
|
||||
)
|
||||
|
||||
render_panel
|
||||
end
|
||||
|
||||
@@ -39,5 +33,9 @@ module Admin
|
||||
def render_panel
|
||||
redirect_to "#{edit_admin_enterprise_path(enterprise)}#/connected_apps_panel"
|
||||
end
|
||||
|
||||
def connected_app_params
|
||||
params.permit(:type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,13 +35,7 @@ module Admin
|
||||
private
|
||||
|
||||
def fetch_catalog(url)
|
||||
if url =~ /food-data-collaboration/
|
||||
fdc_json = FdcRequest.new(spree_current_user).call(url)
|
||||
fdc_message = JSON.parse(fdc_json)
|
||||
fdc_message["products"]
|
||||
else
|
||||
DfcRequest.new(spree_current_user).call(url)
|
||||
end
|
||||
DfcRequest.new(spree_current_user).call(url)
|
||||
end
|
||||
|
||||
# Most of this code is the same as in the DfcProvider::SuppliedProductsController.
|
||||
|
||||
@@ -65,7 +65,9 @@ module Admin
|
||||
order_cycle ||= OrderCycle.new(coordinator:) if coordinator.present?
|
||||
enterprises = OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user,
|
||||
order_cycle).visible_enterprises
|
||||
EnterpriseFee.for_enterprises(enterprises).order('enterprise_id', 'fee_type', 'name')
|
||||
|
||||
fees = EnterpriseFee.for_enterprises(enterprises).order('enterprise_id', 'fee_type', 'name')
|
||||
filter_fees(fees)
|
||||
else
|
||||
collection = EnterpriseFee.managed_by(spree_current_user).order('enterprise_id',
|
||||
'fee_type', 'name')
|
||||
@@ -74,6 +76,12 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def filter_fees(fees)
|
||||
fees = fees.per_item if params[:per_item]
|
||||
fees = fees.per_order if params[:per_order]
|
||||
fees
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index, :for_order_cycle, :bulk_update]
|
||||
end
|
||||
|
||||
@@ -11,6 +11,7 @@ module Admin
|
||||
before_action :remove_protected_attrs, only: [:update]
|
||||
before_action :require_order_cycle_set_params, only: [:bulk_update]
|
||||
around_action :protect_invalid_destroy, only: :destroy
|
||||
before_action :verify_datetime_change, only: :update
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
@@ -235,7 +236,7 @@ module Admin
|
||||
else
|
||||
begin
|
||||
yield
|
||||
rescue ActiveRecord::InvalidForeignKey
|
||||
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::DeleteRestrictionError
|
||||
redirect_to main_app.admin_order_cycles_url
|
||||
flash[:error] = I18n.t('admin.order_cycles.destroy_errors.orders_present')
|
||||
end
|
||||
@@ -294,5 +295,22 @@ module Admin
|
||||
collection_attributes: [:id] + PermittedAttributes::OrderCycle.basic_attributes
|
||||
).to_h.with_indifferent_access
|
||||
end
|
||||
|
||||
# Check that order cycle datetime values changed if it has existing orders
|
||||
def verify_datetime_change
|
||||
return unless params[:order_cycle][:confirm]
|
||||
return unless @order_cycle.orders.exists?
|
||||
return if same_dates(@order_cycle.orders_open_at, order_cycle_params[:orders_open_at]) &&
|
||||
same_dates(@order_cycle.orders_close_at, order_cycle_params[:orders_close_at])
|
||||
|
||||
render json: { trigger_action: params[:order_cycle][:trigger_action] },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def same_dates(date, string)
|
||||
false unless date && string
|
||||
|
||||
DateTime.parse(string).to_fs(:short) == date.to_fs(:short)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,8 @@ require 'roo'
|
||||
|
||||
module Admin
|
||||
class ProductImportController < Spree::Admin::BaseController
|
||||
TMPDIR_PREFIX = "product_import-"
|
||||
|
||||
before_action :validate_upload_presence, except: %i[index guide validate_data]
|
||||
|
||||
def index
|
||||
@@ -101,8 +103,7 @@ module Admin
|
||||
|
||||
def save_uploaded_file(upload)
|
||||
extension = File.extname(upload.original_filename)
|
||||
directory = Dir.mktmpdir 'product_import'
|
||||
File.open(File.join(directory, "import#{extension}"), 'wb') do |f|
|
||||
File.open(File.join(mktmpdir, "import#{extension}"), 'wb') do |f|
|
||||
data = UploadSanitizer.new(upload.read).call
|
||||
f.write(data)
|
||||
f.path
|
||||
@@ -126,6 +127,14 @@ module Admin
|
||||
ProductImport::ProductImporter
|
||||
end
|
||||
|
||||
def mktmpdir
|
||||
Dir::Tmpname.create(TMPDIR_PREFIX, Rails.root.join('tmp') ) { |tmpname| Dir.mkdir(tmpname) }
|
||||
end
|
||||
|
||||
def tmpdir_base
|
||||
Rails.root.join('tmp', TMPDIR_PREFIX).to_s
|
||||
end
|
||||
|
||||
def file_path
|
||||
@file_path ||= validate_file_path(sanitize_file_path(params[:filepath]))
|
||||
end
|
||||
@@ -134,8 +143,9 @@ module Admin
|
||||
FilePathSanitizer.new.sanitize(file_path, on_error: method(:raise_invalid_file_path))
|
||||
end
|
||||
|
||||
# Ensure file is under the safe tmp directory
|
||||
def validate_file_path(file_path)
|
||||
return file_path if file_path.to_s.match?(TEMP_FILE_PATH_REGEX)
|
||||
return file_path if file_path.to_s.match?(%r{^#{tmpdir_base}[A-Za-z0-9-]*/import\.csv$})
|
||||
|
||||
raise_invalid_file_path
|
||||
end
|
||||
@@ -145,6 +155,5 @@ module Admin
|
||||
notice: I18n.t(:product_import_no_data_in_spreadsheet_notice)
|
||||
raise 'Invalid File Path'
|
||||
end
|
||||
TEMP_FILE_PATH_REGEX = %r{^/tmp/product_import[A-Za-z0-9-]*/import\.csv$}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,8 @@ module Admin
|
||||
def index
|
||||
fetch_products
|
||||
render "index", locals: { producers:, categories:, tax_category_options:, flash: }
|
||||
|
||||
session[:products_return_to_url] = request.url
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
|
||||
@@ -96,7 +96,7 @@ module Api
|
||||
def distributed_products
|
||||
OrderCycles::DistributedProductsService.new(
|
||||
distributor, order_cycle, customer
|
||||
).products_supplier_relation.pluck(:id)
|
||||
).products_relation.pluck(:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V0
|
||||
class TaxonomiesController < Api::V0::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check only: :jstree
|
||||
|
||||
def jstree
|
||||
@taxonomy = Spree::Taxonomy.find(params[:id])
|
||||
render json: @taxonomy.root, serializer: Api::TaxonJstreeSerializer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,12 +5,10 @@ module Api
|
||||
class TaxonsController < Api::V0::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check only: [:index, :show, :jstree]
|
||||
skip_authorization_check only: [:index, :show]
|
||||
|
||||
def index
|
||||
@taxons = if taxonomy
|
||||
taxonomy.root.children
|
||||
elsif params[:ids]
|
||||
@taxons = if params[:ids]
|
||||
Spree::Taxon.where(id: raw_params[:ids].split(","))
|
||||
else
|
||||
Spree::Taxon.ransack(raw_params[:q]).result
|
||||
@@ -18,23 +16,9 @@ module Api
|
||||
render json: @taxons, each_serializer: Api::TaxonSerializer
|
||||
end
|
||||
|
||||
def jstree
|
||||
@taxon = taxon
|
||||
render json: @taxon.children, each_serializer: Api::TaxonJstreeSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
authorize! :create, Spree::Taxon
|
||||
@taxon = Spree::Taxon.new(taxon_params)
|
||||
@taxon.taxonomy_id = params[:taxonomy_id]
|
||||
taxonomy = Spree::Taxonomy.find_by(id: params[:taxonomy_id])
|
||||
|
||||
if taxonomy.nil?
|
||||
@taxon.errors.add(:taxonomy_id, I18n.t(:invalid_taxonomy_id, scope: 'spree.api'))
|
||||
invalid_resource!(@taxon) && return
|
||||
end
|
||||
|
||||
@taxon.parent_id = taxonomy.root.id unless params.dig(:taxon, :parent_id)
|
||||
|
||||
if @taxon.save
|
||||
render json: @taxon, serializer: Api::TaxonSerializer, status: :created
|
||||
@@ -60,20 +44,14 @@ module Api
|
||||
|
||||
private
|
||||
|
||||
def taxonomy
|
||||
return if params[:taxonomy_id].blank?
|
||||
|
||||
@taxonomy ||= Spree::Taxonomy.find(params[:taxonomy_id])
|
||||
end
|
||||
|
||||
def taxon
|
||||
@taxon ||= taxonomy.taxons.find(params[:id])
|
||||
@taxon = Spree::Taxon.find(params[:id])
|
||||
end
|
||||
|
||||
def taxon_params
|
||||
return if params[:taxon].blank?
|
||||
|
||||
params.require(:taxon).permit([:name, :parent_id, :position])
|
||||
params.require(:taxon).permit([:name, :position])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,15 +19,18 @@ module Spree
|
||||
|
||||
before_action :authorize_admin
|
||||
before_action :set_locale
|
||||
before_action :warn_invalid_order_cycles, if: :html_request?
|
||||
before_action :warn_invalid_order_cycles, if: :page_load_request?
|
||||
|
||||
# Warn the user when they have an active order cycle with hubs that are not ready
|
||||
# for checkout (ie. does not have valid shipping and payment methods).
|
||||
def warn_invalid_order_cycles
|
||||
return if flash[:notice].present?
|
||||
return if flash[:notice].present? || session[:displayed_order_cycle_warning]
|
||||
|
||||
warning = OrderCycles::WarningService.new(spree_current_user).call
|
||||
flash[:notice] = warning if warning.present?
|
||||
return if warning.blank?
|
||||
|
||||
flash.now[:notice] = warning
|
||||
session[:displayed_order_cycle_warning] = true
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -81,6 +84,12 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def page_load_request?
|
||||
return false if request.format.include?('turbo')
|
||||
|
||||
html_request?
|
||||
end
|
||||
|
||||
def html_request?
|
||||
request.format.html?
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ module Spree
|
||||
include OpenFoodNetwork::SpreeApiKeyLoader
|
||||
include OrderCyclesHelper
|
||||
include EnterprisesHelper
|
||||
helper ::Admin::ProductsHelper
|
||||
|
||||
before_action :load_data
|
||||
before_action :load_producers, only: [:index, :new]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class TaxonomiesController < ::Admin::ResourceController
|
||||
respond_to :json, only: [:get_children]
|
||||
|
||||
def get_children
|
||||
@taxons = Taxon.find(params[:parent_id]).children
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def location_after_save
|
||||
if @taxonomy.created_at == @taxonomy.updated_at
|
||||
spree.edit_admin_taxonomy_url(@taxonomy)
|
||||
else
|
||||
spree.admin_taxonomies_url
|
||||
end
|
||||
end
|
||||
|
||||
def permitted_resource_params
|
||||
params.require(:taxonomy).permit(:name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,122 +2,70 @@
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class TaxonsController < Spree::Admin::BaseController
|
||||
respond_to :html, :json, :js
|
||||
class TaxonsController < ::Admin::ResourceController
|
||||
before_action :set_taxon, except: %i[create index new]
|
||||
|
||||
def edit
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.find(params[:id])
|
||||
@permalink_part = @taxon.permalink.split("/").last
|
||||
def index
|
||||
@taxons = Taxon.order(:name)
|
||||
end
|
||||
|
||||
def new
|
||||
@taxon = Taxon.new
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.build(params[:taxon])
|
||||
@taxon = Spree::Taxon.new(taxon_params)
|
||||
if @taxon.save
|
||||
respond_with(@taxon) do |format|
|
||||
format.json { render json: @taxon.to_json }
|
||||
end
|
||||
flash[:success] = flash_message_for(@taxon, :successfully_created)
|
||||
redirect_to edit_admin_taxon_path(@taxon.id)
|
||||
else
|
||||
flash[:error] = Spree.t('errors.messages.could_not_create_taxon')
|
||||
respond_with(@taxon) do |format|
|
||||
format.html do
|
||||
if redirect_to @taxonomy
|
||||
spree.edit_admin_taxonomy_url(@taxonomy)
|
||||
else
|
||||
spree.admin_taxonomies_url
|
||||
end
|
||||
end
|
||||
end
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.find(params[:id])
|
||||
parent_id = params[:taxon][:parent_id]
|
||||
new_position = params[:taxon][:position]
|
||||
|
||||
if parent_id || new_position # taxon is being moved
|
||||
new_parent = parent_id.nil? ? @taxon.parent : Taxon.find(parent_id.to_i)
|
||||
new_position = new_position.nil? ? -1 : new_position.to_i
|
||||
|
||||
# Bellow is a very complicated way of finding where in nested set we
|
||||
# should actually move the taxon to achieve sane results,
|
||||
# JS is giving us the desired position, which was awesome for previous setup,
|
||||
# but now it's quite complicated to find where we should put it as we have
|
||||
# to differenciate between moving to the same branch, up down and into
|
||||
# first position.
|
||||
new_siblings = new_parent.children
|
||||
if new_position <= 0 && new_siblings.empty?
|
||||
@taxon.move_to_child_of(new_parent)
|
||||
elsif new_parent.id != @taxon.parent_id
|
||||
if new_position.zero?
|
||||
@taxon.move_to_left_of(new_siblings.first)
|
||||
else
|
||||
@taxon.move_to_right_of(new_siblings[new_position - 1])
|
||||
end
|
||||
elsif new_position < new_siblings.index(@taxon)
|
||||
@taxon.move_to_left_of(new_siblings[new_position]) # we move up
|
||||
else
|
||||
@taxon.move_to_right_of(new_siblings[new_position - 1]) # we move down
|
||||
end
|
||||
# Reset legacy position, if any extensions still rely on it
|
||||
new_parent.children.reload.each do |t|
|
||||
t.update_columns(
|
||||
position: t.position,
|
||||
updated_at: Time.zone.now
|
||||
)
|
||||
end
|
||||
|
||||
if parent_id
|
||||
@taxon.reload
|
||||
@taxon.set_permalink
|
||||
@taxon.save!
|
||||
@update_children = true
|
||||
end
|
||||
end
|
||||
|
||||
if params.key? "permalink_part"
|
||||
parent_permalink = @taxon.permalink.split("/")[0...-1].join("/")
|
||||
parent_permalink += "/" if parent_permalink.present?
|
||||
params[:taxon][:permalink] = parent_permalink + params[:permalink_part]
|
||||
end
|
||||
# check if we need to rename child taxons if parent name or permalink changes
|
||||
if params[:taxon][:name] != @taxon.name || params[:taxon][:permalink] != @taxon.permalink
|
||||
@update_children = true
|
||||
end
|
||||
|
||||
if @taxon.update(taxon_params)
|
||||
flash[:success] = flash_message_for(@taxon, :successfully_updated)
|
||||
end
|
||||
|
||||
# rename child taxons
|
||||
if @update_children
|
||||
@taxon.descendants.each do |taxon|
|
||||
taxon.reload
|
||||
taxon.set_permalink
|
||||
taxon.save!
|
||||
end
|
||||
end
|
||||
|
||||
respond_with(@taxon) do |format|
|
||||
format.html { redirect_to spree.edit_admin_taxonomy_url(@taxonomy) }
|
||||
format.json { render json: @taxon.to_json }
|
||||
redirect_to edit_admin_taxon_path(@taxon.id)
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@taxon = Taxon.find(params[:id])
|
||||
@taxon.destroy
|
||||
respond_with(@taxon) { |format| format.json { render json: '' } }
|
||||
status = if @taxon.destroy
|
||||
flash_message = t('.delete_taxon.success')
|
||||
status = :ok
|
||||
else
|
||||
flash_message = t('.delete_taxon.error')
|
||||
status = :unprocessable_entity
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
flash[:success] = flash_message if status == :ok
|
||||
flash[:error] = flash_message if status == :unprocessable_entity
|
||||
redirect_to admin_taxons_path
|
||||
}
|
||||
format.turbo_stream {
|
||||
flash[:success] = flash_message if status == :ok
|
||||
flash[:error] = flash_message if status == :unprocessable_entity
|
||||
render :destroy_taxon, status:
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_taxon
|
||||
@taxon = Taxon.find(params[:id])
|
||||
end
|
||||
|
||||
def taxon_params
|
||||
params.require(:taxon).permit(
|
||||
:name, :parent_id, :position, :icon, :description, :permalink, :taxonomy_id,
|
||||
:name, :position, :icon, :description, :permalink,
|
||||
:meta_description, :meta_keywords, :meta_title, :dfc_id
|
||||
)
|
||||
end
|
||||
|
||||
@@ -47,14 +47,8 @@ module Spree
|
||||
@user = Spree::User.new(user_params)
|
||||
|
||||
if @user.save
|
||||
render cable_ready: cable_car.inner_html(
|
||||
"#signup-feedback",
|
||||
partial("layouts/alert",
|
||||
locals: {
|
||||
type: "success",
|
||||
message: t('devise.user_registrations.spree_user.signed_up_but_unconfirmed')
|
||||
})
|
||||
)
|
||||
flash[:success] = t('devise.user_registrations.spree_user.signed_up_but_unconfirmed')
|
||||
render cable_ready: cable_car.redirect_to(url: main_app.root_path)
|
||||
else
|
||||
render status: :unprocessable_entity, cable_ready: cable_car.morph(
|
||||
"#signup-tab",
|
||||
|
||||
@@ -26,7 +26,8 @@ module Admin
|
||||
show_enterprise_fees = can?(:manage_enterprise_fees,
|
||||
enterprise) && (is_shop || enterprise.is_primary_producer)
|
||||
show_connected_apps = can?(:manage_connected_apps, enterprise) &&
|
||||
feature?(:connected_apps, spree_current_user, enterprise)
|
||||
feature?(:connected_apps, spree_current_user, enterprise) &&
|
||||
Spree::Config.connected_apps_enabled.present?
|
||||
|
||||
build_enterprise_side_menu_items(
|
||||
is_shop:,
|
||||
@@ -38,6 +39,11 @@ module Admin
|
||||
)
|
||||
end
|
||||
|
||||
def connected_apps_enabled
|
||||
connected_apps_enabled = Spree::Config.connected_apps_enabled&.split(',') || []
|
||||
ConnectedApp::TYPES & connected_apps_enabled
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_enterprise_side_menu_items(
|
||||
|
||||
@@ -9,5 +9,33 @@ module Admin
|
||||
new_admin_product_image_path(product.id)
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_new_variant(product)
|
||||
product.variants.build
|
||||
end
|
||||
|
||||
def unit_value_with_description(variant)
|
||||
precised_unit_value = nil
|
||||
|
||||
if variant.unit_value
|
||||
scaled_unit_value = variant.unit_value / (variant.product.variant_unit_scale || 1)
|
||||
precised_unit_value = number_with_precision(
|
||||
scaled_unit_value,
|
||||
precision: nil,
|
||||
strip_insignificant_zeros: true,
|
||||
significant: false,
|
||||
)
|
||||
end
|
||||
|
||||
[precised_unit_value, variant.unit_description].compact_blank.join(" ")
|
||||
end
|
||||
|
||||
def products_return_to_url(url_filters)
|
||||
if feature?(:admin_style_v3, spree_current_user)
|
||||
return session[:products_return_to_url] || admin_products_url
|
||||
end
|
||||
|
||||
"#{admin_products_path}#{url_filters.empty? ? '' : "#?#{url_filters.to_query}"}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module I18nHelper
|
||||
def locale_options
|
||||
OpenFoodNetwork::I18nConfig.available_locales.map do |locale|
|
||||
OpenFoodNetwork::I18nConfig.selectable_locales.map do |locale|
|
||||
[t('language_name', locale:), locale]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,4 +10,8 @@ module MailerHelper
|
||||
link_to ofn, "https://www.openfoodnetwork.org"
|
||||
end
|
||||
end
|
||||
|
||||
def order_reply_email(order)
|
||||
order.distributor.email_address.presence || order.distributor.contact.email
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
module TaxonsHelper
|
||||
def taxon_path(taxon)
|
||||
taxon.ancestors.reverse.collect(&:name).join( " >> ")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -19,8 +19,8 @@ class ConnectAppJob < ApplicationJob
|
||||
|
||||
selector = "#connected-app-discover-regen.enterprise_#{enterprise.id}"
|
||||
html = ApplicationController.render(
|
||||
partial: "admin/enterprises/form/connected_apps",
|
||||
locals: { enterprise: },
|
||||
partial: "admin/enterprises/form/connected_apps/discover_regen",
|
||||
locals: { enterprise:, connected_app: enterprise.connected_apps.discover_regen.first },
|
||||
)
|
||||
|
||||
cable_ready[channel].morph(selector:, html:).broadcast
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Rails 5 introduced some breaking changes to these built-in methods, and the new versions
|
||||
# no longer work correctly in relation to decrementing stock with LineItems / VariantOverrides.
|
||||
# The following methods re-instate the pre-Rails-5 versions, which work as expected.
|
||||
# https://apidock.com/rails/v4.2.9/ActiveRecord/Persistence/increment%21
|
||||
# https://apidock.com/rails/v4.2.9/ActiveRecord/Persistence/decrement%21
|
||||
|
||||
module LineItemStockChanges
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def increment!(attribute, by = 1)
|
||||
increment(attribute, by).update_attribute(attribute, self[attribute])
|
||||
end
|
||||
|
||||
def decrement!(attribute, by = 1)
|
||||
decrement(attribute, by).update_attribute(attribute, self[attribute])
|
||||
end
|
||||
end
|
||||
@@ -4,8 +4,34 @@
|
||||
#
|
||||
# Here we store keys and links to access the app.
|
||||
class ConnectedApp < ApplicationRecord
|
||||
TYPES = ['discover_regen', 'affiliate_sales_data'].freeze
|
||||
|
||||
belongs_to :enterprise
|
||||
after_destroy :disconnect
|
||||
|
||||
scope :discover_regen, -> { where(type: "ConnectedApp") }
|
||||
scope :affiliate_sales_data, -> { where(type: "ConnectedApps::AffiliateSalesData") }
|
||||
|
||||
scope :connecting, -> { where(data: nil) }
|
||||
scope :ready, -> { where.not(data: nil) }
|
||||
|
||||
def connecting?
|
||||
data.nil?
|
||||
end
|
||||
|
||||
def ready?
|
||||
!connecting?
|
||||
end
|
||||
|
||||
def connect(api_key:, channel:)
|
||||
ConnectAppJob.perform_later(self, api_key, channel:)
|
||||
end
|
||||
|
||||
def disconnect
|
||||
WebhookDeliveryJob.perform_later(
|
||||
data["destroy"],
|
||||
"disconnect-app",
|
||||
nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
13
app/models/connected_apps/affiliate_sales_data.rb
Normal file
13
app/models/connected_apps/affiliate_sales_data.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# An enterprise can opt-in for their data to be included in the affiliate_sales_data endpoint
|
||||
#
|
||||
module ConnectedApps
|
||||
class AffiliateSalesData < ConnectedApp
|
||||
def connect(_opts)
|
||||
update! data: true # not-nil value indicates it is ready
|
||||
end
|
||||
|
||||
def disconnect; end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
require "mini_magick"
|
||||
|
||||
class Enterprise < ApplicationRecord
|
||||
SELLS = %w(unspecified none own any).freeze
|
||||
ENTERPRISE_SEARCH_RADIUS = 100
|
||||
|
||||
@@ -24,6 +24,7 @@ class OrderCycle < ApplicationRecord
|
||||
where incoming: false
|
||||
}, class_name: "Exchange", dependent: :destroy
|
||||
|
||||
has_many :orders, class_name: 'Spree::Order', dependent: :restrict_with_exception
|
||||
has_many :suppliers, -> { distinct }, source: :sender, through: :cached_incoming_exchanges
|
||||
has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges
|
||||
has_many :order_cycle_schedules, dependent: :destroy
|
||||
|
||||
@@ -162,7 +162,7 @@ module ProductImport
|
||||
return
|
||||
end
|
||||
|
||||
product = Spree::Product.new
|
||||
product = Spree::Product.new(supplier_id: entry.enterprise_id)
|
||||
product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
|
||||
@@ -377,7 +377,7 @@ module ProductImport
|
||||
end
|
||||
|
||||
def mark_as_new_product(entry)
|
||||
new_product = Spree::Product.new
|
||||
new_product = Spree::Product.new(supplier_id: entry.enterprise_id)
|
||||
new_product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
|
||||
@@ -287,8 +287,6 @@ module ProductImport
|
||||
end
|
||||
|
||||
def delete_uploaded_file
|
||||
return unless @file.path == Rails.root.join("tmp/product_import").to_s
|
||||
|
||||
File.delete(@file)
|
||||
end
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ module Spree
|
||||
can [:index, :read], StockLocation
|
||||
can [:index, :read], StockMovement
|
||||
can [:index, :read], Taxon
|
||||
can [:index, :read], Taxonomy
|
||||
can [:index, :read], Variant
|
||||
can [:index, :read], Zone
|
||||
end
|
||||
|
||||
@@ -139,5 +139,8 @@ module Spree
|
||||
|
||||
# Available units
|
||||
preference :available_units, :string, default: "g,kg,T,mL,L,kL"
|
||||
|
||||
# Connected Apps
|
||||
preference :connected_apps_enabled, :string, default: nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "mini_magick"
|
||||
|
||||
module Spree
|
||||
class Image < Asset
|
||||
has_one_attached :attachment, service: image_service do |attachment|
|
||||
|
||||
@@ -5,7 +5,6 @@ require 'open_food_network/scope_variant_to_hub'
|
||||
module Spree
|
||||
class LineItem < ApplicationRecord
|
||||
include VariantUnits::VariantAndLineItemNaming
|
||||
include LineItemStockChanges
|
||||
|
||||
searchable_attributes :price, :quantity, :order_id, :variant_id, :tax_category_id
|
||||
searchable_associations :order, :order_cycle, :variant, :product, :supplier, :tax_category
|
||||
|
||||
@@ -69,6 +69,7 @@ module Spree
|
||||
attr_accessor :price, :display_as, :unit_value, :unit_description, :tax_category_id,
|
||||
:shipping_category_id, :primary_taxon_id, :supplier_id
|
||||
|
||||
after_validation :validate_variant_attrs, on: :create
|
||||
after_create :ensure_standard_variant
|
||||
after_update :touch_supplier, if: :saved_change_to_primary_taxon_id?
|
||||
around_destroy :destruction
|
||||
@@ -264,6 +265,7 @@ module Spree
|
||||
def variant_unit_with_scale
|
||||
scale_clean = ActiveSupport::NumberHelper.number_to_rounded(variant_unit_scale,
|
||||
precision: nil,
|
||||
significant: false,
|
||||
strip_insignificant_zeros: true)
|
||||
[variant_unit, scale_clean].compact_blank.join("_")
|
||||
end
|
||||
@@ -288,6 +290,21 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def validate_variant_attrs
|
||||
# Avoid running validation when we can't set variant attrs
|
||||
# eg clone product. Will raise error if clonning a product with no variant
|
||||
return if variants.first&.valid?
|
||||
|
||||
unless Spree::Taxon.find_by(id: primary_taxon_id)
|
||||
errors.add(:primary_taxon_id,
|
||||
I18n.t('activerecord.errors.models.spree/product.must_exist'))
|
||||
end
|
||||
return if Enterprise.find_by(id: supplier_id)
|
||||
|
||||
errors.add(:supplier_id,
|
||||
I18n.t('activerecord.errors.models.spree/product.must_exist'))
|
||||
end
|
||||
|
||||
def update_units
|
||||
return unless saved_change_to_variant_unit? || saved_change_to_variant_unit_name?
|
||||
|
||||
|
||||
@@ -2,19 +2,11 @@
|
||||
|
||||
module Spree
|
||||
class Taxon < ApplicationRecord
|
||||
self.belongs_to_required_by_default = false
|
||||
|
||||
acts_as_nested_set dependent: :destroy
|
||||
|
||||
belongs_to :taxonomy, class_name: 'Spree::Taxonomy', touch: true
|
||||
|
||||
has_many :variants, class_name: "Spree::Variant", foreign_key: "primary_taxon_id",
|
||||
inverse_of: :primary_taxon, dependent: :restrict_with_error
|
||||
|
||||
has_many :products, through: :variants, dependent: nil
|
||||
|
||||
before_create :set_permalink
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
# Indicate which filters should be used for this taxon
|
||||
@@ -31,26 +23,6 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
def set_permalink
|
||||
if parent.present?
|
||||
self.permalink = [parent.permalink, permalink_end].join('/')
|
||||
elsif permalink.blank?
|
||||
self.permalink = UrlGenerator.to_url(name)
|
||||
end
|
||||
end
|
||||
|
||||
# For #2759
|
||||
def to_param
|
||||
permalink
|
||||
end
|
||||
|
||||
def pretty_name
|
||||
ancestor_chain = ancestors.inject("") do |name, ancestor|
|
||||
name + "#{ancestor.name} -> "
|
||||
end
|
||||
ancestor_chain + name.to_s
|
||||
end
|
||||
|
||||
# Find all the taxons of supplied products for each enterprise, indexed by enterprise.
|
||||
# Format: {enterprise_id => [taxon_id, ...]}
|
||||
def self.supplied_taxons
|
||||
@@ -90,13 +62,5 @@ module Spree
|
||||
ts[t.enterprise_id.to_i] << t.id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def permalink_end
|
||||
return UrlGenerator.to_url(name) if permalink.blank?
|
||||
|
||||
permalink.split('/').last
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
class Taxonomy < ApplicationRecord
|
||||
validates :name, presence: true
|
||||
|
||||
has_many :taxons, dependent: :nullify
|
||||
has_one :root, -> { where parent_id: nil }, class_name: "Spree::Taxon", dependent: :destroy
|
||||
|
||||
after_save :set_name
|
||||
|
||||
default_scope -> { order("#{table_name}.position") }
|
||||
|
||||
private
|
||||
|
||||
def set_name
|
||||
if root
|
||||
root.update_columns(
|
||||
name:,
|
||||
updated_at: Time.zone.now
|
||||
)
|
||||
else
|
||||
self.root = Taxon.create!(taxonomy_id: id, name:)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -156,6 +156,12 @@ module Spree
|
||||
self.disabled_at = value == '1' ? Time.zone.now : nil
|
||||
end
|
||||
|
||||
def affiliate_enterprises
|
||||
return [] unless Flipper.enabled?(:affiliate_sales_data, self)
|
||||
|
||||
Enterprise.joins(:connected_apps).merge(ConnectedApps::AffiliateSalesData.ready)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def password_required?
|
||||
|
||||
@@ -87,7 +87,6 @@ module Spree
|
||||
before_validation :ensure_unit_value
|
||||
before_validation :update_weight_from_unit_value, if: ->(v) { v.product.present? }
|
||||
before_validation :convert_variant_weight_to_decimal
|
||||
before_validation :assign_related_taxon, if: ->(v) { v.primary_taxon.blank? }
|
||||
|
||||
before_save :assign_units, if: ->(variant) {
|
||||
variant.new_record? || variant.changed_attributes.keys.intersection(NAME_FIELDS).any?
|
||||
@@ -217,10 +216,6 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def assign_related_taxon
|
||||
self.primary_taxon ||= product.variants.last&.primary_taxon
|
||||
end
|
||||
|
||||
def check_currency
|
||||
return unless currency.nil?
|
||||
|
||||
|
||||
@@ -52,11 +52,14 @@ class VariantOverride < ApplicationRecord
|
||||
return
|
||||
end
|
||||
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
# Cf. conversation https://github.com/openfoodfoundation/openfoodnetwork/pull/12647
|
||||
if quantity > 0
|
||||
increment! :count_on_hand, quantity
|
||||
elsif quantity < 0
|
||||
decrement! :count_on_hand, -quantity
|
||||
end
|
||||
# rubocop:enable Rails/SkipsModelValidations
|
||||
end
|
||||
|
||||
def default_stock?
|
||||
|
||||
@@ -41,6 +41,7 @@ class ProductScopeQuery
|
||||
merge(product_scope).
|
||||
includes(variants: [:product, :default_price, :stock_items, :supplier]).
|
||||
where(variants: { supplier_id: producer_ids }).
|
||||
order("enterprises.name, spree_products.name").
|
||||
ransack(@params[:q]).result(distinct: true)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProductsReflex < ApplicationReflex
|
||||
include Pagy::Backend
|
||||
|
||||
before_reflex :init_filters_params, :init_pagination_params
|
||||
|
||||
def change_per_page
|
||||
@per_page = element.value.to_i
|
||||
@page = 1
|
||||
|
||||
fetch_and_render_products_with_flash
|
||||
end
|
||||
|
||||
def clear_search
|
||||
@search_term = nil
|
||||
@producer_id = nil
|
||||
@category_id = nil
|
||||
@page = 1
|
||||
|
||||
fetch_and_render_products_with_flash
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_filters_params
|
||||
# params comes from the form
|
||||
# _params comes from the url
|
||||
# priority is given to params from the form (if present) over url params
|
||||
@search_term = params[:search_term] || params[:_search_term]
|
||||
@producer_id = params[:producer_id] || params[:_producer_id]
|
||||
@category_id = params[:category_id] || params[:_category_id]
|
||||
end
|
||||
|
||||
def init_pagination_params
|
||||
# prority is given to element dataset (if present) over url params
|
||||
@page = element.dataset.page || params[:_page] || 1
|
||||
@per_page = element.dataset.perpage || params[:_per_page] || 15
|
||||
end
|
||||
|
||||
def fetch_and_render_products_with_flash
|
||||
fetch_products
|
||||
render_products
|
||||
end
|
||||
|
||||
def render_products
|
||||
cable_ready.replace(
|
||||
selector: "#products-content",
|
||||
html: render(partial: "admin/products_v3/content",
|
||||
locals: { products: @products, pagy: @pagy, search_term: @search_term,
|
||||
producer_options: producers, producer_id: @producer_id,
|
||||
category_options: categories, tax_category_options:,
|
||||
category_id: @category_id, flashes: flash })
|
||||
)
|
||||
|
||||
cable_ready.replace_state(
|
||||
url: current_url,
|
||||
)
|
||||
|
||||
morph :nothing
|
||||
end
|
||||
|
||||
def render_products_form_with_flash
|
||||
locals = { products: @products }
|
||||
locals[:error_counts] = @error_counts if @error_counts.present?
|
||||
locals[:flashes] = flash if flash.any?
|
||||
|
||||
cable_ready.replace(
|
||||
selector: "#products-form",
|
||||
html: render(partial: "admin/products_v3/table", locals:)
|
||||
)
|
||||
morph :nothing
|
||||
|
||||
# dunno why this doesn't work. The HTML stops after the first `<col>` element, wtf?!
|
||||
# morph "#products-form", render(partial: "admin/products_v3/table", locals:)
|
||||
end
|
||||
|
||||
def producers
|
||||
producers = OpenFoodNetwork::Permissions.new(current_user)
|
||||
.managed_product_enterprises.is_primary_producer.by_name
|
||||
producers.map { |p| [p.name, p.id] }
|
||||
end
|
||||
|
||||
def categories
|
||||
Spree::Taxon.order(:name).map { |c| [c.name, c.id] }
|
||||
end
|
||||
|
||||
def tax_category_options
|
||||
Spree::TaxCategory.order(:name).pluck(:name, :id)
|
||||
end
|
||||
|
||||
def fetch_products
|
||||
product_query = OpenFoodNetwork::Permissions.new(current_user)
|
||||
.editable_products.merge(product_scope).ransack(ransack_query).result(distinct: true)
|
||||
@pagy, @products = pagy(product_query.order(:name), items: @per_page, page: @page,
|
||||
size: [1, 2, 2, 1])
|
||||
end
|
||||
|
||||
def product_scope
|
||||
scope = if current_user.has_spree_role?("admin") || current_user.enterprises.present?
|
||||
Spree::Product
|
||||
else
|
||||
Spree::Product.active
|
||||
end
|
||||
|
||||
scope.includes(product_query_includes)
|
||||
end
|
||||
|
||||
def ransack_query
|
||||
query = {}
|
||||
query.merge!(variants_supplier_id_in: @producer_id) if @producer_id.present?
|
||||
if @search_term.present?
|
||||
query.merge!(Spree::Variant::SEARCH_KEY => @search_term)
|
||||
end
|
||||
query.merge!(variants_primary_taxon_id_in: @category_id) if @category_id.present?
|
||||
query
|
||||
end
|
||||
|
||||
# Optimise by pre-loading required columns
|
||||
def product_query_includes
|
||||
# TODO: add other fields used in columns? (eg supplier: [:name])
|
||||
[
|
||||
# variants: [
|
||||
# :default_price,
|
||||
# :stock_locations,
|
||||
# :stock_items,
|
||||
# :variant_overrides
|
||||
# ]
|
||||
]
|
||||
end
|
||||
|
||||
def current_url
|
||||
url = URI(request.original_url)
|
||||
url.query = url.query.present? ? "#{url.query}&" : ""
|
||||
# add params with _ to avoid conflicts with params from the form
|
||||
url.query += "_page=#{@page}"
|
||||
url.query += "&_per_page=#{@per_page}"
|
||||
url.query += "&_search_term=#{@search_term}" if @search_term.present?
|
||||
url.query += "&_producer_id=#{@producer_id}" if @producer_id.present?
|
||||
url.query += "&_category_id=#{@category_id}" if @category_id.present?
|
||||
url.to_s
|
||||
end
|
||||
|
||||
# Similar to spree/admin/products_controller
|
||||
def product_set_from_params
|
||||
# Form field names:
|
||||
# '[products][0][id]' (hidden field)
|
||||
# '[products][0][name]'
|
||||
# '[products][0][variants_attributes][0][id]' (hidden field)
|
||||
# '[products][0][variants_attributes][0][display_name]'
|
||||
#
|
||||
# Resulting in params:
|
||||
# "products" => {
|
||||
# "0" => {
|
||||
# "id" => "123"
|
||||
# "name" => "Pommes",
|
||||
# "variants_attributes" => {
|
||||
# "0" => {
|
||||
# "id" => "1234",
|
||||
# "display_name" => "Large box",
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
collection_hash = products_bulk_params[:products]
|
||||
.transform_values { |product|
|
||||
# Convert variants_attributes form hash to an array if present
|
||||
product[:variants_attributes] &&= product[:variants_attributes].values
|
||||
product
|
||||
}.with_indifferent_access
|
||||
Sets::ProductSet.new(collection_attributes: collection_hash)
|
||||
end
|
||||
|
||||
def products_bulk_params
|
||||
params.permit(products: ::PermittedAttributes::Product.attributes)
|
||||
.to_h.with_indifferent_access
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@
|
||||
module Api
|
||||
module Admin
|
||||
class TaxonSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :pretty_name
|
||||
attributes :id, :name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,5 +4,5 @@ class Api::TaxonSerializer < ActiveModel::Serializer
|
||||
cached
|
||||
delegate :cache_key, to: :object
|
||||
|
||||
attributes :id, :name, :permalink, :pretty_name, :position, :parent_id, :taxonomy_id
|
||||
attributes :id, :name, :permalink, :position
|
||||
end
|
||||
|
||||
@@ -4,50 +4,23 @@
|
||||
# The stock-checking includes on_demand and stock level overrides from variant_overrides.
|
||||
|
||||
module OrderCycles
|
||||
class DistributedProductsService
|
||||
class DistributedProductsService # rubocop:disable Metrics/ClassLength
|
||||
def initialize(distributor, order_cycle, customer)
|
||||
@distributor = distributor
|
||||
@order_cycle = order_cycle
|
||||
@customer = customer
|
||||
end
|
||||
|
||||
# TODO refactor products_taxons_relation and products_supplier_relation
|
||||
# Joins on the first product variant to allow us to filter product by taxon. # This is so
|
||||
# enterprise can display product sorted by category in a custom order on their shopfront.
|
||||
#
|
||||
# Caveat, the category sorting won't work properly if there are multiple variant with different
|
||||
# category for a given product.
|
||||
#
|
||||
def products_taxons_relation
|
||||
Spree::Product.where(id: stocked_products).
|
||||
joins("LEFT JOIN (
|
||||
SELECT DISTINCT ON(product_id) id, product_id, primary_taxon_id
|
||||
FROM spree_variants WHERE deleted_at IS NULL
|
||||
) first_variant ON spree_products.id = first_variant.product_id").
|
||||
select("spree_products.*, first_variant.primary_taxon_id").
|
||||
group("spree_products.id, first_variant.primary_taxon_id")
|
||||
def products_relation
|
||||
relation_by_sorting.order(Arel.sql(order))
|
||||
end
|
||||
|
||||
# Joins on the first product variant to allow us to filter product by supplier. This is so
|
||||
# enterprise can display product sorted by supplier in a custom order on their shopfront.
|
||||
#
|
||||
# Caveat, the supplier sorting won't work properly if there are multiple variant with different
|
||||
# supplier for a given product.
|
||||
#
|
||||
def products_supplier_relation
|
||||
Spree::Product.where(id: stocked_products).
|
||||
joins("LEFT JOIN (SELECT DISTINCT ON(product_id) id, product_id, supplier_id
|
||||
FROM spree_variants WHERE deleted_at IS NULL) first_variant
|
||||
ON spree_products.id = first_variant.product_id").
|
||||
select("spree_products.*, first_variant.supplier_id").
|
||||
group("spree_products.id, first_variant.supplier_id")
|
||||
end
|
||||
def products_relation_incl_supplier_properties
|
||||
query = relation_by_sorting
|
||||
|
||||
def supplier_property_join(query)
|
||||
query.joins("
|
||||
JOIN enterprises ON enterprises.id = first_variant.supplier_id
|
||||
LEFT OUTER JOIN producer_properties ON producer_properties.producer_id = enterprises.id
|
||||
")
|
||||
query = supplier_property_join(query)
|
||||
|
||||
query.order(Arel.sql(order))
|
||||
end
|
||||
|
||||
def variants_relation
|
||||
@@ -61,6 +34,81 @@ module OrderCycles
|
||||
|
||||
attr_reader :distributor, :order_cycle, :customer
|
||||
|
||||
def relation_by_sorting
|
||||
query = Spree::Product.where(id: stocked_products)
|
||||
|
||||
if sorting == "by_producer"
|
||||
# Joins on the first product variant to allow us to filter product by supplier. This is so
|
||||
# enterprise can display product sorted by supplier in a custom order on their shopfront.
|
||||
#
|
||||
# Caveat, the supplier sorting won't work properly if there are multiple variant with
|
||||
# different supplier for a given product.
|
||||
query.
|
||||
joins("LEFT JOIN (SELECT DISTINCT ON(product_id) id, product_id, supplier_id
|
||||
FROM spree_variants WHERE deleted_at IS NULL) first_variant
|
||||
ON spree_products.id = first_variant.product_id").
|
||||
select("spree_products.*, first_variant.supplier_id").
|
||||
group("spree_products.id, first_variant.supplier_id")
|
||||
elsif sorting == "by_category"
|
||||
# Joins on the first product variant to allow us to filter product by taxon. # This is so
|
||||
# enterprise can display product sorted by category in a custom order on their shopfront.
|
||||
#
|
||||
# Caveat, the category sorting won't work properly if there are multiple variant with
|
||||
# different category for a given product.
|
||||
query.
|
||||
joins("LEFT JOIN (
|
||||
SELECT DISTINCT ON(product_id) id, product_id, primary_taxon_id,
|
||||
supplier_id
|
||||
FROM spree_variants WHERE deleted_at IS NULL
|
||||
) first_variant ON spree_products.id = first_variant.product_id").
|
||||
select("spree_products.*, first_variant.primary_taxon_id").
|
||||
group("spree_products.id, first_variant.primary_taxon_id")
|
||||
else
|
||||
query.group("spree_products.id")
|
||||
end
|
||||
end
|
||||
|
||||
def sorting
|
||||
distributor.preferred_shopfront_product_sorting_method
|
||||
end
|
||||
|
||||
def sorting_by_producer?
|
||||
sorting == "by_producer" &&
|
||||
distributor.preferred_shopfront_producer_order.present?
|
||||
end
|
||||
|
||||
def sorting_by_category?
|
||||
sorting == "by_category" &&
|
||||
distributor.preferred_shopfront_taxon_order.present?
|
||||
end
|
||||
|
||||
def supplier_property_join(query)
|
||||
query.joins("
|
||||
JOIN enterprises ON enterprises.id = first_variant.supplier_id
|
||||
LEFT OUTER JOIN producer_properties ON producer_properties.producer_id = enterprises.id
|
||||
")
|
||||
end
|
||||
|
||||
def order
|
||||
if sorting_by_producer?
|
||||
order_by_producer = distributor
|
||||
.preferred_shopfront_producer_order
|
||||
.split(",").map { |id| "first_variant.supplier_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
|
||||
"#{order_by_producer}, spree_products.name ASC, spree_products.id ASC"
|
||||
elsif sorting_by_category?
|
||||
order_by_category = distributor
|
||||
.preferred_shopfront_taxon_order
|
||||
.split(",").map { |id| "first_variant.primary_taxon_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
|
||||
"#{order_by_category}, spree_products.name ASC, spree_products.id ASC"
|
||||
else
|
||||
"spree_products.name ASC, spree_products.id"
|
||||
end
|
||||
end
|
||||
|
||||
def stocked_products
|
||||
order_cycle.
|
||||
variants_distributed_by(distributor).
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
require 'open_food_network/scope_product_to_hub'
|
||||
|
||||
|
||||
class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
class ProductsRenderer
|
||||
include Pagy::Backend
|
||||
|
||||
class NoProducts < RuntimeError; end
|
||||
@@ -35,8 +35,11 @@ class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
return unless order_cycle
|
||||
|
||||
@products ||= begin
|
||||
results = products_relation.
|
||||
order(Arel.sql(products_order))
|
||||
results = if supplier_properties.present?
|
||||
distributed_products.products_relation_incl_supplier_properties
|
||||
else
|
||||
distributed_products.products_relation
|
||||
end
|
||||
|
||||
results = filter(results)
|
||||
# Scope results with variant_overrides
|
||||
@@ -52,38 +55,19 @@ class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
OpenFoodNetwork::EnterpriseFeeCalculator.new distributor, order_cycle
|
||||
end
|
||||
|
||||
# TODO refactor this, distributed_products should be able to give use the relation based
|
||||
# on the sorting method, same for ordering. It would prevent the SQL implementation from
|
||||
# leaking here
|
||||
def products_relation
|
||||
if distributor.preferred_shopfront_product_sorting_method == "by_category" &&
|
||||
distributor.preferred_shopfront_taxon_order.present?
|
||||
return distributed_products.products_taxons_relation
|
||||
end
|
||||
|
||||
distributed_products.products_supplier_relation
|
||||
end
|
||||
|
||||
# TODO: refactor to address CyclomaticComplexity
|
||||
def filter(query) # rubocop:disable Metrics/CyclomaticComplexity
|
||||
supplier_properties = args[:q]&.slice("with_variants_supplier_properties")
|
||||
|
||||
def filter(query)
|
||||
ransack_results = query.ransack(args[:q]).result.to_a
|
||||
|
||||
return ransack_results if supplier_properties.blank?
|
||||
|
||||
with_properties = args[:q]&.dig("with_properties")
|
||||
supplier_properties_results = []
|
||||
|
||||
if supplier_properties.present?
|
||||
# We can't search on an association's scope with ransack, a work around is to define
|
||||
# the a scope on the parent (Spree::Product) but because we are joining on "first_variant"
|
||||
# to get the supplier it doesn't work, so we do the filtering manually here
|
||||
# see:
|
||||
# OrderCycleDistributedProducts#products_supplier_relation
|
||||
# OrderCycleDistributedProducts#supplier_property_join
|
||||
supplier_property_ids = supplier_properties["with_variants_supplier_properties"]
|
||||
supplier_properties_results = distributed_products.supplier_property_join(query).
|
||||
# OrderCycleDistributedProducts#products_relation
|
||||
supplier_properties_results = query.
|
||||
where(producer_properties: { property_id: supplier_property_ids }).
|
||||
where(inherits_properties: true)
|
||||
end
|
||||
@@ -101,6 +85,18 @@ class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
ransack_results
|
||||
end
|
||||
|
||||
def supplier_properties
|
||||
args[:q]&.slice("with_variants_supplier_properties")
|
||||
end
|
||||
|
||||
def supplier_property_ids
|
||||
supplier_properties["with_variants_supplier_properties"]
|
||||
end
|
||||
|
||||
def with_properties
|
||||
args[:q]&.dig("with_properties")
|
||||
end
|
||||
|
||||
def paginate(results)
|
||||
_pagy, paginated_results = pagy_array(
|
||||
results,
|
||||
@@ -115,27 +111,6 @@ class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
OrderCycles::DistributedProductsService.new(distributor, order_cycle, customer)
|
||||
end
|
||||
|
||||
# TODO refactor, see above
|
||||
def products_order
|
||||
if distributor.preferred_shopfront_product_sorting_method == "by_producer" &&
|
||||
distributor.preferred_shopfront_producer_order.present?
|
||||
order_by_producer = distributor
|
||||
.preferred_shopfront_producer_order
|
||||
.split(",").map { |id| "first_variant.supplier_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
"#{order_by_producer}, spree_products.name ASC, spree_products.id ASC"
|
||||
elsif distributor.preferred_shopfront_product_sorting_method == "by_category" &&
|
||||
distributor.preferred_shopfront_taxon_order.present?
|
||||
order_by_category = distributor
|
||||
.preferred_shopfront_taxon_order
|
||||
.split(",").map { |id| "first_variant.primary_taxon_id=#{id} DESC" }
|
||||
.join(", ")
|
||||
"#{order_by_category}, spree_products.name ASC, spree_products.id ASC"
|
||||
else
|
||||
"spree_products.name ASC, spree_products.id"
|
||||
end
|
||||
end
|
||||
|
||||
def variants_for_shop
|
||||
@variants_for_shop ||= begin
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
|
||||
@@ -19,5 +19,7 @@ class ShopsListService
|
||||
.includes(address: [:state, :country])
|
||||
.includes(:properties)
|
||||
.includes(supplied_products: :properties)
|
||||
.with_attached_promo_image
|
||||
.with_attached_logo
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,9 +15,11 @@ class WeightsAndMeasures
|
||||
|
||||
def system
|
||||
return "custom" unless scales = scales_for_variant_unit(ignore_available_units: true)
|
||||
return "custom" unless product_scale = @variant.product.variant_unit_scale
|
||||
|
||||
scales[product_scale.to_f]['system']
|
||||
product_scale = @variant.product.variant_unit_scale&.to_f
|
||||
return "custom" unless product_scale.present? && product_scale.positive?
|
||||
|
||||
scales[product_scale]['system']
|
||||
end
|
||||
|
||||
# @returns enumerable with label and value for select
|
||||
@@ -25,7 +27,7 @@ class WeightsAndMeasures
|
||||
available_units_sorted.flat_map do |measurement, measurement_info|
|
||||
measurement_info.filter_map do |scale, unit_info|
|
||||
scale_clean =
|
||||
ActiveSupport::NumberHelper.number_to_rounded(scale, precision: nil,
|
||||
ActiveSupport::NumberHelper.number_to_rounded(scale, precision: nil, significant: false,
|
||||
strip_insignificant_zeros: true)
|
||||
[
|
||||
"#{I18n.t(measurement)} (#{unit_info['name']})", # Label (eg "Weight (g)")
|
||||
|
||||
25
app/views/admin/connected_app_settings/edit.html.haml
Normal file
25
app/views/admin/connected_app_settings/edit.html.haml
Normal file
@@ -0,0 +1,25 @@
|
||||
= render :partial => 'spree/admin/shared/configuration_menu'
|
||||
|
||||
- content_for :page_title do
|
||||
= t('.title')
|
||||
|
||||
= form_tag main_app.admin_connected_app_settings_path, :method => :put do
|
||||
|
||||
%fieldset
|
||||
%legend= t('.enabled_legend')
|
||||
|
||||
= t('.info_html')
|
||||
|
||||
.field
|
||||
-# Blank value in case nothing is selected
|
||||
= hidden_field_tag("preferences[connected_apps_enabled][]", "")
|
||||
|
||||
- ConnectedApp::TYPES.each do |type|
|
||||
%label
|
||||
= check_box_tag("preferences[connected_apps_enabled][]", type,
|
||||
Spree::Config.connected_apps_enabled&.split(',')&.include?(type))
|
||||
= t('.connected_apps_enabled.' + type)
|
||||
%br
|
||||
|
||||
.form-buttons
|
||||
= button t(:update), 'icon-refresh'
|
||||
@@ -1,30 +1,3 @@
|
||||
%div{ id: "connected-app-discover-regen", class: "enterprise_#{enterprise.id}" }
|
||||
.connected-app__head
|
||||
%div
|
||||
%h3= t ".title"
|
||||
%p= t ".tagline"
|
||||
%div
|
||||
- if enterprise.connected_apps.empty?
|
||||
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id), method: :post, disabled: !managed_by_user?(enterprise)
|
||||
-# This is only seen by super-admins:
|
||||
%em= t(".need_to_be_manager") unless managed_by_user?(enterprise)
|
||||
- elsif enterprise.connected_apps.connecting.present?
|
||||
%button{ disabled: true }
|
||||
%i.spinner.fa.fa-spin.fa-circle-o-notch
|
||||
|
||||
= t ".loading"
|
||||
- else
|
||||
= button_to t(".disable"), admin_enterprise_connected_app_path(0, enterprise_id: enterprise.id), method: :delete
|
||||
|
||||
.connected-app__connection
|
||||
- if enterprise.connected_apps.ready.present?
|
||||
.connected-app__note
|
||||
- link = enterprise.connected_apps[0].data["link"]
|
||||
%p= t ".note"
|
||||
%div
|
||||
%a{ href: link, target: "_blank", class: "button secondary" }
|
||||
= t ".link_label"
|
||||
|
||||
%hr
|
||||
.connected-app__description
|
||||
= t ".description_html"
|
||||
- connected_apps_enabled.each do |type|
|
||||
= render partial: "/admin/enterprises/form/connected_apps/#{type}",
|
||||
locals: { enterprise:, connected_app: enterprise.connected_apps.public_send(type).first }
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
%section.connected_app
|
||||
.connected-app__head
|
||||
%div
|
||||
%h3= t ".title"
|
||||
%p= t ".tagline"
|
||||
%div
|
||||
- if connected_app.nil?
|
||||
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id, type: "ConnectedApps::AffiliateSalesData"), method: :post, disabled: !managed_by_user?(enterprise)
|
||||
-# This is only seen by super-admins:
|
||||
%em= t(".need_to_be_manager") unless managed_by_user?(enterprise)
|
||||
- else
|
||||
= button_to t(".disable"), admin_enterprise_connected_app_path(connected_app.id, enterprise_id: enterprise.id), method: :delete
|
||||
%hr
|
||||
.connected-app__description
|
||||
= t ".description_html"
|
||||
@@ -0,0 +1,30 @@
|
||||
%section.connected_app{ id: "connected-app-discover-regen", class: "enterprise_#{enterprise.id}" }
|
||||
.connected-app__head
|
||||
%div
|
||||
%h3= t ".title"
|
||||
%p= t ".tagline"
|
||||
%div
|
||||
- if connected_app.nil?
|
||||
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id), method: :post, disabled: !managed_by_user?(enterprise)
|
||||
-# This is only seen by super-admins:
|
||||
%em= t(".need_to_be_manager") unless managed_by_user?(enterprise)
|
||||
- elsif connected_app&.connecting?
|
||||
%button{ disabled: true }
|
||||
%i.spinner.fa.fa-spin.fa-circle-o-notch
|
||||
|
||||
= t ".loading"
|
||||
- else
|
||||
= button_to t(".disable"), admin_enterprise_connected_app_path(connected_app.id, enterprise_id: enterprise.id), method: :delete
|
||||
|
||||
.connected-app__connection
|
||||
- if connected_app&.ready?
|
||||
.connected-app__note
|
||||
- link = connected_app.data["link"]
|
||||
%p= t ".note"
|
||||
%div
|
||||
%a{ href: link, target: "_blank", class: "button secondary" }
|
||||
= t ".link_label"
|
||||
|
||||
%hr
|
||||
.connected-app__description
|
||||
= t ".description_html"
|
||||
@@ -0,0 +1,14 @@
|
||||
.flex-column-gap-1
|
||||
%h6
|
||||
= t('.title')
|
||||
%div{ style: 'font-size: 1rem;' }
|
||||
= t('.content')
|
||||
%p.modal-actions.justify-end.gap-1
|
||||
%button.button.secondary{ "ng-click": "submit($event, null)", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'save' } }
|
||||
= t('.proceed')
|
||||
%button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndNext' } }
|
||||
= t('.proceed')
|
||||
%button.button.secondary{ "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", type: "button", style: "display: none;", data: { action: 'click->modal#close', 'trigger-action': 'saveAndBack' } }
|
||||
= t('.proceed')
|
||||
%button.button.primary{ type: "button", 'data-action': 'click->modal#close' }
|
||||
= t('.cancel')
|
||||
@@ -16,18 +16,25 @@
|
||||
|
||||
- ng_controller = @order_cycle.simple? ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl'
|
||||
= admin_inject_order_cycle_instance(@order_cycle)
|
||||
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f|
|
||||
%div{ data: { controller: 'order-cycle-form' } }
|
||||
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f|
|
||||
|
||||
%save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
|
||||
%input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'save' } }
|
||||
- if @order_cycle.simple?
|
||||
%input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndBack' } }
|
||||
- else
|
||||
%input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid", data: { confirm: "true", 'trigger-action': 'saveAndNext' } }
|
||||
%input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" }
|
||||
%input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" }
|
||||
|
||||
%save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
|
||||
%input.red{ type: "button", value: t('.save'), "ng-click": "submit($event, null)", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" }
|
||||
- if @order_cycle.simple?
|
||||
%input.red{ type: "button", value: t('.save_and_back_to_list'), "ng-click": "submit($event, '#{main_app.admin_order_cycles_path}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" }
|
||||
= render 'simple_form', f: f
|
||||
- else
|
||||
%input.red{ type: "button", value: t('.save_and_next'), "ng-click": "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "!order_cycle_form.$dirty || order_cycle_form.$invalid" }
|
||||
%input{ type: "button", value: t('.next'), "ng-click": "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", "ng-disabled": "order_cycle_form.$dirty" }
|
||||
%input{ type: "button", "ng-value": "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", "ng-click": "cancel('#{main_app.admin_order_cycles_path}')" }
|
||||
= render 'form', f: f
|
||||
|
||||
- if @order_cycle.simple?
|
||||
= render 'simple_form', f: f
|
||||
- else
|
||||
= render 'form', f: f
|
||||
%div.warning-modal{ data: { controller: 'modal modal-link', 'modal-link-target-value': "linked-order-warning-modal" } }
|
||||
%button.modal-target-trigger{ type: 'button', data: { 'action': 'modal-link#open' }, style: 'display: none;' }
|
||||
= render ModalComponent.new(id: "linked-order-warning-modal", close_button: false) do
|
||||
.content.flex-column.gap-2
|
||||
= render 'date_time_warning_modal_content'
|
||||
@@ -1,6 +1,6 @@
|
||||
= form_with url: admin_products_path, id: "filters", method: :get, data: { "search-target": "form", 'turbo-frame': "_self" } do
|
||||
= form_with url: admin_products_path, id: "filters", method: :get, data: { "search-target": "form", 'turbo-frame': "_self", 'turbo-action': "advance" } do
|
||||
= hidden_field_tag :page, nil, class: "page"
|
||||
= hidden_field_tag :per_page, nil, class: "per-page"
|
||||
= hidden_field_tag :per_page, params[:per_page], class: "per-page"
|
||||
= hidden_field_tag '[q][s]', params.dig(:q, :s) || 'name asc', class: 'sort', 'data-default': 'name asc'
|
||||
|
||||
.query
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- if search_term.present? || producer_id.present? || category_id.present?
|
||||
= t('.no_products_found_for_search')
|
||||
%a{ href: "#", class: "button disruptive relaxed", data: { reflex: "click->products#clear_search" } }
|
||||
%a{ href: admin_products_path, class: "button disruptive relaxed", 'data-turbo-frame': "_self" }
|
||||
= t("admin.products_v3.sort.pagination.clear_search")
|
||||
- else
|
||||
= t('.no_products_found')
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
%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: }
|
||||
|
||||
= form.fields_for("products][#{product_index}][variants_attributes][NEW_RECORD", product.variants.build) do |new_variant_form|
|
||||
= form.fields_for("products][#{product_index}][variants_attributes][NEW_RECORD", prepare_new_variant(product)) do |new_variant_form|
|
||||
%template{ 'data-nested-form-target': "template" }
|
||||
%tr.condensed{ 'data-controller': "variant", 'class': "nested-form-wrapper", 'data-new-record': "true" }
|
||||
= render partial: 'variant_row', locals: { variant: new_variant_form.object, f: new_variant_form, category_options:, tax_category_options:, producer_options: }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
= t(".pagination.total_html", total: pagy.count, from: pagy.from, to: pagy.to)
|
||||
|
||||
- if search_term.present? || producer_id.present? || category_id.present?
|
||||
%a{ href: url_for(page: 1), class: "button disruptive", 'data-turbo-frame': "_self" }
|
||||
%a{ href: url_for(page: 1), class: "button disruptive", data: { 'turbo-frame': "_self", 'turbo-action': "advance" } }
|
||||
= t(".pagination.clear_search")
|
||||
|
||||
%form.with-dropdown
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
-# Show a composite field for unit_value and unit_description
|
||||
= f.hidden_field :unit_value
|
||||
= f.hidden_field :unit_description
|
||||
-# todo: create a method for value_with_description
|
||||
= f.text_field :unit_value_with_description,
|
||||
value: [number_with_precision((variant.unit_value || 1) / (variant.product.variant_unit_scale || 1), precision: nil, strip_insignificant_zeros: true), variant.unit_description].compact_blank.join(" "),
|
||||
'aria-label': t('admin.products_page.columns.unit_value'), required: true
|
||||
value: unit_value_with_description(variant), 'aria-label': t('admin.products_page.columns.unit_value')
|
||||
.field
|
||||
= f.label :display_as, t('admin.products_page.columns.display_as')
|
||||
= f.text_field :display_as, placeholder: VariantUnits::OptionValueNamer.new(variant).name
|
||||
@@ -39,20 +37,24 @@
|
||||
= f.label :on_demand do
|
||||
= f.check_box :on_demand, 'data-action': 'change->toggle-control#disableIfPresent change->popout#closeIfChecked'
|
||||
= t(:on_demand)
|
||||
%td.col-producer.naked_inputs
|
||||
%td.col-producer.field.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :supplier_id,
|
||||
aria_label: t('.producer_field_name'),
|
||||
options: producer_options,
|
||||
selected_option: variant.supplier_id,
|
||||
placeholder_value: t('admin.products_v3.filters.search_for_producers')))
|
||||
include_blank: t('admin.products_v3.filters.select_producer'),
|
||||
placeholder_value: t('admin.products_v3.filters.select_producer')))
|
||||
= error_message_on variant, :supplier
|
||||
%td.col-category.field.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :primary_taxon_id,
|
||||
options: category_options,
|
||||
selected_option: variant.primary_taxon_id,
|
||||
aria_label: t('.category_field_name'),
|
||||
placeholder_value: t('admin.products_v3.filters.search_for_categories')))
|
||||
include_blank: t('admin.products_v3.filters.select_category'),
|
||||
placeholder_value: t('admin.products_v3.filters.select_category')))
|
||||
= error_message_on variant, :primary_taxon
|
||||
%td.col-tax_category.field.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :tax_category_id,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
tax_category_options: @tax_category_options,
|
||||
should_slide_in: true },
|
||||
formats: :html)
|
||||
= turbo_stream.before dom_id(@product) do
|
||||
= turbo_stream.after dom_id(@product) do
|
||||
= product_body
|
||||
|
||||
= turbo_stream.append "flashes" do
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
%th.add=t('admin.variant_overrides.index.add')
|
||||
%tbody{ "ng-repeat": 'product in filteredProducts | limitTo:productLimit' }
|
||||
%tr{ id: "v_{{variant.id}}", "ng-repeat": 'variant in product.variants | inventoryVariants:hub_id:views' }
|
||||
%td.producer{ "ng-bind": '::producersByID[product.producer_id].name' }
|
||||
%td.producer{ "ng-bind": '::producersByID[variant.producer_id].name' }
|
||||
%td.product{ "ng-bind": '::product.name' }
|
||||
%td.variant
|
||||
%span{ "ng-bind": '::variant.display_name || ""' }
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
%th.hide=t('admin.variant_overrides.index.hide')
|
||||
%tbody{ "ng-repeat": 'product in filteredProducts | limitTo:productLimit' }
|
||||
%tr{ id: "v_{{variant.id}}", "ng-repeat": 'variant in product.variants | inventoryVariants:hub_id:views' }
|
||||
%td.producer{ "ng-bind-html": '::producersByID[product.producer_id].name' }
|
||||
%td.producer{ "ng-bind-html": '::producersByID[variant.producer_id].name' }
|
||||
%td.product{ "ng-bind": '::product.name' }
|
||||
%td.variant
|
||||
%span{ "ng-bind": '::variant.display_name || ""' }
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
%tr
|
||||
%th{:align => "left"}
|
||||
%h5= t(:invoice_column_item)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%th{:align => "right", :width => "6%" }
|
||||
%h5= t(:invoice_column_qty)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h5= t(:invoice_column_weight_volume)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%h5= t(:invoice_column_price_per_unit_without_taxes)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%th{:align => "right", :width => "12%"}
|
||||
%h5= t(:invoice_column_price_without_taxes)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%th{:align => "right", :width => "9%" }
|
||||
%h5= t(:invoice_column_tax_rate)
|
||||
%th{:align => "right", :width => "15%"}
|
||||
%th{:align => "right", :width => "12%" }
|
||||
%h5= t(:invoice_column_price_with_taxes)
|
||||
%tbody
|
||||
- @order.sorted_line_items.each do |item|
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
= admin_inject_available_units
|
||||
|
||||
- content_for :page_actions do
|
||||
%li= button_link_to t('admin.products.back_to_products_list'), "#{admin_products_path}#{(@url_filters.empty? ? "" : "#?#{@url_filters.to_query}")}", :icon => 'icon-arrow-left'
|
||||
%li= button_link_to t('admin.products.back_to_products_list'), products_return_to_url(@url_filters), :icon => 'icon-arrow-left'
|
||||
%li#new_product_link
|
||||
= button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' }
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
.form-buttons.filter-actions.actions
|
||||
= button t(:update), 'icon-refresh'
|
||||
|
||||
= button_link_to t(:cancel), "#{collection_url}#{(@url_filters.empty? ? "" : "#?#{@url_filters.to_query}")}", icon: 'icon-remove'
|
||||
= button_link_to t(:cancel), products_return_to_url(@url_filters), icon: 'icon-remove'
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
= f.label :supplier, t(".supplier")
|
||||
%span.required *
|
||||
= f.select :supplier_id, options_from_collection_for_select(@producers, :id, :name, @product.supplier_id), { include_blank: t("spree.admin.products.new.supplier_select_placeholder") }, { "data-controller": "tom-select", class: "primary" }
|
||||
= f.error_message_on :supplier
|
||||
= f.error_message_on :supplier_id
|
||||
.eight.columns.omega
|
||||
= f.field_container :name do
|
||||
= f.label :name, t(".product_name")
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
- if DefaultCountry.id
|
||||
= configurations_sidebar_menu_item Spree.t(:states), admin_country_states_path(DefaultCountry.id)
|
||||
= configurations_sidebar_menu_item Spree.t(:payment_methods), admin_payment_methods_path
|
||||
= configurations_sidebar_menu_item Spree.t(:taxonomies), admin_taxonomies_path
|
||||
= configurations_sidebar_menu_item Spree.t(:taxons), admin_taxons_path
|
||||
= configurations_sidebar_menu_item Spree.t(:shipping_methods), admin_shipping_methods_path
|
||||
= configurations_sidebar_menu_item Spree.t(:shipping_categories), admin_shipping_categories_path
|
||||
= configurations_sidebar_menu_item t(:enterprise_fees), main_app.admin_enterprise_fees_path
|
||||
@@ -23,3 +23,4 @@
|
||||
= configurations_sidebar_menu_item t('admin.invoice_settings.edit.title'), main_app.edit_admin_invoice_settings_path
|
||||
= configurations_sidebar_menu_item t('admin.matomo_settings.edit.title'), main_app.edit_admin_matomo_settings_path
|
||||
= configurations_sidebar_menu_item t('admin.stripe_connect_settings.edit.title'), main_app.edit_admin_stripe_connect_settings_path
|
||||
= configurations_sidebar_menu_item t('admin.connected_app_settings.edit.title'), main_app.edit_admin_connected_app_settings_path
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}
|
||||
%meta{ name: "turbo-cache-control", content: "no-cache" }
|
||||
|
||||
= csrf_meta_tags
|
||||
= action_cable_meta_tag
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
= tab :order_cycles, url: main_app.admin_order_cycles_path, icon: 'icon-refresh'
|
||||
= tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path, icon: 'icon-shopping-cart'
|
||||
= tab :reports, url: main_app.admin_reports_path, icon: 'icon-file'
|
||||
= tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxonomies, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path
|
||||
= tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxons, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path
|
||||
= tab :enterprises, :enterprise_relationships, :vouchers, :oidc_settings, url: main_app.admin_enterprises_path
|
||||
= tab :customers, url: main_app.admin_customers_path
|
||||
= tab :enterprise_groups, url: main_app.admin_enterprise_groups_path, label: 'groups'
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.field.align-center
|
||||
= f.field_container :name do
|
||||
= f.label :name, t("spree.name")
|
||||
%span.required *
|
||||
%br/
|
||||
= error_message_on :taxonomy, :name
|
||||
= text_field :taxonomy, :name
|
||||
@@ -1,13 +0,0 @@
|
||||
<% content_for :head do %>
|
||||
<%= javascript_tag "var taxonomy_id = #{@taxonomy.id};
|
||||
var loading = '#{escape_javascript t("spree.loading")}';
|
||||
var new_taxon = '#{escape_javascript t("spree.new_taxon")}';
|
||||
var server_error = '#{escape_javascript t("spree.server_error")}';
|
||||
var taxonomy_tree_error = '#{escape_javascript t("spree.taxonomy_tree_error")}';
|
||||
|
||||
$(document).ready(function(){
|
||||
setup_taxonomy_tree(taxonomy_id);
|
||||
});
|
||||
"
|
||||
%>
|
||||
<% end %>
|
||||
@@ -1,19 +0,0 @@
|
||||
%table#listing_taxonomies.index.sortable{"data-sortable-link" => update_positions_admin_taxonomies_url}
|
||||
%colgroup
|
||||
%col{style: "width: 85%"}/
|
||||
%col{style: "width: 15%"}/
|
||||
%thead
|
||||
%tr
|
||||
%th= t("spree.name")
|
||||
%th.actions
|
||||
%tbody
|
||||
- @taxonomies.each do |taxonomy|
|
||||
- tr_class = cycle('odd', 'even')
|
||||
- tr_id = spree_dom_id(taxonomy)
|
||||
%tr{class: tr_class, id: tr_id}
|
||||
%td
|
||||
%span.handle
|
||||
= taxonomy.name
|
||||
%td.actions
|
||||
= link_to_edit taxonomy.id, no_text: true
|
||||
= link_to_delete taxonomy, no_text: true
|
||||
@@ -1,7 +0,0 @@
|
||||
- if taxon.children.length != 0
|
||||
%ul
|
||||
- taxon.children.each do |child|
|
||||
%li{id: "#{child.id}", rel: "taxon"}
|
||||
%a{href: "#"}= child.name
|
||||
- if child.children.length > 0
|
||||
= render partial: 'taxon', locals: { taxon: child }
|
||||
@@ -1,27 +0,0 @@
|
||||
= render partial: 'spree/admin/shared/configuration_menu'
|
||||
|
||||
= render partial: 'js_head'
|
||||
|
||||
- content_for :page_title do
|
||||
= t("spree.taxonomy_edit")
|
||||
|
||||
- content_for :page_actions do
|
||||
%li
|
||||
= button_link_to t("spree.back_to_taxonomies_list"), spree.admin_taxonomies_path, icon: 'icon-arrow-left'
|
||||
|
||||
#ajax_error.errorExplanation{style: "display:none;"}
|
||||
= form_for [:admin, @taxonomy] do |f|
|
||||
%fieldset.no-border-top
|
||||
= render partial: 'form', locals: { f: f }
|
||||
%div
|
||||
= label_tag nil, t("spree.tree")
|
||||
%br/
|
||||
:javascript
|
||||
Spree.routes.taxonomy_taxons = "#{main_app.api_v0_taxonomy_taxons_url(@taxonomy)}";
|
||||
Spree.routes.admin_taxonomy_taxons = "#{spree.admin_taxonomy_taxons_url(@taxonomy)}";
|
||||
#taxonomy_tree.tree
|
||||
.info= t("spree.taxonomy_tree_instruction")
|
||||
%br/
|
||||
.filter-actions.actions
|
||||
= button t('spree.actions.update'), 'icon-refresh'
|
||||
= button_link_to t('spree.actions.cancel'), admin_taxonomies_path, icon: 'icon-remove'
|
||||
@@ -1,11 +0,0 @@
|
||||
= render partial: 'spree/admin/shared/configuration_menu'
|
||||
|
||||
- content_for :page_title do
|
||||
= t("spree.taxonomies")
|
||||
|
||||
- content_for :page_actions do
|
||||
%li
|
||||
= button_link_to t("spree.new_taxonomy"), spree.new_admin_taxonomy_url, icon: 'icon-plus', id: 'admin_new_taxonomy_link'
|
||||
|
||||
#list-taxonomies
|
||||
= render partial: 'list'
|
||||
@@ -1,15 +0,0 @@
|
||||
= render partial: 'spree/admin/shared/configuration_menu'
|
||||
|
||||
- content_for :page_title do
|
||||
= t("spree.new_taxonomy")
|
||||
|
||||
- content_for :page_actions do
|
||||
%li
|
||||
= button_link_to t("spree.back_to_taxonomies_list"), spree.admin_taxonomies_path, icon: 'icon-arrow-left'
|
||||
|
||||
= form_for [:admin, @taxonomy] do |f|
|
||||
= render partial: 'form', locals: { f: f }
|
||||
%fieldset.no-border-top
|
||||
%br/
|
||||
.filter-actions.actions
|
||||
= button t("spree.create"), 'icon-ok'
|
||||
@@ -6,12 +6,6 @@
|
||||
%br/
|
||||
= error_message_on :taxon, :name
|
||||
= text_field :taxon, :name, class: 'fullwidth'
|
||||
= f.field_container :permalink_part do
|
||||
= f.label :permalink_part, t(".permalink")
|
||||
%span.required *
|
||||
%br/
|
||||
= @taxon.permalink.split("/")[0...-1].join("/") + "/"
|
||||
= text_field_tag :permalink_part, @permalink_part
|
||||
= f.field_container :meta_title do
|
||||
= f.label :meta_title, t(".meta_title")
|
||||
%br/
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
- unless flash[:error]
|
||||
= turbo_stream.remove(spree_dom_id(@taxon))
|
||||
= turbo_stream.append "flashes" do
|
||||
= render(partial: 'admin/shared/flashes', locals: { flashes: flash })
|
||||
@@ -1,16 +1,16 @@
|
||||
= render partial: 'spree/admin/shared/configuration_menu'
|
||||
|
||||
- content_for :page_title do
|
||||
= t("spree.taxonomy_edit")
|
||||
= t(".title")
|
||||
|
||||
- content_for :page_actions do
|
||||
%li
|
||||
= button_link_to t("spree.back_to_taxonomies_list"), spree.admin_taxonomies_path, icon: 'icon-arrow-left'
|
||||
= button_link_to t("spree.admin.taxons.back_to_list"), admin_taxons_path, icon: 'icon-arrow-left'
|
||||
|
||||
- # Because otherwise the form would attempt to use to_param of @taxon
|
||||
- form_url = admin_taxonomy_taxon_path(@taxonomy.id, @taxon.id)
|
||||
= form_for [:admin, @taxonomy, @taxon], method: :put, url: form_url, html: { multipart: true } do |f|
|
||||
= form_with model: @taxon, url: admin_taxon_path(@taxon.id),
|
||||
data: { turbo: true }, id: "edit-taxon-#{@taxon.id}",
|
||||
method: :put, html: { multipart: true } do |f|
|
||||
= render partial: 'form', locals: { f: f }
|
||||
.form-buttons
|
||||
= button t('spree.actions.update'), 'icon-refresh'
|
||||
= button_link_to t('spree.actions.cancel'), edit_admin_taxonomy_url(@taxonomy), icon: "icon-remove"
|
||||
= button_link_to t('spree.actions.cancel'), admin_taxons_url, icon: "icon-remove"
|
||||
|
||||
27
app/views/spree/admin/taxons/index.html.haml
Normal file
27
app/views/spree/admin/taxons/index.html.haml
Normal file
@@ -0,0 +1,27 @@
|
||||
= render partial: 'spree/admin/shared/configuration_menu'
|
||||
|
||||
- content_for :page_title do
|
||||
= t(".title")
|
||||
|
||||
- content_for :page_actions do
|
||||
%li
|
||||
= button_link_to t(".new_taxon"), spree.new_admin_taxon_url, icon: 'icon-plus', id: 'admin_new_taxon_link'
|
||||
|
||||
%table#listing_taxons.index
|
||||
%colgroup
|
||||
%col{style: "width: 85%"}/
|
||||
%col{style: "width: 15%"}/
|
||||
%thead
|
||||
%tr
|
||||
%th= t("spree.name")
|
||||
%th.actions
|
||||
%tbody
|
||||
- @taxons.each do |taxon|
|
||||
%tr{class: cycle('odd', 'even'), id: spree_dom_id(taxon)}
|
||||
%td
|
||||
= taxon.name
|
||||
%td.actions
|
||||
= link_to_edit taxon.id, no_text: true
|
||||
= link_to '', admin_taxon_path(taxon.id), method: :delete,
|
||||
class: "icon_link with-tip icon-trash no-text",
|
||||
data: { turbo: true, turbo_method: :delete, turbo_confirm: t(:are_you_sure) }
|
||||
14
app/views/spree/admin/taxons/new.html.haml
Normal file
14
app/views/spree/admin/taxons/new.html.haml
Normal file
@@ -0,0 +1,14 @@
|
||||
= render partial: 'spree/admin/shared/configuration_menu'
|
||||
|
||||
- content_for :page_title do
|
||||
= t(".title")
|
||||
|
||||
- content_for :page_actions do
|
||||
%li
|
||||
= button_link_to t("spree.admin.taxons.back_to_list"), spree.admin_taxons_path, icon: 'icon-arrow-left'
|
||||
|
||||
= form_with model: @taxon, url: admin_taxons_path, data: { turbo: true }, html: { multipart: true } do |f|
|
||||
= render partial: 'form', locals: { f: f }
|
||||
.form-buttons
|
||||
= button t('actions.create'), 'icon-ok'
|
||||
= button_link_to t('actions.cancel'), admin_taxons_url, icon: "icon-remove"
|
||||
@@ -9,8 +9,8 @@
|
||||
%br
|
||||
= @order.distributor.phone || ""
|
||||
%br
|
||||
%a{:href => "mailto:#{@order.distributor.contact.email}", :target => "_blank"}
|
||||
= @order.distributor.contact.email
|
||||
%a{:href => "mailto:#{order_reply_email(@order)}", :target => "_blank"}
|
||||
= order_reply_email(@order)
|
||||
%br
|
||||
= @order.distributor.website || ""
|
||||
%br
|
||||
|
||||
@@ -12,6 +12,9 @@ export const useOpenAndCloseAsAModal = (controller) => {
|
||||
}.bind(controller),
|
||||
|
||||
close: function (_event, remove = false) {
|
||||
// Only execute close if there is an open modal
|
||||
if (!document.querySelector("body").classList.contains('modal-open')) return;
|
||||
|
||||
this.modalTarget.classList.remove("in");
|
||||
this.backgroundTarget.classList.remove("in");
|
||||
document.querySelector("body").classList.remove("modal-open");
|
||||
|
||||
44
app/webpacker/controllers/order_cycle_form_controller.js
Normal file
44
app/webpacker/controllers/order_cycle_form_controller.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['statusMessage']
|
||||
connect() {
|
||||
this.observer = new MutationObserver(this.updateCallback);
|
||||
this.observer.observe(
|
||||
this.statusMessageTarget,
|
||||
{ attributes: true, attributeOldValue: true, attributeFilter: ['data-type'] }
|
||||
);
|
||||
}
|
||||
|
||||
// Callback to trigger warning modal
|
||||
updateCallback(mutationsList) {
|
||||
const newDataType = document.getElementById('status-message').getAttribute('data-type');
|
||||
const actionName = document.getElementById('status-message').getAttribute('data-action-name');
|
||||
if(!actionName) return;
|
||||
|
||||
for(let mutation of mutationsList) {
|
||||
if (mutation.type === 'attributes' && mutation.attributeName === 'data-type') {
|
||||
// Only trigger warning modal when notice display (notice) is preceeded by progress display (progress)
|
||||
if(mutation.oldValue === 'progress' && newDataType === 'notice') {
|
||||
// Hide all confirmation buttons in warning modal
|
||||
document.querySelectorAll(
|
||||
'#linked-order-warning-modal .modal-actions button.secondary'
|
||||
).forEach((node) => {
|
||||
node.style.display = 'none';
|
||||
});
|
||||
// Show the appropriate confirmation button, open warning modal, and return
|
||||
document.querySelectorAll(
|
||||
`#linked-order-warning-modal button[data-trigger-action=${actionName}]`
|
||||
).forEach((node) => {
|
||||
node.style.display = 'block';
|
||||
})
|
||||
document.querySelector('.warning-modal button.modal-target-trigger').click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
const BUTTON_TYPES = ['submit', 'button'];
|
||||
|
||||
// Toggle state of a control based on a condition.
|
||||
//
|
||||
// 1. When an action occurs on an element,
|
||||
@@ -57,10 +59,8 @@ export default class extends Controller {
|
||||
target.disabled = disable;
|
||||
});
|
||||
|
||||
// Focus first when enabled
|
||||
if (!disable) {
|
||||
this.controlTargets[0].focus();
|
||||
}
|
||||
// Focus first when enabled and it's not a button
|
||||
if (!disable) this.#focusFieldControl();
|
||||
}
|
||||
|
||||
#toggleDisplay(show) {
|
||||
@@ -69,9 +69,7 @@ export default class extends Controller {
|
||||
});
|
||||
|
||||
// Focus first when displayed
|
||||
if (show) {
|
||||
this.controlTargets[0].focus();
|
||||
}
|
||||
if (show) this.#focusFieldControl();
|
||||
}
|
||||
|
||||
// Return input's value, but only if it would be submitted by a form
|
||||
@@ -81,4 +79,10 @@ export default class extends Controller {
|
||||
return input.value;
|
||||
}
|
||||
}
|
||||
|
||||
#focusFieldControl() {
|
||||
const control = this.controlTargets[0];
|
||||
const isButton = BUTTON_TYPES.includes(control.type);
|
||||
if(!isButton) control.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
|
||||
@import "plugins/flatpickr-customization";
|
||||
@import "plugins/powertip";
|
||||
@import "plugins/jstree";
|
||||
@import "plugins/select2";
|
||||
|
||||
@import "sections/orders";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user