Compare commits

..

1 Commits

Author SHA1 Message Date
Maikel
ac7a36416b Revert "Allow negative stock levels for on-demand products" 2024-06-19 15:22:25 +10:00
842 changed files with 20182 additions and 31550 deletions

9
.env
View File

@@ -61,12 +61,3 @@ SMTP_PASSWORD="f00d"
# NEW_RELIC_AGENT_ENABLED=true
# NEW_RELIC_APP_NAME="Open Food Network"
# NEW_RELIC_LICENSE_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Database encryption configuration, required for VINE connected app
# Generate with bin/rails db:encryption:init
# ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# VINE API settings
# VINE_API_URL="https://vine-staging.openfoodnetwork.org.au/api/v1"

View File

@@ -24,8 +24,3 @@ SITE_URL="0.0.0.0:3000"
RACK_TIMEOUT_SERVICE_TIMEOUT="0"
RACK_TIMEOUT_WAIT_TIMEOUT="0"
RACK_TIMEOUT_WAIT_OVERTIME="0"
# Database encryption configuration, required for VINE connected app
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="dev_primary_key"
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="dev_determinnistic_key"
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="dev_derivation_salt"

View File

@@ -16,9 +16,5 @@ STRIPE_PUBLIC_TEST_API_KEY="bogus_stripe_publishable_key"
SITE_URL="test.host"
OPENID_APP_ID="test-provider"
OPENID_APP_SECRET="dummy-openid-app-secret-token"
OPENID_APP_SECRET="12345"
OPENID_REFRESH_TOKEN="dummy-refresh-token"
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="test_primary_key"
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="test_deterministic_key"
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="test_derivation_salt"

View File

