Compare commits

..

1 Commits

Author SHA1 Message Date
Matt-Yorkley
0262dcd11b Rescue ImageMagick errors 2023-08-02 12:07:16 +10:00
896 changed files with 9331 additions and 18157 deletions

8
.env
View File

@@ -52,8 +52,6 @@ SMTP_PASSWORD="f00d"
# see="https://developers.google.com/maps/documentation/javascript/get-api-key
# GOOGLE_MAPS_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# see https://developers.google.com/maps/documentation/javascript/localization#Region
# GOOGLE_MAPS_REGION="XX"
# Stripe details for instance account
# Find these under 'Developers' -> 'API keys' in your Stripe account dashboard.
@@ -63,9 +61,3 @@ SMTP_PASSWORD="f00d"
# STRIPE_INSTANCE_PUBLISHABLE_KEY="pk_test_xxxx" # This can be a test key or a live key
# STRIPE_CLIENT_ID="ca_xxxx" # This can be a development ID or a production ID
# STRIPE_ENDPOINT_SECRET="whsec_xxxx"
# New relic settings
# see: https://one.eu.newrelic.com/admin-portal/, Administration > API keys to get the license key
# NEW_RELIC_AGENT_ENABLED=true
# NEW_RELIC_APP_NAME="Open Food Network"
# NEW_RELIC_LICENSE_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

View File

