Compare commits

..

5 Commits

Author SHA1 Message Date
Mohamed ABDELLANI
1c60a3a660 read rendering options directly from the model ReportRendingOptions 2022-11-16 12:40:02 +01:00
Mohamed ABDELLANI
ac7a7b11a5 store display_header_row flag in the model 2022-11-16 12:29:44 +01:00
Mohamed ABDELLANI
0fb273ce93 store display_summary_row in the model 2022-11-16 12:27:52 +01:00
Mohamed ABDELLANI
709dfa42bc load fields_to_show from DB when user access the report 2022-11-16 12:08:32 +01:00
Mohamed ABDELLANI
9561140466 create ReportRenderingOptions model 2022-11-16 12:02:47 +01:00
410 changed files with 7456 additions and 9252 deletions

View File

@@ -2,4 +2,3 @@
.gitignore
log/*
tmp/*
node_modules/

11
.gitattributes vendored
View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ on:
pull_request:
env:
DISABLE_KNAPSACK_PRO: false
DISABLE_KNAPSACK: true
TIMEZONE: UTC
COVERAGE: true
RAILS_ENV: test
@@ -17,7 +17,7 @@ permissions:
contents: read
jobs:
knapsack_rspec_controllers:
rspec:
runs-on: ubuntu-20.04
services:
postgres:
@@ -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
@@ -68,161 +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: |
bundle exec rake knapsack_pro:rspec
knapsack_rspec_models:
runs-on: ubuntu-20.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: 16
- 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-20.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: 16
- 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()
@@ -233,233 +83,7 @@ jobs:
retention-days: 7
if-no-files-found: ignore
knapsack_rspec_system_consumer:
runs-on: ubuntu-20.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: 16
- 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-20.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: 16
- 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-20.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: 16
- 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:
test-the-rest:
runs-on: ubuntu-20.04
services:
postgres:
@@ -498,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"]

1
.gitignore vendored
View File

@@ -50,6 +50,7 @@ vendor/bundle/
coverage
/reports/
!/reports/README.md
/spec/components/stories/**/*.stories.json
/public/packs
/public/packs-test

View File

@@ -1,8 +1,6 @@
# Basically, ignore everythings expect app/webpacker/controllers/*.js and app/webpacker/packs/*.js
*.css
*.scss
# Except v2
!/app/webpacker/css/admin/v2/**/*.scss
*.md
*.yml
*.yaml
@@ -12,6 +10,7 @@
babel.config.js
postcss.config.js
.storybook/
/app/assets/
/config/
/coverage/

View File

@@ -440,7 +440,6 @@ Metrics/BlockNesting:
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ClassLength:
Exclude:
- 'app/components/products_table_component.rb'
- 'app/controllers/admin/customers_controller.rb'
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/admin/order_cycles_controller.rb'
@@ -682,7 +681,18 @@ Rails/ActiveRecordOverride:
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/ApplicationController:
Exclude:
- 'engines/dfc_provider/app/controllers/dfc_provider/base_controller.rb'
- 'engines/dfc_provider/app/controllers/dfc_provider/api/base_controller.rb'
# Offense count: 6
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/ApplicationJob:
Exclude:
- 'app/jobs/bulk_invoice_job.rb'
- 'app/jobs/heartbeat_job.rb'
- 'app/jobs/order_cycle_closing_job.rb'
- 'app/jobs/order_cycle_notification_job.rb'
- 'app/jobs/subscription_confirm_job.rb'
- 'app/jobs/subscription_placement_job.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).

7
.storybook/main.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
stories: ['../spec/components/stories/**/*.stories.json'],
addons: [
'@storybook/addon-docs',
'@storybook/addon-controls',
],
};

View 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
View File

@@ -0,0 +1,5 @@
export const parameters = {
server: {
url: `http://localhost:3000/rails/stories`,
},
};

View File

@@ -35,10 +35,7 @@ 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} && \

View File

@@ -28,7 +28,7 @@ For those new to Rails, the following tutorial will help get you up to speed wit
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:
@@ -36,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):
@@ -83,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.

10
Gemfile
View File

@@ -92,6 +92,7 @@ gem 'gmaps4rails'
gem 'mimemagic', '> 0.3.5'
gem 'paper_trail', '~> 12.1.0'
gem 'rack-rewrite'
gem 'rack-ssl', require: 'rack/ssl'
gem 'roadie-rails'
gem 'hiredis'
@@ -117,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'
@@ -134,9 +135,6 @@ gem 'flipper-active_record'
gem 'flipper-ui'
gem "view_component"
gem 'view_component_reflex', '3.1.14.pre9'
gem 'mini_portile2', '~> 2.8'
group :production, :staging do
gem 'ddtrace'
@@ -152,7 +150,7 @@ group :test, :development do
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
@@ -166,6 +164,7 @@ 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
@@ -184,6 +183,7 @@ group :development do
gem 'spring-commands-rspec'
gem 'web-console'
gem "view_component_storybook"
gem 'rack-mini-profiler', '< 3.0.0'
end

View File

