Compare commits

..

1 Commits

Author SHA1 Message Date
Maikel Linke
52839ea51d Import product's invalid weight as 1 item
We previously stored a scale which made the product screen believe that
we are dealing with weight.
2024-10-15 12:43:45 +11:00
1601 changed files with 77740 additions and 143725 deletions

56
.codeclimate.yml Normal file
View File

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

View File

@@ -6,9 +6,9 @@
# cp .env.development .env.local
# Locale for translation. Using a locale other than `en` tests the
# successful fallback to `en`. To see up-to-date text used in production,
# set another locale in a local env file.
LOCALE="en_TST"
# successful fallback to `en`. You will also see up-to-date text used
# in production
LOCALE="en_AU"
VERBOSE_QUERY_LOGS=true
@@ -16,9 +16,8 @@ SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
OFN_REDIS_URL="redis://localhost:6379/1"
OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
OFN_REDIS_CABLE_URL="redis://localhost:6379/0"
SITE_URL="localhost:3000"
SITE_URL="0.0.0.0:3000"
# Deactivate rack-timeout in development.
# https://github.com/zombocom/rack-timeout#configuring

View File

@@ -1,17 +1,8 @@
# ENV vars for the test environment
# Override locally with `.env.test.local`
# Test env specific variables
#
# Adjust this to your computer. When you start test-driven development, you may
# want to reduce this value to avoid waiting for a test that you expect to fail.
CAPYBARA_MAX_WAIT_TIME="10"
# General app specific variables
# Locale for translation. Using a locale other than `en` tests the
# successful fallback to `en`.
LOCALE="en_TST"
# Locale for translation.
LOCALE="en_TEST"
OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
@@ -20,15 +11,12 @@ STRIPE_INSTANCE_SECRET_KEY="bogus_key"
STRIPE_CUSTOMER="bogus_customer"
STRIPE_ACCOUNT="bogus_account"
STRIPE_CLIENT_ID="bogus_client_id"
STRIPE_PUBLIC_TEST_API_KEY="bogus_stripe_publishable_key"
SITE_URL="test.host"
# OIDC Settings for DFC authentication
# Find secrets in BitWarden.
# To get a refresh token: log into the OIDC provider, connect your OFN user to it at /admin/oidc_settings, then copy the token from the database:
# ./bin/rails runner 'puts "OPENID_REFRESH_TOKEN=\"#{OidcAccount.last.refresh_token}\""'
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"

View File