@@ -10,29 +10,16 @@ assignees: ''
## 1. Preparation on Thursday
- [ ] Merge pull requests in the [Ready To Go] column
- [ ] Include translations
<details><summary>Command line instructions:</summary>
<pre>
<code>
git checkout master
git pull upstream master
tx pull --force
git commit -a -m "Update all locales with the latest Transifex translations"
git push upstream master
</code>
</pre>
</details>
- [ ] Create a tag: `git push upstream HEAD:refs/tags/vX.Y.Z`
- [ ] Include translations: `tx pull --force`
- [ ] [Draft new release]. Look at previous [releases] for inspiration.
- Select new release tag
- _Generate release notes_ and check to ensure all items are arranged in the right category.
- [ ] Notify [#instance-managers] of both user-facing :eyes: and :warning: API changes.
- [ ] Notify [#instance-managers] of user-facing changes.
## 2. Testing
- [ ] [Find build] of the release commit and copy it below.
- [ ] Move this issue to Test Ready.
- [ ] Notify `@testers` in [#testing].
- [ ] Test build: [Deploy to Staging] with release tag.
- [ ] Test build: <!-- paste build link here, e.g. https://semaphore...builds/1234 -->
## 3. Finish on Tuesday
@@ -42,7 +29,7 @@ assignees: ''
<pre>
cd ofn-install
git pull
ansible-playbook --limit all-prod --extra-vars "git_version=vX.Y.Z" playbooks/deploy.yml
ansible-playbook --limit all-prod --extra-vars "git_version=vx.y.z" playbooks/deploy.yml
</pre>
</details>
- [ ] Notify [#instance-managers]:
@@ -53,9 +40,9 @@ The full process is described at https://github.com/openfoodfoundation/openfoodn
[Ready To Go]: #zenhub
[Transifex pull request]: https://github.com/openfoodfoundation/openfoodnetwork/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+head%3Atransifex
[Draft new release]: https://github.com/openfoodfoundation/openfoodnetwork/releases/new?tag=v&title=v+Code+Name&body=Congrats%0A%0ADescription%0A%0A
[Draft new release]: https://github.com/openfoodfoundation/openfoodnetwork/releases/new?tag=v&title=v+Code+Name&body=Congrats%0A%0ADescription%0A%0A%23%23+User+facing+changes+:eyes:%0A%0A%0A%23%23%23+Experimental+features+for+testing+:sunglasses:%0A%0A%0A%23%23+Technical+changes+:wrench:%0A%0A
[releases]: https://github.com/openfoodfoundation/openfoodnetwork/releases
[#instance-managers]: https://app.slack.com/client/T02G54U79/CG7NJ966B
[#testing]: https://openfoodnetwork.slack.com/app_redirect?channel=C02TZ6X00
[Deploy to Staging]: https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/stage.yml
[Find build]: https://semaphoreci.com/openfoodfoundation/openfoodnetwork-2/branches/master
[#global-community]: https://app.slack.com/client/T02G54U79/C59ADD8F2

View File

@@ -20,12 +20,7 @@
<!-- Please select one for your PR and delete the other. -->
Changelog Category (reviewers may add a label for the release notes):
- [x] User facing changes
- [ ] API changes (V0, V1, DFC or Webhook)
- [ ] Technical changes only
- [ ] Feature toggled
Changelog Category: User facing changes | Technical changes
<!-- Choose a pull request title above which explains your change to a
a user of the Open Food Network app. -->

23
.github/release.yml vendored
View File

@@ -1,23 +0,0 @@
changelog:
categories:
- title: "User-facing changes 👀"
labels:
- '*'
exclude:
labels:
- api changes
- dependencies
- feature toggled
- technical changes only
- title: "API changes ⚠️"
labels:
- api changes
- title: "Features under construction 🚧"
labels:
- feature toggled
- title: "Technical changes 🛠️"
labels:
- technical changes only
- title: "Dependencies 📦"
labels:
- dependencies

View File

@@ -17,7 +17,7 @@ permissions:
contents: read
jobs:
controllers:
knapsack_rspec_controllers:
runs-on: ubuntu-22.04
services:
postgres:
@@ -85,7 +85,7 @@ jobs:
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
bundle exec rake knapsack_pro:rspec
models:
knapsack_rspec_models:
runs-on: ubuntu-22.04
services:
postgres:
@@ -106,10 +106,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [5]
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]
ci_node_index: [0, 1, 2, 3, 4, 5, 6]
steps:
- uses: actions/checkout@v3
@@ -154,7 +154,7 @@ jobs:
run: |
bundle exec rake knapsack_pro:rspec
system_admin:
knapsack_rspec_system_admin:
runs-on: ubuntu-22.04
services:
postgres:
@@ -175,10 +175,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [13]
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, 10, 11, 12]
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
- uses: actions/checkout@v3
@@ -232,7 +232,7 @@ jobs:
retention-days: 7
if-no-files-found: ignore
system_consumer:
knapsack_rspec_system_consumer:
runs-on: ubuntu-22.04
services:
postgres:
@@ -253,10 +253,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [12]
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, 10, 11]
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
- uses: actions/checkout@v3
@@ -310,7 +310,7 @@ jobs:
retention-days: 7
if-no-files-found: ignore
engines:
knapsack_rspec_engines:
runs-on: ubuntu-22.04
services:
postgres:
@@ -331,10 +331,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [2]
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]
ci_node_index: [0, 1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
@@ -388,7 +388,7 @@ jobs:
retention-days: 7
if-no-files-found: ignore
test_the_rest:
knapsack_rspec_test_the_rest:
runs-on: ubuntu-22.04
services:
postgres:

View File

@@ -28,7 +28,7 @@ jobs:
with:
mapi-token: ${{ secrets.MAPI_TOKEN }}
api-url: http://localhost:3000
api-spec: swagger/v1.yaml
api-spec: swagger/v1/swagger.yaml
target: openfoodfoundation/openfoodnetwork
duration: 1min
sarif-report: mapi.sarif

View File

@@ -13,10 +13,6 @@ on:
- staging.openfoodnetwork.org.uk
- staging.openfoodnetwork.org.au
- staging.coopcircuits.fr
commit_ref:
description: "Commit Reference"
type: string
required: false
jobs:
deploy_pr:
@@ -63,4 +59,4 @@ jobs:
- name: Deploy to Staging
if: success()
run: |
ssh ofn-deploy@${{ inputs.server }} -o LogLevel=ERROR "$GITHUB_REF_NAME ${{ inputs.commit_ref || github.sha }}"
ssh ofn-deploy@${{ inputs.server }} -o LogLevel=ERROR "$GITHUB_REF_NAME $GITHUB_SHA"

View File

@@ -13,8 +13,7 @@ postcss.config.js
# SCSS
# Enabled: most of admin
/app/webpacker/css/admin/globals/mixins.scss
/app/webpacker/css/admin/globals/variables.scss
/app/webpacker/css/admin/globals/
/app/webpacker/css/admin/shared/
/app/webpacker/css/admin_v3/globals/variables.scss
/app/webpacker/css/darkswarm/

View File

@@ -39,7 +39,6 @@ Metrics/BlockLength:
"put",
"resource",
"resources",
"response",
"scenario",
"shared_examples",
"shared_examples_for",
@@ -54,13 +53,6 @@ Rails/ApplicationRecord:
# Migrations should not contain application code:
- "db/migrate/*.rb"
# Allow many-to-many associations without explicit model.
# - It avoids the additional code of a model class.
# - It simplifies the declaration of the association.
# - Rails may know that there are no callbacks associated.
Rails/HasAndBelongsToMany:
Enabled: false
Rails/SkipsModelValidations:
AllowedMethods:
- "touch"

File diff suppressed because it is too large Load Diff

View File

@@ -103,8 +103,8 @@ gem 'redis', '>= 4.0', require: ['redis', 'redis/connection/hiredis']
gem 'sidekiq'
gem 'sidekiq-scheduler'
gem "cable_ready", "5.0.1"
gem "stimulus_reflex", "3.5.0.rc3"
gem "cable_ready", "5.0.0.rc2"
gem "stimulus_reflex", "3.5.0.rc2"
gem 'combine_pdf'
gem 'wicked_pdf'
@@ -138,8 +138,6 @@ gem 'mini_portile2', '~> 2.8'
gem "faraday"
gem "private_address_check"
gem 'newrelic_rpm'
group :production, :staging do
gem 'sd_notify' # For better Systemd process management. Used by Puma.
end
@@ -159,7 +157,6 @@ group :test, :development do
gem 'rspec-retry', require: false
gem 'rswag-specs'
gem 'shoulda-matchers'
gem 'stimulus_reflex_testing'
gem 'timecop'
end

View File

@@ -41,49 +41,49 @@ GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.1.0)
actioncable (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
actioncable (7.0.6)
actionpack (= 7.0.6)
activesupport (= 7.0.6)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
actionmailbox (7.0.6)
actionpack (= 7.0.6)
activejob (= 7.0.6)
activerecord (= 7.0.6)
activestorage (= 7.0.6)
activesupport (= 7.0.6)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.8)
actionpack (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activesupport (= 7.0.8)
actionmailer (7.0.6)
actionpack (= 7.0.6)
actionview (= 7.0.6)
activejob (= 7.0.6)
activesupport (= 7.0.6)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.8)
actionview (= 7.0.8)
activesupport (= 7.0.8)
actionpack (7.0.6)
actionview (= 7.0.6)
activesupport (= 7.0.6)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actionpack-action_caching (1.2.2)
actionpack (>= 4.0.0)
actiontext (7.0.8)
actionpack (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
actiontext (7.0.6)
actionpack (= 7.0.6)
activerecord (= 7.0.6)
activestorage (= 7.0.6)
activesupport (= 7.0.6)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.8)
activesupport (= 7.0.8)
actionview (7.0.6)
activesupport (= 7.0.6)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@@ -95,38 +95,37 @@ GEM
activemodel (>= 5.2.0)
activestorage (>= 5.2.0)
activesupport (>= 5.2.0)
activejob (7.0.8)
activesupport (= 7.0.8)
activejob (7.0.6)
activesupport (= 7.0.6)
globalid (>= 0.3.6)
activemerchant (1.123.0)
activesupport (>= 4.2)
builder (>= 2.1.2, < 4.0.0)
i18n (>= 0.6.9)
nokogiri (~> 1.4)
activemodel (7.0.8)
activesupport (= 7.0.8)
activerecord (7.0.8)
activemodel (= 7.0.8)
activesupport (= 7.0.8)
activerecord-import (1.5.0)
activemodel (7.0.6)
activesupport (= 7.0.6)
activerecord (7.0.6)
activemodel (= 7.0.6)
activesupport (= 7.0.6)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activerecord-postgresql-adapter (0.0.1)
pg
activerecord-session_store (2.1.0)
actionpack (>= 6.1)
activerecord (>= 6.1)
cgi (>= 0.3.6)
activerecord-session_store (2.0.0)
actionpack (>= 5.2.4.1)
activerecord (>= 5.2.4.1)
multi_json (~> 1.11, >= 1.11.2)
rack (>= 2.0.8, < 4)
railties (>= 6.1)
activestorage (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activesupport (= 7.0.8)
rack (>= 2.0.8, < 3)
railties (>= 5.2.4.1)
activestorage (7.0.6)
actionpack (= 7.0.6)
activejob (= 7.0.6)
activerecord (= 7.0.6)
activesupport (= 7.0.6)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.8)
activesupport (7.0.6)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -155,8 +154,8 @@ GEM
awesome_nested_set (3.5.0)
activerecord (>= 4.0.0, < 7.1)
aws-eventstream (1.2.0)
aws-partitions (1.828.0)
aws-sdk-core (3.183.1)
aws-partitions (1.792.0)
aws-sdk-core (3.179.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
@@ -164,13 +163,12 @@ GEM
aws-sdk-kms (1.71.0)
aws-sdk-core (~> 3, >= 3.177.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.136.0)
aws-sdk-core (~> 3, >= 3.181.0)
aws-sdk-s3 (1.132.0)
aws-sdk-core (~> 3, >= 3.179.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
aws-sigv4 (1.6.0)
aws-eventstream (~> 1, >= 1.0.2)
base64 (0.1.1)
bcrypt (3.1.18)
bigdecimal (3.0.2)
bindata (2.4.15)
@@ -183,7 +181,7 @@ GEM
bullet (7.0.7)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
cable_ready (5.0.1)
cable_ready (5.0.0.rc2)
actionpack (>= 5.2)
actionview (>= 5.2)
activesupport (>= 5.2)
@@ -204,7 +202,6 @@ GEM
marcel (~> 1.0)
nokogiri (~> 1.10, >= 1.10.4)
rubyzip (>= 1.3.0, < 3)
cgi (0.3.6)
choice (0.2.0)
chronic (0.10.2)
coderay (1.1.3)
@@ -234,8 +231,8 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
datafoodconsortium-connector (1.0.0.pre.alpha.8)
virtual_assembly-semantizer (~> 1.0, >= 1.0.5)
datafoodconsortium-connector (1.0.0.pre.alpha.6)
virtual_assembly-semantizer (~> 1.0, >= 1.0.4)
date (3.3.3)
debug (1.8.0)
irb (>= 1.5.0)
@@ -270,8 +267,7 @@ GEM
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
faraday (2.7.11)
base64
faraday (2.7.10)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-follow_redirects (0.3.0)
@@ -282,7 +278,7 @@ GEM
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (>= 0.6, < 0.8)
ffaker (2.23.0)
ffaker (2.21.0)
ffi (1.15.5)
flipper (0.26.2)
concurrent-ruby (< 2)
@@ -319,8 +315,8 @@ GEM
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
geocoder (1.8.2)
globalid (1.2.1)
activesupport (>= 6.1)
globalid (1.1.0)
activesupport (>= 5.0)
gmaps4rails (2.1.2)
good_migrations (0.2.1)
activerecord (>= 3.1)
@@ -356,20 +352,20 @@ GEM
jquery-ui-rails (4.2.1)
railties (>= 3.2.16)
json (2.6.3)
json-canonicalization (0.3.2)
json-canonicalization (0.3.1)
json-jwt (1.16.3)
activesupport (>= 4.2)
aes_key_wrap
bindata
faraday (~> 2.0)
faraday-follow_redirects
json-ld (3.2.5)
json-ld (3.2.3)
htmlentities (~> 4.3)
json-canonicalization (~> 0.3, >= 0.3.2)
json-canonicalization (~> 0.3)
link_header (~> 0.0, >= 0.0.8)
multi_json (~> 1.15)
rack (>= 2.2, < 4)
rdf (~> 3.2, >= 3.2.10)
rack (~> 2.2)
rdf (~> 3.2, >= 3.2.9)
json-schema (3.0.0)
addressable (>= 2.8)
json_spec (1.1.5)
@@ -378,7 +374,7 @@ GEM
jsonapi-serializer (2.2.0)
activesupport (>= 4.2)
jwt (2.7.1)
knapsack_pro (5.7.0)
knapsack_pro (5.3.4)
rake
language_server-protocol (3.17.0.3)
launchy (2.5.0)
@@ -400,16 +396,16 @@ GEM
marcel (1.0.2)
matrix (0.4.2)
method_source (1.0.0)
mime-types (3.5.1)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2023.0808)
mime-types-data (3.2021.0225)
mimemagic (0.4.3)
nokogiri (~> 1)
rake
mini_magick (4.11.0)
mini_mime (1.1.5)
mini_mime (1.1.2)
mini_portile2 (2.8.4)
minitest (5.20.0)
minitest (5.18.1)
monetize (1.12.0)
money (~> 6.12)
money (6.16.0)
@@ -417,18 +413,17 @@ GEM
msgpack (1.7.1)
multi_json (1.15.0)
multi_xml (0.6.0)
net-imap (0.3.7)
net-imap (0.3.6)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.1)
timeout
net-smtp (0.4.0)
net-smtp (0.3.3)
net-protocol
newrelic_rpm (9.5.0)
nio4r (2.5.9)
nokogiri (1.15.4)
nokogiri (1.15.3)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oauth2 (1.4.11)
@@ -487,14 +482,14 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.3)
puma (6.4.0)
puma (6.3.0)
nio4r (~> 2.0)
query_count (1.1.1)
activerecord (>= 4.2)
railties (>= 4.2)
raabro (1.4.0)
racc (1.7.1)
rack (2.2.8)
rack (2.2.7)
rack-mini-profiler (2.3.4)
rack (>= 1.2.0)
rack-oauth2 (1.21.3)
@@ -511,25 +506,25 @@ GEM
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.6.3)
rails (7.0.8)
actioncable (= 7.0.8)
actionmailbox (= 7.0.8)
actionmailer (= 7.0.8)
actionpack (= 7.0.8)
actiontext (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activemodel (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
rails (7.0.6)
actioncable (= 7.0.6)
actionmailbox (= 7.0.6)
actionmailer (= 7.0.6)
actionpack (= 7.0.6)
actiontext (= 7.0.6)
actionview (= 7.0.6)
activejob (= 7.0.6)
activemodel (= 7.0.6)
activerecord (= 7.0.6)
activestorage (= 7.0.6)
activesupport (= 7.0.6)
bundler (>= 1.15.0)
railties (= 7.0.8)
railties (= 7.0.6)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
rails-dom-testing (2.2.0)
rails-dom-testing (2.1.1)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
@@ -541,13 +536,13 @@ GEM
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
rails-i18n (7.0.8)
rails-i18n (7.0.7)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
rails_safe_tasks (1.0.0)
railties (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
railties (7.0.6)
actionpack (= 7.0.6)
activesupport (= 7.0.6)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -561,11 +556,11 @@ GEM
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rdf (3.2.11)
rdf (3.2.9)
link_header (~> 0.0, >= 0.0.8)
redcarpet (3.6.0)
redis (4.8.1)
redis-client (0.17.0)
redis-client (0.14.1)
connection_pool
regexp_parser (2.8.1)
reline (0.3.3)
@@ -621,8 +616,7 @@ GEM
rswag-ui (2.10.1)
actionpack (>= 3.1, < 7.1)
railties (>= 3.1, < 7.1)
rubocop (1.56.4)
base64 (~> 0.1.1)
rubocop (1.55.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
@@ -635,7 +629,7 @@ GEM
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-rails (2.21.2)
rubocop-rails (2.20.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
@@ -663,7 +657,7 @@ GEM
semantic_range (3.0.0)
shoulda-matchers (5.3.0)
activesupport (>= 5.2.0)
sidekiq (7.1.5)
sidekiq (7.1.2)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
rack (>= 2.2.4)
@@ -698,20 +692,18 @@ GEM
state_machines-activerecord (0.9.0)
activerecord (>= 6.0)
state_machines-activemodel (>= 0.9.0)
stimulus_reflex (3.5.0.rc3)
stimulus_reflex (3.5.0.rc2)
actioncable (>= 5.2, < 8)
actionpack (>= 5.2, < 8)
actionview (>= 5.2, < 8)
activesupport (>= 5.2, < 8)
cable_ready (~> 5.0)
cable_ready (>= 5.0.0.rc2)
nokogiri (~> 1.0)
rack (>= 2, < 4)
railties (>= 5.2, < 8)
redis (>= 4.0, < 6.0)
stimulus_reflex_testing (0.3.0)
stimulus_reflex (>= 3.3.0)
stringex (2.8.6)
stripe (9.3.0)
stripe (8.6.0)
swd (1.3.0)
activesupport (>= 3)
attr_required (>= 0.0.5)
@@ -720,14 +712,14 @@ GEM
thor (1.2.2)
thread-local (1.1.0)
tilt (2.1.0)
timecop (0.9.8)
timecop (0.9.6)
timeout (0.4.0)
ttfunk (1.7.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
unicode-display_width (2.4.2)
uniform_notifier (1.16.0)
valid_email2 (5.1.0)
valid_email2 (4.0.6)
activemodel (>= 3.2)
mail (~> 2.5)
validate_email (0.1.6)
@@ -737,7 +729,7 @@ GEM
activemodel (>= 3.0.0)
public_suffix
vcr (6.2.0)
view_component (3.6.0)
view_component (3.5.0)
activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0)
method_source (~> 1.0)
@@ -745,11 +737,11 @@ GEM
rails (>= 5.2, < 8.0)
stimulus_reflex (>= 3.5.0.pre2)
view_component (>= 2.28.0)
virtual_assembly-semantizer (1.0.5)
virtual_assembly-semantizer (1.0.4)
json-ld (~> 3.2, >= 3.2.3)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
@@ -767,7 +759,7 @@ GEM
railties (>= 5.2)
semantic_range (>= 2.3.0)
webrick (1.7.0)
websocket-driver (0.7.6)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
whenever (1.0.0)
@@ -778,7 +770,7 @@ GEM
xml-simple (1.1.8)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.11)
zeitwerk (2.6.8)
PLATFORMS
ruby
@@ -804,7 +796,7 @@ DEPENDENCIES
bootsnap
bugsnag
bullet
cable_ready (= 5.0.1)
cable_ready (= 5.0.0.rc2)
cancancan (~> 1.15.0)
capybara
catalog!
@@ -855,7 +847,6 @@ DEPENDENCIES
mimemagic (> 0.3.5)
mini_portile2 (~> 2.8)
monetize (~> 1.11)
newrelic_rpm
oauth2 (~> 1.4.7)
omniauth-rails_csrf_protection
omniauth_openid_connect
@@ -903,8 +894,7 @@ DEPENDENCIES
spring
spring-commands-rspec
state_machines-activerecord
stimulus_reflex (= 3.5.0.rc3)
stimulus_reflex_testing
stimulus_reflex (= 3.5.0.rc2)
stringex (~> 2.8.5)
stripe
timecop

View File

@@ -135,7 +135,6 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
display_name: null
on_hand: null
price: null
tax_category_id: null
DisplayProperties.setShowVariants product.id, true
@@ -347,6 +346,9 @@ filterSubmitProducts = (productsToFilter) ->
if product.hasOwnProperty("category_id")
filteredProduct.primary_taxon_id = product.category_id
hasUpdatableProperty = true
if product.hasOwnProperty("tax_category_id")
filteredProduct.tax_category_id = product.tax_category_id
hasUpdatableProperty = true
if product.hasOwnProperty("inherits_properties")
filteredProduct.inherits_properties = product.inherits_properties
hasUpdatableProperty = true
@@ -387,9 +389,6 @@ filterSubmitVariant = (variant) ->
if variant.hasOwnProperty("display_name")
filteredVariant.display_name = variant.display_name
hasUpdatableProperty = true
if variant.hasOwnProperty("tax_category_id")
filteredVariant.tax_category_id = variant.tax_category_id
hasUpdatableProperty = true
if variant.hasOwnProperty("display_as")
filteredVariant.display_as = variant.display_as
hasUpdatableProperty = true

View File

@@ -11,8 +11,14 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
pendingChanges.remove(scope.object().id, scope.attr)
scope.clear()
else
change =
object: scope.object()
type: scope.type
attr: scope.attr
value: if value? then value else ""
scope: scope
scope.pending()
addPendingChange(scope.attr, value ? "")
pendingChanges.add(scope.object().id, scope.attr, change)
scope.reset = (value) ->
scope.savedValue = value
@@ -28,33 +34,3 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
scope.clear = ->
switchClass( element, "", ["update-pending", "update-error", "update-success"], false )
# When a list of customer is filtered and we removed the "filtered value" from a customer, we
# want to make sure the customer is updated. IE. filtering by tag, and removing said tag.
# Deleting the "filtered value" from a customer will remove the customer entry, thus
# removing "objForUpdate" directive from the active scope. That means $watch won't pick up
# the attribute changed.
# To ensure the customer is still updated, we check on the $destroy event to see if
# the attribute has changed, if so we queue up the change.
scope.$on '$destroy', (value) ->
# No update
return if scope.object()[scope.attr] is scope.savedValue
# For some reason the code attribute is removed from the object when cleared, so we add
# an emptyvalue so it gets updated properly
if scope.attr is "code" and scope.object()[scope.attr] is undefined
scope.object()["code"] = ""
# Queuing up change
addPendingChange(scope.attr, scope.object()[scope.attr])
# private
addPendingChange = (attr, value) ->
change =
object: scope.object()
type: scope.type
attr: attr
value: value
scope: scope
pendingChanges.add(scope.object().id, attr, change)

View File

@@ -9,9 +9,10 @@ angular.module('Darkswarm').controller "RegistrationFormCtrl", ($scope, Registra
$scope.create = (form) ->
if ($scope.valid(form))
$scope.disableButton()
EnterpriseRegistrationService.create($scope.enableButton).then(() ->
EnterpriseRegistrationService.create().then(() ->
$scope.enableButton()
)
end
$scope.update = (nextStep, form) ->
EnterpriseRegistrationService.update(nextStep) if $scope.valid(form)

View File

@@ -19,7 +19,7 @@
.name {{ product.name }}
.supplier {{ product.supplier_name }}
.exchange-product-variant{'ng-repeat' => 'variant in product.variants | visibleVariants:exchange:order_cycle.visible_variants_for_outgoing_exchanges | filter:variantSuppliedToOrderCycle as filteredVariants'}
.exchange-product-variant{'ng-repeat' => 'variant in product.variants | visibleVariants:exchange:order_cycle.visible_variants_for_outgoing_exchanges | filter:variantSuppliedToOrderCycle'}
%label
%input{ type: 'checkbox', name: 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
value: 1,
@@ -27,8 +27,5 @@
'id' => 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
'ng-disabled' => '!order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' }
{{ variant.label }}
%em{ 'ng-if' => 'filteredVariants.length === 0' }
{{ 'js.admin.panels.exchange_products.no_variants' | t }}
%div{ 'ng-include' => "'admin/panels/exchange_products_panel_footer.html'" }

View File

@@ -1,7 +1,5 @@
.exchange-load-all-variants
%div
{{ 'js.admin.panels.exchange_products.variants_loaded' | t:{ num_of_variants_loaded: enterprises[exchange.enterprise_id].loaded_variants, total_number_of_variants: exchangeTotalVariants(exchange) } }}
%em{ 'ng-if': 'enterprises[exchange.enterprise_id].loaded_variants > exchangeTotalVariants(exchange)' }
{{ 'js.admin.panels.exchange_products.some_variants_hidden' | t }}
%a{ 'ng-click' => 'loadAllExchangeProducts(exchange)', 'ng-show' => 'enterprises[exchange.enterprise_id].last_page_loaded < enterprises[exchange.enterprise_id].num_of_pages' }
{{ 'js.admin.panels.exchange_products.load_all_variants' | t }}

View File

@@ -1 +1 @@
.question-mark-icon{"ng-class" => "{open: tt_isOpen}", type: 'button'}
%button.question-mark-icon{"ng-class" => "{open: tt_isOpen}", type: 'button'}

View File

@@ -3,7 +3,7 @@
class ConfirmModalComponent < ModalComponent
def initialize(id:, confirm_actions: nil, reflex: nil, controller: nil, message: nil,
confirm_reflexes: nil)
super(id:, close_button: true)
super(id: id, close_button: true)
@confirm_actions = confirm_actions
@reflex = reflex
@confirm_reflexes = confirm_reflexes

View File

@@ -2,6 +2,6 @@
class HelpModalComponent < ModalComponent
def initialize(id:, close_button: true)
super(id:, close_button:)
super(id: id, close_button: close_button)
end
end

View File

@@ -0,0 +1,15 @@
# 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

@@ -0,0 +1,16 @@
= 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

@@ -0,0 +1,69 @@
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

@@ -0,0 +1,57 @@
# frozen_string_literal: true
class ProductComponent < ViewComponentReflex::Component
DATETIME_FORMAT = '%F %T'
def initialize(product:, columns:)
super
@product = product
@image = @product.image if product.image.present?
@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
def column_value(column)
case column
when 'name'
@product.name
when 'price'
@product.price
when 'unit'
"#{@product.variants.first.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 'import_date'
format_date(@product.import_date)
end
end
# rubocop:enable Metrics/CyclomaticComplexity
private
def format_date(date)
date&.strftime(DATETIME_FORMAT) || ''
end
end

View File

@@ -0,0 +1,6 @@
%tr
- @columns.each do |column|
%td.products_column{class: column[:id]}
- if column[:id] == "name" && @image&.attachment.present?
= image_tag @image.url(:mini)
= column[:value]

View File

@@ -0,0 +1,179 @@
# 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.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
[
:image,
variants: [
:default_price,
:stock_locations,
:stock_items,
:variant_overrides
]
]
end
end

View File

@@ -0,0 +1,21 @@
= 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

@@ -0,0 +1,47 @@
.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

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

View File

@@ -0,0 +1,5 @@
= 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

@@ -0,0 +1,23 @@
.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

@@ -0,0 +1,17 @@
# 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

@@ -0,0 +1,11 @@
= 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

@@ -0,0 +1,86 @@
.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

@@ -0,0 +1,11 @@
# 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

@@ -0,0 +1,22 @@
= 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

@@ -0,0 +1,51 @@
.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

@@ -0,0 +1,10 @@
# frozen_string_literal: true
class TableHeaderComponent < ViewComponentReflex::Component
def initialize(columns:, sort:, data: {})
super
@columns = columns
@sort = sort
@data = data
end
end

View File

@@ -0,0 +1,7 @@
= 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

@@ -0,0 +1,23 @@
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

@@ -11,7 +11,7 @@ module Admin
respond_override update: { json: {
success: lambda {
tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise))
render_as_json @customer, tag_rule_mapping:
render_as_json @customer, tag_rule_mapping: tag_rule_mapping
},
failure: lambda {
render json: { errors: @customer.errors.full_messages },
@@ -25,7 +25,7 @@ module Admin
format.json do
render json: @collection,
each_serializer: ::Api::Admin::CustomerWithBalanceSerializer,
tag_rule_mapping:,
tag_rule_mapping: tag_rule_mapping,
customer_tags: customer_tags_by_id
end
end
@@ -42,7 +42,7 @@ module Admin
@customer.created_manually = true
if @customer.save
tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise))
render_as_json @customer, tag_rule_mapping:
render_as_json @customer, tag_rule_mapping: tag_rule_mapping
else
render json: { errors: @customer.errors.full_messages }, status: :bad_request
end

View File

@@ -48,7 +48,7 @@ module Admin
private
def load_enterprise_fee_set
@enterprise_fee_set = Sets::EnterpriseFeeSet.new collection:
@enterprise_fee_set = Sets::EnterpriseFeeSet.new collection: collection
end
def load_data
@@ -61,7 +61,7 @@ module Admin
when :for_order_cycle
order_cycle = OrderCycle.find_by(id: params[:order_cycle_id]) if params[:order_cycle_id]
coordinator = Enterprise.find_by(id: params[:coordinator_id]) if params[:coordinator_id]
order_cycle ||= OrderCycle.new(coordinator:) if coordinator.present?
order_cycle ||= OrderCycle.new(coordinator: coordinator) if coordinator.present?
enterprises = OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user,
order_cycle).visible_enterprises
EnterpriseFee.for_enterprises(enterprises).order('enterprise_id', 'fee_type', 'name')

View File

@@ -39,7 +39,7 @@ module Admin
# The ! version is important to raise a RecordNotFound error.
def find_resource
permalink = params[:id] || params[:enterprise_group_id]
EnterpriseGroup.find_by!(permalink:)
EnterpriseGroup.find_by!(permalink: permalink)
end
private

View File

@@ -38,7 +38,7 @@ module Admin
format.html
format.json {
render_as_json @collection, ams_prefix: params[:ams_prefix],
spree_current_user:
spree_current_user: spree_current_user
}
end
end
@@ -47,12 +47,12 @@ module Admin
@object = Enterprise.where(permalink: params[:id]).
includes(users: [:ship_address, :bill_address]).first
@object.build_custom_tab if @object.custom_tab.nil?
return unless 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"))
.morph("#permalink", partial("admin/enterprises/form/permalink"))
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"))
.morph("#permalink", partial("admin/enterprises/form/permalink"))
end
end
def welcome
@@ -63,7 +63,6 @@ module Admin
tag_rules_attributes = params[object_name].delete :tag_rules_attributes
update_tag_rules(tag_rules_attributes) if tag_rules_attributes.present?
update_enterprise_notifications
update_vouchers
delete_custom_tab if params[:custom_tab] == 'false'
@@ -73,7 +72,7 @@ module Admin
format.html { redirect_to location_after_save }
format.js { render layout: false }
format.json {
render_as_json @object, ams_prefix: 'index', spree_current_user:
render_as_json @object, ams_prefix: 'index', spree_current_user: spree_current_user
}
end
else
@@ -126,7 +125,7 @@ module Admin
json: @collection,
each_serializer: Api::Admin::ForOrderCycle::EnterpriseSerializer,
order_cycle: @order_cycle,
spree_current_user:
spree_current_user: spree_current_user
)
end
end
@@ -136,7 +135,7 @@ module Admin
respond_to do |format|
format.json do
render_as_json @collection, ams_prefix: params[:ams_prefix] || 'basic',
spree_current_user:
spree_current_user: spree_current_user
end
end
end
@@ -183,7 +182,7 @@ module Admin
when :for_order_cycle
@order_cycle = OrderCycle.find_by(id: params[:order_cycle_id]) if params[:order_cycle_id]
coordinator = Enterprise.find_by(id: params[:coordinator_id]) if params[:coordinator_id]
@order_cycle ||= OrderCycle.new(coordinator:) if coordinator.present?
@order_cycle ||= OrderCycle.new(coordinator: coordinator) if coordinator.present?
enterprises = OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user, @order_cycle)
.visible_enterprises
@@ -264,44 +263,32 @@ module Admin
def update_enterprise_notifications
user_id = params[:receives_notifications].to_i
return unless user_id.positive? && @enterprise.user_ids.include?(user_id)
@enterprise.update_contact(user_id)
end
def update_vouchers
params_voucher_ids = params[:enterprise][:voucher_ids].to_a.map(&:to_i)
voucher_ids = @enterprise.vouchers.map(&:id)
deleted_voucher_ids = @enterprise.vouchers.only_deleted.map(&:id)
vouchers_to_destroy = voucher_ids - params_voucher_ids
Voucher.where(id: vouchers_to_destroy).destroy_all if vouchers_to_destroy.present?
vouchers_to_restore = deleted_voucher_ids.intersection(params_voucher_ids)
Voucher.restore(vouchers_to_restore) if vouchers_to_restore.present?
if user_id.positive? && @enterprise.user_ids.include?(user_id)
@enterprise.update_contact(user_id)
end
end
def create_calculator_for(rule, attrs)
return unless attrs[:calculator_type].present? && attrs[:calculator_attributes].present?
rule.update(calculator_type: attrs[:calculator_type])
attrs[:calculator_attributes].merge!( id: rule.calculator.id )
if attrs[:calculator_type].present? && attrs[:calculator_attributes].present?
rule.update(calculator_type: attrs[:calculator_type])
attrs[:calculator_attributes].merge!( id: rule.calculator.id )
end
end
def check_can_change_bulk_sells
return if spree_current_user.admin?
params[:sets_enterprise_set][:collection_attributes].each do |_i, enterprise_params|
unless spree_current_user == Enterprise.find_by(id: enterprise_params[:id]).owner
enterprise_params.delete :sells
unless spree_current_user.admin?
params[:sets_enterprise_set][:collection_attributes].each do |_i, enterprise_params|
unless spree_current_user == Enterprise.find_by(id: enterprise_params[:id]).owner
enterprise_params.delete :sells
end
end
end
end
def check_can_change_sells
return if spree_current_user.admin? || spree_current_user == @enterprise.owner
enterprise_params.delete :sells
unless spree_current_user.admin? || spree_current_user == @enterprise.owner
enterprise_params.delete :sells
end
end
def override_owner
@@ -309,31 +296,31 @@ module Admin
end
def override_sells
return if spree_current_user.admin?
has_hub = spree_current_user.owned_enterprises.is_hub.any?
new_enterprise_is_producer = Enterprise.new(enterprise_params).is_primary_producer
enterprise_params[:sells] = has_hub && !new_enterprise_is_producer ? 'any' : 'none'
unless spree_current_user.admin?
has_hub = spree_current_user.owned_enterprises.is_hub.any?
new_enterprise_is_producer = Enterprise.new(enterprise_params).is_primary_producer
enterprise_params[:sells] = has_hub && !new_enterprise_is_producer ? 'any' : 'none'
end
end
def check_can_change_owner
return if ( spree_current_user == @enterprise.owner ) || spree_current_user.admin?
enterprise_params.delete :owner_id
end
def check_can_change_bulk_owner
return if spree_current_user.admin?
bulk_params[:collection_attributes].each do |_i, enterprise_params|
unless ( spree_current_user == @enterprise.owner ) || spree_current_user.admin?
enterprise_params.delete :owner_id
end
end
def check_can_change_managers
return if ( spree_current_user == @enterprise.owner ) || spree_current_user.admin?
def check_can_change_bulk_owner
unless spree_current_user.admin?
bulk_params[:collection_attributes].each do |_i, enterprise_params|
enterprise_params.delete :owner_id
end
end
end
enterprise_params.delete :user_ids
def check_can_change_managers
unless ( spree_current_user == @enterprise.owner ) || spree_current_user.admin?
enterprise_params.delete :user_ids
end
end
def strip_new_properties

View File

@@ -182,17 +182,17 @@ module Admin
end
def load_data_for_index
return unless json_request?
# Split ransack params into all those that currently exist and new ones
# to limit returned ocs to recent or undated
orders_close_at_gt = raw_params[:q]&.delete(:orders_close_at_gt) || 31.days.ago
raw_params[:q] = {
g: [raw_params.delete(:q) || {}, { m: 'or',
orders_close_at_gt:,
orders_close_at_null: true }]
}
@collection = collection
if json_request?
# Split ransack params into all those that currently exist and new ones
# to limit returned ocs to recent or undated
orders_close_at_gt = raw_params[:q]&.delete(:orders_close_at_gt) || 31.days.ago
raw_params[:q] = {
g: [raw_params.delete(:q) || {}, { m: 'or',
orders_close_at_gt: orders_close_at_gt,
orders_close_at_null: true }]
}
@collection = collection
end
end
def redirect_to_after_update_path
@@ -245,10 +245,10 @@ module Admin
order_cycle_params.delete :coordinator_id
return if Enterprise.managed_by(spree_current_user).include?(@order_cycle.coordinator)
order_cycle_params.delete_if do |k, _v|
[:name, :orders_open_at, :orders_close_at].include? k.to_sym
unless Enterprise.managed_by(spree_current_user).include?(@order_cycle.coordinator)
order_cycle_params.delete_if do |k, _v|
[:name, :orders_open_at, :orders_close_at].include? k.to_sym
end
end
end

View File

@@ -56,9 +56,9 @@ module Admin
private
def validate_upload_presence
return if params[:file] || (params[:filepath] && File.exist?(params[:filepath]))
redirect_to '/admin/product_import', notice: I18n.t(:product_import_file_not_found_notice)
unless params[:file] || (params[:filepath] && File.exist?(params[:filepath]))
redirect_to '/admin/product_import', notice: I18n.t(:product_import_file_not_found_notice)
end
end
def process_data(method)
@@ -90,11 +90,11 @@ module Admin
end
def check_spreadsheet_has_data(importer)
return if importer.item_count
redirect_to '/admin/product_import',
notice: I18n.t(:product_import_no_data_in_spreadsheet_notice)
true
unless importer.item_count
redirect_to '/admin/product_import',
notice: I18n.t(:product_import_no_data_in_spreadsheet_notice)
true
end
end
def save_uploaded_file(upload)

View File

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

View File

@@ -13,7 +13,7 @@ module Admin
if @proxy_order.cancel
render_as_json @proxy_order
else
render json: { errors: [t('.could_not_cancel_the_order')] },
render json: { errors: [t('admin.proxy_orders.cancel.could_not_cancel_the_order')] },
status: :unprocessable_entity
end
end
@@ -22,7 +22,7 @@ module Admin
if @proxy_order.resume
render_as_json @proxy_order
else
render json: { errors: [t('.could_not_resume_the_order')] },
render json: { errors: [t('admin.proxy_orders.resume.could_not_resume_the_order')] },
status: :unprocessable_entity
end
end

View File

@@ -55,15 +55,6 @@ module Admin
@report_title = report_title
@rendering_options = rendering_options
@data = Reporting::FrontendData.new(spree_current_user)
variant_id_in = params[:variant_id_in]&.compact_blank
load_selected_variant if variant_id_in.present?
end
# Orders and Fulfillment Reports include a per product filter, load any selected product
def load_selected_variant
variant = Spree::Variant.find(params[:variant_id_in][0])
@variant_serialized = Api::Admin::VariantSerializer.new(variant)
end
def render_data?
@@ -83,8 +74,8 @@ module Admin
blob = ReportBlob.create_for_upload_later!(report_filename)
ReportJob.perform_later(
report_class:, user: spree_current_user, params:,
format:, blob:, channel: ScopedChannel.for_id(params[:uuid]),
report_class: report_class, user: spree_current_user, params: params,
format: format, blob: blob, channel: ScopedChannel.for_id(params[:uuid]),
)
head :no_content

View File

@@ -24,6 +24,18 @@ module Admin
end
end
def update
if @object.update(permitted_resource_params)
flash[:success] = flash_message_for(@object, :successfully_updated)
respond_with(@object) do |format|
format.html { redirect_to location_after_save }
format.js { render layout: false }
end
else
respond_with(@object)
end
end
def create
@object.attributes = permitted_resource_params
if @object.save
@@ -37,21 +49,9 @@ module Admin
end
end
def update
if @object.update(permitted_resource_params)
flash[:success] = flash_message_for(@object, :successfully_updated)
respond_with(@object) do |format|
format.html { redirect_to location_after_save }
format.js { render layout: false }
end
else
respond_with(@object)
end
end
def update_positions
params[:positions].each do |id, index|
model_class.where(id:).update_all(position: index)
model_class.where(id: id).update_all(position: index)
end
respond_to do |format|

View File

@@ -8,13 +8,15 @@ module Admin
include PaperTrailLogging
before_action :adapt_params, only: [:update]
before_action :editable_order_cycle_ids_for_create, only: [:create]
before_action :editable_order_cycle_ids_for_update, only: [:update]
before_action :check_dependent_subscriptions, only: [:destroy]
after_action :sync_subscriptions_for_update, only: :update
respond_to :json
OVERRIDE_RESPONSE = { json: {
respond_override create: { json: {
success: lambda {
render_as_json @schedule,
editable_schedule_ids: permissions.editable_schedules.pluck(:id)
@@ -23,10 +25,17 @@ module Admin
render json: { errors: @schedule.errors.full_messages },
status: :unprocessable_entity
}
} }.freeze
respond_override create: OVERRIDE_RESPONSE
respond_override update: OVERRIDE_RESPONSE
} }
respond_override update: { json: {
success: lambda {
render_as_json @schedule,
editable_schedule_ids: permissions.editable_schedules.pluck(:id)
},
failure: lambda {
render json: { errors: @schedule.errors.full_messages },
status: :unprocessable_entity
}
} }
def index
respond_to do |format|
@@ -41,23 +50,22 @@ module Admin
end
def create
@schedule_form = ScheduleForm.new(params, spree_current_user, @schedule)
return respond_with(@schedule) if params[:order_cycle_ids].blank?
@schedule.attributes = permitted_resource_params
if @schedule.save
@schedule.order_cycle_ids = params[:order_cycle_ids]
@schedule.save!
if @schedule_form.save
flash[:success] = flash_message_for(@schedule, :successfully_created)
@existing_order_cycle_ids = []
sync_subscriptions_for_create
flash[:success] = flash_message_for(@schedule, :successfully_created)
end
respond_with(@schedule)
end
def update
@existing_order_cycle_ids = @schedule.order_cycle_ids
@object = ScheduleForm.new(params, spree_current_user, @schedule)
super
end
private
def collection
@@ -88,6 +96,37 @@ module Admin
params[:schedule][:order_cycle_ids] = params[:order_cycle_ids]
end
def editable_order_cycle_ids_for_create
return unless params[:order_cycle_ids]
@existing_order_cycle_ids = []
result = editable_order_cycles(params[:order_cycle_ids])
params[:order_cycle_ids] = result
end
def editable_order_cycle_ids_for_update
return unless params[:schedule][:order_cycle_ids]
@existing_order_cycle_ids = @schedule.order_cycle_ids
result = editable_order_cycles(params[:schedule][:order_cycle_ids])
params[:schedule][:order_cycle_ids] = result
@schedule.order_cycle_ids = result
end
def editable_order_cycles(requested)
permitted = OrderCycle
.where(id: params[:order_cycle_ids] | @existing_order_cycle_ids)
.merge(OrderCycle.managed_by(spree_current_user))
.pluck(:id)
result = @existing_order_cycle_ids
result |= (requested & permitted) # add any requested & permitted ids
# remove any existing and permitted ids that were not specifically requested
result -= ((result & permitted) - requested)
result
end
def check_dependent_subscriptions
return if Subscription.where(schedule_id: @schedule).empty?
@@ -101,14 +140,14 @@ module Admin
@permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
end
def sync_subscriptions_for_create
return unless params[:order_cycle_ids]
def sync_subscriptions_for_update
return unless params[:schedule][:order_cycle_ids] && @object.errors.blank?
sync_subscriptions
end
def sync_subscriptions_for_update
return unless params[:schedule][:order_cycle_ids] && @schedule.errors.blank?
def sync_subscriptions_for_create
return unless params[:order_cycle_ids]
sync_subscriptions
end
@@ -116,7 +155,6 @@ module Admin
def sync_subscriptions
removed_ids = @existing_order_cycle_ids - @schedule.order_cycle_ids
new_ids = @schedule.order_cycle_ids - @existing_order_cycle_ids
return unless removed_ids.any? || new_ids.any?
subscriptions = Subscription.where(schedule_id: @schedule)

View File

@@ -21,7 +21,7 @@ module Admin
def update
Spree::Config.set(settings_params.to_h)
resource = t('admin.controllers.stripe_connect_settings.resource')
flash[:success] = t(:successfully_updated, resource:)
flash[:success] = t(:successfully_updated, resource: resource)
redirect_to_edit
end

View File

@@ -9,33 +9,19 @@ module Admin
end
def create
@voucher = Voucher.new(
permitted_resource_params.merge(enterprise: @enterprise)
)
@voucher = Voucher.create(permitted_resource_params.merge(enterprise: @enterprise))
if @voucher.save
flash[:success] = I18n.t(:successfully_created, resource: "Voucher")
flash[:success] = flash_message_for(@voucher, :successfully_created)
redirect_to edit_admin_enterprise_path(@enterprise, anchor: :vouchers_panel)
else
render_error
flash[:error] = @voucher.errors.full_messages.to_sentence
render :new
end
rescue ActiveRecord::SubclassNotFound
@voucher.errors.add(:type)
render_error
rescue ActiveRecord::RecordNotUnique
# Rails unique validation doesn't work with soft deleted object, so we rescue the database
# exception to display a nice message to the user
@voucher.errors.add(:code, :taken)
render_error
end
private
def render_error
flash[:error] = @voucher.errors.full_messages.to_sentence
render :new
end
def load_enterprise
@enterprise = OpenFoodNetwork::Permissions
.new(spree_current_user)
@@ -44,7 +30,7 @@ module Admin
end
def permitted_resource_params
params.require(:voucher).permit(:code, :amount, :type)
params.require(:voucher).permit(:code, :amount)
end
end
end

View File

@@ -21,7 +21,7 @@ module Api
@enterprise.update!(attachment_name => nil)
render json: @enterprise,
serializer: Admin::EnterpriseSerializer,
spree_current_user:
spree_current_user: spree_current_user
end
protected

View File

@@ -5,6 +5,11 @@ module Api
class OrdersController < Api::V0::BaseController
include PaginationData
def show
authorize! :read, order
render json: order, serializer: Api::OrderDetailedSerializer, current_order: order
end
def index
authorize! :admin, Spree::Order
@@ -21,11 +26,6 @@ module Api
}
end
def show
authorize! :read, order
render json: order, serializer: Api::OrderDetailedSerializer, current_order: order
end
def update
authorize! :admin, order

View File

@@ -13,7 +13,7 @@ module Api
skip_authorization_check only: [:show, :bulk_products, :overridable]
def show
@product = product_finder.find_product
@product = find_product(params[:id])
render json: @product, serializer: Api::Admin::ProductSerializer
end
@@ -30,7 +30,7 @@ module Api
def update
authorize! :update, Spree::Product
@product = product_finder.find_product
@product = find_product(params[:id])
if @product.update(product_params)
render json: @product, serializer: Api::Admin::ProductSerializer, status: :ok
else
@@ -40,20 +40,36 @@ module Api
def destroy
authorize! :delete, Spree::Product
@product = product_finder.find_product
@product = find_product(params[:id])
authorize! :delete, @product
@product.destroy
render json: @product, serializer: Api::Admin::ProductSerializer, status: :no_content
end
def bulk_products
@products = product_finder.bulk_products
product_query = OpenFoodNetwork::Permissions.
new(current_api_user).
editable_products.
merge(product_scope)
if params[:import_date].present?
product_query = product_query.
imported_on(params[:import_date]).
group_by_products_id
end
@products = product_query.
ransack(query_params_with_defaults).
result
render_paged_products @products
end
def overridable
@products = product_finder.paged_products_for_producers
producer_ids = OpenFoodNetwork::Permissions.new(current_api_user).
variant_override_producers.by_name.select('enterprises.id')
@products = paged_products_for_producers producer_ids
render_paged_products @products, ::Api::Admin::ProductSimpleSerializer
end
@@ -62,7 +78,7 @@ module Api
#
def clone
authorize! :create, Spree::Product
original_product = product_finder.find_product_to_be_cloned
original_product = find_product(params[:product_id])
authorize! :update, original_product
@product = original_product.duplicate
@@ -72,8 +88,37 @@ module Api
private
def product_finder
ProductScopeQuery.new(current_api_user, params)
def find_product(id)
product_scope.find(id)
end
def product_scope
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present?
scope = Spree::Product
if params[:show_deleted]
scope = scope.with_deleted
end
else
scope = Spree::Product.active
end
scope.includes(product_query_includes)
end
def product_query_includes
[
image: { attachment_attachment: :blob },
variants: [:default_price, :stock_locations, :stock_items, :variant_overrides]
]
end
def paged_products_for_producers(producer_ids)
Spree::Product.where(nil).
merge(product_scope).
includes(variants: [:product, :default_price, :stock_items]).
where(supplier_id: producer_ids).
by_producer.by_name.
ransack(params[:q]).result
end
def render_paged_products(products, product_serializer = ::Api::Admin::ProductSerializer)
@@ -90,6 +135,10 @@ module Api
}
end
def query_params_with_defaults
(params[:q] || {}).reverse_merge(s: 'created_at desc')
end
def product_params
@product_params ||=
params.permit(product: PermittedAttributes::Product.attributes)[:product].to_h

View File

@@ -44,16 +44,14 @@ module Api
def ready
authorize! :read, Spree::Shipment
unless @shipment.ready? || @shipment.can_ready?
return render(
json: { error: I18n.t(:cannot_ready, scope: "spree.api.shipment") },
status: :unprocessable_entity
)
unless @shipment.ready?
if @shipment.can_ready?
@shipment.ready!
else
render(json: { error: I18n.t(:cannot_ready, scope: "spree.api.shipment") },
status: :unprocessable_entity) && return
end
end
@shipment.ready! unless @shipment.ready?
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end
@@ -110,7 +108,7 @@ module Api
end
def get_or_create_shipment(stock_location_id)
@order.shipment || @order.shipments.create(stock_location_id:)
@order.shipment || @order.shipments.create(stock_location_id: stock_location_id)
end
def shipment_params

View File

@@ -69,14 +69,14 @@ module Api
end
def missing_parameter(error)
message = I18n.t('api.missing_parameter', param: error.param)
message = I18n.t(:missing_parameter, param: error.param, scope: :api)
render status: :unprocessable_entity,
json: json_api_error(message)
end
def unpermitted_parameters(error)
message = I18n.t('api.unpermitted_parameters', params: error.params.join(", "))
message = I18n.t(:unpermitted_parameters, params: error.params.join(", "), scope: :api)
render status: :unprocessable_entity,
json: json_api_error(message)

View File

@@ -115,25 +115,25 @@ class ApplicationController < ActionController::Base
end
def require_distributor_chosen
return if (@distributor = current_distributor)
redirect_to main_app.root_path
false
unless (@distributor = current_distributor)
redirect_to main_app.root_path
false
end
end
def require_order_cycle
return if current_order_cycle
redirect_to main_app.shop_path
unless current_order_cycle
redirect_to main_app.shop_path
end
end
def check_hub_ready_for_checkout
return unless current_distributor_closed?
current_order.empty!
current_order.set_distribution! nil, nil
flash[:info] = I18n.t('order_cycles_closed_for_hub')
redirect_to main_app.root_url
if current_distributor_closed?
current_order.empty!
current_order.set_distribution! nil, nil
flash[:info] = I18n.t('order_cycles_closed_for_hub')
redirect_to main_app.root_url
end
end
def current_distributor_closed?

View File

@@ -1,47 +0,0 @@
# frozen_string_literal: true
module CheckoutSteps
extend ActiveSupport::Concern
private
def summary_step?
params[:step] == "summary"
end
def payment_step?
params[:step] == "payment"
end
def redirect_to_step_based_on_order
case @order.state
when "cart", "address", "delivery"
redirect_to checkout_step_path(:details)
when "payment"
redirect_to checkout_step_path(:payment)
when "confirmation"
redirect_to checkout_step_path(:summary)
else
redirect_to order_path(@order, order_token: @order.token)
end
end
def redirect_to_step
case params[:step]
when "details"
return redirect_to checkout_step_path(:payment)
when "payment"
return redirect_to checkout_step_path(:summary)
end
redirect_to_step_based_on_order
end
def check_step
case @order.state
when "cart", "address", "delivery"
redirect_to checkout_step_path(:details) unless params[:step] == "details"
when "payment"
redirect_to checkout_step_path(:payment) if params[:step] == "summary"
end
end
end

View File

@@ -5,11 +5,11 @@ module ExtraFields
extend ActiveSupport::Concern
def invalid_query_param(name, status, msg)
render status:, json: json_api_error(msg, error_options:
render status: status, json: json_api_error(msg, error_options:
{
title: I18n.t("api.query_param.error.title"),
source: { parameter: name },
status:,
status: status,
code: Rack::Utils::SYMBOL_TO_STATUS_CODE[status]
})
end

View File

@@ -5,7 +5,7 @@ module ManagerInvitations
def create_new_manager(email, enterprise)
password = Devise.friendly_token
new_user = Spree::User.create(email:, unconfirmed_email: email, password:)
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

View File

@@ -74,8 +74,8 @@ module ReportsActions
def rendering_options
@rendering_options ||= ReportRenderingOptions.where(
user: spree_current_user,
report_type:,
report_subtype:
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?

View File

@@ -16,7 +16,7 @@ module RequestTimeouts
respond_to do |type|
type.html {
render status: :gateway_timeout,
file: Rails.public_path.join('500.html'),
file: Rails.root.join("public/500.html"),
formats: [:html],
layout: nil
}

View File

@@ -9,7 +9,7 @@ class ErrorsController < ApplicationController
event.add_metadata(:request, request.env)
end
render status: :not_found, formats: :html
render status: :not_found
end
def internal_server_error

View File

@@ -4,14 +4,14 @@ class HomeController < BaseController
layout 'darkswarm'
def index
return unless ContentConfig.home_show_stats
@num_distributors = cached_count('distributors', Enterprise.is_distributor.activated.visible)
@num_producers = cached_count('producers', Enterprise.is_primary_producer.activated.visible)
@num_orders = cached_count('orders', Spree::Order.complete)
@num_users = cached_count(
'users', Spree::Order.complete.select('DISTINCT spree_orders.user_id')
)
if ContentConfig.home_show_stats
@num_distributors = cached_count('distributors', Enterprise.is_distributor.activated.visible)
@num_producers = cached_count('producers', Enterprise.is_primary_producer.activated.visible)
@num_orders = cached_count('orders', Spree::Order.complete)
@num_users = cached_count(
'users', Spree::Order.complete.select('DISTINCT spree_orders.user_id')
)
end
end
def sell; end

View File

@@ -31,7 +31,7 @@ class LineItemsController < BaseController
def unauthorized
status = spree_current_user ? 403 : 401
render(body: nil, status:) && return
render(body: nil, status: status) && return
end
def not_found

View File

@@ -50,7 +50,7 @@ module PaymentGateways
payer_id: params[:PayerID]
),
amount: @order.total,
payment_method:
payment_method: payment_method
)
process_payment_completion!

View File

@@ -8,7 +8,6 @@ class SplitCheckoutController < ::BaseController
include OrderStockCheck
include Spree::BaseHelper
include CheckoutCallbacks
include CheckoutSteps
include OrderCompletion
include CablecarResponses
include WhiteLabel
@@ -51,13 +50,60 @@ class SplitCheckoutController < ::BaseController
private
def render_error
flash.now[:error] ||= I18n.t('split_checkout.errors.saving_failed')
flash.now[:error] ||= I18n.t(
'split_checkout.errors.saving_failed',
messages: order_error_messages
)
render status: :unprocessable_entity, cable_ready: cable_car.
replace("#checkout", partial("split_checkout/checkout")).
replace("#flashes", partial("shared/flashes", locals: { flashes: flash }))
end
def order_error_messages
# Remove ship_address.* errors if no shipping method is not selected
remove_ship_address_errors if no_ship_address_needed?
# Reorder errors to make sure the most important ones are shown first
# and finally, return the error messages to sentence
reorder_errors.map(&:full_message).to_sentence
end
def no_ship_address_needed?
@order.errors[:shipping_method].present? || params[:ship_address_same_as_billing] == "1"
end
def remove_ship_address_errors
@order.errors.delete("ship_address.firstname")
@order.errors.delete("ship_address.address1")
@order.errors.delete("ship_address.city")
@order.errors.delete("ship_address.phone")
@order.errors.delete("ship_address.lastname")
@order.errors.delete("ship_address.zipcode")
end
def reorder_errors
@order.errors.sort_by do |e|
case e.attribute
when /email/i then 0
when /phone/i then 1
when /bill_address/i then 2 + bill_address_error_order(e)
else 20
end
end
end
def bill_address_error_order(error)
case error.attribute
when /firstname/i then 0
when /lastname/i then 1
when /address1/i then 2
when /city/i then 3
when /zipcode/i then 4
else 5
end
end
def check_payments_adjustments
@order.payments.each(&:ensure_correct_adjustment)
end
@@ -68,7 +114,7 @@ class SplitCheckoutController < ::BaseController
def confirm_order
return unless summary_step? && @order.confirmation?
return unless validate_current_step
return unless validate_summary! && @order.errors.empty?
@order.customer.touch :terms_and_conditions_accepted_at
@@ -94,15 +140,48 @@ class SplitCheckoutController < ::BaseController
def update_order
return if params[:confirm_order] || @order.errors.any?
# If we have "pick up" shipping method (require_ship_address is set to false), use the
# distributor address as shipping address
use_shipping_address_from_distributor if shipping_method_ship_address_not_required?
@order.select_shipping_method(params[:shipping_method_id])
@order.update(order_params)
@order.update_totals_and_states
validate_current_step
validate_current_step!
@order.errors.empty?
end
def validate_current_step
Checkout::Validation.new(@order, params).call && @order.errors.empty?
def use_shipping_address_from_distributor
@order.ship_address = @order.address_from_distributor
# Add the missing data
bill_address = params[:order][:bill_address_attributes]
@order.ship_address.firstname = bill_address[:firstname]
@order.ship_address.lastname = bill_address[:lastname]
@order.ship_address.phone = bill_address[:phone]
# Remove shipping address from parameter so we don't override the address we just set
params[:order].delete(:ship_address_attributes)
end
def shipping_method_ship_address_not_required?
selected_shipping_method = available_shipping_methods&.select do |sm|
sm.id.to_s == params[:shipping_method_id]
end
return false if selected_shipping_method.empty?
selected_shipping_method.first.require_ship_address == false
end
def summary_step?
params[:step] == "summary"
end
def payment_step?
params[:step] == "payment"
end
def advance_order_state
@@ -111,7 +190,64 @@ class SplitCheckoutController < ::BaseController
OrderWorkflow.new(@order).advance_checkout(raw_params.slice(:shipping_method_id))
end
def validate_current_step!
step = ([params[:step]] & ["details", "payment", "summary"]).first
send("validate_#{step}!")
end
def validate_details!
return true if params[:shipping_method_id].present?
@order.errors.add :shipping_method, I18n.t('split_checkout.errors.select_a_shipping_method')
end
def validate_payment!
return true if params.dig(:order, :payments_attributes, 0, :payment_method_id).present?
return true if @order.zero_priced_order?
@order.errors.add :payment_method, I18n.t('split_checkout.errors.select_a_payment_method')
end
def validate_summary!
return true if params[:accept_terms]
return true unless TermsOfService.required?(@order.distributor)
@order.errors.add(:terms_and_conditions, t("split_checkout.errors.terms_not_accepted"))
end
def order_params
@order_params ||= Checkout::Params.new(@order, params, spree_current_user).call
end
def redirect_to_step_based_on_order
case @order.state
when "cart", "address", "delivery"
redirect_to checkout_step_path(:details)
when "payment"
redirect_to checkout_step_path(:payment)
when "confirmation"
redirect_to checkout_step_path(:summary)
else
redirect_to order_path(@order, order_token: @order.token)
end
end
def redirect_to_step
case params[:step]
when "details"
return redirect_to checkout_step_path(:payment)
when "payment"
return redirect_to checkout_step_path(:summary)
end
redirect_to_step_based_on_order
end
def check_step
case @order.state
when "cart", "address", "delivery"
redirect_to checkout_step_path(:details) unless params[:step] == "details"
when "payment"
redirect_to checkout_step_path(:payment) if params[:step] == "summary"
end
end
end

View File

@@ -20,10 +20,6 @@ module Spree
render layout: !request.xhr?
end
def edit
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
end
def create
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
set_viewable
@@ -38,6 +34,10 @@ module Spree
end
end
def edit
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
end
def update
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
set_viewable

View File

@@ -10,24 +10,57 @@ module Spree
authorize! :invoice, @order
end
def show
invoice_id = params[:id]
invoice_pdf = filepath(invoice_id)
def create
Spree::Order.where(id: params[:order_ids]).find_each do |order|
authorize! :invoice, order
end
invoice_service = BulkInvoiceService.new
invoice_service.start_pdf_job(params[:order_ids])
send_file(invoice_pdf, type: 'application/pdf', disposition: :inline)
render json: invoice_service.id, status: :ok
end
def generate
@order = Order.find_by(number: params[:order_id])
authorize! :invoice, @order
OrderInvoiceGenerator.new(@order).generate_or_update_latest_invoice
@comparator = OrderInvoiceComparator.new(@order)
if @comparator.can_generate_new_invoice?
@order.invoices.create!(
date: Time.zone.today,
number: @order.invoices.count + 1,
data: invoice_data
)
elsif @comparator.can_update_latest_invoice?
@order.invoices.last.update!(
date: Time.zone.today,
data: invoice_data
)
end
redirect_back(fallback_location: spree.admin_dashboard_path)
end
private
def show
invoice_id = params[:id]
invoice_pdf = BulkInvoiceService.new.filepath(invoice_id)
def filepath(invoice_id)
"tmp/invoices/#{invoice_id}.pdf"
send_file(invoice_pdf, type: 'application/pdf', disposition: :inline)
end
def poll
invoice_id = params[:invoice_id]
if BulkInvoiceService.new.invoice_created? invoice_id
render json: { created: true }, status: :ok
else
render json: { created: false }, status: :unprocessable_entity
end
end
protected
def invoice_data
@invoice_data ||= InvoiceDataGenerator.new(@order).generate
end
end
end

View File

@@ -23,7 +23,7 @@ module Spree
flash[:error] = Spree.t('admin.mail_methods.testmail.delivery_error')
end
rescue StandardError => e
flash[:error] = Spree.t('admin.mail_methods.testmail.error') % ({ e: })
flash[:error] = Spree.t('admin.mail_methods.testmail.error') % { e: e }
ensure
redirect_to spree.edit_admin_mail_methods_url
end

View File

@@ -43,8 +43,8 @@ module Spree
def build_addresses
country_id = Address.default.country.id
@order.build_bill_address(country_id:) if @order.bill_address.nil?
@order.build_ship_address(country_id:) if @order.ship_address.nil?
@order.build_bill_address(country_id: country_id) if @order.bill_address.nil?
@order.build_ship_address(country_id: country_id) if @order.ship_address.nil?
end
def refresh_shipment_rates

View File

@@ -16,22 +16,8 @@ module Spree
before_action :load_spree_api_key, only: [:index, :variant_overrides]
before_action :strip_new_properties, only: [:create, :update]
def index
@current_user = spree_current_user
@show_latest_import = params[:latest_import] || false
end
def show
session[:return_to] ||= request.referer
redirect_to( action: :edit )
end
def new
@object.shipping_category_id = DefaultShippingCategory.find_or_create.id
end
def edit
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
@object.shipping_category = DefaultShippingCategory.find_or_create
end
def create
@@ -49,6 +35,20 @@ module Spree
end
end
def show
session[:return_to] ||= request.referer
redirect_to( action: :edit )
end
def index
@current_user = spree_current_user
@show_latest_import = params[:latest_import] || false
end
def edit
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
end
def update
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
@@ -180,7 +180,7 @@ module Spree
select('DISTINCT spree_variants.import_date').
joins(:product).
where('spree_products.supplier_id IN (?)', editable_enterprises.collect(&:id)).
where.not(spree_variants: { import_date: nil }).
where('spree_variants.import_date IS NOT NULL').
where(spree_variants: { deleted_at: nil }).
order('spree_variants.import_date DESC')
end

View File

@@ -5,12 +5,6 @@ module Spree
class TaxonsController < Spree::Admin::BaseController
respond_to :html, :json, :js
def edit
@taxonomy = Taxonomy.find(params[:taxonomy_id])
@taxon = @taxonomy.taxons.find(params[:id])
@permalink_part = @taxon.permalink.split("/").last
end
def create
@taxonomy = Taxonomy.find(params[:taxonomy_id])
@taxon = @taxonomy.taxons.build(params[:taxon])
@@ -32,6 +26,12 @@ module Spree
end
end
def edit
@taxonomy = Taxonomy.find(params[:taxonomy_id])
@taxon = @taxonomy.taxons.find(params[:id])
@permalink_part = @taxon.permalink.split("/").last
end
def update
@taxonomy = Taxonomy.find(params[:taxonomy_id])
@taxon = @taxonomy.taxons.find(params[:id])

View File

@@ -3,8 +3,6 @@
module Spree
module Admin
class UsersController < ::Admin::ResourceController
helper I18nHelper
rescue_from Spree::User::DestroyWithOrdersError, with: :user_destroy_with_orders_error
after_action :sign_in_if_change_own_password, only: :update
@@ -30,11 +28,11 @@ module Spree
if @user.save
if roles
@user.spree_roles = roles.compact_blank.collect{ |r| Spree::Role.find(r) }
@user.spree_roles = roles.reject(&:blank?).collect{ |r| Spree::Role.find(r) }
end
flash[:success] = Spree.t(:created_successfully)
redirect_to edit_admin_user_path(@user)
flash.now[:success] = Spree.t(:created_successfully)
render :edit
else
render :new
end
@@ -47,14 +45,12 @@ module Spree
if @user.update(user_params)
if roles
@user.spree_roles = roles.compact_blank.collect{ |r| Spree::Role.find(r) }
@user.spree_roles = roles.reject(&:blank?).collect{ |r| Spree::Role.find(r) }
end
flash[:success] = update_message
redirect_to edit_admin_user_path(@user)
else
render :edit
flash.now[:success] = update_message
end
render :edit
end
protected
@@ -131,10 +127,6 @@ module Spree
params[:user][:email] != @user.email
end
def build_resource
model_class.new(locale: I18n.default_locale)
end
def user_params
::PermittedAttributes::User.new(params).call(
%i[enterprise_limit show_api_key_view]

View File

@@ -7,18 +7,28 @@ module Spree
class VariantsController < ::Admin::ResourceController
belongs_to 'spree/product'
before_action :load_data, only: [:new, :edit]
def index
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
end
def new
def edit
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
@object.shipping_category ||= DefaultShippingCategory.find_or_create
end
def edit
def update
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
if @object.update(permitted_resource_params)
flash[:success] = flash_message_for(@object, :successfully_updated)
redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters)
else
redirect_to spree.edit_admin_product_variant_url(params[:product_id],
@object,
@url_filters)
end
end
def new
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
end
@@ -43,19 +53,6 @@ module Spree
@object.on_hand = on_hand.to_i if on_hand.present?
end
def update
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
if @object.update(permitted_resource_params)
flash[:success] = flash_message_for(@object, :successfully_updated)
redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters)
else
redirect_to spree.edit_admin_product_variant_url(params[:product_id],
@object,
@url_filters)
end
end
def search
scoper = OpenFoodNetwork::ScopeVariantsForSearch.new(variant_search_params)
@variants = scoper.search
@@ -110,13 +107,6 @@ module Spree
:include_out_of_stock
).to_h.with_indifferent_access
end
private
def load_data
@tax_categories = TaxCategory.order(:name)
@shipping_categories = ShippingCategory.order(:name)
end
end
end
end