@@ -158,16 +158,16 @@ GEM
awesome_nested_set (3.5.0)
activerecord (>= 4.0.0, < 7.1)
aws-eventstream (1.2.0)
aws-partitions (1.701.0)
aws-sdk-core (3.170.0)
aws-partitions (1.651.0)
aws-sdk-core (3.166.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.62.0)
aws-sdk-kms (1.59.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.119.0)
aws-sdk-s3 (1.117.1)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
@@ -177,12 +177,12 @@ GEM
bigdecimal (3.0.2)
bindata (2.4.12)
bindex (0.8.1)
bootsnap (1.16.0)
bootsnap (1.13.0)
msgpack (~> 1.2)
bugsnag (6.25.1)
bugsnag (6.24.2)
concurrent-ruby (~> 1.0)
builder (3.2.4)
bullet (7.0.7)
bullet (7.0.3)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
cable_ready (5.0.0.pre9)
@@ -210,6 +210,7 @@ GEM
rubyzip (>= 1.3.0, < 3)
choice (0.2.0)
chronic (0.10.2)
cliver (0.3.2)
coderay (1.1.3)
coffee-rails (5.0.0)
coffee-script (>= 2.2.0)
@@ -221,30 +222,28 @@ GEM
combine_pdf (1.0.22)
matrix
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.2.0)
concurrent-ruby (1.1.10)
connection_pool (2.3.0)
crack (0.4.5)
rexml
crass (1.0.6)
css_parser (1.11.0)
addressable
cuprite (0.14.3)
capybara (~> 3.0)
ferrum (~> 0.13.0)
cuprite (0.13)
capybara (>= 2.1, < 4)
ferrum (~> 0.11.0)
database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0)
database_cleaner-active_record (2.0.0)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
ddtrace (1.9.0)
debase-ruby_core_source (>= 0.10.16, <= 3.2.0)
libdatadog (~> 1.0.1.1.0)
libddwaf (~> 1.5.1.0.0)
ddtrace (0.54.1)
debase-ruby_core_source (= 0.10.12)
msgpack
debase-ruby_core_source (3.2.0)
debug (1.7.1)
irb (>= 1.5.0)
debase-ruby_core_source (0.10.12)
debug (1.6.3)
irb (>= 1.3.6)
reline (>= 0.3.1)
debugger-linecache (1.2.0)
devise (4.8.1)
@@ -260,7 +259,7 @@ GEM
devise-token_authenticatable (1.1.0)
devise (>= 4.0.0, < 5.0.0)
diff-lcs (1.5.0)
digest (3.1.1)
digest (3.1.0)
docile (1.4.0)
dotenv (2.8.1)
dotenv-rails (2.8.1)
@@ -282,10 +281,10 @@ GEM
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-net_http (3.0.1)
ferrum (0.13)
ferrum (0.11)
addressable (~> 2.5)
cliver (~> 0.3)
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (>= 0.6, < 0.8)
ffaker (2.21.0)
ffi (1.15.5)
@@ -315,7 +314,7 @@ GEM
nokogiri (>= 1.5.11, < 2.0.0)
foreman (0.87.2)
formatador (0.2.5)
fugit (1.8.1)
fugit (1.7.1)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
fuubar (2.5.1)
@@ -326,7 +325,7 @@ GEM
addressable (~> 2.7)
omniauth (>= 1.9, < 3)
openid_connect (~> 1.2)
globalid (1.0.1)
globalid (1.0.0)
activesupport (>= 5.0)
gmaps4rails (2.1.2)
good_migrations (0.2.1)
@@ -351,18 +350,18 @@ GEM
ruby-vips (>= 2.0.17, < 3)
immigrant (0.3.6)
activerecord (>= 3.0)
io-console (0.6.0)
io-console (0.5.11)
ipaddress (0.8.3)
irb (1.6.2)
irb (1.4.2)
reline (>= 0.3.0)
jmespath (1.6.2)
jmespath (1.6.1)
jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-ui-rails (4.2.1)
railties (>= 3.2.16)
json (2.6.3)
json (2.6.2)
json-jwt (1.16.0)
activesupport (>= 4.2)
aes_key_wrap
@@ -376,21 +375,18 @@ GEM
rspec (>= 2.0, < 4.0)
jsonapi-serializer (2.2.0)
activesupport (>= 4.2)
jwt (2.6.0)
knapsack_pro (3.7.0)
jwt (2.5.0)
knapsack (4.0.0)
rake
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.8.1)
launchy (>= 2.2, < 3)
libdatadog (1.0.1.1.0)
libddwaf (1.5.1.0.0)
ffi (~> 1.0)
libv8-node (16.10.0.0)
listen (3.8.0)
libv8-node (15.14.0.1)
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.19.1)
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@@ -406,15 +402,15 @@ GEM
rake
mini_magick (4.11.0)
mini_mime (1.1.2)
mini_portile2 (2.8.1)
mini_racer (0.6.3)
libv8-node (~> 16.10.0.0)
minitest (5.17.0)
mini_portile2 (2.8.0)
mini_racer (0.4.0)
libv8-node (~> 15.14.0.0)
minitest (5.16.3)
monetize (1.12.0)
money (~> 6.12)
money (6.16.0)
i18n (>= 0.6.4, <= 2)
msgpack (1.6.0)
msgpack (1.5.4)
multi_json (1.15.0)
multi_xml (0.6.0)
net-protocol (0.1.3)
@@ -422,7 +418,7 @@ GEM
net-smtp (0.3.2)
net-protocol
nio4r (2.5.8)
nokogiri (1.13.10)
nokogiri (1.13.9)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
oauth2 (1.4.11)
@@ -456,9 +452,9 @@ GEM
activerecord (>= 5.2)
request_store (~> 1.1)
parallel (1.22.1)
paranoia (2.6.1)
paranoia (2.6.0)
activerecord (>= 5.1, < 7.1)
parser (3.2.0.0)
parser (3.1.2.1)
ast (~> 2.4.1)
paypal-sdk-core (0.3.4)
multi_json (~> 1.0)
@@ -472,16 +468,16 @@ GEM
ruby-rc4
ttfunk
pg (1.2.3)
power_assert (2.0.2)
power_assert (2.0.1)
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.0)
puma (6.0.2)
puma (5.6.5)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.1)
rack (2.2.6.2)
racc (1.6.0)
rack (2.2.4)
rack-mini-profiler (2.3.4)
rack (>= 1.2.0)
rack-oauth2 (1.21.3)
@@ -495,6 +491,8 @@ GEM
rack-proxy (0.7.0)
rack
rack-rewrite (1.5.1)
rack-ssl (1.4.1)
rack
rack-test (2.0.2)
rack (>= 1.3)
rack-timeout (0.6.3)
@@ -525,9 +523,9 @@ GEM
activesupport (>= 4.2)
choice (~> 0.2.0)
ruby-graphviz (~> 1.2)
rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1)
rails-i18n (7.0.6)
rails-html-sanitizer (1.4.3)
loofah (~> 2.3)
rails-i18n (7.0.5)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
rails_safe_tasks (1.0.0)
@@ -543,13 +541,13 @@ GEM
activerecord (>= 5.2.4)
activesupport (>= 5.2.4)
i18n
rb-fsevent (0.11.2)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
redcarpet (3.6.0)
redcarpet (3.5.1)
redis (4.8.0)
regexp_parser (2.6.2)
reline (0.3.2)
regexp_parser (2.6.0)
reline (0.3.1)
io-console (~> 0.5)
request_store (1.5.0)
rack (>= 1.4)
@@ -592,29 +590,29 @@ GEM
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-support (3.10.3)
rswag-api (2.8.0)
rswag-api (2.7.0)
railties (>= 3.1, < 7.1)
rswag-specs (2.8.0)
rswag-specs (2.7.0)
activesupport (>= 3.1, < 7.1)
json-schema (>= 2.2, < 4.0)
railties (>= 3.1, < 7.1)
rspec-core (>= 2.14)
rswag-ui (2.8.0)
rswag-ui (2.7.0)
actionpack (>= 3.1, < 7.1)
railties (>= 3.1, < 7.1)
rubocop (1.44.1)
rubocop (1.38.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.24.1, < 2.0)
rubocop-ast (>= 1.23.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.24.1)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.23.0)
parser (>= 3.1.1.0)
rubocop-rails (2.17.4)
rubocop-rails (2.17.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
@@ -637,26 +635,27 @@ GEM
tilt (>= 1.1, < 3)
sd_notify (0.1.1)
semantic_range (3.0.0)
shoulda-matchers (5.3.0)
shoulda-matchers (5.2.0)
activesupport (>= 5.2.0)
sidekiq (6.5.8)
connection_pool (>= 2.2.5, < 3)
rack (~> 2.0)
redis (>= 4.5.0, < 5)
sidekiq-scheduler (5.0.1)
sidekiq-scheduler (4.0.3)
redis (>= 4.2.0)
rufus-scheduler (~> 3.2)
sidekiq (>= 4, < 8)
sidekiq (>= 4, < 7)
tilt (>= 1.4.0)
simplecov (0.22.0)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
simplecov_json_formatter (0.1.3)
spreadsheet_architect (5.0.0)
caxlsx (>= 3.3.0, < 4)
rodf (>= 1.0.0, < 2)
spring (4.1.1)
spring (4.1.0)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
sprockets (3.7.2)
@@ -684,23 +683,24 @@ GEM
railties (>= 5.2)
redis
stringex (2.8.5)
stripe (8.0.0)
stripe (7.1.0)
swd (1.3.0)
activesupport (>= 3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
temple (0.8.2)
test-unit (3.5.7)
test-prof (1.0.11)
test-unit (3.5.5)
power_assert
thor (1.2.1)
thread-local (1.1.0)
tilt (2.0.11)
timecop (0.9.6)
timecop (0.9.5)
timeout (0.3.0)
ttfunk (1.7.0)
tzinfo (2.0.6)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2)
unicode-display_width (2.3.0)
uniform_notifier (1.16.0)
valid_email2 (4.0.4)
activemodel (>= 3.2)
@@ -712,14 +712,12 @@ GEM
activemodel (>= 3.0.0)
public_suffix
vcr (6.1.0)
view_component (2.82.0)
activesupport (>= 5.2.0, < 8.0)
view_component (2.75.0)
activesupport (>= 5.0.0, < 8.0)
concurrent-ruby (~> 1.0)
method_source (~> 1.0)
view_component_reflex (3.1.14.pre9)
rails (>= 5.2, < 8.0)
stimulus_reflex (>= 3.5.0.pre2)
view_component (>= 2.28.0)
view_component_storybook (0.11.1)
view_component (>= 2.36)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.0)
@@ -739,7 +737,6 @@ GEM
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
webrick (1.7.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -747,11 +744,11 @@ GEM
chronic (>= 0.6.3)
wicked_pdf (2.6.3)
activesupport
wkhtmltopdf-binary (0.12.6.6)
wkhtmltopdf-binary (0.12.6.5)
xml-simple (1.1.8)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.6)
zeitwerk (2.6.4)
PLATFORMS
ruby
@@ -821,13 +818,12 @@ DEPENDENCIES
json_spec (~> 1.1.4)
jsonapi-serializer
jwt (~> 2.3)
knapsack_pro
knapsack
letter_opener (>= 1.4.1)
listen
mime-types
mimemagic (> 0.3.5)
mini_portile2 (~> 2.8)
mini_racer
mini_racer (= 0.4.0)
monetize (~> 1.11)
oauth2 (~> 1.4.7)
ofn-qz!
@@ -844,6 +840,7 @@ DEPENDENCIES
puma
rack-mini-profiler (< 3.0.0)
rack-rewrite
rack-ssl
rack-timeout
rails (>= 6.1.4)
rails-controller-testing
@@ -877,12 +874,13 @@ DEPENDENCIES
stimulus_reflex (= 3.5.0.pre9)
stringex (~> 2.8.5)
stripe
test-prof
test-unit (~> 3.5)
timecop
valid_email2
vcr
view_component
view_component_reflex (= 3.1.14.pre9)
view_component_storybook
web!
web-console
webmock
@@ -895,4 +893,4 @@ RUBY VERSION
ruby 3.0.3p157
BUNDLED WITH
2.4.3
2.1.4