@@ -7,11 +7,10 @@ assignees: ''
---
## 1. Drafting on Friday
## 1. Preparation on Thursday
- [ ] Merge pull requests in the [Ready To Go] column
- [ ] Include translations: `script/release/update_locales`
- You need the [Transifex Client] installed on your local dev environement to run the script.
- [ ] Increment version number: `git push upstream HEAD:refs/tags/vX.Y.Z`
- Major: if server changes are required (eg. provision with ofn-install)
- Minor: larger change that is irreversible (eg. migration deleting data)
@@ -26,9 +25,8 @@ assignees: ''
- [ ] Move this issue to Test Ready.
- [ ] Notify `@testers` in [#testing].
- [ ] Test build: [Deploy to Staging] with release tag.
- [ ] Notify a deployer to deploy it
## 3. Deployment at beginning of week
## 3. Finish on Tuesday
- [ ] Publish and notify [#global-community] (this is automatically posted with a plugin)
- [ ] Deploy the new release to all managed instances.
@@ -41,7 +39,7 @@ assignees: ''
</details>
- [ ] Notify [#instance-managers]:
> @instance_managers The new release has been deployed.
- [ ] [Create issue] for next release and confirm with next release drafter in [#delivery-circle].
- [ ] [Create issue] for next release and confirm with next release manager in [#core-devs].
The full process is described at https://github.com/openfoodfoundation/openfoodnetwork/wiki/Releasing.
@@ -54,5 +52,4 @@ The full process is described at https://github.com/openfoodfoundation/openfoodn
[Deploy to Staging]: https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/stage.yml
[#global-community]: https://app.slack.com/client/T02G54U79/C59ADD8F2
[Create issue]: https://github.com/openfoodfoundation/openfoodnetwork/issues/new?assignees=&labels=&projects=&template=release.md&title=Release
[#delivery-circle]: https://openfoodnetwork.slack.com/archives/C01T75H6G0Z
[Transifex Client]: https://developers.transifex.com/docs/cli
[#core-devs]: https://openfoodnetwork.slack.com/archives/GK2T38QPJ

View File

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

View File

@@ -3,7 +3,7 @@ name: Build
on:
workflow_dispatch:
push:
branches-ignore:
branches-ignore:
- 'dependabot/**'
pull_request:
@@ -47,7 +47,7 @@ jobs:
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
with:
redis-version: 6
- name: Set up Ruby
@@ -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
@@ -81,20 +80,11 @@ jobs:
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}"
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}"
run: |
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
bin/rake knapsack_pro:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
with:
name: simplecov-chunk-controllers-${{ matrix.ci_node_index }}
path: coverage/*.*
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
if-no-files-found: ignore
include-hidden-files: true
models:
runs-on: ubuntu-22.04
services:
@@ -125,7 +115,7 @@ jobs:
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
with:
redis-version: 6
- name: Set up Ruby
@@ -150,19 +140,10 @@ jobs:
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}"
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}"
run: |
bin/rake knapsack_pro:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
with:
name: simplecov-chunk-models-${{ matrix.ci_node_index }}
path: coverage/*.*
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
if-no-files-found: ignore
include-hidden-files: true
system_admin:
runs-on: ubuntu-22.04
services:
@@ -193,7 +174,7 @@ jobs:
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
with:
redis-version: 6
- name: Set up Ruby
@@ -204,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
@@ -227,25 +207,16 @@ jobs:
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
run: |
bin/rake knapsack_pro:queue:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
with:
name: simplecov-chunk-system-admin-${{ matrix.ci_node_index }}
path: coverage/*.*
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
if-no-files-found: ignore
include-hidden-files: true
- name: Archive failed tests screenshots
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: failed-admin_${{ matrix.ci_node_index }}-tests-screenshots
name: failed-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
@@ -274,13 +245,13 @@ jobs:
ci_node_total: [12]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
with:
redis-version: 6
- name: Set up Ruby
@@ -291,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
@@ -314,25 +284,16 @@ jobs:
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}"
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}"
run: |
bin/rake knapsack_pro:queue:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
with:
name: simplecov-chunk-system-consumer-${{ matrix.ci_node_index }}
path: coverage/*.*
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
if-no-files-found: ignore
include-hidden-files: true
- name: Archive failed tests screenshots
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: failed-consumer_${{ matrix.ci_node_index }}-tests-screenshots
name: failed-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
@@ -367,7 +328,7 @@ jobs:
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
with:
redis-version: 6
- name: Set up Ruby
@@ -379,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
@@ -407,14 +367,14 @@ jobs:
run: |
bin/rake knapsack_pro:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
- name: Archive failed tests screenshots
if: failure()
uses: actions/upload-artifact@v3
with:
name: simplecov-chunk-engines-${{ matrix.ci_node_index }}
path: coverage/*.*
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
name: failed-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
include-hidden-files: true
test_the_rest:
runs-on: ubuntu-22.04
@@ -446,7 +406,7 @@ jobs:
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
with:
redis-version: 6
- name: Set up Ruby
@@ -458,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
@@ -484,15 +443,6 @@ jobs:
run: |
bin/rake knapsack_pro:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
with:
name: simplecov-chunk-the-rest-${{ matrix.ci_node_index }}
path: coverage/*.*
retention-days: 2 # doesn't need to be long, because it's the combined results that matter
if-no-files-found: ignore
include-hidden-files: true
non_knapsack_jest_karma:
runs-on: ubuntu-22.04
services:
@@ -520,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
@@ -530,39 +479,3 @@ jobs:
- name: Run jest tests
run: yarn jest
collate_simplecov_results:
runs-on: ubuntu-22.04
needs:
- controllers
- models
- engines
- system_admin
- system_consumer
- test_the_rest
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Download individual results from individual runners
uses: actions/download-artifact@v4
with:
pattern: simplecov-chunk-*
path: tmp/simplecov
merge-multiple: true
- name: collate results from each of the workers
run: bundle exec rake 'simplecov:collate_results[tmp/simplecov]'
- name: Upload collated results
uses: actions/upload-artifact@v4
with:
name: combined-simplecov-report
path: coverage/**/*.*
retention-days: 7
if-no-files-found: ignore
include-hidden-files: true

View File

@@ -14,12 +14,12 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: docker/build
- run: docker compose up --detach
- run: docker-compose up --detach
- run: until curl -f -s http://localhost:3000; do echo "waiting for api server"; sleep 1; done
- run: docker compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="update spree_users set spree_api_key='testing' where login='ofn@example.com'"
- run: docker-compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="update spree_users set spree_api_key='testing' where login='ofn@example.com'"
# equivalent to Flipper.enable(:api_v1)
- run: docker compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="insert into flipper_features (key, created_at, updated_at) values ('api_v1', localtimestamp, localtimestamp)"
- run: docker compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="insert into flipper_gates (feature_key, key, value, created_at, updated_at) values ('api_v1', 'boolean', 'true', localtimestamp, localtimestamp)"
- run: docker-compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="insert into flipper_features (key, created_at, updated_at) values ('api_v1', localtimestamp, localtimestamp)"
- run: docker-compose exec -T db psql postgresql://ofn:f00d@localhost:5432/open_food_network_dev --command="insert into flipper_gates (feature_key, key, value, created_at, updated_at) values ('api_v1', 'boolean', 'true', localtimestamp, localtimestamp)"
# Run Mayhem for API
- name: Run Mayhem for API

View File

@@ -5,7 +5,6 @@
*.yaml
*.json
*.html
**/*.rb
# JS
# Enabled: app/webpacker/controllers/*.js and app/webpacker/packs/*.js
@@ -28,5 +27,6 @@ postcss.config.js
/coverage/
/engines/
/public/
/spec/
/tmp/
/vendor/

View File

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

View File

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

View File

@@ -14,6 +14,4 @@ SimpleCov.start 'rails' do
add_filter '/log'
add_filter '/db'
add_filter '/lib/tasks/sample_data/'
formatter SimpleCov::Formatter::SimpleFormatter
end

View File

@@ -31,7 +31,7 @@ This project needs specific ruby/bundler versions as well as node/yarn specific
* Install or change your Ruby version according to the one specified at [.ruby-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.ruby-version) file.
- To manage versions, it's recommended to use [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://rvm.io/).
* Install [nodenv](https://github.com/nodenv/nodenv) to ensure the correct [.node-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.node-version) is used.
- [nodenv](https://github.com/nodenv/nodenv) is recommended as a node version manager.
- [nodevn](https://github.com/nodenv/nodenv) is recommended as a node version manager.
* PostgreSQL database
* Redis (for background jobs)
* Chrome (for testing)

View File

@@ -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'
@@ -104,7 +105,6 @@ gem 'sidekiq-scheduler'
gem "cable_ready"
gem "stimulus_reflex"
gem "turbo_power"
gem "turbo-rails"
gem 'combine_pdf'

View File

@@ -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)
@@ -246,7 +248,7 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
datafoodconsortium-connector (1.0.0.pre.alpha.13)
datafoodconsortium-connector (1.0.0.pre.alpha.12)
virtual_assembly-semantizer (~> 1.0, >= 1.0.5)
date (3.3.4)
debug (1.9.2)
@@ -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)
@@ -379,14 +381,13 @@ GEM
bindata
faraday (~> 2.0)
faraday-follow_redirects
json-ld (3.3.2)
json-ld (3.3.1)
htmlentities (~> 4.3)
json-canonicalization (~> 1.0)
link_header (~> 0.0, >= 0.0.8)
multi_json (~> 1.15)
rack (>= 2.2, < 4)
rdf (~> 3.3)
rexml (~> 3.2)
json-schema (4.1.1)
addressable (>= 2.8)
json_spec (1.1.5)
@@ -416,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)
@@ -448,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)
@@ -786,8 +787,6 @@ GEM
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0)
turbo_power (0.6.2)
turbo-rails (>= 1.3.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
@@ -838,7 +837,7 @@ GEM
websocket-extensions (0.1.5)
whenever (1.0.0)
chronic (>= 0.6.3)
wicked_pdf (2.8.1)
wicked_pdf (2.6.3)
activesupport
wkhtmltopdf-binary (0.12.6.7)
xml-simple (1.1.8)
@@ -864,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
@@ -976,7 +976,6 @@ DEPENDENCIES
stripe
timecop
turbo-rails
turbo_power
valid_email2
validates_lengths_from_database
vcr

View File

@@ -10,11 +10,11 @@
//= require jquery.ui.all
//= require jquery.powertip
//= require jquery.cookie
//= require jquery.jstree/jquery.jstree
//= require jquery.vAlign
//= require angular
//= require angular-resource
//= require angular-animate
//= require angular-sanitize
//= require angularjs-file-upload
//= require ../shared/ng-infinite-scroll.min.js
//= require ../shared/ng-tags-input.min.js
@@ -61,6 +61,11 @@
//= require ./variant_overrides/variant_overrides
// text, dates and translations
//= require textAngular-rangy.min.js
// This replaces angular-sanitize. We should include only one.
// https://github.com/textAngular/textAngular#where-to-get-it
//= require textAngular-sanitize.min.js
//= require textAngular.min.js
//= require i18n/translations
//= require darkswarm/i18n.translate.js

View File

@@ -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 = ->
@@ -187,8 +183,9 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
product.variants.length > 0
$scope.hasUnit = (variant) ->
variant.variant_unit_with_scale?
$scope.hasUnit = (product) ->
product.variant_unit_with_scale?
$scope.variantSaved = (variant) ->
variant.hasOwnProperty('id') && variant.id > 0
@@ -220,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
@@ -241,28 +238,32 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$window.location = destination
$scope.packProduct = (product) ->
if product.variant_unit_with_scale
match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
product.variant_unit = match[1]
product.variant_unit_scale = parseFloat(match[2])
else
product.variant_unit = product.variant_unit_with_scale
product.variant_unit_scale = null
else
product.variant_unit = product.variant_unit_scale = null
if product.variants
for id, variant of product.variants
$scope.packVariant variant
$scope.packVariant product, variant
$scope.packVariant = (variant) ->
if variant.variant_unit_with_scale
match = variant.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
variant.variant_unit = match[1]
variant.variant_unit_scale = parseFloat(match[2])
else
variant.variant_unit = variant.variant_unit_with_scale
variant.variant_unit_scale = null
$scope.packVariant = (product, variant) ->
if variant.hasOwnProperty("unit_value_with_description")
match = variant.unit_value_with_description.match(/^([\d\.\,]+(?= |$)|)( |)(.*)$/)
if match
product = BulkProducts.find product.id
variant.unit_value = parseFloat(match[1].replace(",", "."))
variant.unit_value = null if isNaN(variant.unit_value)
if variant.unit_value && variant.variant_unit_scale
variant.unit_value = parseFloat(window.bigDecimal.multiply(variant.unit_value, variant.variant_unit_scale, 2))
if variant.unit_value && product.variant_unit_scale
variant.unit_value = parseFloat(window.bigDecimal.multiply(variant.unit_value, product.variant_unit_scale, 2))
variant.unit_description = match[3]
$scope.incrementLimit = ->
@@ -313,9 +314,19 @@ 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
if product.hasOwnProperty("variant_unit_with_scale")
filteredProduct.variant_unit = product.variant_unit
filteredProduct.variant_unit_scale = product.variant_unit_scale
hasUpdatableProperty = true
if product.hasOwnProperty("variant_unit_name")
filteredProduct.variant_unit_name = product.variant_unit_name
hasUpdatableProperty = true
if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present
filteredProduct.on_hand = product.on_hand
hasUpdatableProperty = true
@@ -368,17 +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
if variant.hasOwnProperty("variant_unit_with_scale")
filteredVariant.variant_unit = variant.variant_unit
filteredVariant.variant_unit_scale = variant.variant_unit_scale
hasUpdatableProperty = true
if variant.hasOwnProperty("variant_unit_name")
filteredVariant.variant_unit_name = variant.variant_unit_name
hasUpdatableProperty = true
{filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty}

View File

@@ -4,30 +4,31 @@ angular.module("ofn.admin").directive "ofnDisplayAs", (OptionValueNamer) ->
scope.$watchCollection ->
return [
scope.$eval(attrs.ofnDisplayAs).unit_value_with_description
scope.variant.variant_unit_name
scope.variant.variant_unit_with_scale
scope.product.variant_unit_name
scope.product.variant_unit_with_scale
]
, ->
[variant_unit, variant_unit_scale] = productUnitProperties()
[unit_value, unit_description] = variantUnitProperties(variant_unit_scale)
variant_object =
variant_object =
unit_value: unit_value
unit_description: unit_description
variant_unit_scale: variant_unit_scale
variant_unit: variant_unit
variant_unit_name: scope.variant.variant_unit_name
product:
variant_unit_scale: variant_unit_scale
variant_unit: variant_unit
variant_unit_name: scope.product.variant_unit_name
scope.placeholder_text = new OptionValueNamer(variant_object).name()
productUnitProperties = ->
# get relevant product properties
if scope.variant.variant_unit_with_scale?
match = scope.variant.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if scope.product.variant_unit_with_scale?
match = scope.product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
variant_unit = match[1]
variant_unit_scale = parseFloat(match[2])
else
variant_unit = scope.variant.variant_unit_with_scale
variant_unit = scope.product.variant_unit_with_scale
variant_unit_scale = null
else
variant_unit = variant_unit_scale = null
@@ -44,4 +45,4 @@ angular.module("ofn.admin").directive "ofnDisplayAs", (OptionValueNamer) ->
unit_value = null if isNaN(unit_value)
unit_value *= variant_unit_scale if unit_value && variant_unit_scale
unit_description = match[3]
[unit_value, unit_description]
[unit_value, unit_description]

View File

@@ -0,0 +1,8 @@
angular.module("ofn.admin").directive "ofnMaintainUnitScale", ->
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
scope.$watch 'product.variant_unit_with_scale', (newValue, oldValue) ->
if not (oldValue == newValue)
# Triggers track-variant directive to track the unit_value, so that changes to the unit are passed to the server
ngModel.$setViewValue ngModel.$viewValue

View File

@@ -0,0 +1,8 @@
angular.module("ofn.admin").directive "ofnTrackMaster", (DirtyProducts) ->
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
ngModel.$parsers.push (viewValue) ->
if ngModel.$dirty
DirtyProducts.addMasterProperty scope.product.id, scope.product.master.id, attrs.ofnTrackMaster, viewValue
scope.displayDirtyProducts()
viewValue

View File

@@ -1 +1 @@
angular.module("admin.enterprise_groups", ["admin.side_menu", "admin.users", "ngSanitize"])
angular.module("admin.enterprise_groups", ["admin.side_menu", "admin.users", "textAngular"])

View File

@@ -3,6 +3,7 @@ angular.module("admin.enterprises", [
"admin.utils",
"admin.shippingMethods",
"admin.users",
"textAngular",
"admin.side_menu",
"admin.taxons",
'admin.indexUtils',
@@ -10,3 +11,16 @@ angular.module("admin.enterprises", [
'admin.dropdown',
'ngSanitize']
)
# For more options: https://github.com/textAngular/textAngular/blob/master/src/textAngularSetup.js
.config [
'$provide', ($provide) ->
$provide.decorator 'taTranslations', [
'$delegate'
(taTranslations) ->
taTranslations.insertLink = {
tooltip: t('admin.enterprises.form.shop_preferences.shopfront_message_link_tooltip'),
dialogPrompt: t('admin.enterprises.form.shop_preferences.shopfront_message_link_prompt')
}
taTranslations
]
]

View File

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

View File

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

View File

@@ -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,14 +199,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.refreshData()
$scope.getLineItemScale = (lineItem) ->
if lineItem.units_variant && lineItem.units_variant.variant_unit_scale && (lineItem.units_variant.variant_unit == "weight" || lineItem.units_variant.variant_unit == "volume")
lineItem.units_variant.variant_unit_scale
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
$scope.sumUnitValues = ->
sum = $scope.filteredLineItems?.reduce (sum, lineItem) ->
if lineItem.units_variant.variant_unit == "items"
if lineItem.units_product.variant_unit == "items"
sum + lineItem.quantity
else
sum + $scope.roundToThreeDecimals(lineItem.final_weight_volume / $scope.getLineItemScale(lineItem))
@@ -214,7 +214,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.sumMaxUnitValues = ->
sum = $scope.filteredLineItems?.reduce (sum,lineItem) ->
if lineItem.units_variant.variant_unit == "items"
if lineItem.units_product.variant_unit == "items"
sum + lineItem.max_quantity
else
sum + lineItem.max_quantity * $scope.roundToThreeDecimals(lineItem.units_variant.unit_value / $scope.getLineItemScale(lineItem))
@@ -228,41 +228,39 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
return false if !lineItem.hasOwnProperty('final_weight_volume') || !(lineItem.final_weight_volume > 0)
true
$scope.getScale = (unitsVariant) ->
if unitsVariant.hasOwnProperty("variant_unit") && (unitsVariant.variant_unit == "weight" || unitsVariant.variant_unit == "volume")
unitsVariant.variant_unit_scale
else if unitsVariant.hasOwnProperty("variant_unit") && unitsVariant.variant_unit == "items"
$scope.getScale = (unitsProduct, unitsVariant) ->
if unitsProduct.hasOwnProperty("variant_unit") && (unitsProduct.variant_unit == "weight" || unitsProduct.variant_unit == "volume")
unitsProduct.variant_unit_scale
else if unitsProduct.hasOwnProperty("variant_unit") && unitsProduct.variant_unit == "items"
1
else
null
$scope.getFormattedValueWithUnitName = (value, unitsVariant, scale) ->
unit_name = VariantUnitManager.getUnitName(scale, unitsVariant.variant_unit)
$scope.getFormattedValueWithUnitName = (value, unitsProduct, unitsVariant, scale) ->
unit_name = VariantUnitManager.getUnitName(scale, unitsProduct.variant_unit)
$scope.roundToThreeDecimals(value) + " " + unit_name
$scope.getGroupBySizeFormattedValueWithUnitName = (value, unitsVariant) ->
scale = $scope.getScale(unitsVariant)
$scope.getGroupBySizeFormattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
scale = $scope.getScale(unitsProduct, unitsVariant)
if scale && value
value = value / scale if scale != 28.35 && scale != 1 && scale != 453.6 # divide by scale if not smallest unit
$scope.getFormattedValueWithUnitName(value, unitsVariant, scale)
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
else
''
$scope.formattedValueWithUnitName = (value, unitsVariant) ->
scale = $scope.getScale(unitsVariant)
$scope.formattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
scale = $scope.getScale(unitsProduct, unitsVariant)
if scale
$scope.getFormattedValueWithUnitName(value, unitsVariant, scale)
else
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
else
''
$scope.fulfilled = (sumOfUnitValues) ->
# A Units Variant is an API object which holds unit properies of a variant
if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size") && $scope.selectedUnitsProduct.group_buy_unit_size > 0 &&
$scope.selectedUnitsVariant.hasOwnProperty("variant_unit")
if $scope.selectedUnitsVariant.variant_unit == "weight" || $scope.selectedUnitsVariant.variant_unit == "volume"
scale = $scope.selectedUnitsVariant.variant_unit_scale
if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size")&& $scope.selectedUnitsProduct.group_buy_unit_size > 0 &&
$scope.selectedUnitsProduct.hasOwnProperty("variant_unit")
if $scope.selectedUnitsProduct.variant_unit == "weight" || $scope.selectedUnitsProduct.variant_unit == "volume"
scale = $scope.selectedUnitsProduct.variant_unit_scale
sumOfUnitValues = sumOfUnitValues * scale unless scale == 28.35 || scale == 453.6
$scope.roundToThreeDecimals(sumOfUnitValues / $scope.selectedUnitsProduct.group_buy_unit_size)
else

View File

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

View File

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

View File

@@ -1,11 +1,7 @@
angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, Columns, StatusMessage, RequestMonitor, OrderCycles, Enterprises, Schedules, Dereferencer) ->
$scope.RequestMonitor = RequestMonitor
$scope.columns = Columns.columns
$scope.saveAll = ($event) ->
trigger_action = $($event.target).data('trigger-action')
confirm = $($event.target).data('confirm')
OrderCycles.saveChanges($scope.order_cycles_form, { trigger_action, confirm })
$scope.saveAll = -> OrderCycles.saveChanges($scope.order_cycles_form)
$scope.ordersCloseAtLimit = -31 # days
$scope.resetSelectFilters = ->

View File

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

View File

@@ -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'
})
{

View File

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

View File

@@ -0,0 +1,24 @@
angular.module("admin.products").controller "editUnitsCtrl", ($scope, VariantUnitManager) ->
$scope.product =
variant_unit: angular.element('#variant_unit').val()
variant_unit_scale: angular.element('#variant_unit_scale').val()
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
if $scope.product.variant_unit == 'items'
$scope.variant_unit_with_scale = 'items'
else
$scope.variant_unit_with_scale = $scope.product.variant_unit + '_' + $scope.product.variant_unit_scale.replace(/\.0$/, '');
$scope.setFields = ->
if $scope.variant_unit_with_scale == 'items'
variant_unit = 'items'
variant_unit_scale = null
else
options = $scope.variant_unit_with_scale.split('_')
variant_unit = options[0]
variant_unit_scale = options[1]
$scope.product.variant_unit = variant_unit
$scope.product.variant_unit_scale = variant_unit_scale

View File

@@ -1,14 +1,15 @@
# Controller for "New Products" form (spree/admin/products/new)
angular.module("admin.products")
.controller "unitsCtrl", ($scope, VariantUnitManager, OptionValueNamer, UnitPrices, PriceParser) ->
$scope.product = {}
$scope.product = { master: {} }
$scope.product.master.product = $scope.product
$scope.placeholder_text = ""
$scope.$watchCollection '[product.variant_unit_with_scale, product.unit_value_with_description, product.price, product.variant_unit_name]', ->
$scope.$watchCollection '[product.variant_unit_with_scale, product.master.unit_value_with_description, product.price, product.variant_unit_name]', ->
$scope.processVariantUnitWithScale()
$scope.processUnitValueWithDescription()
$scope.processUnitPrice()
$scope.placeholder_text = new OptionValueNamer($scope.product).name() if $scope.product.variant_unit_scale
$scope.placeholder_text = new OptionValueNamer($scope.product.master).name() if $scope.product.variant_unit_scale
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
@@ -37,24 +38,24 @@ angular.module("admin.products")
# Extract unit_value and unit_description from text field unit_value_with_description,
# and update hidden variant fields
$scope.processUnitValueWithDescription = ->
if $scope.product.hasOwnProperty("unit_value_with_description")
match = $scope.product.unit_value_with_description.match(/^([\d\.,]+(?= *|$)|)( *)(.*)$/)
if $scope.product.master.hasOwnProperty("unit_value_with_description")
match = $scope.product.master.unit_value_with_description.match(/^([\d\.,]+(?= *|$)|)( *)(.*)$/)
if match
$scope.product.unit_value = PriceParser.parse(match[1])
$scope.product.unit_value = null if isNaN($scope.product.unit_value)
$scope.product.unit_value = window.bigDecimal.multiply($scope.product.unit_value, $scope.product.variant_unit_scale, 2) if $scope.product.unit_value && $scope.product.variant_unit_scale
$scope.product.unit_description = match[3]
$scope.product.master.unit_value = PriceParser.parse(match[1])
$scope.product.master.unit_value = null if isNaN($scope.product.master.unit_value)
$scope.product.master.unit_value = window.bigDecimal.multiply($scope.product.master.unit_value, $scope.product.variant_unit_scale, 2) if $scope.product.master.unit_value && $scope.product.variant_unit_scale
$scope.product.master.unit_description = match[3]
else
value = $scope.product.unit_value
value = window.bigDecimal.divide(value, $scope.product.variant_unit_scale, 2) if $scope.product.unit_value && $scope.product.variant_unit_scale
$scope.product.unit_value_with_description = value + " " + $scope.product.unit_description
value = $scope.product.master.unit_value
value = window.bigDecimal.divide(value, $scope.product.variant_unit_scale, 2) if $scope.product.master.unit_value && $scope.product.variant_unit_scale
$scope.product.master.unit_value_with_description = value + " " + $scope.product.master.unit_description
# Calculate unit price based on product price and variant_unit_scale
$scope.processUnitPrice = ->
price = $scope.product.price
scale = $scope.product.variant_unit_scale
unit_type = $scope.product.variant_unit
unit_value = $scope.product.unit_value
unit_value = $scope.product.master.unit_value
variant_unit_name = $scope.product.variant_unit_name
$scope.unit_price = UnitPrices.displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name)

View File

@@ -0,0 +1,32 @@
angular.module("admin.products").controller "variantUnitsCtrl", ($scope, VariantUnitManager, $timeout, UnitPrices, PriceParser) ->
$scope.unitName = (scale, type) ->
VariantUnitManager.getUnitName(scale, type)
$scope.$watchCollection "[unit_value_human, variant.price]", ->
$scope.processUnitPrice()
$scope.processUnitPrice = ->
if ($scope.variant)
price = $scope.variant.price
scale = $scope.scale
unit_type = angular.element("#product_variant_unit").val()
if (unit_type != "items")
$scope.updateValue()
unit_value = $scope.unit_value
else
unit_value = 1
variant_unit_name = angular.element("#product_variant_unit_name").val()
$scope.unit_price = UnitPrices.displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name)
$scope.scale = angular.element('#product_variant_unit_scale').val()
$scope.updateValue = ->
unit_value_human = angular.element('#unit_value_human').val()
$scope.unit_value = bigDecimal.multiply(PriceParser.parse(unit_value_human), $scope.scale, 2)
variant_unit_value = angular.element('#variant_unit_value').val()
$scope.unit_value_human = parseFloat(bigDecimal.divide(variant_unit_value, $scope.scale, 2))
$timeout -> $scope.processUnitPrice()
$timeout -> $scope.updateValue()

View File

@@ -0,0 +1,19 @@
angular.module("admin.products").directive "setOnDemand", ->
link: (scope, element, attr) ->
onHand = element.context.querySelector("#variant_on_hand")
onDemand = element.context.querySelector("#variant_on_demand")
disableOnHandIfOnDemand = ->
if onDemand.checked
onHand.disabled = 'disabled'
onHand.dataStock = onHand.value
onHand.value = t('admin.products.variants.infinity')
disableOnHandIfOnDemand()
onDemand.addEventListener 'change', (event) ->
disableOnHandIfOnDemand()
if !onDemand.checked
onHand.removeAttribute('disabled')
onHand.value = onHand.dataStock

View File

@@ -1 +1 @@
angular.module("admin.products", ["ngSanitize", "admin.utils", "OFNShared"])
angular.module("admin.products", ["textAngular", "admin.utils", "OFNShared"])

View File

@@ -13,16 +13,16 @@ angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager
name_fields.join ' '
value_scaled: ->
@variant.variant_unit_scale?
@variant.product.variant_unit_scale?
option_value_value_unit: ->
if @variant.unit_value?
if @variant.variant_unit in ["weight", "volume"]
if @variant.product.variant_unit in ["weight", "volume"]
[value, unit_name] = @option_value_value_unit_scaled()
else
value = @variant.unit_value
unit_name = @pluralize(@variant.variant_unit_name, value)
unit_name = @pluralize(@variant.product.variant_unit_name, value)
value = parseInt(value, 10) if value == parseInt(value, 10)
@@ -58,13 +58,14 @@ angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager
# to >= 1 when expressed in it.
# If there is none available where this is true, use the smallest
# available unit.
scales = VariantUnitManager.compatibleUnitScales(@variant.variant_unit_scale, @variant.variant_unit)
product = @variant.product
scales = VariantUnitManager.compatibleUnitScales(product.variant_unit_scale, product.variant_unit)
variantUnitValue = @variant.unit_value
# sets largestScale = last element in filtered scales array
[_, ..., largestScale] = (scales.filter (s) -> variantUnitValue / s >= 1)
if (largestScale)
[largestScale, VariantUnitManager.getUnitName(largestScale, @variant.variant_unit)]
[largestScale, VariantUnitManager.getUnitName(largestScale, product.variant_unit)]
else
[scales[0], VariantUnitManager.getUnitName(scales[0], @variant.variant_unit)]
[scales[0], VariantUnitManager.getUnitName(scales[0], product.variant_unit)]

View File

@@ -29,13 +29,13 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy
deferred.reject(response)
deferred.promise
saveChanges: (form, params = {}) ->
saveChanges: (form) ->
changed = {}
for id, orderCycle of @byID when not @saved(orderCycle)
changed[Object.keys(changed).length] = @changesFor(orderCycle)
if Object.keys(changed).length > 0
StatusMessage.display('progress', "Saving...")
OrderCycleResource.bulkUpdate { order_cycle_set: { collection_attributes: changed }, confirm: params['confirm'], trigger_action: params['trigger_action'] }, (data) =>
OrderCycleResource.bulkUpdate { order_cycle_set: { collection_attributes: changed } }, (data) =>
for orderCycle in data
delete orderCycle.coordinator
delete orderCycle.producers
@@ -47,10 +47,8 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy
, (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.bulk_save_error'))
StatusMessage.display('failure', "Oh no! I was unable to save your changes.")
saved: (order_cycle) ->
@diff(order_cycle).length == 0

View File

@@ -19,7 +19,7 @@ angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetche
for server_product in serverProducts
product = @findProductInList(server_product.id, @products)
product.variants = server_product.variants
@loadVariantUnitValues product.variants
@loadVariantUnitValues product
find: (id) ->
@findProductInList id, @products
@@ -38,32 +38,34 @@ angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetche
@products.splice(index + 1, 0, newProduct)
unpackProduct: (product) ->
#$scope.matchProducer product
@loadVariantUnit product
loadVariantUnit: (product) ->
@loadVariantUnitValues product.variants if product.variants
loadVariantUnitValues: (variants) ->
for variant in variants
@loadVariantUnitValue variant
loadVariantUnitValue: (variant) ->
variant.variant_unit_with_scale =
if variant.variant_unit && variant.variant_unit_scale && variant.variant_unit != 'items'
"#{variant.variant_unit}_#{variant.variant_unit_scale}"
else if variant.variant_unit
variant.variant_unit
product.variant_unit_with_scale =
if product.variant_unit && product.variant_unit_scale && product.variant_unit != 'items'
"#{product.variant_unit}_#{product.variant_unit_scale}"
else if product.variant_unit
product.variant_unit
else
null
unit_value = @variantUnitValue variant
@loadVariantUnitValues product if product.variants
@loadVariantUnitValue product, product.master if product.master
loadVariantUnitValues: (product) ->
for variant in product.variants
@loadVariantUnitValue product, variant
loadVariantUnitValue: (product, variant) ->
unit_value = @variantUnitValue product, variant
unit_value = if unit_value? then unit_value else ''
variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim()
variantUnitValue: (variant) ->
variantUnitValue: (product, variant) ->
if variant.unit_value?
if variant.variant_unit_scale
variant_unit_value = @divideAsInteger variant.unit_value, variant.variant_unit_scale
if product.variant_unit_scale
variant_unit_value = @divideAsInteger variant.unit_value, product.variant_unit_scale
parseFloat(window.bigDecimal.round(variant_unit_value, 2))
else
variant.unit_value

View File

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

View 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()

View File

@@ -0,0 +1,6 @@
angular.module("admin.utils").directive "textangularLinksTargetBlank", () ->
restrict: 'CA'
link: (scope, element, attrs) ->
setTimeout ->
element.find(".ta-editor").scope().defaultTagAttributes.a.target = '_blank'
, 500

View File

@@ -0,0 +1,11 @@
angular.module("admin.utils").directive "textangularStrip", () ->
restrict: 'CA'
link: (scope, element, attrs) ->
scope.stripFormatting = ($html) ->
element = document.createElement("div")
element.innerHTML = String($html)
allTags = element.getElementsByTagName("*")
for child in allTags
child.removeAttribute("style")
child.removeAttribute("class")
return element.innerHTML

View File

@@ -9,7 +9,6 @@ angular.module("admin.utils")
$window.onbeforeunload = @onBeforeUnloadHandler
$rootScope.$on "$locationChangeStart", @locationChangeStartHandler
$window.onBeforeUnloadHandler = @onBeforeUnloadHandler
# Action for regular browser navigation.
onBeforeUnloadHandler: ($event) =>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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'
})

View File

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

View File

@@ -1,3 +1,2 @@
- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml
%li{ "ng-class": "{active: selector.active}" }
%a{ tooltip: "{{selector.object.value}}", "tooltip-placement": "bottom", "ng-transclude": true, "ng-class": "{active: selector.active, 'has-tip': selector.object.value}" }

View File

@@ -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()" }

View File

@@ -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 || "&nbsp;" }}
%h5#status-message{ style: 'color: #C85136', "ng-show": "StatusMessage.invalidMessage !== ''" }
{{ StatusMessage.invalidMessage || "&nbsp;" }}

View File

@@ -1,4 +1,3 @@
- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml
%ul
%active-selector{ "ng-repeat": "selector in allSelectors", "ng-show": "ifDefined(selector.fits, true)" }
%span{"ng-bind" => "::selector.object.name"}

View File

@@ -1,4 +1,3 @@
- # NOTE: make sure that any changes in this template are reflected in app/views/admin/products_v3/product_preview.turbo_stream.haml
.row
.columns.small-12.medium-6.large-6.product-header
%h3{"ng-bind" => "::product.name"}

View File

@@ -1 +0,0 @@
@import './mail/all.scss';

View File

@@ -1,3 +0,0 @@
@import '../../../webpacker/css/admin/globals/palette.scss';
@import 'email';
@import 'payments_list';

View File

@@ -1,11 +0,0 @@
# frozen_string_literal: true
class AdminTooltipComponent < ViewComponent::Base
def initialize(text:, link_text:, placement: "top", link: "", link_class: "")
@text = text
@link_text = link_text
@placement = placement
@link = link
@link_class = link_class
end
end

View File

@@ -1,8 +0,0 @@
%div{"data-controller": "tooltip", "data-tooltip-placement-value": @placement }
%a{"data-tooltip-target": "element", href: @link, class: @link_class}
= @link_text
.tooltip-container
.tooltip{"data-tooltip-target": "tooltip"}
= sanitize @text
.arrow{"data-tooltip-target": "arrow"}

View File

@@ -1,15 +1,11 @@
# frozen_string_literal: true
class ModalComponent < ViewComponent::Base
def initialize(id:, close_button: true, instant: false, modal_class: :small, **options)
def initialize(id:, close_button: true, instant: false, modal_class: :small)
@id = id
@close_button = close_button
@instant = instant
@modal_class = modal_class
@options = options
@data_controller = "modal #{@options.delete(:'data-controller')}".squish
@data_action =
"keyup@document->modal#closeIfEscapeKey #{@options.delete(:'data-action')}".squish
end
private

View File

@@ -1,4 +1,4 @@
%div{ id: @id, "data-controller": @data_controller, "data-action": @data_action, "data-modal-instant-value": @instant, **@options }
%div{ id: @id, "data-controller": "modal", "data-action": "keyup@document->modal#closeIfEscapeKey", "data-modal-instant-value": @instant }
.reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#close" }
.reveal-modal.fade.modal-component{ "data-modal-target": "modal", class: @modal_class }
= content

View File

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

View File

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

View File

@@ -11,8 +11,7 @@ class SearchableDropdownComponent < ViewComponent::Base
selected_option:,
placeholder_value:,
include_blank: false,
aria_label: '',
other_attrs: {}
aria_label: ''
)
@f = form
@name = name
@@ -21,13 +20,11 @@ class SearchableDropdownComponent < ViewComponent::Base
@placeholder_value = placeholder_value
@include_blank = include_blank
@aria_label = aria_label
@other_attrs = other_attrs
end
private
attr_reader :f, :name, :options, :selected_option, :placeholder_value, :include_blank,
:aria_label, :other_attrs
attr_reader :f, :name, :options, :selected_option, :placeholder_value, :include_blank, :aria_label
def classes
"fullwidth #{remove_search_plugin? ? 'no-input' : ''}"

View File

@@ -1 +1 @@
= f.select name, options_for_select(options, selected_option), { include_blank: }, class: classes, data:, 'aria-label': aria_label, **other_attrs
= f.select name, options_for_select(options, selected_option), { include_blank: }, class: classes, data:, 'aria-label': aria_label

View File

@@ -8,10 +8,6 @@ export default class extends Controller {
window.addEventListener("click", this.#hideIfClickedOutside);
}
disconnect() {
window.removeEventListener("click", this.#hideIfClickedOutside);
}
toggle() {
this.contentTarget.classList.toggle("show");
}

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,12 @@ module Admin
def create
authorize! :admin, enterprise
connect
app = ConnectedApp.create!(enterprise_id: enterprise.id)
ConnectAppJob.perform_later(
app, spree_current_user.spree_api_key,
channel: SessionChannel.for_request(request),
)
render_panel
end
@@ -13,55 +18,20 @@ 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
private
def create_connected_app
attributes = {}
attributes[:type] = connected_app_params[:type] if connected_app_params[:type]
@app = ConnectedApp.create!(enterprise_id: enterprise.id, **attributes)
end
def connect
return connect_vine if connected_app_params[:type] == "ConnectedApps::Vine"
create_connected_app
@app.connect(api_key: spree_current_user.spree_api_key,
channel: SessionChannel.for_request(request))
end
def connect_vine
if vine_params_empty?
return flash[:error] =
I18n.t("admin.enterprises.form.connected_apps.vine.api_parameters_empty")
end
create_connected_app
jwt_service = VineJwtService.new(secret: connected_app_params[:vine_secret])
vine_api = VineApiService.new(api_key: connected_app_params[:vine_api_key],
jwt_generator: jwt_service)
if !@app.connect(api_key: connected_app_params[:vine_api_key],
secret: connected_app_params[:vine_secret], vine_api:)
error_message = "#{@app.errors.full_messages.to_sentence}. \
#{I18n.t('admin.enterprises.form.connected_apps.vine.api_parameters_error')}".squish
handle_error(error_message)
end
rescue Faraday::Error => e
log_and_notify_exception(e)
handle_error(I18n.t("admin.enterprises.form.connected_apps.vine.connection_error"))
rescue KeyError => e
log_and_notify_exception(e)
handle_error(I18n.t("admin.enterprises.form.connected_apps.vine.setup_error"))
end
def enterprise
@enterprise ||= Enterprise.find(params.require(:enterprise_id))
end
@@ -69,23 +39,5 @@ module Admin
def render_panel
redirect_to "#{edit_admin_enterprise_path(enterprise)}#/connected_apps_panel"
end
def handle_error(message)
flash[:error] = message
@app.destroy
end
def log_and_notify_exception(exception)
Rails.logger.error exception.inspect
Bugsnag.notify(exception)
end
def vine_params_empty?
connected_app_params[:vine_api_key].empty? || connected_app_params[:vine_secret].empty?
end
def connected_app_params
params.permit(:type, :vine_api_key, :vine_secret)
end
end
end

View File

@@ -26,29 +26,35 @@ module Admin
# * First step: import all products for given enterprise.
# * Second step: render table and let user decide which ones to import.
imported = graph.map do |subject|
next unless subject.is_a? DataFoodConsortium::Connector::SuppliedProduct
existing_variant = enterprise.supplied_variants.linked_to(subject.semanticId)
if existing_variant
SuppliedProductBuilder.update_product(subject, existing_variant)
else
SuppliedProductBuilder.store_product(subject, enterprise)
end
import_product(subject, enterprise)
end
@count = imported.compact.count
rescue Faraday::Error,
Addressable::URI::InvalidURIError,
ActionController::ParameterMissing => e
flash[:error] = e.message
redirect_to admin_product_import_path
end
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.
def import_product(subject, enterprise)
return unless subject.is_a? DataFoodConsortium::Connector::SuppliedProduct
variant = SuppliedProductBuilder.import_variant(subject, enterprise)
product = variant.product
product.save! if product.new_record?
variant.save! if variant.new_record?
variant
end
end
end

View File

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

View File

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

View File

@@ -2,8 +2,6 @@
module Admin
class OrderCyclesController < Admin::ResourceController
class DateTimeChangeError < StandardError; end
include ::OrderCyclesHelper
include PaperTrailLogging
@@ -64,7 +62,9 @@ module Admin
end
def update
@order_cycle_form = set_order_cycle_form
@order_cycle_form = OrderCycles::FormService.new(@order_cycle, order_cycle_params,
spree_current_user)
if @order_cycle_form.save
update_nil_subscription_line_items_price_estimate(@order_cycle)
respond_to do |format|
@@ -77,9 +77,6 @@ module Admin
elsif request.format.json?
render json: { errors: @order_cycle.errors.full_messages }, status: :unprocessable_entity
end
rescue DateTimeChangeError
render json: { trigger_action: params[:trigger_action] },
status: :unprocessable_entity
end
def bulk_update
@@ -93,9 +90,6 @@ module Admin
order_cycle = order_cycle_set.collection.find{ |oc| oc.errors.present? }
render json: { errors: order_cycle.errors.full_messages }, status: :unprocessable_entity
end
rescue DateTimeChangeError
render json: { trigger_action: params[:trigger_action] },
status: :unprocessable_entity
end
def bulk_update_nil_subscription_line_items_price_estimate
@@ -241,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
@@ -276,10 +270,7 @@ module Admin
end
def order_cycle_set
@order_cycle_set ||= Sets::OrderCycleSet.new(
@order_cycles, { **order_cycle_bulk_params,
confirm_datetime_change: params[:confirm], error_class: DateTimeChangeError }
)
@order_cycle_set ||= Sets::OrderCycleSet.new(@order_cycles, order_cycle_bulk_params)
end
def require_order_cycle_set_params
@@ -303,14 +294,5 @@ module Admin
collection_attributes: [:id] + PermittedAttributes::OrderCycle.basic_attributes
).to_h.with_indifferent_access
end
def set_order_cycle_form
OrderCycles::FormService.new(
@order_cycle, order_cycle_params.merge(
{ confirm_datetime_change: params[:order_cycle][:confirm],
error_class: DateTimeChangeError }
), spree_current_user
)
end
end
end

View File

@@ -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
@@ -21,7 +19,8 @@ module Admin
@importer = ProductImport::ProductImporter.new(File.new(@filepath), spree_current_user,
params[:settings])
@original_filename = params[:file].try(:original_filename)
@non_updatable_fields = ProductImport::EntryValidator.non_updatable_variant_fields
@non_updatable_fields = ProductImport::EntryValidator.non_updatable_fields
return if contains_errors? @importer
@ams_data = ams_data
@@ -102,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
@@ -126,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
@@ -142,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
@@ -154,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

View File

@@ -1,22 +0,0 @@
# frozen_string_literal: true
module Admin
class ProductPreviewController < Spree::Admin::BaseController
def show
@product = Spree::Product.find(params[:id])
authorize! :show, @product
respond_with do |format|
format.turbo_stream {
render "admin/products_v3/product_preview", status: :ok
}
end
end
private
def model_class
Spree::Product
end
end
end

View File

@@ -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,11 +38,7 @@ module Admin
{ id: params[:id] }
).find_product
authorize! :delete, @record
@record.destroyed_by = spree_current_user
status = :ok
if @record.destroy
flash.now[:success] = t('.delete_product.success')
else
@@ -74,31 +68,6 @@ module Admin
end
end
def clone
@product = Spree::Product.find(params[:id])
authorize! :clone, @product
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] = clone_error_message(e)
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
@@ -155,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
@@ -169,13 +138,13 @@ module Admin
def product_query_includes
[
:image,
:supplier,
{ variants: [
:default_price,
:primary_taxon,
:product,
:stock_items,
:tax_category,
:supplier,
] },
]
end
@@ -213,15 +182,6 @@ module Admin
params.permit(products: ::PermittedAttributes::Product.attributes)
.to_h.with_indifferent_access
end
def clone_error_message(error)
case error
when ActiveRecord::RecordInvalid
error.record.errors.full_messages.to_sentence
else
t('.error')
end
end
end
end
# rubocop:enable Metrics/ClassLength

View File

@@ -6,7 +6,7 @@ module Admin
include ReportsActions
helper ReportsHelper
before_action :authorize_report, only: [:show, :create]
before_action :authorize_report, only: [:show]
# Define model class for Can? permissions
def model_class
@@ -20,17 +20,14 @@ module Admin
end
def show
@report = report_class.new(spree_current_user, params, render: false)
@rendering_options = rendering_options
@report = report_class.new(spree_current_user, params, render: render_data?)
@rendering_options = rendering_options # also stores user preferences
show_report
end
def create
@report = report_class.new(spree_current_user, params, render: true)
update_rendering_options
render_in_background
if render_data?
render_in_background
else
show_report
end
end
private
@@ -57,15 +54,28 @@ module Admin
@variant_serialized = Api::Admin::VariantSerializer.new(variant)
end
def render_data?
request.post?
end
def render_in_background
@blob = ReportBlob.create_for_upload_later!(report_filename)
cable_ready[ScopedChannel.for_id(params[:uuid])]
.inner_html(
selector: "#report-table",
html: render_to_string(partial: "admin/reports/loading")
).scroll_into_view(
selector: "#report-table",
block: "start"
).broadcast
ReportJob.perform_later(
report_class:, user: spree_current_user, params:,
format: report_format,
blob: @blob,
filename: report_filename,
channel: ScopedChannel.for_id(params[:uuid]),
)
head :no_content
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ module Api
authorize! :create, Spree::Product
@product = Spree::Product.new(product_params)
if @product.save(context: :create_and_create_standard_variant)
if @product.save
render json: @product, serializer: Api::Admin::ProductSerializer, status: :created
else
invalid_resource!(@product)
@@ -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

View 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

View File

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

View File

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

View File

@@ -11,8 +11,6 @@ class CartController < BaseController
order.cap_quantity_at_stock!
order.recreate_all_fees!
StockSyncJob.sync_linked_catalogs(order)
render json: { error: false, stock_levels: stock_levels(order) }, status: :ok
else
render json: { error: cart_service.errors.full_messages.join(",") },

View File

@@ -9,12 +9,6 @@ module CheckoutCallbacks
# Otherwise we fail on duplicate indexes or end up with negative stock.
prepend_around_action CurrentOrderLocker, only: [:edit, :update]
# We want to download the latest stock data before anything else happens.
# We don't want it to be in the same database transaction as the order
# locking because this action locks a different set of variants and it
# could cause race conditions.
prepend_around_action :sync_stock, only: :update
prepend_before_action :check_hub_ready_for_checkout
prepend_before_action :check_order_cycle_expiry
prepend_before_action :require_order_cycle
@@ -31,14 +25,6 @@ module CheckoutCallbacks
private
def sync_stock
if current_order&.state == "confirmation"
StockSyncJob.sync_linked_catalogs_now(current_order)
end
yield
end
def load_order
@order = current_order
@order.manual_shipping_selection = true
@@ -77,6 +63,12 @@ module CheckoutCallbacks
end
end
def valid_order_line_items?
@order.insufficient_stock_lines.empty? &&
OrderCycles::DistributedVariantsService.new(@order.order_cycle, @order.distributor).
distributes_order_variants?(@order)
end
def ensure_order_not_completed
redirect_to main_app.cart_path if @order.completed?
end

View File

@@ -51,7 +51,7 @@ module OrderCompletion
def order_invalid!
Bugsnag.notify("Notice: invalid order loaded during checkout") do |payload|
payload.add_metadata :order, :order, @order
payload.add_metadata :order, @order
end
flash[:error] = t('checkout.order_not_loaded')

View File

@@ -21,7 +21,7 @@ module OrderStockCheck
return unless current_order_cycle&.closed?
Bugsnag.notify("Notice: order cycle closed during checkout completion") do |payload|
payload.add_metadata :order, :order, current_order
payload.add_metadata :order, current_order
end
current_order.empty!
current_order.set_order_cycle! nil

View File

@@ -88,10 +88,14 @@ module ReportsActions
display_header_row: false
}
end
update_rendering_options
@rendering_options
end
def update_rendering_options
rendering_options.update(
return unless request.post?
@rendering_options.update(
options: {
fields_to_show: params[:fields_to_show],
display_summary_row: params[:display_summary_row].present?,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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]
@@ -39,11 +37,10 @@ module Spree
def create
delete_stock_params_and_set_after do
@object.attributes = permitted_resource_params
if @object.save(context: :create_and_create_standard_variant)
if @object.save
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
@@ -214,10 +215,10 @@ module Spree
def notify_bugsnag(error, product, variant)
Bugsnag.notify(error) do |report|
report.add_metadata(:product,
{ product: product.attributes, variant: variant.attributes })
report.add_metadata(:product, :product_error, product.errors.first) unless product.valid?
report.add_metadata(:product, :variant_error, variant.errors.first) unless variant.valid?
report.add_metadata(:product, product.attributes)
report.add_metadata(:product_error, product.errors.first) unless product.valid?
report.add_metadata(:variant, variant.attributes)
report.add_metadata(:variant_error, variant.errors.first) unless variant.valid?
end
end

View File

@@ -23,7 +23,7 @@ module Spree
def permitted_resource_params
params.require(:return_authorization).
permit(:amount, :reason)
permit(:amount, :reason, :stock_location_id)
end
end
end

View 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

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