View File

@@ -69,10 +69,6 @@ module Spree
if @order.contents.update_cart(order_params)
@order.recreate_all_fees! # Enterprise fees on line items and on the order itself
# Re apply the voucher
VoucherAdjustmentsService.new(@order).update
@order.update_totals_and_states
if @order.complete?
@order.update_payment_fees!
@order.create_tax_charge!
@@ -133,10 +129,10 @@ module Spree
end
def filter_order_params
return unless params[:order] && params[:order][:line_items_attributes]
params[:order][:line_items_attributes] =
remove_missing_line_items(params[:order][:line_items_attributes])
if params[:order] && params[:order][:line_items_attributes]
params[:order][:line_items_attributes] =
remove_missing_line_items(params[:order][:line_items_attributes])
end
end
def remove_missing_line_items(attrs)
@@ -180,10 +176,10 @@ module Spree
items = params[:order][:line_items_attributes]
&.select{ |_k, attrs| attrs["quantity"].to_i > 0 }
return unless items.empty?
flash[:error] = I18n.t(:orders_cannot_remove_the_final_item)
redirect_to main_app.order_path(order_to_update)
if items.empty?
flash[:error] = I18n.t(:orders_cannot_remove_the_final_item)
redirect_to main_app.order_path(order_to_update)
end
end
def order_params