View File

@@ -8,3 +8,6 @@ require_relative 'config/application'
Openfoodnetwork::Application.load_tasks
if !ENV['DISABLE_KNAPSACK'] && defined?(Knapsack)
Knapsack.load_tasks
end

View File

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

View File

@@ -3,21 +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 = Orders.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.confirmRefresh = ->
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
@@ -27,21 +22,42 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.supplierFilter = ''
$scope.orderCycleFilter = ''
$scope.quickSearch = ''
$scope.startDate = undefined
$scope.endDate = undefined
event = new CustomEvent('flatpickr:clear')
window.dispatchEvent(event)
$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()
@@ -51,25 +67,31 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$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 = ->
[formattedStartDate, formattedEndDate] = $scope.formatDates($scope.startDate, $scope.endDate)
RequestMonitor.load $scope.orders = Orders.index(
"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]": if formattedStartDate then formattedStartDate else undefined,
"q[completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
"page": $scope.page,
"per_page": $scope.per_page
"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[order_state_not_eq]": "canceled",
"q[order_shipment_state_not_eq]": "shipped",
@@ -77,17 +99,10 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
"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()}")
@@ -218,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
@@ -262,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()

View File

@@ -38,4 +38,3 @@ angular.module('admin.orderCycles')
$scope.removeCoordinatorFee = ($event, index) ->
$event.preventDefault()
OrderCycle.removeCoordinatorFee(index)
$scope.order_cycle_form.$dirty = true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
$( document ).ready(function() {
$("#closeie").click(function() {
$("#ie-warning").hide();
});
})