@@ -7,13 +7,12 @@ 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`
Check for [minor or major breaking changes]
- Major: if server changes are required (eg. provision with ofn-install)
- Minor: larger change that is irreversible (eg. migration deleting data)
- Patch: all others. Shortcut: `script/release/tag`
@@ -27,9 +26,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.
@@ -42,7 +40,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.
@@ -55,6 +53,5 @@ 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
[#core-devs]: https://openfoodnetwork.slack.com/archives/GK2T38QPJ
[Transifex Client]: https://developers.transifex.com/docs/cli
[minor or major breaking changes]: https://github.com/openfoodfoundation/openfoodnetwork/pulls?q=label%3A%22breaking+change%22%2C%22major+breaking+change%22

View File

@@ -1,4 +1,4 @@
## What? Why?
#### What? Why?
- Closes # <!-- Insert issue number here. -->
@@ -7,7 +7,7 @@
## What should we test?
#### What should we test?
<!-- List which features should be tested and how.
This can be similar to the Steps to Reproduce in the issue.
Also think of other parts of the app which could be affected
@@ -16,7 +16,7 @@
- Visit ... page.
-
## Release notes
#### Release notes
<!-- Please select one for your PR and delete the other. -->
@@ -33,12 +33,12 @@ Changelog Category (reviewers may add a label for the release notes):
The title of the pull request will be included in the release notes.
## Dependencies
#### Dependencies
<!-- Does this PR depend on another one?
Add the link or remove this section. -->
## Documentation updates
#### Documentation updates
<!-- Are there any wiki pages that need updating after merging this PR?
List them here or remove this section. -->

View File

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

6
.github/release.yml vendored
View File

@@ -14,12 +14,6 @@ changelog:
- technical changes only
- user facing changes
# These will require a minor or major version increment
- title: "Significant changes 🚀"
labels:
- breaking change
- major breaking change
# Posted in advance for #instance-managers
- title: "User-facing changes 👀"
labels:

View File

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

51
.github/workflows/brakeman-analysis.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
# This workflow integrates Brakeman with GitHub's Code Scanning feature
# Brakeman is a static analysis security vulnerability scanner for Ruby on Rails applications
name: Brakeman Scan
# This section configures the trigger for the workflow. Feel free to customize depending on your convention
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
brakeman-scan:
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
name: Brakeman Scan
runs-on: ubuntu-latest
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout
uses: actions/checkout@v3
# Customize the ruby version depending on your needs
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '2.7'
- name: Setup Brakeman
env:
BRAKEMAN_VERSION: '5.4.0'
run: |
gem install brakeman --version $BRAKEMAN_VERSION
# Execute Brakeman CLI and generate a SARIF output with the security issues identified during the analysis
- name: Scan
continue-on-error: true
run: |
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
brakeman -f sarif -o output.sarif.json .
# Upload the SARIF file generated in the previous step
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: output.sarif.json

View File

@@ -17,7 +17,85 @@ permissions:
contents: read
jobs:
controllers_and_models:
controllers:
runs-on: ubuntu-22.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [8]
# 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]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
# JS is required in order for webpacker to compile, in order to render templates containing image urls
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bin/rake db:create db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: 864ef557d85ea8e603e086c0387d5154
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
# 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}"
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:
postgres:
@@ -55,22 +133,13 @@ jobs:
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
# JS is required in order for webpacker to compile, in order to render templates containing image urls
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bin/rails db:create db:schema:load
bin/rake db:create db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: 864ef557d85ea8e603e086c0387d5154
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: 09476e2ce491c12083df62768667c674
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
@@ -81,21 +150,20 @@ 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,spec/models/**/*_spec.rb}"
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}"
run: |
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
bin/rails assets:precompile knapsack_pro:rspec
bin/rake knapsack_pro:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
with:
name: simplecov-chunk-controllers-${{ matrix.ci_node_index }}
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:
system_admin:
runs-on: ubuntu-22.04
services:
postgres:
@@ -116,10 +184,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [19]
ci_node_total: [14]
# 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, 12, 13, 14, 15, 16, 17, 18]
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
steps:
- uses: actions/checkout@v3
@@ -143,7 +211,7 @@ jobs:
- name: Set up database
run: |
bin/rails db:create db:schema:load
bin/rake db:create db:schema:load
- name: Run tests
@@ -159,15 +227,15 @@ 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,spec/system/consumer/**/*_spec.rb}"
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
run: |
bin/rails assets:precompile knapsack_pro:queue:rspec
bin/rake knapsack_pro:queue:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
with:
name: simplecov-chunk-system-${{ matrix.ci_node_index }}
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
@@ -177,7 +245,94 @@ jobs:
if: failure()
uses: actions/upload-artifact@v4
with:
name: failed-system_${{ matrix.ci_node_index }}-tests-screenshots
name: failed-admin_${{ matrix.ci_node_index }}-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
system_consumer:
runs-on: ubuntu-22.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
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]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bin/rake db:create db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: e52bd4390c853e6c5bdfe4d0334586c1
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
KNAPSACK_PRO_FIXED_QUEUE_SPLIT: true
# 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}"
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
with:
name: failed-consumer_${{ matrix.ci_node_index }}-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
@@ -231,7 +386,7 @@ jobs:
- name: Set up database
run: |
bin/rails db:create db:schema:load
bin/rake db:create db:schema:load
- name: Run tests
@@ -250,7 +405,7 @@ jobs:
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}"
run: |
bin/rails assets:precompile knapsack_pro:rspec
bin/rake knapsack_pro:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
@@ -282,10 +437,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [3]
ci_node_total: [5]
# 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]
ci_node_index: [0, 1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
@@ -310,7 +465,7 @@ jobs:
- name: Set up database
run: |
bin/rails db:create db:schema:load
bin/rake db:create db:schema:load
- name: Run tests
env:
@@ -327,7 +482,7 @@ jobs:
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}"
run: |
bin/rails assets:precompile knapsack_pro:rspec
bin/rake knapsack_pro:rspec
- name: Save SimpleCov file
uses: actions/upload-artifact@v4
@@ -379,9 +534,11 @@ jobs:
collate_simplecov_results:
runs-on: ubuntu-22.04
needs:
- controllers_and_models
- controllers
- models
- engines
- system
- system_admin
- system_consumer
- test_the_rest
steps:
- uses: actions/checkout@v3
@@ -396,6 +553,7 @@ jobs:
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]'
@@ -408,8 +566,3 @@ jobs:
retention-days: 7
if-no-files-found: ignore
include-hidden-files: true
- name: Compare SimpleCov results with Undercover
run: |
git fetch --no-tags origin ${{ github.event.pull_request.base.ref }}:master
bundle exec undercover
if: ${{ github.ref != 'refs/heads/master' }} # Does not run on master, as we can't fetch master in the master branch

View File

@@ -1,30 +1,50 @@
name: Linters
on: [pull_request]
on: [push, pull_request]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
lint:
name: reviewdog
rubocop:
name: runner / rubocop
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- run: git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
- uses: reviewdog/action-setup@v1
- name: rubocop
uses: reviewdog/action-rubocop@v2
with:
reviewdog_version: v0.21.0
rubocop_version: gemfile
rubocop_extensions: rubocop-rails:gemfile rubocop-rspec:gemfile
reporter: github-pr-check
level: error
filter_mode: nofilter
use_bundler: true
fail_on_error: true
prettier:
name: runner / prettier
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- run: ./script/reviewdog.sh
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.github_token }}
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- run: git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
- name: prettier
uses: EPMatt/reviewdog-action-prettier@v1
with:
github_token: ${{ secrets.github_token }}
reporter: github-pr-check
level: error
fail_on_error: true

51
.github/workflows/mapi.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: 'Mayhem for API'
on: workflow_dispatch
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
test:
permissions:
contents: read # to fetch code (actions/checkout)
security-events: write # to upload SARIF results (github/codeql-action/upload-sarif)
if: ${{ github.repository_owner == 'openfoodfoundation' }}
runs-on: ubuntu-latest
strategy:
fail-fast: true
steps:
- uses: actions/checkout@v3
- run: docker/build
- 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'"
# 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 Mayhem for API
- name: Run Mayhem for API
uses: ForAllSecure/mapi-action@v1
continue-on-error: true
with:
mapi-token: ${{ secrets.MAPI_TOKEN }}
api-url: http://localhost:3000
api-spec: swagger/v1.yaml
target: openfoodfoundation/openfoodnetwork
duration: 1min
sarif-report: mapi.sarif
html-report: mapi.html
run-args: |
--header-auth
X-Api-Token: testing
# Archive HTML report
- name: Archive Mayhem for API report
uses: actions/upload-artifact@v3
with:
name: mapi-report
path: mapi.html
# Upload SARIF file (only available on public repos or github enterprise)
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: mapi.sarif

View File

@@ -1,151 +0,0 @@
name: Auto-move Dependabot PRs to Code Review
permissions:
contents: read
pull-requests: read
on:
pull_request_target:
types: [opened]
jobs:
move-pr-to-code-review:
runs-on: ubuntu-latest
if: github.event.pull_request.user.login == 'dependabot[bot]' || startsWith(github.event.pull_request.title, 'Bump')
steps:
- name: Generate GitHub App Token
id: app-token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.DEPENDABOT_PR_APP_ID }}
private_key: ${{ secrets.DEPENDABOT_PR_APP_PRIVATE_KEY }}
installation_retrieval_mode: id
installation_retrieval_payload: ${{ secrets.DEPENDABOT_PR_APP_INSTALLATION_ID }}
- name: Move PR to Code Review in Project v2
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const projectNumber = 8; // for "OFN Delivery board"
const org = "openfoodfoundation";
const repo = context.repo.repo;
const prNumber = context.payload.pull_request.number;
const statusFieldName = "Status";
const statusValue = "Code review 🔎";
// ---- Helper: Get PR Node ID ----
async function getPrNodeId(owner, repo, number) {
const res = await github.graphql(`
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
id
number
title
}
}
}
`, { owner, repo, number });
return res.repository.pullRequest.id;
}
console.log("🚀 Starting ProjectV2 automation...");
// ---- Step 1: Get Project and Fields ----
const projectRes = await github.graphql(`
query($org: String!, $number: Int!) {
organization(login: $org) {
projectV2(number: $number) {
id
title
fields(first: 50) {
nodes {
... on ProjectV2Field {
id
name
}
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}
`, { org, number: projectNumber });
const project = projectRes.organization.projectV2;
if (!project) throw new Error(`❌ Project #${projectNumber} not found`);
console.log(`✅ Found project: ${project.title} (${project.id})`);
const statusField = project.fields.nodes.find(f => f.name === statusFieldName);
if (!statusField) throw new Error(`❌ Field '${statusFieldName}' not found`);
const option = statusField.options.find(o => o.name === statusValue);
if (!option) throw new Error(`❌ Option '${statusValue}' not found in '${statusFieldName}'`);
console.log(`✅ Found field '${statusFieldName}' and option '${statusValue}'`);
// ---- Step 2: Get PR Node ID ----
const prNodeId = await getPrNodeId(org, repo, prNumber);
console.log(`✅ PR #${prNumber} node ID: ${prNodeId}`);
// ---- Step 3: Check if PR is already in Project ----
const itemRes = await github.graphql(`
query($prId: ID!) {
node(id: $prId) {
... on PullRequest {
projectItems(first: 50) {
nodes {
id
project { id title }
}
}
}
}
}
`, { prId: prNodeId });
let projectItem = itemRes.node.projectItems.nodes.find(i => i.project.id === project.id);
if (!projectItem) {
console.log(" PR not yet in project, adding...");
const addRes = await github.graphql(`
mutation($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) {
item { id }
}
}
`, { projectId: project.id, contentId: prNodeId });
projectItem = addRes.addProjectV2ItemById.item;
console.log(`✅ Added PR to project: ${projectItem.id}`);
} else {
console.log(` PR already in project: ${projectItem.id}`);
}
// ---- Step 4: Update Status ----
await github.graphql(`
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $projectId,
itemId: $itemId,
fieldId: $fieldId,
value: { singleSelectOptionId: $optionId }
}) {
projectV2Item { id }
}
}
`, {
projectId: project.id,
itemId: projectItem.id,
fieldId: statusField.id,
optionId: option.id,
});
console.log(`🎉 Moved PR #${prNumber} → '${statusValue}'`);

1
.gitignore vendored
View File

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

View File

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

View File

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

6
.hound.yml Normal file
View File

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

View File

@@ -1 +1 @@
24.10.0
17.9.1

View File

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

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

View File

@@ -3,20 +3,15 @@
# These are the rules we agreed upon and we work towards.
AllCops:
NewCops: enable
MigratedSchemaVersion: "20250111000000"
Exclude:
- bin/**/*
- db/**/*
- config/**/*
- db/bad_migrations/*
- db/migrate/201*
- db/migrate/202[0-4]*
- db/schema.rb
- script/**/*
- vendor/**/*
- node_modules/**/*
# Excluding: inadequate Naming/FileName rule rejects GemFile name with camelcase
- engines/web/Gemfile
- .undercover
Bundler/DuplicatedGem:
Enabled: false
@@ -94,7 +89,7 @@ Metrics/PerceivedComplexity:
Enabled: true
Max: 14 # default 8
Naming/PredicatePrefix:
Naming/PredicateName:
Enabled: false
Naming/VariableNumber:
@@ -103,8 +98,6 @@ Naming/VariableNumber:
- street_address_2
AllowedPatterns:
- _v[\d]+
# Cf. conversation https://github.com/openfoodfoundation/openfoodnetwork/pull/13306#pullrequestreview-2831644286
- menu_[\d]
Rails/ApplicationRecord:
Exclude:
@@ -118,10 +111,6 @@ Rails/ApplicationRecord:
Rails/HasAndBelongsToMany:
Enabled: false
# Cf. conversation https://github.com/openfoodfoundation/openfoodnetwork/pull/13251
Rails/LexicallyScopedActionFilter:
Enabled: false
Rails/OutputSafety:
Exclude:
- spec/**/*

View File

@@ -1,43 +1,89 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 1400 --no-auto-gen-timestamp`
# using RuboCop version 1.81.7.
# using RuboCop version 1.64.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: RequireParenthesesForMethodChains.
Lint/AmbiguousRange:
# This cop supports safe autocorrection (--autocorrect).
Layout/EmptyLines:
Exclude:
- 'app/models/concerns/permalink_generator.rb'
- 'app/services/products_renderer.rb'
# Offense count: 6
Lint/CopDirectiveSyntax:
# Offense count: 16
# Configuration parameters: AllowComments, AllowEmptyLambdas.
Lint/EmptyBlock:
Exclude:
- 'app/services/orders/bulk_cancel_service.rb'
- 'lib/tasks/simplecov.rake'
- 'spec/models/database_spec.rb'
- 'spec/system/admin/bulk_order_management_spec.rb'
- 'spec/system/admin/enterprise_relationships_spec.rb'
- 'engines/catalog/config/routes.rb'
- 'spec/components/distributor_title_component_spec.rb'
- 'spec/components/example_component_spec.rb'
- 'spec/controllers/admin/subscription_line_items_controller_spec.rb'
- 'spec/controllers/api/v0/shipments_controller_spec.rb'
- 'spec/controllers/concerns/extra_fields_spec.rb'
- 'spec/factories.rb'
- 'spec/factories/enterprise_factory.rb'
- 'spec/jobs/order_cycle_opened_job_spec.rb'
- 'spec/jobs/subscription_placement_job_spec.rb'
- 'spec/models/product_import/entry_validator_spec.rb'
# Offense count: 2
Lint/DuplicateMethods:
# Offense count: 4
# Configuration parameters: AllowComments.
Lint/EmptyClass:
Exclude:
- 'app/models/spree/order.rb'
- 'engines/order_management/app/services/order_management/subscriptions/form.rb'
# Offense count: 3
Lint/UselessConstantScoping:
Exclude:
- 'app/services/weights_and_measures.rb'
- 'lib/reporting/report_metadata_builder.rb'
- 'spec/lib/reports/report_loader_spec.rb'
# Offense count: 1
Lint/UselessOr:
# Configuration parameters: AllowComments.
Lint/EmptyFile:
Exclude:
- 'spec/lib/open_food_network/enterprise_injection_data_spec.rb'
# Offense count: 2
Lint/FloatComparison:
Exclude:
- 'app/models/product_import/entry_validator.rb'
- 'app/models/spree/gateway/pay_pal_express.rb'
# Offense count: 1
Lint/IneffectiveAccessModifier:
Exclude:
- 'app/models/spree/user.rb'
# Offense count: 1
Lint/NoReturnInBeginEndBlocks:
Exclude:
- 'app/controllers/payment_gateways/stripe_controller.rb'
# Offense count: 4
# This cop supports unsafe autocorrection (--autocorrect-all).
Lint/RedundantDirGlobSort:
Exclude:
- 'engines/catalog/spec/spec_helper.rb'
- 'engines/dfc_provider/spec/spec_helper.rb'
- 'spec/base_spec_helper.rb'
- 'spec/system_helper.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowedMethods.
# AllowedMethods: instance_of?, kind_of?, is_a?, eql?, respond_to?, equal?, presence, present?
Lint/RedundantSafeNavigation:
Exclude:
- 'app/models/spree/payment.rb'
# Offense count: 1
Lint/SelfAssignment:
Exclude:
- 'app/models/spree/order/checkout.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AutoCorrect.
Lint/UselessMethodDefinition:
Exclude:
- 'app/models/spree/gateway.rb'
# Offense count: 24
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
@@ -46,6 +92,7 @@ Metrics/AbcSize:
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/payment_gateways/paypal_controller.rb'
- 'app/controllers/spree/admin/payments_controller.rb'
- 'app/controllers/spree/admin/taxons_controller.rb'
- 'app/controllers/spree/admin/variants_controller.rb'
- 'app/controllers/spree/orders_controller.rb'
- 'app/helpers/spree/admin/navigation_helper.rb'
@@ -75,12 +122,12 @@ Metrics/BlockLength:
- 'lib/tasks/data.rake'
# Offense count: 1
# Configuration parameters: CountBlocks, CountModifierForms, Max.
# Configuration parameters: CountBlocks, Max.
Metrics/BlockNesting:
Exclude:
- 'app/models/spree/payment/processing.rb'
# Offense count: 49
# Offense count: 46
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ClassLength:
Exclude:
@@ -90,12 +137,12 @@ Metrics/ClassLength:
- 'app/controllers/admin/resource_controller.rb'
- 'app/controllers/admin/subscriptions_controller.rb'
- 'app/controllers/application_controller.rb'
- 'app/controllers/checkout_controller.rb'
- 'app/controllers/payment_gateways/paypal_controller.rb'
- 'app/controllers/spree/admin/orders_controller.rb'
- 'app/controllers/spree/admin/payment_methods_controller.rb'
- 'app/controllers/spree/admin/payments_controller.rb'
- 'app/controllers/spree/admin/products_controller.rb'
- 'app/controllers/spree/admin/users_controller.rb'
- 'app/controllers/spree/orders_controller.rb'
- 'app/models/enterprise.rb'
- 'app/models/invoice/data_presenter.rb'
@@ -121,9 +168,6 @@ Metrics/ClassLength:
- 'app/services/cart_service.rb'
- 'app/services/order_cycles/form_service.rb'
- 'app/services/orders/sync_service.rb'
- 'app/services/permissions/order.rb'
- 'app/services/products_renderer.rb'
- 'app/services/sets/product_set.rb'
- 'engines/order_management/app/services/order_management/order/updater.rb'
- 'lib/open_food_network/enterprise_fee_calculator.rb'
- 'lib/open_food_network/order_cycle_form_applicator.rb'
@@ -134,12 +178,12 @@ Metrics/ClassLength:
- 'lib/reporting/reports/enterprise_fee_summary/scope.rb'
- 'lib/reporting/reports/xero_invoices/base.rb'
# Offense count: 37
# Offense count: 32
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
Metrics/CyclomaticComplexity:
Exclude:
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/spree/admin/payments_controller.rb'
- 'app/controllers/spree/admin/taxons_controller.rb'
- 'app/controllers/spree/orders_controller.rb'
- 'app/helpers/checkout_helper.rb'
- 'app/helpers/order_cycles_helper.rb'
@@ -155,26 +199,24 @@ Metrics/CyclomaticComplexity:
- 'app/models/spree/preferences/preferable_class_methods.rb'
- 'app/models/spree/return_authorization.rb'
- 'app/models/spree/tax_rate.rb'
- 'app/models/spree/variant.rb'
- 'app/models/spree/zone.rb'
- 'lib/open_food_network/enterprise_issue_validator.rb'
- 'lib/reporting/reports/orders_and_fulfillment/order_cycle_customer_totals.rb'
- 'lib/reporting/reports/orders_and_fulfillment/order_cycle_supplier_totals.rb'
- 'lib/reporting/reports/payments/itemised_payment_totals.rb'
- 'lib/reporting/reports/payments/payment_totals.rb'
- 'lib/reporting/reports/sales_tax/sales_tax_totals_by_producer.rb'
- 'lib/reporting/reports/xero_invoices/base.rb'
- 'lib/spree/core/controller_helpers/order.rb'
- 'lib/spree/core/controller_helpers/respond_with.rb'
- 'lib/spree/localized_number.rb'
- 'spec/models/product_importer_spec.rb'
# Offense count: 22
# Offense count: 24
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Exclude:
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/payment_gateways/paypal_controller.rb'
- 'app/controllers/spree/admin/taxons_controller.rb'
- 'app/controllers/spree/orders_controller.rb'
- 'app/helpers/spree/admin/navigation_helper.rb'
- 'app/models/spree/ability.rb'
- 'app/models/spree/gateway/pay_pal_express.rb'
- 'app/models/spree/order/checkout.rb'
@@ -187,7 +229,7 @@ Metrics/MethodLength:
- 'lib/spree/localized_number.rb'
- 'lib/tasks/sample_data/product_factory.rb'
# Offense count: 10
# Offense count: 49
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ModuleLength:
Exclude:
@@ -199,7 +241,46 @@ Metrics/ModuleLength:
- 'app/helpers/spree/admin/orders_helper.rb'
- 'app/models/spree/order/checkout.rb'
- 'app/models/spree/payment/processing.rb'
- 'engines/catalog/spec/services/catalog/product_import/products_reset_strategy_spec.rb'
- 'engines/order_management/spec/services/order_management/order/updater_spec.rb'
- 'engines/order_management/spec/services/order_management/stock/package_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/form_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/summary_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/validator_spec.rb'
- 'engines/order_management/spec/services/order_management/subscriptions/variants_list_spec.rb'
- 'lib/open_food_network/column_preference_defaults.rb'
- 'spec/controllers/admin/customers_controller_spec.rb'
- 'spec/controllers/admin/order_cycles_controller_spec.rb'
- 'spec/controllers/api/v0/order_cycles_controller_spec.rb'
- 'spec/controllers/api/v0/orders_controller_spec.rb'
- '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'
- 'spec/lib/open_food_network/order_cycle_permissions_spec.rb'
- 'spec/lib/open_food_network/permissions_spec.rb'
- 'spec/lib/open_food_network/scope_variant_to_hub_spec.rb'
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
- 'spec/lib/reports/customers_report_spec.rb'
- 'spec/lib/reports/enterprise_fee_summary/authorizer_spec.rb'
- 'spec/lib/reports/order_cycle_management_report_spec.rb'
- 'spec/lib/reports/products_and_inventory_report_spec.rb'
- 'spec/lib/reports/users_and_enterprises_report_spec.rb'
- 'spec/models/spree/adjustment_spec.rb'
- 'spec/models/spree/credit_card_spec.rb'
- 'spec/models/spree/line_item_spec.rb'
- 'spec/models/spree/order/tax_spec.rb'
- 'spec/models/spree/product_spec.rb'
- 'spec/models/spree/shipping_method_spec.rb'
- 'spec/models/spree/tax_rate_spec.rb'
- 'spec/services/permissions/order_spec.rb'
- 'spec/services/variant_units/option_value_namer_spec.rb'
- 'spec/support/request/stripe_stubs.rb'
# Offense count: 7
@@ -216,143 +297,340 @@ Metrics/ParameterLists:
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
Metrics/PerceivedComplexity:
Exclude:
- 'app/controllers/spree/admin/taxons_controller.rb'
- 'app/models/enterprise_relationship.rb'
- 'app/models/spree/ability.rb'
- 'app/models/spree/order/checkout.rb'
# Offense count: 1
# Configuration parameters: EnforcedStyle, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns.
# SupportedStyles: snake_case, camelCase
# ForbiddenIdentifiers: __id__, __send__
Naming/MethodName:
# Offense count: 8
Naming/AccessorMethodName:
Exclude:
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
- 'app/controllers/spree/admin/taxonomies_controller.rb'
- 'app/mailers/producer_mailer.rb'
- 'app/models/spree/order.rb'
- 'app/services/checkout/post_checkout_actions.rb'
- 'lib/spree/core/controller_helpers/common.rb'
- 'spec/support/request/shop_workflow.rb'
- 'spec/support/request/web_helper.rb'
# Offense count: 1
# Configuration parameters: ForbiddenDelimiters.
# ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$))
Naming/HeredocDelimiterNaming:
Exclude:
- 'app/models/content_configuration.rb'
# Offense count: 5
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
Naming/MemoizedInstanceVariableName:
Exclude:
- 'app/mailers/producer_mailer.rb'
- 'app/models/concerns/balance.rb'
- 'lib/open_food_network/address_finder.rb'
# Offense count: 1
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
Naming/MethodParameterName:
Exclude:
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
- 'app/services/process_payment_intent.rb'
# Offense count: 60
# Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
# AllowedMethods: call
# WaywardPredicates: nonzero?
Naming/PredicateMethod:
# Offense count: 28
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
# SupportedStyles: snake_case, normalcase, non_integer
# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
Naming/VariableNumber:
Exclude:
- 'app/controllers/admin/product_import_controller.rb'
- 'app/controllers/api/v0/order_cycles_controller.rb'
- 'app/controllers/spree/admin/overview_controller.rb'
- 'app/controllers/spree/admin/payments_controller.rb'
- 'app/controllers/voucher_adjustments_controller.rb'
- 'app/forms/enterprise_fees_bulk_update.rb'
- 'app/forms/schedule_form.rb'
- 'app/helpers/spree/orders_helper.rb'
- 'app/models/concerns/variant_stock.rb'
- 'app/models/enterprise.rb'
- 'app/models/enterprise_fee.rb'
- 'app/models/invoice/data_presenter.rb'
- 'app/models/order_cycle.rb'
- 'app/models/product_import/entry_processor.rb'
- 'app/models/product_import/entry_validator.rb'
- 'app/models/spree/order.rb'
- 'app/models/spree/order_contents.rb'
- 'app/models/spree/payment.rb'
- 'app/models/spree/payment/processing.rb'
- 'app/models/spree/state_change.rb'
- 'app/models/spree/user.rb'
- 'app/reflexes/admin/orders_reflex.rb'
- 'app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb'
- 'app/serializers/api/admin/index_enterprise_serializer.rb'
- 'app/serializers/api/admin/index_order_cycle_serializer.rb'
- 'app/serializers/api/admin/order_cycle_serializer.rb'
- 'app/serializers/api/admin/order_serializer.rb'
- 'app/serializers/api/admin/schedule_serializer.rb'
- 'app/serializers/api/admin/subscription_line_item_serializer.rb'
- 'app/serializers/api/admin/user_serializer.rb'
- 'app/serializers/api/admin/variant_serializer.rb'
- 'app/serializers/api/enterprise_shopfront_serializer.rb'
- 'app/serializers/api/enterprise_thin_serializer.rb'
- 'app/serializers/api/order_serializer.rb'
- 'app/serializers/api/uncached_enterprise_serializer.rb'
- 'app/services/cart_service.rb'
- 'app/services/orders/fetch_adjustments_service.rb'
- 'app/services/orders/workflow_service.rb'
- 'app/services/sets/model_set.rb'
- 'app/services/sets/order_cycle_set.rb'
- 'app/services/sets/product_set.rb'
- 'engines/dfc_provider/app/controllers/dfc_provider/addresses_controller.rb'
- 'lib/open_food_network/order_cycle_form_applicator.rb'
- 'lib/open_food_network/order_cycle_permissions.rb'
- 'lib/reporting/reports/enterprise_fee_summary/enterprise_fees_with_tax_report_by_order.rb'
- 'lib/reporting/reports/enterprise_fee_summary/enterprise_fees_with_tax_report_by_producer.rb'
- 'lib/tasks/data/check_invalid_address_used.rake'
- 'app/controllers/spree/orders_controller.rb'
- 'app/models/content_configuration.rb'
- 'app/models/preference_sections/main_links_section.rb'
- 'lib/spree/core/controller_helpers/common.rb'
- 'spec/controllers/spree/admin/search_controller_spec.rb'
- 'spec/factories/stock_location_factory.rb'
- 'spec/models/spree/stock_item_spec.rb'
- 'spec/models/spree/tax_rate_spec.rb'
- 'spec/requests/api/orders_spec.rb'
# Offense count: 3
# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns.
# SupportedStyles: snake_case, camelCase
Naming/VariableName:
Exclude:
- 'engines/dfc_provider/lib/dfc_provider/catalog_item.rb'
# Offense count: 6
# Offense count: 142
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/FindByOrAssignmentMemoization:
# Configuration parameters: ResponseMethods.
# ResponseMethods: response, last_response
RSpecRails/HaveHttpStatus:
Exclude:
- 'app/controllers/admin/customers_controller.rb'
- 'app/controllers/admin/resource_controller.rb'
- 'app/controllers/api/v0/enterprise_fees_controller.rb'
- 'app/controllers/api/v0/order_cycles_controller.rb'
- 'lib/stripe/account_connector.rb'
- 'spec/controllers/admin/bulk_line_items_controller_spec.rb'
- 'spec/controllers/admin/order_cycles_controller_spec.rb'
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
- 'spec/controllers/api/v0/base_controller_spec.rb'
- 'spec/controllers/api/v0/customers_controller_spec.rb'
- 'spec/controllers/api/v0/enterprises_controller_spec.rb'
- 'spec/controllers/api/v0/logos_controller_spec.rb'
- 'spec/controllers/api/v0/order_cycles_controller_spec.rb'
- 'spec/controllers/api/v0/orders_controller_spec.rb'
- 'spec/controllers/api/v0/product_images_controller_spec.rb'
- 'spec/controllers/api/v0/products_controller_spec.rb'
- 'spec/controllers/api/v0/promo_images_controller_spec.rb'
- 'spec/controllers/api/v0/reports/packing_report_spec.rb'
- 'spec/controllers/api/v0/reports_controller_spec.rb'
- 'spec/controllers/api/v0/shipments_controller_spec.rb'
- 'spec/controllers/api/v0/statuses_controller_spec.rb'
- 'spec/controllers/api/v0/taxons_controller_spec.rb'
- 'spec/controllers/api/v0/terms_and_conditions_controller_spec.rb'
- 'spec/controllers/api/v0/variants_controller_spec.rb'
- 'spec/controllers/cart_controller_spec.rb'
- 'spec/controllers/checkout_controller_spec.rb'
- 'spec/controllers/enterprises_controller_spec.rb'
- 'spec/controllers/line_items_controller_spec.rb'
- 'spec/controllers/shop_controller_spec.rb'
- 'spec/controllers/spree/admin/orders/payments/payments_controller_spec.rb'
- 'spec/controllers/spree/admin/orders_controller_spec.rb'
- 'spec/controllers/spree/admin/products_controller_spec.rb'
- 'spec/controllers/spree/credit_cards_controller_spec.rb'
- 'spec/controllers/spree/orders_controller_spec.rb'
- 'spec/controllers/stripe/callbacks_controller_spec.rb'
- 'spec/controllers/stripe/webhooks_controller_spec.rb'
- 'spec/controllers/user_passwords_controller_spec.rb'
- 'spec/controllers/user_registrations_controller_spec.rb'
- 'spec/requests/api/routes_spec.rb'
- 'spec/requests/checkout/stripe_sca_spec.rb'
- 'spec/requests/home_controller_spec.rb'
- 'spec/requests/omniauth_callbacks_controller_spec.rb'
- 'spec/services/embedded_page_service_spec.rb'
- 'spec/support/api_helper.rb'
# Offense count: 32
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/OrderArguments:
# Offense count: 8
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: numeric, symbolic, be_status
RSpecRails/HttpStatus:
Exclude:
- 'spec/controllers/spree/admin/products_controller_spec.rb'
- 'spec/requests/api/orders_spec.rb'
# Offense count: 144
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Inferences.
RSpecRails/InferredSpecType:
Exclude:
- 'engines/dfc_provider/spec/requests/addresses_spec.rb'
- 'engines/dfc_provider/spec/requests/catalog_items_spec.rb'
- 'engines/dfc_provider/spec/requests/enterprise_groups/affiliated_by_spec.rb'
- 'engines/dfc_provider/spec/requests/enterprise_groups_spec.rb'
- 'engines/dfc_provider/spec/requests/enterprises_spec.rb'
- 'engines/dfc_provider/spec/requests/offers_spec.rb'
- 'engines/dfc_provider/spec/requests/persons_spec.rb'
- 'engines/dfc_provider/spec/requests/social_medias_spec.rb'
- 'engines/dfc_provider/spec/requests/supplied_products_spec.rb'
- 'engines/web/spec/helpers/cookies_policy_helper_spec.rb'
- 'spec/controllers/admin/bulk_line_items_controller_spec.rb'
- 'spec/controllers/admin/column_preferences_controller_spec.rb'
- 'spec/controllers/admin/customers_controller_spec.rb'
- 'spec/controllers/admin/enterprises_controller_spec.rb'
- 'spec/controllers/admin/inventory_items_controller_spec.rb'
- 'spec/controllers/admin/invoice_settings_controller_spec.rb'
- 'spec/controllers/admin/matomo_settings_controller_spec.rb'
- 'spec/controllers/admin/order_cycles_controller_spec.rb'
- 'spec/controllers/admin/product_import_controller_spec.rb'
- 'spec/controllers/admin/proxy_orders_controller_spec.rb'
- 'spec/controllers/admin/reports_controller_spec.rb'
- 'spec/controllers/admin/schedules_controller_spec.rb'
- 'spec/controllers/admin/stripe_accounts_controller_spec.rb'
- 'spec/controllers/admin/stripe_connect_settings_controller_spec.rb'
- 'spec/controllers/admin/subscription_line_items_controller_spec.rb'
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
- 'spec/controllers/admin/tag_rules_controller_spec.rb'
- 'spec/controllers/admin/terms_of_service_files_controller_spec.rb'
- 'spec/controllers/admin/variant_overrides_controller_spec.rb'
- 'spec/controllers/api/v0/customers_controller_spec.rb'
- 'spec/controllers/api/v0/enterprise_fees_controller_spec.rb'
- 'spec/controllers/api/v0/enterprises_controller_spec.rb'
- 'spec/controllers/api/v0/exchange_products_controller_spec.rb'
- 'spec/controllers/api/v0/logos_controller_spec.rb'
- 'spec/controllers/api/v0/order_cycles_controller_spec.rb'
- 'spec/controllers/api/v0/orders_controller_spec.rb'
- 'spec/controllers/api/v0/product_images_controller_spec.rb'
- 'spec/controllers/api/v0/products_controller_spec.rb'
- 'spec/controllers/api/v0/promo_images_controller_spec.rb'
- 'spec/controllers/api/v0/reports/packing_report_spec.rb'
- 'spec/controllers/api/v0/reports_controller_spec.rb'
- 'spec/controllers/api/v0/shipments_controller_spec.rb'
- 'spec/controllers/api/v0/shops_controller_spec.rb'
- 'spec/controllers/api/v0/statuses_controller_spec.rb'
- 'spec/controllers/api/v0/terms_and_conditions_controller_spec.rb'
- 'spec/controllers/api/v0/variants_controller_spec.rb'
- 'spec/controllers/base_controller_spec.rb'
- 'spec/controllers/cart_controller_spec.rb'
- 'spec/controllers/checkout_controller_spec.rb'
- 'spec/controllers/enterprises_controller_spec.rb'
- 'spec/controllers/groups_controller_spec.rb'
- 'spec/controllers/line_items_controller_spec.rb'
- 'spec/controllers/payment_gateways/paypal_controller_spec.rb'
- 'spec/controllers/payment_gateways/stripe_controller_spec.rb'
- 'spec/controllers/registration_controller_spec.rb'
- 'spec/controllers/shop_controller_spec.rb'
- 'spec/controllers/shops_controller_spec.rb'
- 'spec/controllers/spree/admin/adjustments_controller_spec.rb'
- 'spec/controllers/spree/admin/base_controller_spec.rb'
- 'spec/controllers/spree/admin/countries_controller_spec.rb'
- 'spec/controllers/spree/admin/general_settings_controller_spec.rb'
- 'spec/controllers/spree/admin/orders/customer_details_controller_spec.rb'
- 'spec/controllers/spree/admin/orders/invoices_spec.rb'
- 'spec/controllers/spree/admin/orders/payments/payments_controller_refunds_spec.rb'
- 'spec/controllers/spree/admin/orders/payments/payments_controller_spec.rb'
- 'spec/controllers/spree/admin/orders_controller_spec.rb'
- 'spec/controllers/spree/admin/overview_controller_spec.rb'
- 'spec/controllers/spree/admin/payment_methods_controller_spec.rb'
- 'spec/controllers/spree/admin/products_controller_spec.rb'
- 'spec/controllers/spree/admin/return_authorizations_controller_spec.rb'
- 'spec/controllers/spree/admin/search_controller_spec.rb'
- 'spec/controllers/spree/admin/shipping_categories_controller_spec.rb'
- 'spec/controllers/spree/admin/shipping_methods_controller_spec.rb'
- 'spec/controllers/spree/admin/tax_rates_controller_spec.rb'
- 'spec/controllers/spree/admin/tax_settings_controller_spec.rb'
- 'spec/controllers/spree/admin/variants_controller_spec.rb'
- 'spec/controllers/spree/api_keys_controller_spec.rb'
- 'spec/controllers/spree/credit_cards_controller_spec.rb'
- 'spec/controllers/spree/orders_controller_spec.rb'
- 'spec/controllers/spree/user_sessions_controller_spec.rb'
- 'spec/controllers/spree/users_controller_spec.rb'
- 'spec/controllers/stripe/callbacks_controller_spec.rb'
- 'spec/controllers/stripe/webhooks_controller_spec.rb'
- 'spec/controllers/user_confirmations_controller_spec.rb'
- 'spec/controllers/user_passwords_controller_spec.rb'
- 'spec/controllers/user_registrations_controller_spec.rb'
- 'spec/controllers/webhook_endpoints_controller_spec.rb'
- 'spec/helpers/admin/enterprises_helper_spec.rb'
- 'spec/helpers/admin/orders_helper_spec.rb'
- 'spec/helpers/admin/reports_helper_spec.rb'
- 'spec/helpers/admin/subscriptions_helper_spec.rb'
- 'spec/helpers/application_helper_spec.rb'
- 'spec/helpers/checkout_helper_spec.rb'
- 'spec/helpers/i18n_helper_spec.rb'
- 'spec/helpers/injection_helper_spec.rb'
- 'spec/helpers/link_helper_spec.rb'
- 'spec/helpers/navigation_helper_spec.rb'
- 'spec/helpers/order_cycles_helper_spec.rb'
- 'spec/helpers/serializer_helper_spec.rb'
- 'spec/helpers/shop_helper_spec.rb'
- 'spec/helpers/spree/admin/base_helper_spec.rb'
- 'spec/helpers/spree/admin/general_settings_helper_spec.rb'
- 'spec/helpers/spree/admin/orders_helper_spec.rb'
- 'spec/helpers/spree/orders_helper_spec.rb'
- 'spec/helpers/tax_helper_spec.rb'
- 'spec/helpers/terms_and_conditions_helper_spec.rb'
- 'spec/jobs/connect_app_job_spec.rb'
- 'spec/mailers/producer_mailer_spec.rb'
- 'spec/mailers/subscription_mailer_spec.rb'
- 'spec/models/column_preference_spec.rb'
- 'spec/models/connected_app_spec.rb'
- 'spec/models/customer_spec.rb'
- 'spec/models/invoice_spec.rb'
- 'spec/models/oidc_account_spec.rb'
- 'spec/models/proxy_order_spec.rb'
- 'spec/models/report_blob_spec.rb'
- 'spec/models/semantic_link_spec.rb'
- 'spec/models/spree/gateway/stripe_sca_spec.rb'
- 'spec/models/subscription_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/models/tag_rule_spec.rb'
- 'spec/models/webhook_endpoint_spec.rb'
- 'spec/requests/admin/images_spec.rb'
- 'spec/requests/admin/product_import_spec.rb'
- 'spec/requests/admin/vouchers_spec.rb'
- 'spec/requests/api/orders_spec.rb'
- 'spec/requests/api/routes_spec.rb'
- 'spec/requests/api/v1/customers_spec.rb'
- 'spec/requests/api_docs_spec.rb'
- 'spec/requests/checkout/paypal_spec.rb'
- 'spec/requests/checkout/routes_spec.rb'
- 'spec/requests/checkout/stripe_sca_spec.rb'
- 'spec/requests/errors_spec.rb'
- 'spec/requests/home_controller_spec.rb'
- 'spec/requests/large_request_spec.rb'
- 'spec/requests/omniauth_callbacks_controller_spec.rb'
- 'spec/requests/spree/admin/overview_spec.rb'
- 'spec/requests/spree/admin/payments_spec.rb'
- 'spec/requests/voucher_adjustments_spec.rb'
- 'spec/routing/stripe_spec.rb'
# Offense count: 22
# Configuration parameters: IgnoreScopes, Include.
# Include: app/models/**/*.rb
Rails/InverseOf:
Exclude:
- 'app/controllers/admin/enterprise_fees_controller.rb'
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/admin/order_cycles_controller.rb'
- 'app/controllers/admin/product_import_controller.rb'
- 'app/controllers/api/v0/states_controller.rb'
- 'app/controllers/spree/admin/overview_controller.rb'
- 'app/controllers/spree/admin/products_controller.rb'
- 'app/helpers/enterprises_helper.rb'
- 'app/models/enterprise.rb'
- 'app/models/enterprise_group.rb'
- 'app/models/enterprise_relationship_permission.rb'
- 'app/models/order_cycle.rb'
- 'app/models/product_import/product_importer.rb'
- 'app/models/schedule.rb'
- 'app/models/spree/country.rb'
- 'app/models/spree/inventory_unit.rb'
- 'app/models/spree/line_item.rb'
- 'app/models/spree/order.rb'
- 'app/models/spree/shipping_rate.rb'
- 'app/models/spree/user.rb'
- 'app/models/spree/zone.rb'
- 'app/models/subscription_line_item.rb'
- 'app/models/tag_rule.rb'
- 'app/models/variant_override.rb'
- 'lib/open_food_network/address_finder.rb'
- 'spec/services/orders/generate_invoice_service_spec.rb'
- 'spec/system/admin/order_cycles/simple_spec.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
Rails/Presence:
Exclude:
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/models/spree/payment.rb'
- 'app/models/spree/price.rb'
- 'app/models/spree/product.rb'
- 'app/models/spree/stock_item.rb'
- 'app/models/spree/taxonomy.rb'
- 'app/models/spree/variant.rb'
- 'app/models/subscription_line_item.rb'
# Offense count: 6
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Severity.
Rails/RedirectBackOrTo:
# Offense count: 35
# Configuration parameters: Include.
# Include: app/controllers/**/*.rb, app/mailers/**/*.rb
Rails/LexicallyScopedActionFilter:
Exclude:
- 'app/controllers/admin/enterprise_groups_controller.rb'
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/admin/order_cycles_controller.rb'
- 'app/controllers/locales_controller.rb'
- 'app/controllers/spree/admin/invoices_controller.rb'
- 'app/controllers/spree/admin/orders_controller.rb'
- 'app/controllers/admin/producer_properties_controller.rb'
- 'app/controllers/admin/product_import_controller.rb'
- 'app/controllers/admin/schedules_controller.rb'
- 'app/controllers/admin/subscriptions_controller.rb'
- 'app/controllers/concerns/checkout_callbacks.rb'
- 'app/controllers/registration_controller.rb'
- 'app/controllers/spree/admin/adjustments_controller.rb'
- 'app/controllers/spree/admin/payment_methods_controller.rb'
- 'app/controllers/spree/admin/payments_controller.rb'
- 'app/controllers/spree/admin/products_controller.rb'
- 'app/controllers/spree/admin/return_authorizations_controller.rb'
- 'app/controllers/spree/admin/search_controller.rb'
- 'app/controllers/spree/admin/shipping_methods_controller.rb'
- 'app/controllers/spree/admin/users_controller.rb'
- 'app/controllers/spree/admin/zones_controller.rb'
- 'app/controllers/spree/users_controller.rb'
# Offense count: 56
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Include.
# Include: spec/controllers/**/*.rb, spec/requests/**/*.rb, test/controllers/**/*.rb, test/integration/**/*.rb
Rails/ResponseParsedBody:
Exclude:
- 'spec/controllers/admin/bulk_line_items_controller_spec.rb'
- 'spec/controllers/admin/customers_controller_spec.rb'
- 'spec/controllers/admin/order_cycles_controller_spec.rb'
- 'spec/controllers/admin/proxy_orders_controller_spec.rb'
- 'spec/controllers/admin/schedules_controller_spec.rb'
- 'spec/controllers/admin/stripe_accounts_controller_spec.rb'
- 'spec/controllers/admin/subscription_line_items_controller_spec.rb'
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
- 'spec/controllers/cart_controller_spec.rb'
- 'spec/controllers/line_items_controller_spec.rb'
- 'spec/controllers/spree/admin/search_controller_spec.rb'
- 'spec/controllers/spree/credit_cards_controller_spec.rb'
- 'spec/controllers/user_registrations_controller_spec.rb'
# Offense count: 7
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: strict, flexible
Rails/TimeZone:
Exclude:
- 'app/models/spree/gateway/pay_pal_express.rb'
- 'spec/controllers/spree/credit_cards_controller_spec.rb'
- 'spec/services/order_cycles/webhook_service_spec.rb'
- 'spec/services/orders/customer_cancellation_service_spec.rb'
# Offense count: 1
# Configuration parameters: TransactionMethods.
@@ -360,25 +638,45 @@ Rails/TransactionExitStatement:
Exclude:
- 'app/services/place_proxy_order.rb'
# Offense count: 3
# Offense count: 5
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/UniqueValidationWithoutIndex:
Exclude:
- 'app/models/customer.rb'
- 'app/models/exchange.rb'
- 'app/models/spree/stock_item.rb'
- 'app/models/spree/tax_category.rb'
- 'app/models/spree/zone.rb'
# Offense count: 1
Security/Open:
Exclude:
- 'app/services/image_importer.rb'
# Offense count: 7
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/ArrayIntersect:
Exclude:
- 'app/models/spree/ability.rb'
- 'app/models/spree/variant.rb'
- 'app/models/tag_rule/filter_order_cycles.rb'
- 'app/models/tag_rule/filter_payment_methods.rb'
- 'app/models/tag_rule/filter_products.rb'
- 'app/models/tag_rule/filter_shipping_methods.rb'
- 'lib/open_food_network/tag_rule_applicator.rb'
- 'spec/support/matchers/select2_matchers.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/BitwisePredicate:
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowOnConstant, AllowOnSelfClass.
Style/CaseEquality:
Exclude:
- 'app/helpers/admin/enterprises_helper.rb'
- 'spec/models/spree/payment_spec.rb'
# Offense count: 23
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: nested, compact
# SupportedStylesForClasses: ~, nested, compact
# SupportedStylesForModules: ~, nested, compact
Style/ClassAndModuleChildren:
Exclude:
- 'app/models/calculator/flat_percent_per_item.rb'
@@ -404,35 +702,87 @@ Style/ClassAndModuleChildren:
- 'lib/open_food_network/locking.rb'
- 'spec/models/spree/payment_method_spec.rb'
# Offense count: 14
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/CollectionQuerying:
Exclude:
- 'app/controllers/spree/credit_cards_controller.rb'
- 'app/models/product_import/product_importer.rb'
- 'app/models/product_import/spreadsheet_entry.rb'
- 'app/models/spree/order.rb'
- 'app/models/spree/order_inventory.rb'
- 'app/models/spree/payment_method.rb'
- 'app/models/spree/user.rb'
- 'app/models/stripe_account.rb'
- 'app/services/order_cycles/warning_service.rb'
- 'lib/reporting/report_renderer.rb'
- 'lib/tasks/sample_data.rake'
# Offense count: 2
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/HashSlice:
Exclude:
- 'app/services/product_filters.rb'
- 'lib/reporting/report_row_builder.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, always_true, never
Style/FrozenStringLiteralComment:
Exclude:
- '.simplecov'
# Offense count: 6
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/GlobalStdStream:
Exclude:
- 'lib/tasks/data.rake'
- 'lib/tasks/missing_payments.rake'
- 'lib/tasks/sample_data/logging.rb'
- 'lib/tasks/subscriptions/debug.rake'
- 'lib/tasks/subscriptions/test.rake'
# Offense count: 12
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowSplatArgument.
Style/HashConversion:
Exclude:
- 'app/controllers/admin/column_preferences_controller.rb'
- 'app/controllers/admin/variant_overrides_controller.rb'
- 'app/controllers/spree/admin/products_controller.rb'
- 'app/models/order_cycle.rb'
- 'app/models/product_import/product_importer.rb'
- 'app/models/spree/shipping_method.rb'
- 'app/serializers/api/admin/exchange_serializer.rb'
- 'app/services/variants_stock_levels.rb'
- 'spec/controllers/admin/inventory_items_controller_spec.rb'
- 'spec/controllers/admin/variant_overrides_controller_spec.rb'
# Offense count: 13
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowedReceivers.
# AllowedReceivers: Thread.current
Style/HashEachMethods:
Exclude:
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/spree/admin/shipping_methods_controller.rb'
- 'app/forms/enterprise_fees_bulk_update.rb'
- 'app/models/product_import/entry_processor.rb'
- 'app/models/spree/preferences/configuration.rb'
- 'app/services/sets/model_set.rb'
- 'lib/reporting/reports/sales_tax/sales_tax_totals_by_producer.rb'
- 'spec/models/product_importer_spec.rb'
- 'spec/support/cancan_helper.rb'
# Offense count: 4
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/MapToHash:
Exclude:
- 'lib/reporting/report_query_template.rb'
- 'lib/reporting/report_row_builder.rb'
- 'lib/reporting/reports/enterprise_fee_summary/fee_summary.rb'
- 'lib/tasks/sample_data/user_factory.rb'
# Offense count: 3
Style/MissingRespondToMissing:
Exclude:
- 'app/helpers/application_helper.rb'
- 'app/models/spree/gateway.rb'
- 'app/models/spree/preferences/configuration.rb'
# Offense count: 22
# This cop supports safe autocorrection (--autocorrect).
Style/NestedModifier:
Exclude:
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
- 'spec/controllers/line_items_controller_spec.rb'
- 'spec/controllers/spree/admin/orders_controller_spec.rb'
- 'spec/controllers/spree/orders_controller_spec.rb'
- 'spec/factories/order_factory.rb'
- 'spec/models/proxy_order_spec.rb'
- 'spec/models/spree/line_item_spec.rb'
- 'spec/services/place_proxy_order_spec.rb'
- 'spec/system/admin/payments_stripe_spec.rb'
- 'spec/system/admin/reports_spec.rb'
# Offense count: 38
Style/OpenStructUse:
Exclude:
@@ -462,21 +812,47 @@ Style/OptionalBooleanParameter:
- 'lib/spree/core/controller_helpers/order.rb'
- 'spec/support/request/web_helper.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: short, verbose
Style/PreferredHashMethods:
Exclude:
- 'app/controllers/api/v0/shipments_controller.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Methods.
Style/RedundantArgument:
Exclude:
- 'engines/dfc_provider/app/services/authorization_control.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantAssignment:
Exclude:
- 'spec/models/database_spec.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AutoCorrect, AllowComments.
Style/RedundantInitialize:
Exclude:
- 'spec/models/spree/gateway_spec.rb'
# Offense count: 19
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowedMethods, AllowedPatterns.
Style/ReturnNilInPredicateMethodDefinition:
Exclude:
- 'app/models/order_cycle.rb'
- 'app/serializers/api/admin/customer_serializer.rb'
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
# Offense count: 3
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/RedundantFormat:
Style/SlicingWithRange:
Exclude:
- 'spec/models/product_importer_spec.rb'
- 'spec/requests/checkout/stripe_sca_spec.rb'
- 'spec/system/consumer/account/cards_spec.rb'
# Offense count: 8
# Configuration parameters: Max.
Style/SafeNavigationChainLength:
Exclude:
- 'app/controllers/concerns/extra_fields.rb'
- 'app/services/customer_syncer.rb'
- 'app/services/fdc_offer_broker.rb'
- 'engines/dfc_provider/app/services/dfc_catalog.rb'
- 'engines/dfc_provider/app/services/image_builder.rb'
- 'engines/dfc_provider/app/services/quantitative_value_builder.rb'
- 'app/helpers/spree/admin/navigation_helper.rb'
- 'app/services/embedded_page_service.rb'
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'

View File

@@ -1 +1 @@
3.3.10
3.1.4

19
.scss-lint.yml Normal file
View File

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

View File

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

View File

@@ -1,18 +1,19 @@
#!/bin/env ruby
# frozen_string_literal: true
SimpleCov.start 'rails' do
# The rails profile contains some filters already:
#
# - "/test/"
# - "/features/"
# - "/spec/"
# - "/autotest/"
# - /^\/config\//
# - /^\/db\//
add_filter '/bin/'
add_filter '/config/' # to include engine config
add_filter '/config/'
add_filter '/jobs/application_job.rb'
add_filter '/schemas/'
add_filter '/lib/generators'
add_filter '/spec/'
add_filter '/vendor/'
add_filter '/public'
add_filter '/swagger'
add_filter '/script'
add_filter '/log'
add_filter '/db'
add_filter '/lib/tasks/sample_data/'
formatter SimpleCov::Formatter::SimpleFormatter
end

View File

@@ -1,9 +0,0 @@
#!/bin/env ruby
# frozen_string_literal: true
--compare master
# This shouldn't be needed in undercover > 0.7.4
#
# * https://github.com/grodowski/undercover/issues/233
--exclude-files "bin/*,db/*,config/*,spec/*,engines/*/config/*,engines/*/spec/*"

View File

@@ -1,34 +1,92 @@
FROM ruby:3.3.10-alpine3.19 AS base
ENV LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
TZ=Europe/London \
RAILS_ROOT=/usr/src/app \
BUNDLE_PATH=/bundles \
BUNDLE_APP_CONFIG=/bundles
RUN apk --no-cache upgrade && \
apk add --no-cache tzdata postgresql-client imagemagick imagemagick-jpeg && \
apk add --no-cache --virtual wkhtmltopdf
FROM ubuntu:20.04
WORKDIR $RAILS_ROOT
ENV TZ Europe/London
# Development dependencies
FROM base AS development-base
RUN apk add --no-cache --virtual .build-deps \
build-base postgresql-dev git nodejs yarn && \
apk add --no-cache --virtual .dev-utils \
bash curl less vim chromium-chromedriver zlib-dev openssl-dev cmake\
readline-dev yaml-dev sqlite-dev libxml2-dev libxslt-dev libffi-dev vips-dev && \
curl -o /usr/local/bin/wait-for-it https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh && \
chmod +x /usr/local/bin/wait-for-it
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Install yarn dependencies separately for caching
FROM development-base AS yarn-dependencies
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
RUN echo "deb http://security.ubuntu.com/ubuntu bionic-security main" >> /etc/apt/sources.list
# Install Ruby gems
FROM development-base
COPY . $RAILS_ROOT
COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs "$(nproc)"
COPY --from=yarn-dependencies $RAILS_ROOT/node_modules ./node_modules
# Install all the requirements
RUN apt-get update && apt-get install -y \
curl \
git \
build-essential \
software-properties-common \
wget \
zlib1g-dev \
libreadline-dev \
libyaml-dev \
libffi-dev \
libxml2-dev \
libxslt1-dev \
wait-for-it \
imagemagick \
unzip \
libjemalloc-dev \
libssl-dev \
ca-certificates \
gnupg
# Setup ENV variables
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:/usr/local/src/nodenv/shims:/usr/local/src/nodenv/bin:$PATH
ENV RBENV_ROOT /usr/local/src/rbenv
ENV NODENV_ROOT /usr/local/src/nodenv
ENV CONFIGURE_OPTS --disable-install-doc
ENV BUNDLE_PATH /bundles
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so
WORKDIR /usr/src/app
# trim spaces and line return from .ruby-version file
COPY .ruby-version .ruby-version.raw
RUN cat .ruby-version.raw | tr -d '\r\t ' > .ruby-version
# Install Rbenv & Ruby
RUN git clone --depth 1 https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
git clone --depth 1 https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh && \
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install $(cat .ruby-version) && \
rbenv global $(cat .ruby-version)
# Install Postgres
RUN sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main' >> /etc/apt/sources.list.d/pgdg.list" && \
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null && \
apt-get update && \
apt-get install -yqq --no-install-recommends postgresql-client-10 libpq-dev
# trim spaces and line return from .node-version file
COPY .node-version .node-version.raw
RUN cat .node-version.raw | tr -d '\r\t ' > .node-version
# Install Node and Yarn with Nodenv
RUN git clone --depth 1 https://github.com/nodenv/nodenv.git ${NODENV_ROOT} && \
git clone --depth 1 https://github.com/nodenv/node-build.git ${NODENV_ROOT}/plugins/node-build && \
git clone --depth 1 https://github.com/pine/nodenv-yarn-install.git ${NODENV_ROOT}/plugins/nodenv-yarn-install && \
git clone --depth 1 https://github.com/nodenv/nodenv-package-rehash.git ${NODENV_ROOT}/plugins/nodenv-package-rehash && \
echo 'eval "$(nodenv init -)"' >> /etc/profile.d/nodenv.sh && \
nodenv install $(cat .node-version) && \
nodenv global $(cat .node-version)
# Install Chrome
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
sh -c "echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google-chrome.list" && \
apt-get update && \
apt-get install -fy google-chrome-stable
# Install Chromedriver
RUN wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip && \
unzip chromedriver_linux64.zip -d /usr/bin && \
chmod u+x /usr/bin/chromedriver
# Copy code and install app dependencies
COPY . /usr/src/app/
# Install Bundler
RUN ./script/install-bundler
# Install front-end dependencies
RUN yarn install
# Run bundler install in parallel with the amount of available CPUs
RUN bundle install --jobs="$(nproc)"

View File

@@ -1,93 +0,0 @@
FROM ubuntu:20.04
ENV TZ Europe/London
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN echo "deb http://security.ubuntu.com/ubuntu bionic-security main" >> /etc/apt/sources.list
# Install all the requirements
RUN apt-get update && apt-get install -y \
curl \
git \
build-essential \
software-properties-common \
wget \
zlib1g-dev \
libreadline-dev \
libyaml-dev \
libffi-dev \
libxml2-dev \
libxslt1-dev \
wait-for-it \
imagemagick \
unzip \
libjemalloc-dev \
libssl-dev \
ca-certificates \
gnupg \
cmake
# Setup ENV variables
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:/usr/local/src/nodenv/shims:/usr/local/src/nodenv/bin:$PATH
ENV RBENV_ROOT /usr/local/src/rbenv
ENV NODENV_ROOT /usr/local/src/nodenv
ENV CONFIGURE_OPTS --disable-install-doc
ENV BUNDLE_PATH /bundles
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so
WORKDIR /usr/src/app
# trim spaces and line return from .ruby-version file
COPY .ruby-version .ruby-version.raw
RUN cat .ruby-version.raw | tr -d '\r\t ' > .ruby-version
# Install Rbenv & Ruby
RUN git clone --depth 1 https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
git clone --depth 1 https://github.com/rbenv/ruby-build.git ${RBENV_ROOT}/plugins/ruby-build && \
echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh && \
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install $(cat .ruby-version) && \
rbenv global $(cat .ruby-version)
# Install Postgres
RUN sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main' >> /etc/apt/sources.list.d/pgdg.list" && \
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null && \
apt-get update && \
apt-get install -yqq --no-install-recommends postgresql-client-10 libpq-dev
# trim spaces and line return from .node-version file
COPY .node-version .node-version.raw
RUN cat .node-version.raw | tr -d '\r\t ' > .node-version
# Install Node and Yarn with Nodenv
RUN git clone --depth 1 https://github.com/nodenv/nodenv.git ${NODENV_ROOT} && \
git clone --depth 1 https://github.com/nodenv/node-build.git ${NODENV_ROOT}/plugins/node-build && \
git clone --depth 1 https://github.com/pine/nodenv-yarn-install.git ${NODENV_ROOT}/plugins/nodenv-yarn-install && \
git clone --depth 1 https://github.com/nodenv/nodenv-package-rehash.git ${NODENV_ROOT}/plugins/nodenv-package-rehash && \
echo 'eval "$(nodenv init -)"' >> /etc/profile.d/nodenv.sh && \
nodenv install $(cat .node-version) && \
nodenv global $(cat .node-version)
# Install Chrome
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
sh -c "echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google-chrome.list" && \
apt-get update && \
apt-get install -fy google-chrome-stable
# Install Chromedriver
RUN wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip && \
unzip chromedriver_linux64.zip -d /usr/bin && \
chmod u+x /usr/bin/chromedriver
# Copy code and install app dependencies
COPY . /usr/src/app/
# Install Bundler
RUN ./script/install-bundler
# Install front-end dependencies
RUN yarn install
# Run bundler install in parallel with the amount of available CPUs
RUN bundle install --jobs="$(nproc)"

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

33
Gemfile
View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
source 'https://gem.coop'
source 'https://rubygems.org'
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
ruby File.read('.ruby-version').chomp
@@ -14,19 +14,16 @@ gem "active_storage_validations"
gem "aws-sdk-s3", require: false
gem "image_processing"
gem 'activemerchant'
gem 'angular-rails-templates'
gem 'activemerchant', '>= 1.78.0'
gem 'angular-rails-templates', '>= 0.3.0'
gem 'ransack', '~> 4.1.0'
gem 'responders'
gem 'webpacker', '~> 5'
# Indirect dependency but we access it directly in JS specs.
# It turns out to be hard to upgrade but please do if you can.
gem 'sprockets', '~> 3.7'
gem 'i18n'
gem 'i18n-js', '~> 3.9.0'
gem 'rails-i18n'
gem 'rails_safe_tasks', '~> 1.0'
gem "activerecord-import"
gem "db2fog", github: "openfoodfoundation/db2fog", branch: "rails-7"
@@ -43,13 +40,13 @@ gem 'web', path: './engines/web'
gem "activerecord-postgresql-adapter"
gem "arel-helpers", "~> 2.12"
gem "pg"
gem "pg", "~> 1.2.3"
gem 'acts_as_list', '1.0.4'
gem 'cancancan', '~> 1.15.0'
gem 'digest'
gem 'ffaker'
gem 'highline'
gem 'highline', '2.0.3' # Necessary for the install generator
gem 'json'
gem 'monetize', '~> 1.11'
gem 'paranoia', '~> 2.4'
@@ -57,7 +54,7 @@ gem 'state_machines-activerecord'
gem 'stringex', '~> 2.8.5', require: false
gem 'paypal-sdk-merchant', '1.117.2'
gem 'stripe', '~> 15'
gem 'stripe'
gem 'devise'
gem 'devise-encryptable'
@@ -68,7 +65,7 @@ gem 'oauth2', '~> 1.4.7' # Used for Stripe Connect
gem 'datafoodconsortium-connector'
gem 'jsonapi-serializer'
gem 'pagy', '~> 9'
gem 'pagy', '~> 5.1'
gem 'rswag-api'
gem 'rswag-ui'
@@ -89,7 +86,7 @@ gem "active_model_serializers", "0.8.4"
gem 'activerecord-session_store'
gem 'acts-as-taggable-on'
gem 'angularjs-file-upload-rails', '~> 2.4.1'
gem 'bigdecimal'
gem 'bigdecimal', '3.0.2'
gem 'bootsnap', require: false
gem 'geocoder'
gem 'gmaps4rails'
@@ -155,7 +152,6 @@ end
group :test, :development do
gem 'bullet'
gem 'capybara'
gem 'capybara-shadowdom'
gem 'cuprite'
gem 'database_cleaner', require: false
gem 'debug', '>= 1.0.0'
@@ -170,14 +166,13 @@ group :test, :development do
gem 'rswag'
gem 'shoulda-matchers'
gem 'stimulus_reflex_testing', github: "podia/stimulus_reflex_testing", branch: :main
gem 'timecop'
end
group :test do
gem 'pdf-reader'
gem 'puffing-billy'
gem 'rails-controller-testing'
gem 'simplecov', require: false
gem 'undercover', require: false
gem 'vcr', require: false
gem 'webmock', require: false
# See spec/spec_helper.rb for instructions
@@ -185,19 +180,15 @@ group :test do
end
group :development do
gem 'debugger-linecache'
gem 'foreman'
gem 'haml_lint', require: false
gem 'i18n-tasks'
gem 'listen'
gem 'pry'
gem 'pry', '~> 0.13.0'
gem 'query_count'
gem 'rails-erd'
gem 'rubocop'
gem 'rubocop-capybara'
gem 'rubocop-factory_bot'
gem 'rubocop-rails'
gem 'rubocop-rspec'
gem 'rubocop-rspec_rails'
gem 'spring'
gem 'spring-commands-rspec'
gem 'spring-commands-rubocop'

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
# Foreman Procfile for Docker env. Start all dev server processes with: `bundle exec foreman start -f Procfile.docker`
webpack: WEBPACKER_DEV_SERVER_HOST=0.0.0.0 ./bin/webpack-dev-server
sidekiq: DEV_CACHING=true bundle exec sidekiq -q mailers -q default
rails: WEBPACKER_DEV_SERVER_HOST=0.0.0.0 DEV_CACHING=true bundle exec rails s -p 3000 -b 0.0.0.0

View File

@@ -67,5 +67,8 @@
// foundation
//= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js
// LocalStorage
//= require ../shared/angular-local-storage.js
// requires the rest of the JS code in this folder
//= require_tree .

View File

@@ -0,0 +1,397 @@
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, $location, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor, SortOptions, ErrorsParser, ProductFiltersUrl) ->
$scope.StatusMessage = StatusMessage
$scope.columns = Columns.columns
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
$scope.RequestMonitor = RequestMonitor
$scope.pagination = BulkProducts.pagination
$scope.per_page_options = [
{id: 15, name: t('js.admin.orders.index.per_page', results: 15)},
{id: 50, name: t('js.admin.orders.index.per_page', results: 50)},
{id: 100, name: t('js.admin.orders.index.per_page', results: 100)}
]
$scope.q = {
producerFilter: ""
categoryFilter: ""
importDateFilter: ""
query: ""
sorting: ""
}
$scope.sorting = "name asc"
$scope.producers = producers
$scope.taxons = Taxons.all
$scope.tax_categories = tax_categories
$scope.page = 1
$scope.per_page = 15
$scope.products = BulkProducts.products
$scope.DisplayProperties = DisplayProperties
$scope.sortOptions = SortOptions
$scope.initialise = ->
$scope.q = ProductFiltersUrl.loadFromUrl($location.search())
$scope.fetchProducts()
$scope.$watchCollection '[q.query, q.producerFilter, q.categoryFilter, q.importDateFilter, per_page]', ->
$scope.page = 1 # Reset page when changing filters for new search
$scope.changePage = (newPage) ->
$scope.page = newPage
$scope.fetchProducts()
$scope.fetchProducts = ->
removeClearedValues()
params = {
'q[name_cont]': $scope.q.query,
'q[variants_supplier_id_eq]': $scope.q.producerFilter,
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter,
'q[s]': $scope.sorting,
import_date: $scope.q.importDateFilter,
page: $scope.page,
per_page: $scope.per_page
}
RequestMonitor.load(BulkProducts.fetch(params).$promise).then ->
# update url with the filters used
$location.search(ProductFiltersUrl.generate($scope.q))
$scope.resetProducts()
removeClearedValues = ->
delete $scope.q.producerFilter if $scope.q.producerFilter == "0"
delete $scope.q.categoryFilter if $scope.q.categoryFilter == "0"
delete $scope.q.importDateFilter if $scope.q.importDateFilter == "0"
$timeout ->
if $scope.showLatestImport
$scope.q.importDateFilter = $scope.importDates[1].id
$scope.resetProducts = ->
DirtyProducts.clear()
StatusMessage.clear()
$scope.updateOnHand = (product) ->
on_demand_variants = []
if product.variants
on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
unless product.on_demand || on_demand_variants.length > 0
product.on_hand = $scope.onHand(product)
$scope.onHand = (product) ->
onHand = 0
if product.hasOwnProperty("variants") and product.variants instanceof Object
for id, variant of product.variants
onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
else
onHand = "error"
onHand
$scope.shiftTab = (tab) ->
$scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined
tab.visible = !tab.visible
$scope.visibleTab = tab
$scope.resetSelectFilters = ->
$scope.q.query = ""
$scope.q.producerFilter = "0"
$scope.q.categoryFilter = "0"
$scope.q.importDateFilter = "0"
$scope.fetchProducts()
$scope.$watch 'sortOptions', (sort) ->
return unless sort && sort.predicate != ""
$scope.sorting = sort.getSortingExpr(defaultDirection: "asc")
$scope.fetchProducts()
, true
confirm_unsaved_changes = () ->
(DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
editProductUrl = (product, variant) ->
"/admin/products/" + product.id + ((if variant then "/variants/" + variant.id else "")) + "/edit"
$scope.editWarn = (product, variant) ->
if confirm_unsaved_changes()
$window.location.href = ProductFiltersUrl.buildUrl(editProductUrl(product, variant), $scope.q)
$scope.toggleShowAllVariants = ->
showVariants = !DisplayProperties.showVariants 0
$scope.products.forEach (product) ->
DisplayProperties.setShowVariants product.id, showVariants
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
unit_value: null
unit_description: null
on_demand: false
display_as: null
display_name: null
on_hand: null
price: null
tax_category_id: null
category_id: newVariantCategoryId
DisplayProperties.setShowVariants product.id, true
DirtyProducts.addVariantProperty(product.id, newVariantId, 'category_id', newVariantCategoryId)
$scope.nextVariantId = ->
$scope.variantIdCounter = 0 unless $scope.variantIdCounter?
$scope.variantIdCounter -= 1
$scope.variantIdCounter
$scope.deleteProduct = (product) ->
if confirm(t('are_you_sure'))
$http(
method: "DELETE"
url: "/api/v0/products/" + product.id
).then (response) ->
$scope.products.splice $scope.products.indexOf(product), 1
DirtyProducts.deleteProduct product.id
$scope.displayDirtyProducts()
$scope.deleteVariant = (product, variant) ->
if product.variants.length > 1
if !$scope.variantSaved(variant)
$scope.removeVariant(product, variant)
else
if confirm(t("are_you_sure"))
$http(
method: "DELETE"
url: "/api/v0/products/" + product.id + "/variants/" + variant.id
).then (response) ->
$scope.removeVariant(product, variant)
else
alert(t("delete_product_variant"))
$scope.removeVariant = (product, variant) ->
product.variants.splice product.variants.indexOf(variant), 1
DirtyProducts.deleteVariant product.id, variant.id
$scope.displayDirtyProducts()
$scope.cloneProduct = (product) ->
BulkProducts.cloneProduct product
$scope.hasVariants = (product) ->
product.variants.length > 0
$scope.hasUnit = (product) ->
product.variant_unit_with_scale?
$scope.variantSaved = (variant) ->
variant.hasOwnProperty('id') && variant.id > 0
$scope.hasOnDemandVariants = (product) ->
(variant for id, variant of product.variants when variant.on_demand).length > 0
$scope.submitProducts = ->
# Pack pack $scope.products, so they will match the list returned from the server,
# then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
$scope.packProduct product for id, product of $scope.products
$scope.packProduct product for id, product of DirtyProducts.all()
productsToSubmit = filterSubmitProducts(DirtyProducts.all())
if productsToSubmit.length > 0
$scope.updateProducts productsToSubmit # Don't submit an empty list
else
StatusMessage.display 'alert', t("products_change")
$scope.updateProducts = (productsToSubmit) ->
$scope.displayUpdating()
$http(
method: "POST"
url: "/admin/products/bulk_update"
data:
products: productsToSubmit
filters:
'q[name_cont]': $scope.q.query
'q[variants_supplier_id_eq]': $scope.q.producerFilter
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter
'q[s]': $scope.sorting
import_date: $scope.q.importDateFilter
page: $scope.page
per_page: $scope.per_page
).then((response) ->
DirtyProducts.clear()
BulkProducts.updateVariantLists(response.data.products || [])
$timeout -> $scope.displaySuccess()
).catch (response) ->
if response.status == 400 && response.data.errors?
errorsString = ErrorsParser.toString(response.data.errors, response.status)
$scope.displayFailure t("products_update_error") + "\n" + errorsString
else
$scope.displayFailure t("products_update_error_data") + response.status
$scope.cancel = (destination) ->
$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 product, variant
$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 && 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 = ->
if $scope.limit < $scope.products.length
$scope.limit = $scope.limit + 5
$scope.displayUpdating = ->
StatusMessage.display 'progress', t("saving")
$scope.displaySuccess = ->
StatusMessage.display 'success',t("products_changes_saved")
$scope.bulk_product_form.$setPristine()
$scope.displayFailure = (failMessage) ->
StatusMessage.display 'failure', t("products_update_error_msg") + " #{failMessage}"
$scope.displayDirtyProducts = ->
count = DirtyProducts.count()
switch count
when 0 then StatusMessage.clear()
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
else StatusMessage.display 'notice', t("products_unsaved", n: count)
filterSubmitProducts = (productsToFilter) ->
filteredProducts = []
if productsToFilter instanceof Object
angular.forEach productsToFilter, (product) ->
if product.hasOwnProperty("id")
filteredProduct = {id: product.id}
filteredVariants = []
hasUpdatableProperty = false
if product.hasOwnProperty("variants")
angular.forEach product.variants, (variant) ->
result = filterSubmitVariant variant
filteredVariant = result.filteredVariant
variantHasUpdatableProperty = result.hasUpdatableProperty
filteredVariants.push filteredVariant if variantHasUpdatableProperty
if product.hasOwnProperty("sku")
filteredProduct.sku = product.sku
hasUpdatableProperty = true
if product.hasOwnProperty("name")
filteredProduct.name = product.name
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
if product.hasOwnProperty("on_demand") and filteredVariants.length == 0 #only update if no variants present
filteredProduct.on_demand = product.on_demand
hasUpdatableProperty = true
if product.hasOwnProperty("inherits_properties")
filteredProduct.inherits_properties = product.inherits_properties
hasUpdatableProperty = true
if filteredVariants.length > 0 # Note that the name of the property changes to enable mass assignment of variants attributes with rails
filteredProduct.variants_attributes = filteredVariants
hasUpdatableProperty = true
filteredProducts.push filteredProduct if hasUpdatableProperty
filteredProducts
filterSubmitVariant = (variant) ->
hasUpdatableProperty = false
filteredVariant = {}
if not variant.deleted_at? and variant.hasOwnProperty("id")
filteredVariant.id = variant.id unless variant.id <= 0
if variant.hasOwnProperty("sku")
filteredVariant.sku = variant.sku
hasUpdatableProperty = true
if variant.hasOwnProperty("on_hand")
filteredVariant.on_hand = variant.on_hand
hasUpdatableProperty = true
if variant.hasOwnProperty("on_demand")
filteredVariant.on_demand = variant.on_demand
hasUpdatableProperty = true
if variant.hasOwnProperty("price")
filteredVariant.price = variant.price
hasUpdatableProperty = true
if variant.hasOwnProperty("unit_value")
filteredVariant.unit_value = variant.unit_value
hasUpdatableProperty = true
if variant.hasOwnProperty("unit_description")
filteredVariant.unit_description = variant.unit_description
hasUpdatableProperty = true
if variant.hasOwnProperty("display_name")
filteredVariant.display_name = variant.display_name
hasUpdatableProperty = true
if variant.hasOwnProperty("tax_category_id")
filteredVariant.tax_category_id = variant.tax_category_id
hasUpdatableProperty = true
if variant.hasOwnProperty("category_id")
filteredVariant.primary_taxon_id = variant.category_id
hasUpdatableProperty = true
if variant.hasOwnProperty("display_as")
filteredVariant.display_as = variant.display_as
hasUpdatableProperty = true
if variant.hasOwnProperty("producer_id")
filteredVariant.supplier_id = variant.producer_id
hasUpdatableProperty = true
{filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty}
toObjectWithIDKeys = (array) ->
object = {}
for i of array
if array[i] instanceof Object and array[i].hasOwnProperty("id")
object[array[i].id] = angular.copy(array[i])
object[array[i].id].variants = toObjectWithIDKeys(array[i].variants) if array[i].hasOwnProperty("variants") and array[i].variants instanceof Array
object

View File

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

View File

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

@@ -55,6 +55,12 @@ angular.module("admin.enterprises")
else
alert ("#{manager.email}" + " " + t("is_already_manager"))
$scope.removeLogo = ->
$scope.performEnterpriseAction("removeLogo", "immediate_logo_removal_warning", "removed_logo_successfully")
$scope.removePromoImage = ->
$scope.performEnterpriseAction("removePromoImage", "immediate_promo_image_removal_warning", "removed_promo_image_successfully")
$scope.performEnterpriseAction = (enterpriseActionName, warning_message_key, success_message_key) ->
return unless confirm($scope.translation(warning_message_key))

View File

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

View File

@@ -4,16 +4,15 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
type: "@objForUpdate"
attr: "@attrForUpdate"
link: (scope, element, attrs) ->
scope.savedValue = scope.object()[scope.attr] || ""
scope.savedValue = scope.object()[scope.attr]
scope.$watch "object().#{scope.attr}", (value) ->
strValue = value || ""
if strValue == scope.savedValue
if value == scope.savedValue
pendingChanges.remove(scope.object().id, scope.attr)
scope.clear()
else
scope.pending()
addPendingChange(scope.attr, strValue)
addPendingChange(scope.attr, value ? "")
scope.reset = (value) ->
scope.savedValue = value
@@ -38,13 +37,16 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
# To ensure the customer is still updated, we check on the $destroy event to see if
# the attribute has changed, if so we queue up the change.
scope.$on '$destroy', (value) ->
currentValue = scope.object()[scope.attr] || ""
# No update
return if currentValue is scope.savedValue
return if scope.object()[scope.attr] is scope.savedValue
# For some reason the code attribute is removed from the object when cleared, so we add
# an emptyvalue so it gets updated properly
if scope.attr is "code" and scope.object()[scope.attr] is undefined
scope.object()["code"] = ""
# Queuing up change
addPendingChange(scope.attr, currentValue)
addPendingChange(scope.attr, scope.object()[scope.attr])
# private

View File

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

View File

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

View File

@@ -19,6 +19,18 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.page = 1
$scope.per_page = $scope.per_page_options[0].id
$scope.filterByVariantId = null
searchThrough = ["order_distributor_name",
"order_bill_address_phone",
"order_bill_address_firstname",
"order_bill_address_lastname",
"order_bill_address_full_name",
"order_bill_address_full_name_reversed",
"order_bill_address_full_name_with_comma",
"order_bill_address_full_name_with_comma_reversed",
"variant_supplier_name",
"order_email",
"order_number",
"product_name"].join("_or_") + "_cont"
$scope.confirmRefresh = ->
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
@@ -63,10 +75,11 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
[formattedStartDate, formattedEndDate] = $scope.formatDates($scope.startDate, $scope.endDate)
RequestMonitor.load LineItems.index(
"q[#{searchThrough}]": $scope.query,
"q[variant_id_eq]": $scope.filterByVariantId if $scope.filterByVariantId,
"q[order_state_not_eq]": "canceled",
"q[order_shipment_state_not_eq]": "shipped",
"q[order_completed_at_not_null]": "true",
"q[variant_id_eq]": $scope.filterByVariantId if $scope.filterByVariantId,
"q[order_distributor_id_eq]": $scope.distributorFilter,
"q[variant_supplier_id_eq]": $scope.supplierFilter,
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
@@ -74,8 +87,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
"q[order_completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
"q[s]": "order_completed_at desc",
"page": $scope.page,
"per_page": $scope.per_page,
"search_query": $scope.query
"per_page": $scope.per_page
)
$scope.formatDates = (startDate, endDate) ->
@@ -85,7 +97,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.loadAssociatedData = ->
RequestMonitor.load $scope.distributors = Enterprises.index(action: "visible", ams_prefix: "basic", "q[sells_in][]": ["own", "any"])
RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{moment().subtract(1,'year').format()}")
RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{moment().subtract(90,'days').format()}")
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
$scope.dereferenceLoadedData = ->
@@ -187,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))
@@ -202,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))
@@ -216,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)
$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

@@ -5,8 +5,6 @@ angular.module("admin.orders").controller "orderCtrl", ($scope, shops, orderCycl
$scope.distributor_id = parseInt($attrs.ofnDistributorId)
$scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId)
$scope.search_variants_as = $attrs.ofnSearchVariantsAs
$scope.order_id = $attrs.ofnOrderId
$scope.validOrderCycle = (oc) ->
$scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id)

View File

@@ -15,4 +15,4 @@ angular.module("admin.paymentMethods").controller "StripeController", ($scope, $
permalink = shops.filter((shop) ->
shop.id == $scope.paymentMethod.preferred_enterprise_id
)[0].permalink
"/admin/enterprises/#{permalink}/edit#/payment_methods_panel"
"/admin/enterprises/#{permalink}/edit#/payment_methods"

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

@@ -0,0 +1,8 @@
angular.module("ofn.admin").controller "ProductImageCtrl", ($scope, ProductImageService) ->
$scope.imageUploader = ProductImageService.imageUploader
$scope.imagePreview = ProductImageService.imagePreview
$scope.$watch 'product.image_url', (newValue, oldValue) ->
if newValue != oldValue
$scope.imagePreview = newValue
$scope.uploadModal.close()

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,6 @@
angular.module("ofn.admin").directive "imageModal", ($modal, ProductImageService) ->
restrict: 'C'
link: (scope, elem, attrs, ctrl) ->
elem.on "click", (ev) =>
scope.uploadModal = $modal.open(templateUrl: 'admin/modals/image_upload.html', controller: ctrl, scope: scope, windowClass: 'simple-modal')
ProductImageService.configure(scope.product)

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

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

@@ -8,4 +8,10 @@ angular.module("admin.resources").factory 'EnterpriseResource', ($resource) ->
isArray: true
'update':
method: 'PUT'
'removeLogo':
url: '/api/v0/enterprises/:id/logo.json'
method: 'DELETE'
'removePromoImage':
url: '/api/v0/enterprises/:id/promo_image.json'
method: 'DELETE'
})

View File

@@ -0,0 +1,6 @@
angular.module("admin.resources").factory 'ProductResource', ($resource) ->
$resource('/admin/product/:id/:action.json', {}, {
'index':
url: '/api/v0/products/bulk_products.json'
method: 'GET'
})

View File

@@ -39,6 +39,17 @@ angular.module("admin.resources").factory 'Enterprises', ($q, $filter, Enterpris
resetAttribute: (enterprise, attribute) ->
enterprise[attribute] = @pristineByID[enterprise.id][attribute]
performActionOnEnterpriseResource = (resourceAction) ->
(enterprise) ->
deferred = $q.defer()
resourceAction({id: enterprise.permalink}, ((data) =>
@pristineByID[enterprise.id] = angular.copy(data)
deferred.resolve(data)
), ((response) ->
deferred.reject(response)
))
deferred.promise
findByID: (id) ->
@byID[id]
@@ -50,3 +61,5 @@ angular.module("admin.resources").factory 'Enterprises', ($q, $filter, Enterpris
$filter('filter')(enterprises, term)
removeLogo: performActionOnEnterpriseResource(EnterpriseResource.removeLogo)
removePromoImage: performActionOnEnterpriseResource(EnterpriseResource.removePromoImage)

View File

@@ -0,0 +1,78 @@
angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetcher, $http) ->
new class BulkProducts
products: []
pagination: {}
fetch: (params) ->
ProductResource.index params, (data) =>
@products.length = 0
@addProducts data.products
angular.extend(@pagination, data.pagination)
cloneProduct: (product) ->
$http.post("/api/v0/products/" + product.id + "/clone").then (response) =>
dataFetcher("/api/v0/products/" + response.data.id + "?template=bulk_show").then (newProduct) =>
@unpackProduct newProduct
@insertProductAfter(product, newProduct)
updateVariantLists: (serverProducts) ->
for server_product in serverProducts
product = @findProductInList(server_product.id, @products)
product.variants = server_product.variants
@loadVariantUnitValues product
find: (id) ->
@findProductInList id, @products
findProductInList: (id, product_list) ->
products = (product for product in product_list when product.id == id)
if products.length == 0 then null else products[0]
addProducts: (products) ->
for product in products
@unpackProduct product
@products.push product
insertProductAfter: (product, newProduct) ->
index = @products.indexOf(product)
@products.splice(index + 1, 0, newProduct)
unpackProduct: (product) ->
#$scope.matchProducer product
@loadVariantUnit product
loadVariantUnit: (product) ->
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
@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: (product, variant) ->
if variant.unit_value?
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
else
null
# forces integer division to avoid javascript floating point imprecision
# using one billion as the multiplier so that it works for numbers with up to 9 decimal places
divideAsInteger: (a, b) ->
(a * 1000000000) / (b * 1000000000)

View File

@@ -0,0 +1,29 @@
angular.module("admin.indexUtils").factory 'KeyValueMapStore', (localStorageService)->
new class KeyValueMapStore
localStorageKey: ''
storableKeys: []
constructor: ->
localStorageService.setStorageType("sessionStorage")
getStoredKeyValueMap: ->
localStorageService.get(@localStorageKey) || {}
setStoredValues: (source) ->
keyValueMap = {}
for key in @storableKeys
keyValueMap[key] = source[key]
localStorageService.set(@localStorageKey, keyValueMap)
restoreValues: (target) ->
storedKeyValueMap = @getStoredKeyValueMap()
return false if _.isEmpty(storedKeyValueMap)
for k,v of storedKeyValueMap
target[k] = v
return true
clearKeyValueMap: () ->
localStorageService.remove(@localStorageKey)

View File

@@ -187,17 +187,18 @@ addVariantFromStockLocation = function() {
$('#stock_details').hide();
var variant_id = $('input.variant_autocomplete').val();
var quantity = $("input.quantity").val();
var stock_location_id = $(this).data('stock-location-id');
var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();
var shipment = _.find(shipments, function(shipment){
return shipment.state == 'ready' || shipment.state == 'pending';
return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
});
if(shipment==undefined){
$.ajax({
type: "POST",
url: Spree.url(Spree.routes.orders_api + "/" + order_number + "/shipments.json"),
data: { variant_id: variant_id, quantity: quantity }
data: { variant_id: variant_id, quantity: quantity, stock_location_id: stock_location_id }
}).done(function( msg ) {
window.location.reload();
}).error(function( msg ) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,8 +26,6 @@ angular.module("admin.utils").directive "variantAutocomplete", ($timeout) ->
order_cycle_id: scope.order_cycle_id
eligible_for_subscriptions: scope.eligible_for_subscriptions
include_out_of_stock: scope.include_out_of_stock
search_variants_as: scope.search_variants_as
order_id: scope.order_id
results: (data, page) ->
window.variants = data # this is how spree auto complete JS code picks up variants
results: data

View File

@@ -17,6 +17,7 @@
#= require angular-google-maps.min.js
#= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js
#= require ../shared/ng-infinite-scroll.min.js
#= require ../shared/angular-local-storage.js
#= require ../shared/angular-slideables.js
#= require ../shared/shared
#= require_tree ../shared/directives

View File

@@ -0,0 +1,2 @@
angular.module('Darkswarm').controller "TmpCtrl", ($scope)->
$scope.test = {foo: "bar"}

View File

@@ -1,6 +1,7 @@
angular.module("Darkswarm", [
'ngResource',
'mm.foundation',
'LocalStorageModule',
'infinite-scroll',
'angular-flash.service',
'templates',

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
angular.module('Darkswarm').directive "ofnDisableScroll", ()->
# Stops scrolling from incrementing or decrementing input value
# Useful for number inputs
restrict: 'A'
link: (scope, element, attrs)->
element.bind 'focus', ->
element.bind 'mousewheel', (e)->
e.preventDefault()
element.bind 'blur', ->
element.unbind 'mousewheel'

View File

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

View File

@@ -0,0 +1,5 @@
angular.module('Darkswarm').directive "integer", ->
restrict: 'A'
link: (scope, elem, attr) ->
elem.bind 'input', ->
elem.val Math.round(elem.val())

View File

@@ -20,13 +20,10 @@ angular.module('Darkswarm').directive 'mapSearch', ($timeout, Search) ->
$timeout =>
map = ctrl.getMap()
if !map
alert(t('gmap_load_failure'))
else
searchBox = scope.createSearchBox map
scope.bindSearchResponse map, searchBox
scope.biasResults map, searchBox
scope.performUrlSearch map
searchBox = scope.createSearchBox map
scope.bindSearchResponse map, searchBox
scope.biasResults map, searchBox
scope.performUrlSearch map
scope.createSearchBox = (map) ->
map.controls[google.maps.ControlPosition.TOP_LEFT].push scope.input

View File

@@ -13,8 +13,6 @@ angular.module('Darkswarm').directive 'ofnOpenStreetMap', ($window, MapCentreCal
buildMarker = (enterprise, latlng, title) ->
icon = L.icon
iconAnchor: [14, 33]
iconSize: [28, 33]
iconUrl: enterprise.icon
marker = L.marker latlng,
draggable: true,

View File

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

View File

@@ -0,0 +1,14 @@
angular.module('Darkswarm').directive "renderSvg", ()->
# Magical directive that'll render SVGs from URLs
# If only there were a neater way of doing this
restrict: 'E'
priority: 99
template: "<svg-wrapper></svg-wrapper>"
# Fetch SVG via ajax, inject into page using DOM
link: (scope, elem, attr)->
if /.svg/.test attr.path # Only do this if we've got an svg
$.ajax
url: attr.path
success: (html)->
elem.html($(html).find("svg"))

View File

@@ -0,0 +1,9 @@
angular.module('Darkswarm').directive "ofnScrollTo", ($location, $anchorScroll)->
# Onclick sets $location.hash to attrs.ofnScrollTo
# Then triggers anchorScroll
restrict: 'A'
link: (scope, element, attrs)->
element.bind 'click', (ev)->
ev.stopPropagation()
$location.hash attrs.ofnScrollTo
$anchorScroll()

View File

@@ -1,4 +1,4 @@
angular.module('Darkswarm').factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, Messages) ->
angular.module('Darkswarm').factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService, Messages) ->
# Handles syncing of current cart/order state to server
new class Cart
dirty: false
@@ -113,6 +113,7 @@ angular.module('Darkswarm').factory 'Cart', (CurrentOrder, Variants, $timeout, $
clear: ->
@line_items = []
localStorageService.clearAll() # One day this will have to be moar GRANULAR
isOnlyItemInOrder: (id) =>
deletedItem = @line_items_finalised.find((item) -> item.id == id)

View File

@@ -0,0 +1,546 @@
/**
* An Angular module that gives you access to the browsers local storage
* @version v0.5.0 - 2016-08-29
* @link https://github.com/grevory/angular-local-storage
* @author grevory <greg@gregpike.ca>
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
(function (window, angular) {
var isDefined = angular.isDefined,
isUndefined = angular.isUndefined,
isNumber = angular.isNumber,
isObject = angular.isObject,
isArray = angular.isArray,
extend = angular.extend,
toJson = angular.toJson;
angular
.module('LocalStorageModule', [])
.provider('localStorageService', function() {
// You should set a prefix to avoid overwriting any local storage variables from the rest of your app
// e.g. localStorageServiceProvider.setPrefix('yourAppName');
// With provider you can use config as this:
// myApp.config(function (localStorageServiceProvider) {
// localStorageServiceProvider.prefix = 'yourAppName';
// });
this.prefix = 'ls';
// You could change web storage type localstorage or sessionStorage
this.storageType = 'localStorage';
// Cookie options (usually in case of fallback)
// expiry = Number of days before cookies expire // 0 = Does not expire
// path = The web path the cookie represents
// secure = Wether the cookies should be secure (i.e only sent on HTTPS requests)
this.cookie = {
expiry: 30,
path: '/',
secure: false
};
// Decides wether we should default to cookies if localstorage is not supported.
this.defaultToCookie = true;
// Send signals for each of the following actions?
this.notify = {
setItem: true,
removeItem: false
};
// Setter for the prefix
this.setPrefix = function(prefix) {
this.prefix = prefix;
return this;
};
// Setter for the storageType
this.setStorageType = function(storageType) {
this.storageType = storageType;
return this;
};
// Setter for defaultToCookie value, default is true.
this.setDefaultToCookie = function (shouldDefault) {
this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value.
return this;
};
// Setter for cookie config
this.setStorageCookie = function(exp, path, secure) {
this.cookie.expiry = exp;
this.cookie.path = path;
this.cookie.secure = secure;
return this;
};
// Setter for cookie domain
this.setStorageCookieDomain = function(domain) {
this.cookie.domain = domain;
return this;
};
// Setter for notification config
// itemSet & itemRemove should be booleans
this.setNotify = function(itemSet, itemRemove) {
this.notify = {
setItem: itemSet,
removeItem: itemRemove
};
return this;
};
this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) {
var self = this;
var prefix = self.prefix;
var cookie = self.cookie;
var notify = self.notify;
var storageType = self.storageType;
var webStorage;
// When Angular's $document is not available
if (!$document) {
$document = document;
} else if ($document[0]) {
$document = $document[0];
}
// If there is a prefix set in the config lets use that with an appended period for readability
if (prefix.substr(-1) !== '.') {
prefix = !!prefix ? prefix + '.' : '';
}
var deriveQualifiedKey = function(key) {
return prefix + key;
};
// Removes prefix from the key.
var underiveQualifiedKey = function (key) {
return key.replace(new RegExp('^' + prefix, 'g'), '');
};
// Check if the key is within our prefix namespace.
var isKeyPrefixOurs = function (key) {
return key.indexOf(prefix) === 0;
};
// Checks the browser to see if local storage is supported
var checkSupport = function () {
try {
var supported = (storageType in $window && $window[storageType] !== null);
// When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage
// is available, but trying to call .setItem throws an exception.
//
// "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage
// that exceeded the quota."
var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7));
if (supported) {
webStorage = $window[storageType];
webStorage.setItem(key, '');
webStorage.removeItem(key);
}
return supported;
} catch (e) {
// Only change storageType to cookies if defaulting is enabled.
if (self.defaultToCookie)
storageType = 'cookie';
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return false;
}
};
var browserSupportsLocalStorage = checkSupport();
// Directly adds a value to local storage
// If local storage is not available in the browser use cookies
// Example use: localStorageService.add('library','angular');
var addToLocalStorage = function (key, value, type) {
setStorageType(type);
// Let's convert undefined values to null to get the value consistent
if (isUndefined(value)) {
value = null;
} else {
value = toJson(value);
}
// If this browser does not support local storage use cookies
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
}
if (notify.setItem) {
$rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'});
}
return addToCookies(key, value);
}
try {
if (webStorage) {
webStorage.setItem(deriveQualifiedKey(key), value);
}
if (notify.setItem) {
$rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType});
}
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return addToCookies(key, value);
}
return true;
};
// Directly get a value from local storage
// Example use: localStorageService.get('library'); // returns 'angular'
var getFromLocalStorage = function (key, type) {
setStorageType(type);
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
}
return getFromCookies(key);
}
var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null;
// angular.toJson will convert null to 'null', so a proper conversion is needed
// FIXME not a perfect solution, since a valid 'null' string can't be stored
if (!item || item === 'null') {
return null;
}
try {
return JSON.parse(item);
} catch (e) {
return item;
}
};
// Remove an item from local storage
// Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular'
//
// This is var-arg removal, check the last argument to see if it is a storageType
// and set type accordingly before removing.
//
var removeFromLocalStorage = function () {
// can't pop on arguments, so we do this
var consumed = 0;
if (arguments.length >= 1 &&
(arguments[arguments.length - 1] === 'localStorage' ||
arguments[arguments.length - 1] === 'sessionStorage')) {
consumed = 1;
setStorageType(arguments[arguments.length - 1]);
}
var i, key;
for (i = 0; i < arguments.length - consumed; i++) {
key = arguments[i];
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
}
if (notify.removeItem) {
$rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'});
}
removeFromCookies(key);
}
else {
try {
webStorage.removeItem(deriveQualifiedKey(key));
if (notify.removeItem) {
$rootScope.$broadcast('LocalStorageModule.notification.removeitem', {
key: key,
storageType: self.storageType
});
}
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
removeFromCookies(key);
}
}
}
};
// Return array of keys for local storage
// Example use: var keys = localStorageService.keys()
var getKeysForLocalStorage = function (type) {
setStorageType(type);
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
return [];
}
var prefixLength = prefix.length;
var keys = [];
for (var key in webStorage) {
// Only return keys that are for this app
if (key.substr(0, prefixLength) === prefix) {
try {
keys.push(key.substr(prefixLength));
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.Description);
return [];
}
}
}
return keys;
};
// Remove all data for this app from local storage
// Also optionally takes a regular expression string and removes the matching key-value pairs
// Example use: localStorageService.clearAll();
// Should be used mostly for development purposes
var clearAllFromLocalStorage = function (regularExpression, type) {
setStorageType(type);
// Setting both regular expressions independently
// Empty strings result in catchall RegExp
var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp();
var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp();
if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
if (!browserSupportsLocalStorage) {
$rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
}
return clearAllFromCookies();
}
if (!browserSupportsLocalStorage && !self.defaultToCookie)
return false;
var prefixLength = prefix.length;
for (var key in webStorage) {
// Only remove items that are for this app and match the regular expression
if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) {
try {
removeFromLocalStorage(key.substr(prefixLength));
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return clearAllFromCookies();
}
}
}
return true;
};
// Checks the browser to see if cookies are supported
var browserSupportsCookies = (function() {
try {
return $window.navigator.cookieEnabled ||
("cookie" in $document && ($document.cookie.length > 0 ||
($document.cookie = "test").indexOf.call($document.cookie, "test") > -1));
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return false;
}
}());
// Directly adds a value to cookies
// Typically used as a fallback if local storage is not available in the browser
// Example use: localStorageService.cookie.add('library','angular');
var addToCookies = function (key, value, daysToExpiry, secure) {
if (isUndefined(value)) {
return false;
} else if(isArray(value) || isObject(value)) {
value = toJson(value);
}
if (!browserSupportsCookies) {
$rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
return false;
}
try {
var expiry = '',
expiryDate = new Date(),
cookieDomain = '';
if (value === null) {
// Mark that the cookie has expired one day ago
expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000));
expiry = "; expires=" + expiryDate.toGMTString();
value = '';
} else if (isNumber(daysToExpiry) && daysToExpiry !== 0) {
expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000));
expiry = "; expires=" + expiryDate.toGMTString();
} else if (cookie.expiry !== 0) {
expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000));
expiry = "; expires=" + expiryDate.toGMTString();
}
if (!!key) {
var cookiePath = "; path=" + cookie.path;
if (cookie.domain) {
cookieDomain = "; domain=" + cookie.domain;
}
/* Providing the secure parameter always takes precedence over config
* (allows developer to mix and match secure + non-secure) */
if (typeof secure === 'boolean') {
if (secure === true) {
/* We've explicitly specified secure,
* add the secure attribute to the cookie (after domain) */
cookieDomain += "; secure";
}
// else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says
}
else if (cookie.secure === true) {
// secure parameter wasn't specified, get default from config
cookieDomain += "; secure";
}
$document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain;
}
} catch (e) {
$rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
return false;
}
return true;
};
// Directly get a value from a cookie
// Example use: localStorageService.cookie.get('library'); // returns 'angular'
var getFromCookies = function (key) {
if (!browserSupportsCookies) {
$rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
return false;
}
var cookies = $document.cookie && $document.cookie.split(';') || [];
for(var i=0; i < cookies.length; i++) {
var thisCookie = cookies[i];
while (thisCookie.charAt(0) === ' ') {
thisCookie = thisCookie.substring(1,thisCookie.length);
}
if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) {
var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length));
try {
return JSON.parse(storedValues);
} catch(e) {
return storedValues;
}
}
}
return null;
};
var removeFromCookies = function (key) {
addToCookies(key,null);
};
var clearAllFromCookies = function () {
var thisCookie = null;
var prefixLength = prefix.length;
var cookies = $document.cookie.split(';');
for(var i = 0; i < cookies.length; i++) {
thisCookie = cookies[i];
while (thisCookie.charAt(0) === ' ') {
thisCookie = thisCookie.substring(1, thisCookie.length);
}
var key = thisCookie.substring(prefixLength, thisCookie.indexOf('='));
removeFromCookies(key);
}
};
var getStorageType = function() {
return storageType;
};
var setStorageType = function(type) {
if (type && storageType !== type) {
storageType = type;
browserSupportsLocalStorage = checkSupport();
}
return browserSupportsLocalStorage;
};
// Add a listener on scope variable to save its changes to local storage
// Return a function which when called cancels binding
var bindToScope = function(scope, key, def, lsKey, type) {
lsKey = lsKey || key;
var value = getFromLocalStorage(lsKey, type);
if (value === null && isDefined(def)) {
value = def;
} else if (isObject(value) && isObject(def)) {
value = extend(value, def);
}
$parse(key).assign(scope, value);
return scope.$watch(key, function(newVal) {
addToLocalStorage(lsKey, newVal, type);
}, isObject(scope[key]));
};
// Add listener to local storage, for update callbacks.
if (browserSupportsLocalStorage) {
if ($window.addEventListener) {
$window.addEventListener("storage", handleStorageChangeCallback, false);
$rootScope.$on('$destroy', function() {
$window.removeEventListener("storage", handleStorageChangeCallback);
});
} else if($window.attachEvent){
// attachEvent and detachEvent are proprietary to IE v6-10
$window.attachEvent("onstorage", handleStorageChangeCallback);
$rootScope.$on('$destroy', function() {
$window.detachEvent("onstorage", handleStorageChangeCallback);
});
}
}
// Callback handler for storage changed.
function handleStorageChangeCallback(e) {
if (!e) { e = $window.event; }
if (notify.setItem) {
if (isKeyPrefixOurs(e.key)) {
var key = underiveQualifiedKey(e.key);
// Use timeout, to avoid using $rootScope.$apply.
$timeout(function () {
$rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType });
});
}
}
}
// Return localStorageService.length
// ignore keys that not owned
var lengthOfLocalStorage = function(type) {
setStorageType(type);
var count = 0;
var storage = $window[storageType];
for(var i = 0; i < storage.length; i++) {
if(storage.key(i).indexOf(prefix) === 0 ) {
count++;
}
}
return count;
};
return {
isSupported: browserSupportsLocalStorage,
getStorageType: getStorageType,
setStorageType: setStorageType,
set: addToLocalStorage,
add: addToLocalStorage, //DEPRECATED
get: getFromLocalStorage,
keys: getKeysForLocalStorage,
remove: removeFromLocalStorage,
clearAll: clearAllFromLocalStorage,
bind: bindToScope,
deriveKey: deriveQualifiedKey,
underiveKey: underiveQualifiedKey,
length: lengthOfLocalStorage,
defaultToCookie: this.defaultToCookie,
cookie: {
isSupported: browserSupportsCookies,
set: addToCookies,
add: addToCookies, //DEPRECATED
get: getFromCookies,
remove: removeFromCookies,
clearAll: clearAllFromCookies
}
};
}];
});
})(window, window.angular);

View File

@@ -1,4 +1,5 @@
window.OFNShared = angular.module("OFNShared", [
"mm.foundation",
"LocalStorageModule"
]).config ($httpProvider) ->
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"

View File

@@ -2,13 +2,13 @@
.ofn-drop-down-label
= "&nbsp; #{t('admin.columns')}".html_safe
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
.menu{ 'ng-show' => "expanded" }
%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 }}
%hr
.menu_item.text-center
%div.menu_item.text-center
%input.fullwidth.orange{ type: "button", "ng-value": "saved() ? 'Saved': 'Saving'", "ng-show": "saved() || saving", "ng-disabled": "saved()" }
%input.fullwidth.red{ type: "button", value: t('admin.column_save_as_default').html_safe, "ng-show": "!saved() && !saving", "ng-click": "saveColumnPreferences(action)" }

View File

@@ -2,7 +2,7 @@
%i.fa.fa-times-circle{'aria-hidden' => "true"}
%form#image_upload{ name: 'form', novalidate: true, enctype: 'multipart/form-data', multipart: true, "ng-controller": "ProductImageCtrl" }
.image-preview
%div.image-preview
%img.spinner{ src: image_path("/spinning-circles.svg"), "ng-hide": "!imageUploader.isUploading" }
%img.preview{ "ng-src": "{{imagePreview}}", "ng-class": "{'faded': imageUploader.isUploading}" }

View File

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

View File

@@ -15,7 +15,7 @@
.exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products | filter:visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges' }
.exchange-product-details
%label
%img{'ng-src' => '{{ product.image_url }}'}
%img{'ng-src' => '{{ product.image_url }}'}
.name {{ product.name }}
.supplier {{ product.supplier_name }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
// please update the modal html in app/components/out_of_stock_modal_component/out_of_stock_modal_component.html.haml if updating this template
%a.close-reveal-modal{"ng-click" => "$close()"}
%i.ofn-i_009-close
@@ -13,4 +12,4 @@
%span{'ng-if' => "v.on_hand == 0"}
{{ 'js.out_of_stock.now_out_of_stock' | t }}
%span{'ng-if' => "v.on_hand > 0"}
{{ 'js.out_of_stock.only_n_remaining' | t:{ num: v.on_hand } }}
{{ 'js.out_of_stock.only_n_remainging' | t:{ num: v.on_hand } }}

View File

@@ -1,5 +1,5 @@
.contact-container
.modal-centered{"ng-if" => "::enterprise.email_address || enterprise.website || enterprise.phone || enterprise.whatsapp_phone"}
%div.contact-container
%div.modal-centered{"ng-if" => "::enterprise.email_address || enterprise.website || enterprise.phone || enterprise.whatsapp_phone"}
%p.modal-header {{'contact' | t}}
%p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"}

View File

@@ -1,4 +1,4 @@
.modal-centered{ "ng-if" => "::enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"}
%div.modal-centered{ "ng-if" => "::enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"}
%p.modal-header {{'follow' | t}}
.follow-icons
%span{"ng-if" => "::enterprise.twitter"}

View File

@@ -7,12 +7,12 @@
.columns.small-12.fat
%div{"ng-if" => "::enterprise.name"}
%label{"ng-bind-html" => "::'shop_for_products_html' | t:{enterprise: enterprise.name}"}
.show-for-medium-up{"ng-if" => "::!enterprise.name"}
%div.show-for-medium-up{"ng-if" => "::!enterprise.name"}
&nbsp;
.row
.columns.small-12
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'",
"ng-href" => "{{::hub.path}}#/shop_panel",
"ng-href" => "{{::hub.path}}#/shop_panel", "ofn-empties-cart" => "hub",
"ng-class" => "::{primary: hub.active, secondary: !hub.active}",
"ng-click" => "$close()",
"ofn-change-hub" => "hub"}

View File

@@ -12,6 +12,11 @@ img {
max-width: 100%;
}
.collapse {
margin: 0;
padding: 0;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
@@ -27,6 +32,18 @@ a {
color: #0096ad;
}
.btn {
text-decoration: none;
color: #FFF;
background-color: #666;
padding: 10px 16px;
font-weight: bold;
margin-right: 10px;
text-align: center;
cursor: pointer;
display: inline-block;
}
p.callout {
padding: 15px;
background-color: #e1f0f5;
@@ -104,12 +121,6 @@ table.order-summary {
text-align: right;
}
.logo {
vertical-align: middle;
max-height: 50px;
max-width: 247px;
}
.social .soc-btn {
padding: 3px 7px;
font-size: 12px;
@@ -167,11 +178,17 @@ del.quantity_was {
table.head-wrap {
width: 100%;
background-color: #f2f2f2;
}
.collapse {
margin: 0;
.header.container table td {
&.logo {
padding: 15px;
}
&.label {
padding: 15px;
padding-left: 0px;
}
}
/* -------------------------------------
@@ -250,6 +267,10 @@ h6 {
color: #999;
}
.collapse {
margin: 0 !important;
}
p, ul {
margin-bottom: 10px;
font-weight: normal;
@@ -361,6 +382,12 @@ ul {
}
}
.column-wrap {
padding: 0 !important;
margin: 0 auto;
max-width: 600px !important;
}
.column table {
width: 100%;
}

View File

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

View File

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

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