View File

@@ -49,10 +49,8 @@ module Spree
render cable_ready: cable_car.inner_html(
"#signup-feedback",
partial("layouts/alert",
locals: {
type: "success",
message: t('devise.user_registrations.spree_user.signed_up_but_unconfirmed')
})
locals: { type: "success",
message: t('devise.user_registrations.spree_user.signed_up_but_unconfirmed') })
)
else
render status: :unprocessable_entity, cable_ready: cable_car.morph(

View File

@@ -5,13 +5,6 @@ class UserConfirmationsController < DeviseController
include Spree::Core::ControllerHelpers::Auth
include CablecarResponses
# GET /resource/confirmation?confirmation_token=abcdef
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource) }
end
# GET /resource/confirmation/new
def new
build_resource({})
@@ -40,6 +33,13 @@ class UserConfirmationsController < DeviseController
respond_with_navigational(resource){ redirect_to login_path }
end
# GET /resource/confirmation?confirmation_token=abcdef
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource) }
end
protected
def set_return_url

View File

@@ -5,6 +5,9 @@ class VoucherAdjustmentsController < BaseController
def create
if add_voucher
VoucherAdjustmentsService.calculate(@order)
@order.update_totals_and_states
update_payment_section
elsif @order.errors.present?
render_error
@@ -18,9 +21,6 @@ class VoucherAdjustmentsController < BaseController
@order.voucher_adjustments.where(originator_id: adjustment.originator_id)&.destroy_all
end
# Update order to make sure we display the appropriate payment method
@order.update_totals_and_states
update_payment_section
end
@@ -45,17 +45,12 @@ class VoucherAdjustmentsController < BaseController
adjustment = voucher.create_adjustment(voucher.code, @order)
unless adjustment.persisted?
unless adjustment.valid?
@order.errors.add(:voucher_code, I18n.t('split_checkout.errors.add_voucher_error'))
adjustment.errors.each { |error| @order.errors.import(error) }
return false
end
clear_payments
VoucherAdjustmentsService.new(@order).update
@order.update_totals_and_states
true
end
@@ -83,9 +78,4 @@ class VoucherAdjustmentsController < BaseController
def voucher_params
params.require(:order).permit(:voucher_code)
end
# Clear payments and payment fees, to not affect voucher adjustment calculation
def clear_payments
@order.payments.incomplete.destroy_all
end
end