View File

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

View File

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

View File

@@ -1,15 +0,0 @@
# frozen_string_literal: true
class ConfirmModalComponent < ModalComponent
def initialize(id:, confirm_actions: nil, controllers: nil)
super(id: id, close_button: true)
@confirm_actions = confirm_actions
@controllers = controllers
end
private
def close_button_class
"secondary"
end
end

View File

@@ -1,8 +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
.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 }

View File

@@ -1,4 +0,0 @@
.modal-actions {
display: flex;
justify-content: space-around;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,30 +0,0 @@
# frozen_string_literal: true
class ProductComponent < ViewComponentReflex::Component
def initialize(product:, columns:)
super
@product = product
@image = @product.images[0] if product.images.any?
@columns = columns.map { |c|
{
id: c[:value],
value: column_value(c[:value])
}
}
end
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(', ')
end
end
end

View File

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

View File

@@ -1,152 +0,0 @@
# frozen_string_literal: true
class ProductsTableComponent < ViewComponentReflex::Component
include Pagy::Backend
SORTABLE_COLUMNS = ["name"].freeze
SELECTABLE_COMUMNS = [{ 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" }].sort { |a, b|
a[:label] <=> b[:label]
}.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_COMUMNS
@columns_selected = ["price", "unit"]
@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
def before_render
fetch_products
refresh_columns
end
def search_term
@search_term = element.dataset['value']
end
def toggle_column
column = element.dataset['value']
@columns_selected = if @columns_selected.include?(column)
@columns_selected - [column]
else
@columns_selected + [column]
end
end
def click_sort
@sort = { column: element.dataset['sort-value'],
direction: element.dataset['sort-direction'] == "asc" ? "desc" : "asc" }
end
def toggle_per_page
selected = element.dataset['value'].to_i
@per_page_selected = [selected] if PER_PAGE_VALUE.include?(selected)
end
def toggle_category
category_clicked = element.dataset['value']
@categories_selected = toggle_selector_with_filter(category_clicked, @categories_selected)
end
def toggle_producer
producer_clicked = element.dataset['value']
@producers_selected = toggle_selector_with_filter(producer_clicked, @producers_selected)
end
def change_page
page = element.dataset['page'].to_i
@page = page if page > 0
end
private
def refresh_columns
@columns = @columns_selected.map { |column|
{ label: I18n.t("admin.products_page.columns.#{column}"), value: column,
sortable: SORTABLE_COLUMNS.include?(column) }
}.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

View File

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

View File

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

View File

@@ -1,9 +0,0 @@
# frozen_string_literal: true
class SearchInputComponent < ViewComponentReflex::Component
def initialize(value: nil, data: {})
super
@value = value
@data = data
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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%;
}
}
}
}

View File

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

View File

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

View File

@@ -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: "";
}
}
}
}

View File

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

View File

@@ -116,7 +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

View File

@@ -1,7 +0,0 @@
# frozen_string_literal: true
module Admin
class ProductsController < Spree::Admin::BaseController
def index; end
end
end

View File

