mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-20 19:56:48 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac7a36416b |
14
.github/workflows/auto-author-assign.yml
vendored
14
.github/workflows/auto-author-assign.yml
vendored
@@ -1,14 +0,0 @@
|
||||
name: Auto Author Assign
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
assign-author:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: toshimaru/auto-author-assign@v2.1.0
|
||||
23
.github/workflows/build.yml
vendored
23
.github/workflows/build.yml
vendored
@@ -59,7 +59,6 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: yarn
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
@@ -186,7 +185,6 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: yarn
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
@@ -216,9 +214,9 @@ jobs:
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: failed-admin-tests-screenshots
|
||||
name: failed-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
@@ -264,7 +262,6 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: yarn
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
@@ -294,9 +291,9 @@ jobs:
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: failed-consumer-tests-screenshots
|
||||
name: failed-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
@@ -343,7 +340,6 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: yarn
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
@@ -371,6 +367,15 @@ 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:
|
||||
@@ -413,7 +418,6 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: yarn
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
@@ -466,7 +470,6 @@ jobs:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: yarn
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
@@ -115,11 +115,6 @@ Rails/OutputSafety:
|
||||
Exclude:
|
||||
- spec/**/*
|
||||
|
||||
Rails/RedundantActiveRecordAllMethod:
|
||||
AllowedReceivers:
|
||||
- ActionMailer::Preview
|
||||
- ActiveSupport::TimeZone
|
||||
|
||||
Rails/SkipsModelValidations:
|
||||
AllowedMethods:
|
||||
- touch
|
||||
@@ -129,13 +124,6 @@ Rails/SkipsModelValidations:
|
||||
- update_column
|
||||
- update_columns
|
||||
|
||||
Rails/UnknownEnv:
|
||||
Environments:
|
||||
- development
|
||||
- production
|
||||
- staging
|
||||
- test
|
||||
|
||||
Rails/WhereExists:
|
||||
EnforcedStyle: where # Cf. conversion https://github.com/openfoodfoundation/openfoodnetwork/pull/12363
|
||||
|
||||
|
||||
@@ -6,12 +6,35 @@
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines.
|
||||
Layout/EmptyLineBetweenDefs:
|
||||
Exclude:
|
||||
- 'app/services/products_renderer.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
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:
|
||||
@@ -127,7 +150,7 @@ Metrics/BlockNesting:
|
||||
Exclude:
|
||||
- 'app/models/spree/payment/processing.rb'
|
||||
|
||||
# Offense count: 46
|
||||
# Offense count: 47
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ClassLength:
|
||||
Exclude:
|
||||
@@ -163,6 +186,7 @@ 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'
|
||||
@@ -229,7 +253,7 @@ Metrics/MethodLength:
|
||||
- 'lib/spree/localized_number.rb'
|
||||
- 'lib/tasks/sample_data/product_factory.rb'
|
||||
|
||||
# Offense count: 49
|
||||
# Offense count: 48
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ModuleLength:
|
||||
Exclude:
|
||||
@@ -259,7 +283,6 @@ Metrics/ModuleLength:
|
||||
- 'spec/controllers/payment_gateways/stripe_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/adjustments_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/payment_methods_controller_spec.rb'
|
||||
- 'spec/controllers/spree/admin/variants_controller_spec.rb'
|
||||
- 'spec/lib/open_food_network/address_finder_spec.rb'
|
||||
- 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb'
|
||||
- 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb'
|
||||
@@ -392,6 +415,7 @@ 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'
|
||||
@@ -601,6 +625,52 @@ Rails/LexicallyScopedActionFilter:
|
||||
- 'app/controllers/spree/admin/zones_controller.rb'
|
||||
- 'app/controllers/spree/users_controller.rb'
|
||||
|
||||
# Offense count: 32
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/Pluck:
|
||||
Exclude:
|
||||
- 'app/controllers/admin/variant_overrides_controller.rb'
|
||||
- 'app/services/cart_service.rb'
|
||||
- 'lib/reporting/report_headers_builder.rb'
|
||||
- 'spec/controllers/admin/bulk_line_items_controller_spec.rb'
|
||||
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
|
||||
- 'spec/controllers/api/v0/order_cycles_controller_spec.rb'
|
||||
- 'spec/controllers/api/v0/orders_controller_spec.rb'
|
||||
- 'spec/controllers/api/v0/products_controller_spec.rb'
|
||||
- 'spec/controllers/api/v0/shops_controller_spec.rb'
|
||||
- 'spec/controllers/api/v0/states_controller_spec.rb'
|
||||
- 'spec/controllers/api/v0/taxons_controller_spec.rb'
|
||||
- 'spec/helpers/spree/admin/orders_helper_spec.rb'
|
||||
- 'spec/lib/reports/lettuce_share_report_spec.rb'
|
||||
- 'spec/lib/reports/users_and_enterprises_report_spec.rb'
|
||||
- 'spec/serializers/api/admin/for_order_cycle/supplied_product_serializer_spec.rb'
|
||||
- 'spec/support/api_helper.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: conservative, aggressive
|
||||
Rails/PluckInWhere:
|
||||
Exclude:
|
||||
- 'app/models/spree/variant.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: AllowedReceivers.
|
||||
# AllowedReceivers: ActionMailer::Preview, ActiveSupport::TimeZone
|
||||
Rails/RedundantActiveRecordAllMethod:
|
||||
Exclude:
|
||||
- 'app/models/spree/tax_rate.rb'
|
||||
- 'app/models/spree/user.rb'
|
||||
- 'app/models/spree/variant.rb'
|
||||
- 'spec/system/admin/product_import_spec.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/RelativeDateConstant:
|
||||
Exclude:
|
||||
- 'lib/tasks/data/remove_transient_data.rb'
|
||||
|
||||
# Offense count: 56
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: Include.
|
||||
@@ -621,6 +691,26 @@ 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: 1
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/SelectMap:
|
||||
Exclude:
|
||||
- 'app/models/enterprise.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.
|
||||
@@ -649,6 +739,24 @@ Rails/UniqueValidationWithoutIndex:
|
||||
- 'app/models/spree/tax_category.rb'
|
||||
- 'app/models/spree/zone.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: Severity, Environments.
|
||||
# Environments: development, test, production
|
||||
Rails/UnknownEnv:
|
||||
Exclude:
|
||||
- 'app/models/spree/app_configuration.rb'
|
||||
|
||||
# Offense count: 7
|
||||
# Configuration parameters: Severity.
|
||||
Rails/UnusedRenderContent:
|
||||
Exclude:
|
||||
- 'app/controllers/admin/bulk_line_items_controller.rb'
|
||||
- 'app/controllers/admin/tag_rules_controller.rb'
|
||||
- 'app/controllers/api/v0/enterprise_fees_controller.rb'
|
||||
- 'app/controllers/api/v0/products_controller.rb'
|
||||
- 'app/controllers/api/v0/taxons_controller.rb'
|
||||
- 'app/controllers/api/v0/variants_controller.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Security/Open:
|
||||
Exclude:
|
||||
@@ -840,6 +948,13 @@ Style/RedundantInitialize:
|
||||
Exclude:
|
||||
- 'spec/models/spree/gateway_spec.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Style/RedundantInterpolation:
|
||||
Exclude:
|
||||
- 'lib/tasks/karma.rake'
|
||||
- 'spec/base_spec_helper.rb'
|
||||
|
||||
# Offense count: 19
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
||||
@@ -849,6 +964,39 @@ Style/ReturnNilInPredicateMethodDefinition:
|
||||
- 'app/serializers/api/admin/customer_serializer.rb'
|
||||
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
|
||||
|
||||
# Offense count: 204
|
||||
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,6 +16,7 @@ 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,6 +161,8 @@ 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)
|
||||
@@ -356,7 +358,7 @@ GEM
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
immigrant (0.3.6)
|
||||
activerecord (>= 3.0)
|
||||
invisible_captcha (2.3.0)
|
||||
invisible_captcha (2.2.0)
|
||||
rails (>= 5.2)
|
||||
io-console (0.7.2)
|
||||
ipaddress (0.8.3)
|
||||
@@ -415,7 +417,7 @@ GEM
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.4)
|
||||
marcel (1.0.2)
|
||||
matrix (0.4.2)
|
||||
method_source (1.1.0)
|
||||
mime-types (3.5.2)
|
||||
@@ -447,7 +449,7 @@ GEM
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
newrelic_rpm (9.9.0)
|
||||
nio4r (2.7.1)
|
||||
nio4r (2.7.0)
|
||||
nokogiri (1.16.5)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
@@ -861,6 +863,7 @@ 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,6 +10,7 @@
|
||||
//= require jquery.ui.all
|
||||
//= require jquery.powertip
|
||||
//= require jquery.cookie
|
||||
//= require jquery.jstree/jquery.jstree
|
||||
//= require jquery.vAlign
|
||||
//= require angular
|
||||
//= require angular-resource
|
||||
|
||||
@@ -47,7 +47,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
removeClearedValues()
|
||||
params = {
|
||||
'q[name_cont]': $scope.q.query,
|
||||
'q[variants_supplier_id_eq]': $scope.q.producerFilter,
|
||||
'q[supplier_id_eq]': $scope.q.producerFilter,
|
||||
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter,
|
||||
'q[s]': $scope.sorting,
|
||||
import_date: $scope.q.importDateFilter,
|
||||
@@ -126,11 +126,8 @@ 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: newVariantId
|
||||
id: $scope.nextVariantId()
|
||||
unit_value: null
|
||||
unit_description: null
|
||||
on_demand: false
|
||||
@@ -139,9 +136,8 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
on_hand: null
|
||||
price: null
|
||||
tax_category_id: null
|
||||
category_id: newVariantCategoryId
|
||||
category_id: null
|
||||
DisplayProperties.setShowVariants product.id, true
|
||||
DirtyProducts.addVariantProperty(product.id, newVariantId, 'category_id', newVariantCategoryId)
|
||||
|
||||
|
||||
$scope.nextVariantId = ->
|
||||
@@ -221,7 +217,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
products: productsToSubmit
|
||||
filters:
|
||||
'q[name_cont]': $scope.q.query
|
||||
'q[variants_supplier_id_eq]': $scope.q.producerFilter
|
||||
'q[supplier_id_eq]': $scope.q.producerFilter
|
||||
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter
|
||||
'q[s]': $scope.sorting
|
||||
import_date: $scope.q.importDateFilter
|
||||
@@ -318,6 +314,9 @@ filterSubmitProducts = (productsToFilter) ->
|
||||
if product.hasOwnProperty("name")
|
||||
filteredProduct.name = product.name
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("producer_id")
|
||||
filteredProduct.supplier_id = product.producer_id
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("price")
|
||||
filteredProduct.price = product.price
|
||||
hasUpdatableProperty = true
|
||||
@@ -380,9 +379,6 @@ filterSubmitVariant = (variant) ->
|
||||
if variant.hasOwnProperty("display_as")
|
||||
filteredVariant.display_as = variant.display_as
|
||||
hasUpdatableProperty = true
|
||||
if variant.hasOwnProperty("producer_id")
|
||||
filteredVariant.supplier_id = variant.producer_id
|
||||
hasUpdatableProperty = true
|
||||
{filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty}
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
# Used like a regular angular filter where an object is passed
|
||||
# Adds the additional special case that a value of 0 for the filter
|
||||
# acts as a bypass for that particular attribute
|
||||
|
||||
# NOTE the name doesn't reflect what the filter does, it only fiters on the variant.producer_id
|
||||
angular.module("admin.indexUtils").filter "attrFilter", ($filter) ->
|
||||
return (objects, filters) ->
|
||||
filter = filters["producer_id"]
|
||||
|
||||
return objects if !filter? || filter == 0
|
||||
|
||||
return $filter('filter')(objects, (product) ->
|
||||
for variant in product.variants
|
||||
return true if variant["producer_id"] == filter
|
||||
false
|
||||
, true)
|
||||
Object.keys(filters).reduce (filtered, attr) ->
|
||||
filter = filters[attr]
|
||||
return filtered if !filter? || filter == 0
|
||||
return $filter('filter')(filtered, (object) ->
|
||||
object[attr] == filter
|
||||
)
|
||||
, objects
|
||||
|
||||
@@ -31,7 +31,7 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, $injec
|
||||
savePreferences: (action_name) =>
|
||||
$http
|
||||
method: "PUT"
|
||||
url: "/admin/column_preferences/bulk_update.json"
|
||||
url: "/admin/column_preferences/bulk_update"
|
||||
data:
|
||||
action_name: action_name
|
||||
column_preferences: (preference for column_name, preference of @columns)
|
||||
|
||||
@@ -27,7 +27,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
"order_bill_address_full_name_reversed",
|
||||
"order_bill_address_full_name_with_comma",
|
||||
"order_bill_address_full_name_with_comma_reversed",
|
||||
"variant_supplier_name",
|
||||
"variant_product_supplier_name",
|
||||
"order_email",
|
||||
"order_number",
|
||||
"product_name"].join("_or_") + "_cont"
|
||||
@@ -81,7 +81,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
"q[order_shipment_state_not_eq]": "shipped",
|
||||
"q[order_completed_at_not_null]": "true",
|
||||
"q[order_distributor_id_eq]": $scope.distributorFilter,
|
||||
"q[variant_supplier_id_eq]": $scope.supplierFilter,
|
||||
"q[variant_product_supplier_id_eq]": $scope.supplierFilter,
|
||||
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
|
||||
"q[order_completed_at_gteq]": if formattedStartDate then formattedStartDate else undefined,
|
||||
"q[order_completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
|
||||
@@ -105,7 +105,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "supplier", Enterprises.byID
|
||||
$scope.loadOrders()
|
||||
RequestMonitor.load $q.all([$scope.orders.$promise]).then ->
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID
|
||||
$scope.bulk_order_form.$setPristine()
|
||||
@@ -133,7 +133,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
return $http(
|
||||
method: 'GET'
|
||||
url: "/admin/orders/#{order.number}/fire?e=cancel&send_cancellation_email=#{sendEmailCancellation}&restock_items=#{restock_items}")
|
||||
|
||||
|
||||
$scope.deleteLineItem = (lineItem) ->
|
||||
if lineItem.order.item_count == 1
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) ->
|
||||
@@ -167,7 +167,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.cancelOrder(order, sendEmailCancellation, restock_items).then(-> $scope.refreshData())
|
||||
else
|
||||
Promise.all(LineItems.delete(item) for item in items).then(-> $scope.refreshData())
|
||||
, "js.admin.deleting_item_will_cancel_order")
|
||||
, "js.admin.deleting_item_will_cancel_order")
|
||||
else
|
||||
ofnDeleteLineItemsAlert(() ->
|
||||
Promise.all(LineItems.delete(item) for item in lineItemsToDelete).then(-> $scope.refreshData())
|
||||
@@ -199,7 +199,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.getLineItemScale = (lineItem) ->
|
||||
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
|
||||
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
|
||||
lineItem.units_product.variant_unit_scale
|
||||
else
|
||||
1
|
||||
@@ -252,7 +252,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
scale = $scope.getScale(unitsProduct, unitsVariant)
|
||||
if scale
|
||||
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
|
||||
else
|
||||
else
|
||||
''
|
||||
|
||||
$scope.fulfilled = (sumOfUnitValues) ->
|
||||
|
||||
@@ -3,7 +3,6 @@ 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)
|
||||
@@ -19,8 +18,6 @@ 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)
|
||||
|
||||
@@ -28,4 +25,4 @@ angular.module('admin.orderCycles')
|
||||
if $scope.order_cycle_form?.$dirty
|
||||
t('admin.unsaved_confirm_leave')
|
||||
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
@@ -1,9 +1,8 @@
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, EnterpriseFee, OrderCycle, ExchangeProduct, ocInstance) ->
|
||||
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, 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,8 +22,6 @@ 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,8 +6,6 @@ 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,11 +161,7 @@ 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(),
|
||||
confirm: this.order_cycle.confirm,
|
||||
trigger_action: this.order_cycle.trigger_action
|
||||
})
|
||||
oc = new OrderCycleResource({order_cycle: this.dataForSubmit()})
|
||||
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
|
||||
form.$setPristine() if form
|
||||
if destination?
|
||||
@@ -175,8 +171,6 @@ 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'))
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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()
|
||||
139
app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
Normal file
139
app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
Normal file
@@ -0,0 +1,139 @@
|
||||
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,9 +10,7 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
||||
|
||||
statusMessage:
|
||||
text: ""
|
||||
style: {},
|
||||
type: null,
|
||||
actionName: null
|
||||
style: {}
|
||||
|
||||
invalidMessage: ""
|
||||
|
||||
@@ -25,15 +23,11 @@ angular.module("admin.utils").factory "StatusMessage", ->
|
||||
active: ->
|
||||
@statusMessage.text != ''
|
||||
|
||||
display: (type, text, actionName = null) ->
|
||||
display: (type, text) ->
|
||||
@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
|
||||
|
||||
@@ -2,10 +2,10 @@ angular.module("admin.variantOverrides").directive "trackInheritance", (VariantO
|
||||
require: "ngModel"
|
||||
link: (scope, element, attrs, ngModel) ->
|
||||
# This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it
|
||||
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant
|
||||
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant.id
|
||||
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
if ngModel.$dirty && viewValue
|
||||
DirtyVariantOverrides.inherit scope.hub_id, scope.variant, scope.variantOverrides[scope.hub_id][scope.variant.id].id
|
||||
DirtyVariantOverrides.inherit scope.hub_id, scope.variant.id, scope.variantOverrides[scope.hub_id][scope.variant.id].id
|
||||
scope.displayDirty()
|
||||
viewValue
|
||||
|
||||
@@ -2,8 +2,4 @@ angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
|
||||
return (products, hubPermissions, hub_id) ->
|
||||
return [] if !hub_id
|
||||
return [] if !hubPermissions[hub_id]
|
||||
|
||||
return $filter('filter')(products, ((product) ->
|
||||
for variant in product.variants
|
||||
return hubPermissions[hub_id].indexOf(variant.producer_id) > -1
|
||||
), true)
|
||||
return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)
|
||||
|
||||
@@ -12,11 +12,11 @@ angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http
|
||||
@add(hub_id, variant_id, vo_id)
|
||||
@dirtyVariantOverrides[hub_id][variant_id][attr] = value
|
||||
|
||||
inherit: (hub_id, variant, vo_id) ->
|
||||
@add(hub_id, variant.id, vo_id)
|
||||
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant))
|
||||
inherit: (hub_id, variant_id, vo_id) ->
|
||||
@add(hub_id, variant_id, vo_id)
|
||||
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant_id))
|
||||
delete blankVo[attr] for attr, value of blankVo when attr not in @requiredAttrs()
|
||||
@dirtyVariantOverrides[hub_id][variant.id] = blankVo
|
||||
@dirtyVariantOverrides[hub_id][variant_id] = blankVo
|
||||
|
||||
count: ->
|
||||
count = 0
|
||||
|
||||
@@ -13,18 +13,17 @@ angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOve
|
||||
@variantOverrides[hub.id] ||= {}
|
||||
for product in products
|
||||
for variant in product.variants
|
||||
@inherit(hub.id, variant) unless @variantOverrides[hub.id][variant.id]
|
||||
@inherit(hub.id, variant.id) unless @variantOverrides[hub.id][variant.id]
|
||||
|
||||
inherit: (hub_id, variant) ->
|
||||
inherit: (hub_id, variant_id) ->
|
||||
# This method is called from the trackInheritance directive, to reinstate inheritance
|
||||
@variantOverrides[hub_id][variant.id] ||= {}
|
||||
angular.extend @variantOverrides[hub_id][variant.id], @newFor(hub_id, variant)
|
||||
@variantOverrides[hub_id][variant_id] ||= {}
|
||||
angular.extend @variantOverrides[hub_id][variant_id], @newFor hub_id, variant_id
|
||||
|
||||
newFor: (hub_id, variant) ->
|
||||
newFor: (hub_id, variant_id) ->
|
||||
# These properties need to match those checked in VariantOverrideSet.deletable?
|
||||
hub_id: hub_id
|
||||
variant_id: variant.id
|
||||
producer_id: variant.producer_id
|
||||
variant_id: variant_id
|
||||
sku: null
|
||||
price: null
|
||||
count_on_hand: null
|
||||
|
||||
@@ -4,18 +4,15 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.query = ""
|
||||
$scope.taxonSelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.propertySelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.producerPropertySelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.filtersActive = true
|
||||
$scope.page = 1
|
||||
$scope.per_page = 10
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
$scope.supplied_taxons = null
|
||||
$scope.supplied_properties = null
|
||||
$scope.supplied_producer_properties = null
|
||||
$scope.showFilterSidebar = false
|
||||
$scope.activeTaxons = []
|
||||
$scope.activeProperties = []
|
||||
$scope.activeProducerProperties = []
|
||||
|
||||
# Update filters after initial load of shop tab
|
||||
$timeout =>
|
||||
@@ -48,12 +45,6 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.supplied_properties[property.id] = Properties.properties_by_id[property.id]
|
||||
)
|
||||
|
||||
OrderCycleResource.producerProperties params, (data)=>
|
||||
$scope.supplied_producer_properties = {}
|
||||
data.map( (property) ->
|
||||
$scope.supplied_producer_properties[property.id] = Properties.properties_by_id[property.id]
|
||||
)
|
||||
|
||||
$scope.loadMore = ->
|
||||
if ($scope.page * $scope.per_page) <= Products.products.length
|
||||
$scope.loadMoreProducts()
|
||||
@@ -61,7 +52,6 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.$watch 'query', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeTaxons', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
$scope.$watchCollection 'activeProducerProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
|
||||
|
||||
$scope.loadProducts = ->
|
||||
$scope.page = 1
|
||||
@@ -76,9 +66,8 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
id: $scope.order_cycle.order_cycle_id,
|
||||
page: page || $scope.page,
|
||||
per_page: $scope.per_page,
|
||||
'q[name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_variants_supplier_name_cont]': $scope.query,
|
||||
'q[name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_supplier_name_cont]': $scope.query,
|
||||
'q[with_properties][]': $scope.activeProperties,
|
||||
'q[with_variants_supplier_properties][]': $scope.activeProducerProperties,
|
||||
'q[variants_primary_taxon_id_in_any][]': $scope.activeTaxons
|
||||
}
|
||||
|
||||
@@ -97,12 +86,6 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
Properties.properties_by_id[property_id].name
|
||||
).join($scope.filtersJoinWord()) if $scope.activeProperties?
|
||||
|
||||
$scope.appliedProducerPropertiesList = ->
|
||||
$scope.activeProducerProperties.map( (property_id) ->
|
||||
Properties.properties_by_id[property_id].name
|
||||
).join($scope.filtersJoinWord()) if $scope.activeProducerProperties?
|
||||
|
||||
|
||||
$scope.filtersJoinWord = ->
|
||||
$sce.trustAsHtml(" <span class='join-word'>#{t('products_or')}</span> ")
|
||||
|
||||
@@ -116,7 +99,6 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.clearFilters = ->
|
||||
$scope.taxonSelectors.clearAll()
|
||||
$scope.propertySelectors.clearAll()
|
||||
$scope.producerPropertySelectors.clearAll()
|
||||
|
||||
$scope.refreshStaleData = ->
|
||||
# If the products template has already been loaded but the controller is being initialized
|
||||
@@ -127,7 +109,7 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
|
||||
$scope.loadProducts()
|
||||
|
||||
$scope.filtersCount = () ->
|
||||
$scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive() + $scope.producerPropertySelectors.totalActive()
|
||||
$scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive()
|
||||
|
||||
$scope.toggleFilterSidebar = ->
|
||||
$scope.showFilterSidebar = !$scope.showFilterSidebar
|
||||
|
||||
@@ -18,11 +18,4 @@ angular.module('Darkswarm').factory 'OrderCycleResource', ($resource) ->
|
||||
url: '/api/v0/order_cycles/:id/properties.json'
|
||||
params:
|
||||
id: '@id'
|
||||
'producerProperties':
|
||||
method: 'GET'
|
||||
isArray: true
|
||||
url: '/api/v0/order_cycles/:id/producer_properties.json'
|
||||
params:
|
||||
id: '@id'
|
||||
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ angular.module('Darkswarm').factory 'Products', (OrderCycleResource, OrderCycle,
|
||||
|
||||
dereference: ->
|
||||
for product in @fetched_products
|
||||
product.supplier = Shopfront.producers_by_id[product.variants[0].supplier.id]
|
||||
product.supplier = Shopfront.producers_by_id[product.supplier.id]
|
||||
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
|
||||
|
||||
product.properties = angular.copy(product.properties_with_values)
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
%div.menu{ 'ng-show' => "expanded" }
|
||||
.menu_items
|
||||
.menu_item{ "ng-repeat": "column in columns", "ng-click": "toggle(column);" }
|
||||
%input{ type: "checkbox", "ng-checked": "column.visible" }
|
||||
%span
|
||||
{{ column.name }}
|
||||
%input.redesigned-input{ type: "checkbox", "ng-checked": "column.visible" }
|
||||
{{ column.name }}
|
||||
%hr
|
||||
%div.menu_item.text-center
|
||||
%input.fullwidth.orange{ type: "button", "ng-value": "saved() ? 'Saved': 'Saving'", "ng-show": "saved() || saving", "ng-disabled": "saved()" }
|
||||
|
||||
@@ -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', data: { 'order-cycle-form-target': 'statusMessage' }, "ng-attr-data-type": "{{StatusMessage.statusMessage.type}}", "ng-attr-data-action-name": "{{StatusMessage.statusMessage.actionName}}" }
|
||||
%h5#status-message{ "ng-show": "StatusMessage.invalidMessage == ''", "ng-style": 'StatusMessage.statusMessage.style' }
|
||||
{{ StatusMessage.statusMessage.text || " " }}
|
||||
%h5#status-message{ style: 'color: #C85136', "ng-show": "StatusMessage.invalidMessage !== ''" }
|
||||
{{ StatusMessage.invalidMessage || " " }}
|
||||
|
||||
@@ -24,19 +24,6 @@
|
||||
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 */
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
%div.menu_items
|
||||
- @options.each do |option|
|
||||
%label.menu_item{ "data-multiple-checked-select-target": "option", "data-value": option[1], "data-label": option[0] }
|
||||
%input{ type: "checkbox", checked: @selected.include?(option[1]), name: "#{@name}[]", value: option[1] }
|
||||
%span= option[0]
|
||||
%input.redesigned-input{ type: "checkbox", checked: @selected.include?(option[1]), name: "#{@name}[]", value: option[1] }
|
||||
= option[0]
|
||||
|
||||
@@ -35,7 +35,7 @@ module Admin
|
||||
order.with_lock do
|
||||
if order.contents.update_item(@line_item, line_item_params)
|
||||
# No Content, does not trigger ng resource auto-update
|
||||
head :no_content
|
||||
render body: nil, status: :no_content
|
||||
else
|
||||
render json: { errors: @line_item.errors }, status: :precondition_failed
|
||||
end
|
||||
@@ -49,7 +49,7 @@ module Admin
|
||||
authorize! :update, order
|
||||
|
||||
order.contents.remove(@line_item.variant)
|
||||
head :no_content # No Content, does not trigger ng resource auto-update
|
||||
render body: nil, status: :no_content # No Content, does not trigger ng resource auto-update
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -4,25 +4,17 @@ module Admin
|
||||
class ColumnPreferencesController < Admin::ResourceController
|
||||
before_action :load_collection, only: [:bulk_update]
|
||||
|
||||
respond_to :json
|
||||
|
||||
def bulk_update
|
||||
@cp_set.collection.each { |cp| authorize! :bulk_update, cp }
|
||||
|
||||
respond_to do |format|
|
||||
if @cp_set.save
|
||||
format.json {
|
||||
render json: @cp_set.collection, each_serializer: Api::Admin::ColumnPreferenceSerializer
|
||||
}
|
||||
format.turbo_stream {
|
||||
flash.now[:success] = t('.success')
|
||||
render :bulk_update, locals: { action: permitted_params[:action_name] }
|
||||
}
|
||||
else
|
||||
format.json { render json: { errors: @cp_set.errors }, status: :bad_request }
|
||||
format.turbo_stream {
|
||||
flash.now[:error] = @cp_set.errors.full_messages.to_sentence
|
||||
render :bulk_update, locals: { action: permitted_params[:action_name] }
|
||||
}
|
||||
end
|
||||
if @cp_set.save
|
||||
render json: @cp_set.collection, each_serializer: Api::Admin::ColumnPreferenceSerializer
|
||||
elsif @cp_set.errors.present?
|
||||
render json: { errors: @cp_set.errors }, status: :bad_request
|
||||
else
|
||||
render body: nil, status: :internal_server_error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,26 +28,11 @@ module Admin
|
||||
end
|
||||
|
||||
def load_collection
|
||||
collection_attributes = nil
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
collection_attributes = Hash[permitted_params[:column_preferences].
|
||||
each_with_index.map { |cp, i| [i, cp] }]
|
||||
collection_attributes.select!{ |_i, cp|
|
||||
cp[:action_name] == permitted_params[:action_name]
|
||||
}
|
||||
end
|
||||
format.all do
|
||||
# Inject action name and user ID for each column_preference
|
||||
collection_attributes = permitted_params[:column_preferences].to_h.each_value { |cp|
|
||||
cp[:action_name] = permitted_params[:action_name]
|
||||
cp[:user_id] = spree_current_user.id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@cp_set = Sets::ColumnPreferenceSet.new(@column_preferences, collection_attributes:)
|
||||
collection_hash = Hash[permitted_params[:column_preferences].
|
||||
each_with_index.map { |cp, i| [i, cp] }]
|
||||
collection_hash.select!{ |_i, cp| cp[:action_name] == permitted_params[:action_name] }
|
||||
@cp_set = Sets::ColumnPreferenceSet.new(@column_preferences,
|
||||
collection_attributes: collection_hash)
|
||||
end
|
||||
|
||||
def collection
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# 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
|
||||
|
||||
attributes = {}
|
||||
attributes[:type] = connected_app_params[:type] if connected_app_params[:type]
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id)
|
||||
|
||||
app = ConnectedApp.create!(enterprise_id: enterprise.id, **attributes)
|
||||
app.connect(api_key: spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request))
|
||||
ConnectAppJob.perform_later(
|
||||
app, spree_current_user.spree_api_key,
|
||||
channel: SessionChannel.for_request(request),
|
||||
)
|
||||
|
||||
render_panel
|
||||
end
|
||||
@@ -18,9 +18,15 @@ module Admin
|
||||
def destroy
|
||||
authorize! :admin, enterprise
|
||||
|
||||
app = enterprise.connected_apps.find(params.require(:id))
|
||||
app = enterprise.connected_apps.first
|
||||
app.destroy
|
||||
|
||||
WebhookDeliveryJob.perform_later(
|
||||
app.data["destroy"],
|
||||
"disconnect-app",
|
||||
nil
|
||||
)
|
||||
|
||||
render_panel
|
||||
end
|
||||
|
||||
@@ -33,9 +39,5 @@ 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,7 +35,13 @@ module Admin
|
||||
private
|
||||
|
||||
def fetch_catalog(url)
|
||||
DfcRequest.new(spree_current_user).call(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
|
||||
end
|
||||
|
||||
# Most of this code is the same as in the DfcProvider::SuppliedProductsController.
|
||||
|
||||
@@ -65,9 +65,7 @@ module Admin
|
||||
order_cycle ||= OrderCycle.new(coordinator:) if coordinator.present?
|
||||
enterprises = OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user,
|
||||
order_cycle).visible_enterprises
|
||||
|
||||
fees = EnterpriseFee.for_enterprises(enterprises).order('enterprise_id', 'fee_type', 'name')
|
||||
filter_fees(fees)
|
||||
EnterpriseFee.for_enterprises(enterprises).order('enterprise_id', 'fee_type', 'name')
|
||||
else
|
||||
collection = EnterpriseFee.managed_by(spree_current_user).order('enterprise_id',
|
||||
'fee_type', 'name')
|
||||
@@ -76,12 +74,6 @@ 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
|
||||
|
||||
@@ -189,7 +189,10 @@ module Admin
|
||||
.visible_enterprises
|
||||
|
||||
if enterprises.present?
|
||||
enterprises.includes(supplied_products: [:variants, :image])
|
||||
enterprises.includes(
|
||||
supplied_products:
|
||||
[:supplier, :variants, :image]
|
||||
)
|
||||
end
|
||||
when :index
|
||||
if spree_current_user.admin?
|
||||
|
||||
@@ -11,7 +11,6 @@ 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|
|
||||
@@ -236,7 +235,7 @@ module Admin
|
||||
else
|
||||
begin
|
||||
yield
|
||||
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::DeleteRestrictionError
|
||||
rescue ActiveRecord::InvalidForeignKey
|
||||
redirect_to main_app.admin_order_cycles_url
|
||||
flash[:error] = I18n.t('admin.order_cycles.destroy_errors.orders_present')
|
||||
end
|
||||
@@ -295,22 +294,5 @@ 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,8 +4,6 @@ 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
|
||||
@@ -103,7 +101,8 @@ module Admin
|
||||
|
||||
def save_uploaded_file(upload)
|
||||
extension = File.extname(upload.original_filename)
|
||||
File.open(File.join(mktmpdir, "import#{extension}"), 'wb') do |f|
|
||||
directory = Dir.mktmpdir 'product_import'
|
||||
File.open(File.join(directory, "import#{extension}"), 'wb') do |f|
|
||||
data = UploadSanitizer.new(upload.read).call
|
||||
f.write(data)
|
||||
f.path
|
||||
@@ -127,14 +126,6 @@ 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
|
||||
@@ -143,9 +134,8 @@ 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?(%r{^#{tmpdir_base}[A-Za-z0-9-]*/import\.csv$})
|
||||
return file_path if file_path.to_s.match?(TEMP_FILE_PATH_REGEX)
|
||||
|
||||
raise_invalid_file_path
|
||||
end
|
||||
@@ -155,5 +145,6 @@ 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,8 +11,6 @@ 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
|
||||
@@ -40,9 +38,7 @@ module Admin
|
||||
{ id: params[:id] }
|
||||
).find_product
|
||||
|
||||
@record.destroyed_by = spree_current_user
|
||||
status = :ok
|
||||
|
||||
if @record.destroy
|
||||
flash.now[:success] = t('.delete_product.success')
|
||||
else
|
||||
@@ -72,29 +68,6 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def clone
|
||||
@product = Spree::Product.find(params[:id])
|
||||
status = :ok
|
||||
|
||||
begin
|
||||
@cloned_product = @product.duplicate
|
||||
flash.now[:success] = t('.success')
|
||||
|
||||
@product_index = "-#{@cloned_product.id}"
|
||||
@producer_options = producers
|
||||
@category_options = categories
|
||||
@tax_category_options = tax_category_options
|
||||
rescue ActiveRecord::ActiveRecordError => _e
|
||||
flash.now[:error] = t('.error')
|
||||
status = :unprocessable_entity
|
||||
@product_index = "-1" # Create a unique enough index
|
||||
end
|
||||
|
||||
respond_with do |format|
|
||||
format.turbo_stream { render :clone, status: }
|
||||
end
|
||||
end
|
||||
|
||||
def index_url(params)
|
||||
"/admin/products?#{params.to_query}" # todo: fix routing so this can be automaticly generated
|
||||
end
|
||||
@@ -151,7 +124,7 @@ module Admin
|
||||
|
||||
def ransack_query
|
||||
query = {}
|
||||
query.merge!(variants_supplier_id_in: @producer_id) if @producer_id.present?
|
||||
query.merge!(supplier_id_in: @producer_id) if @producer_id.present?
|
||||
if @search_term.present?
|
||||
query.merge!(Spree::Variant::SEARCH_KEY => @search_term)
|
||||
end
|
||||
@@ -165,13 +138,13 @@ module Admin
|
||||
def product_query_includes
|
||||
[
|
||||
:image,
|
||||
:supplier,
|
||||
{ variants: [
|
||||
:default_price,
|
||||
:primary_taxon,
|
||||
:product,
|
||||
:stock_items,
|
||||
:tax_category,
|
||||
:supplier,
|
||||
] },
|
||||
]
|
||||
end
|
||||
|
||||
@@ -61,9 +61,6 @@ module Admin
|
||||
def render_in_background
|
||||
cable_ready[ScopedChannel.for_id(params[:uuid])]
|
||||
.inner_html(
|
||||
selector: "#report-go",
|
||||
html: helpers.button(t(:go), "report__submit-btn", "submit", disabled: true)
|
||||
).inner_html(
|
||||
selector: "#report-table",
|
||||
html: render_to_string(partial: "admin/reports/loading")
|
||||
).scroll_into_view(
|
||||
|
||||
@@ -5,7 +5,7 @@ module Admin
|
||||
respond_to :json
|
||||
|
||||
respond_override destroy: { json: {
|
||||
success: lambda { head :no_content }
|
||||
success: lambda { render body: nil, status: :no_content }
|
||||
} }
|
||||
|
||||
def map_by_tag
|
||||
|
||||
@@ -88,7 +88,7 @@ module Admin
|
||||
end
|
||||
|
||||
def modified_variant_overrides_ids
|
||||
variant_overrides_params.pluck(:id)
|
||||
variant_overrides_params.map { |vo| vo[:id] }
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
|
||||
@@ -9,7 +9,7 @@ module Api
|
||||
authorize! :destroy, enterprise_fee
|
||||
|
||||
if enterprise_fee.destroy
|
||||
head :no_content
|
||||
render plain: I18n.t(:successfully_removed), status: :no_content
|
||||
else
|
||||
render plain: enterprise_fee.errors.full_messages.first, status: :forbidden
|
||||
end
|
||||
|
||||
@@ -7,11 +7,9 @@ module Api
|
||||
include ApiActionCaching
|
||||
|
||||
skip_authorization_check
|
||||
skip_before_action :authenticate_user, :ensure_api_key, only: [
|
||||
:taxons, :properties, :producer_properties
|
||||
]
|
||||
skip_before_action :authenticate_user, :ensure_api_key, only: [:taxons, :properties]
|
||||
|
||||
caches_action :taxons, :properties, :producer_properties,
|
||||
caches_action :taxons, :properties,
|
||||
expires_in: CacheService::FILTERS_EXPIRY,
|
||||
cache_path: proc { |controller| controller.request.url }
|
||||
|
||||
@@ -43,13 +41,7 @@ module Api
|
||||
|
||||
def properties
|
||||
render plain: ActiveModel::ArraySerializer.new(
|
||||
product_properties, each_serializer: Api::PropertySerializer
|
||||
).to_json
|
||||
end
|
||||
|
||||
def producer_properties
|
||||
render plain: ActiveModel::ArraySerializer.new(
|
||||
load_producer_properties, each_serializer: Api::PropertySerializer
|
||||
product_properties | producer_properties, each_serializer: Api::PropertySerializer
|
||||
).to_json
|
||||
end
|
||||
|
||||
@@ -66,7 +58,7 @@ module Api
|
||||
select('DISTINCT spree_properties.*')
|
||||
end
|
||||
|
||||
def load_producer_properties
|
||||
def producer_properties
|
||||
producers = Enterprise.
|
||||
joins(:supplied_products).
|
||||
where(spree_products: { id: distributed_products })
|
||||
@@ -94,9 +86,8 @@ module Api
|
||||
end
|
||||
|
||||
def distributed_products
|
||||
OrderCycles::DistributedProductsService.new(
|
||||
distributor, order_cycle, customer
|
||||
).products_relation.pluck(:id)
|
||||
OrderCycles::DistributedProductsService.new(distributor, order_cycle,
|
||||
customer).products_relation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,7 +44,7 @@ module Api
|
||||
authorize! :delete, @product
|
||||
@product.destroyed_by = current_api_user
|
||||
@product.destroy
|
||||
head :no_content
|
||||
render json: @product, serializer: Api::Admin::ProductSerializer, status: :no_content
|
||||
end
|
||||
|
||||
def bulk_products
|
||||
@@ -54,7 +54,7 @@ module Api
|
||||
end
|
||||
|
||||
def overridable
|
||||
@products = product_finder.products_for_producers
|
||||
@products = product_finder.paged_products_for_producers
|
||||
|
||||
render_paged_products @products, ::Api::Admin::ProductSimpleSerializer
|
||||
end
|
||||
|
||||
16
app/controllers/api/v0/taxonomies_controller.rb
Normal file
16
app/controllers/api/v0/taxonomies_controller.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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,10 +5,12 @@ module Api
|
||||
class TaxonsController < Api::V0::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_authorization_check only: [:index, :show]
|
||||
skip_authorization_check only: [:index, :show, :jstree]
|
||||
|
||||
def index
|
||||
@taxons = if params[:ids]
|
||||
@taxons = if taxonomy
|
||||
taxonomy.root.children
|
||||
elsif params[:ids]
|
||||
Spree::Taxon.where(id: raw_params[:ids].split(","))
|
||||
else
|
||||
Spree::Taxon.ransack(raw_params[:q]).result
|
||||
@@ -16,9 +18,23 @@ 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
|
||||
@@ -39,19 +55,25 @@ module Api
|
||||
def destroy
|
||||
authorize! :delete, Spree::Taxon
|
||||
taxon.destroy
|
||||
head :no_content
|
||||
render json: taxon, serializer: Api::TaxonSerializer, status: :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def taxonomy
|
||||
return if params[:taxonomy_id].blank?
|
||||
|
||||
@taxonomy ||= Spree::Taxonomy.find(params[:taxonomy_id])
|
||||
end
|
||||
|
||||
def taxon
|
||||
@taxon = Spree::Taxon.find(params[:id])
|
||||
@taxon ||= taxonomy.taxons.find(params[:id])
|
||||
end
|
||||
|
||||
def taxon_params
|
||||
return if params[:taxon].blank?
|
||||
|
||||
params.require(:taxon).permit([:name, :position])
|
||||
params.require(:taxon).permit([:name, :parent_id, :position])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,7 +44,7 @@ module Api
|
||||
authorize! :delete, @variant
|
||||
|
||||
VariantDeleter.new.delete(@variant)
|
||||
head :no_content
|
||||
render json: @variant, serializer: Api::VariantSerializer, status: :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -19,18 +19,15 @@ module Spree
|
||||
|
||||
before_action :authorize_admin
|
||||
before_action :set_locale
|
||||
before_action :warn_invalid_order_cycles, if: :page_load_request?
|
||||
before_action :warn_invalid_order_cycles, if: :html_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? || session[:displayed_order_cycle_warning]
|
||||
return if flash[:notice].present?
|
||||
|
||||
warning = OrderCycles::WarningService.new(spree_current_user).call
|
||||
return if warning.blank?
|
||||
|
||||
flash.now[:notice] = warning
|
||||
session[:displayed_order_cycle_warning] = true
|
||||
flash[:notice] = warning if warning.present?
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -84,12 +81,6 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def page_load_request?
|
||||
return false if request.format.include?('turbo')
|
||||
|
||||
html_request?
|
||||
end
|
||||
|
||||
def html_request?
|
||||
request.format.html?
|
||||
end
|
||||
|
||||
@@ -37,14 +37,13 @@ module Spree
|
||||
|
||||
if @object.save
|
||||
flash[:success] = flash_message_for(@object, :successfully_created)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to location_after_save }
|
||||
format.turbo_stream { render :update }
|
||||
end
|
||||
redirect_to location_after_save
|
||||
else
|
||||
respond_with_error(@object.errors)
|
||||
respond_with(@object)
|
||||
end
|
||||
rescue ActiveStorage::IntegrityError
|
||||
@object.errors.add :attachment, :integrity_error
|
||||
respond_with(@object)
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -54,13 +53,16 @@ module Spree
|
||||
if @object.update(permitted_resource_params)
|
||||
flash[:success] = flash_message_for(@object, :successfully_updated)
|
||||
|
||||
respond_to do |format|
|
||||
respond_with do |format|
|
||||
format.html { redirect_to location_after_save }
|
||||
format.turbo_stream
|
||||
end
|
||||
else
|
||||
respond_with_error(@object.errors)
|
||||
respond_with(@object)
|
||||
end
|
||||
rescue ActiveStorage::IntegrityError
|
||||
@object.errors.add :attachment, :integrity_error
|
||||
respond_with(@object)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@@ -110,14 +112,6 @@ module Spree
|
||||
:attachment, :viewable_id, :alt
|
||||
)
|
||||
end
|
||||
|
||||
def respond_with_error(errors)
|
||||
@errors = errors.map(&:full_message)
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@object) }
|
||||
format.turbo_stream { render :edit }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,8 +15,6 @@ module Spree
|
||||
invoice_pdf = filepath(invoice_id)
|
||||
|
||||
send_file(invoice_pdf, type: 'application/pdf', disposition: :inline)
|
||||
rescue ActionController::MissingFile
|
||||
render "errors/not_found", status: :not_found, formats: :html
|
||||
end
|
||||
|
||||
def generate
|
||||
|
||||
@@ -8,7 +8,6 @@ module Spree
|
||||
before_action :setup_property, only: [:index]
|
||||
|
||||
def index
|
||||
@supplier = @product.variants.first.supplier
|
||||
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
|
||||
end
|
||||
|
||||
|
||||
@@ -10,10 +10,8 @@ module Spree
|
||||
include OpenFoodNetwork::SpreeApiKeyLoader
|
||||
include OrderCyclesHelper
|
||||
include EnterprisesHelper
|
||||
helper ::Admin::ProductsHelper
|
||||
|
||||
before_action :load_data
|
||||
before_action :load_producers, only: [:index, :new]
|
||||
before_action :load_form_data, only: [:index, :new, :create, :edit, :update]
|
||||
before_action :load_spree_api_key, only: [:index, :variant_overrides]
|
||||
before_action :strip_new_properties, only: [:create, :update]
|
||||
@@ -43,7 +41,6 @@ module Spree
|
||||
flash[:success] = flash_message_for(@object, :successfully_created)
|
||||
redirect_after_save
|
||||
else
|
||||
load_producers
|
||||
# Re-fill the form with deleted params on product
|
||||
@on_hand = request.params[:product][:on_hand]
|
||||
@on_demand = request.params[:product][:on_demand]
|
||||
@@ -55,9 +52,14 @@ module Spree
|
||||
def update
|
||||
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
|
||||
|
||||
original_supplier_id = @product.supplier_id
|
||||
delete_stock_params_and_set_after do
|
||||
params[:product] ||= {} if params[:clear_product_properties]
|
||||
if @object.update(permitted_resource_params)
|
||||
if original_supplier_id != @product.supplier_id
|
||||
ExchangeVariantDeleter.new.delete(@product)
|
||||
end
|
||||
|
||||
flash[:success] = flash_message_for(@object, :successfully_updated)
|
||||
end
|
||||
redirect_to spree.edit_admin_product_url(@object, @url_filters)
|
||||
@@ -155,13 +157,10 @@ module Spree
|
||||
end
|
||||
|
||||
def load_form_data
|
||||
@taxons = Spree::Taxon.order(:name)
|
||||
@import_dates = product_import_dates.uniq.to_json
|
||||
end
|
||||
|
||||
def load_producers
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
managed_product_enterprises.is_primary_producer.by_name
|
||||
@taxons = Spree::Taxon.order(:name)
|
||||
@import_dates = product_import_dates.uniq.to_json
|
||||
end
|
||||
|
||||
def product_import_dates
|
||||
@@ -174,10 +173,12 @@ module Spree
|
||||
|
||||
def product_import_dates_query
|
||||
Spree::Variant.
|
||||
select('import_date').distinct.
|
||||
where(supplier_id: editable_enterprises.collect(&:id)).
|
||||
select('DISTINCT spree_variants.import_date').
|
||||
joins(:product).
|
||||
where(spree_products: { supplier_id: editable_enterprises.collect(&:id) }).
|
||||
where.not(spree_variants: { import_date: nil }).
|
||||
order('import_date DESC')
|
||||
where(spree_variants: { deleted_at: nil }).
|
||||
order('spree_variants.import_date DESC')
|
||||
end
|
||||
|
||||
def strip_new_properties
|
||||
|
||||
27
app/controllers/spree/admin/taxonomies_controller.rb
Normal file
27
app/controllers/spree/admin/taxonomies_controller.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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,70 +2,122 @@
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class TaxonsController < ::Admin::ResourceController
|
||||
before_action :set_taxon, except: %i[create index new]
|
||||
class TaxonsController < Spree::Admin::BaseController
|
||||
respond_to :html, :json, :js
|
||||
|
||||
def index
|
||||
@taxons = Taxon.order(:name)
|
||||
def edit
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.find(params[:id])
|
||||
@permalink_part = @taxon.permalink.split("/").last
|
||||
end
|
||||
|
||||
def new
|
||||
@taxon = Taxon.new
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
@taxon = Spree::Taxon.new(taxon_params)
|
||||
@taxonomy = Taxonomy.find(params[:taxonomy_id])
|
||||
@taxon = @taxonomy.taxons.build(params[:taxon])
|
||||
if @taxon.save
|
||||
flash[:success] = flash_message_for(@taxon, :successfully_created)
|
||||
redirect_to edit_admin_taxon_path(@taxon.id)
|
||||
respond_with(@taxon) do |format|
|
||||
format.json { render json: @taxon.to_json }
|
||||
end
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
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
|
||||
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)
|
||||
redirect_to edit_admin_taxon_path(@taxon.id)
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
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 }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
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
|
||||
@taxon = Taxon.find(params[:id])
|
||||
@taxon.destroy
|
||||
respond_with(@taxon) { |format| format.json { render json: '' } }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_taxon
|
||||
@taxon = Taxon.find(params[:id])
|
||||
end
|
||||
|
||||
def taxon_params
|
||||
params.require(:taxon).permit(
|
||||
:name, :position, :icon, :description, :permalink,
|
||||
:name, :parent_id, :position, :icon, :description, :permalink, :taxonomy_id,
|
||||
:meta_description, :meta_keywords, :meta_title, :dfc_id
|
||||
)
|
||||
end
|
||||
|
||||
@@ -46,13 +46,7 @@ module Spree
|
||||
def update
|
||||
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
|
||||
|
||||
original_supplier_id = @object.supplier_id
|
||||
|
||||
if @object.update(permitted_resource_params)
|
||||
if original_supplier_id != @object.supplier_id
|
||||
ExchangeVariantDeleter.new.delete(@object)
|
||||
end
|
||||
|
||||
flash[:success] = flash_message_for(@object, :successfully_updated)
|
||||
redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters)
|
||||
else
|
||||
@@ -119,8 +113,6 @@ module Spree
|
||||
private
|
||||
|
||||
def load_data
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
managed_product_enterprises.is_primary_producer.by_name
|
||||
@tax_categories = TaxCategory.order(:name)
|
||||
@shipping_categories = ShippingCategory.order(:name)
|
||||
end
|
||||
|
||||
@@ -47,8 +47,14 @@ module Spree
|
||||
@user = Spree::User.new(user_params)
|
||||
|
||||
if @user.save
|
||||
flash[:success] = t('devise.user_registrations.spree_user.signed_up_but_unconfirmed')
|
||||
render cable_ready: cable_car.redirect_to(url: main_app.root_path)
|
||||
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')
|
||||
})
|
||||
)
|
||||
else
|
||||
render status: :unprocessable_entity, cable_ready: cable_car.morph(
|
||||
"#signup-tab",
|
||||
|
||||
@@ -26,8 +26,7 @@ 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) &&
|
||||
Spree::Config.connected_apps_enabled.present?
|
||||
feature?(:connected_apps, spree_current_user, enterprise)
|
||||
|
||||
build_enterprise_side_menu_items(
|
||||
is_shop:,
|
||||
@@ -39,11 +38,6 @@ 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(
|
||||
@@ -72,8 +66,8 @@ module Admin
|
||||
{ name: 'inventory_settings', icon_class: "icon-list-ol", show: is_shop },
|
||||
{ name: 'tag_rules', icon_class: "icon-random", show: is_shop },
|
||||
{ name: 'shop_preferences', icon_class: "icon-shopping-cart", show: is_shop },
|
||||
{ name: 'white_label', icon_class: "icon-leaf", show: true },
|
||||
{ name: 'users', icon_class: "icon-user", show: true },
|
||||
{ name: 'white_label', icon_class: "icon-leaf", show: true },
|
||||
{ name: 'connected_apps', icon_class: "icon-puzzle-piece", show: show_connected_apps },
|
||||
]
|
||||
end
|
||||
|
||||
@@ -9,33 +9,5 @@ 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.selectable_locales.map do |locale|
|
||||
OpenFoodNetwork::I18nConfig.available_locales.map do |locale|
|
||||
[t('language_name', locale:), locale]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,8 +10,4 @@ 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
|
||||
|
||||
11
app/helpers/spree/admin/taxons_helper.rb
Normal file
11
app/helpers/spree/admin/taxons_helper.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# 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/discover_regen",
|
||||
locals: { enterprise:, connected_app: enterprise.connected_apps.discover_regen.first },
|
||||
partial: "admin/enterprises/form/connected_apps",
|
||||
locals: { enterprise: },
|
||||
)
|
||||
|
||||
cable_ready[channel].morph(selector:, html:).broadcast
|
||||
|
||||
@@ -39,14 +39,10 @@ class ReportJob < ApplicationJob
|
||||
end
|
||||
|
||||
def broadcast_result(channel, format, blob)
|
||||
cable_ready[channel]
|
||||
.inner_html(
|
||||
selector: "#report-go",
|
||||
html: Spree::Admin::BaseController.helpers.button(I18n.t(:go), "report__submit-btn")
|
||||
).inner_html(
|
||||
selector: "#report-table",
|
||||
html: actioncable_content(format, blob)
|
||||
).broadcast
|
||||
cable_ready[channel].inner_html(
|
||||
selector: "#report-table",
|
||||
html: actioncable_content(format, blob)
|
||||
).broadcast
|
||||
end
|
||||
|
||||
def broadcast_error(channel)
|
||||
@@ -57,14 +53,7 @@ class ReportJob < ApplicationJob
|
||||
end
|
||||
|
||||
def actioncable_content(format, blob)
|
||||
if format.to_sym == :html
|
||||
return blob.result if blob.byte_size < 10**6 # 1 MB
|
||||
|
||||
return render(
|
||||
partial: "admin/reports/display",
|
||||
locals: { file_url: blob.expiring_service_url }
|
||||
)
|
||||
end
|
||||
return blob.result if format.to_sym == :html
|
||||
|
||||
render(partial: "admin/reports/download", locals: { file_url: blob.expiring_service_url })
|
||||
end
|
||||
|
||||
@@ -60,12 +60,11 @@ class ProducerMailer < ApplicationMailer
|
||||
|
||||
def line_items_from(order_cycle, producer)
|
||||
@line_items ||= Spree::LineItem.
|
||||
includes(variant: :product).
|
||||
joins(variant: :product).
|
||||
includes(variant: [:product]).
|
||||
from_order_cycle(order_cycle).
|
||||
merge(Spree::Variant.with_deleted.where(supplier: producer)).
|
||||
merge(Spree::Order.by_state(["complete", "resumed"])).
|
||||
sorted_by_name_and_unit_value
|
||||
sorted_by_name_and_unit_value.
|
||||
merge(Spree::Product.with_deleted.in_supplier(producer)).
|
||||
merge(Spree::Order.by_state(["complete", "resumed"]))
|
||||
end
|
||||
|
||||
def total_from_line_items(line_items)
|
||||
@@ -82,7 +81,7 @@ class ProducerMailer < ApplicationMailer
|
||||
line_items.map do |line_item|
|
||||
{
|
||||
sku: line_item.variant.sku,
|
||||
supplier_name: line_item.variant.supplier.name,
|
||||
supplier_name: line_item.product.supplier.name,
|
||||
product_and_full_name: line_item.product_and_full_name,
|
||||
quantity: line_item.quantity,
|
||||
first_name: line_item.order.billing_address.first_name,
|
||||
|
||||
19
app/models/concerns/line_item_stock_changes.rb
Normal file
19
app/models/concerns/line_item_stock_changes.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# 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
|
||||
@@ -10,12 +10,9 @@ module LogDestroyPerformer
|
||||
after_destroy :log_who_destroyed
|
||||
|
||||
def log_who_destroyed
|
||||
message = if destroyed_by.nil?
|
||||
"#{self.class} #{id} deleted"
|
||||
else
|
||||
"#{self.class} #{id} deleted by #{destroyed_by.id} <#{destroyed_by.email}>"
|
||||
end
|
||||
Rails.logger.info message
|
||||
return if destroyed_by.nil?
|
||||
|
||||
Rails.logger.info "#{self.class} #{id} deleted by #{destroyed_by.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,34 +4,8 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# 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
|
||||
@@ -4,14 +4,4 @@ class CustomTab < ApplicationRecord
|
||||
belongs_to :enterprise
|
||||
|
||||
validates :title, presence: true, length: { maximum: 20 }
|
||||
|
||||
# Remove any unsupported HTML.
|
||||
def content
|
||||
HtmlSanitizer.sanitize(super)
|
||||
end
|
||||
|
||||
# Remove any unsupported HTML.
|
||||
def content=(html)
|
||||
super(HtmlSanitizer.sanitize(html))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
require "mini_magick"
|
||||
|
||||
class Enterprise < ApplicationRecord
|
||||
SELLS = %w(unspecified none own any).freeze
|
||||
ENTERPRISE_SEARCH_RADIUS = 100
|
||||
@@ -41,13 +39,13 @@ class Enterprise < ApplicationRecord
|
||||
class_name: 'EnterpriseGroup'
|
||||
has_many :producer_properties, foreign_key: 'producer_id', dependent: :destroy
|
||||
has_many :properties, through: :producer_properties
|
||||
has_many :supplied_variants,
|
||||
class_name: 'Spree::Variant', foreign_key: 'supplier_id', dependent: :destroy
|
||||
has_many :supplied_products, through: :supplied_variants, source: :product
|
||||
has_many :supplied_products, class_name: 'Spree::Product',
|
||||
foreign_key: 'supplier_id',
|
||||
dependent: :destroy
|
||||
has_many :supplied_variants, through: :supplied_products, source: :variants
|
||||
has_many :distributed_orders, class_name: 'Spree::Order',
|
||||
foreign_key: 'distributor_id',
|
||||
dependent: :restrict_with_exception
|
||||
|
||||
belongs_to :address, class_name: 'Spree::Address'
|
||||
belongs_to :business_address, optional: true, class_name: 'Spree::Address', dependent: :destroy
|
||||
has_many :enterprise_fees, dependent: :restrict_with_exception
|
||||
@@ -169,7 +167,7 @@ class Enterprise < ApplicationRecord
|
||||
scope :is_distributor, -> { where.not(sells: 'none') }
|
||||
scope :is_hub, -> { where(sells: 'any') }
|
||||
scope :supplying_variant_in, lambda { |variants|
|
||||
joins(:supplied_variants).
|
||||
joins(supplied_products: :variants).
|
||||
where(spree_variants: { id: variants }).
|
||||
select('DISTINCT enterprises.*')
|
||||
}
|
||||
@@ -207,14 +205,14 @@ class Enterprise < ApplicationRecord
|
||||
select('DISTINCT enterprises.*')
|
||||
}
|
||||
|
||||
scope :distributing_variants, lambda { |variants_ids|
|
||||
scope :distributing_products, lambda { |product_ids|
|
||||
exchanges = joins("
|
||||
INNER JOIN exchanges
|
||||
ON (exchanges.receiver_id = enterprises.id AND exchanges.incoming = false)
|
||||
ON (exchanges.receiver_id = enterprises.id AND exchanges.incoming = 'f')
|
||||
").
|
||||
joins('INNER JOIN exchange_variants ON (exchange_variants.exchange_id = exchanges.id)').
|
||||
joins('INNER JOIN spree_variants ON (spree_variants.id = exchange_variants.variant_id)').
|
||||
where(spree_variants: { id: variants_ids }).select('DISTINCT enterprises.id')
|
||||
where(spree_variants: { product_id: product_ids }).select('DISTINCT enterprises.id')
|
||||
|
||||
where(id: exchanges)
|
||||
}
|
||||
@@ -429,9 +427,10 @@ class Enterprise < ApplicationRecord
|
||||
test_permalink = UrlGenerator.to_url(test_permalink)
|
||||
test_permalink = "my-enterprise" if test_permalink.blank?
|
||||
existing = Enterprise.
|
||||
select(:permalink).
|
||||
order(:permalink).
|
||||
where("permalink LIKE ?", "#{test_permalink}%").
|
||||
pluck(:permalink)
|
||||
map(&:permalink)
|
||||
|
||||
if existing.include?(test_permalink)
|
||||
used_indices = existing.map do |p|
|
||||
@@ -599,7 +598,7 @@ class Enterprise < ApplicationRecord
|
||||
# Touch distributors without them touching their distributors.
|
||||
# We avoid an infinite loop and don't need to touch the whole distributor tree.
|
||||
def touch_distributors
|
||||
Enterprise.distributing_variants(supplied_variants.select(:id)).
|
||||
Enterprise.distributing_products(supplied_products.select(:id)).
|
||||
where.not(enterprises: { id: }).
|
||||
update_all(updated_at: Time.zone.now)
|
||||
end
|
||||
|
||||
@@ -108,6 +108,6 @@ class EnterpriseRelationship < ApplicationRecord
|
||||
|
||||
def child_variant_overrides
|
||||
VariantOverride.unscoped.for_hubs(child)
|
||||
.joins(:variant).where(spree_variants: { supplier_id: parent } )
|
||||
.joins(variant: :product).where(spree_products: { supplier_id: parent })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,6 @@ 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
|
||||
|
||||
@@ -54,7 +54,10 @@ module ProductImport
|
||||
if settings.importing_into_inventory?
|
||||
VariantOverride.for_hubs([enterprise_id]).count
|
||||
else
|
||||
Spree::Variant.where(supplier_id: enterprise_id).count
|
||||
Spree::Variant.
|
||||
joins(:product).
|
||||
where(spree_products: { supplier_id: enterprise_id }).
|
||||
count
|
||||
end
|
||||
|
||||
@enterprise_products[enterprise_id] = products_count
|
||||
@@ -162,10 +165,11 @@ module ProductImport
|
||||
return
|
||||
end
|
||||
|
||||
product = Spree::Product.new(supplier_id: entry.enterprise_id)
|
||||
product = Spree::Product.new
|
||||
product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
product.supplier_id = entry.producer_id
|
||||
|
||||
if product.save
|
||||
ensure_variant_updated(product, entry)
|
||||
@@ -224,13 +228,10 @@ module ProductImport
|
||||
# Ensure attributes are correctly copied to a new product's variant
|
||||
variant = product.variants.first
|
||||
variant.display_name = entry.display_name if entry.display_name
|
||||
variant.import_date = @import_time
|
||||
variant.supplier_id = entry.producer_id
|
||||
variant.save
|
||||
|
||||
# on_demand and on_hand require a stock level, which is created after the variant is created
|
||||
variant.on_demand = entry.on_demand if entry.on_demand
|
||||
variant.on_hand = entry.on_hand if entry.on_hand
|
||||
variant.import_date = @import_time
|
||||
variant.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,7 +73,6 @@ module ProductImport
|
||||
# Variant needs a product. Product needs to be assigned first in order for
|
||||
# delegate to work. name= will fail otherwise.
|
||||
new_variant = Spree::Variant.new(product_id:, **variant_attributes)
|
||||
new_variant.supplier_id = entry.producer_id
|
||||
|
||||
new_variant.save
|
||||
if new_variant.persisted?
|
||||
@@ -288,7 +287,9 @@ module ProductImport
|
||||
end
|
||||
|
||||
def inventory_validation(entry)
|
||||
products = Spree::Product.in_supplier(entry.producer_id).where(name: entry.name)
|
||||
products = Spree::Product.where(supplier_id: entry.producer_id,
|
||||
name: entry.name,
|
||||
deleted_at: nil)
|
||||
|
||||
if products.empty?
|
||||
mark_as_invalid(entry, attribute: 'name',
|
||||
@@ -357,7 +358,9 @@ module ProductImport
|
||||
end
|
||||
|
||||
def product_validation(entry)
|
||||
products = Spree::Product.in_supplier(entry.enterprise_id).where(name: entry.name)
|
||||
products = Spree::Product.where(supplier_id: entry.enterprise_id,
|
||||
name: entry.name,
|
||||
deleted_at: nil)
|
||||
|
||||
if products.empty?
|
||||
mark_as_new_product(entry)
|
||||
@@ -377,10 +380,11 @@ module ProductImport
|
||||
end
|
||||
|
||||
def mark_as_new_product(entry)
|
||||
new_product = Spree::Product.new(supplier_id: entry.enterprise_id)
|
||||
new_product = Spree::Product.new
|
||||
new_product.assign_attributes(
|
||||
entry.assignable_attributes.except('id', 'on_hand', 'on_demand', 'display_name')
|
||||
)
|
||||
new_product.supplier_id = entry.producer_id
|
||||
entry.on_hand = 0 if entry.on_hand.nil?
|
||||
|
||||
if new_product.valid?
|
||||
|
||||
@@ -287,6 +287,8 @@ module ProductImport
|
||||
end
|
||||
|
||||
def delete_uploaded_file
|
||||
return unless @file.path == Rails.root.join("tmp/product_import").to_s
|
||||
|
||||
File.delete(@file)
|
||||
end
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ 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
|
||||
@@ -188,22 +189,20 @@ module Spree
|
||||
:seo, :group_buy_options,
|
||||
:bulk_update, :clone, :delete,
|
||||
:destroy], Spree::Product do |product|
|
||||
OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include?(
|
||||
product.variants.first.supplier
|
||||
)
|
||||
OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? product.supplier
|
||||
end
|
||||
|
||||
can [:admin, :index, :bulk_update, :destroy, :destroy_variant, :clone], :products_v3
|
||||
can [:admin, :index, :bulk_update, :destroy, :destroy_variant], :products_v3
|
||||
|
||||
can [:create], Spree::Variant
|
||||
can [:admin, :index, :read, :edit,
|
||||
:update, :search, :delete, :destroy], Spree::Variant do |variant|
|
||||
OpenFoodNetwork::Permissions.new(user).
|
||||
managed_product_enterprises.include? variant.supplier
|
||||
managed_product_enterprises.include? variant.product.supplier
|
||||
end
|
||||
|
||||
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
|
||||
next false unless vo.hub.present? && vo.variant&.supplier.present?
|
||||
next false unless vo.hub.present? && vo.variant&.product&.supplier.present?
|
||||
|
||||
hub_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_hubs.
|
||||
@@ -211,14 +210,14 @@ module Spree
|
||||
|
||||
producer_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_producers.
|
||||
include? vo.variant.supplier
|
||||
include? vo.variant.product.supplier
|
||||
|
||||
hub_auth && producer_auth
|
||||
end
|
||||
|
||||
can [:admin, :create, :update], InventoryItem do |ii|
|
||||
next false unless ii.enterprise.present? &&
|
||||
ii.variant&.supplier.present?
|
||||
ii.variant&.product&.supplier.present?
|
||||
|
||||
hub_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_hubs.
|
||||
@@ -226,7 +225,7 @@ module Spree
|
||||
|
||||
producer_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_producers.
|
||||
include? ii.variant.supplier
|
||||
include? ii.variant.product.supplier
|
||||
|
||||
hub_auth && producer_auth
|
||||
end
|
||||
|
||||
@@ -139,8 +139,5 @@ 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,7 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "mini_magick"
|
||||
|
||||
module Spree
|
||||
class Image < Asset
|
||||
has_one_attached :attachment, service: image_service do |attachment|
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
@@ -15,7 +16,7 @@ module Spree
|
||||
|
||||
belongs_to :variant, -> { with_deleted }, class_name: "Spree::Variant"
|
||||
has_one :product, through: :variant
|
||||
has_one :supplier, through: :variant
|
||||
has_one :supplier, through: :product
|
||||
belongs_to :tax_category, class_name: "Spree::TaxCategory", optional: true
|
||||
|
||||
has_many :adjustments, as: :adjustable, dependent: :destroy
|
||||
@@ -84,11 +85,13 @@ module Spree
|
||||
where(order_cycles: { id: order_cycle })
|
||||
}
|
||||
|
||||
# Here we are simply joining the line item to its variant
|
||||
# We dont use joins here to avoid the default scopes, and with that, include deleted variants
|
||||
# Here we are simply joining the line item to its variant and product
|
||||
# We dont use joins here to avoid the default scopes,
|
||||
# and with that, include deleted variants and deleted products
|
||||
scope :supplied_by_any, lambda { |enterprises|
|
||||
variant_ids = Spree::Variant.unscoped.where(supplier: enterprises).select(:id)
|
||||
where(variant_id: variant_ids)
|
||||
product_ids = Spree::Product.unscoped.where(supplier_id: enterprises).select(:id)
|
||||
variant_ids = Spree::Variant.unscoped.where(product_id: product_ids).select(:id)
|
||||
where(spree_line_items: { variant_id: variant_ids })
|
||||
}
|
||||
|
||||
scope :with_tax, -> {
|
||||
|
||||
@@ -5,15 +5,19 @@ require 'open_food_network/property_merge'
|
||||
# PRODUCTS
|
||||
# Products represent an entity for sale in a store.
|
||||
# Products can have variations, called variants
|
||||
# Products properties include description, meta_keywork, etc. that do not change by variant.
|
||||
# Products properties include description, permalink, availability,
|
||||
# shipping category, etc. that do not change by variant.
|
||||
#
|
||||
# MASTER VARIANT
|
||||
# Every product has one master variant, which stores master price and sku, size and weight, etc.
|
||||
# Price, SKU, size, weight, etc. are all delegated to the master variant.
|
||||
# Contains on_hand inventory levels only when there are no variants for the product.
|
||||
#
|
||||
# VARIANTS
|
||||
# Every product has at least one variant (standard variant), which stores price and availability,
|
||||
# shipping category, sku, size and weight, etc.
|
||||
# All variants can access the product name, description, and meta_keyword directly (via reverse
|
||||
# delegation).
|
||||
# All variants can access the product properties directly (via reverse delegation).
|
||||
# Inventory units are tied to Variant.
|
||||
# All variants have option values and may have inventory units.
|
||||
# The master variant can have inventory units, but not option values.
|
||||
# All other variants have option values and may have inventory units.
|
||||
# Sum of on_hand each variant's inventory level determine "on_hand" level for the product.
|
||||
#
|
||||
module Spree
|
||||
@@ -22,14 +26,15 @@ module Spree
|
||||
include LogDestroyPerformer
|
||||
|
||||
self.belongs_to_required_by_default = false
|
||||
self.ignored_columns += [:supplier_id]
|
||||
|
||||
acts_as_paranoid
|
||||
|
||||
searchable_attributes :meta_keywords, :sku
|
||||
searchable_associations :properties, :variants
|
||||
searchable_attributes :supplier_id, :meta_keywords, :sku
|
||||
searchable_associations :supplier, :properties, :variants
|
||||
searchable_scopes :active, :with_properties
|
||||
|
||||
belongs_to :supplier, class_name: 'Enterprise', optional: false, touch: true
|
||||
|
||||
has_one :image, class_name: "Spree::Image", as: :viewable, dependent: :destroy
|
||||
|
||||
has_many :product_properties, dependent: :destroy
|
||||
@@ -40,6 +45,7 @@ module Spree
|
||||
has_many :prices, -> { order('spree_variants.id, currency') }, through: :variants
|
||||
|
||||
has_many :stock_items, through: :variants
|
||||
has_many :supplier_properties, through: :supplier, source: :properties
|
||||
has_many :variant_images, -> { order(:position) }, source: :images,
|
||||
through: :variants
|
||||
|
||||
@@ -62,28 +68,28 @@ module Spree
|
||||
accepts_nested_attributes_for :image
|
||||
accepts_nested_attributes_for :product_properties,
|
||||
allow_destroy: true,
|
||||
reject_if: ->(pp) { pp[:property_name].blank? }
|
||||
reject_if: lambda { |pp| pp[:property_name].blank? }
|
||||
|
||||
# Transient attributes used temporarily when creating a new product,
|
||||
# these values are persisted on the product's variant
|
||||
attr_accessor :price, :display_as, :unit_value, :unit_description, :tax_category_id,
|
||||
:shipping_category_id, :primary_taxon_id, :supplier_id
|
||||
:shipping_category_id, :primary_taxon_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
|
||||
after_save :update_units
|
||||
after_touch :touch_supplier
|
||||
|
||||
# -- Scopes
|
||||
scope :with_properties, ->(*property_ids) {
|
||||
left_outer_joins(:product_properties).
|
||||
left_outer_joins(:supplier_properties).
|
||||
where(inherits_properties: true).
|
||||
where(spree_product_properties: { property_id: property_ids })
|
||||
where(producer_properties: { property_id: property_ids }).
|
||||
or(
|
||||
where(spree_product_properties: { property_id: property_ids })
|
||||
)
|
||||
}
|
||||
|
||||
scope :with_order_cycles_outer, lambda {
|
||||
scope :with_order_cycles_outer, -> {
|
||||
joins("
|
||||
LEFT OUTER JOIN spree_variants AS o_spree_variants
|
||||
ON (o_spree_variants.product_id = spree_products.id)").
|
||||
@@ -105,7 +111,9 @@ module Spree
|
||||
where(import_date: import_date.all_day))
|
||||
}
|
||||
|
||||
scope :with_order_cycles_inner, -> { joins(variants: { exchanges: :order_cycle }) }
|
||||
scope :with_order_cycles_inner, -> {
|
||||
joins(variants: { exchanges: :order_cycle })
|
||||
}
|
||||
|
||||
scope :visible_for, lambda { |enterprise|
|
||||
joins('
|
||||
@@ -118,9 +126,8 @@ module Spree
|
||||
distinct
|
||||
}
|
||||
|
||||
scope :in_supplier, lambda { |supplier|
|
||||
distinct.joins(:variants).where(spree_variants: { supplier: })
|
||||
}
|
||||
# -- Scopes
|
||||
scope :in_supplier, lambda { |supplier| where(supplier_id: supplier) }
|
||||
|
||||
# Products distributed via the given distributor through an OC
|
||||
scope :in_distributor, lambda { |distributor|
|
||||
@@ -137,6 +144,18 @@ module Spree
|
||||
distinct
|
||||
}
|
||||
|
||||
# Products supplied by a given enterprise or distributed via that enterprise through an OC
|
||||
scope :in_supplier_or_distributor, lambda { |enterprise|
|
||||
enterprise = enterprise.respond_to?(:id) ? enterprise.id : enterprise.to_i
|
||||
|
||||
with_order_cycles_outer.
|
||||
where("
|
||||
spree_products.supplier_id = ?
|
||||
OR (o_exchanges.incoming = ? AND o_exchanges.receiver_id = ?)
|
||||
", enterprise, false, enterprise).
|
||||
select('distinct spree_products.*')
|
||||
}
|
||||
|
||||
# Products distributed by the given order cycle
|
||||
scope :in_order_cycle, lambda { |order_cycle|
|
||||
with_order_cycles_inner.
|
||||
@@ -151,17 +170,27 @@ module Spree
|
||||
where.not(order_cycles: { id: nil })
|
||||
}
|
||||
|
||||
scope :by_producer, -> { joins(variants: :supplier).order('enterprises.name') }
|
||||
scope :by_name, -> { order('spree_products.name') }
|
||||
scope :by_producer, -> { joins(:supplier).order('enterprises.name') }
|
||||
scope :by_name, -> { order('name') }
|
||||
|
||||
scope :managed_by, lambda { |user|
|
||||
if user.has_spree_role?('admin')
|
||||
where(nil)
|
||||
else
|
||||
in_supplier(user.enterprises)
|
||||
where(supplier_id: user.enterprises.select("enterprises.id"))
|
||||
end
|
||||
}
|
||||
|
||||
scope :stockable_by, lambda { |enterprise|
|
||||
return where('1=0') if enterprise.blank?
|
||||
|
||||
permitted_producer_ids = EnterpriseRelationship.joins(:parent).permitting(enterprise.id)
|
||||
.with_permission(:add_to_order_cycle)
|
||||
.where(enterprises: { is_primary_producer: true })
|
||||
.pluck(:parent_id)
|
||||
where(spree_products: { supplier_id: [enterprise.id] | permitted_producer_ids })
|
||||
}
|
||||
|
||||
scope :active, lambda { where(spree_products: { deleted_at: nil }) }
|
||||
|
||||
def self.group_by_products_id
|
||||
@@ -207,10 +236,7 @@ module Spree
|
||||
ps = product_properties.all
|
||||
|
||||
if inherits_properties
|
||||
# NOTE: Set the supplier as the first variant supplier. If variants have different supplier,
|
||||
# result might not be correct
|
||||
supplier = variants.first.supplier
|
||||
ps = OpenFoodNetwork::PropertyMerge.merge(ps, supplier&.producer_properties || [])
|
||||
ps = OpenFoodNetwork::PropertyMerge.merge(ps, supplier.producer_properties)
|
||||
end
|
||||
|
||||
ps.
|
||||
@@ -237,6 +263,8 @@ module Spree
|
||||
|
||||
def destruction
|
||||
transaction do
|
||||
touch_distributors
|
||||
|
||||
ExchangeVariant.
|
||||
where(exchange_variants: { variant_id: variants.with_deleted.
|
||||
select(:id) }).destroy_all
|
||||
@@ -257,7 +285,6 @@ module Spree
|
||||
variant.tax_category_id = tax_category_id
|
||||
variant.shipping_category_id = shipping_category_id
|
||||
variant.primary_taxon_id = primary_taxon_id
|
||||
variant.supplier_id = supplier_id
|
||||
variants << variant
|
||||
end
|
||||
|
||||
@@ -265,7 +292,6 @@ 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
|
||||
@@ -290,44 +316,14 @@ 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?
|
||||
|
||||
variants.each do |v|
|
||||
if v.persisted?
|
||||
v.update_units
|
||||
else
|
||||
v.assign_units
|
||||
end
|
||||
end
|
||||
variants.each(&:update_units)
|
||||
end
|
||||
|
||||
def touch_supplier
|
||||
return if variants.empty?
|
||||
|
||||
# Assume the product supplier is the supplier of the first variant
|
||||
# Will breack if product has mutiple variants with different supplier
|
||||
first_variant = variants.first
|
||||
|
||||
# The variant is invalid if no supplier is present, but this method can be triggered when
|
||||
# importing product. In this scenario the variant has not been updated with the supplier yet
|
||||
# hence the check.
|
||||
first_variant.supplier.touch if first_variant.supplier.present?
|
||||
def touch_distributors
|
||||
Enterprise.distributing_products(id).each(&:touch)
|
||||
end
|
||||
|
||||
def validate_image
|
||||
|
||||
@@ -6,8 +6,6 @@ module Spree
|
||||
has_many :products, through: :product_properties
|
||||
has_many :producer_properties, dependent: :destroy
|
||||
|
||||
after_touch :touch_producer_properties
|
||||
|
||||
validates :name, :presentation, presence: true
|
||||
|
||||
scope :sorted, -> { order(:name) }
|
||||
@@ -15,11 +13,5 @@ module Spree
|
||||
def property
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def touch_producer_properties
|
||||
producer_properties.each(&:touch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,7 +31,7 @@ module Spree
|
||||
return [] if order.distributor && !order.distributor.charges_sales_tax
|
||||
return [] unless order.tax_zone
|
||||
|
||||
includes(zone: { zone_members: :zoneable }).load.select do |rate|
|
||||
all.includes(zone: { zone_members: :zoneable }).load.select do |rate|
|
||||
rate.potentially_applicable?(order.tax_zone)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,11 +2,19 @@
|
||||
|
||||
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
|
||||
@@ -23,13 +31,33 @@ 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
|
||||
taxons = {}
|
||||
|
||||
Spree::Taxon.
|
||||
joins(variants: :supplier).
|
||||
joins(products: :supplier).
|
||||
select('spree_taxons.*, enterprises.id AS enterprise_id').
|
||||
each do |t|
|
||||
taxons[t.enterprise_id.to_i] ||= Set.new
|
||||
@@ -62,5 +90,13 @@ 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
|
||||
|
||||
27
app/models/spree/taxonomy.rb
Normal file
27
app/models/spree/taxonomy.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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
|
||||
@@ -96,7 +96,7 @@ module Spree
|
||||
end
|
||||
|
||||
def build_enterprise_roles
|
||||
Enterprise.find_each do |enterprise|
|
||||
Enterprise.all.find_each do |enterprise|
|
||||
unless enterprise_roles.find_by enterprise_id: enterprise.id
|
||||
enterprise_roles.build(enterprise:)
|
||||
end
|
||||
@@ -156,12 +156,6 @@ 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?
|
||||
|
||||
@@ -13,8 +13,8 @@ module Spree
|
||||
|
||||
acts_as_paranoid
|
||||
|
||||
searchable_attributes :sku, :display_as, :display_name, :primary_taxon_id, :supplier_id
|
||||
searchable_associations :product, :default_price, :primary_taxon, :supplier
|
||||
searchable_attributes :sku, :display_as, :display_name, :primary_taxon_id
|
||||
searchable_associations :product, :default_price, :primary_taxon
|
||||
searchable_scopes :active, :deleted
|
||||
|
||||
NAME_FIELDS = ["display_name", "display_as", "weight", "unit_value", "unit_description"].freeze
|
||||
@@ -23,15 +23,12 @@ module Spree
|
||||
meta_keywords
|
||||
variants_display_as
|
||||
variants_display_name
|
||||
variants_supplier_name).join('_or_')}_cont".freeze
|
||||
supplier_name).join('_or_')}_cont".freeze
|
||||
|
||||
belongs_to :product, -> {
|
||||
with_deleted
|
||||
}, touch: true, class_name: 'Spree::Product', optional: false
|
||||
belongs_to :product, -> { with_deleted }, touch: true, class_name: 'Spree::Product'
|
||||
belongs_to :tax_category, class_name: 'Spree::TaxCategory'
|
||||
belongs_to :shipping_category, class_name: 'Spree::ShippingCategory', optional: false
|
||||
belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true, optional: false
|
||||
belongs_to :supplier, class_name: 'Enterprise', optional: false, touch: true
|
||||
|
||||
delegate :name, :name=, :description, :description=, :meta_keywords, to: :product
|
||||
|
||||
@@ -61,7 +58,6 @@ module Spree
|
||||
has_many :variant_overrides, dependent: :destroy
|
||||
has_many :inventory_items, dependent: :destroy
|
||||
has_many :semantic_links, dependent: :delete_all
|
||||
has_many :supplier_properties, through: :supplier, source: :properties
|
||||
|
||||
localize_number :price, :weight
|
||||
|
||||
@@ -69,7 +65,7 @@ module Spree
|
||||
validate :check_currency
|
||||
validates :price, numericality: { greater_than_or_equal_to: 0 }, presence: true
|
||||
validates :tax_category, presence: true,
|
||||
if: proc { Spree::Config.products_require_tax_category }
|
||||
if: proc { Spree::Config[:products_require_tax_category] }
|
||||
|
||||
validates :unit_value, presence: true, if: ->(variant) {
|
||||
%w(weight volume).include?(variant.product&.variant_unit)
|
||||
@@ -87,6 +83,7 @@ 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?
|
||||
@@ -97,7 +94,7 @@ module Spree
|
||||
after_save :save_default_price
|
||||
|
||||
# default variant scope only lists non-deleted variants
|
||||
scope :deleted, -> { where.not(deleted_at: nil) }
|
||||
scope :deleted, lambda { where.not(deleted_at: nil) }
|
||||
|
||||
scope :with_order_cycles_inner, -> { joins(exchanges: :order_cycle) }
|
||||
|
||||
@@ -142,9 +139,11 @@ module Spree
|
||||
.where("o_inventory_items.id IS NULL OR o_inventory_items.visible = (?)", true)
|
||||
}
|
||||
|
||||
scope :with_properties, lambda { |property_ids|
|
||||
left_outer_joins(:supplier_properties).
|
||||
where(producer_properties: { property_id: property_ids })
|
||||
scope :stockable_by, lambda { |enterprise|
|
||||
return where("1=0") if enterprise.blank?
|
||||
|
||||
joins(:product).
|
||||
where(spree_products: { id: Spree::Product.stockable_by(enterprise).pluck(:id) })
|
||||
}
|
||||
|
||||
# Define sope as class method to allow chaining with other scopes filtering id.
|
||||
@@ -216,6 +215,10 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def assign_related_taxon
|
||||
self.primary_taxon ||= product.variants.last&.primary_taxon
|
||||
end
|
||||
|
||||
def check_currency
|
||||
return unless currency.nil?
|
||||
|
||||
@@ -235,7 +238,7 @@ module Spree
|
||||
end
|
||||
|
||||
def create_stock_items
|
||||
StockLocation.find_each do |stock_location|
|
||||
StockLocation.all.find_each do |stock_location|
|
||||
stock_location.propagate_variant(self)
|
||||
end
|
||||
end
|
||||
@@ -247,16 +250,8 @@ module Spree
|
||||
end
|
||||
|
||||
def destruction
|
||||
transaction do
|
||||
# Even tough Enterprise will touch associated variant distributors when touched,
|
||||
# the variant will be removed from the exchange by the time it's triggered,
|
||||
# so it won't be able to find the deleted variant's distributors.
|
||||
# This why we do it here
|
||||
touch_distributors
|
||||
|
||||
exchange_variants.reload.destroy_all
|
||||
yield
|
||||
end
|
||||
exchange_variants.reload.destroy_all
|
||||
yield
|
||||
end
|
||||
|
||||
def ensure_unit_value
|
||||
@@ -273,9 +268,5 @@ module Spree
|
||||
def convert_variant_weight_to_decimal
|
||||
self.weight = weight.to_d
|
||||
end
|
||||
|
||||
def touch_distributors
|
||||
Enterprise.distributing_variants(id).each(&:touch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -52,14 +52,11 @@ 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?
|
||||
|
||||
@@ -31,18 +31,16 @@ class ProductScopeQuery
|
||||
product_scope.find(@params[:product_id])
|
||||
end
|
||||
|
||||
def products_for_producers
|
||||
def paged_products_for_producers
|
||||
producer_ids = OpenFoodNetwork::Permissions.new(@user).
|
||||
variant_override_producers.by_name.select('enterprises.id')
|
||||
|
||||
# Use `order("enterprises.name")` instead of `by_producer scope`, the scope adds a join
|
||||
# on variants which messes our query
|
||||
Spree::Product.where(nil).
|
||||
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)
|
||||
includes(variants: [:product, :default_price, :stock_items]).
|
||||
where(supplier_id: producer_ids).
|
||||
by_producer.by_name.
|
||||
ransack(@params[:q]).result
|
||||
end
|
||||
|
||||
def product_scope
|
||||
|
||||
@@ -33,16 +33,23 @@ module Admin
|
||||
end
|
||||
|
||||
def bulk_invoice(params)
|
||||
visible_orders = bulk_load_orders(params)
|
||||
visible_orders = editable_orders.invoiceable.where(id: params[:bulk_ids])
|
||||
|
||||
return if notify_if_abn_related_issue(visible_orders)
|
||||
if Spree::Config.enterprise_number_required_on_invoices?
|
||||
distributors_without_abn = Enterprise.where(
|
||||
id: visible_orders.select(:distributor_id),
|
||||
abn: nil,
|
||||
)
|
||||
|
||||
file_id = "#{Time.zone.now.to_i}-#{SecureRandom.hex(2)}"
|
||||
if distributors_without_abn.exists?
|
||||
render_business_number_required_error(distributors_without_abn)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
cable_ready.append(
|
||||
selector: "#orders-index",
|
||||
html: render(partial: "spree/admin/orders/bulk/invoice_modal",
|
||||
locals: { invoice_url: "/admin/orders/invoices/#{file_id}" })
|
||||
html: render(partial: "spree/admin/orders/bulk/invoice_modal")
|
||||
).broadcast
|
||||
|
||||
# Preserve order of bulk_ids.
|
||||
@@ -52,7 +59,7 @@ module Admin
|
||||
|
||||
BulkInvoiceJob.perform_later(
|
||||
visible_order_ids,
|
||||
"tmp/invoices/#{file_id}.pdf",
|
||||
"tmp/invoices/#{Time.zone.now.to_i}-#{SecureRandom.hex(2)}.pdf",
|
||||
channel: SessionChannel.for_request(request),
|
||||
current_user_id: current_user.id
|
||||
)
|
||||
@@ -127,35 +134,5 @@ module Admin
|
||||
enterprise_name: distributor_names.join(", "))
|
||||
morph_admin_flashes
|
||||
end
|
||||
|
||||
def bulk_load_orders(params)
|
||||
editable_orders.invoiceable.where(id: params[:bulk_ids])
|
||||
end
|
||||
|
||||
def notify_if_abn_related_issue(orders)
|
||||
return false unless abn_required?
|
||||
|
||||
distributors = distributors_without_abn(orders)
|
||||
return false if distributors.empty?
|
||||
|
||||
render_business_number_required_error(distributors)
|
||||
true
|
||||
end
|
||||
|
||||
def abn_required?
|
||||
Spree::Config.enterprise_number_required_on_invoices?
|
||||
end
|
||||
|
||||
def distributors_without_abn(orders)
|
||||
abn = if OpenFoodNetwork::FeatureToggle.enabled?(:invoices)
|
||||
[nil, ""]
|
||||
else
|
||||
[nil]
|
||||
end
|
||||
Enterprise.where(
|
||||
id: orders.select(:distributor_id),
|
||||
abn:,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
177
app/reflexes/products_reflex.rb
Normal file
177
app/reflexes/products_reflex.rb
Normal file
@@ -0,0 +1,177 @@
|
||||
# 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!(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
|
||||
@@ -7,7 +7,7 @@ module Api
|
||||
attributes :name, :supplier_name, :image_url, :variants
|
||||
|
||||
def supplier_name
|
||||
object.variants.first.supplier&.name
|
||||
object.supplier&.name
|
||||
end
|
||||
|
||||
def image_url
|
||||
|
||||
@@ -9,7 +9,7 @@ module Api
|
||||
has_one :order, serializer: Api::Admin::IdSerializer
|
||||
|
||||
def supplier
|
||||
{ id: object.supplier.id }
|
||||
{ id: object.product.supplier_id }
|
||||
end
|
||||
|
||||
def units_product
|
||||
|
||||
@@ -7,6 +7,8 @@ module Api
|
||||
:inherits_properties, :on_hand, :price, :import_date, :image_url,
|
||||
:thumb_url, :variants
|
||||
|
||||
has_one :supplier, key: :producer_id, embed: :id
|
||||
|
||||
def variants
|
||||
ActiveModel::ArraySerializer.new(
|
||||
object.variants,
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
module Api
|
||||
module Admin
|
||||
class ProductSimpleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name
|
||||
attributes :id, :name, :producer_id
|
||||
|
||||
has_many :variants, key: :variants, serializer: Api::Admin::VariantSimpleSerializer
|
||||
|
||||
def producer_id
|
||||
object.supplier_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
module Api
|
||||
module Admin
|
||||
class TaxonSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name
|
||||
attributes :id, :name, :pretty_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,6 @@ module Api
|
||||
:price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name
|
||||
|
||||
has_one :primary_taxon, key: :category_id, embed: :id
|
||||
has_one :supplier, key: :producer_id, embed: :id
|
||||
|
||||
def name
|
||||
if object.full_name.present?
|
||||
@@ -32,7 +31,7 @@ module Api
|
||||
end
|
||||
|
||||
def producer_name
|
||||
object.supplier.name
|
||||
object.product.supplier.name
|
||||
end
|
||||
|
||||
def image
|
||||
|
||||
@@ -6,7 +6,7 @@ module Api
|
||||
attributes :id, :name, :import_date,
|
||||
:options_text, :unit_value, :unit_description, :unit_to_display,
|
||||
:display_as, :display_name, :name_to_display,
|
||||
:price, :on_demand, :on_hand, :producer_id
|
||||
:price, :on_demand, :on_hand
|
||||
|
||||
has_many :variant_overrides
|
||||
|
||||
@@ -27,10 +27,6 @@ module Api
|
||||
def price
|
||||
object.price.nil? ? 0.to_f : object.price
|
||||
end
|
||||
|
||||
def producer_id
|
||||
object.supplier_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ class Api::ProductSerializer < ActiveModel::Serializer
|
||||
has_many :variants, serializer: Api::VariantSerializer
|
||||
|
||||
has_one :image, serializer: Api::ImageSerializer
|
||||
has_one :supplier, serializer: Api::IdSerializer
|
||||
|
||||
# return an unformatted descripton
|
||||
def description
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user