View File

@@ -1,78 +0,0 @@
# frozen_string_literal: true
class ScheduleForm
include ActiveModel::Model
attr_reader :errors, :flash_success
def initialize(params, user, schedule = nil)
@errors = ActiveModel::Errors.new self
# Not strong
@params = params
@current_user = user
@schedule = schedule
end
def save
editable_order_cycle_ids_for_create
return false if @params[:order_cycle_ids].blank?
@schedule.attributes = permitted_resource_params
if @schedule.save
@schedule.order_cycle_ids = @params[:order_cycle_ids]
@schedule.save!
end
true
end
def update(_params)
editable_order_cycle_ids_for_update
false unless @schedule.update(permitted_resource_params)
end
def order_cycle_ids
@schedule.order_cycle_ids
end
private
def editable_order_cycle_ids_for_create
return unless @params[:order_cycle_ids]
@existing_order_cycle_ids = []
result = editable_order_cycles(@params[:order_cycle_ids])
@params[:order_cycle_ids] = result
end
def editable_order_cycle_ids_for_update
return unless @params[:schedule][:order_cycle_ids]
@existing_order_cycle_ids = @schedule.order_cycle_ids
result = editable_order_cycles(@params[:schedule][:order_cycle_ids])
@params[:schedule][:order_cycle_ids] = result
@schedule.order_cycle_ids = result
end
def editable_order_cycles(requested)
permitted = OrderCycle
.where(id: @params[:order_cycle_ids] | @existing_order_cycle_ids)
.merge(OrderCycle.managed_by(@current_user))
.pluck(:id)
result = @existing_order_cycle_ids
result |= (requested & permitted) # add any requested & permitted ids
# remove any existing and permitted ids that were not specifically requested
result -= ((result & permitted) - requested)
result
end
def permitted_resource_params
@params.require(:schedule).permit(:id, :name, order_cycle_ids: [])
end
end