@@ -19,22 +19,22 @@ 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 report_format.present?
export_report
else
show_report
render_report
end
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
render "show"
end
@@ -43,29 +43,13 @@ module Admin
@report_type = report_type
@report_subtypes = report_subtypes
@report_subtype = report_subtype
@report_title = report_title
@report_title = if report_subtype
report_subtype_title
else
I18n.t(:name, scope: [:admin, :reports, @report_type])
end
@rendering_options = rendering_options
@table = render_report_as(:html) if render_data?
@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)
job = ReportJob.new
JobProcessor.perform_forked(
job,
report_class, spree_current_user, params, format
)
# This result has been rendered by Rails in safe mode already.
job.result.html_safe # rubocop:disable Rails/OutputSafety
else
@report.render_as(format)
end
end
end
end

View File

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

View File

@@ -1,7 +1,6 @@
# frozen_string_literal: true
module OrderStockCheck
include CablecarResponses
extend ActiveSupport::Concern
def valid_order_line_items?
@@ -30,9 +29,6 @@ module OrderStockCheck
flash[:info] = I18n.t('order_cycle_closed')
respond_to do |format|
format.cable_ready {
render status: :see_other, operations: cable_car.redirect_to(url: main_app.shop_path)
}
format.json { render json: { path: main_app.shop_path }, status: :see_other }
format.html { redirect_to main_app.shop_path, status: :see_other }
end

View File

@@ -39,14 +39,6 @@ module ReportsActions
params[:report_subtype] || report_subtypes_codes.first
end
def report_title
if report_subtype
report_subtype_title
else
I18n.t(:name, scope: [:admin, :reports, report_type])
end
end
def report_subtype_title
report_subtypes.select { |_name, key| key.to_sym == report_subtype.to_sym }.first[0]
end
@@ -76,31 +68,21 @@ module ReportsActions
user: spree_current_user,
report_type: report_type,
report_subtype: report_subtype
).first_or_create do |report_rendering_options|
report_rendering_options.options = {
fields_to_show: if request.get?
@report.columns.keys -
@report.fields_to_hide
else
params[:fields_to_show]
end,
display_summary_row: request.get?,
display_header_row: false
}
).first_or_create do |new_instance|
new_instance.options[:fields_to_show] = if @report.present?
@report.columns.keys - @report.fields_to_hide
else
[]
end
new_instance.options[:display_summary_row] = request.get? || params[:display_summary_row].present?
params[:display_header_row] = params[:display_header_row].present?
end
update_rendering_options
if params[:fields_to_show].present?
@rendering_options.options[:fields_to_show] = params[:fields_to_show]
end
@rendering_options.options[:display_summary_row] = params[:display_summary_row].present?
@rendering_options.options[:display_header_row] = params[:display_header_row].present?
@rendering_options.save
@rendering_options
end
def update_rendering_options
return unless request.post?
@rendering_options.update(
options: {
fields_to_show: params[:fields_to_show],
display_summary_row: params[:display_summary_row].present?,
display_header_row: params[:display_header_row].present?
}
)
end
end

View File

@@ -31,12 +31,15 @@ class SplitCheckoutController < ::BaseController
if confirm_order || update_order
return if performed?
check_payments_adjustments
clear_invalid_payments
advance_order_state
redirect_to_step
else
render_error
flash.now[:error] ||= I18n.t('split_checkout.errors.global')
render status: :unprocessable_entity, operations: cable_car.
replace("#checkout", partial("split_checkout/checkout")).
replace("#flashes", partial("shared/flashes", locals: { flashes: flash }))
end
rescue Spree::Core::GatewayError => e
flash[:error] = I18n.t(:spree_gateway_error_flash_for_checkout, error: e.message)
@@ -46,25 +49,10 @@ class SplitCheckoutController < ::BaseController
private
def render_error
flash.now[:error] ||= I18n.t(
'split_checkout.errors.saving_failed',
messages: @order.errors.full_messages.to_sentence
)
render status: :unprocessable_entity, operations: cable_car.
replace("#checkout", partial("split_checkout/checkout")).
replace("#flashes", partial("shared/flashes", locals: { flashes: flash }))
end
def flash_error_when_no_shipping_method_available
flash[:error] = I18n.t('split_checkout.errors.no_shipping_methods_available')
end
def check_payments_adjustments
@order.payments.each(&:ensure_correct_adjustment)
end
def clear_invalid_payments
@order.payments.with_state(:invalid).delete_all
end
@@ -77,7 +65,6 @@ class SplitCheckoutController < ::BaseController
return true if redirect_to_payment_gateway
@order.process_payments!
@order.confirm!
order_completion_reset @order
end

View File

@@ -6,6 +6,7 @@ module Spree
def edit
@preferences_general = [:site_name, :default_seo_title, :default_meta_keywords,
:default_meta_description, :site_url]
@preferences_security = [:allow_ssl_in_production, :allow_ssl_in_staging]
@preferences_currency = [:display_currency, :hide_cents]
end

View File

@@ -11,7 +11,6 @@ module CheckoutHelper
def checkout_adjustments_for(order, opts = {})
exclude = opts[:exclude] || {}
reject_zero_amount = opts.fetch(:reject_zero_amount, true)
adjustments = order.all_adjustments.eligible.to_a
@@ -33,10 +32,6 @@ module CheckoutHelper
}
end
if reject_zero_amount
adjustments.reject! { |a| a.amount == 0 }
end
adjustments
end

View File

@@ -26,12 +26,6 @@ module ReportsHelper
end.uniq
end
def customer_email_options(order_customers)
order_customers.map do |customer|
[customer&.email, customer&.id]
end
end
def currency_symbol
Spree::Money.currency_symbol
end

View File

