mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abf9c4e438 |
@@ -2,4 +2,3 @@
|
||||
.gitignore
|
||||
log/*
|
||||
tmp/*
|
||||
node_modules/
|
||||
|
||||
@@ -5,17 +5,9 @@
|
||||
#
|
||||
# cp .env.development .env.local
|
||||
|
||||
VERBOSE_QUERY_LOGS=true
|
||||
|
||||
SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
OFN_REDIS_URL="redis://localhost:6379/1"
|
||||
OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
|
||||
|
||||
SITE_URL="0.0.0.0:3000"
|
||||
|
||||
# Deactivate rack-timeout in development.
|
||||
# https://github.com/zombocom/rack-timeout#configuring
|
||||
RACK_TIMEOUT_SERVICE_TIMEOUT="0"
|
||||
RACK_TIMEOUT_WAIT_TIMEOUT="0"
|
||||
RACK_TIMEOUT_WAIT_OVERTIME="0"
|
||||
|
||||
11
.gitattributes
vendored
11
.gitattributes
vendored
@@ -1,11 +0,0 @@
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
* text=auto
|
||||
|
||||
# Set line endings to LF, even on Windows. Otherwise, execution within Docker fails.
|
||||
# See https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings#per-repository-settings
|
||||
*.sh text eol=lf
|
||||
|
||||
# Same thing for following files, but they don't have an sh extension
|
||||
pre-commit eol=lf
|
||||
webpack-dev-server eol=lf
|
||||
install-bundler eol=lf
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: tech debt
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/release.md
vendored
3
.github/ISSUE_TEMPLATE/release.md
vendored
@@ -23,7 +23,8 @@ assignees: ''
|
||||
|
||||
## Finish on Tuesday
|
||||
|
||||
- [ ] Publish and notify [#global-community] (this is automatically posted with a plugin)
|
||||
- [ ] Publish and notify [#global-community]:
|
||||
> The next release is ready: https://github.com/openfoodfoundation/openfoodnetwork/releases/latest
|
||||
- [ ] Deploy the new release to all managed instances.
|
||||
<details><summary>Command line instructions</summary>
|
||||
<pre>
|
||||
|
||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -6,12 +6,10 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
# Only specific requirements are specified in Gemfile, so don't touch it.
|
||||
versioning-strategy: lockfile-only
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
# All versions are specified in package.json, so please update them.
|
||||
versioning-strategy: increase
|
||||
versioning-strategy: lockfile-only
|
||||
|
||||
3
.github/workflows/brakeman-analysis.yml
vendored
3
.github/workflows/brakeman-analysis.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: Setup Brakeman
|
||||
env:
|
||||
BRAKEMAN_VERSION: '5.4.0'
|
||||
BRAKEMAN_VERSION: '4.10' # SARIF support is provided in Brakeman version 4.10+
|
||||
run: |
|
||||
gem install brakeman --version $BRAKEMAN_VERSION
|
||||
|
||||
@@ -41,7 +41,6 @@ jobs:
|
||||
- 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
|
||||
|
||||
419
.github/workflows/build.yml
vendored
419
.github/workflows/build.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
DISABLE_KNAPSACK_PRO: false
|
||||
DISABLE_KNAPSACK: true
|
||||
TIMEZONE: UTC
|
||||
COVERAGE: true
|
||||
RAILS_ENV: test
|
||||
@@ -17,8 +17,8 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
knapsack_rspec_controllers:
|
||||
runs-on: ubuntu-22.04
|
||||
rspec:
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:10
|
||||
@@ -33,15 +33,19 @@ jobs:
|
||||
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]
|
||||
specs:
|
||||
- "spec/controllers"
|
||||
- "spec/models"
|
||||
- "spec/lib"
|
||||
- "spec/migrations"
|
||||
- "spec/serializers"
|
||||
- "spec/system/admin/[a-o0-9]*"
|
||||
- "spec/system/admin/[p-z]*"
|
||||
- "spec/system/consumer/[a-o0-9]*"
|
||||
- "spec/system/consumer/[p-z]*"
|
||||
- "engines/*/spec"
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -57,7 +61,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
node-version: 16
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
@@ -68,160 +72,7 @@ jobs:
|
||||
bundle exec rake 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)
|
||||
bundle exec rake knapsack_pro:rspec
|
||||
|
||||
knapsack_rspec_models:
|
||||
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: [7]
|
||||
# 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]
|
||||
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
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bundle exec rake db:create
|
||||
bundle exec rake db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
|
||||
env:
|
||||
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
|
||||
# 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/models/**/*_spec.rb}"
|
||||
|
||||
run: |
|
||||
bundle exec rake knapsack_pro:rspec
|
||||
|
||||
knapsack_rspec_system_admin:
|
||||
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: [10]
|
||||
# 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]
|
||||
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
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bundle exec rake db:create
|
||||
bundle exec rake db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
|
||||
env:
|
||||
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: ff2456e64c9f2aa5157eb0daf711d3c3
|
||||
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/admin/**/*_spec.rb}"
|
||||
|
||||
run: |
|
||||
bundle exec rake knapsack_pro:queue:rspec
|
||||
run: bundle exec rspec --profile -- ${{ matrix.specs }}
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
@@ -232,234 +83,8 @@ jobs:
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
knapsack_rspec_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: [10]
|
||||
# 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]
|
||||
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
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bundle exec rake db:create
|
||||
bundle exec rake 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: |
|
||||
bundle exec rake knapsack_pro:queue:rspec
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
knapsack_rspec_engines:
|
||||
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: [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, 3, 4]
|
||||
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
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bundle exec rake db:create
|
||||
bundle exec rake db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
|
||||
env:
|
||||
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: d6ea7ceb766404ccd016c19aa2c81b1c
|
||||
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/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}"
|
||||
|
||||
run: |
|
||||
bundle exec rake knapsack_pro:rspec
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: failed-tests-screenshots
|
||||
path: tmp/capybara/screenshots/*.png
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
knapsack_rspec_test_the_rest:
|
||||
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: [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, 3, 4]
|
||||
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
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Set up database
|
||||
run: |
|
||||
bundle exec rake db:create
|
||||
bundle exec rake db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
|
||||
env:
|
||||
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: e3b8800198d2d89b70c7edbdd85f8fd8
|
||||
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_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: |
|
||||
bundle exec rake knapsack_pro:rspec
|
||||
|
||||
non_knapsack_jest_karma:
|
||||
runs-on: ubuntu-22.04
|
||||
test-the-rest:
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:10
|
||||
@@ -488,7 +113,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
node-version: 16
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
@@ -497,8 +122,12 @@ jobs:
|
||||
run: |
|
||||
bundle exec rake db:create
|
||||
bundle exec rake db:schema:load
|
||||
|
||||
- name: Run JS tests
|
||||
run: bundle exec rake karma:run
|
||||
|
||||
- name: Run jest tests
|
||||
run: yarn jest
|
||||
|
||||
- name: Run all other tests
|
||||
run: bundle exec rake ofn:specs:run:excluding_folders["models,controllers,serializers,features,lib,migrations,system"]
|
||||
|
||||
14
.github/workflows/linters.yml
vendored
14
.github/workflows/linters.yml
vendored
@@ -9,11 +9,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- uses: ruby/setup-ruby@v1
|
||||
|
||||
- run: git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
|
||||
|
||||
- name: rubocop
|
||||
uses: reviewdog/action-rubocop@v2
|
||||
with:
|
||||
@@ -28,16 +24,6 @@ jobs:
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- 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:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -50,6 +50,7 @@ vendor/bundle/
|
||||
coverage
|
||||
/reports/
|
||||
!/reports/README.md
|
||||
/spec/components/stories/**/*.stories.json
|
||||
|
||||
/public/packs
|
||||
/public/packs-test
|
||||
|
||||
@@ -1 +1 @@
|
||||
17.9.1
|
||||
14.16.1
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Basically, ignore everythings expect app/webpacker/controllers/*.js and app/webpacker/packs/*.js
|
||||
|
||||
*.css
|
||||
*.scss
|
||||
*.md
|
||||
*.yml
|
||||
*.yaml
|
||||
@@ -9,10 +10,7 @@
|
||||
babel.config.js
|
||||
postcss.config.js
|
||||
|
||||
/app/webpacker/css/darkswarm/
|
||||
/app/webpacker/css/mail/
|
||||
/app/webpacker/css/shared/
|
||||
|
||||
.storybook/
|
||||
/app/assets/
|
||||
/config/
|
||||
/coverage/
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
{
|
||||
"printWidth": 100
|
||||
}
|
||||
{}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#
|
||||
# These are the rules we agreed upon and we work towards.
|
||||
AllCops:
|
||||
NewCops: enable
|
||||
NewCops: disable
|
||||
SuggestExtensions: false
|
||||
TargetRailsVersion: 5.0
|
||||
Exclude:
|
||||
- 'bin/**/*'
|
||||
- 'db/**/*'
|
||||
@@ -40,7 +41,6 @@ Metrics/BlockLength:
|
||||
"resources",
|
||||
"scenario",
|
||||
"shared_examples",
|
||||
"shared_examples_for",
|
||||
"xdescribe",
|
||||
]
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
7
.storybook/main.js
Normal file
7
.storybook/main.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
stories: ['../spec/components/stories/**/*.stories.json'],
|
||||
addons: [
|
||||
'@storybook/addon-docs',
|
||||
'@storybook/addon-controls',
|
||||
],
|
||||
};
|
||||
2
.storybook/preview-head.html
Normal file
2
.storybook/preview-head.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300italic,400italic,300,700,700italic|Oswald:300,400,700' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" media="screen" href="http://localhost:3000/assets/darkswarm/all.css" />
|
||||
5
.storybook/preview.js
Normal file
5
.storybook/preview.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const parameters = {
|
||||
server: {
|
||||
url: `http://localhost:3000/rails/stories`,
|
||||
},
|
||||
};
|
||||
@@ -57,7 +57,6 @@ TL;DR:
|
||||
* Maintain a clean commit history
|
||||
* Use a style consistent with the rest of the codebase
|
||||
* Before submitting, [rebase your work][rebase] on the current master branch
|
||||
* After submitting, be sure to check the [CI test results](ci). Click on a ❌ result to view the logged results and investigate.
|
||||
|
||||
From here, your pull request will progress through the [Review, Test, Merge & Deploy process][process].
|
||||
|
||||
@@ -71,5 +70,4 @@ From here, your pull request will progress through the [Review, Test, Merge & De
|
||||
[slack-dev]: https://openfoodnetwork.slack.com/messages/C2GQ45KNU
|
||||
[ofn-transifex]: https://www.transifex.com/open-food-foundation/open-food-network/
|
||||
[i18n]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Internationalisation-%28i18n%29
|
||||
[welcome-dev]: https://github.com/orgs/openfoodfoundation/projects/5
|
||||
[ci]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Continuous-Integration
|
||||
[welcome-dev]: https://github.com/orgs/openfoodfoundation/projects/2
|
||||
|
||||
25
Dockerfile
25
Dockerfile
@@ -28,18 +28,14 @@ RUN apt-get update && apt-get install -y \
|
||||
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 PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/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
|
||||
COPY .ruby-version .
|
||||
|
||||
# Install Rbenv & Ruby
|
||||
RUN git clone --depth 1 https://github.com/rbenv/rbenv.git ${RBENV_ROOT} && \
|
||||
@@ -54,19 +50,10 @@ RUN sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-
|
||||
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 NodeJs and yarn
|
||||
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - \
|
||||
&& apt-get install --no-install-recommends -y nodejs \
|
||||
&& npm install -g yarn
|
||||
|
||||
# Install Chrome
|
||||
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
|
||||
|
||||
@@ -11,9 +11,6 @@ Head to our wiki on [Learning Rails](https://github.com/openfoodfoundation/openf
|
||||
The fastest way to make it work locally is to use Docker, you only need to setup git, see the [Docker setup guide](docker/README.md).
|
||||
Otherwise, for a local setup you will need:
|
||||
* Ruby and bundler (check current Ruby version in [.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/)
|
||||
* Node and yarn (check current Node version in [.node-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.node-version) file)
|
||||
- [nodevn](https://github.com/nodenv/nodenv) is recommended.
|
||||
* PostgreSQL database
|
||||
* Redis (for background jobs)
|
||||
* Chrome (for testing)
|
||||
@@ -23,13 +20,15 @@ The following guides will provide OS-specific step-by-step instructions to get t
|
||||
- [Debian Setup Guide][debian]
|
||||
- [OSX Setup Guide][osx]
|
||||
|
||||
If you are likely to need to manage multiple version of ruby on your local machine, we recommend version managers such as [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://rvm.io/).
|
||||
|
||||
For those new to Rails, the following tutorial will help get you up to speed with configuring a [Rails environment](http://guides.rubyonrails.org/getting_started.html).
|
||||
|
||||
### Get it
|
||||
|
||||
So you have set up your local environment according to the requirements listed above. If you're planning on contributing code to the project (which we [LOVE](CONTRIBUTING.md)), it is a good idea to begin by forking this repo using the `Fork` button in the top-right corner of this screen. You should then be able to use `git clone` to copy your fork onto your local machine:
|
||||
|
||||
git clone git@github.com:YOUR_GITHUB_USERNAME_HERE/openfoodnetwork.git
|
||||
git clone https://github.com/YOUR_GITHUB_USERNAME_HERE/openfoodnetwork
|
||||
|
||||
Jump into your new local copy of the Open Food Network:
|
||||
|
||||
@@ -37,7 +36,7 @@ Jump into your new local copy of the Open Food Network:
|
||||
|
||||
And then add an `upstream` remote that points to the main repo:
|
||||
|
||||
git remote add upstream git@github.com:openfoodfoundation/openfoodnetwork.git
|
||||
git remote add upstream https://github.com/openfoodfoundation/openfoodnetwork
|
||||
|
||||
Fetch the latest version of `master` from `upstream` (ie. the main repo):
|
||||
|
||||
@@ -48,14 +47,14 @@ Fetch the latest version of `master` from `upstream` (ie. the main repo):
|
||||
First, you need to create the database user the app will use by manually typing the following in your terminal:
|
||||
|
||||
```sh
|
||||
sudo --login --user=postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'"
|
||||
$ sudo -u postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'"
|
||||
```
|
||||
|
||||
This will create the "ofn" user as superuser and allowing it to create databases. If this command fails, check the [troubleshooting section](#creating-the-database) for an alternative.
|
||||
|
||||
Next, it is _strongly recommended_ to run the setup script.
|
||||
```sh
|
||||
./script/setup
|
||||
$ script/setup
|
||||
```
|
||||
If the script succeeds you're ready to start developing. If not, take a look at the output as it should be informative enough to help you troubleshoot.
|
||||
|
||||
@@ -84,7 +83,7 @@ Then the main application tests can be run with:
|
||||
|
||||
The tests of all custom engines can be run with:
|
||||
|
||||
bundle exec rspec ./engines
|
||||
bundle exec rake ofn:specs:engines:rspec
|
||||
|
||||
Note: If your OS is not explicitly supported in the setup guides then not all tests may pass. However, you may still be able to develop.
|
||||
|
||||
@@ -114,13 +113,13 @@ Below are fixes to potential issues that can happen during the installation proc
|
||||
|
||||
#### Creating the database
|
||||
|
||||
If the `sudo -u postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'"` command doesn't work, you can run the following commands instead:
|
||||
If the ```$ sudo -u postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'"``` command doesn't work, you can run the following commands instead:
|
||||
```
|
||||
createuser --superuser --pwprompt ofn
|
||||
# Enter password for new role: f00d
|
||||
# Enter it again: f00d
|
||||
createdb open_food_network_dev --owner=ofn
|
||||
createdb open_food_network_test --owner=ofn
|
||||
$ createuser --superuser --pwprompt ofn
|
||||
Enter password for new role: f00d
|
||||
Enter it again: f00d
|
||||
$ createdb open_food_network_dev --owner=ofn
|
||||
$ createdb open_food_network_test --owner=ofn
|
||||
```
|
||||
If these commands succeed, you should be able to [continue the setup process](#get-it-running).
|
||||
|
||||
|
||||
48
Gemfile
48
Gemfile
@@ -6,7 +6,7 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
|
||||
|
||||
gem 'dotenv-rails', require: 'dotenv/rails-now' # Load ENV vars before other gems
|
||||
|
||||
gem 'rails'
|
||||
gem 'rails', '>= 6.1.4'
|
||||
|
||||
# Active Storage
|
||||
gem "active_storage_validations"
|
||||
@@ -14,11 +14,11 @@ gem "aws-sdk-s3", require: false
|
||||
gem "image_processing"
|
||||
|
||||
gem 'activemerchant', '>= 1.78.0'
|
||||
gem 'rexml'
|
||||
gem 'angular-rails-templates', '>= 0.3.0'
|
||||
gem 'awesome_nested_set'
|
||||
gem 'ransack', '~> 2.6.0'
|
||||
gem 'ransack', '2.4.2'
|
||||
gem 'responders'
|
||||
gem 'rexml'
|
||||
gem 'webpacker', '~> 5'
|
||||
|
||||
gem 'i18n'
|
||||
@@ -27,7 +27,7 @@ gem 'rails-i18n'
|
||||
gem 'rails_safe_tasks', '~> 1.0'
|
||||
|
||||
gem "activerecord-import"
|
||||
gem "db2fog", github: "openfoodfoundation/db2fog", branch: "rails-7"
|
||||
gem "db2fog", github: "openfoodfoundation/db2fog", branch: "rails-6"
|
||||
gem "fog-aws", "~> 2.0" # db2fog does not support v3
|
||||
gem "mime-types" # required by fog
|
||||
|
||||
@@ -63,16 +63,15 @@ gem 'devise-token_authenticatable'
|
||||
gem 'jwt', '~> 2.3'
|
||||
gem 'oauth2', '~> 1.4.7' # Used for Stripe Connect
|
||||
|
||||
gem 'datafoodconsortium-connector'
|
||||
gem 'jsonapi-serializer'
|
||||
gem 'pagy', '~> 5.1'
|
||||
|
||||
gem 'rswag-api'
|
||||
gem 'rswag-ui'
|
||||
|
||||
gem 'omniauth_openid_connect'
|
||||
gem 'omniauth-rails_csrf_protection'
|
||||
gem 'gitlab-omniauth-openid-connect', require: 'omniauth_openid_connect'
|
||||
gem 'openid_connect', '~> 1.3'
|
||||
gem 'omniauth-rails_csrf_protection'
|
||||
|
||||
gem 'angularjs-rails', '1.8.0'
|
||||
gem 'bugsnag'
|
||||
@@ -84,16 +83,16 @@ gem 'actionpack-action_caching'
|
||||
# AMS is deprecated, we will introduce an alternative at some point
|
||||
gem "active_model_serializers", "0.8.4"
|
||||
gem 'activerecord-session_store'
|
||||
gem 'acts-as-taggable-on'
|
||||
gem 'acts-as-taggable-on', '~> 8.1'
|
||||
gem 'angularjs-file-upload-rails', '~> 2.4.1'
|
||||
gem 'bigdecimal', '3.0.2'
|
||||
gem 'bootsnap', require: false
|
||||
gem 'geocoder'
|
||||
gem 'gmaps4rails'
|
||||
gem 'mimemagic', '> 0.3.5'
|
||||
gem 'paper_trail', '~> 12.1'
|
||||
gem 'paper_trail', '~> 12.1.0'
|
||||
gem 'rack-rewrite'
|
||||
gem 'rack-timeout'
|
||||
gem 'rack-ssl', require: 'rack/ssl'
|
||||
gem 'roadie-rails'
|
||||
|
||||
gem 'hiredis'
|
||||
@@ -102,16 +101,16 @@ gem 'redis', '>= 4.0', require: ['redis', 'redis/connection/hiredis']
|
||||
gem 'sidekiq'
|
||||
gem 'sidekiq-scheduler'
|
||||
|
||||
gem "cable_ready", "5.0.0.rc2"
|
||||
gem "stimulus_reflex", "3.5.0.rc2"
|
||||
gem "cable_ready", "5.0.0.pre9"
|
||||
gem "stimulus_reflex", "3.5.0.pre9"
|
||||
|
||||
gem 'combine_pdf'
|
||||
gem 'wicked_pdf'
|
||||
gem 'wkhtmltopdf-binary'
|
||||
|
||||
gem 'immigrant'
|
||||
gem 'roo' # read spreadsheets
|
||||
gem 'spreadsheet_architect' # write spreadsheets
|
||||
gem 'roo'
|
||||
gem 'spreadsheet_architect'
|
||||
|
||||
gem 'whenever', require: false
|
||||
|
||||
@@ -119,7 +118,7 @@ gem 'test-unit', '~> 3.5'
|
||||
|
||||
gem 'coffee-rails', '~> 5.0.0'
|
||||
|
||||
gem 'mini_racer'
|
||||
gem 'mini_racer', '0.4.0'
|
||||
|
||||
gem 'angular_rails_csrf'
|
||||
|
||||
@@ -127,6 +126,8 @@ gem 'jquery-rails', '4.4.0'
|
||||
gem 'jquery-ui-rails', '~> 4.2'
|
||||
gem "select2-rails", github: "openfoodfoundation/select2-rails", branch: "v349_with_thor_v1"
|
||||
|
||||
gem 'ofn-qz', github: 'openfoodfoundation/ofn-qz', branch: 'ofn-rails-4'
|
||||
|
||||
gem 'good_migrations'
|
||||
|
||||
gem 'flipper'
|
||||
@@ -134,15 +135,10 @@ gem 'flipper-active_record'
|
||||
gem 'flipper-ui'
|
||||
|
||||
gem "view_component"
|
||||
gem 'view_component_reflex', '3.1.14.pre9'
|
||||
|
||||
gem 'mini_portile2', '~> 2.8'
|
||||
|
||||
gem "faraday"
|
||||
gem "private_address_check"
|
||||
|
||||
group :production, :staging do
|
||||
gem 'ddtrace'
|
||||
gem 'rack-timeout'
|
||||
gem 'sd_notify' # For better Systemd process management. Used by Puma.
|
||||
end
|
||||
|
||||
@@ -151,23 +147,24 @@ group :test, :development do
|
||||
gem 'capybara'
|
||||
gem 'cuprite'
|
||||
gem 'database_cleaner', require: false
|
||||
gem 'debug', '>= 1.0.0'
|
||||
gem "factory_bot_rails", '6.2.0', require: false
|
||||
gem 'fuubar', '~> 2.5.1'
|
||||
gem 'json_spec', '~> 1.1.4'
|
||||
gem 'knapsack_pro'
|
||||
gem 'knapsack', require: false
|
||||
gem 'letter_opener', '>= 1.4.1'
|
||||
gem 'rspec-rails', ">= 3.5.2"
|
||||
gem 'rspec-retry', require: false
|
||||
gem 'rswag-specs'
|
||||
gem 'shoulda-matchers'
|
||||
gem 'timecop'
|
||||
gem 'debug', '>= 1.0.0'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'pdf-reader'
|
||||
gem 'rails-controller-testing'
|
||||
gem 'simplecov', require: false
|
||||
gem 'test-prof', require: false
|
||||
gem 'vcr', require: false
|
||||
gem 'webmock', require: false
|
||||
# See spec/spec_helper.rb for instructions
|
||||
@@ -176,16 +173,17 @@ end
|
||||
|
||||
group :development do
|
||||
gem 'debugger-linecache'
|
||||
gem 'rails-erd'
|
||||
gem 'foreman'
|
||||
gem 'listen'
|
||||
gem 'pry', '~> 0.13.0'
|
||||
gem 'query_count'
|
||||
gem 'rails-erd'
|
||||
gem 'rubocop'
|
||||
gem 'rubocop-rails'
|
||||
gem 'spring'
|
||||
gem 'spring-commands-rspec'
|
||||
gem 'web-console'
|
||||
|
||||
gem "view_component_storybook"
|
||||
|
||||
gem 'rack-mini-profiler', '< 3.0.0'
|
||||
end
|
||||
|
||||
561
Gemfile.lock
561
Gemfile.lock
File diff suppressed because it is too large
Load Diff
4
Procfile
4
Procfile
@@ -1,5 +1,5 @@
|
||||
# Foreman Procfile. Start all dev server processes with: `foreman start`
|
||||
|
||||
rails: DEV_CACHING=true bundle exec rails s -p 3000
|
||||
rails: bundle exec rails s -p 3000
|
||||
webpack: ./bin/webpack-dev-server
|
||||
sidekiq: DEV_CACHING=true bundle exec sidekiq -q mailers -q default
|
||||
sidekiq: bundle exec sidekiq -q mailers -q default
|
||||
|
||||
13
README.md
13
README.md
@@ -33,15 +33,9 @@ We also have a [Super Admin Guide][super-admin-guide] to help with configuration
|
||||
|
||||
## Testing
|
||||
|
||||
If you'd like to help out with testing, please introduce yourself on the #testing channel on [Slack][slack-invite] and download the [ZenHub browser extension][zenhub] to view the development pipeline. Also, do have a look in our [Welcome New QAs board][welcome-qa] for some good first issues, both on manual and automated testing (RSpec/Capybara).
|
||||
|
||||
We use [BrowserStack](https://www.browserstack.com/) as a manual testing tool. BrowserStack provides open source projects with unlimited and free of charge accounts. A big thanks to them!
|
||||
|
||||
|
||||
We use [KnapsackPro](https://knapsackpro.com/) for optimal parallelisation of our automated tests. KnapsackPro offers unlimited plans for non-commercial open source projects, like ours - a big thanks to them!
|
||||
|
||||

|
||||
If you'd like to help out with testing, please introduce yourself on the #testing channel on [Slack][slack-invite] and download the [ZenHub browser extension][zenhub] to view the development pipeline. Also, do have a look in our [Welcome New QAs board](https://github.com/orgs/openfoodfoundation/projects/1) for some good first issues, both on manual and automated testing (RSpec/Capybara).
|
||||
|
||||
We use [BrowserStack](https://www.browserstack.com/) as a manual testing tool. BrowserStack provides open source projects with unlimited and free of charge accounts. A big thanks to them!
|
||||
|
||||
## Licence
|
||||
|
||||
@@ -52,6 +46,5 @@ Copyright (c) 2012 - 2022 Open Food Foundation, released under the AGPL licence.
|
||||
[ofn-handbook]: https://ofn-user-guide.gitbook.io/ofn-handbook/
|
||||
[ofn-install]: https://github.com/openfoodfoundation/ofn-install
|
||||
[super-admin-guide]: https://ofn-user-guide.gitbook.io/ofn-super-admin-guide
|
||||
[welcome-dev]: https://github.com/orgs/openfoodfoundation/projects/5
|
||||
[welcome-qa]: https://github.com/orgs/openfoodfoundation/projects/6
|
||||
[welcome-dev]: https://github.com/orgs/openfoodfoundation/projects/2
|
||||
[zenhub]: https://www.zenhub.com/extension
|
||||
|
||||
3
Rakefile
3
Rakefile
@@ -8,3 +8,6 @@ require_relative 'config/application'
|
||||
|
||||
Openfoodnetwork::Application.load_tasks
|
||||
|
||||
if !ENV['DISABLE_KNAPSACK'] && defined?(Knapsack)
|
||||
Knapsack.load_tasks
|
||||
end
|
||||
|
||||
@@ -12,6 +12,5 @@ angular.module("ofn.admin", [
|
||||
"admin.orders"
|
||||
]).config ($httpProvider, $locationProvider, $qProvider) ->
|
||||
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"
|
||||
# for the next line, you should also probably check file: app/assets/javascripts/admin/utils/utils.js.coffee
|
||||
$locationProvider.hashPrefix('')
|
||||
$qProvider.errorOnUnhandledRejections(false)
|
||||
|
||||
@@ -55,12 +55,28 @@ angular.module("admin.enterprises")
|
||||
else
|
||||
alert ("#{manager.email}" + " " + t("is_already_manager"))
|
||||
|
||||
$scope.inviteManager = ->
|
||||
$scope.invite_errors = $scope.invite_success = null
|
||||
email = $scope.newUser
|
||||
|
||||
$http.post("/admin/manager_invitations", {email: email, enterprise_id: $scope.Enterprise.id}).then (response)->
|
||||
$scope.addManager({id: response.data.user, email: email})
|
||||
$scope.invite_success = t('user_invited', email: email)
|
||||
.catch (response) ->
|
||||
$scope.invite_errors = response.data.errors
|
||||
|
||||
$scope.resetModal = ->
|
||||
$scope.newUser = $scope.invite_errors = $scope.invite_success = null
|
||||
|
||||
$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.removeTermsAndConditions = ->
|
||||
$scope.performEnterpriseAction("removeTermsAndConditions", "immediate_terms_and_conditions_removal_warning", "removed_terms_and_conditions_successfully")
|
||||
|
||||
$scope.performEnterpriseAction = (enterpriseActionName, warning_message_key, success_message_key) ->
|
||||
return unless confirm($scope.translation(warning_message_key))
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
angular.module("admin.enterprises").directive 'termsAndConditionsWarning', ($rootScope, $compile, $templateCache, DialogDefaults, $timeout) ->
|
||||
restrict: 'A'
|
||||
scope: true
|
||||
|
||||
link: (scope, element, attr) ->
|
||||
# This file input click handler will hold the browser file input dialog and show a warning modal
|
||||
scope.hold_file_input_and_show_warning_modal = (event) ->
|
||||
event.preventDefault()
|
||||
scope.template = $compile($templateCache.get('admin/modals/terms_and_conditions_warning.html'))(scope)
|
||||
if scope.template.dialog
|
||||
scope.template.dialog(DialogDefaults)
|
||||
scope.template.dialog('open')
|
||||
$rootScope.$evalAsync()
|
||||
|
||||
element.bind 'click', scope.hold_file_input_and_show_warning_modal
|
||||
|
||||
# When the user presses continue in the warning modal, we open the browser file input dialog
|
||||
scope.continue = ->
|
||||
scope.template.dialog('close')
|
||||
$rootScope.$evalAsync()
|
||||
|
||||
# unbind warning modal handler and click file input again to open the browser file input dialog
|
||||
element.unbind('click').trigger('click')
|
||||
# afterwards, bind warning modal handler again so that the warning is shown the next time
|
||||
$timeout ->
|
||||
element.bind 'click', scope.hold_file_input_and_show_warning_modal
|
||||
return
|
||||
|
||||
scope.close = ->
|
||||
scope.template.dialog('close')
|
||||
$rootScope.$evalAsync()
|
||||
return
|
||||
@@ -0,0 +1,12 @@
|
||||
angular.module("admin.indexUtils").component 'showMore',
|
||||
templateUrl: 'admin/show_more.html'
|
||||
bindings:
|
||||
data: "="
|
||||
limit: "="
|
||||
increment: "="
|
||||
|
||||
# For now, this component is not being used.
|
||||
# Something about binding "data" to a variable on the parent scope that is continually refreshed by
|
||||
# being assigned within an ng-repeat means that we get $digest iteration errors. Seems to be solved
|
||||
# by using the new "as" syntax for ng-repeat to assign and alias the outcome of the filters, but this
|
||||
# has the limitation of not being able to be limited AFTER the assignment has been made, which we need
|
||||
@@ -11,6 +11,5 @@ angular.module("admin.indexUtils").factory 'SortOptions', ->
|
||||
sortingExpr
|
||||
|
||||
toggle: (predicate) ->
|
||||
# predicate is a string or an array of strings
|
||||
@reverse = (JSON.stringify(@predicate) == JSON.stringify(predicate)) && !@reverse
|
||||
@reverse = (@predicate == predicate) && !@reverse
|
||||
@predicate = predicate
|
||||
|
||||
@@ -3,30 +3,16 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.RequestMonitor = RequestMonitor
|
||||
$scope.line_items = LineItems.all
|
||||
$scope.confirmDelete = true
|
||||
$scope.startDate = moment().startOf('day').subtract(7, 'days').format('YYYY-MM-DD')
|
||||
$scope.endDate = moment().startOf('day').format('YYYY-MM-DD')
|
||||
$scope.previousDates = { startDate: $scope.startDate, endDate: $scope.endDate }
|
||||
$scope.datesChangedOnCancelEvent = false
|
||||
$scope.bulkActions = [ { name: t("admin.orders.bulk_management.actions_delete"), callback: 'deleteLineItems' } ]
|
||||
$scope.selectedUnitsProduct = {}
|
||||
$scope.selectedUnitsVariant = {}
|
||||
$scope.sharedResource = false
|
||||
$scope.columns = Columns.columns
|
||||
$scope.sorting = SortOptions
|
||||
$scope.pagination = LineItems.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.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",
|
||||
"variant_product_supplier_name",
|
||||
"order_email",
|
||||
"order_number",
|
||||
"product_name"].join("_or_") + "_cont"
|
||||
|
||||
$scope.confirmRefresh = ->
|
||||
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
|
||||
@@ -35,74 +21,101 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.distributorFilter = ''
|
||||
$scope.supplierFilter = ''
|
||||
$scope.orderCycleFilter = ''
|
||||
$scope.query = ''
|
||||
$scope.startDate = undefined
|
||||
$scope.endDate = undefined
|
||||
event = new CustomEvent('flatpickr:clear')
|
||||
window.dispatchEvent(event)
|
||||
$scope.quickSearch = ''
|
||||
|
||||
$scope.resetSelectFilters = ->
|
||||
$scope.resetFilters()
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.fetchResults = ->
|
||||
# creates indirection in order to factorize the code between orders and bulk orders
|
||||
# used in app/views/admin/shared/_angular_per_page_controls.html.haml
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.$watchCollection "[startDate, endDate]", (newValues, oldValues) ->
|
||||
if newValues != oldValues && !$scope.datesChangedOnCancelEvent
|
||||
state = $scope.refreshData()
|
||||
if (state == "cancel")
|
||||
$scope.datesChangedOnCancelEvent = true
|
||||
$scope.cancelDateChange()
|
||||
|
||||
$scope.cancelDateChange = ->
|
||||
# Reset the date filters to the previous values
|
||||
$scope.startDate = $scope.previousDates.startDate
|
||||
$scope.endDate = $scope.previousDates.endDate
|
||||
# throw a flatpickr:change event to change the date back in the datepicker
|
||||
event = new CustomEvent('flatpickr:change', {
|
||||
detail: {
|
||||
startDate: $scope.previousDates.startDate,
|
||||
endDate: $scope.previousDates.endDate
|
||||
}
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
$timeout ->
|
||||
$scope.datesChangedOnCancelEvent = false
|
||||
|
||||
$scope.refreshData = ->
|
||||
unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == ''
|
||||
$scope.setOrderCycleDateRange()
|
||||
|
||||
$scope.formattedStartDate = moment($scope.startDate).format()
|
||||
$scope.formattedEndDate = moment($scope.endDate).add(1,'day').format()
|
||||
|
||||
return unless moment($scope.formattedStartDate).isValid() and moment($scope.formattedEndDate).isValid()
|
||||
|
||||
return "cancel" unless $scope.confirmRefresh()
|
||||
|
||||
$scope.loadOrders()
|
||||
$scope.loadLineItems()
|
||||
|
||||
unless $scope.initialized
|
||||
$scope.loadAssociatedData()
|
||||
|
||||
$scope.dereferenceLoadedData()
|
||||
|
||||
$timeout ->
|
||||
# update the previous dates with the current ones since loading was successful
|
||||
$scope.previousDates.startDate = $scope.startDate
|
||||
$scope.previousDates.endDate = $scope.endDate
|
||||
|
||||
$scope.setOrderCycleDateRange = ->
|
||||
start_date = OrderCycles.byID[$scope.orderCycleFilter].orders_open_at
|
||||
end_date = OrderCycles.byID[$scope.orderCycleFilter].orders_close_at
|
||||
format = "YYYY-MM-DD HH:mm:ss Z"
|
||||
$scope.startDate = moment(start_date, format).format('YYYY-MM-DD')
|
||||
$scope.endDate = moment(end_date, format).startOf('day').format('YYYY-MM-DD')
|
||||
|
||||
$scope.loadOrders = ->
|
||||
RequestMonitor.load $scope.orders = Orders.index(
|
||||
"q[id_in][]": $scope.line_items.map((line_item) -> line_item.order.id)
|
||||
"q[state_not_eq]": "canceled",
|
||||
"q[shipment_state_not_eq]": "shipped",
|
||||
"q[completed_at_not_null]": "true",
|
||||
"q[distributor_id_eq]": $scope.distributorFilter,
|
||||
"q[order_cycle_id_eq]": $scope.orderCycleFilter,
|
||||
"q[completed_at_gteq]": $scope.formattedStartDate,
|
||||
"q[completed_at_lt]": $scope.formattedEndDate
|
||||
)
|
||||
|
||||
$scope.loadLineItems = ->
|
||||
[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[order_distributor_id_eq]": $scope.distributorFilter,
|
||||
"q[variant_product_supplier_id_eq]": $scope.supplierFilter,
|
||||
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
|
||||
"q[order_completed_at_gteq]": if formattedStartDate then formattedStartDate else undefined,
|
||||
"q[order_completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
|
||||
"page": $scope.page,
|
||||
"per_page": $scope.per_page
|
||||
"q[order_completed_at_gteq]": $scope.formattedStartDate,
|
||||
"q[order_completed_at_lt]": $scope.formattedEndDate
|
||||
)
|
||||
|
||||
$scope.formatDates = (startDate, endDate) ->
|
||||
formattedStartDate = moment(startDate).format('YYYY-MM-DD') if startDate
|
||||
formattedEndDate = moment(endDate).add(1,'day').format('YYYY-MM-DD') if endDate
|
||||
return [formattedStartDate, formattedEndDate]
|
||||
|
||||
$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(90,'days').format()}")
|
||||
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
|
||||
|
||||
$scope.dereferenceLoadedData = ->
|
||||
RequestMonitor.load $q.all([$scope.distributors.$promise, $scope.orderCycles.$promise, $scope.suppliers.$promise, $scope.line_items.$promise]).then ->
|
||||
RequestMonitor.load $q.all([$scope.orders.$promise, $scope.distributors.$promise, $scope.orderCycles.$promise, $scope.suppliers.$promise, $scope.line_items.$promise]).then ->
|
||||
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "supplier", Enterprises.byID
|
||||
$scope.loadOrders()
|
||||
RequestMonitor.load $q.all([$scope.orders.$promise]).then ->
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID
|
||||
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID
|
||||
$scope.bulk_order_form.$setPristine()
|
||||
StatusMessage.clear()
|
||||
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
|
||||
$scope.bulk_order_form.$setPristine()
|
||||
StatusMessage.clear()
|
||||
|
||||
unless $scope.initialized
|
||||
$scope.initialized = true
|
||||
@@ -122,16 +135,16 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
else
|
||||
StatusMessage.display 'failure', t "unsaved_changes_error"
|
||||
|
||||
$scope.cancelOrder = (order, sendEmailCancellation, restock_items) ->
|
||||
$scope.cancelOrder = (order, sendEmailCancellation) ->
|
||||
return $http(
|
||||
method: 'GET'
|
||||
url: "/admin/orders/#{order.number}/fire?e=cancel&send_cancellation_email=#{sendEmailCancellation}&restock_items=#{restock_items}")
|
||||
url: "/admin/orders/#{order.number}/fire?e=cancel&send_cancellation_email=#{sendEmailCancellation}")
|
||||
|
||||
$scope.deleteLineItem = (lineItem) ->
|
||||
if lineItem.order.item_count == 1
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) ->
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation) ->
|
||||
if confirm
|
||||
$scope.cancelOrder(lineItem.order, sendEmailCancellation, restock_items).then(->
|
||||
$scope.cancelOrder(lineItem.order, sendEmailCancellation).then(->
|
||||
$scope.refreshData()
|
||||
)
|
||||
else
|
||||
@@ -153,18 +166,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
willCancelOrders = true if (order.item_count == itemsPerOrder.get(order).length)
|
||||
|
||||
if willCancelOrders
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) ->
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation) ->
|
||||
if confirm
|
||||
itemsPerOrder.forEach (items, order) =>
|
||||
if order.item_count == items.length
|
||||
$scope.cancelOrder(order, sendEmailCancellation, restock_items).then(-> $scope.refreshData())
|
||||
$scope.cancelOrder(order, sendEmailCancellation).then(-> $scope.refreshData())
|
||||
else
|
||||
Promise.all(LineItems.delete(item) for item in items).then(-> $scope.refreshData())
|
||||
, "js.admin.deleting_item_will_cancel_order")
|
||||
else
|
||||
ofnDeleteLineItemsAlert(() ->
|
||||
Promise.all(LineItems.delete(item) for item in lineItemsToDelete).then(-> $scope.refreshData())
|
||||
, lineItemsToDelete.length)
|
||||
|
||||
$scope.allBoxesChecked = ->
|
||||
checkedCount = $scope.filteredLineItems.reduce (count,lineItem) ->
|
||||
@@ -179,17 +188,6 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
$scope.setSelectedUnitsVariant = (unitsProduct,unitsVariant) ->
|
||||
$scope.selectedUnitsProduct = unitsProduct
|
||||
$scope.selectedUnitsVariant = unitsVariant
|
||||
$scope.filterByVariantId = unitsVariant.id
|
||||
$scope.page = 1
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.resetSelectedUnitsVariant = ->
|
||||
$scope.selectedUnitsProduct = { }
|
||||
$scope.selectedUnitsVariant = { }
|
||||
$scope.filterByVariantId = null
|
||||
$scope.sharedResource = false
|
||||
$scope.page = 1
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.getLineItemScale = (lineItem) ->
|
||||
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
|
||||
@@ -235,7 +233,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
|
||||
$scope.getGroupBySizeFormattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
|
||||
scale = $scope.getScale(unitsProduct, unitsVariant)
|
||||
if scale && value
|
||||
if scale
|
||||
value = value / scale if scale != 28.35 && scale != 1 && scale != 453.6 # divide by scale if not smallest unit
|
||||
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
|
||||
else
|
||||
@@ -279,8 +277,5 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
lineItem.final_weight_volume = LineItems.pristineByID[lineItem.id].final_weight_volume * lineItem.quantity / LineItems.pristineByID[lineItem.id].quantity
|
||||
$scope.weightAdjustedPrice(lineItem)
|
||||
|
||||
$scope.changePage = (newPage) ->
|
||||
$scope.page = newPage
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.resetSelectFilters()
|
||||
$scope.resetFilters()
|
||||
$scope.refreshData()
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
angular.module("admin.lineItems").filter "variantFilter", ->
|
||||
return (lineItems,selectedUnitsProduct,selectedUnitsVariant,sharedResource) ->
|
||||
filtered = []
|
||||
filtered.push lineItem for lineItem in lineItems when (angular.equals(selectedUnitsProduct,{}) ||
|
||||
(lineItem.units_product.id == selectedUnitsProduct.id && (sharedResource || lineItem.units_variant.id == selectedUnitsVariant.id ) ) )
|
||||
filtered
|
||||
@@ -18,6 +18,7 @@ angular.module('admin.orderCycles')
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
NavigationCheck.clear()
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
OrderCycle.update(destination, $scope.order_cycle_form)
|
||||
|
||||
|
||||
@@ -38,4 +38,3 @@ angular.module('admin.orderCycles')
|
||||
$scope.removeCoordinatorFee = ($event, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeCoordinatorFee(index)
|
||||
$scope.order_cycle_form.$dirty = true
|
||||
|
||||
@@ -35,11 +35,7 @@ angular.module('admin.orderCycles')
|
||||
OrderCycle.removeExchangeFee(exchange, index)
|
||||
$scope.order_cycle_form.$dirty = true
|
||||
|
||||
$scope.setPickupTimeFieldDirty = (index, pickup_time) ->
|
||||
# if the pickup_time is already set we are in edit mode, so no need to set pickup_time field as dirty
|
||||
# to show it is required (it has a red border when set to dirty)
|
||||
return if pickup_time
|
||||
|
||||
$scope.setPickupTimeFieldDirty = (index) ->
|
||||
$timeout ->
|
||||
pickup_time_field_name = "order_cycle_outgoing_exchange_" + index + "_pickup_time"
|
||||
$scope.order_cycle_form[pickup_time_field_name].$setDirty()
|
||||
|
||||
@@ -33,11 +33,9 @@ angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, C
|
||||
StatusMessage.display 'notice', "You have unsaved changes" if newVal
|
||||
|
||||
$scope.showMore = (days) ->
|
||||
orderCycles = OrderCycles.index(ams_prefix: "index",
|
||||
"q[orders_close_at_gt]": "#{daysFromToday($scope.ordersCloseAtLimit - days)}",
|
||||
"q[orders_close_at_lteq]": "#{daysFromToday($scope.ordersCloseAtLimit)}"
|
||||
)
|
||||
$scope.ordersCloseAtLimit -= days
|
||||
existingIDs = Object.keys(OrderCycles.byID)
|
||||
orderCycles = OrderCycles.index(ams_prefix: "index", "q[orders_close_at_gt]": "#{daysFromToday($scope.ordersCloseAtLimit)}", "q[id_not_in][]": existingIDs)
|
||||
orderCycles.$promise.then ->
|
||||
$scope.orderCycles.push(orderCycle) for orderCycle in orderCycles
|
||||
compileData()
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
angular.module("admin.orders").controller "bulkCancelCtrl", ($scope, $http, $timeout) ->
|
||||
|
||||
$scope.cancelOrder = (orderIds, sendEmailCancellation, restock_items) ->
|
||||
$http(
|
||||
method: 'post'
|
||||
url: "/admin/orders/bulk_cancel?order_ids=#{orderIds}&send_cancellation_email=#{sendEmailCancellation}&restock_items=#{restock_items}" ).then(->
|
||||
window.location.reload()
|
||||
)
|
||||
|
||||
$scope.cancelSelectedOrders = ->
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) ->
|
||||
if confirm
|
||||
$scope.cancelOrder $scope.selected_orders, sendEmailCancellation, restock_items
|
||||
)
|
||||
@@ -21,14 +21,8 @@ angular.module("admin.products")
|
||||
else
|
||||
$scope.product.variant_unit = $scope.product.variant_unit_with_scale
|
||||
$scope.product.variant_unit_scale = null
|
||||
else if $scope.product.variant_unit
|
||||
# Preserves variant_unit_with_scale when form validation fails and reload triggers
|
||||
if $scope.product.variant_unit_scale
|
||||
$scope.product.variant_unit_with_scale = VariantUnitManager.getUnitWithScale(
|
||||
$scope.product.variant_unit, parseFloat($scope.product.variant_unit_scale)
|
||||
)
|
||||
else
|
||||
$scope.product.variant_unit_with_scale = $scope.product.variant_unit
|
||||
else if $scope.product.variant_unit && $scope.product.variant_unit_scale
|
||||
$scope.product.variant_unit_with_scale = VariantUnitManager.getUnitWithScale($scope.product.variant_unit, parseFloat($scope.product.variant_unit_scale))
|
||||
else
|
||||
$scope.product.variant_unit = $scope.product.variant_unit_scale = null
|
||||
|
||||
|
||||
@@ -14,4 +14,7 @@ angular.module("admin.resources").factory 'EnterpriseResource', ($resource) ->
|
||||
'removePromoImage':
|
||||
url: '/api/v0/enterprises/:id/promo_image.json'
|
||||
method: 'DELETE'
|
||||
'removeTermsAndConditions':
|
||||
url: '/api/v0/enterprises/:id/terms_and_conditions.json'
|
||||
method: 'DELETE'
|
||||
})
|
||||
|
||||
@@ -63,3 +63,4 @@ angular.module("admin.resources").factory 'Enterprises', ($q, $filter, Enterpris
|
||||
|
||||
removeLogo: performActionOnEnterpriseResource(EnterpriseResource.removeLogo)
|
||||
removePromoImage: performActionOnEnterpriseResource(EnterpriseResource.removePromoImage)
|
||||
removeTermsAndConditions: performActionOnEnterpriseResource(EnterpriseResource.removeTermsAndConditions)
|
||||
|
||||
@@ -15,7 +15,7 @@ $(document).ready(function() {
|
||||
console.log(msg);
|
||||
});
|
||||
}
|
||||
$('.admin-order-edit-form a.ship').click(handle_ship_click);
|
||||
$('[data-hook=admin_order_edit_form] a.ship').click(handle_ship_click);
|
||||
|
||||
//handle shipping method edit click
|
||||
$('a.edit-method').click(toggleMethodEdit);
|
||||
@@ -37,7 +37,7 @@ $(document).ready(function() {
|
||||
console.log(msg);
|
||||
});
|
||||
}
|
||||
$('.admin-order-edit-form a.save-method').click(handle_shipping_method_save);
|
||||
$('[data-hook=admin_order_edit_form] a.save-method').click(handle_shipping_method_save);
|
||||
|
||||
//handle tracking info edit/delete
|
||||
|
||||
@@ -64,8 +64,8 @@ $(document).ready(function() {
|
||||
return Spree.url( Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipmentNumber + ".json");
|
||||
}
|
||||
|
||||
$('.admin-order-edit-form a.save-tracking').click(saveTrackingInfo);
|
||||
$('.admin-order-edit-form a.delete-tracking').click(deleteTrackingInfo);
|
||||
$('[data-hook=admin_order_edit_form] a.save-tracking').click(saveTrackingInfo);
|
||||
$('[data-hook=admin_order_edit_form] a.delete-tracking').click(deleteTrackingInfo);
|
||||
|
||||
// handle note edit/delete
|
||||
|
||||
@@ -96,8 +96,8 @@ $(document).ready(function() {
|
||||
});
|
||||
}
|
||||
|
||||
$('.admin-order-edit-form a.save-note').click(saveNote);
|
||||
$('.admin-order-edit-form a.delete-note').click(deleteNote);
|
||||
$('[data-hook=admin_order_edit_form] a.save-note').click(saveNote);
|
||||
$('[data-hook=admin_order_edit_form] a.delete-note').click(deleteNote);
|
||||
|
||||
// Makes API call for notes/tracking info
|
||||
makeApiCall = function(url, params) {
|
||||
|
||||
@@ -4,8 +4,7 @@ $(document).ready(function() {
|
||||
|
||||
initAlert()
|
||||
initConfirm()
|
||||
initButtonCancel()
|
||||
initLinkCancel()
|
||||
initCancelOrder()
|
||||
|
||||
if ($('#variant_autocomplete_template').length > 0) {
|
||||
window.variantTemplate = Handlebars.compile($('#variant_autocomplete_template').text());
|
||||
@@ -100,7 +99,6 @@ adjustItems = function(shipment_number, variant_id, quantity, restock_item){
|
||||
doAdjustItems(shipment_number, variant_id, quantity, inventory_units, restock_item, () => {
|
||||
var redirectTo = new URL(Spree.routes.cancel_order.toString());
|
||||
redirectTo.searchParams.append("send_cancellation_email", sendEmailCancellation);
|
||||
redirectTo.searchParams.append("restock_item", restock_item);
|
||||
window.location.href = redirectTo.toString();
|
||||
});
|
||||
}
|
||||
@@ -252,18 +250,6 @@ ofnCancelOrderAlert = function(callback, i18nKey) {
|
||||
$('#custom-confirm').show();
|
||||
}
|
||||
|
||||
ofnDeleteLineItemsAlert = function(callback, count) {
|
||||
$('#custom-confirm .message').html(`${t("js.admin.orders.delete_line_items_html", {count: count})}`);
|
||||
$('#custom-confirm button.confirm').click(() => {
|
||||
$('#custom-confirm').hide();
|
||||
callback();
|
||||
});
|
||||
$('#custom-confirm button.cancel').click(() => {
|
||||
$('#custom-confirm').hide();
|
||||
});
|
||||
$('#custom-confirm').show();
|
||||
}
|
||||
|
||||
ofnConfirm = function(callback) {
|
||||
$('#custom-confirm .message').html(
|
||||
` ${t("are_you_sure")}
|
||||
@@ -278,23 +264,17 @@ ofnConfirm = function(callback) {
|
||||
$('#custom-confirm').show();
|
||||
}
|
||||
|
||||
initCancelAction = function(e){
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) => {
|
||||
if (confirm) {
|
||||
var redirectTo = new URL(Spree.routes.cancel_order.toString());
|
||||
redirectTo.searchParams.append("send_cancellation_email", sendEmailCancellation);
|
||||
redirectTo.searchParams.append("restock_items", restock_items);
|
||||
window.location.href = redirectTo.toString();
|
||||
}
|
||||
initCancelOrder = function() {
|
||||
$('#cancel_order_form').submit(function(e){
|
||||
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) => {
|
||||
if (confirm) {
|
||||
var redirectTo = new URL(Spree.routes.cancel_order.toString());
|
||||
redirectTo.searchParams.append("send_cancellation_email", sendEmailCancellation);
|
||||
redirectTo.searchParams.append("restock_items", restock_items);
|
||||
window.location.href = redirectTo.toString();
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
initButtonCancel = function() {
|
||||
$('#cancel_order_form').submit(initCancelAction)
|
||||
}
|
||||
|
||||
initLinkCancel = function() {
|
||||
$('#links-dropdown a[href$="cancel"]').click(initCancelAction);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ handle_move = (e, data) ->
|
||||
node = data.rslt.o
|
||||
new_parent = data.rslt.np
|
||||
|
||||
url = new URL(Spree.routes.admin_taxonomy_taxons)
|
||||
url = new URL(base_url)
|
||||
url.pathname = url.pathname + '/' + node.attr("id")
|
||||
data = {
|
||||
_method: "put",
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
angular.module("admin.utils", ["templates", "ngSanitize"]).config ($httpProvider, $locationProvider) ->
|
||||
# for the next line, you should also probably check file: app/assets/javascripts/admin/admin_ofn.js.coffee
|
||||
$locationProvider.hashPrefix('')
|
||||
angular.module("admin.utils", ["templates", "ngSanitize"]).config ($httpProvider) ->
|
||||
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"
|
||||
|
||||
@@ -7,12 +7,8 @@ angular.module('Darkswarm').controller "RegistrationFormCtrl", ($scope, Registra
|
||||
form.$valid
|
||||
|
||||
$scope.create = (form) ->
|
||||
if ($scope.valid(form))
|
||||
$scope.disableButton()
|
||||
EnterpriseRegistrationService.create().then(() ->
|
||||
$scope.enableButton()
|
||||
)
|
||||
end
|
||||
$scope.disableButton()
|
||||
EnterpriseRegistrationService.create($scope.enableButton) if $scope.valid(form)
|
||||
|
||||
$scope.update = (nextStep, form) ->
|
||||
EnterpriseRegistrationService.update(nextStep) if $scope.valid(form)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
angular.module('Darkswarm').directive "fillVertical", ($window)->
|
||||
# Makes something fill the window vertically. Used on the Google Map.
|
||||
restrict: 'A'
|
||||
link: (scope, element, attrs)->
|
||||
setSize = ->
|
||||
element.css "height", ($window.innerHeight - element.offset().top)
|
||||
setSize()
|
||||
angular.element($window).bind "resize", ->
|
||||
setSize()
|
||||
@@ -17,12 +17,9 @@ angular.module('Darkswarm').directive "ofnFlash", (flash, $timeout, RailsFlashLo
|
||||
|
||||
# Callback when a new flash message is pushed to flash service
|
||||
show = (message, type)=>
|
||||
return unless message
|
||||
# if same message already exists, don't add it again
|
||||
return if $scope.flashes.some((flash) -> flash.message == message)
|
||||
|
||||
$scope.flashes.push({message: message, type: typePairings[type]})
|
||||
$timeout($scope.delete, 10000)
|
||||
if message
|
||||
$scope.flashes.push({message: message, type: typePairings[type]})
|
||||
$timeout($scope.delete, 10000)
|
||||
|
||||
$scope.delete = ->
|
||||
$scope.flashes.shift()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module('Darkswarm').directive "ofnOnHand", (StockQuantity, Messages) ->
|
||||
angular.module('Darkswarm').directive "ofnOnHand", (StockQuantity) ->
|
||||
restrict: 'A'
|
||||
require: "ngModel"
|
||||
scope: true
|
||||
@@ -16,7 +16,7 @@ angular.module('Darkswarm').directive "ofnOnHand", (StockQuantity, Messages) ->
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
available_quantity = scope.available_quantity()
|
||||
if parseInt(viewValue) > available_quantity
|
||||
Messages.flash({error: t("js.insufficient_stock", {on_hand: available_quantity})})
|
||||
alert t("js.insufficient_stock", {on_hand: available_quantity})
|
||||
viewValue = available_quantity
|
||||
ngModel.$setViewValue viewValue
|
||||
ngModel.$render()
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
angular.module('Darkswarm').directive 'singleLineSelectors', ($timeout, $filter) ->
|
||||
restrict: 'E'
|
||||
templateUrl: "single_line_selectors.html"
|
||||
scope:
|
||||
selectors: "="
|
||||
objects: "&"
|
||||
activeSelectors: "="
|
||||
selectorName: "@activeSelectors"
|
||||
link: (scope, element, attrs) ->
|
||||
scope.fitting = false
|
||||
|
||||
scope.refit = ->
|
||||
if scope.allSelectors?
|
||||
scope.fitting = true
|
||||
selector.fits = true for selector in scope.allSelectors
|
||||
$timeout(loadWidths, 0, true).then ->
|
||||
$timeout fit, 0, true
|
||||
|
||||
fit = ->
|
||||
used = $(element).find("li.more").outerWidth(true)
|
||||
used += selector.width for selector in scope.allSelectors when selector.fits
|
||||
available = $(element).parent(".filter-shopfront").innerWidth() - used
|
||||
if available > 0
|
||||
for selector in scope.allSelectors when !selector.fits
|
||||
available -= selector.width
|
||||
selector.fits = true if available > 0
|
||||
else
|
||||
if scope.allSelectors.length > 0
|
||||
for i in [scope.allSelectors.length-1..0]
|
||||
selector = scope.allSelectors[i]
|
||||
if !selector.fits
|
||||
continue
|
||||
else
|
||||
if available < 0
|
||||
selector.fits = false
|
||||
available += selector.width
|
||||
scope.fitting = false
|
||||
|
||||
loadWidths = ->
|
||||
$(element).find("li").not(".more").each (i) ->
|
||||
if i < scope.allSelectors.length
|
||||
scope.allSelectors[i].width = $(this).outerWidth(true)
|
||||
return null # So we don't exit the loop weirdly
|
||||
|
||||
scope.overFlowSelectors = ->
|
||||
return [] unless scope.allSelectors?
|
||||
$filter('filter')(scope.allSelectors, { fits: false })
|
||||
|
||||
scope.selectedOverFlowSelectors = ->
|
||||
$filter('filter')(scope.overFlowSelectors(), { active: true })
|
||||
|
||||
# had to duplicate this to make overflow selectors work
|
||||
scope.emit = ->
|
||||
scope.activeSelectors = scope.allSelectors.filter (selector)->
|
||||
selector.active
|
||||
.map (selector) ->
|
||||
selector.object.id
|
||||
|
||||
# From: http://stackoverflow.com/questions/4298612/jquery-how-to-call-resize-event-only-once-its-finished-resizing
|
||||
debouncer = (func, timeout) ->
|
||||
timeoutID = undefined
|
||||
timeout = timeout or 50
|
||||
->
|
||||
subject = this
|
||||
args = arguments
|
||||
clearTimeout timeoutID
|
||||
timeoutID = setTimeout(->
|
||||
func.apply subject, Array::slice.call(args)
|
||||
, timeout)
|
||||
|
||||
|
||||
# -- Event management
|
||||
scope.$watchCollection "allSelectors", ->
|
||||
scope.refit()
|
||||
|
||||
scope.$on "filtersToggled", ->
|
||||
scope.refit()
|
||||
|
||||
$(window).resize debouncer (e) ->
|
||||
scope.fitting = true
|
||||
if scope.allSelectors?
|
||||
$timeout fit, 0, true
|
||||
@@ -2,7 +2,6 @@ angular.module('Darkswarm').factory "EnterpriseModal", ($modal, $rootScope, $htt
|
||||
# Build a modal popup for an enterprise.
|
||||
new class EnterpriseModal
|
||||
open: (enterprise)->
|
||||
return if enterprise.visible == 'hidden'
|
||||
scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise
|
||||
scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1
|
||||
|
||||
|
||||
@@ -55,10 +55,6 @@ angular.module('Darkswarm').factory "EnterpriseRegistrationService", ($http, Reg
|
||||
).catch((response) ->
|
||||
Loading.clear()
|
||||
alert(t('failed_to_update_enterprise_unknown'))
|
||||
if response.data.errors.instagram
|
||||
igErr = document.querySelector("#instagram-error")
|
||||
igErr.style.display = 'block'
|
||||
igErr.textContent = response.data.errors.instagram[0]
|
||||
)
|
||||
|
||||
prepare: =>
|
||||
|
||||
5
app/assets/javascripts/iehack.js
Normal file
5
app/assets/javascripts/iehack.js
Normal file
@@ -0,0 +1,5 @@
|
||||
$( document ).ready(function() {
|
||||
$("#closeie").click(function() {
|
||||
$("#ie-warning").hide();
|
||||
});
|
||||
})
|
||||
9227
app/assets/javascripts/shared/jquery-1.8.0.js
vendored
Normal file
9227
app/assets/javascripts/shared/jquery-1.8.0.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,11 @@
|
||||
.ofn-drop-down.ofn-drop-down-v2.right#columns-dropdown{ ng: { controller: 'ColumnsDropdownCtrl' } }
|
||||
.ofn-drop-down-label
|
||||
= " #{t('admin.columns')}".html_safe
|
||||
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
|
||||
.ofn-drop-down.right#columns-dropdown{ ng: { controller: 'ColumnsDropdownCtrl' } }
|
||||
%span{ :class => 'icon-reorder' }= " #{t('admin.columns')}".html_safe
|
||||
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
|
||||
%div.menu{ 'ng-show' => "expanded" }
|
||||
.menu_items
|
||||
.menu_item{ ng: { repeat: "column in columns", click: "toggle(column);" } }
|
||||
%input.redesigned-input{ type: "checkbox", ng: { checked: "column.visible" } }
|
||||
{{ column.name }}
|
||||
%hr
|
||||
%div.menu_item.text-center
|
||||
%input.fullwidth.orange{ type: "button", ng: { value: "saved() ? 'Saved': 'Saving'", show: "saved() || saving", disabled: "saved()" } }
|
||||
%input.fullwidth.red{ type: "button", :value => t('admin.column_save_as_default').html_safe, ng: { show: "!saved() && !saving", click: "saveColumnPreferences(action)"} }
|
||||
%div.menu_item{ ng: { repeat: "column in columns", click: "toggle(column)", class: "{selected: column.visible}" } }
|
||||
%span.check
|
||||
%span.name {{ column.name }}
|
||||
%hr
|
||||
%div.menu_item.text-center
|
||||
%input.fullwidth.orange{ type: "button", ng: { value: "saved() ? 'Saved': 'Saving'", show: "saved() || saving", disabled: "saved()" } }
|
||||
%input.fullwidth.red{ type: "button", :value => t('admin.column_save_as_default').html_safe, ng: { show: "!saved() && !saving", click: "saveColumnPreferences(action)"} }
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
%div
|
||||
.margin-bottom-30.text-center
|
||||
.text-big
|
||||
{{ 'js.admin.modals.terms_and_conditions_warning.title' | t }}
|
||||
.margin-bottom-30
|
||||
%p
|
||||
{{ 'js.admin.modals.terms_and_conditions_warning.message_1' | t }}
|
||||
.margin-bottom-30
|
||||
%p
|
||||
{{ 'js.admin.modals.terms_and_conditions_warning.message_2' | t }}
|
||||
|
||||
.text-center
|
||||
%input.button.red{ type: 'button', value: t('js.admin.modals.close'), ng: { click: 'close()' } }
|
||||
%input.button.red{ type: 'button', value: t('js.admin.modals.continue'), ng: { click: 'continue()' } }
|
||||
@@ -0,0 +1,3 @@
|
||||
%div{ ng: { show: "data.length > limit" } }
|
||||
%input{ type: 'button', value: t(:show_more), ng: { click: 'limit = limit + increment' } }
|
||||
%input{ type: 'button', value: t(:show_all_with_more, num: '{{ data.length - limit }}'), ng: { click: 'limit = data.length' } }
|
||||
@@ -0,0 +1,14 @@
|
||||
-# In order for the single-line-selector scope to have access to the available selectors,
|
||||
%filter-selector{"selector-set" => "selectors", objects: "objects()", "active-selectors" => "activeSelectors", "all-selectors" => "allSelectors" }
|
||||
|
||||
%ul{ ng: { if: "overFlowSelectors().length > 0 || fitting" } }
|
||||
%li.more
|
||||
%a.dropdown{ data: { dropdown: "{{ 'show-more-' + selectorName }}" }, ng: { class: "{active: selectedOverFlowSelectors().length > 0}" } }
|
||||
%span
|
||||
{{ 'js.more_items' | t:{ count: overFlowSelectors().length } }}
|
||||
%i.ofn-i_052-point-down
|
||||
.f-dropdown.text-right.content{ ng: { attr: { id: "{{ 'show-more-' + selectorName }}" } } }
|
||||
%ul
|
||||
%active-selector{ ng: { repeat: "selector in overFlowSelectors()", hide: "selector.fits" } }
|
||||
%render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"}}
|
||||
%span {{ selector.object.name }}
|
||||
@@ -1,17 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ConfirmModalComponent < ModalComponent
|
||||
def initialize(id:, confirm_actions: nil, controllers: nil, message: nil, confirm_reflexes: nil)
|
||||
super(id: id, close_button: true)
|
||||
@confirm_actions = confirm_actions
|
||||
@confirm_reflexes = confirm_reflexes
|
||||
@controllers = controllers
|
||||
@message = message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def close_button_class
|
||||
"secondary"
|
||||
end
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
%div{ id: @id, "data-controller": "modal #{@controllers}", "data-action": "keyup@document->modal#closeIfEscapeKey" }
|
||||
.reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#close" }
|
||||
.reveal-modal.fade.tiny.help-modal{ "data-modal-target": "modal" }
|
||||
= content
|
||||
|
||||
= render @message if @message
|
||||
|
||||
.modal-actions
|
||||
%input{ class: "button icon-plus #{close_button_class}", type: 'button', value: t('js.admin.modals.cancel'), "data-action": "click->modal#close" }
|
||||
%input{ class: "button icon-plus primary", type: 'button', value: t('js.admin.modals.confirm'), "data-action": @confirm_actions, "data-reflex": @confirm_reflexes }
|
||||
@@ -1,4 +0,0 @@
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
@@ -1,7 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class HelpModalComponent < ModalComponent
|
||||
class HelpModalComponent < ViewComponent::Base
|
||||
def initialize(id:, close_button: true)
|
||||
super(id: id, close_button: close_button)
|
||||
@id = id
|
||||
@close_button = close_button
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def close_button_class
|
||||
if namespace == "admin"
|
||||
"red"
|
||||
else
|
||||
"primary"
|
||||
end
|
||||
end
|
||||
|
||||
def close_button?
|
||||
!!@close_button
|
||||
end
|
||||
|
||||
def namespace
|
||||
helpers.controller_path.split("/").first
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ModalComponent < ViewComponent::Base
|
||||
def initialize(id:, close_button: true)
|
||||
@id = id
|
||||
@close_button = close_button
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def close_button_class
|
||||
if namespace == "admin"
|
||||
"red"
|
||||
else
|
||||
"primary"
|
||||
end
|
||||
end
|
||||
|
||||
def close_button?
|
||||
!!@close_button
|
||||
end
|
||||
|
||||
def namespace
|
||||
helpers.controller_path.split("/").first
|
||||
end
|
||||
end
|
||||
@@ -1,15 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PaginationComponent < ViewComponentReflex::Component
|
||||
def initialize(pagy:, data:)
|
||||
super
|
||||
@count = pagy.count
|
||||
@page = pagy.page
|
||||
@per_page = pagy.items
|
||||
@pages = pagy.pages
|
||||
@next = pagy.next
|
||||
@prev = pagy.prev
|
||||
@data = data
|
||||
@series = pagy.series
|
||||
end
|
||||
end
|
||||
@@ -1,16 +0,0 @@
|
||||
= component_controller do
|
||||
%nav{"aria-label": "pagination"}
|
||||
.pagination
|
||||
.pagination-prev{data: @prev.nil? ? nil : @data, "data-page": @prev, class: "#{'inactive' if @prev.nil?}"}
|
||||
= I18n.t "components.pagination.previous"
|
||||
.pagination-pages
|
||||
- @series.each do |page|
|
||||
- if page == :gap
|
||||
.pagination-gap
|
||||
…
|
||||
- else
|
||||
.pagination-page{data: @data, "data-page": page, class: "#{'active' if page.to_i == @page}"}
|
||||
= page
|
||||
.pagination-next{data: @next.nil? ? nil : @data, "data-page": @next, class: "#{'inactive' if @next.nil?}"}
|
||||
= I18n.t "components.pagination.next"
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
nav {
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
font-size: 14px;
|
||||
|
||||
.pagination-prev, .pagination-next {
|
||||
cursor: pointer;
|
||||
|
||||
&:after, &:before {
|
||||
font-size: 2em;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
cursor: default;
|
||||
color: $disabled-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-prev {
|
||||
margin-left: 10px;
|
||||
|
||||
&:before {
|
||||
content: "‹";
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-next {
|
||||
margin-right: 10px;
|
||||
|
||||
&:after {
|
||||
content: "›";
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.pagination-pages {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
.pagination-gap, .pagination-page {
|
||||
padding: 0 0.5rem;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.pagination-gap {
|
||||
color: $disabled-dark;
|
||||
}
|
||||
|
||||
.pagination-page {
|
||||
color: $color-4;
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
border-top: 3px solid $spree-blue;
|
||||
color: $spree-blue;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProductComponent < ViewComponentReflex::Component
|
||||
DATETIME_FORMAT = '%F %T'
|
||||
|
||||
def initialize(product:, columns:)
|
||||
super
|
||||
@product = product
|
||||
@image = @product.images[0] if product.images.any?
|
||||
@columns = columns.map do |c|
|
||||
{
|
||||
id: c[:value],
|
||||
value: column_value(c[:value])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# This must be define when using ProductComponent.with_collection()
|
||||
def collection_key
|
||||
@product.id
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
||||
def column_value(column)
|
||||
case column
|
||||
when 'name'
|
||||
@product.name
|
||||
when 'price'
|
||||
@product.price
|
||||
when 'unit'
|
||||
"#{@product.unit_value} #{@product.variant_unit}"
|
||||
when 'producer'
|
||||
@product.supplier.name
|
||||
when 'category'
|
||||
@product.taxons.map(&:name).join(', ')
|
||||
when 'sku'
|
||||
@product.sku
|
||||
when 'on_hand'
|
||||
@product.on_hand || 0
|
||||
when 'on_demand'
|
||||
@product.on_demand
|
||||
when 'tax_category'
|
||||
@product.tax_category.name
|
||||
when 'inherits_properties'
|
||||
@product.inherits_properties
|
||||
when 'available_on'
|
||||
format_date(@product.available_on)
|
||||
when 'import_date'
|
||||
format_date(@product.import_date)
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
||||
|
||||
private
|
||||
|
||||
def format_date(date)
|
||||
date&.strftime(DATETIME_FORMAT) || ''
|
||||
end
|
||||
end
|
||||
@@ -1,6 +0,0 @@
|
||||
%tr
|
||||
- @columns.each do |column|
|
||||
%td.products_column{class: column[:id]}
|
||||
- if column[:id] == "name" && @image
|
||||
= image_tag @image.url(:mini)
|
||||
= column[:value]
|
||||
@@ -1,181 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ProductsTableComponent < ViewComponentReflex::Component
|
||||
include Pagy::Backend
|
||||
|
||||
SORTABLE_COLUMNS = ['name', 'import_date'].freeze
|
||||
SELECTABLE_COLUMNS = [
|
||||
{ label: I18n.t("admin.products_page.columns_selector.price"), value: "price" },
|
||||
{ label: I18n.t("admin.products_page.columns_selector.unit"), value: "unit" },
|
||||
{ label: I18n.t("admin.products_page.columns_selector.producer"), value: "producer" },
|
||||
{ label: I18n.t("admin.products_page.columns_selector.category"), value: "category" },
|
||||
{ label: I18n.t("admin.products_page.columns_selector.sku"), value: "sku" },
|
||||
{ label: I18n.t("admin.products_page.columns_selector.on_hand"), value: "on_hand" },
|
||||
{ label: I18n.t("admin.products_page.columns_selector.on_demand"), value: "on_demand" },
|
||||
{ label: I18n.t("admin.products_page.columns_selector.tax_category"), value: "tax_category" },
|
||||
{
|
||||
label: I18n.t("admin.products_page.columns_selector.inherits_properties"),
|
||||
value: "inherits_properties"
|
||||
},
|
||||
{ label: I18n.t("admin.products_page.columns_selector.available_on"), value: "available_on" },
|
||||
{ label: I18n.t("admin.products_page.columns_selector.import_date"), value: "import_date" }
|
||||
].sort do |a, b|
|
||||
a[:label] <=> b[:label]
|
||||
end.freeze
|
||||
|
||||
PER_PAGE_VALUE = [10, 25, 50, 100].freeze
|
||||
PER_PAGE = PER_PAGE_VALUE.map { |value| { label: value, value: value } }
|
||||
NAME_COLUMN = {
|
||||
label: I18n.t("admin.products_page.columns.name"), value: "name", sortable: true
|
||||
}.freeze
|
||||
|
||||
def initialize(user:)
|
||||
super
|
||||
@user = user
|
||||
@selectable_columns = SELECTABLE_COLUMNS
|
||||
@columns_selected = ['unit', 'price', 'on_hand', 'category', 'import_date']
|
||||
@per_page = PER_PAGE
|
||||
@per_page_selected = [10]
|
||||
@categories = [{ label: "All", value: "all" }] +
|
||||
Spree::Taxon.order(:name)
|
||||
.map { |taxon| { label: taxon.name, value: taxon.id.to_s } }
|
||||
@categories_selected = ["all"]
|
||||
@producers = [{ label: "All", value: "all" }] +
|
||||
OpenFoodNetwork::Permissions.new(@user)
|
||||
.managed_product_enterprises.is_primary_producer.by_name
|
||||
.map { |producer| { label: producer.name, value: producer.id.to_s } }
|
||||
@producers_selected = ["all"]
|
||||
@page = 1
|
||||
@sort = { column: "name", direction: "asc" }
|
||||
@search_term = ""
|
||||
end
|
||||
|
||||
# any change on a "reflex_data_attributes" (defined in the template) will trigger a re render
|
||||
def before_render
|
||||
fetch_products
|
||||
refresh_columns
|
||||
end
|
||||
|
||||
# Element refers to the component the data is set on
|
||||
def search_term
|
||||
# Element is SearchInputComponent
|
||||
@search_term = element.dataset['value']
|
||||
end
|
||||
|
||||
def toggle_column
|
||||
# Element is SelectorComponent
|
||||
column = element.dataset['value']
|
||||
@columns_selected = if @columns_selected.include?(column)
|
||||
@columns_selected - [column]
|
||||
else
|
||||
@columns_selected + [column]
|
||||
end
|
||||
end
|
||||
|
||||
def click_sort
|
||||
# Element is TableHeaderComponent
|
||||
@sort = {
|
||||
column: element.dataset['sort-value'],
|
||||
direction: element.dataset['sort-direction'] == "asc" ? "desc" : "asc"
|
||||
}
|
||||
end
|
||||
|
||||
def toggle_per_page
|
||||
# Element is SelectorComponent
|
||||
selected = element.dataset['value'].to_i
|
||||
@per_page_selected = [selected] if PER_PAGE_VALUE.include?(selected)
|
||||
end
|
||||
|
||||
def toggle_category
|
||||
# Element is SelectorWithFilterComponent
|
||||
category_clicked = element.dataset['value']
|
||||
@categories_selected = toggle_selector_with_filter(category_clicked, @categories_selected)
|
||||
end
|
||||
|
||||
def toggle_producer
|
||||
# Element is SelectorWithFilterComponent
|
||||
producer_clicked = element.dataset['value']
|
||||
@producers_selected = toggle_selector_with_filter(producer_clicked, @producers_selected)
|
||||
end
|
||||
|
||||
def change_page
|
||||
# Element is PaginationComponent
|
||||
page = element.dataset['page'].to_i
|
||||
@page = page if page > 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_columns
|
||||
@columns = @columns_selected.map do |column|
|
||||
{
|
||||
label: I18n.t("admin.products_page.columns.#{column}"),
|
||||
value: column,
|
||||
sortable: SORTABLE_COLUMNS.include?(column)
|
||||
}
|
||||
end.sort! { |a, b| a[:label] <=> b[:label] }
|
||||
@columns.unshift(NAME_COLUMN)
|
||||
end
|
||||
|
||||
def toggle_selector_with_filter(clicked, selected)
|
||||
selected = if selected.include?(clicked)
|
||||
selected - [clicked]
|
||||
else
|
||||
selected + [clicked]
|
||||
end
|
||||
|
||||
if clicked == "all" || selected.empty?
|
||||
selected = ["all"]
|
||||
elsif selected.include?("all") && selected.length > 1
|
||||
selected -= ["all"]
|
||||
end
|
||||
selected
|
||||
end
|
||||
|
||||
def fetch_products
|
||||
product_query = OpenFoodNetwork::Permissions.new(@user).editable_products.merge(product_scope)
|
||||
@products = product_query.ransack(ransack_query).result
|
||||
@pagy, @products = pagy(@products, items: @per_page_selected.first, page: @page)
|
||||
end
|
||||
|
||||
def product_scope
|
||||
scope = if @user.has_spree_role?("admin") || @user.enterprises.present?
|
||||
Spree::Product
|
||||
else
|
||||
Spree::Product.active
|
||||
end
|
||||
|
||||
scope.includes(product_query_includes)
|
||||
end
|
||||
|
||||
def ransack_query
|
||||
query = { s: "#{@sort[:column]} #{@sort[:direction]}" }
|
||||
|
||||
query = if @producers_selected.include?("all")
|
||||
query.merge({ supplier_id_eq: "" })
|
||||
else
|
||||
query.merge({ supplier_id_in: @producers_selected })
|
||||
end
|
||||
|
||||
query = query.merge({ name_cont: @search_term }) if @search_term.present?
|
||||
|
||||
if @categories_selected.include?("all")
|
||||
query.merge({ primary_taxon_id_eq: "" })
|
||||
else
|
||||
query.merge({ primary_taxon_id_in: @categories_selected })
|
||||
end
|
||||
end
|
||||
|
||||
def product_query_includes
|
||||
[
|
||||
master: [:images],
|
||||
variants: [
|
||||
:default_price,
|
||||
:stock_locations,
|
||||
:stock_items,
|
||||
:variant_overrides,
|
||||
{ option_values: :option_type }
|
||||
]
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
= component_controller(class: "products-table") do
|
||||
.products-table-form
|
||||
.products-table-form_filter_results
|
||||
= render(SearchInputComponent.new(value: @search_term, data: reflex_data_attributes(:search_term)))
|
||||
.products-table-form_categories_selector
|
||||
= render(SelectorWithFilterComponent.new(title: t("admin.products_page.filters.categories.title"), selected: @categories_selected, items: @categories, data: reflex_data_attributes(:toggle_category), selected_items_i18n_key: "admin.products_page.filters.categories.selected_categories"))
|
||||
.products-table-form_producers_selector
|
||||
= render(SelectorWithFilterComponent.new(title: t("admin.products_page.filters.producers.title"), selected: @producers_selected, items: @producers, data: reflex_data_attributes(:toggle_producer), selected_items_i18n_key: "admin.products_page.filters.producers.selected_producers"))
|
||||
.products-table-form_per-page_selector
|
||||
= render(SelectorComponent.new(title: t('admin.products_page.filters.per_page', count: @per_page_selected[0]), selected: @per_page_selected, items: @per_page, data: reflex_data_attributes(:toggle_per_page)))
|
||||
.products-table-form_columns_selector
|
||||
= render(SelectorComponent.new(title: t("admin.products_page.filters.columns"), selected: @columns_selected, items: @selectable_columns, data: reflex_data_attributes(:toggle_column)))
|
||||
|
||||
.products-table_table
|
||||
%table
|
||||
= render(TableHeaderComponent.new(columns: @columns, sort: @sort, data: reflex_data_attributes(:click_sort)))
|
||||
%tbody
|
||||
= render(ProductComponent.with_collection(@products, columns: @columns))
|
||||
|
||||
.products-table-form_pagination
|
||||
= render(PaginationComponent.new(pagy: @pagy, data: reflex_data_attributes(:change_page)))
|
||||
@@ -1,47 +0,0 @@
|
||||
.products-table {
|
||||
.products-table-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) );
|
||||
grid-gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.products-table_table {
|
||||
box-shadow: 0 10px 10px -1px rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.products-table-form_pagination {
|
||||
position: relative;
|
||||
top: -15px;
|
||||
|
||||
nav, .pagination {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.products-table.loading {
|
||||
.products-table-form_pagination, .products-table_table {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.products-table_table {
|
||||
&:before {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 50px 50px;
|
||||
background-image: url("../images/spinning-circles.svg");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SearchInputComponent < ViewComponentReflex::Component
|
||||
def initialize(value: nil, data: {})
|
||||
super
|
||||
@value = value
|
||||
@data = data
|
||||
end
|
||||
end
|
||||
@@ -1,5 +0,0 @@
|
||||
= component_controller do
|
||||
%div.search-input
|
||||
%input{type: 'text', placeholder: t("components.search_input.placeholder"), id: 'search_query', data: {action: 'debounced:input->search-input#search'}, value: @value}
|
||||
.search-button{data: @data}
|
||||
%i.fa.fa-search
|
||||
@@ -1,23 +0,0 @@
|
||||
.search-input {
|
||||
border: 1px solid $disabled-light;
|
||||
height: 3em;
|
||||
display: flex;
|
||||
line-height: 3em;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
border: none;
|
||||
height: 3em;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
padding-right: 10px;
|
||||
padding-left: 5px;
|
||||
cursor: pointer;
|
||||
color: $color-4;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SelectorComponent < ViewComponentReflex::Component
|
||||
def initialize(title:, selected:, items:, data: {})
|
||||
super
|
||||
@title = title
|
||||
@items = items.map do |item|
|
||||
{
|
||||
label: item[:label],
|
||||
value: item[:value],
|
||||
selected: selected.include?(item[:value])
|
||||
}
|
||||
end
|
||||
@selected = selected
|
||||
@data = data
|
||||
end
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
= component_controller do
|
||||
.selector.selector-close
|
||||
.selector-main{ data: { action: "click->selector#toggle" } }
|
||||
.selector-main-title
|
||||
= @title
|
||||
.selector-arrow
|
||||
.selector-wrapper
|
||||
.selector-items
|
||||
- @items.each do |item|
|
||||
.selector-item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] }
|
||||
= item[:label]
|
||||
@@ -1,86 +0,0 @@
|
||||
.selector {
|
||||
position: relative;
|
||||
|
||||
.selector-main {
|
||||
border: 1px solid $disabled-light;
|
||||
height: 3em;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.selector-main-title {
|
||||
line-height: 3em;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.selector-arrow {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
height: 3em;
|
||||
width: 1.5em;
|
||||
top: -1px;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 5px;
|
||||
margin-top: -5px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 5px solid $disabled-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selector-wrapper {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 1;
|
||||
background-color: white;
|
||||
margin-top: -1px;
|
||||
border: 1px solid $disabled-light;
|
||||
|
||||
.selector-items {
|
||||
overflow-y: auto;
|
||||
min-height: 6em;
|
||||
|
||||
.selector-item {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-bottom: 1px solid $disabled-light;
|
||||
position: relative;
|
||||
height: 3em;
|
||||
line-height: 3em;
|
||||
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
&:after {
|
||||
content: "✓";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.selector-close {
|
||||
.selector-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SelectorWithFilterComponent < SelectorComponent
|
||||
def initialize(title:, selected:, items:, data: {},
|
||||
selected_items_i18n_key: 'components.selector_with_filter.selected_items')
|
||||
super(title: title, selected: selected, items: items, data: data)
|
||||
@selected_items = items.select { |item| @selected.include?(item[:value]) }
|
||||
@selected_items_i18n_key = selected_items_i18n_key
|
||||
@items = items
|
||||
end
|
||||
end
|
||||
@@ -1,22 +0,0 @@
|
||||
= component_controller do
|
||||
.super-selector.selector.selector-close
|
||||
.selector-main{ data: { action: "click->selector-with-filter#toggle" } }
|
||||
.super-selector-label
|
||||
= @title
|
||||
.super-selector-selected-items
|
||||
- case @selected_items.length
|
||||
- when 1, 2
|
||||
- @selected_items.each do |item|
|
||||
.super-selector-selected-item
|
||||
= item[:label]
|
||||
- else
|
||||
.super-selector-selected-item
|
||||
= t(@selected_items_i18n_key, count: @selected_items.length)
|
||||
.selector-arrow
|
||||
.selector-wrapper
|
||||
.super-selector-search
|
||||
%input{type: "text", placeholder: t("components.selector_with_filter.search_placeholder"), data: { action: "debounced:input->selector-with-filter#filter" } }
|
||||
.selector-items
|
||||
- @items.each do |item|
|
||||
.selector-item{ class: ("selected" if item[:selected]), data: @data.merge({ "selector-with-filter-target": "items" }), "data-value": item[:value] }
|
||||
= item[:label]
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
.super-selector {
|
||||
position: relative;
|
||||
|
||||
.selector-main {
|
||||
.super-selector-label {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
margin-left: 10px;
|
||||
position: absolute;
|
||||
top: -1em;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.super-selector-selected-items {
|
||||
margin-left: 5px;
|
||||
margin-right: 2em;
|
||||
margin-top: 7px;
|
||||
display: flex;
|
||||
|
||||
.super-selector-selected-item {
|
||||
border: 1px solid $pale-blue;
|
||||
background-color: $spree-light-blue;
|
||||
border-radius: 20px;
|
||||
height: 2em;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
padding-top: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.selector-wrapper {
|
||||
.super-selector-search {
|
||||
border-bottom: 1px solid $disabled-light;
|
||||
padding: 10px 5px;
|
||||
|
||||
input {
|
||||
border: 1px solid $disabled-light;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TableHeaderComponent < ViewComponentReflex::Component
|
||||
def initialize(columns:, sort:, data: {})
|
||||
super
|
||||
@columns = columns
|
||||
@sort = sort
|
||||
@data = data
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
= component_controller do
|
||||
%thead.table-header
|
||||
%tr
|
||||
- @columns.each do |column|
|
||||
%th{class: (column[:sortable] ? "th-sortable " : "" ) + (@sort[:column] == column[:value] ? " th-sorted-#{@sort[:direction]}" : ""), data: (@data if column[:sortable] == true), "data-sort-value": column[:value], "data-sort-direction": @sort[:direction]}
|
||||
= column[:label]
|
||||
@@ -1,23 +0,0 @@
|
||||
thead.table-header {
|
||||
th {
|
||||
&.th-sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
&.th-sorted-asc, &.th-sorted-desc {
|
||||
&:after {
|
||||
display: inline-block;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
&.th-sorted-asc {
|
||||
&:after {
|
||||
content: "⇧";
|
||||
}
|
||||
}
|
||||
&.th-sorted-desc {
|
||||
&:after {
|
||||
content: "⇩";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "open_food_network/feature_toggle"
|
||||
|
||||
class FeatureToggleConstraint
|
||||
def initialize(feature_name, negate: false)
|
||||
def initialize(feature_name)
|
||||
@feature = feature_name
|
||||
@negate = negate
|
||||
end
|
||||
|
||||
def matches?(request)
|
||||
enabled?(request) ^ @negate
|
||||
end
|
||||
|
||||
def enabled?(request)
|
||||
OpenFoodNetwork::FeatureToggle.enabled?(@feature, current_user(request))
|
||||
end
|
||||
|
||||
def current_user(request)
|
||||
request.env['warden']&.user
|
||||
request.env['warden'].user
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,8 +18,6 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
ContentConfig.updated_at = Time.zone.now
|
||||
|
||||
flash[:success] =
|
||||
t(:successfully_updated, resource: I18n.t('admin.contents.edit.your_content'))
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
require 'open_food_network/address_finder'
|
||||
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
module Admin
|
||||
class CustomersController < Admin::ResourceController
|
||||
before_action :load_managed_shops, only: :index, if: :html_request?
|
||||
@@ -68,7 +67,7 @@ module Admin
|
||||
|
||||
def collection
|
||||
if json_request? && params[:enterprise_id].present?
|
||||
CustomersWithBalance.new(customers).query.
|
||||
CustomersWithBalance.new(managed_enterprise_id).query.
|
||||
includes(
|
||||
:enterprise,
|
||||
{ bill_address: [:state, :country] },
|
||||
@@ -80,15 +79,6 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
def customers
|
||||
return @customers if @customers.present?
|
||||
|
||||
@customers = Customer.managed_by(spree_current_user)
|
||||
return @customers if params[:enterprise_id].blank?
|
||||
|
||||
@customers = @customers.where(enterprise_id: params[:enterprise_id])
|
||||
end
|
||||
|
||||
def managed_enterprise_id
|
||||
@managed_enterprise_id ||= Enterprise.managed_by(spree_current_user).
|
||||
select('enterprises.id').find_by(id: params[:enterprise_id])
|
||||
@@ -126,8 +116,21 @@ module Admin
|
||||
|
||||
# Fetches tags for all customers of the enterprise and returns a hash indexed by customer_id
|
||||
def customer_tags_by_id
|
||||
BatchTaggableTagsQuery.call(Customer.of(managed_enterprise_id))
|
||||
customer_tags = ::ActsAsTaggableOn::Tag.
|
||||
joins(:taggings).
|
||||
includes(:taggings).
|
||||
where(taggings:
|
||||
{ taggable_type: 'Customer',
|
||||
taggable_id: Customer.of(managed_enterprise_id),
|
||||
context: 'tags' })
|
||||
|
||||
customer_tags.each_with_object({}) do |tag, indexed_hash|
|
||||
tag.taggings.each do |tagging|
|
||||
customer_id = tagging.taggable_id
|
||||
indexed_hash[customer_id] ||= []
|
||||
indexed_hash[customer_id] << tag.name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/ClassLength
|
||||
|
||||
@@ -7,7 +7,6 @@ module Admin
|
||||
before_action :load_enterprise_fee_set, only: :index
|
||||
before_action :load_data
|
||||
before_action :check_enterprise_fee_input, only: [:bulk_update]
|
||||
before_action :check_calculators_compatibility_with_taxes, only: [:bulk_update]
|
||||
|
||||
def index
|
||||
@include_calculators = params[:include_calculators].present?
|
||||
@@ -120,17 +119,5 @@ module Admin
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_calculators_compatibility_with_taxes
|
||||
enterprise_fee_bulk_params['collection_attributes'].each do |_, enterprise_fee|
|
||||
next unless enterprise_fee['inherits_tax_category'] == "true"
|
||||
next unless EnterpriseFee::PER_ORDER_CALCULATORS.include?(enterprise_fee['calculator_type'])
|
||||
|
||||
flash[:error] = I18n.t(
|
||||
'activerecord.errors.models.enterprise_fee.inherit_tax_requires_per_item_calculator'
|
||||
)
|
||||
return redirect_to redirect_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,12 @@ module Admin
|
||||
class EnterprisesController < Admin::ResourceController
|
||||
include GeocodeEnterpriseAddress
|
||||
include CablecarResponses
|
||||
include Pagy::Backend
|
||||
|
||||
# These need to run before #load_resource so that @object is initialised with sanitised values
|
||||
prepend_before_action :override_owner, only: :create
|
||||
prepend_before_action :override_sells, only: :create
|
||||
|
||||
before_action :load_enterprise_set, only: :index
|
||||
before_action :load_countries, except: [:index, :register, :check_permalink]
|
||||
before_action :load_methods_and_fees, only: [:edit, :update]
|
||||
before_action :load_groups, only: [:new, :edit, :update, :create]
|
||||
@@ -33,8 +33,6 @@ module Admin
|
||||
include OrderCyclesHelper
|
||||
|
||||
def index
|
||||
load_enterprise_set_on_index
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json {
|
||||
@@ -50,7 +48,7 @@ module Admin
|
||||
if params[:stimulus]
|
||||
@enterprise.is_primary_producer = params[:is_primary_producer]
|
||||
@enterprise.sells = params[:enterprise_sells]
|
||||
render cable_ready: cable_car.morph("#side_menu", partial("admin/shared/side_menu"))
|
||||
render operations: cable_car.morph("#side_menu", partial("admin/shared/side_menu"))
|
||||
.morph("#permalink", partial("admin/enterprises/form/permalink"))
|
||||
end
|
||||
end
|
||||
@@ -102,8 +100,7 @@ module Admin
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
load_enterprise_set_with_params(bulk_params)
|
||||
|
||||
@enterprise_set = Sets::EnterpriseSet.new(collection, bulk_params)
|
||||
if @enterprise_set.save
|
||||
flash[:success] = I18n.t(:enterprise_bulk_update_success_notice)
|
||||
|
||||
@@ -151,15 +148,8 @@ module Admin
|
||||
|
||||
private
|
||||
|
||||
def load_enterprise_set_on_index
|
||||
return unless spree_current_user.admin?
|
||||
|
||||
load_enterprise_set_with_params
|
||||
end
|
||||
|
||||
def load_enterprise_set_with_params(params = {})
|
||||
@pagy, @paginated_collection = pagy(@collection)
|
||||
@enterprise_set = Sets::EnterpriseSet.new(@paginated_collection, params)
|
||||
def load_enterprise_set
|
||||
@enterprise_set = Sets::EnterpriseSet.new(collection) if spree_current_user.admin?
|
||||
end
|
||||
|
||||
def load_countries
|
||||
|
||||
@@ -18,6 +18,7 @@ module Admin
|
||||
params.require(:preferences).permit(
|
||||
:enable_invoices?,
|
||||
:invoice_style2?,
|
||||
:enable_receipt_printing?,
|
||||
:enterprise_number_required_on_invoices?,
|
||||
)
|
||||
end
|
||||
|
||||
47
app/controllers/admin/manager_invitations_controller.rb
Normal file
47
app/controllers/admin/manager_invitations_controller.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ManagerInvitationsController < Spree::Admin::BaseController
|
||||
authorize_resource class: false
|
||||
|
||||
def create
|
||||
@email = params[:email]
|
||||
@enterprise = Enterprise.find(params[:enterprise_id])
|
||||
|
||||
authorize! :edit, @enterprise
|
||||
|
||||
existing_user = Spree::User.find_by(email: @email)
|
||||
|
||||
if existing_user
|
||||
render json: { errors: t('admin.enterprises.invite_manager.user_already_exists') },
|
||||
status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
new_user = create_new_manager
|
||||
|
||||
if new_user
|
||||
render json: { user: new_user.id }, status: :ok
|
||||
else
|
||||
render json: { errors: t('admin.enterprises.invite_manager.error') },
|
||||
status: :internal_server_error
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_new_manager
|
||||
password = Devise.friendly_token
|
||||
new_user = Spree::User.create(email: @email, unconfirmed_email: @email, password: password)
|
||||
new_user.reset_password_token = Devise.friendly_token
|
||||
# Same time as used in Devise's lib/devise/models/recoverable.rb.
|
||||
new_user.reset_password_sent_at = Time.now.utc
|
||||
new_user.save!
|
||||
|
||||
@enterprise.users << new_user
|
||||
EnterpriseMailer.manager_invitation(@enterprise, new_user).deliver_later
|
||||
|
||||
new_user
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -100,13 +100,10 @@ module Admin
|
||||
order_cycle.schedules.each do |schedule|
|
||||
Subscription.where(schedule_id: schedule.id).each do |subscription|
|
||||
shop = Enterprise.managed_by(spree_current_user).find_by(id: subscription.shop_id)
|
||||
fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(shop, order_cycle)
|
||||
subscription.subscription_line_items.nil_price_estimate.each do |line_item|
|
||||
variant = OrderManagement::Subscriptions::
|
||||
VariantsList.eligible_variants(shop).find_by(id: line_item.variant_id)
|
||||
# If the variant is not available in the shop, the price estimate will be nil
|
||||
next if variant.nil?
|
||||
|
||||
fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(shop, order_cycle)
|
||||
price = variant.price + fee_calculator.indexed_fees_for(variant)
|
||||
line_item.update_column(:price_estimate, price)
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ module Admin
|
||||
end
|
||||
|
||||
def reset_absent_products
|
||||
@importer = ProductImport::ProductImporter.new(File.new(file_path),
|
||||
@importer = ProductImport::ProductImporter.new(File.new(params[:filepath]),
|
||||
spree_current_user, import_into: params[:import_into], enterprises_to_reset: params[:enterprises_to_reset], updated_ids: params[:updated_ids], settings: params[:settings])
|
||||
|
||||
if params.key?(:enterprises_to_reset) && params.key?(:updated_ids)
|
||||
@@ -56,7 +56,7 @@ module Admin
|
||||
end
|
||||
|
||||
def process_data(method)
|
||||
@importer = ProductImport::ProductImporter.new(File.new(file_path),
|
||||
@importer = ProductImport::ProductImporter.new(File.new(params[:filepath]),
|
||||
spree_current_user, start: params[:start], end: params[:end], settings: params[:settings])
|
||||
|
||||
begin
|
||||
@@ -112,25 +112,5 @@ module Admin
|
||||
def model_class
|
||||
ProductImport::ProductImporter
|
||||
end
|
||||
|
||||
def file_path
|
||||
@file_path ||= validate_file_path(sanitize_file_path(params[:filepath]))
|
||||
end
|
||||
|
||||
def sanitize_file_path(file_path)
|
||||
FilePathSanitizer.new.sanitize(file_path, on_error: method(:raise_invalid_file_path))
|
||||
end
|
||||
|
||||
def validate_file_path(file_path)
|
||||
return file_path if file_path.to_s.match?(TEMP_FILE_PATH_REGEX)
|
||||
|
||||
raise_invalid_file_path
|
||||
end
|
||||
|
||||
def raise_invalid_file_path
|
||||
redirect_to '/admin/product_import', notice: I18n.t(:product_import_no_data_in_spreadsheet_notice)
|
||||
raise 'Invalid File Path'
|
||||
end
|
||||
TEMP_FILE_PATH_REGEX = %r{^/tmp/product_import[A-Za-z0-9-]*/import\.csv$}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ProductsController < Spree::Admin::BaseController
|
||||
def index; end
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
module Admin
|
||||
class ReportsController < Spree::Admin::BaseController
|
||||
include ActiveStorage::SetCurrent
|
||||
include ReportsActions
|
||||
helper ReportsHelper
|
||||
|
||||
@@ -20,26 +19,23 @@ module Admin
|
||||
end
|
||||
|
||||
def show
|
||||
@report = report_class.new(spree_current_user, params, render: render_data?)
|
||||
@report = report_class.new(spree_current_user, params, request)
|
||||
|
||||
if params[:report_format].present?
|
||||
if report_format.present?
|
||||
export_report
|
||||
else
|
||||
show_report
|
||||
render_report
|
||||
end
|
||||
rescue Timeout::Error
|
||||
render_timeout_error
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_report
|
||||
send_data render_report_as(report_format), filename: report_filename
|
||||
send_data @report.render_as(report_format, controller: self), filename: report_filename
|
||||
end
|
||||
|
||||
def show_report
|
||||
def render_report
|
||||
assign_view_data
|
||||
@table = render_report_as(:html) if render_data?
|
||||
render "show"
|
||||
end
|
||||
|
||||
@@ -47,54 +43,21 @@ module Admin
|
||||
@report_type = report_type
|
||||
@report_subtypes = report_subtypes
|
||||
@report_subtype = report_subtype
|
||||
@report_title = report_title
|
||||
@rendering_options = rendering_options
|
||||
@report_title = if report_subtype
|
||||
report_subtype_title
|
||||
else
|
||||
I18n.t(:name, scope: [:admin, :reports, @report_type])
|
||||
end
|
||||
|
||||
# Initialize data
|
||||
params[:display_summary_row] = true if request.get?
|
||||
@params_fields_to_show = if request.get?
|
||||
@report.columns.keys - @report.fields_to_hide
|
||||
else
|
||||
params[:fields_to_show]
|
||||
end
|
||||
|
||||
@data = Reporting::FrontendData.new(spree_current_user)
|
||||
end
|
||||
|
||||
def render_data?
|
||||
request.post?
|
||||
end
|
||||
|
||||
def render_report_as(format)
|
||||
if OpenFoodNetwork::FeatureToggle.enabled?(:background_reports, spree_current_user)
|
||||
@blob = ReportBlob.create_for_upload_later!(report_filename)
|
||||
ReportJob.perform_later(
|
||||
report_class, spree_current_user, params, format, @blob
|
||||
)
|
||||
Timeout.timeout(max_wait_time) do
|
||||
sleep 1 until @blob.content_stored?
|
||||
end
|
||||
|
||||
# This result has been rendered by Rails in safe mode already.
|
||||
@blob.result.html_safe # rubocop:disable Rails/OutputSafety
|
||||
else
|
||||
@report.render_as(format)
|
||||
end
|
||||
end
|
||||
|
||||
def render_timeout_error
|
||||
assign_view_data
|
||||
if @blob
|
||||
@error = ".report_taking_longer_html"
|
||||
@error_url = @blob.expiring_service_url
|
||||
else
|
||||
@error = ".report_taking_longer"
|
||||
@error_url = ""
|
||||
end
|
||||
render "show"
|
||||
end
|
||||
|
||||
def max_wait_time
|
||||
# This value is used by rack-timeout and nginx, usually 30 seconds in
|
||||
# staging and production:
|
||||
server_timeout = ENV.fetch("RACK_TIMEOUT_SERVICE_TIMEOUT", "15").to_f
|
||||
|
||||
# Zero disables the timeout:
|
||||
return 0 if server_timeout.zero?
|
||||
|
||||
# We want to time out earlier than nginx:
|
||||
server_timeout - 2.seconds
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,8 +16,7 @@ module Admin
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if view_context.subscriptions_setup_complete?(@shops)
|
||||
@order_cycles = OrderCycle.joins(:schedules).managed_by(spree_current_user)
|
||||
.includes([:distributors, :cached_incoming_exchanges])
|
||||
@order_cycles = OrderCycle.joins(:schedules).managed_by(spree_current_user).includes([:distributors, :cached_incoming_exchanges])
|
||||
@payment_methods = Spree::PaymentMethod.managed_by(spree_current_user).includes(:taggings)
|
||||
@payment_method_tags = payment_method_tags_by_id
|
||||
@shipping_methods = Spree::ShippingMethod.managed_by(spree_current_user)
|
||||
@@ -26,10 +25,7 @@ module Admin
|
||||
render :setup_explanation
|
||||
end
|
||||
end
|
||||
format.json {
|
||||
render_as_json @collection, ams_prefix: params[:ams_prefix],
|
||||
payment_method_tags: payment_method_tags_by_id
|
||||
}
|
||||
format.json { render_as_json @collection, ams_prefix: params[:ams_prefix], payment_method_tags: payment_method_tags_by_id }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -166,20 +162,25 @@ module Admin
|
||||
[:index]
|
||||
end
|
||||
|
||||
def managed_enterprise_id
|
||||
Enterprise.managed_by(spree_current_user).select('enterprises.id').
|
||||
find_by(id: params[:enterprise_id])
|
||||
end
|
||||
|
||||
def subscription_params
|
||||
@subscription_params ||= PermittedAttributes::Subscription.new(params).call.
|
||||
to_h.with_indifferent_access
|
||||
end
|
||||
|
||||
def payment_method_tags_by_id
|
||||
@payment_method_tags_by_id ||= BatchTaggableTagsQuery.call(
|
||||
Spree::PaymentMethod.from(managed_enterprise_id)
|
||||
)
|
||||
payment_method_tags = ::ActsAsTaggableOn::Tag.
|
||||
joins(:taggings).
|
||||
includes(:taggings).
|
||||
where(taggings: { taggable_type: "Spree::PaymentMethod",
|
||||
taggable_id: Spree::PaymentMethod.from(Enterprise.managed_by(spree_current_user).
|
||||
select('enterprises.id').find_by(id: params[:enterprise_id])),
|
||||
context: 'tags' })
|
||||
|
||||
payment_method_tags.each_with_object({}) do |tag, hash|
|
||||
payment_method_id = tag.taggings.first.taggable_id
|
||||
hash[payment_method_id] ||= []
|
||||
hash[payment_method_id] << tag.name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,7 +63,7 @@ module Admin
|
||||
|
||||
options = [{ id: '0', name: 'All' }]
|
||||
import_dates.collect(&:import_date).map { |i|
|
||||
options.push(id: i.to_date, name: i.to_date.to_fs(:long))
|
||||
options.push(id: i.to_date, name: i.to_date.to_formatted_s(:long))
|
||||
}
|
||||
|
||||
options
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class VouchersController < ResourceController
|
||||
before_action :load_enterprise
|
||||
|
||||
def new
|
||||
@voucher = Voucher.new
|
||||
end
|
||||
|
||||
def create
|
||||
voucher_params = permitted_resource_params.merge(enterprise: @enterprise)
|
||||
@voucher = Voucher.create(voucher_params)
|
||||
|
||||
if @voucher.save
|
||||
redirect_to(
|
||||
"#{edit_admin_enterprise_path(@enterprise)}#vouchers_panel",
|
||||
flash: { success: flash_message_for(@voucher, :successfully_created) }
|
||||
)
|
||||
else
|
||||
flash[:error] = @voucher.errors.full_messages.to_sentence
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_enterprise
|
||||
@enterprise = OpenFoodNetwork::Permissions
|
||||
.new(spree_current_user)
|
||||
.editable_enterprises
|
||||
.find_by(permalink: params[:enterprise_id])
|
||||
end
|
||||
|
||||
def permitted_resource_params
|
||||
params.require(:voucher).permit(:code, :amount)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -116,7 +116,7 @@ module Api
|
||||
|
||||
def product_query_includes
|
||||
[
|
||||
master: { images: { attachment_attachment: :blob } },
|
||||
master: [:images],
|
||||
variants: [:default_price, :stock_locations, :stock_items, :variant_overrides,
|
||||
{ option_values: :option_type }]
|
||||
]
|
||||
|
||||
@@ -106,9 +106,7 @@ module Api
|
||||
end
|
||||
|
||||
def json_api_error(message, **options)
|
||||
error_options = options.delete(:error_options) || {}
|
||||
|
||||
{ errors: [{ detail: message }.merge(error_options)] }.merge(options)
|
||||
{ errors: [{ detail: message }] }.merge(options)
|
||||
end
|
||||
|
||||
def json_api_invalid(message, errors)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user