View File

@@ -42,7 +42,7 @@ module Admin
{ name: 'shipping_methods', icon_class: "icon-truck", show: show_shipping_methods },
{ name: 'payment_methods', icon_class: "icon-money", show: show_payment_methods },
{ name: 'enterprise_fees', icon_class: "icon-tasks", show: show_enterprise_fees },
{ name: 'vouchers', icon_class: "icon-ticket", show: is_shop },
{ name: 'vouchers', icon_class: "icon-ticket", show: true },
{ name: 'enterprise_permissions', icon_class: "icon-plug", show: true,
href: admin_enterprise_relationships_path },
{ name: 'inventory_settings', icon_class: "icon-list-ol", show: is_shop },

View File

@@ -2,71 +2,71 @@
module Admin
module InjectionHelper
def admin_inject_enterprise(enterprise)
def admin_inject_enterprise
admin_inject_json_ams "admin.enterprises",
"enterprise",
enterprise,
@enterprise,
Api::Admin::EnterpriseSerializer
end
def admin_inject_enterprises(my_enterprises, all_enterprises)
def admin_inject_enterprises
admin_inject_json_ams_array("ofn.admin",
"my_enterprises",
my_enterprises,
@my_enterprises,
Api::Admin::BasicEnterpriseSerializer) +
admin_inject_json_ams_array("ofn.admin",
"all_enterprises",
all_enterprises,
@all_enterprises,
Api::Admin::BasicEnterpriseSerializer)
end
def admin_inject_enterprise_relationships(enterprise_relationships)
def admin_inject_enterprise_relationships
admin_inject_json_ams_array "ofn.admin",
"enterprise_relationships",
enterprise_relationships,
@enterprise_relationships,
Api::Admin::EnterpriseRelationshipSerializer
end
def admin_inject_enterprise_roles(enterprise_roles)
def admin_inject_enterprise_roles
admin_inject_json_ams_array "ofn.admin",
"enterpriseRoles",
enterprise_roles,
@enterprise_roles,
Api::Admin::EnterpriseRoleSerializer
end
def admin_inject_payment_methods(payment_methods)
def admin_inject_payment_methods
admin_inject_json_ams_array "admin.paymentMethods",
"paymentMethods",
payment_methods,
@payment_methods,
Api::Admin::IdNameSerializer
end
def admin_inject_payment_method(payment_method)
def admin_inject_payment_method
admin_inject_json_ams "admin.paymentMethods",
"paymentMethod",
payment_method,
@payment_method,
Api::Admin::PaymentMethodSerializer
end
def admin_inject_shipping_methods(shipping_methods)
def admin_inject_shipping_methods
admin_inject_json_ams_array "admin.shippingMethods",
"shippingMethods",
shipping_methods,
@shipping_methods,
Api::Admin::IdNameSerializer
end
def admin_inject_shipping_method(shipping_method)
def admin_inject_shipping_method
admin_inject_json_ams "admin.shippingMethods",
"shippingMethod",
shipping_method,
@shipping_method,
Api::Admin::ShippingMethodSerializer
end
def admin_inject_shops(shops, opts = {})
def admin_inject_shops(opts = {})
opts.reverse_merge!(module: 'admin.customers')
admin_inject_json_ams_array opts[:module],
"shops",
shops,
@shops,
Api::Admin::IdNameSerializer
end
@@ -78,26 +78,26 @@ module Admin
Api::CountrySerializer
end
def admin_inject_hubs(hubs, opts = {})
def admin_inject_hubs(opts = {})
opts.reverse_merge!(module: 'ofn.admin')
admin_inject_json_ams_array opts[:module],
"hubs",
hubs,
@hubs,
Api::Admin::IdNameSerializer
end
def admin_inject_producers(producers, opts = {})
def admin_inject_producers(opts = {})
opts.reverse_merge!(module: 'ofn.admin')
admin_inject_json_ams_array opts[:module],
"producers",
producers,
@producers,
Api::Admin::IdNameSerializer
end
def admin_inject_inventory_items(inventory_items, opts = { module: 'ofn.admin' })
def admin_inject_inventory_items(opts = { module: 'ofn.admin' })
admin_inject_json_ams_array opts[:module],
"inventoryItems",
inventory_items,
@inventory_items,
Api::Admin::InventoryItemSerializer
end
@@ -117,69 +117,69 @@ module Admin
Api::CurrencyConfigSerializer
end
def admin_inject_enterprise_permissions(enterprise)
def admin_inject_enterprise_permissions
permissions =
{ can_manage_shipping_methods: can?(:manage_shipping_methods, enterprise),
can_manage_payment_methods: can?(:manage_payment_methods, enterprise),
can_manage_enterprise_fees: can?(:manage_enterprise_fees, enterprise) }
{ can_manage_shipping_methods: can?(:manage_shipping_methods, @enterprise),
can_manage_payment_methods: can?(:manage_payment_methods, @enterprise),
can_manage_enterprise_fees: can?(:manage_enterprise_fees, @enterprise) }
admin_inject_json "admin.enterprises", "enterprisePermissions", permissions
end
def admin_inject_hub_permissions(hub_permissions)
def admin_inject_hub_permissions
render partial: "admin/json/injection_ams", locals: { ngModule: "admin.variantOverrides",
name: "hubPermissions",
json: hub_permissions.to_json }
json: @hub_permissions.to_json }
end
def admin_inject_tax_categories(tax_categories, opts = { module: 'ofn.admin' })
def admin_inject_tax_categories(opts = { module: 'ofn.admin' })
admin_inject_json_ams_array opts[:module],
"tax_categories",
tax_categories,
@tax_categories,
Api::Admin::TaxCategorySerializer
end
def admin_inject_taxons(taxons)
def admin_inject_taxons
admin_inject_json_ams_array "admin.taxons",
"taxons",
taxons,
@taxons,
Api::Admin::TaxonSerializer
end
def admin_inject_users(users)
def admin_inject_users
admin_inject_json_ams_array "ofn.admin",
"users",
users,
@users,
Api::Admin::UserSerializer
end
def admin_inject_variant_overrides(variant_overrides)
def admin_inject_variant_overrides
admin_inject_json_ams_array "admin.variantOverrides",
"variantOverrides",
variant_overrides,
@variant_overrides,
Api::Admin::VariantOverrideSerializer
end
def admin_inject_order_cycle_instance(order_cycle)
def admin_inject_order_cycle_instance
render partial: "admin/json/injection_ams",
locals: { ngModule: 'admin.orderCycles',
name: 'ocInstance',
json: "{coordinator_id: '#{order_cycle.coordinator.id}'}" }
json: "{coordinator_id: '#{@order_cycle.coordinator.id}'}" }
end
def admin_inject_order_cycles(order_cycles)
def admin_inject_order_cycles
admin_inject_json_ams_array "admin.orders",
"orderCycles",
order_cycles,
@order_cycles,
Api::Admin::BasicOrderCycleSerializer,
current_user: spree_current_user
end
def admin_inject_spree_api_key(spree_api_key)
def admin_inject_spree_api_key
render partial: "admin/json/injection_ams",
locals: { ngModule: 'admin.indexUtils',
name: 'SpreeApiKey',
json: "'#{spree_api_key}'" }
json: "'#{@spree_api_key}'" }
end
def admin_inject_available_units
@@ -192,16 +192,16 @@ module Admin
json = data.to_json
render partial: "admin/json/injection_ams",
locals: { ngModule: ng_module,
name:,
json: }
name: name,
json: json }
end
def admin_inject_json_ams(ng_module, name, data, serializer, opts = {})
json = serializer.new(data, { scope: spree_current_user }.merge(opts)).to_json
render partial: "admin/json/injection_ams",
locals: { ngModule: ng_module,
name:,
json: }
name: name,
json: json }
end
def admin_inject_json_ams_array(ng_module, name, data, serializer, opts = {})
@@ -210,8 +210,8 @@ module Admin
render partial: "admin/json/injection_ams",
locals: { ngModule: ng_module,
name:,
json: }
name: name,
json: json }
end
end
end