@@ -34,7 +34,6 @@ module Spree
link = link_to_with_icon(options[:icon], titleized_label, destination_url)
css_classes << 'tab-with-icon'
else
titleized_label = raw("<span class='text'>#{titleized_label}</span>")
link = link_to(titleized_label, destination_url)
end

View File

@@ -133,7 +133,7 @@ module Spree
event_label = I18n.t("cancel", scope: "actions")
button_link_to(event_label,
fire_admin_order_url(@order, e: "cancel"),
method: :put, icon: "icon-remove", form_id: "cancel_order_form")
method: :put, icon: "icon-cancel", form_id: "cancel_order_form")
end
def resume_event_link

View File

@@ -12,16 +12,6 @@ module TaxHelper
end
end
def display_line_items_taxes(line_item, display_zero: true)
if line_item.included_tax.positive?
Spree::Money.new(line_item.included_tax, currency: line_item.currency)
elsif line_item.added_tax.positive?
Spree::Money.new(line_item.added_tax, currency: line_item.currency)
elsif display_zero
Spree::Money.new(0.00, currency: line_item.currency)
end
end
def display_total_with_tax(taxable)
total = taxable.amount + taxable.additional_tax_total
Spree::Money.new(total, currency: taxable.currency)

View File

@@ -1,10 +0,0 @@
# frozen_string_literal: true
# Rails standard class for common job code.
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
class BulkInvoiceJob < ApplicationJob
class BulkInvoiceJob < ActiveJob::Base
def perform(order_ids, filepath)
pdf = CombinePDF.new

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
class HeartbeatJob < ApplicationJob
class HeartbeatJob < ActiveJob::Base
def perform
Spree::Config.last_job_queue_heartbeat_at = Time.now.in_time_zone
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
class OrderCycleClosingJob < ApplicationJob
class OrderCycleClosingJob < ActiveJob::Base
def perform
return if recently_closed_order_cycles.empty?

View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Delivers an email with a report of the order cycle to each of its suppliers
class OrderCycleNotificationJob < ApplicationJob
class OrderCycleNotificationJob < ActiveJob::Base
def perform(order_cycle_id)
order_cycle = OrderCycle.find(order_cycle_id)
order_cycle.suppliers.each do |supplier|

View File

@@ -1,34 +0,0 @@
# frozen_string_literal: true
# Renders a report and saves it to a temporary file.
class ReportJob < ActiveJob::Base
def perform(report_class, user, params, format)
report = report_class.new(user, params, render: true)
result = report.render_as(format)
write(result)
end
def done?
@done ||= File.file?(filename)
end
def result
@result ||= read_result
end
private
def write(result)
File.write(filename, result)
end
def read_result
File.read(filename)
ensure
File.unlink(filename)
end
def filename
Rails.root.join("tmp/report-#{job_id}")
end
end

View File

@@ -3,7 +3,7 @@
require 'order_management/subscriptions/summarizer'
# Confirms orders of unconfirmed proxy orders in recently closed Order Cycles
class SubscriptionConfirmJob < ApplicationJob
class SubscriptionConfirmJob < ActiveJob::Base
def perform
confirm_proxy_orders!
end

View File

@@ -2,7 +2,7 @@
require 'order_management/subscriptions/summarizer'
class SubscriptionPlacementJob < ApplicationJob
class SubscriptionPlacementJob < ActiveJob::Base
def perform
proxy_orders.each do |proxy_order|
place_order_for(proxy_order)

View File

@@ -7,6 +7,7 @@ module Calculator
extend Spree::LocalizedNumber
preference :amount, :decimal, default: 0
preference :currency, :string, default: Spree::Config[:currency]
localize_number :preferred_amount

View File

@@ -9,6 +9,7 @@ module Calculator
preference :first_item, :decimal, default: 0.0
preference :additional_item, :decimal, default: 0.0
preference :max_items, :integer, default: 0
preference :currency, :string, default: Spree::Config[:currency]
localize_number :preferred_first_item,
:preferred_additional_item

View File

@@ -1,13 +0,0 @@
# frozen_string_literal: false
module Calculator
class None < Spree::Calculator
def self.description
I18n.t(:none)
end
def compute(_object = nil)
0
end
end
end

View File

@@ -7,6 +7,7 @@ module Calculator
extend Spree::LocalizedNumber
preference :amount, :decimal, default: 0
preference :currency, :string, default: Spree::Config[:currency]
localize_number :preferred_amount

View File

@@ -9,6 +9,7 @@ module Calculator
preference :minimal_amount, :decimal, default: 0
preference :normal_amount, :decimal, default: 0
preference :discount_amount, :decimal, default: 0
preference :currency, :string, default: Spree::Config[:currency]
localize_number :preferred_minimal_amount,
:preferred_normal_amount,

View File