View File

@@ -23,9 +23,8 @@ module ApplicationHelper
end
end
# Checks weather a feature is enabled for any of the given actors.
def feature?(feature, *actors)
OpenFoodNetwork::FeatureToggle.enabled?(feature, *actors)
def feature?(feature, user = nil)
OpenFoodNetwork::FeatureToggle.enabled?(feature, user)
end
def language_meta_tags
@@ -55,10 +54,10 @@ module ApplicationHelper
end
end
def body_classes(hide_menu, shopfront_layout)
def body_classes
classes = []
classes << "off-canvas" unless hide_menu
classes << shopfront_layout
classes << "off-canvas" unless @hide_menu
classes << @shopfront_layout
end
def pdf_stylesheet_pack_tag(source)

View File

@@ -95,7 +95,7 @@ module CheckoutHelper
"ng-class" => "{error: !fieldValid('#{path}')}"
}.merge args
render "shared/validated_input", name:, path:, attributes:
render "shared/validated_input", name: name, path: path, attributes: attributes
end
def validated_select(name, path, options, args = {})
@@ -106,8 +106,8 @@ module CheckoutHelper
"ng-class" => "{error: !fieldValid('#{path}')}"
}.merge args
render "shared/validated_select", name:, path:, options:,
attributes:
render "shared/validated_select", name: name, path: path, options: options,
attributes: attributes
end
def payment_method_price(method, order)