@@ -6,16 +6,15 @@ class Enterprise < ApplicationRecord
# The next Rails version will have named variants but we need to store them
# ourselves for now.
LOGO_SIZES = {
thumb: { gravity: "Center", resize: "100x100^", crop: '100x100+0+0' },
small: { gravity: "Center", resize: "180x180^", crop: '180x180+0+0' },
medium: { gravity: "Center", resize: "300x300^", crop: '300x300+0+0' },
thumb: { resize_to_limit: [100, 100] },
small: { resize_to_limit: [180, 180] },
medium: { resize_to_limit: [300, 300] },
}.freeze
PROMO_IMAGE_SIZES = {
thumb: { resize_to_limit: [100, 100] },
medium: { resize_to_fill: [720, 156] },
large: { resize_to_fill: [1200, 260] },
}.freeze
VALID_INSTAGRAM_REGEX = %r{\A[a-zA-Z0-9._]{1,30}([^/-]*)\z}
searchable_attributes :sells, :is_primary_producer
searchable_associations :properties
@@ -100,7 +99,6 @@ class Enterprise < ApplicationRecord
validate :shopfront_taxons
validate :shopfront_producers
validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? }
validates :instagram, format: { with: VALID_INSTAGRAM_REGEX, message: Spree.t('errors.messages.invalid_instagram_url') }, allow_blank: true
before_validation :initialize_permalink, if: lambda { permalink.nil? }
before_validation :set_unused_address_fields
@@ -458,7 +456,7 @@ class Enterprise < ApplicationRecord
end
def correct_instagram_url(url)
url && strip_url(url.downcase).sub(%r{www.instagram.com/}, '').sub(%r{instagram.com/}, '').delete("@")
url && strip_url(url).sub(%r{www.instagram.com/}, '').delete("@")
end
def correct_twitter_url(url)

View File

@@ -211,7 +211,7 @@ module ProductImport
reference_entry = all_entries_for_product(entry).first
return if entry.variant_unit_name.to_s == reference_entry.variant_unit_name.to_s
mark_as_values_must_be_same(entry, "variant_unit_name")
mark_as_not_updatable(entry, "variant_unit_name")
end
def producer_validation(entry)
@@ -425,11 +425,6 @@ module ProductImport
error: I18n.t("admin.product_import.model.not_updatable"))
end
def mark_as_values_must_be_same(entry, attribute)
mark_as_invalid(entry, attribute: attribute,
error: I18n.t("admin.product_import.model.values_must_be_same"))
end
def import_into_inventory?
@import_settings.dig(:settings, 'import_into') == 'inventories'
end

View File

@@ -1,5 +1,3 @@
# frozen_string_literal: true
class ReportRenderingOptions < ApplicationRecord
belongs_to :user, class_name: "Spree::User"
serialize :options, Hash

View File

@@ -33,6 +33,8 @@ module Spree
preference :allow_backorder_shipping, :boolean, default: false
preference :allow_checkout_on_gateway_error, :boolean, default: false
preference :allow_guest_checkout, :boolean, default: true
preference :allow_ssl_in_production, :boolean, default: true
preference :allow_ssl_in_staging, :boolean, default: true
# Replace with the name of a zone if you would like to limit the countries
preference :checkout_zone, :string, default: nil
preference :currency, :string, default: "USD"

View File

@@ -186,10 +186,6 @@ module Spree
adjustments.tax.inclusive.sum(:amount)
end
def added_tax
adjustments.tax.additional.sum(:amount)
end
def tax_rates
product.tax_category&.tax_rates || []
end

View File

@@ -13,7 +13,7 @@ module Spree
include SetUnusedAddressFields
searchable_attributes :number, :state, :shipment_state, :payment_state, :distributor_id,
:order_cycle_id, :email, :total, :customer_id
:order_cycle_id, :email, :total
searchable_associations :shipping_method, :bill_address
searchable_scopes :complete, :incomplete

View File

@@ -113,7 +113,7 @@ module Spree
end
def init
self.calculator ||= ::Calculator::None.new
self.calculator ||= ::Calculator::FlatRate.new(preferred_amount: 0)
end
def has_distributor?(distributor)

View File

@@ -32,8 +32,6 @@ module Spree
validate :at_least_one_shipping_category
validates :display_on, inclusion: { in: DISPLAY_ON_OPTIONS.values }, allow_nil: true
after_initialize :init
after_save :touch_distributors
scope :managed_by, lambda { |user|
@@ -69,6 +67,10 @@ module Spree
tracking_url.gsub(/:tracking/, tracking) unless tracking.blank? || tracking_url.blank?
end
def self.calculators
spree_calculators.__send__ model_name_without_spree_namespace
end
# Some shipping methods are only meant to be set via backend
def frontend?
display_on != "back_end"
@@ -108,10 +110,6 @@ module Spree
where(display_on: [nil, ""])
end
def init
self.calculator ||= ::Calculator::None.new if new_record?
end
private
def no_active_or_upcoming_order_cycle_distributors_with_only_one_shipping_method?

View File

@@ -37,7 +37,7 @@ module Spree
foreign_key: :owner_id, inverse_of: :owner
has_many :customers
has_many :credit_cards
has_many :report_rendering_options, class_name: "::ReportRenderingOptions", dependent: :destroy
accepts_nested_attributes_for :enterprise_roles, allow_destroy: true
accepts_nested_attributes_for :bill_address

View File

@@ -1,20 +0,0 @@
# frozen_string_literal: true
class BatchTaggableTagsQuery
def self.call(taggables)
::ActsAsTaggableOn::Tag.
joins(:taggings).
includes(:taggings).
where(taggings:
{
taggable_type: taggables.model.to_s,
taggable_id: taggables,
context: 'tags'
}).order("tags.name").each_with_object({}) do |tag, indexed_hash|
tag.taggings.each do |tagging|
indexed_hash[tagging.taggable_id] ||= []
indexed_hash[tagging.taggable_id] << tag.name
end
end
end
end

View File

@@ -16,11 +16,4 @@ class ApplicationReflex < StimulusReflex::Reflex
#
# For code examples, considerations and caveats, see:
# https://docs.stimulusreflex.com/rtfm/patterns#internationalization
include CanCan::ControllerAdditions
delegate :current_user, to: :connection
def current_ability
Spree::Ability.new(current_user)
end
end

View File

@@ -1,38 +0,0 @@
# frozen_string_literal: true
class ExampleReflex < ApplicationReflex
# Add Reflex methods in this file.
#
# All Reflex instances include CableReady::Broadcaster and expose the following properties:
#
# - connection - the ActionCable connection
# - channel - the ActionCable channel
# - request - an ActionDispatch::Request proxy for the socket connection
# - session - the ActionDispatch::Session store for the current visitor
# - flash - the ActionDispatch::Flash::FlashHash for the current request
# - url - the URL of the page that triggered the reflex
# - params - parameters from the element's closest form (if any)
# - element - a Hash like object that represents the HTML element that triggered the reflex
# - signed - use a signed Global ID to map dataset attribute to a model
# eg. element.signed[:foo]
# - unsigned - use an unsigned Global ID to map dataset attribute to a model
# eg. element.unsigned[:foo]
# - cable_ready - a special cable_ready that can broadcast to the current visitor
# (no brackets needed)
# - reflex_id - a UUIDv4 that uniquely identies each Reflex
# - tab_id - a UUIDv4 that uniquely identifies the browser tab
#
# Example:
#
# before_reflex do
# # throw :abort # this will prevent the Reflex from continuing
# # learn more about callbacks at https://docs.stimulusreflex.com/rtfm/lifecycle
# end
#
# def example(argument=true)
# # Your logic here...
# # Any declared instance variables will be made available to the Rails controller and view.
# end
#
# Learn more at: https://docs.stimulusreflex.com/rtfm/reflex-classes
end

View File

@@ -1,13 +0,0 @@
# frozen_string_literal: true
class ResendConfirmationEmailReflex < ApplicationReflex
def confirm(order_ids)
Spree::Order.where(id: order_ids).find_each do |o|
Spree::OrderMailer.confirm_email_for_customer(o.id, true).deliver_later if can? :resend, o
end
flash[:success] = I18n.t("admin.resend_confirmation_emails_feedback", count: order_ids.count)
cable_ready.dispatch_event(name: "modal:close")
morph "#flashes", render(partial: "shared/flashes", locals: { flashes: flash })
end
end

View File

@@ -42,18 +42,12 @@ module Api
.visible_variants_for_outgoing_exchanges_to(object.receiver)
end
def preloaded_tag_list
return object.tag_list unless options[:preloaded_tags]
options.dig(:preloaded_tags, object.id) || []
end
def tag_list
preloaded_tag_list.join(",")
object.tag_list.join(",")
end
def tags
preloaded_tag_list.map { |tag| { text: tag } }
object.tag_list.map{ |t| { text: t } }
end
end
end

View File

@@ -34,8 +34,7 @@ module Api
ActiveModel::ArraySerializer.
new(scoped_exchanges, each_serializer: Api::Admin::ExchangeSerializer,
current_user: options[:current_user],
preloaded_tags: BatchTaggableTagsQuery.call(scoped_exchanges))
current_user: options[:current_user])
end
def editable_variants_for_incoming_exchanges

View File

@@ -1,29 +0,0 @@
# frozen_string_literal: true
# Forks into a separate process to contain memory usage and timeout errors.
class JobProcessor
def self.perform_forked(job, *args)
# Reports should abort when puma threads are killed to avoid wasting
# resources. Nobody would be collecting the result. We still need to
# implement a way to email or download reports later.
timeout = ENV.fetch("RACK_TIMEOUT_WAIT_TIMEOUT", "30").to_i
child = fork do
Process.setproctitle("Job worker #{job.job_id}")
Timeout.timeout(timeout) do
job.perform(*args)
end
# Exit is not a good idea within a Rails process but Rubocop doesn't know
# that we are in a forked process.
exit # rubocop:disable Rails/Exit
end
# Wait for all forked child processes to exit
Process.waitall
ensure
# If this Puma thread is interrupted then we need to detach the child
# process to avoid it becoming a zombie.
Process.detach(child)
end
end

View File

@@ -30,14 +30,8 @@ class OrderAvailablePaymentMethods
distributor.payment_methods
else
distributor.payment_methods.where(
id: available_distributor_payment_methods_ids
id: order_cycle.distributor_payment_methods.select(:payment_method_id)
)
end.available.select(&:configured?)
end
def available_distributor_payment_methods_ids
order_cycle.distributor_payment_methods
.where(distributor_id: distributor.id)
.select(:payment_method_id)
end
end

View File

@@ -30,14 +30,8 @@ class OrderAvailableShippingMethods
distributor.shipping_methods
else
distributor.shipping_methods.where(
id: available_distributor_shipping_methods_ids
id: order_cycle.distributor_shipping_methods.select(:shipping_method_id)
)
end.frontend.to_a
end
def available_distributor_shipping_methods_ids
order_cycle.distributor_shipping_methods
.where(distributor_id: distributor.id)
.select(:shipping_method_id)
end
end

View File

@@ -4,7 +4,7 @@ module PermittedAttributes
class Calculator
def self.attributes
[
:id, :preferred_amount, :preferred_flat_percent,
:id, :preferred_currency, :preferred_amount, :preferred_flat_percent,
:preferred_minimal_amount, :preferred_normal_amount, :preferred_discount_amount,
:preferred_unit_from_list, :preferred_per_unit, :preferred_first_item,
:preferred_additional_item, :preferred_max_items

View File

@@ -56,7 +56,7 @@ module VariantUnits
def option_value_value_unit_scaled
unit_scale, unit_name = scale_for_unit_value
value = (@variant.unit_value / unit_scale).to_d.truncate(2)
value = BigDecimal(@variant.unit_value / unit_scale, 6)
[value, unit_name]
end

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