View File

@@ -6,7 +6,7 @@ module DiscourseHelper
end
def discourse_url
ENV.fetch('DISCOURSE_URL', nil)
ENV['DISCOURSE_URL']
end
def discourse_login_url

View File

@@ -1,12 +1,6 @@
# frozen_string_literal: true
module I18nHelper
def locale_options
OpenFoodNetwork::I18nConfig.available_locales.map do |locale|
[t('language_name', locale:), locale]
end
end
def set_locale
UserLocaleSetter.new(spree_current_user, params[:locale], cookies).set_locale
end

View File

@@ -12,7 +12,7 @@ module InjectionHelper
"enterprises",
enterprises || default_enterprise_query,
Api::EnterpriseSerializer,
enterprise_injection_data,
enterprise_injection_data
)
end
@@ -56,16 +56,15 @@ module InjectionHelper
inject_json_array "enterprises",
enterprises_and_relatives,
Api::EnterpriseSerializer,
enterprise_injection_data
Api::EnterpriseSerializer, enterprise_injection_data
end
def inject_group_enterprises(group)
def inject_group_enterprises
inject_json_array(
"enterprises",
group.enterprises.activated.visible.all,
@group.enterprises.activated.visible.all,
Api::EnterpriseSerializer,
enterprise_injection_data,
enterprise_injection_data
)
end
@@ -80,14 +79,14 @@ module InjectionHelper
inject_json "currentOrder",
current_order,
Api::CurrentOrderSerializer,
current_distributor:,
current_order_cycle:
current_distributor: current_distributor,
current_order_cycle: current_order_cycle
end
def inject_current_order_cycle
serializer = Api::OrderCycleSerializer.new(current_order_cycle)
json = serializer.object.present? ? serializer.to_json : "{}"
render partial: "json/injection_ams", locals: { name: "orderCycleData", json: }
render partial: "json/injection_ams", locals: { name: "orderCycleData", json: json }
end
def inject_taxons
@@ -106,18 +105,29 @@ module InjectionHelper
inject_json "openStreetMapConfig", {}, Api::OpenStreetMapConfigSerializer
end
def inject_spree_api_key(spree_api_key)
def inject_spree_api_key
render partial: "json/injection_ams",
locals: { name: 'spreeApiKey', json: "'#{spree_api_key}'" }
locals: { name: 'spreeApiKey', json: "'#{@spree_api_key}'" }
end
def inject_available_countries
inject_json_array "availableCountries", available_countries, Api::CountrySerializer
end
def inject_enterprise_attributes(enterprise_attributes)
def inject_enterprise_attributes
render partial: "json/injection_ams",
locals: { name: 'enterpriseAttributes', json: enterprise_attributes.to_json.to_s }
locals: { name: 'enterpriseAttributes', json: @enterprise_attributes.to_json.to_s }
end
def inject_orders
inject_json_array "orders", @orders.all, Api::OrderSerializer
end
def inject_shops
customers = spree_current_user.customers
shops = Enterprise.where(id: @orders.pluck(:distributor_id).uniq |
customers.pluck(:enterprise_id))
inject_json_array "shops", shops.all, Api::ShopForOrdersSerializer
end
def inject_saved_credit_cards
@@ -144,7 +154,7 @@ module InjectionHelper
def inject_json(name, data, serializer, opts = {})
serializer_instance = serializer.new(data, opts)
json = serializer_instance.to_json
render partial: "json/injection_ams", locals: { name:, json: }
render partial: "json/injection_ams", locals: { name: name, json: json }
end
private

View File

@@ -55,8 +55,8 @@ module OrderCyclesHelper
end
end
def active_order_cycle_for_distributor?(distributor)
OrderCycle.active.with_distributor(distributor).present?
def active_order_cycle_for_distributor?(_distributor)
OrderCycle.active.with_distributor(@distributor).present?
end
def simple_index

View File

@@ -4,7 +4,7 @@ module SerializerHelper
def ids_to_objs(ids)
return [] if ids.blank?
ids.map { |id| { id: } }
ids.map { |id| { id: id } }
end
# Returns an array of the fields a serializer needs from it's object

View File

@@ -1,8 +1,8 @@
# frozen_string_literal: true
module ShopHelper
def oc_select_options(order_cycles)
order_cycles.map { |oc| { time: pickup_time(oc), id: oc.id } }
def oc_select_options
@order_cycles.map { |oc| { time: pickup_time(oc), id: oc.id } }
end
def require_customer?
@@ -40,17 +40,20 @@ module ShopHelper
}
end
def shop_tab_names
shop_tabs.map { |tab| tab[:name] }
end
def show_home_tab?
require_customer? || current_distributor.preferred_shopfront_message.present?
end
def shopfront_closed_message?(order_cycles)
no_open_order_cycles?(order_cycles) && \
current_distributor.preferred_shopfront_closed_message.present?
def shopfront_closed_message?
no_open_order_cycles? && current_distributor.preferred_shopfront_closed_message.present?
end
def no_open_order_cycles?(order_cycles)
@no_open_order_cycles ||= order_cycles&.empty?
def no_open_order_cycles?
@no_open_order_cycles ||= @order_cycles&.empty?
end
def show_shopping_cta?

View File

@@ -129,7 +129,7 @@ module Spree
link_to_with_icon('icon-plus',
name,
'javascript:',
data: { target: },
data: { target: target },
class: css_classes)
end

View File

@@ -3,7 +3,7 @@
module Spree
module Admin
module NavigationHelper
# Make an admin tab that covers one or more resources supplied by symbols
# Make an admin tab that coveres one or more resources supplied by symbols
# Option hash may follow. Valid options are
# * :label to override link text, otherwise based on the first resource name (translated)
# * :route to override automatically determining the default route

View File

@@ -4,7 +4,7 @@ module TaxHelper
def display_taxes(taxable, display_zero: true)
if !taxable.included_tax_total.zero?
amount = Spree::Money.new(taxable.included_tax_total, currency: taxable.currency)
I18n.t(:tax_amount_included, amount:)
I18n.t(:tax_amount_included, amount: amount)
elsif !taxable.additional_tax_total.zero?
Spree::Money.new(taxable.additional_tax_total, currency: taxable.currency)
elsif display_zero

View File

@@ -5,9 +5,13 @@ class BulkInvoiceJob < ApplicationJob
delegate :render, to: ActionController::Base
def perform(order_ids, filepath, options = {})
orders = sorted_orders(order_ids)
orders.filter!(&:invoiceable?) if OpenFoodNetwork::FeatureToggle.enabled?(:invoices)
orders.each(&method(:generate_invoice))
pdf = CombinePDF.new
sorted_orders(order_ids).each do |order|
invoice = renderer.render_to_string(order)
pdf << CombinePDF.parse(invoice)
end
ensure_directory_exists filepath
@@ -28,17 +32,6 @@ class BulkInvoiceJob < ApplicationJob
@renderer ||= InvoiceRenderer.new
end
def generate_invoice(order)
renderer_data = if OpenFoodNetwork::FeatureToggle.enabled?(:invoices)
OrderInvoiceGenerator.new(order).generate_or_update_latest_invoice
order.invoices.first.presenter
else
order
end
invoice = renderer.render_to_string(renderer_data)
pdf << CombinePDF.parse(invoice)
end
def broadcast(filepath, channel)
file_id = filepath.split("/").last.split(".").first
@@ -54,8 +47,4 @@ class BulkInvoiceJob < ApplicationJob
def ensure_directory_exists(filepath)
FileUtils.mkdir_p(File.dirname(filepath))
end
def pdf
@pdf ||= CombinePDF.new
end
end

View File

@@ -26,7 +26,7 @@ class ReportJob < ApplicationJob
def email_result(user, blob)
ReportMailer.with(
to: user.email,
blob:,
blob: blob,
).report_ready.deliver_later
end

View File

@@ -17,7 +17,7 @@ class WebhookDeliveryJob < ApplicationJob
body = {
id: job_id,
at: Time.zone.now.to_s,
event:,
event: event,
data: payload,
}

View File

@@ -69,7 +69,7 @@ class JsonApiSchema
attributes: {
type: :object,
properties: attributes,
required:
required: required
},
relationships: {
type: :object,

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