Compare commits

...

188 Commits

Author SHA1 Message Date
David Cook
90288b8cbf Merge pull request #13492 from chahmedejaz/revert/dependabot/bundler/rexml-3.3.9
500 Error preventing check out with Stripe payment method
2025-08-19 10:50:09 +10:00
Ahmed Ejaz
c821b0a285 revert "Bump rexml from 3.2.9 to 3.3.9" 2025-08-19 05:27:58 +05:00
Gaetan Craig-Riou
c37376d67e Merge pull request #13485 from openfoodfoundation/dependabot/bundler/bullet-8.0.8
Bump bullet from 7.1.6 to 8.0.8
2025-08-18 13:58:39 +10:00
dependabot[bot]
49ec5b2089 Bump bullet from 7.1.6 to 8.0.8
Bumps [bullet](https://github.com/flyerhzm/bullet) from 7.1.6 to 8.0.8.
- [Changelog](https://github.com/flyerhzm/bullet/blob/main/CHANGELOG.md)
- [Commits](https://github.com/flyerhzm/bullet/compare/7.1.6...8.0.8)

---
updated-dependencies:
- dependency-name: bullet
  dependency-version: 8.0.8
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-15 09:56:17 +00:00
Maikel
92ef5fe3d5 Merge pull request #13483 from openfoodfoundation/dependabot/bundler/pdf-reader-2.15.0
Bump pdf-reader from 2.12.0 to 2.15.0
2025-08-15 10:29:09 +10:00
Filipe
ae477b7e52 Merge pull request #13455 from kirst-n/13366-only-load-necessary-flatpickr
Optimise loading of language-specific date pickers
2025-08-14 23:24:59 +01:00
Filipe
0e191e5fca Merge pull request #13459 from rioug/13454-fix-redeemeing-voucher-when-using-paypal
[VINE] Redeem voucher before redirecting to payment url
2025-08-14 16:30:22 +01:00
dependabot[bot]
64f9ea6fc0 Bump pdf-reader from 2.12.0 to 2.15.0
Bumps [pdf-reader](https://github.com/yob/pdf-reader) from 2.12.0 to 2.15.0.
- [Changelog](https://github.com/yob/pdf-reader/blob/main/CHANGELOG)
- [Commits](https://github.com/yob/pdf-reader/compare/v2.12.0...v2.15.0)

---
updated-dependencies:
- dependency-name: pdf-reader
  dependency-version: 2.15.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-14 10:01:32 +00:00
Maikel
058c6749da Merge pull request #13477 from openfoodfoundation/dependabot/bundler/timecop-0.9.10
Bump timecop from 0.9.8 to 0.9.10
2025-08-14 13:20:18 +10:00
Maikel
2d15ec4458 Merge pull request #13478 from openfoodfoundation/dependabot/bundler/activerecord-7.1.5.2
Bump activerecord from 7.1.5.1 to 7.1.5.2
2025-08-14 13:18:58 +10:00
dependabot[bot]
56eaa8bb98 Bump activerecord from 7.1.5.1 to 7.1.5.2
Bumps [activerecord](https://github.com/rails/rails) from 7.1.5.1 to 7.1.5.2.
- [Release notes](https://github.com/rails/rails/releases)
- [Changelog](https://github.com/rails/rails/blob/v8.0.2.1/activerecord/CHANGELOG.md)
- [Commits](https://github.com/rails/rails/compare/v7.1.5.1...v7.1.5.2)

---
updated-dependencies:
- dependency-name: activerecord
  dependency-version: 7.1.5.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-14 01:21:32 +00:00
dependabot[bot]
1e1f1e1e1b Bump timecop from 0.9.8 to 0.9.10
Bumps [timecop](https://github.com/travisjeffery/timecop) from 0.9.8 to 0.9.10.
- [Changelog](https://github.com/travisjeffery/timecop/blob/master/History.md)
- [Commits](https://github.com/travisjeffery/timecop/compare/v0.9.8...v0.9.10)

---
updated-dependencies:
- dependency-name: timecop
  dependency-version: 0.9.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 09:58:01 +00:00
Maikel
1f8a9f9c76 Merge pull request #13470 from openfoodfoundation/dependabot/bundler/rack-2.2.14
Bump rack from 2.2.11 to 2.2.14
2025-08-13 12:45:59 +10:00
Maikel
b1893942ac Merge pull request #13469 from openfoodfoundation/dependabot/bundler/net-imap-0.4.20
Bump net-imap from 0.4.10 to 0.4.20
2025-08-13 12:40:50 +10:00
dependabot[bot]
ad59ed4d40 Bump rack from 2.2.11 to 2.2.14
Bumps [rack](https://github.com/rack/rack) from 2.2.11 to 2.2.14.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v2.2.11...v2.2.14)

---
updated-dependencies:
- dependency-name: rack
  dependency-version: 2.2.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-13 00:02:14 +00:00
Maikel
8491a167ed Merge pull request #13350 from mkllnk/dfc-ui
Add permission module with example data
2025-08-13 10:00:53 +10:00
dependabot[bot]
59277292fb Bump net-imap from 0.4.10 to 0.4.20
Bumps [net-imap](https://github.com/ruby/net-imap) from 0.4.10 to 0.4.20.
- [Release notes](https://github.com/ruby/net-imap/releases)
- [Commits](https://github.com/ruby/net-imap/compare/v0.4.10...v0.4.20)

---
updated-dependencies:
- dependency-name: net-imap
  dependency-version: 0.4.20
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 06:54:11 +00:00
Gaetan Craig-Riou
354a7ab687 Merge pull request #13465 from openfoodfoundation/dependabot/bundler/fugit-1.11.1
Bump fugit from 1.8.1 to 1.11.1
2025-08-12 15:06:20 +10:00
Gaetan Craig-Riou
a5a1ee9bd9 Merge pull request #13462 from openfoodfoundation/dependabot/bundler/thor-1.4.0
Bump thor from 1.3.1 to 1.4.0
2025-08-12 15:02:23 +10:00
Gaetan Craig-Riou
ad3f78ef69 Merge pull request #13466 from openfoodfoundation/dependabot/bundler/uri-0.13.2
Bump uri from 0.13.0 to 0.13.2
2025-08-12 14:58:40 +10:00
Gaetan Craig-Riou
e02497b163 Merge pull request #13467 from openfoodfoundation/dependabot/bundler/rails-html-sanitizer-1.6.1
Bump rails-html-sanitizer from 1.6.0 to 1.6.1
2025-08-12 14:57:59 +10:00
Gaetan Craig-Riou
7d2d94398f Merge pull request #13464 from openfoodfoundation/dependabot/bundler/cgi-0.3.7
Bump cgi from 0.3.6 to 0.3.7
2025-08-12 14:49:47 +10:00
Gaetan Craig-Riou
0ecf004ff2 Merge pull request #13463 from openfoodfoundation/dependabot/bundler/rexml-3.3.9
Bump rexml from 3.2.9 to 3.3.9
2025-08-12 14:48:06 +10:00
dependabot[bot]
444f448207 Bump rails-html-sanitizer from 1.6.0 to 1.6.1
Bumps [rails-html-sanitizer](https://github.com/rails/rails-html-sanitizer) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/rails/rails-html-sanitizer/releases)
- [Changelog](https://github.com/rails/rails-html-sanitizer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rails/rails-html-sanitizer/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: rails-html-sanitizer
  dependency-version: 1.6.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 04:31:53 +00:00
dependabot[bot]
d9381b23d7 Bump uri from 0.13.0 to 0.13.2
Bumps [uri](https://github.com/ruby/uri) from 0.13.0 to 0.13.2.
- [Release notes](https://github.com/ruby/uri/releases)
- [Commits](https://github.com/ruby/uri/compare/v0.13.0...v0.13.2)

---
updated-dependencies:
- dependency-name: uri
  dependency-version: 0.13.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 04:31:50 +00:00
dependabot[bot]
6a9a2884d6 Bump fugit from 1.8.1 to 1.11.1
Bumps [fugit](https://github.com/floraison/fugit) from 1.8.1 to 1.11.1.
- [Changelog](https://github.com/floraison/fugit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/floraison/fugit/compare/v1.8.1...v1.11.1)

---
updated-dependencies:
- dependency-name: fugit
  dependency-version: 1.11.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 04:31:11 +00:00
dependabot[bot]
70edd4b898 Bump cgi from 0.3.6 to 0.3.7
Bumps [cgi](https://github.com/ruby/cgi) from 0.3.6 to 0.3.7.
- [Release notes](https://github.com/ruby/cgi/releases)
- [Commits](https://github.com/ruby/cgi/compare/v0.3.6...v0.3.7)

---
updated-dependencies:
- dependency-name: cgi
  dependency-version: 0.3.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 04:31:07 +00:00
dependabot[bot]
b57a2befd9 Bump rexml from 3.2.9 to 3.3.9
Bumps [rexml](https://github.com/ruby/rexml) from 3.2.9 to 3.3.9.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.9...v3.3.9)

---
updated-dependencies:
- dependency-name: rexml
  dependency-version: 3.3.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 04:27:20 +00:00
dependabot[bot]
fef9a78198 Bump thor from 1.3.1 to 1.4.0
Bumps [thor](https://github.com/rails/thor) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/rails/thor/releases)
- [Commits](https://github.com/rails/thor/compare/v1.3.1...v1.4.0)

---
updated-dependencies:
- dependency-name: thor
  dependency-version: 1.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 04:24:48 +00:00
Maikel
dd86736170 Merge pull request #13461 from openfoodfoundation/dependabot/bundler/webrick-1.8.2
Bump webrick from 1.8.1 to 1.8.2
2025-08-12 13:54:23 +10:00
Maikel
0d8c7ef118 Merge pull request #13460 from openfoodfoundation/dependabot/bundler/nokogiri-1.18.9
Bump nokogiri from 1.16.5 to 1.18.9
2025-08-12 13:52:57 +10:00
dependabot[bot]
20730b8768 Bump webrick from 1.8.1 to 1.8.2
Bumps [webrick](https://github.com/ruby/webrick) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/ruby/webrick/releases)
- [Commits](https://github.com/ruby/webrick/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: webrick
  dependency-version: 1.8.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 03:43:07 +00:00
dependabot[bot]
ad7c69189b Bump nokogiri from 1.16.5 to 1.18.9
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.16.5 to 1.18.9.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.16.5...v1.18.9)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-version: 1.18.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 03:22:15 +00:00
Maikel Linke
3a72aefc1c Fail test when timeout reached
In this example it didn't matter but if we re-use the helper then it
needs to raise an error after the timeout has been reached.
2025-08-12 09:54:48 +10:00
David Cook
e855ea0dbd Merge pull request #13453 from mkllnk/rails-7.1
Allow only existing deprecations in Rails 7.1
2025-08-12 09:44:25 +10:00
David Cook
1eba950e19 Merge pull request #13451 from mkllnk/remove-person
Remove Person from product catalog
2025-08-12 09:42:40 +10:00
David Cook
9cd04c087e Merge pull request #13450 from mkllnk/engine-ruby-version
Losen engine gemspec requirement for Dependabot
2025-08-12 09:40:01 +10:00
David Cook
459d25e533 Merge pull request #13447 from mkllnk/cover-rake
Report code coverage on rake tasks
2025-08-12 09:37:51 +10:00
Kirst
b06e562425 Remove flatpickr controller test that isn't useful based on feedback 2025-08-11 20:29:11 +10:00
Kirst
2936cfebca Remove I18n assignment from flatpickr controller test from feedback 2025-08-11 20:29:11 +10:00
Kirst
b8ad428b5d Load only specified flatpickr locale
Dynamically import only the requested flatpickr locale.

English locale is bundled by default, so passing null triggers flatpickr's built-in English fallback without an explicit import.
2025-08-11 20:28:47 +10:00
Maikel Linke
ca34d24847 Replace long waits with better polling
Capybara polls under the hood as well. So we do something similar here
but tailored to the tested code. This reduced the test run time on my
machine from 35 seconds to 15 seconds.
2025-08-11 17:07:44 +10:00
Maikel Linke
6e581fce75 Remove unnecessary styling
The early dev versions of the DPM needed these styles. Now the module
looks fine without any additional styles.
2025-08-11 16:29:08 +10:00
Gaetan Craig-Riou
66041061fb Redeem VINE voucher before redirecting to payment url
When using paypal, we need to redeem the voucher before redirecting to
the payment gateway url, otherwise the voucher will never get redeemed.
2025-08-11 16:28:04 +10:00
Maikel Linke
e54c27c900 Use more precise regex 2025-08-11 16:23:08 +10:00
Maikel Linke
b3d3d6bf06 Allow DPM feature for specific users 2025-08-11 15:25:50 +10:00
Maikel Linke
5876c52318 Test all known scopes 2025-08-11 15:15:00 +10:00
Maikel Linke
842f4ae40e Re-enable CSRF check supported by DPM now 2025-08-11 15:13:35 +10:00
Maikel Linke
342ef4e9eb Complete smoke test of DFC data sharing
Working within a shadow root of the web component isn't well supported
by Capybara and I needed to find some workarounds. It's not pretty but
it works (on my machine). *fingers crossed*
2025-08-08 14:00:41 +10:00
Maikel Linke
210201514e Add gem capybara-shadowdom to access web component 2025-08-08 14:00:41 +10:00
Maikel Linke
2d3f18a71b Load DFC Permissions module in system spec
But we can't access the inside of the component yet.
2025-08-08 14:00:41 +10:00
Maikel Linke
9d284b7110 Set language to display scope labels 2025-08-08 14:00:41 +10:00
Maikel Linke
994f1ca6c6 Update scope ids 2025-08-08 14:00:41 +10:00
Maikel Linke
f65e4797cf Add feature toggle for DFC dev platform 2025-08-08 14:00:40 +10:00
Maikel Linke
52aeec5ac4 Update and list scopes for real 2025-08-08 14:00:40 +10:00
Maikel Linke
7032b3f463 Add endpoint to update scopes of platform
Dummy implementation only.
2025-08-08 14:00:40 +10:00
Maikel Linke
c26686b430 Add DfcPermission model to persist granted scopes 2025-08-08 14:00:38 +10:00
Maikel Linke
60c8f4ee20 Add DFC API endpoint for listing platforms
Only listing example JSON for now.

This is not part of the official DFC API but it's a DFC-related API and
therefore we put it in the same namespace.

The DFC Permission Module will make authenticated requests to grant
certain platforms certain permissions.
2025-08-08 14:00:01 +10:00
Maikel Linke
25f396c126 Add permission module with example data
It's basically just copied from the Readme file still pointing to the
development server and it's not interacting with OFN just yet.
2025-08-08 14:00:01 +10:00
Maikel Linke
0166abcd2a Remove deprecated and unnecessary config 2025-08-08 11:18:34 +10:00
Maikel Linke
4cd0071dd4 Allow only existing deprecations
* Allow deprecated cache_format_version
* Allow deprecated Rails.application.secrets
* Allow deprecated Passing the class as positional argument
* Allow deprecated alias_attribute with non-attribute targets
* Allow deprecated model aliases
* Allow deprecated action_dispatch.show_exceptions
2025-08-08 11:17:41 +10:00
Maikel Linke
32c96b72ad Update all locales with the latest Transifex translations 2025-08-08 10:24:04 +10:00
Maikel
30701d61e2 Merge pull request #13232 from chitty/rails7.1
Bump Rails to 7.1
2025-08-08 10:09:58 +10:00
Carlos Chitty
45b712ddcd Set latest invoce date explicitly in Orders::GenerateInvoiceService test
Solves CI failure:
https://github.com/openfoodfoundation/openfoodnetwork/actions/runs/14760883756/job/41441014958?pr=13232
2025-08-07 17:44:52 +02:00
Carlos Chitty
3153e99497 Update OpenOrderCycleJob test "syncing remote products" to expect 58 queries instead of 59
The main point of the test is to alert us if the query count increased (https://github.com/openfoodfoundation/openfoodnetwork/pull/13232#discussion_r2199896280).
The missing query in rails 7.1:

Spree::StockItem Load  SELECT "spree_stock_items"."id", "spree_stock_items"."variant_id", "spree_stock_items"."count_on_hand", "spree_stock_items"."created_at", "spree_stock_items"."updated_at", "spree_stock_items"."backorderable", "spree_stock_items"."deleted_at", "spree_stock_items"."lock_version" FROM "spree_stock_items" WHERE "spree_stock_items"."id" = $1 LIMIT $2 FOR UPDATE
2025-08-07 17:44:52 +02:00
Carlos Chitty
a2f263e081 User Rails.env.local?
https://github.com/rails/rails/pull/46786
Solves rubocop failure in rails 7.1 bump branch https://github.com/openfoodfoundation/openfoodnetwork/actions/runs/14739687970/job/41374340281?pr=13232
2025-08-07 17:44:52 +02:00
Carlos Chitty
3cb6a2617b Do not fail tests on deprecation warnings for the next rails version (7.2) 2025-08-07 17:44:52 +02:00
Carlos Chitty
420deca437 Bump rails from 7.0.8 to 7.1.5.1 2025-08-07 17:44:52 +02:00
Filipe
76aebf8a72 Merge pull request #13436 from chahmedejaz/task/13432-decommission-old-products-screen
Decommission Old Products UI and Related Code
2025-08-07 17:39:43 +02:00
Maikel Linke
1ec570375f Remove Person from product catalog
Early versions of the DFC standard demanded that all data is published
in relationship to the authenticated user. But that is not necessary
anymore and can add complications when a platform is authenticated as
client user.
2025-08-07 14:56:35 +10:00
Maikel Linke
75c33b29d5 Losen engine gemspec requirement for Dependabot
Dependabot doesn't seem to be able to resolve the version correctly. We
got this message:

```
Could not find compatible versions

Because every version of web depends on Ruby = 0.0.1
  and Gemfile depends on web >= 0,
  Ruby = 0.0.1 is required.
So, because current Ruby version is = 3.1.4,
  version solving has failed.
```
2025-08-07 12:59:32 +10:00
Maikel
1bfff91c72 Merge pull request #13449 from openfoodfoundation/dependabot/npm_and_yarn/tmp-0.2.4
Bump tmp from 0.2.1 to 0.2.4
2025-08-07 10:24:15 +10:00
Maikel Linke
d469552afc Fix schema version 2025-08-07 10:13:14 +10:00
dependabot[bot]
e6cffde8fb Bump tmp from 0.2.1 to 0.2.4
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.1 to 0.2.4.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.1...v0.2.4)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-06 18:31:11 +00:00
Maikel
910ded1a8c Typo
[skip ci]
2025-08-05 13:49:44 +10:00
Maikel Linke
2555a9e710 Ignore breaking code coverage for coverage spec
When we test our code coverage compilation, it breaks the code coverage
report for the current rspec process. By running that code separately,
we gain a correct coverage report for the rest of the code again.

So unfortunately, we can't report on the code coverage of this
particular task and have to ignore it. But at least CI depends on the
correct function of this task and would fail if it didn't work.
2025-08-05 12:45:05 +10:00
Maikel Linke
f532c4712e Load rake tasks only once for code coverage
Apparently, Rake's way of reloading the task code confuses the code
coverage report. Code tested by rake task specs was not recognised as
covered even though it was.
2025-08-05 12:44:13 +10:00
Gaetan Craig-Riou
3af28c4b5b Merge pull request #13441 from mkllnk/cover-more
Check more code for coverage
2025-08-04 16:49:56 +10:00
David Cook
63b864253d Merge pull request #13442 from mkllnk/puffing-billy
Add gem puffing-billy to record browser requests
2025-08-04 12:35:34 +10:00
David Cook
8efeec4301 Merge pull request #13440 from mkllnk/tidy
Remove unused test helper
2025-08-04 12:03:43 +10:00
Maikel Linke
bed33928e0 Declare simplecov as direct dependency
The undercover docs recommended to remove it from the Gemfile but that's
only valid if you use only undercover. We do rely directly on the
simplecov gem to generate reports though.
2025-08-04 11:55:26 +10:00
Maikel Linke
bb7a31b286 Update all locales with the latest Transifex translations 2025-08-01 12:37:40 +10:00
Ahmed Ejaz
75b2fe1dd4 revert API removals 2025-08-01 01:48:21 +05:00
Maikel Linke
c0924fbe5e Use new Undercover formatter for :nocov: support 2025-07-31 14:56:17 +10:00
Maikel Linke
d72bc49409 Compare coverage to upstream master when on fork 2025-07-31 14:56:17 +10:00
Maikel Linke
06867ff7ea Remove unnecessary simplecov filters
* /schemas doesn't exist.
* /lib/generators doesn't exist.
* /vendor doesn't contain rb files.
* /public doesn't contain rb files.
* /swagger doesn't contain rb files.
* /log doens't contain rb files.
2025-07-31 14:56:17 +10:00
Maikel Linke
76a1fe7767 Ignore inaccurate coverage of rake tasks
I tried several ways to get code coverage for rake tasks but I haven't
succeeded yet. Somehow rake is confusing simplecov.
2025-07-31 14:56:11 +10:00
Maikel Linke
3363c523ea Check more code for coverage
* ApplicationJob should be covered by tests.
* Spec should all be executed, except `xit` which should be avoided and
  can be flagged.
2025-07-31 14:53:51 +10:00
Maikel
91628f8daa Merge pull request #13443 from openfoodfoundation/dependabot/npm_and_yarn/floating-ui/dom-1.7.3
Bump @floating-ui/dom from 1.7.2 to 1.7.3
2025-07-31 11:52:14 +10:00
dependabot[bot]
f3dfbab109 Bump @floating-ui/dom from 1.7.2 to 1.7.3
Bumps [@floating-ui/dom](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/dom) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/floating-ui/floating-ui/releases)
- [Changelog](https://github.com/floating-ui/floating-ui/blob/master/packages/dom/CHANGELOG.md)
- [Commits](https://github.com/floating-ui/floating-ui/commits/@floating-ui/dom@1.7.3/packages/dom)

---
updated-dependencies:
- dependency-name: "@floating-ui/dom"
  dependency-version: 1.7.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-30 10:00:39 +00:00
Maikel Linke
d01474ebcd Ignore Chrome's automatic requests to Google services 2025-07-30 12:25:34 +10:00
Maikel Linke
a062a7b697 Add Billy proxy to Chrome in system specs
And demonstrate the use of puffing-billy browser proxy.

Billy can cache and record responses to browser requests. For that to
work we need to allow network connections and disable VCR. But instead I
found that the Billy proxy is just like any other Ruby backend code and
its connections can be recorded with VCR instead.

And instead of stubbing requests via Billy.proxy, we can use standard
Webmock `stub_request`. Now we use puffing-billy just to relay browser
requests via our Ruby app.
2025-07-29 14:37:27 +10:00
Maikel Linke
fe8b805e1f Add gem puffing-billy 2025-07-29 14:37:27 +10:00
Maikel Linke
f3f43225cb Remove unused test helper 2025-07-29 13:42:51 +10:00
Maikel
65604f5b04 Merge pull request #13437 from dacook/remove-unused-method
Remove unused method
2025-07-28 16:29:15 +10:00
David Cook
661bb29029 Remove unused method
The name doesn't appear in the codebase so I guess it's unused.
2025-07-28 13:19:16 +10:00
Gaetan Craig-Riou
b725697972 Merge pull request #13433 from mkllnk/inventory-switch-august-4
Disable inventory only for future enterprises
2025-07-28 12:02:54 +10:00
David Cook
b8546db1e5 Update date for 11th Aug 2025-07-28 11:43:31 +10:00
Gaetan Craig-Riou
bc25a5ecd6 Merge pull request #13428 from openfoodfoundation/dependabot/npm_and_yarn/jasmine-core-5.9.0
Bump jasmine-core from 5.8.0 to 5.9.0
2025-07-28 11:37:48 +10:00
Gaetan Craig-Riou
71de96e0a6 Merge pull request #13430 from openfoodfoundation/dependabot/npm_and_yarn/form-data-3.0.4
Bump form-data from 3.0.1 to 3.0.4
2025-07-28 10:50:12 +10:00
David Cook
23bcdc1cb7 Merge pull request #13434 from mkllnk/private-address-test
Provide open port for private address test
2025-07-28 09:27:07 +10:00
David Cook
60ac1c9fbe Merge pull request #13414 from mkllnk/remove-unused-hub
Remove unused instance variable
2025-07-28 09:22:15 +10:00
Ahmed Ejaz
fcd4d073c4 Update all locales with the latest Transifex translations 2025-07-27 08:02:49 +05:00
Ahmed Ejaz
df4cf4b768 Fix specs 2025-07-27 07:58:04 +05:00
Ahmed Ejaz
3f39d94bd3 Remove conditional rendering for previous page icon in pagination 2025-07-27 07:21:10 +05:00
Ahmed Ejaz
acfe3f6589 Remove admin_style_v3 feature toggle and related conditional logic 2025-07-27 07:20:16 +05:00
Ahmed Ejaz
1717c5376b Remove deprecated migrations for admin style v3 activation 2025-07-27 07:11:07 +05:00
Ahmed Ejaz
1426b6eeb7 Remove legacy admin styles in favor of v3 styling
Completes migration to the new admin v3 styling system by:
- Removing conditional stylesheet inclusion in admin head
- Deleting all legacy admin style files and components
- Making admin-style-v3 the default and only stylesheet

This change reduces maintenance overhead and simplifies the admin styling codebase by removing the old styling system that was being conditionally loaded based on feature flags.
2025-07-27 07:02:53 +05:00
Ahmed Ejaz
188b2eb754 Simplify pagination next button by removing conditional icon rendering 2025-07-27 06:44:02 +05:00
Ahmed Ejaz
6e055ddbdf Remove icon parameters from admin navigation tabs for simplification 2025-07-27 06:32:44 +05:00
Ahmed Ejaz
025fc784a8 Refactor products_return_to_url method to remove url_filters parameter and simplify usage in views 2025-07-27 06:26:12 +05:00
Ahmed Ejaz
fefd0239e6 Remove unused product image controller and directive; delete product image update route 2025-07-27 06:15:14 +05:00
Ahmed Ejaz
0fa67c69fd Remove bulk product update functionality
Removes the bulk product update feature and its associated components:
- Removes Angular-based bulk product editing controller and views
- Deletes bulk product API endpoints and related controller actions
- Removes product cloning and variant deletion functionality
- Removes associated JavaScript tests and specs

This appears to be part of a larger effort to modernize/simplify the product management interface, removing legacy Angular-based bulk editing in favor of a different approach.
2025-07-27 06:03:14 +05:00
Ahmed Ejaz
44cbe55c96 Update product routes and views for consistency and clarity 2025-07-27 05:25:50 +05:00
Filipe
56d3ac247d Merge pull request #13426 from cyrillefr/AlignPriceColumnTextToTheLeft
Align Cart Price column texts to the left
2025-07-24 22:51:36 +01:00
Filipe
0ed08f8f9d Merge pull request #13402 from mkllnk/hub-address
Hide the delivery address for pickup on checkout summary
2025-07-24 19:57:31 +01:00
Maikel Linke
667b49b7f1 Show hub address for pickup only when feature enabled 2025-07-24 15:24:32 +10:00
Maikel Linke
dd6d1ea64b Provide open port for private address test 2025-07-24 14:19:32 +10:00
Maikel Linke
f0dd1885c9 Fix: Check feature toggle for hub early 2025-07-24 13:41:26 +10:00
Maikel Linke
05b6200c8f Remove unused instance variable
I noticed this when reviewing another pull request.
2025-07-24 13:37:14 +10:00
Maikel Linke
fcd6897240 Prepare delivery details partial for conditional display 2025-07-24 13:30:40 +10:00
Maikel Linke
c23c773942 Simplify view code 2025-07-24 13:30:39 +10:00
Maikel
7af960fceb Merge pull request #13409 from filipefurtad0/sets_docker_no_sandbox_option_chrome
[Cuprite setup] Adds a DOCKER variable, to disable sandbox mode for system tests
2025-07-24 13:12:15 +10:00
Maikel Linke
45a0705379 Disable inventory only for future enterprises 2025-07-24 13:00:14 +10:00
Ahmed Ejaz
6da1200b64 Refactor product routes to remove feature toggle constraints and simplify access 2025-07-24 02:30:37 +05:00
Gaetan Craig-Riou
1cf31f4028 Merge pull request #13431 from dacook/bump-undercover
Bump undercover to 0.7.4
2025-07-23 10:36:19 +10:00
David Cook
6df71f28ca Bump undercover to 0.7.4
And its dependencies.
2025-07-23 09:42:06 +10:00
dependabot[bot]
9272d6d82f Bump form-data from 3.0.1 to 3.0.4
Bumps [form-data](https://github.com/form-data/form-data) from 3.0.1 to 3.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/v3.0.4/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v3.0.1...v3.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 3.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 22:56:43 +00:00
Filipe
a8114e42a7 Merge pull request #13381 from rioug/add-feature-flag-inventory
Add feature flag to enable inventory
2025-07-21 17:59:54 +01:00
dependabot[bot]
17e02e7304 Bump jasmine-core from 5.8.0 to 5.9.0
Bumps [jasmine-core](https://github.com/jasmine/jasmine) from 5.8.0 to 5.9.0.
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v5.8.0...v5.9.0)

---
updated-dependencies:
- dependency-name: jasmine-core
  dependency-version: 5.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 10:36:34 +00:00
Gaetan Craig-Riou
6ba80f57b3 Disable Choose products from when inventory disabled 2025-07-21 13:47:28 +10:00
Gaetan Craig-Riou
d90200fb3f Disable Coordinator inventory when inventory is off 2025-07-21 11:41:30 +10:00
cyrillefr
08114b495a Align Cart Price column texts to the left 2025-07-17 14:17:57 +02:00
filipefurtad0
7b6b3d907c Update all locales with the latest Transifex translations 2025-07-15 18:58:34 +01:00
Filipe
cf9ffd8931 Merge pull request #13419 from chahmedejaz/bugfix/13416-orders-page-inaccessible-by-admins
Orders page inaccessible as superadmin (error 504)
2025-07-14 13:50:51 +01:00
Ahmed Ejaz
e6b9373570 Refactor line items search to improve security and maintainability
Moves search field configuration from frontend to backend to prevent potential security issues with exposing internal field names. The change also improves maintainability by centralizing search logic in the controller.

Adds conditional logic to use name_alias for non-admin users when searching distributor names, enhancing data access control.
2025-07-13 18:07:14 +05:00
Ahmed Ejaz
ec44947b37 Add special handling for admin users in order permissions
Modifies order and line item permission logic to give admin users full access to all orders and line items, bypassing the regular complex joins queries to get orders editable by producers. These complex joins are needed for regular users but for user admins we need to return all orders.
2025-07-13 05:34:55 +05:00
Gaetan Craig-Riou
c0639b37bb Merge pull request #13412 from openfoodfoundation/dependabot/npm_and_yarn/hotkeys-js-3.13.15
Bump hotkeys-js from 3.13.14 to 3.13.15
2025-07-12 14:47:40 +10:00
filipefurtad0
38388be4da Only the .env.test.local file (not tracked) should be changed
This prevents contributors from inadvertently committing changes on the .env.test file (which is tracked)

Reverts changes to .env.test and changes README.md accordingly
2025-07-10 19:53:00 +01:00
filipefurtad0
352f1ba900 Adds a DOCKER env variable, to disable sandbox mode for system tests
Fixes rubocop offense - || instead of or

Comments out the DOCKER variable

Corrects the syntax for Markdown inline code
2025-07-10 19:53:00 +01:00
dependabot[bot]
7a0ecc777a Bump hotkeys-js from 3.13.14 to 3.13.15
Bumps [hotkeys-js](https://github.com/jaywcjlove/hotkeys-js) from 3.13.14 to 3.13.15.
- [Release notes](https://github.com/jaywcjlove/hotkeys-js/releases)
- [Commits](https://github.com/jaywcjlove/hotkeys-js/compare/v3.13.14...v3.13.15)

---
updated-dependencies:
- dependency-name: hotkeys-js
  dependency-version: 3.13.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-09 09:41:28 +00:00
Gaetan Craig-Riou
aeefe841bf Merge pull request #13403 from chitty/cch/image_variant_url_for
Do not try to generate a URL for unpersisted blobs in development/test environment
2025-07-09 14:15:07 +10:00
Gaetan Craig-Riou
70757ccdef Add migration to enable inventory for existing Enterprises 2025-07-09 13:48:53 +10:00
Gaetan Craig-Riou
7450f8a530 Fix spec, enable inventory when needed 2025-07-09 13:43:18 +10:00
Gaetan Craig-Riou
618d597f6d ScopeVariantToHub require an Enterprise object
The hub parameter is used to check if inventory is enabled, so it breaks
if we just pass an id
2025-07-09 13:43:18 +10:00
Gaetan Craig-Riou
b7f969eed9 Move the inventory feature check to ScopeVariantToHub
Per review, the check is done on the same enterprise as the one use to
initialize ScopeVariantToHub. So it makes sense to move the actual
feature check to ScopeVariantToHub#scope
2025-07-09 13:43:12 +10:00
Gaetan Craig-Riou
b28e30cb6c Inventory is disabled by default
It will be enabled on release for existing Enterprises, we can use the
added group "enterprise_created_before_2025_07_04" to do so.
2025-07-09 11:35:27 +10:00
Gaetan Craig-Riou
681eee9309 Scope variant only when inventory is enabled
This wasn't pick up our specs, so I am not actually sure it's
usefull.Still for consistency sake we don't want to scope if inventory
is disabled
2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
6937a133ae Move inventory enabled check to OpenFoodNetwork::ScopeVariantToHub 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
ddc45e1cd8 Post rebase, fix inventory landing page spec 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
28a11f1fee Enabled inventory by default
Currently inventory is enabled by default, but we enventually want to
disabled it by default. So we disable inventory for specs, it will be
enabled on specific specs to test inventory related code path.
2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
1c4febd332 Enable inventory for variant override test 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
9e1de75db6 Scope variant only when inventory is enabled 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
8829f6ad03 Only scope variant when inventory is enabled 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
6212cd4d07 Only add variant override permission if inventory enabled
The permission shoul not be needed if inventory is disabled, but it will
prevent importing into the inventory if somehow we try to import into
inventory with inventory disabled.
2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
29a24b7305 Scope variant to hub only when inventory enabled 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
7c31c951a1 Refactor spec to use instance_double 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
33bac6f816 Fix specs to take into account inventory feature 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
a6f0a36b6d Enable inventory feature for inventory related specs 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
b253950075 Add feature flag for variant override specs 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
b19b987ed0 Remove variant rule type when inventory disabled 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
dc84d32028 Disable link to inventory related settings for enterprise
Metrics/CyclomaticComplexity is disabled on `enterprise_side_menu_items`
because even though there is a lot of branching it's still readable
2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
82c99891eb Only scope with variant override when inventory enabled 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
f30b899569 Disable inventory option for product import 2025-07-09 11:35:26 +10:00
Gaetan Craig-Riou
cd8b7cd239 First step disable inventory 2025-07-09 11:35:26 +10:00
David Cook
d80481a106 Merge pull request #13405 from openfoodfoundation/dependabot/npm_and_yarn/floating-ui/dom-1.7.2
Bump @floating-ui/dom from 1.7.1 to 1.7.2
2025-07-08 09:52:28 +10:00
David Cook
174be39c5e Merge pull request #13399 from openfoodfoundation/dependabot/npm_and_yarn/pbkdf2-3.1.3
Bump pbkdf2 from 3.1.1 to 3.1.3
2025-07-08 09:38:12 +10:00
Ahmed Ejaz
5f694276f1 Update all locales with the latest Transifex translations 2025-07-07 03:53:59 +05:00
Filipe
affb5d7281 Merge pull request #13338 from chahmedejaz/task/13287-add-producer-seller-ability-to-edit-orders
Allow producer who are also seller to edit their products on hubs' orders
2025-07-04 14:26:39 +01:00
Maikel
87b9eeb2f1 Merge pull request #13407 from rioug/fix-undercover-ci-step
CI - Do not run undercover CI step on the master branch
2025-07-02 11:04:29 +10:00
Gaetan Craig-Riou
81c75b2b71 Do not run undercover on the master branch
No need to compare master to itself.
2025-07-02 10:12:28 +10:00
dependabot[bot]
ec6d490676 Bump @floating-ui/dom from 1.7.1 to 1.7.2
Bumps [@floating-ui/dom](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/dom) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/floating-ui/floating-ui/releases)
- [Changelog](https://github.com/floating-ui/floating-ui/blob/master/packages/dom/CHANGELOG.md)
- [Commits](https://github.com/floating-ui/floating-ui/commits/@floating-ui/dom@1.7.2/packages/dom)

---
updated-dependencies:
- dependency-name: "@floating-ui/dom"
  dependency-version: 1.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 12:15:11 +00:00
Ahmed Ejaz
838e88a502 Refactor display_value_for_producer method to use Spree::Ability for supplier edit permissions 2025-06-29 19:41:41 +05:00
Ahmed Ejaz
4b19d38c58 Refactor variant creation in hub actions spec to use supplier association for clarity 2025-06-29 19:13:44 +05:00
Ahmed Ejaz
7725fae992 Refactor order cycle and order management abilities to improve producer edit permissions 2025-06-29 19:13:31 +05:00
Carlos Chitty
b43fa55a7b Do not try to generate a URL for unpersisted blobs in development/test environment
Explicitly raise an error in `image_variant_url_for` if an Active Storage variant's blob is not persisted.

This addresses `ArgumentError`/`URI::InvalidURIError` in Rails 7.1, which occurs when attempting to generate a URL for an unsaved Active Storage blob. By raising, we ensure existing error handling in calling methods (e.g., `Spree::Image#url`) can provide graceful fallbacks (default image URLs).

This should only affect test and development environments where blobs may not be immediately persisted. Tests in `SuppliedProductImporter` have been updated to reflect this behavior.

References:
  - Suggestion: https://github.com/openfoodfoundation/openfoodnetwork/pull/13232#discussion_r2071116581
  - Example of failing test due to this: https://github.com/openfoodfoundation/openfoodnetwork/actions/runs/14739687958/job/41374346184?pr=13232
  - Related: https://github.com/rails/rails/issues/50234
2025-06-27 15:05:52 -04:00
dependabot[bot]
d5c79be7d9 Bump pbkdf2 from 3.1.1 to 3.1.3
---
updated-dependencies:
- dependency-name: pbkdf2
  dependency-version: 3.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-24 09:47:41 +00:00
Ahmed Ejaz
1b9d64ad5e Refactor search functionality in variants controller spec to include order_id for improved filtering 2025-06-21 16:39:15 +05:00
Ahmed Ejaz
c648249160 Refactor order view specs to improve clarity in expectations 2025-06-21 16:19:31 +05:00
Ahmed Ejaz
765ce68c11 Add order_id to order controller, variant autocomplete, and search parameters for improved order management 2025-06-21 16:19:31 +05:00
Ahmed Ejaz
020d90b957 Enhance line item management abilities by consolidating permissions for Spree::Order 2025-06-21 16:19:31 +05:00
Ahmed Ejaz
8d407b1dc9 Fix lint issues 2025-06-21 16:19:31 +05:00
Ahmed Ejaz
fe1b8aaab3 Add hub actions spec for producer order management functionality 2025-06-21 16:19:31 +05:00
Ahmed Ejaz
ade35f2fa2 Fixes specs and update code respectively 2025-06-21 16:19:31 +05:00
Ahmed Ejaz
cd01a27bdd Add distributor_name_alias to searchable attributes and implement ransacker for filtering line items 2025-06-21 16:19:31 +05:00
Ahmed Ejaz
2f9c856645 Refactors order and line item permissions logic
Simplifies permission checking by:
- Extracting common managed/coordinated orders logic into separate method
- Combining producer-editable and managed/coordinated order clauses
- Merging producer and admin line item permission checks into single query
2025-06-21 16:19:31 +05:00
Ahmed Ejaz
8e8878e43a Add search_variants_as parameter to variant search functionality 2025-06-21 16:19:31 +05:00
Ahmed Ejaz
a37e08c2fd Refactor order management permissions for producers
Introduces granular permissions control for producers editing orders:

- Adds new :edit_as_producer_only permission for suppliers
- Refactors ability checks to clearly separate producer vs admin/distributor access
- Updates order views to properly restrict actions based on user role
- Prevents admins from being restricted by producer-only edit mode
2025-06-21 16:16:58 +05:00
207 changed files with 58239 additions and 5073 deletions

View File

@@ -410,5 +410,6 @@ jobs:
include-hidden-files: true
- name: Compare SimpleCov results with Undercover
run: |
git fetch --no-tags origin master:master
git fetch --no-tags origin ${{ github.event.pull_request.base.ref }}:master
bundle exec undercover
if: ${{ github.ref != 'refs/heads/master' }} # Does not run on master, as we can't fetch master in the master branch

View File

@@ -16,6 +16,7 @@ AllCops:
- node_modules/**/*
# Excluding: inadequate Naming/FileName rule rejects GemFile name with camelcase
- engines/web/Gemfile
- .undercover
Bundler/DuplicatedGem:
Enabled: false

View File

@@ -98,6 +98,7 @@ Metrics/ClassLength:
- 'lib/reporting/reports/enterprise_fee_summary/enterprise_fees_with_tax_report_by_producer.rb'
- 'lib/reporting/reports/enterprise_fee_summary/scope.rb'
- 'lib/reporting/reports/xero_invoices/base.rb'
- 'app/services/permissions/order.rb'
# Offense count: 30
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.

View File

@@ -4,17 +4,8 @@
SimpleCov.start 'rails' do
add_filter '/bin/'
add_filter '/config/'
add_filter '/jobs/application_job.rb'
add_filter '/schemas/'
add_filter '/lib/generators'
add_filter '/spec/'
add_filter '/vendor/'
add_filter '/public'
add_filter '/swagger'
add_filter '/script'
add_filter '/log'
add_filter '/db'
add_filter '/lib/tasks/sample_data/'
formatter SimpleCov::Formatter::SimpleFormatter
end

View File

@@ -1,5 +1,4 @@
#!/bin/env ruby
# frozen_string_literal: true
-l coverage/lcov/openfoodnetwork.lcov
-c master
-c master

View File

@@ -152,6 +152,7 @@ end
group :test, :development do
gem 'bullet'
gem 'capybara'
gem 'capybara-shadowdom'
gem 'cuprite'
gem 'database_cleaner', require: false
gem 'debug', '>= 1.0.0'
@@ -171,9 +172,9 @@ end
group :test do
gem 'pdf-reader'
gem 'puffing-billy'
gem 'rails-controller-testing'
gem 'simplecov', require: false
gem 'simplecov-lcov', require: false
gem 'undercover', require: false
gem 'vcr', require: false
gem 'webmock', require: false

View File

@@ -48,54 +48,58 @@ PATH
GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.1.0)
actioncable (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
Ascii85 (2.0.1)
actioncable (7.1.5.2)
actionpack (= 7.1.5.2)
activesupport (= 7.1.5.2)
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)
zeitwerk (~> 2.6)
actionmailbox (7.1.5.2)
actionpack (= 7.1.5.2)
activejob (= 7.1.5.2)
activerecord (= 7.1.5.2)
activestorage (= 7.1.5.2)
activesupport (= 7.1.5.2)
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.1.5.2)
actionpack (= 7.1.5.2)
actionview (= 7.1.5.2)
activejob (= 7.1.5.2)
activesupport (= 7.1.5.2)
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)
rack (~> 2.0, >= 2.2.4)
rails-dom-testing (~> 2.2)
actionpack (7.1.5.2)
actionview (= 7.1.5.2)
activesupport (= 7.1.5.2)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
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.1.5.2)
actionpack (= 7.1.5.2)
activerecord (= 7.1.5.2)
activestorage (= 7.1.5.2)
activesupport (= 7.1.5.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.8)
activesupport (= 7.0.8)
actionview (7.1.5.2)
activesupport (= 7.1.5.2)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
active_model_serializers (0.8.4)
activemodel (>= 3.0)
active_storage_validations (1.1.4)
@@ -103,8 +107,8 @@ GEM
activemodel (>= 5.2.0)
activestorage (>= 5.2.0)
activesupport (>= 5.2.0)
activejob (7.0.8)
activesupport (= 7.0.8)
activejob (7.1.5.2)
activesupport (= 7.1.5.2)
globalid (>= 0.3.6)
activemerchant (1.133.0)
activesupport (>= 4.2)
@@ -112,11 +116,12 @@ GEM
i18n (>= 0.6.9)
nokogiri (~> 1.4)
rexml (~> 3.2.5)
activemodel (7.0.8)
activesupport (= 7.0.8)
activerecord (7.0.8)
activemodel (= 7.0.8)
activesupport (= 7.0.8)
activemodel (7.1.5.2)
activesupport (= 7.1.5.2)
activerecord (7.1.5.2)
activemodel (= 7.1.5.2)
activesupport (= 7.1.5.2)
timeout (>= 0.4.0)
activerecord-import (1.6.0)
activerecord (>= 4.2)
activerecord-postgresql-adapter (0.0.1)
@@ -128,17 +133,24 @@ GEM
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)
activestorage (7.1.5.2)
actionpack (= 7.1.5.2)
activejob (= 7.1.5.2)
activerecord (= 7.1.5.2)
activesupport (= 7.1.5.2)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.8)
activesupport (7.1.5.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
securerandom (>= 0.3)
tzinfo (~> 2.0)
acts-as-taggable-on (10.0.0)
activerecord (>= 6.1, < 7.2)
@@ -159,7 +171,7 @@ GEM
angularjs-rails (1.8.0)
arel-helpers (2.14.0)
activerecord (>= 3.1.0, < 8)
ast (2.4.2)
ast (2.4.3)
attr_required (1.0.2)
aws-eventstream (1.3.0)
aws-partitions (1.929.0)
@@ -177,10 +189,11 @@ GEM
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
aws-eventstream (~> 1, >= 1.0.2)
base64 (0.2.0)
base64 (0.3.0)
bcp47_spec (0.2.1)
bcrypt (3.1.20)
bigdecimal (3.1.8)
benchmark (0.4.1)
bigdecimal (3.2.2)
bindata (2.5.0)
bindex (0.8.1)
bootsnap (1.18.3)
@@ -188,7 +201,7 @@ GEM
bugsnag (6.26.4)
concurrent-ruby (~> 1.0)
builder (3.2.4)
bullet (7.1.6)
bullet (8.0.8)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
cable_ready (5.0.6)
@@ -208,12 +221,14 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
capybara-shadowdom (0.3.0)
capybara
caxlsx (3.3.0)
htmlentities (~> 4.3, >= 4.3.4)
marcel (~> 1.0)
nokogiri (~> 1.10, >= 1.10.4)
rubyzip (>= 1.3.0, < 3)
cgi (0.3.6)
cgi (0.3.7)
childprocess (5.0.0)
choice (0.2.0)
chronic (0.10.2)
@@ -228,8 +243,9 @@ GEM
combine_pdf (1.0.26)
matrix
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.3.1)
connection_pool (2.4.1)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
cookiejar (0.3.4)
crack (1.0.0)
bigdecimal
rexml
@@ -248,7 +264,7 @@ GEM
database_cleaner-core (2.0.1)
datafoodconsortium-connector (1.1.0)
virtual_assembly-semantizer (~> 1.0, >= 1.0.5)
date (3.3.4)
date (3.4.1)
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
@@ -269,11 +285,25 @@ GEM
digest (3.1.1)
docile (1.4.0)
dotenv (3.1.2)
drb (2.2.3)
em-http-request (1.1.7)
addressable (>= 2.3.4)
cookiejar (!= 0.3.1)
em-socksify (>= 0.3)
eventmachine (>= 1.0.3)
http_parser.rb (>= 0.6.0)
em-socksify (0.3.3)
base64
eventmachine (>= 1.0.0.beta.4)
em-synchrony (1.0.6)
eventmachine (>= 1.0.0.beta.1)
email_validator (2.2.4)
activemodel
erubi (1.12.0)
et-orbi (1.2.7)
et-orbi (1.3.0)
tzinfo
eventmachine (1.2.7)
eventmachine_httpserver (0.2.1)
excon (0.81.0)
execjs (2.7.0)
factory_bot (6.2.0)
@@ -323,8 +353,8 @@ GEM
nokogiri (>= 1.5.11, < 2.0.0)
foreman (0.88.1)
formatador (0.2.5)
fugit (1.8.1)
et-orbi (~> 1, >= 1.2.7)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
fuubar (2.5.1)
rspec-core (~> 3.0)
@@ -347,6 +377,7 @@ GEM
hashie (5.0.0)
highline (2.0.3)
htmlentities (4.3.4)
http_parser.rb (0.8.0)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
i18n-js (3.9.2)
@@ -420,7 +451,8 @@ GEM
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.22.0)
logger (1.7.0)
loofah (2.24.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@@ -440,7 +472,7 @@ GEM
mini_magick (4.11.0)
mini_mime (1.1.5)
mini_portile2 (2.8.6)
minitest (5.23.1)
minitest (5.25.5)
monetize (1.13.0)
money (~> 6.12)
money (6.16.0)
@@ -448,9 +480,10 @@ GEM
msgpack (1.7.2)
multi_json (1.15.0)
multi_xml (0.6.0)
mutex_m (0.3.0)
net-http (0.4.1)
uri
net-imap (0.4.10)
net-imap (0.4.20)
date
net-protocol
net-pop (0.1.2)
@@ -461,7 +494,7 @@ GEM
net-protocol
newrelic_rpm (9.9.0)
nio4r (2.7.1)
nokogiri (1.16.5)
nokogiri (1.18.9)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri-html5-inference (0.3.0)
@@ -505,7 +538,7 @@ GEM
parallel (1.24.0)
paranoia (2.6.3)
activerecord (>= 5.1, < 7.2)
parser (3.3.2.0)
parser (3.3.8.0)
ast (~> 2.4.1)
racc
paypal-sdk-core (0.3.4)
@@ -513,9 +546,9 @@ GEM
xml-simple
paypal-sdk-merchant (1.117.2)
paypal-sdk-core (~> 0.3.0)
pdf-reader (2.12.0)
Ascii85 (~> 1.0)
afm (~> 0.2.1)
pdf-reader (2.15.0)
Ascii85 (>= 1.0, < 3.0, != 2.0.0)
afm (>= 0.2.1, < 2)
hashery (~> 2.0)
ruby-rc4
ttfunk
@@ -527,14 +560,22 @@ GEM
psych (5.1.2)
stringio
public_suffix (5.0.5)
puffing-billy (4.0.2)
addressable (~> 2.5)
em-http-request (~> 1.1, >= 1.1.0)
em-synchrony
eventmachine (~> 1.2)
eventmachine_httpserver
http_parser.rb (~> 0.8.0)
multi_json
puma (6.5.0)
nio4r (~> 2.0)
query_count (1.1.1)
activerecord (>= 4.2)
railties (>= 4.2)
raabro (1.4.0)
racc (1.8.0)
rack (2.2.11)
racc (1.8.1)
rack (2.2.14)
rack-mini-profiler (2.3.4)
rack (>= 1.2.0)
rack-oauth2 (2.2.1)
@@ -555,20 +596,23 @@ GEM
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.7.0)
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)
rackup (1.0.1)
rack (< 3)
webrick
rails (7.1.5.2)
actioncable (= 7.1.5.2)
actionmailbox (= 7.1.5.2)
actionmailer (= 7.1.5.2)
actionpack (= 7.1.5.2)
actiontext (= 7.1.5.2)
actionview (= 7.1.5.2)
activejob (= 7.1.5.2)
activemodel (= 7.1.5.2)
activerecord (= 7.1.5.2)
activestorage (= 7.1.5.2)
activesupport (= 7.1.5.2)
bundler (>= 1.15.0)
railties (= 7.0.8)
railties (= 7.1.5.2)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -582,20 +626,21 @@ GEM
activesupport (>= 4.2)
choice (~> 0.2.0)
ruby-graphviz (~> 1.2)
rails-html-sanitizer (1.6.0)
rails-html-sanitizer (1.6.1)
loofah (~> 2.21)
nokogiri (~> 1.14)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails-i18n (7.0.9)
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)
method_source
railties (7.1.5.2)
actionpack (= 7.1.5.2)
activesupport (= 7.1.5.2)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.2.1)
ransack (4.1.1)
@@ -717,7 +762,7 @@ GEM
rubyzip (2.3.2)
rufus-scheduler (3.8.2)
fugit (~> 1.1, >= 1.1.6)
rugged (1.7.2)
rugged (1.9.0)
sanitize (6.1.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@@ -729,6 +774,7 @@ GEM
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sd_notify (0.1.1)
securerandom (0.4.1)
semantic_range (3.0.0)
shoulda-matchers (6.2.0)
activesupport (>= 5.2.0)
@@ -746,7 +792,6 @@ GEM
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
spreadsheet_architect (5.0.0)
caxlsx (>= 3.3.0, < 4)
@@ -794,12 +839,13 @@ GEM
temple (0.8.2)
terminal-table (4.0.0)
unicode-display_width (>= 1.1.1, < 4)
thor (1.3.1)
thor (1.4.0)
thread-local (1.1.0)
tilt (2.3.0)
timecop (0.9.8)
timeout (0.4.1)
ttfunk (1.7.0)
timecop (0.9.10)
timeout (0.4.3)
ttfunk (1.8.0)
bigdecimal (~> 3.1)
turbo-rails (2.0.5)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
@@ -808,14 +854,17 @@ GEM
turbo-rails (>= 1.3.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
undercover (0.6.3)
undercover (0.7.4)
base64
bigdecimal
imagen (>= 0.2.0)
rainbow (>= 2.1, < 4.0)
rugged (>= 0.27, < 1.8)
rugged (>= 0.27, < 1.10)
simplecov
simplecov_json_formatter
unicode-display_width (2.5.0)
uniform_notifier (1.16.0)
uri (0.13.0)
uniform_notifier (1.17.0)
uri (0.13.2)
valid_email2 (5.2.3)
activemodel (>= 3.2)
mail (~> 2.5)
@@ -855,7 +904,7 @@ GEM
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
webrick (1.8.1)
webrick (1.8.2)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -895,6 +944,7 @@ DEPENDENCIES
cable_ready
cancancan (~> 1.15.0)
capybara
capybara-shadowdom
catalog!
coffee-rails (~> 5.0.0)
combine_pdf
@@ -958,6 +1008,7 @@ DEPENDENCIES
pg (~> 1.2.3)
private_address_check
pry (~> 0.13.0)
puffing-billy
puma
query_count
rack-mini-profiler (< 3.0.0)
@@ -989,7 +1040,6 @@ DEPENDENCIES
sidekiq
sidekiq-scheduler
simplecov
simplecov-lcov
spreadsheet_architect
spring
spring-commands-rspec

View File

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

View File

@@ -19,18 +19,6 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.page = 1
$scope.per_page = $scope.per_page_options[0].id
$scope.filterByVariantId = null
searchThrough = ["order_distributor_name",
"order_bill_address_phone",
"order_bill_address_firstname",
"order_bill_address_lastname",
"order_bill_address_full_name",
"order_bill_address_full_name_reversed",
"order_bill_address_full_name_with_comma",
"order_bill_address_full_name_with_comma_reversed",
"variant_supplier_name",
"order_email",
"order_number",
"product_name"].join("_or_") + "_cont"
$scope.confirmRefresh = ->
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
@@ -75,11 +63,10 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
[formattedStartDate, formattedEndDate] = $scope.formatDates($scope.startDate, $scope.endDate)
RequestMonitor.load LineItems.index(
"q[#{searchThrough}]": $scope.query,
"q[variant_id_eq]": $scope.filterByVariantId if $scope.filterByVariantId,
"q[order_state_not_eq]": "canceled",
"q[order_shipment_state_not_eq]": "shipped",
"q[order_completed_at_not_null]": "true",
"q[variant_id_eq]": $scope.filterByVariantId if $scope.filterByVariantId,
"q[order_distributor_id_eq]": $scope.distributorFilter,
"q[variant_supplier_id_eq]": $scope.supplierFilter,
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
@@ -87,7 +74,8 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
"q[order_completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
"q[s]": "order_completed_at desc",
"page": $scope.page,
"per_page": $scope.per_page
"per_page": $scope.per_page,
"search_query": $scope.query
)
$scope.formatDates = (startDate, endDate) ->

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,76 +0,0 @@
angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetcher, $http) ->
new class BulkProducts
products: []
pagination: {}
fetch: (params) ->
ProductResource.index params, (data) =>
@products.length = 0
@addProducts data.products
angular.extend(@pagination, data.pagination)
cloneProduct: (product) ->
$http.post("/api/v0/products/" + product.id + "/clone").then (response) =>
dataFetcher("/api/v0/products/" + response.data.id + "?template=bulk_show").then (newProduct) =>
@unpackProduct newProduct
@insertProductAfter(product, newProduct)
updateVariantLists: (serverProducts) ->
for server_product in serverProducts
product = @findProductInList(server_product.id, @products)
product.variants = server_product.variants
@loadVariantUnitValues product.variants
find: (id) ->
@findProductInList id, @products
findProductInList: (id, product_list) ->
products = (product for product in product_list when product.id == id)
if products.length == 0 then null else products[0]
addProducts: (products) ->
for product in products
@unpackProduct product
@products.push product
insertProductAfter: (product, newProduct) ->
index = @products.indexOf(product)
@products.splice(index + 1, 0, newProduct)
unpackProduct: (product) ->
@loadVariantUnit product
loadVariantUnit: (product) ->
@loadVariantUnitValues product.variants if product.variants
loadVariantUnitValues: (variants) ->
for variant in variants
@loadVariantUnitValue variant
loadVariantUnitValue: (variant) ->
variant.variant_unit_with_scale =
if variant.variant_unit && variant.variant_unit_scale && variant.variant_unit != 'items'
"#{variant.variant_unit}_#{variant.variant_unit_scale}"
else if variant.variant_unit
variant.variant_unit
else
null
unit_value = @variantUnitValue variant
unit_value = if unit_value? then unit_value else ''
variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim()
variantUnitValue: (variant) ->
if variant.unit_value?
if variant.variant_unit_scale
variant_unit_value = @divideAsInteger variant.unit_value, variant.variant_unit_scale
parseFloat(window.bigDecimal.round(variant_unit_value, 2))
else
variant.unit_value
else
null
# forces integer division to avoid javascript floating point imprecision
# using one billion as the multiplier so that it works for numbers with up to 9 decimal places
divideAsInteger: (a, b) ->
(a * 1000000000) / (b * 1000000000)

View File

@@ -1,4 +1,4 @@
angular.module("admin.tagRules").directive 'newTagRuleDialog', ($rootScope, $compile, $templateCache, DialogDefaults) ->
angular.module("admin.tagRules").directive 'newTagRuleDialog', ($rootScope, $compile, $templateCache, DialogDefaults, ruleTypes) ->
restrict: 'A'
scope:
tagGroup: '='
@@ -7,12 +7,7 @@ angular.module("admin.tagRules").directive 'newTagRuleDialog', ($rootScope, $com
# Compile modal template
template = $compile($templateCache.get('admin/new_tag_rule_dialog.html'))(scope)
scope.ruleTypes = [
{ id: "FilterProducts", name: t('js.tag_rules.show_hide_variants') }
{ id: "FilterShippingMethods", name: t('js.tag_rules.show_hide_shipping') }
{ id: "FilterPaymentMethods", name: t('js.tag_rules.show_hide_payment') }
{ id: "FilterOrderCycles", name: t('js.tag_rules.show_hide_order_cycles') }
]
scope.ruleTypes = ruleTypes
scope.ruleType = scope.ruleTypes[0].id

View File

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

View File

@@ -12,7 +12,7 @@ module Admin
@line_items = order_permissions.
editable_line_items.where(order_id: orders).
includes(:variant).
ransack(params[:q]).result.order(:id)
ransack(line_items_search_query).result.order(:id)
@pagy, @line_items = pagy(@line_items) if pagination_required?
@@ -88,5 +88,27 @@ module Admin
def page
params[:page] || 1
end
def line_items_search_query
query = params.permit(q: {}).to_h[:q] || {}
search_fields_string = [
spree_current_user.admin? ? "order_distributor_name" : "order_distributor_name_alias",
"order_bill_address_phone",
"order_bill_address_firstname",
"order_bill_address_lastname",
"order_bill_address_full_name",
"order_bill_address_full_name_reversed",
"order_bill_address_full_name_with_comma",
"order_bill_address_full_name_with_comma_reversed",
"variant_supplier_name",
"order_email",
"order_number",
"product_name"
].join("_or_")
search_query = "#{search_fields_string}_cont"
query.merge({ search_query => params[:search_query] })
end
end
end

View File

@@ -48,6 +48,9 @@ module Admin
@object = Enterprise.where(permalink: params[:id]).
includes(users: [:ship_address, :bill_address]).first
@object.build_custom_tab if @object.custom_tab.nil?
load_tag_rule_types
return unless params[:stimulus]
@enterprise.is_primary_producer = params[:is_primary_producer]
@@ -375,6 +378,19 @@ module Admin
@properties = Spree::Property.pluck(:name)
end
def load_tag_rule_types
# Load rule types
@tag_rule_types = [
{ id: "FilterShippingMethods", name: t('js.tag_rules.show_hide_shipping') },
{ id: "FilterPaymentMethods", name: t('js.tag_rules.show_hide_payment') },
{ id: "FilterOrderCycles", name: t('js.tag_rules.show_hide_order_cycles') }
]
return unless helpers.feature?(:inventory, @object)
@tag_rule_types.prepend({ id: "FilterProducts", name: t('js.tag_rules.show_hide_variants') })
end
def setup_property
@enterprise.producer_properties.build
end

View File

@@ -17,7 +17,10 @@ module Admin
end
def import
return unless can_import_into_inventories?
@filepath = save_uploaded_file(params[:file])
@importer = ProductImport::ProductImporter.new(File.new(@filepath), spree_current_user,
params[:settings])
@original_filename = params[:file].try(:original_filename)
@@ -36,7 +39,22 @@ module Admin
def save_data
return unless process_data('save')
render json: @importer.save_results
json = {
results: {
products_created: @importer.products_created_count,
products_updated: @importer.products_updated_count,
products_reset: @importer.products_reset_count,
},
updated_ids: @importer.updated_ids,
errors: @importer.errors.full_messages
}
if helpers.feature?(:inventory, spree_current_user.enterprises)
json[:results][:inventory_created] = @importer.inventory_created_count
json[:results][:inventory_updated] = @importer.inventory_updated_count
end
render json:
end
def reset_absent_products
@@ -154,5 +172,15 @@ module Admin
notice: I18n.t(:product_import_no_data_in_spreadsheet_notice)
raise 'Invalid File Path'
end
# Return an error if trying to import into inventories when inventory is disable
def can_import_into_inventories?
return true if helpers.feature?(:inventory, spree_current_user.enterprises) ||
params.dig(:settings, "import_into") != 'inventories'
redirect_to admin_product_import_url, notice: I18n.t(:product_import_inventory_disable)
false
end
end
end

View File

@@ -53,7 +53,9 @@ module Admin
return unless @order_cycle
fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(@shop, @order_cycle)
OpenFoodNetwork::ScopeVariantToHub.new(@shop).scope(@variant)
@variant.price + fee_calculator.indexed_fees_for(@variant)
end

View File

@@ -22,7 +22,8 @@ module Api
distributor,
order_cycle,
customer,
search_params
search_params,
inventory_enabled:
).products_json
render plain: products
@@ -95,9 +96,13 @@ module Api
def distributed_products
OrderCycles::DistributedProductsService.new(
distributor, order_cycle, customer
distributor, order_cycle, customer, inventory_enabled:
).products_relation.pluck(:id)
end
def inventory_enabled
OpenFoodNetwork::FeatureToggle.enabled?(:inventory, distributor)
end
end
end
end

View File

@@ -112,7 +112,9 @@ module Api
def scoped_variant(variant_id)
variant = Spree::Variant.find(variant_id)
OpenFoodNetwork::ScopeVariantToHub.new(@order.distributor).scope(variant)
variant
end

View File

@@ -54,7 +54,7 @@ module Api
def error_during_processing(exception)
Alert.raise(exception)
if Rails.env.development? || Rails.env.test?
if Rails.env.local?
render status: :unprocessable_entity,
json: json_api_error(exception.message, meta: exception.backtrace)
else

View File

@@ -80,8 +80,6 @@ class CheckoutController < BaseController
@order.customer.touch :terms_and_conditions_accepted_at
return true if redirect_to_payment_gateway
# Redeem VINE voucher
vine_voucher_redeemer = Vine::VoucherRedeemerService.new(order: @order)
unless vine_voucher_redeemer.redeem
@@ -94,6 +92,9 @@ class CheckoutController < BaseController
return false
# rubocop:enable Rails/DeprecatedActiveModelErrorsMethods
end
return true if redirect_to_payment_gateway
@order.process_payments!
@order.confirm!
BackorderJob.check_stock(@order)

View File

@@ -19,11 +19,6 @@ 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 )
@@ -65,28 +60,6 @@ module Spree
end
end
def bulk_update
product_set = product_set_from_params
product_set.collection.each { |p| authorize! :update, p }
if product_set.save
redirect_to main_app.bulk_products_api_v0_products_path(bulk_index_query)
elsif product_set.errors.present?
render json: { errors: product_set.errors }, status: :bad_request
else
render body: nil, status: :internal_server_error
end
end
def clone
@new = @product.duplicate
raise "Clone failed" unless @new.save
flash[:success] = t('.success')
redirect_to spree.admin_products_url
end
def group_buy_options
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
end

View File

@@ -117,7 +117,7 @@ module Spree
def variant_search_params
params.permit(
:q, :distributor_id, :order_cycle_id, :schedule_id, :eligible_for_subscriptions,
:include_out_of_stock
:include_out_of_stock, :search_variants_as, :order_id
).to_h.with_indifferent_access
end

View File

@@ -18,7 +18,7 @@ module Admin
enterprise.in?(spree_current_user.enterprises)
end
def enterprise_side_menu_items(enterprise)
def enterprise_side_menu_items(enterprise) # rubocop:disable Metrics/CyclomaticComplexity
is_shop = enterprise.sells != "none"
show_properties = !!enterprise.is_primary_producer
show_shipping_methods = can?(:manage_shipping_methods, enterprise) && is_shop
@@ -26,24 +26,35 @@ module Admin
show_enterprise_fees = can?(:manage_enterprise_fees,
enterprise) && (is_shop || enterprise.is_primary_producer)
show_connected_apps = can?(:manage_connected_apps, enterprise) &&
feature?(:connected_apps, spree_current_user, enterprise) &&
Spree::Config.connected_apps_enabled.present?
(connected_apps_enabled(enterprise).present? ||
dfc_platforms_available?)
show_inventory_settings = feature?(:inventory, spree_current_user.enterprises) && is_shop
build_enterprise_side_menu_items(
is_shop:,
show_options = {
show_properties:,
show_shipping_methods:,
show_payment_methods:,
show_enterprise_fees:,
show_connected_apps:,
)
show_inventory_settings:,
}
build_enterprise_side_menu_items(is_shop:, show_options:)
end
def connected_apps_enabled
def connected_apps_enabled(enterprise)
return [] unless feature?(:connected_apps, spree_current_user, enterprise)
connected_apps_enabled = Spree::Config.connected_apps_enabled&.split(',') || []
ConnectedApp::TYPES & connected_apps_enabled
end
def dfc_platforms_available?
DfcProvider::PlatformsController::PLATFORM_IDS.keys.any? do |id|
feature?(id, spree_current_user)
end
end
def enterprise_attachment_removal_modal_id
attachment_removal_parameter # remove_logo|remove_promo_image|remove_white_label_logo
end
@@ -62,14 +73,7 @@ module Admin
private
def build_enterprise_side_menu_items(
is_shop:,
show_properties:,
show_shipping_methods:,
show_payment_methods:,
show_enterprise_fees:,
show_connected_apps:
)
def build_enterprise_side_menu_items(is_shop:, show_options: ) # rubocop:disable Metrics/MethodLength
[
{ name: 'primary_details', icon_class: "icon-home", show: true, selected: 'selected' },
{ name: 'address', icon_class: "icon-map-marker", show: true },
@@ -78,19 +82,24 @@ module Admin
{ name: 'about', icon_class: "icon-pencil", show: true, form_name: "about_us" },
{ name: 'business_details', icon_class: "icon-briefcase", show: true },
{ name: 'images', icon_class: "icon-picture", show: true },
{ name: 'properties', icon_class: "icon-tags", show: show_properties },
{ 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: 'properties', icon_class: "icon-tags", show: show_options[:show_properties] },
{ name: 'shipping_methods', icon_class: "icon-truck",
show: show_options[:show_shipping_methods] },
{ name: 'payment_methods', icon_class: "icon-money",
show: show_options[:show_payment_methods] },
{ name: 'enterprise_fees', icon_class: "icon-tasks",
show: show_options[:show_enterprise_fees] },
{ name: 'vouchers', icon_class: "icon-ticket", show: is_shop },
{ 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 },
{ name: 'inventory_settings', icon_class: "icon-list-ol",
show: show_options[:show_inventory_settings] },
{ name: 'tag_rules', icon_class: "icon-random", show: is_shop },
{ name: 'shop_preferences', icon_class: "icon-shopping-cart", show: is_shop },
{ name: 'white_label', icon_class: "icon-leaf", show: true },
{ name: 'users', icon_class: "icon-user", show: true },
{ name: 'connected_apps', icon_class: "icon-puzzle-piece", show: show_connected_apps },
{ name: 'connected_apps', icon_class: "icon-puzzle-piece",
show: show_options[:show_connected_apps] },
]
end
end

View File

@@ -32,12 +32,8 @@ module Admin
[precised_unit_value, variant.unit_description].compact_blank.join(" ")
end
def products_return_to_url(url_filters)
if feature?(:admin_style_v3, spree_current_user)
return session[:products_return_to_url] || admin_products_url
end
"#{admin_products_path}#{url_filters.empty? ? '' : "#?#{url_filters.to_query}"}"
def products_return_to_url
session[:products_return_to_url] || admin_products_url
end
# if user hasn't saved any preferences on products page and there's only one producer;

View File

@@ -5,10 +5,6 @@ module CheckoutHelper
order.ship_address == order.bill_address
end
def guest_checkout_allowed?
current_order.distributor.allow_guest_orders?
end
def checkout_adjustments_for(order, opts = {})
exclude = opts[:exclude] || {}
reject_zero_amount = opts.fetch(:reject_zero_amount, true)

View File

@@ -29,13 +29,7 @@ module Spree
scope: [:admin, :tab]).capitalize
css_classes = []
if options[:icon] && !feature?(:admin_style_v3, spree_current_user)
link = link_to_with_icon(options[:icon], titleized_label, destination_url)
css_classes << 'tab-with-icon'
else
link = link_to(titleized_label, destination_url)
end
link = link_to(titleized_label, destination_url)
selected = if options[:match_path]
PathChecker

View File

@@ -155,8 +155,7 @@ module Spree
end
def filter_by_supplier?(order)
order.distributor&.enable_producers_to_edit_orders &&
spree_current_user.can_manage_line_items_in_orders_only?
can? :edit_as_producer_only, order
end
def display_value_for_producer(order, value)

View File

@@ -20,6 +20,10 @@ class ApplicationRecord < ActiveRecord::Base
if ENV["S3_BUCKET"].present? && variant.service.public?
variant.processed.url
else
unless variant.blob.persisted?
raise "ActiveStorage blob for variant is not persisted. Cannot generate URL."
end
url_for(variant)
end
end

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
# Authorisations of a user allowing a platform to access to data.
class DfcPermission < ApplicationRecord
SCOPES = %w[
ReadEnterprise ReadProducts ReadOrders
WriteEnterprise WriteProducts WriteOrders
].freeze
belongs_to :user, class_name: "Spree::User"
belongs_to :enterprise
validates :grantee, presence: true
validates :scope, presence: true, inclusion: { in: SCOPES }
end

View File

@@ -286,9 +286,13 @@ class OrderCycle < ApplicationRecord
user_id: user,
distributor_id: distributor,
order_cycle_id: self)
items = Spree::LineItem.includes(:variant).joins(:order).merge(orders)
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
items = Spree::LineItem.includes(:variant).joins(:order).merge(orders).to_a
items.each { |li| scoper.scope(li.variant) }
items
end
def distributor_payment_methods

View File

@@ -142,20 +142,6 @@ module ProductImport
{ entries: entries_json, reset_counts: }
end
def save_results
{
results: {
products_created: products_created_count,
products_updated: products_updated_count,
inventory_created: inventory_created_count,
inventory_updated: inventory_updated_count,
products_reset: products_reset_count,
},
updated_ids:,
errors: errors.full_messages
}
end
def validate_entries
@validator.validate_all(@entries)
end
@@ -207,6 +193,8 @@ module ProductImport
order('is_primary_producer ASC, name').
map { |e| @editable_enterprises[e.name] = e.id }
return unless OpenFoodNetwork::FeatureToggle.enabled?(:inventory, @current_user.enterprises)
@inventory_permissions = permissions.variant_override_enterprises_per_hub
end

View File

@@ -20,6 +20,10 @@ module Spree
if user.try(:admin?)
can :manage, :all
# this action was needed for restrictions for distributors and suppliers
# however, admins don't need to be restricted, so, bypassing it for admins
cannot :edit_as_producer_only, Spree::Order
else
can [:index, :read], Country
can :create, Order
@@ -209,18 +213,20 @@ module Spree
managed_product_enterprises.include? variant.supplier
end
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
next false unless vo.hub.present? && vo.variant&.supplier.present?
if OpenFoodNetwork::FeatureToggle.enabled?(:inventory, user.enterprises)
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
next false unless vo.hub.present? && vo.variant&.supplier.present?
hub_auth = OpenFoodNetwork::Permissions.new(user).
variant_override_hubs.
include? vo.hub
hub_auth = OpenFoodNetwork::Permissions.new(user).
variant_override_hubs.
include? vo.hub
producer_auth = OpenFoodNetwork::Permissions.new(user).
variant_override_producers.
include? vo.variant.supplier
producer_auth = OpenFoodNetwork::Permissions.new(user).
variant_override_producers.
include? vo.variant.supplier
hub_auth && producer_auth
hub_auth && producer_auth
end
end
can [:admin, :create, :update], InventoryItem do |ii|
@@ -257,8 +263,13 @@ module Spree
end
def add_order_cycle_management_abilities(user)
can [:admin, :index], OrderCycle do |order_cycle|
OrderCycle.visible_by(user).include?(order_cycle) ||
order_cycle.orders.editable_by_producers(user.enterprises).exists?
end
can [
:admin, :index, :read, :edit, :update, :incoming, :outgoing, :checkout_options
:read, :edit, :update, :incoming, :outgoing, :checkout_options
], OrderCycle do |order_cycle|
OrderCycle.visible_by(user).include? order_cycle
end
@@ -274,8 +285,37 @@ module Spree
end
def add_order_management_abilities(user)
can [:index, :create], Spree::Order
can [:read, :update, :fire, :resend, :invoice, :print], Spree::Order do |order|
can [:manage_order_sections], Spree::Order do |order|
user.admin? ||
order.distributor.nil? ||
user.enterprises.include?(order.distributor) ||
order.order_cycle&.coordinated_by?(user)
end
can [:edit_as_producer_only], Spree::Order do |order|
cannot?(:manage_order_sections, order) && can_edit_as_producer(order, user)
end
can [:index], Spree::Order do
user.admin? ||
user.enterprises.any?(&:is_distributor) ||
user.enterprises.distributors.where(enable_producers_to_edit_orders: true).exist?
end
can [:create], Spree::Order
can [:read, :update], Spree::Order do |order|
# We allow editing orders with a nil distributor as this state occurs
# during the order creation process from the admin backend
order.distributor.nil? ||
# Enterprise User can access orders that they are a distributor for
user.enterprises.include?(order.distributor) ||
# Enterprise User can access orders that are placed inside a OC they coordinate
order.order_cycle&.coordinated_by?(user) ||
can_edit_as_producer(order, user)
end
can [:fire, :resend, :invoice, :print], Spree::Order do |order|
# We allow editing orders with a nil distributor as this state occurs
# during the order creation process from the admin backend
order.distributor.nil? ||
@@ -284,22 +324,39 @@ module Spree
# Enterprise User can access orders that are placed inside a OC they coordinate
order.order_cycle&.coordinated_by?(user)
end
can [:admin, :bulk_management, :managed, :distribution], Spree::Order do
can [:admin, :bulk_management], Spree::Order do |order|
user.admin? ||
user.enterprises.any?(&:is_distributor) ||
can_edit_as_producer(order, user)
end
can [:managed, :distribution], Spree::Order do
user.admin? || user.enterprises.any?(&:is_distributor)
end
can [:admin, :index, :create, :show, :poll, :generate], :invoice
can [:admin, :visible], Enterprise
can [:admin, :index, :create, :update, :destroy], :line_item
can [:admin, :index, :create], Spree::LineItem
can [:admin, :index, :create], Spree::LineItem do |item|
user.admin? ||
user.enterprises.any?(&:is_distributor) ||
can_edit_as_producer(item.order, user)
end
can [:destroy, :update], Spree::LineItem do |item|
order = item.order
user.admin? ||
user.enterprises.include?(order.distributor) ||
order.order_cycle&.coordinated_by?(user)
order.order_cycle&.coordinated_by?(user) ||
can_edit_as_producer(order, user)
end
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Shipment do |shipment|
user.admin? ||
user.enterprises.any?(&:is_distributor) ||
can_edit_as_producer(shipment.order, user)
end
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Payment
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Shipment
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Adjustment
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::ReturnAuthorization
can [:destroy], Spree::Adjustment do |adjustment|
@@ -350,26 +407,35 @@ module Spree
can [:admin, :edit, :cancel, :resume], ProxyOrder do |proxy_order|
user.enterprises.include?(proxy_order.subscription.shop)
end
can [:visible], Enterprise
end
def can_edit_order(order, user)
def can_edit_as_producer(order, user)
return unless order.distributor&.enable_producers_to_edit_orders
order.variants.any? { |variant| user.enterprises.ids.include?(variant.supplier_id) }
end
def add_manage_line_items_abilities(user)
can [:admin, :read, :index, :edit, :update, :bulk_management], Spree::Order do |order|
can_edit_order(order, user)
can [
:admin,
:read,
:index,
:edit,
:update,
:bulk_management,
:edit_as_producer_only
], Spree::Order do |order|
can_edit_as_producer(order, user)
end
can [:admin, :index, :create, :destroy, :update], Spree::LineItem do |item|
can_edit_order(item.order, user)
can_edit_as_producer(item.order, user)
end
can [:index, :create, :add, :read, :edit, :update], Spree::Shipment do |shipment|
can_edit_order(shipment.order, user)
can_edit_as_producer(shipment.order, user)
end
can [:admin, :index], OrderCycle do |order_cycle|
can_edit_order(order_cycle.order, user)
can_edit_as_producer(order_cycle.order, user)
end
can [:visible], Enterprise
end

View File

@@ -109,7 +109,7 @@ module Spree
}
scope :editable_by_producers, ->(enterprises_ids) {
joins(:variant, order: :distributor).where(
joins(variant: :supplier, order: :distributor).where(
distributor: { enable_producers_to_edit_orders: true },
spree_variants: { supplier_id: enterprises_ids }
)

View File

@@ -9,7 +9,7 @@ module Spree
include SetUnusedAddressFields
searchable_attributes :number, :state, :shipment_state, :payment_state, :distributor_id,
:order_cycle_id, :email, :total, :customer_id
:order_cycle_id, :email, :total, :customer_id, :distributor_name_alias
searchable_associations :shipping_method, :bill_address, :distributor
searchable_scopes :complete, :incomplete, :sort_by_billing_address_name_asc,
:sort_by_billing_address_name_desc
@@ -181,6 +181,11 @@ module Spree
scope :by_state, lambda { |state| where(state:) }
scope :not_state, lambda { |state| where.not(state:) }
# This is used to filter line items by the distributor name on BOM page
ransacker :distributor_name_alias do
Arel.sql("distributor.name")
end
def initialize(*_args)
@checkout_processing = nil
@manual_shipping_selection = nil

View File

@@ -194,7 +194,9 @@ module Spree
inventory_units.group_by(&:variant).map do |variant, units|
states = {}
units.group_by(&:state).each { |state, iu| states[state] = iu.count }
scoper.scope(variant)
OpenStruct.new(variant:, quantity: units.length, states:)
end
end

View File

@@ -102,9 +102,8 @@ module Api
end
def display_value_for_producer(order, value)
filter_by_supplier =
order.distributor&.enable_producers_to_edit_orders &&
options[:current_user]&.can_manage_line_items_in_orders_only?
@ability ||= Spree::Ability.new(options[:current_user])
filter_by_supplier = @ability.can?(:edit_as_producer_only, order)
return value unless filter_by_supplier
if order.distributor&.show_customer_names_to_suppliers

View File

@@ -60,6 +60,7 @@ class CartService
def attempt_cart_add(variant, quantity, max_quantity = nil)
scoper.scope(variant)
return unless valid_variant?(variant)
cart_add(variant, quantity, max_quantity)

View File

@@ -5,10 +5,11 @@
module OrderCycles
class DistributedProductsService # rubocop:disable Metrics/ClassLength
def initialize(distributor, order_cycle, customer)
def initialize(distributor, order_cycle, customer, **options)
@distributor = distributor
@order_cycle = order_cycle
@customer = customer
@options = options
end
def products_relation
@@ -26,13 +27,13 @@ module OrderCycles
def variants_relation
order_cycle.
variants_distributed_by(distributor).
merge(stocked_variants_and_overrides).
merge(variants).
select("DISTINCT spree_variants.*")
end
private
attr_reader :distributor, :order_cycle, :customer
attr_reader :distributor, :order_cycle, :customer, :options
def relation_by_sorting
query = Spree::Product.where(id: stocked_products)
@@ -112,10 +113,18 @@ module OrderCycles
def stocked_products
order_cycle.
variants_distributed_by(distributor).
merge(stocked_variants_and_overrides).
merge(variants).
select("DISTINCT spree_variants.product_id")
end
def variants
options[:inventory_enabled] ? stocked_variants_and_overrides : stocked_variants
end
def stocked_variants
Spree::Variant.joins(:stock_items).where(query_stock)
end
def stocked_variants_and_overrides
stocked_variants = Spree::Variant.
joins("LEFT OUTER JOIN variant_overrides ON variant_overrides.variant_id = spree_variants.id
@@ -126,6 +135,10 @@ module OrderCycles
ProductTagRulesFilterer.new(distributor, customer, stocked_variants).call
end
def query_stock
"( #{variant_on_demand} OR #{variant_in_stock} )"
end
def query_stock_with_overrides
"( #{variant_not_overriden} AND ( #{variant_on_demand} OR #{variant_in_stock} ) )
OR ( #{variant_overriden} AND ( #{override_on_demand} OR #{override_in_stock} ) )

View File

@@ -52,6 +52,7 @@ module Orders
next unless variant = Spree::Variant.find_by(id: li[:variant_id])
scoper.scope(variant)
li[:quantity] = stock_limited_quantity(variant.on_demand, variant.on_hand, li[:quantity])
li[:price] = variant.price
build_item_from(li)

View File

@@ -21,16 +21,23 @@ module Permissions
filtered_orders(orders)
end
def managed_or_coordinated_orders_where_clause
Spree::Order.where(
managed_orders_where_values.or(coordinated_orders_where_values)
)
end
# Any orders that the user can edit
def editable_orders
orders = if @user.can_manage_line_items_in_orders_only?
orders = if @user.admin?
# It returns all orders if the user is an admin
managed_or_coordinated_orders_where_clause
else
Spree::Order.joins(:distributor).where(
id: produced_orders.select(:id),
distributor: { enable_producers_to_edit_orders: true }
)
else
Spree::Order.where(
managed_orders_where_values.or(coordinated_orders_where_values)
).or(
managed_or_coordinated_orders_where_clause
)
end
@@ -43,12 +50,19 @@ module Permissions
# Any line items that I can edit
def editable_line_items
if @user.can_manage_line_items_in_orders_only?
managed_or_coordinated_line_items_where_clause = Spree::LineItem.where(
order_id: filtered_orders(managed_or_coordinated_orders_where_clause).select(:id)
)
if @user.admin?
# It returns all line_items if the user is an admin
managed_or_coordinated_line_items_where_clause
else
Spree::LineItem.editable_by_producers(
@permissions.managed_enterprises.select("enterprises.id")
).or(
managed_or_coordinated_line_items_where_clause
)
else
Spree::LineItem.where(order_id: editable_orders.select(:id))
end
end

View File

@@ -8,11 +8,12 @@ class ProductsRenderer
class NoProducts < RuntimeError; end
DEFAULT_PER_PAGE = 10
def initialize(distributor, order_cycle, customer, args = {})
def initialize(distributor, order_cycle, customer, args = {}, **options)
@distributor = distributor
@order_cycle = order_cycle
@customer = customer
@args = args
@options = options
end
def products_json
@@ -28,7 +29,7 @@ class ProductsRenderer
private
attr_reader :order_cycle, :distributor, :customer, :args
attr_reader :order_cycle, :distributor, :customer, :args, :options
def products
return unless order_cycle
@@ -40,8 +41,15 @@ class ProductsRenderer
distributed_products.products_relation
end
results = filter(results)
# Scope results with variant_overrides
paginate(results).each { |product| product_scoper.scope(product) }
paginated_products = paginate(results)
if options[:inventory_enabled]
# Scope results with variant_overrides
paginated_products.each { |product| product_scoper.scope(product) }
end
paginated_products
end
end
@@ -106,19 +114,22 @@ class ProductsRenderer
end
def distributed_products
OrderCycles::DistributedProductsService.new(distributor, order_cycle, customer)
OrderCycles::DistributedProductsService.new(distributor, order_cycle, customer, **options)
end
def variants_for_shop
@variants_for_shop ||= begin
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
# rubocop:disable Rails/FindEach # .each returns an array, .find_each returns nil
distributed_products.variants_relation.
variants = distributed_products.variants_relation.
includes(:default_price, :product).
where(product_id: products).
each { |v| scoper.scope(v) } # Scope results with variant_overrides
# rubocop:enable Rails/FindEach
where(product_id: products)
if options[:inventory_enabled]
# Scope results with variant_overrides
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
variants = variants.each { |v| scoper.scope(v) }
end
variants
end
end

View File

@@ -1,3 +1,5 @@
- connected_apps_enabled.each do |type|
- connected_apps_enabled(enterprise).each do |type|
= render partial: "/admin/enterprises/form/connected_apps/#{type}",
locals: { enterprise:, connected_app: enterprise.connected_apps.public_send(type).first }
= render partial: "/admin/enterprises/form/dfc_permissions" if dfc_platforms_available?

View File

@@ -0,0 +1,30 @@
%script{type: "module", src: "https://cdn.jsdelivr.net/npm/@startinblox/core@latest/dist/index.js"}
%script{type: "module"}
:plain
window.orbit = {
client: {
name: "Orbit",
logo: "https://cdn.startinblox.com/logos/ACME.svg",
},
components: [],
getRoute: (route) => route,
getDefaultRoute: () => "",
getComponent: () => undefined,
getComponentFromRoute: () => undefined,
Swal: () => {},
defaultRoute: "",
federations: {},
componentSet: new Set(["routing", "menu", "menu-top", "autoLogin", "solid-permissioning"]),
};
:plain
<solid-permissioning
data-src="#{DfcProvider::Engine.routes.url_helpers.enterprise_platforms_url(@enterprise.id)}"
scopes-uri="https://cdn.startinblox.com/owl/dfc/taxonomies/scopes.jsonld"
noRouter
auto-lang
lang="en"
auth-token="#{form_authenticity_token}">
</solid-permissioning>
%script{type: "module", src: "https://cdn.jsdelivr.net/npm/@startinblox/solid-data-permissioning@latest/dist/index.js"}

View File

@@ -1,4 +1,6 @@
.row{ "ng-controller": "TagRulesCtrl" }
= render partial: "admin/json/injection_ams", locals: { ngModule: "admin.tagRules", name: "ruleTypes", json: @tag_rule_types.to_json }
.row{ "ng-app" => "admin.tagRules", "ng-controller": "TagRulesCtrl" }
.eleven.columns.alpha.omega
%ofn-sortable{ axis: "y", handle: ".header", items: '.customer_tag', position: "tagGroup.position", "after-sort": "updateRuleCounts()" }
.no_tags{ "ng-show": "tagGroups.length == 0" }

View File

@@ -2,17 +2,18 @@
%h3= t('.title')
= form_for [main_app, :admin, @order_cycle] do |f|
.row
.three.columns.alpha
= f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycles.edit.choose_products_from')
.with-tip{ 'data-powertip' => t('.choose_product_tip', inventory: @order_cycle.coordinator.name) }
%a= t('admin.whats_this')
.four.columns
= f.radio_button :preferred_product_selection_from_coordinator_inventory_only, true
= f.label :preferred_product_selection_from_coordinator_inventory_only, t('.preferred_product_selection_from_coordinator_inventory_only_here')
.four.columns.omega
= f.radio_button :preferred_product_selection_from_coordinator_inventory_only, false
= f.label :preferred_product_selection_from_coordinator_inventory_only, t('.preferred_product_selection_from_coordinator_inventory_only_all')
- if feature?(:inventory, @order_cycle.coordinator)
.row
.three.columns.alpha
= f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycles.edit.choose_products_from')
.with-tip{ 'data-powertip' => t('.choose_product_tip', inventory: @order_cycle.coordinator.name) }
%a= t('admin.whats_this')
.four.columns
= f.radio_button :preferred_product_selection_from_coordinator_inventory_only, true
= f.label :preferred_product_selection_from_coordinator_inventory_only, t('.preferred_product_selection_from_coordinator_inventory_only_here')
.four.columns.omega
= f.radio_button :preferred_product_selection_from_coordinator_inventory_only, false
= f.label :preferred_product_selection_from_coordinator_inventory_only, t('.preferred_product_selection_from_coordinator_inventory_only_all')
.row
.alpha.three.columns

View File

@@ -4,8 +4,10 @@
%h6= t('admin.product_import.index.choose_import_type')
%br
- options = { "#{t('admin.product_import.index.product_list')}" => :product_list }
- options = options.merge("#{t('admin.product_import.index.inventories')}" => :inventories) if feature?(:inventory, spree_current_user.enterprises)
= select_tag "settings[import_into]",
options_for_select({"#{t('admin.product_import.index.product_list')}" => :product_list, "#{t('admin.product_import.index.inventories')}" => :inventories}),
options_for_select(options),
{ "data-controller": "tom-select", class: "primary inline no-search", "ng-model": "settings.import_into" }
%br
%br

View File

@@ -5,9 +5,10 @@
%i.icon-external-link
= t('admin.product_import.index.product_list_template')
%a.download{href: '/inventory_template.csv'}
%i.icon-external-link
= t('admin.product_import.index.inventory_template')
- if feature?(:inventory, spree_current_user.enterprises)
%a.download{href: '/inventory_template.csv'}
%i.icon-external-link
= t('admin.product_import.index.inventory_template')
%h5= t('admin.product_import.index.category_values')
@@ -27,4 +28,4 @@
%strong= t('admin.product_import.index.shipping_categories')
- @shipping_categories.each do |sc|
%span.category= sc
%span.category= sc

View File

@@ -1,4 +1,4 @@
= form_with url: bulk_update_admin_products_path, method: :post, id: "products-form",
= form_with url: admin_products_bulk_update_path, method: :post, id: "products-form",
builder: BulkFormBuilder,
html: { data: { 'turbo-frame': "_self",
controller: "bulk-form",

View File

@@ -3,10 +3,7 @@
.pagination{ "data-controller": "search" }
- if pagy.prev
%button.page.prev{ data: { action: 'click->search#changePage', page: pagy.prev } }
- if feature?(:admin_style_v3, spree_current_user)
%i.icon-chevron-left{ data: { action: 'click->search#changePage', page: pagy.prev } }
- else
!= pagy_t('pagy.prev')
%i.icon-chevron-left{ data: { action: 'click->search#changePage', page: pagy.prev } }
- else
%button.page.disabled{disabled: "disabled"}!= pagy_t('pagy.prev')
@@ -22,9 +19,6 @@
- if pagy.next
%button.page.next{ data: { action: 'click->search#changePage', page: pagy.next } }
- if feature?(:admin_style_v3, spree_current_user)
%i.icon-chevron-right{ data: { action: 'click->search#changePage', page: pagy.next } }
- else
!= pagy_t('pagy.next')
%i.icon-chevron-right{ data: { action: 'click->search#changePage', page: pagy.next } }
- else
%button.page.disabled.pagination-next{disabled: "disabled"}!= pagy_t('pagy.next')

View File

@@ -0,0 +1,28 @@
%div
.summary-subtitle
= t("checkout.step3.delivery_details.address")
%span
= @order.ship_address.firstname
= @order.ship_address.lastname
%div
= @order.ship_address.phone
%div
= @order.user.email if @order.user
%br
%div
= @order.ship_address.address1
- unless @order.ship_address.address2.blank?
%div
= @order.ship_address.address2
%div
= @order.ship_address.city
%div
= @order.ship_address.state
%div
= @order.ship_address.zipcode
%div
= @order.ship_address.country
- if @order.special_instructions.present?
%br
%em
= @order.special_instructions

View File

@@ -91,7 +91,7 @@
"data-toggle-show": shipping_method.require_ship_address
= shipping_method_form.label shipping_method.id, shipping_method.name, {for: "shipping_method_" + shipping_method.id.to_s }
%em.fees= payment_or_shipping_price(shipping_method, @order)
- display_ship_address = display_ship_address || (ship_method_is_selected && shipping_method.require_ship_address)
- display_ship_address ||= ship_method_is_selected && shipping_method.require_ship_address
.checkout-input{"data-shippingmethod-target": "shippingMethodDescription", "data-shippingmethodid": shipping_method.id , style: "display: #{ship_method_is_selected ? 'block' : 'none'}" }
#distributor_address.panel
- if shipping_method.description.present?

View File

@@ -11,34 +11,7 @@
= @order.shipping_method.name
%em.fees= payment_or_shipping_price(@order.shipping_method, @order)
.two-columns
%div
.summary-subtitle
= t("checkout.step3.delivery_details.address")
%span
= @order.ship_address.firstname
= @order.ship_address.lastname
%div
= @order.ship_address.phone
%div
= @order.user.email if @order.user
%br
%div
= @order.ship_address.address1
- unless @order.ship_address.address2.blank?
%div
= @order.ship_address.address2
%div
= @order.ship_address.city
%div
= @order.ship_address.state
%div
= @order.ship_address.zipcode
%div
= @order.ship_address.country
- if @order.special_instructions.present?
%br
%em
= @order.special_instructions
= render "delivery_details" if @order.shipping_method.delivery? || feature?(:hub_address)
- if @order.shipping_method.description.present?
%div
.summary-subtitle

View File

@@ -8,7 +8,7 @@
- if @order.shipments.any?
= render :partial => "spree/admin/orders/shipment", :collection => @order.shipments, :locals => { :order => @order }
- if spree_current_user.can_manage_orders?
- if can?(:manage_order_sections, @order)
- if @order.line_items.exists?
= render partial: "spree/admin/orders/note", locals: { order: @order }

View File

@@ -47,10 +47,10 @@
- if local_assigns[:success]
%i.success.icon-ok-sign{"data-controller": "ephemeral"}
= render AdminTooltipComponent.new(text: t('spree.admin.orders.index.edit'), link_text: "", link: edit_admin_order_path(order), link_class: "icon_link with-tip icon-edit no-text")
- if spree_current_user.can_manage_orders? && order.ready_to_ship?
- if can?(:manage_order_sections, order) && order.ready_to_ship?
%form
= render ShipOrderComponent.new(order: order)
= render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-road icon_link with-tip no-text", reflex_data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.ship'), shipment: true}
- if can?(:update, Spree::Payment) && order.payment_required? && order.pending_payments.reject(&:requires_authorization?).any?
- if can?(:manage_order_sections, order) && can?(:update, Spree::Payment) && order.payment_required? && order.pending_payments.reject(&:requires_authorization?).any?
= render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-capture icon_link no-text", button_reflex: "click->Admin::OrdersReflex#capture", reflex_data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.capture')}

View File

@@ -6,7 +6,7 @@
- content_for :page_actions do
- if can?(:fire, @order)
%li= event_links(@order)
- if spree_current_user.can_manage_orders?
- if can?(:manage_order_sections, @order)
= render partial: 'spree/admin/shared/order_links'
- if can?(:admin, Spree::Order)
%li
@@ -14,7 +14,7 @@
= t(:back_to_orders_list)
= render partial: "spree/admin/shared/order_page_title"
- if spree_current_user.can_manage_orders?
- if can?(:manage_order_sections, @order)
= render partial: "spree/admin/shared/order_tabs", locals: { current: 'Order Details' }
%div
@@ -22,7 +22,13 @@
= admin_inject_shops(@shops, module: 'admin.orders')
= admin_inject_order_cycles(@order_cycles)
%div{"ng-controller" => "orderCtrl", "ofn-distributor-id" => @order.distributor_id, "ofn-order-cycle-id" => @order.order_cycle_id}
%div{
"ng-controller" => "orderCtrl",
"ofn-distributor-id" => @order.distributor_id,
"ofn-order-cycle-id" => @order.order_cycle_id,
"ofn-search-variants-as" => (can?(:manage_order_sections, @order) ? 'hub' : 'supplier'),
"ofn-order-id" => @order.id,
}
= render :partial => 'add_product' if can?(:update, @order)

View File

@@ -1,7 +1,7 @@
= admin_inject_available_units
- content_for :page_actions do
%li= button_link_to t('admin.products.back_to_products_list'), products_return_to_url(@url_filters), :icon => 'icon-arrow-left'
%li= button_link_to t('admin.products.back_to_products_list'), products_return_to_url, :icon => 'icon-arrow-left'
%li#new_product_link
= button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' }
@@ -18,6 +18,6 @@
= button t(:update), 'icon-refresh'
= button_link_to t(:cancel), products_return_to_url(@url_filters), icon: 'icon-remove'
= button_link_to t(:cancel), products_return_to_url, icon: 'icon-remove'
#product-preview-modal-container

View File

@@ -1,14 +0,0 @@
= render 'spree/admin/products/index/header'
= render 'spree/admin/products/index/data'
= admin_inject_available_units
%div{ "ng-app": 'ofn.admin', "ng-controller": 'AdminProductEditCtrl', "ng-init": 'initialise()' }
= render 'spree/admin/products/index/filters'
%div{ 'ng-cloak' => true }
= render 'spree/admin/products/index/actions'
= render 'spree/admin/products/index/indicators'
= render 'spree/admin/products/index/products'
%div{'ng-show' => "!RequestMonitor.loading && products.length > 0" }
= render partial: 'admin/shared/angular_pagination'

View File

@@ -1,11 +0,0 @@
.controls.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' }
.ten.columns.alpha.index-controls
.per-page{'ng-show' => '!RequestMonitor.loading && products.length > 0'}
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'min-search' => 999, 'ng-model' => 'per_page', 'ng-change' => 'fetchProducts()'}
%span.per-page-feedback
{{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }}
{{ 'spree.admin.orders.index.viewing' | t:{start: ((pagination.page -1) * pagination.per_page) +1, end: ((pagination.page -1) * pagination.per_page) + products.length} }}
.six.columns.omega
%columns-dropdown{ action: "#{controller_name}_#{action_name}" }

View File

@@ -1,5 +0,0 @@
= admin_inject_producers(@producers)
= admin_inject_taxons(@taxons)
= admin_inject_tax_categories(@tax_categories)
= admin_inject_spree_api_key(@spree_api_key)
= admin_inject_column_preferences

View File

@@ -1,33 +0,0 @@
%fieldset
%legend{align: 'center'}= t(:search)
.filters.sixteen.columns.alpha.omega
.quick_search.three.columns.alpha
%label{ for: 'quick_filter' }
%br
%input.quick-search.fullwidth{ name: "quick_filter", type: 'text', placeholder: t('admin.quick_search'), "ng-keypress": "$event.keyCode === 13 && fetchProducts()", "ng-model": 'q.query' }
.one.columns &nbsp;
.filter_select.three.columns
%label{ for: 'producer_filter' }= t 'producer'
%br
%select.fullwidth{ id: 'producer_filter', "ofn-select2-min-search": 5, "ng-model": 'q.producerFilter', "ng-options": 'producer.id as producer.name for producer in producers' }
.filter_select.three.columns
%label{ for: 'category_filter' }= t 'category'
%br
%select.fullwidth{ id: 'category_filter', "ofn-select2-min-search": 5, "ng-model": 'q.categoryFilter', "ng-options": 'taxon.id as taxon.name for taxon in taxons' }
.filter_select.three.columns
%label{ for: 'import_filter' }= t 'import_date'
%br
%select.fullwidth{ id: 'import_date_filter', "ofn-select2-min-search": 5, "ng-model": 'q.importDateFilter', "ng-init": "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}", "ng-options": 'date.id as date.name for date in importDates' }
.filter_clear.three.columns.omega
%label{ for: 'clear_all_filters' }
%br
%input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" }
.clearfix
.actions.filter-actions
%div
%a.button.icon-search{'ng-click' => 'fetchProducts()'}
= t(:filter_results)

View File

@@ -1,10 +0,0 @@
- content_for :page_title do
= t('.title')
- content_for :page_actions do
%div{ :class => "toolbar" }
%ul{ :class => "actions header-action-links inline-menu" }
%li#new_product_link
= button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' }
= render partial: 'spree/admin/shared/product_sub_menu'

View File

@@ -1,14 +0,0 @@
.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
= render partial: "components/admin_spinner"
%h1
= t('.title')
.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && q.query.length == 0' }
%h1#no_results= t('.no_products')
.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && q.query.length != 0' }
%h1#no_results
= t('.no_results')
'
{{q.query}}
'

View File

@@ -1,14 +0,0 @@
.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' }
%form{ name: 'bulk_product_form', autocomplete: "off" }
%save-bar{ dirty: "bulk_product_form.$dirty", persist: "false" }
%input.red{ type: "button", value: t(:save_changes), "ng-click": "submitProducts()", "ng-disabled": "!bulk_product_form.$dirty" }
%input{ type: "button", value: t(:close), 'ng-click' => "cancel('#{admin_products_path}')" }
%table.index#listing_products.bulk
= render 'spree/admin/products/index/products_head'
%tbody{ 'ng-repeat' => 'product in products', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
= render 'spree/admin/products/index/products_product'
= render 'spree/admin/products/index/products_variant'

View File

@@ -1,41 +0,0 @@
%colgroup
%col.actions
%col.image{ "ng-show": 'columns.image.visible' }
%col.producer{ "ng-show": 'columns.producer.visible' }
%col.sku{ "ng-show": 'columns.sku.visible' }
%col.name{ "ng-show": 'columns.name.visible' }
%col.unit{ "ng-show": 'columns.unit.visible' }
%col.display_as{ "ng-show": 'columns.unit.visible' }
%col.price{ "ng-show": 'columns.price.visible' }
%col.on_hand{ "ng-show": 'columns.on_hand.visible' }
%col.on_demand{ "ng-show": 'columns.on_demand.visible' }
%col.category{ "ng-show": 'columns.category.visible' }
%col.tax_category{ "ng-show": 'columns.tax_category.visible' }
%col.inherits_properties{ "ng-show": 'columns.inherits_properties.visible' }
%col.import_date{ "ng-show": 'columns.import_date.visible' }
%col.actions
%col.actions
%col.actions
%thead
%tr{ "ng-controller": "ColumnsCtrl" }
%th.left-actions
%a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red; cursor: pointer' }
= t(:expand_all)
%th.image{ 'ng-show' => 'columns.image.visible' }
%th.producer{ 'ng-show' => 'columns.producer.visible' }=t('admin.producer')
%th.sku{ 'ng-show' => 'columns.sku.visible' }=t('admin.sku')
%th.name{ 'ng-show' => 'columns.name.visible' }
= render partial: 'spree/admin/shared/sortable_header', locals: {column_name: 'name'}
%th.unit{ 'ng-show' => 'columns.unit.visible' }=t('.unit')
%th.display_as{ 'ng-show' => 'columns.unit.visible' }=t('.display_as')
%th.price{ 'ng-show' => 'columns.price.visible' }=t('admin.price')
%th.on_hand{ 'ng-show' => 'columns.on_hand.visible' }=t('admin.on_hand')
%th.on_demand{ 'ng-show' => 'columns.on_demand.visible' }=t('admin.on_demand?')
%th.category{ 'ng-show' => 'columns.category.visible' }=t('.category')
%th.tax_category{ 'ng-show' => 'columns.tax_category.visible' }=t('.tax_category')
%th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }=t('.inherits_properties?')
%th.import_date{ 'ng-show' => 'columns.import_date.visible' }=t('.import_date')
%th.actions
%th.actions
%th.actions

View File

@@ -1,33 +0,0 @@
%tr.product{ :id => "p_{{product.id}}" }
%td.left-actions
%a{ 'ofn-toggle-variants' => 'true', :class => "view-variants", 'ng-show' => 'hasVariants(product)' }
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" }
%td.image{ 'ng-show' => 'columns.image.visible' }
%a{class: 'image-modal'}
%img{'ng-src' => '{{ product.thumb_url }}'}
%td.producer{ 'ng-show' => 'columns.producer.visible' }
%td.sku{ 'ng-show' => 'columns.sku.visible' }
%input{ 'ng-model' => "product.sku", :name => 'product_sku', 'ofn-track-product' => 'sku', :type => 'text' }
%td.name{ 'ng-show' => 'columns.name.visible' }
%input{ 'ng-model' => "product.name", :name => 'product_name', 'ofn-track-product' => 'name', :type => 'text' }
%td.unit{ 'ng-show' => 'columns.unit.visible' }
%td.display_as{ 'ng-show' => 'columns.unit.visible' }
%td.price{ 'ng-show' => 'columns.price.visible' }
%input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' }
%td.on_hand{ 'ng-show' => 'columns.on_hand.visible' }
%span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-if' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' }
%input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-if' => '!(hasVariants(product) || product.on_demand)', :type => 'number' }
%td.on_demand{ 'ng-show' => 'columns.on_demand.visible' }
%input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' }
%td.category{ 'ng-if' => 'columns.category.visible' }
%td.tax_category{ 'ng-if' => 'columns.tax_category.visible' }
%td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }
%input{ 'ng-model' => 'product.inherits_properties', :name => 'inherits_properties', 'ofn-track-product' => 'inherits_properties', type: "checkbox" }
%td.import_date{ 'ng-show' => 'columns.import_date.visible' }
%span {{(product.import_date | date:"MMMM dd, yyyy HH:mm") || ""}}
%td.actions
%a{ 'ng-click' => 'editWarn(product)', :class => "edit-product icon-edit no-text", 'ofn-with-tip' => t(:edit) }
%td.actions
%a{ 'ng-click' => 'cloneProduct(product)', :class => "clone-product icon-copy no-text", 'ofn-with-tip' => t(:clone) }
%td.actions
%a{ 'ng-click' => 'deleteProduct(product)', :class => "delete-product icon-trash no-text", 'ofn-with-tip' => t(:remove) }

View File

@@ -1,39 +0,0 @@
%tr.variant{ :id => "v_{{variant.id}}", 'ng-repeat' => 'variant in product.variants', 'ng-show' => 'DisplayProperties.showVariants(product.id)', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
%td.left-actions
%a{ :class => "variant-item icon-caret-right", 'ng-hide' => "$last" }
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last", 'ofn-with-tip' => t('.new_variant') }
%td{ 'ng-show' => 'columns.image.visible' }
%td{ 'ng-show' => 'columns.producer.visible' }
%select.fullwidth{ "data-controller": "tom-select", 'ng-model' => 'variant.producer_id', :name => 'producer_id', 'ofn-track-variant' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' }
%td{ 'ng-show' => 'columns.sku.visible' }
%input{ 'ng-model' => "variant.sku", :name => 'variant_sku', 'ofn-track-variant' => 'sku', :type => 'text' }
%td{ 'ng-show' => 'columns.name.visible' }
%input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" }
%td.unit_value{ 'ng-show' => 'columns.unit.visible' }
%select.no-search{ "data-controller": "tom-select", 'ng-model' => 'variant.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-variant' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' }
%input{ 'ng-model' => 'variant.unit_value_with_description', :name => 'variant_unit_value_with_description', 'ofn-track-variant' => 'unit_value_with_description', :type => 'text', :placeholder => 'value', 'ng-show' => "hasUnit(variant)" }
%input{ 'ng-model' => 'variant.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-variant' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "variant.variant_unit_with_scale == 'items'", :type => 'text' }
%td.display_as{ 'ng-show' => 'columns.unit.visible' }
%input{ 'ofn-display-as' => 'variant', 'ng-model' => 'variant.display_as', name: 'variant_display_as', 'ofn-track-variant' => 'display_as', type: 'text', placeholder: '{{ placeholder_text }}' }
%td{ 'ng-show' => 'columns.price.visible' }
%input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' }
%td{ 'ng-show' => 'columns.on_hand.visible' }
%input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-if' => '!variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' }
%span{ :name => 'variant_on_hand', 'ng-if' => 'variant.on_demand' }
= t(:on_demand)
%td{ 'ng-show' => 'columns.on_demand.visible' }
%input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' }
%td{ 'ng-show' => 'columns.category.visible' }
%input.fullwidth{ type: 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'variant.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-variant' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' }
%td{ 'ng-show' => 'columns.tax_category.visible' }
%select.select2{ name: 'variant_tax_category_id', "ofn-track-variant": 'tax_category_id', "ng-model": 'variant.tax_category_id', "ng-options": 'tax_category.id as tax_category.name for tax_category in tax_categories' }
%option{ value: '' }= t(:none)
%td{ 'ng-show' => 'columns.inherits_properties.visible' }
%td{ 'ng-show' => 'columns.import_date.visible' }
%span {{variant.import_date | date:"MMMM dd, yyyy HH:mm"}}
%td.actions
%a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)", 'ofn-with-tip' => t(:edit) }
%td.actions
%span.icon-warning-sign{ 'ng-if' => 'variant.variant_overrides_count > 0', 'ofn-with-tip' => "{{ 'spree.admin.products.index.products_variant.variant_has_n_overrides' | t:{n: variant.variant_overrides_count} }}" }
%td.actions
%a{ 'ng-click' => 'deleteVariant(product,variant)', "ng-class" => '{disabled: product.variants.length < 2}', :class => "delete-variant icon-trash no-text", 'ofn-with-tip' => t(:remove) }

View File

@@ -1,3 +0,0 @@
.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0', style: "margin-bottom: 10px" }
.four.columns.alpha
%input.four.columns.alpha{ :type => 'button', :value => t(:save_changes), 'ng-click' => 'submitProducts()'}

View File

@@ -14,12 +14,7 @@
= " - OFN #{t(:administration)}"
%link{:href => "https://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600&subset=latin,cyrillic,greek,vietnamese", :rel => "stylesheet", :type => "text/css"}
- if feature?(:admin_style_v3, spree_current_user)
= stylesheet_pack_tag 'admin-style-v3', media: "screen, print"
- else
= stylesheet_pack_tag 'admin-styles', media: "screen, print"
= stylesheet_pack_tag 'admin-style-v3', media: "screen, print"
= render "layouts/bugsnag_js"
- if content_for? :minimal_js

View File

@@ -1,6 +1,6 @@
- content_for :sub_menu do
%ul#sub_nav.inline-menu
= tab :products, :products_v3
= tab :products, :products_v3, url: admin_products_path
= tab :properties
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory'
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory' if feature?(:inventory, spree_current_user.enterprises)
= tab :import, url: main_app.admin_product_import_path, match_path: '/product_import'

View File

@@ -1,11 +1,11 @@
= tab :overview, label: 'dashboard', url: spree.admin_dashboard_path, icon: 'icon-dashboard'
= tab :products, :properties, :inventory, :product_import, :images, :variants, :product_properties, :group_buy_options, :seo, :products_v3, :variant_overrides, url: admin_products_path, icon: 'icon-th-large'
= tab :order_cycles, url: main_app.admin_order_cycles_path, icon: 'icon-refresh'
= tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path, icon: 'icon-shopping-cart'
= tab :reports, url: main_app.admin_reports_path, icon: 'icon-file'
= tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxons, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path
= tab :overview, label: 'dashboard', url: spree.admin_dashboard_path
= tab :products, :properties, :inventory, :product_import, :images, :variants, :product_properties, :group_buy_options, :seo, :products_v3, :variant_overrides, url: admin_products_path
= tab :order_cycles, url: main_app.admin_order_cycles_path
= tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path
= tab :reports, url: main_app.admin_reports_path
= tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxons, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', url: edit_admin_general_settings_path
= tab :enterprises, :enterprise_relationships, :vouchers, :oidc_settings, url: main_app.admin_enterprises_path
= tab :customers, url: main_app.admin_customers_path
= tab :enterprise_groups, url: main_app.admin_enterprise_groups_path, label: 'groups'
- if can? :admin, Spree::User
= tab(:users, :enterprise_roles, url: spree.admin_users_path, icon: 'icon-user')
= tab(:users, :enterprise_roles, url: spree.admin_users_path)

View File

@@ -11,7 +11,7 @@
%thead
%tr
%th.cart-item-description-header= t(:item)
%th.cart-item-price-header.text-right= t(:price)
%th.cart-item-price-header= t(:price)
%th.text-center.cart-item-quantity-header= t(:qty)
%th.cart-item-total-header.text-right= t(:total)
%th.cart-item-delete-header

View File

@@ -18,7 +18,7 @@
= t(".unavailable_item")
%br/
%td.text-right.cart-item-price
%td.cart-item-price
= line_item.single_display_amount_with_adjustments.to_html
%br
%span.unit-price

View File

@@ -1,20 +1,5 @@
// import Flatpickr
import Flatpickr from "stimulus-flatpickr";
import { ar } from "flatpickr/dist/l10n/ar";
import { cat } from "flatpickr/dist/l10n/cat";
import { cy } from "flatpickr/dist/l10n/cy";
import { de } from "flatpickr/dist/l10n/de";
import { fi } from "flatpickr/dist/l10n/fi";
import { fr } from "flatpickr/dist/l10n/fr";
import { it } from "flatpickr/dist/l10n/it";
import { nl } from "flatpickr/dist/l10n/nl";
import { pl } from "flatpickr/dist/l10n/pl";
import { pt } from "flatpickr/dist/l10n/pt";
import { ru } from "flatpickr/dist/l10n/ru";
import { sv } from "flatpickr/dist/l10n/sv";
import { tr } from "flatpickr/dist/l10n/tr";
import { en } from "flatpickr/dist/l10n/default.js";
import { hu } from "flatpickr/dist/l10n/hu";
import ShortcutButtonsPlugin from "shortcut-buttons-flatpickr";
import labelPlugin from "flatpickr/dist/plugins/labelPlugin/labelPlugin";
@@ -24,28 +9,11 @@ export default class extends Flatpickr {
*/
static values = { enableTime: Boolean, mode: String, defaultDate: String };
static targets = ["start", "end"];
locales = {
ar: ar,
cat: cat,
cy: cy,
de: de,
fi: fi,
fr: fr,
it: it,
nl: nl,
pl: pl,
pt: pt,
ru: ru,
sv: sv,
tr: tr,
en: en,
hu: hu,
};
initialize() {
const datetimepicker = this.enableTimeValue === true;
const mode = this.modeValue === "range" ? "range" : "single";
// sets your language (you can also set some global setting for all time pickers)
// configure flatpickr options (locale set dynamically in connect())
this.config = {
altInput: true,
altFormat: datetimepicker
@@ -54,13 +22,18 @@ export default class extends Flatpickr {
dateFormat: datetimepicker ? "Y-m-d H:i" : "Y-m-d",
enableTime: datetimepicker,
time_24hr: datetimepicker,
locale: I18n.base_locale,
plugins: this.plugins(mode, datetimepicker),
mode,
};
}
connect() {
async connect() {
const locale = await this.importFlatpickrLocale(I18n.base_locale);
this.config = {
...this.config,
locale,
};
super.connect();
window.addEventListener("flatpickr:change", this.onChangeEvent);
window.addEventListener("flatpickr:clear", this.clear);
@@ -164,4 +137,16 @@ export default class extends Flatpickr {
this.fp.setDate(moment().add(1, "days").startOf("day").format());
}
}
async importFlatpickrLocale(localeCode) {
// null tells flatpickr to fall back to its built-in english locale
if (!localeCode || localeCode === "en") return null;
try {
const localeModule = await import(`flatpickr/dist/l10n/${localeCode}.js`);
return localeModule.default?.[localeCode] ?? null;
} catch {
return null;
}
}
}

View File

@@ -1,125 +0,0 @@
@import "vendor/assets/stylesheets/normalize";
@import "vendor/assets/stylesheets/responsive-tables";
@import "vendor/assets/stylesheets/jquery.powertip";
@import "~jquery-ui/themes/base/core";
@import "~jquery-ui/themes/base/button";
@import "~jquery-ui/themes/base/resizable";
@import "vendor/assets/stylesheets/jquery-ui-theme";
@import "~jquery-ui/themes/base/dialog";
@import "../shared/ng-tags-input.min";
@import "vendor/assets/stylesheets/select2.css.scss";
@import "~flatpickr/dist/flatpickr";
@import "~flatpickr/dist/themes/material_blue";
@import "~shortcut-buttons-flatpickr/dist/themes/light";
@import "globals/functions";
@import "globals/palette";
@import "globals/variables";
@import "globals/mixins";
@import "plugins/font-awesome";
@import "../shared/variables/layout";
@import "../shared/variables/variables";
@import "../shared/utilities";
@import "shared/typography";
@import "shared/tables";
@import "shared/icons";
@import "shared/forms";
@import "shared/layout";
@import "shared/scroll_bar";
@import "../shared/trix";
@import "plugins/flatpickr-customization";
@import "plugins/powertip";
@import "plugins/select2";
@import "sections/orders";
@import "sections/products";
@import "hacks/mozilla";
@import "hacks/opera";
@import "hacks/ie";
@import "components/actions";
@import "components/alert-box";
@import "components/alert_row";
@import "components/buttons";
@import "components/date-picker";
@import "components/dialogs";
@import "components/input";
@import "components/jquery_dialog";
@import "components/messages";
@import "components/navigation";
@import "components/ng-cloak";
@import "components/page_actions";
@import "components/pagination";
@import "components/per_page_controls";
@import "components/product_autocomplete";
@import "components/progress";
@import "components/save_bar";
@import "components/sidebar";
@import "components/simple_modal";
@import "components/states";
@import "components/stripe_connect_button";
@import "components/subscriptions_states";
@import "components/table-filter";
@import "components/table_loading";
@import "components/timepicker";
@import "components/todo";
@import "components/tooltip";
@import "components/wizard_progress";
@import "pages/enterprise_form";
@import "pages/subscription_form";
@import "pages/subscription_line_items";
@import "pages/subscription_review";
@import "advanced_settings";
@import "alert";
@import "animations";
@import "change_type_form";
@import "connected_apps";
@import "customers";
@import "dashboard_item";
@import "dashboard-single-ent";
@import "dialog";
@import "disabled";
@import "dropdown";
@import "enterprise_index_panels";
@import "enterprises";
@import "filters_and_controls";
@import "grid";
@import "icons";
@import "index_panel_buttons";
@import "index_panels";
@import "modals";
@import "offsets";
@import "openfoodnetwork";
@import "order_cycles";
@import "orders";
@import "product_import";
@import "products";
@import "question-mark-tooltip";
@import "relationships";
@import "reports";
@import "select2";
@import "sidebar-item";
@import "side_menu";
@import "tables";
@import "tag_rules";
@import "terms_of_service_banner";
@import "terms_of_service_files";
@import "validation";
@import "variant_overrides";
@import "welcome";
@import "../shared/question-mark-icon";
@import "question-mark-tooltip";
@import "~tom-select/src/scss/tom-select.default";
@import "components/tom_select";
@import "app/components/modal_component/modal_component";
@import "app/webpacker/css/admin/trix.scss";

View File

@@ -1,31 +0,0 @@
table tbody tr {
&.highlight {
@each $action in $actions {
&.action-#{$action} td {
background-color: get-value($actions, $actions-bg-colors, $action);
border-color: get-value($actions, $actions-brd-colors, $action);
}
}
&.action-remove td,
&.action-void td {
text-decoration: line-through;
&.actions {
text-decoration: none;
}
}
}
&.before-highlight {
@each $action in $actions {
&.action-#{$action} td {
border-bottom-color: get-value($actions, $actions-brd-colors, $action);
}
}
}
td.actions {
background-color: transparent !important;
}
}

View File

@@ -1,93 +0,0 @@
input[type="submit"],
input[type="button"]:not(.trix-button),
button:not(.plain):not(.trix-button),
.button {
position: relative;
cursor: pointer;
font-size: 85%;
@include border-radius($border-radius);
display: inline-block;
padding: 8px 15px;
border: none;
background-color: $color-btn-bg;
color: $color-btn-text;
text-transform: uppercase;
font-weight: 600 !important;
&:before {
font-weight: normal !important;
}
&:visited,
&:active,
&:focus {
color: $color-btn-text;
}
&:hover {
background-color: $color-btn-hover-bg;
color: $color-btn-hover-text;
}
&:active:focus {
box-shadow: 0 0 8px 0 darken($color-btn-hover-bg, 5) inset;
}
&.fullwidth {
width: 100%;
text-align: center;
}
&.secondary {
background-color: transparent;
border: 1px solid $color-btn-bg;
color: $color-btn-bg;
&:hover,
&:active,
&:focus {
background-color: #ebf3fb;
}
&:active:focus {
box-shadow: none;
}
}
.badge {
position: absolute;
top: 0;
right: 0;
transform: translateY(-50%);
font-size: 10px;
text-transform: capitalize;
padding: 0px 5px;
border-radius: 3px;
&:before {
padding: 0;
}
&.danger {
background-color: $color-warning;
}
&.success {
background-color: $spree-green;
}
}
}
input.red,
a.button.red,
button.red {
background-color: $color-warning;
margin-right: 5px;
color: #ffffff;
}
a.button.red {
&:not(:hover) {
color: #fff;
background-color: $color-warning;
}
}

View File

@@ -1,20 +0,0 @@
// scss-lint:disable QualifyingElement
input.datetimepicker {
min-width: 12.9em;
}
.container input[readonly].flatpickr-input,
.container input[readonly].datepicker,
.container input[readonly].datetimepicker {
background-color: $white;
cursor: pointer;
}
img.ui-datepicker-trigger {
margin-left: -1.75em;
position: absolute;
margin-top: 0.5em;
}
// scss-lint:enable QualifyingElement

View File

@@ -1,76 +0,0 @@
.errorExplanation {
padding: 10px;
border: 1px solid very-light($color-error, 12);
background-color: very-light($color-error, 6);
border-radius: 3px;
color: $color-error;
margin-bottom: 15px;
h2 {
font-size: 140%;
color: $color-error;
margin-bottom: 5px;
}
p {
padding: 10px 0;
}
ul {
list-style-position: inside;
li {
font-weight: $font-weight-bold;
}
}
}
.flash-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
.flash {
padding: 18px;
text-align: center;
font-size: 120%;
color: $color-1;
font-weight: 600;
margin-top: 0;
&.notice {
background-color: rgba($color-notice, 0.8);
}
&.success {
background-color: rgba($color-success, 0.8);
}
&.error {
background-color: rgba($color-error, 0.8);
}
// Adjust heights to fit main layout dimension (header, navbar...)
&:nth-child(2) {
padding: 24px;
}
&:nth-child(3) {
padding: 20px;
}
.actions {
display: none; /* avoid adding new button on old design */
}
}
}
.notice:not(.flash) {
padding: 1rem;
margin-bottom: 1.5rem;
background-color: $spree-light-blue;
border-radius: $border-radius;
a {
font-weight: bold;
}
}

View File

@@ -1,174 +0,0 @@
// Navigation
//---------------------------------------------------
.inline-menu {
margin: 0;
-webkit-margin-before: 0;
-webkit-padding-start: 0;
}
nav.menu {
ul {
list-style: none;
li {
a {
padding: 10px 0;
display: block;
position: relative;
text-align: left;
border: 1px solid transparent;
text-transform: uppercase;
font-weight: 600;
font-size: 90%;
}
&.active a {
color: $color-2;
border-left-width: 0;
border-bottom-color: $color-2;
}
&:hover a {
color: $color-2;
}
}
}
}
.admin-login-navigation-bar {
ul {
text-align: right;
li {
padding: 5px 0 5px 10px;
text-align: right;
font-size: 90%;
color: $color-link;
margin-top: 8px;
&.user-logged-in-as {
width: 50%;
color: $color-body-text;
}
&:hover {
i {
color: $color-2;
}
}
}
}
}
#admin-menu {
background-color: $color-3;
ul {
display: flex;
}
li {
min-width: 90px;
flex-grow: 1;
a {
display: block;
padding: 25px 5px;
color: $color-1 !important;
text-transform: uppercase;
position: relative;
text-align: center;
font-weight: 600;
i {
display: inline;
}
&:hover {
background-color: $color-2;
&:after {
content: "";
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 5px solid $color-2;
bottom: 0px;
margin-bottom: -5px;
left: 50%;
margin-left: -10px;
z-index: 1;
}
}
span.text {
font-weight: 600;
}
}
a::before {
font-weight: normal;
padding-top: 0;
}
.dropdown {
width: 300px;
background-color: $color-3;
width: 200px;
z-index: 100000;
> li {
width: 200px !important;
a:after {
display: none;
}
}
}
&.selected a {
@extend a, :hover;
}
}
}
#sub-menu {
background-color: $color-2;
padding-bottom: 0;
li {
a {
display: block;
padding: 12px 20px;
color: $color-1;
text-align: center;
text-transform: uppercase;
position: relative;
font-size: 85%;
}
&.selected a,
a:hover {
&:after {
content: "";
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 5px solid $color-2;
bottom: 0px;
margin-bottom: -5px;
left: 50%;
margin-left: -10px;
z-index: 0;
}
}
}
}
#header figure {
margin: 0.25em 0;
}
#login-nav {
line-height: 1.75em;
}

View File

@@ -1,32 +0,0 @@
.pagination {
text-align: center;
margin: 2em 0 1em;
padding: 10px 0;
.page {
padding: 5px 8px;
text-align: center;
display: inline-block;
text-align: center;
&.current {
background-color: $color-2;
border-radius: 3px;
color: $color-1;
}
}
button {
margin: 0 0.35em;
&.active {
background-color: darken($spree-blue, 15%);
cursor: default;
}
&.disabled {
background-color: $color-btn-disabled-bg;
cursor: default;
}
}
}

View File

@@ -1,26 +0,0 @@
// Sidebar
//---------------------------------------------------
#sidebar {
overflow: visible;
border-top: 1px solid $color-border;
margin-top: 17px;
.sidebar-title {
color: $color-2;
text-transform: uppercase;
text-align: center;
font-size: 14px;
font-weight: 600;
> span {
display: inline;
background: #fff;
padding: 5px 10px;
position: relative;
top: -14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
}

View File

@@ -1,141 +0,0 @@
.ts-wrapper {
min-height: initial;
}
.ts-wrapper.multi {
.ts-control {
box-shadow: none;
border-color: $pale-blue;
&:focus {
border-color: $spree-green;
}
[data-value] {
text-shadow: none;
background-image: none;
background-repeat: initial;
box-shadow: none;
background-color: $spree-blue;
}
}
.ts-control > div {
border: none;
background-color: $spree-blue;
}
}
.ts-wrapper.plugin-remove_button .item .remove {
font-weight: bold;
}
.ts-dropdown {
margin-top: 0;
.option {
min-height: 2.25em;
display: block;
}
}
.ts-wrapper.single .ts-control,
.ts-dropdown.single {
border-color: $color-tbl-border;
}
.ts-control,
.ts-wrapper.single.input-active .ts-control {
cursor: pointer;
padding: 6px 8px;
outline: 0 !important;
min-height: 2.5em;
}
.ts-wrapper.single .ts-control {
padding-right: 2rem;
}
.ts-wrapper.inline,
.ts-wrapper.inline.input-active {
width: fit-content;
.ts-control {
padding-right: 2rem;
}
}
.ts-wrapper.single .ts-control {
box-shadow: none;
background-image: none;
background-color: $white;
}
.ts-wrapper.primary.focus .ts-control,
.ts-wrapper.primary .ts-control {
background-color: $spree-blue;
border-color: $spree-blue;
color: $white;
&:after {
border-color: $white transparent transparent transparent;
}
}
.ts-wrapper .select-multiple {
cursor: pointer;
}
.ts-wrapper.dropdown-active.primary .ts-control {
background-color: $spree-green;
border-color: $spree-green;
color: $white;
&:after {
border-color: transparent transparent $white transparent;
}
}
.dropdown-input-wrap {
padding: 0.2em;
position: relative;
&:before {
@extend [class^="icon-"];
@extend .icon-search;
position: absolute;
opacity: 0.4;
line-height: 2em;
left: 0.75em;
}
.dropdown-input {
outline: 0;
padding: 4px 6px;
border: 1px solid $spree-green;
border-radius: 0.3em;
padding-left: 1.75em;
}
}
.ts-wrapper.no-search {
.dropdown-input-wrap {
display: none;
}
}
.ts-dropdown .create:hover,
.ts-dropdown #admin-menu li.selected a.create,
#admin-menu li.selected .ts-dropdown a.create,
.ts-dropdown .option:hover,
.ts-dropdown #admin-menu li.selected a.option,
#admin-menu li.selected .ts-dropdown a.option,
.ts-dropdown .active {
background-color: $spree-blue;
color: $white;
}
.ts-dropdown [data-selectable] .highlight {
background: rgba(149, 180, 255, 0.26);
}

View File

@@ -1,156 +0,0 @@
// -------------------------------------------------------------
// Variables used in all other files
//--------------------------------------------------------------
// Fonts
//--------------------------------------------------------------
$base-font-family: "Open Sans", "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
// Colors
//--------------------------------------------------------------
// Body base colors
$color-body-bg: $color-1 !default;
$color-body-text: $color-4 !default;
$color-headers: $color-4 !default;
$color-link: $color-3 !default;
$color-link-hover: $color-2 !default;
$color-link-active: $color-2 !default;
$color-link-focus: $color-2 !default;
$color-link-visited: $color-3 !default;
$color-border: very-light($color-3, 12) !default;
// Basic flash colors
$color-success: $color-2 !default;
$color-notice: $color-6 !default;
$color-warning: $color-5 !default;
$color-error: $color-5 !default;
// Table colors
$color-tbl-odd: $color-1 !default;
$color-tbl-even: very-light($color-3, 4) !default;
$color-tbl-thead: very-light($color-3, 4) !default;
$color-tbl-border: $pale-blue !default;
// Button colors
$color-btn-bg: $color-3 !default;
$color-btn-text: $color-1 !default;
$color-btn-hover-bg: $color-2 !default;
$color-btn-hover-text: $color-1 !default;
$color-btn-disabled-bg: $light-grey !default;
// Actions colors
$color-action-edit-bg: very-light($color-success, 5 ) !default;
$color-action-edit-brd: very-light($color-success, 20 ) !default;
$color-action-clone-bg: very-light($color-notice, 5 ) !default;
$color-action-clone-brd: very-light($color-notice, 15 ) !default;
$color-action-remove-bg: very-light($color-error, 5 ) !default;
$color-action-remove-brd: very-light($color-error, 10 ) !default;
$color-action-void-bg: very-light($color-error, 10 ) !default;
$color-action-void-brd: very-light($color-error, 20 ) !default;
$color-action-cancel-bg: very-light($color-notice, 10 ) !default;
$color-action-cancel-brd: very-light($color-notice, 20 ) !default;
$color-action-capture-bg: very-light($color-success, 5 ) !default;
$color-action-capture-brd: very-light($color-success, 20 ) !default;
$color-action-save-bg: very-light($color-success, 5 ) !default;
$color-action-save-brd: very-light($color-success, 20 ) !default;
$color-action-mail-bg: very-light($color-success, 5 ) !default;
$color-action-mail-brd: very-light($color-success, 20 ) !default;
// Select2 select field colors
$color-sel-bg: $color-3 !default;
$color-sel-text: $color-1 !default;
$color-sel-hover-bg: $color-2 !default;
$color-sel-hover-text: $color-1 !default;
// Text inputs colors
$color-txt-brd: $color-border !default;
$color-txt-text: $color-3 !default;
$color-txt-hover-brd: $color-2 !default;
$vpadding-txt: 7px;
$hpadding-txt: 10px;
// Modal colors
$color-modal-close-btn: $color-5 !default;
$color-modal-close-btn-hover: darken($color-5, 5%) !default;
// States label colors
$color-ste-complete-bg: $color-success !default;
$color-ste-complete-text: $color-1 !default;
$color-ste-completed-bg: $color-success !default;
$color-ste-completed-text: $color-1 !default;
$color-ste-sold-bg: $color-success !default;
$color-ste-sold-text: $color-1 !default;
$color-ste-pending-bg: $color-notice !default;
$color-ste-pending-text: $color-1 !default;
$color-ste-requires_authorization-bg: $color-notice !default;
$color-ste-requires_authorization-text: $color-1 !default;
$color-ste-awaiting_return-bg: $color-notice !default;
$color-ste-awaiting_return-text: $color-1 !default;
$color-ste-returned-bg: $color-notice !default;
$color-ste-returned-text: $color-1 !default;
$color-ste-credit_owed-bg: $color-notice !default;
$color-ste-credit_owed-text: $color-1 !default;
$color-ste-paid-bg: $color-success !default;
$color-ste-paid-text: $color-1 !default;
$color-ste-shipped-bg: $color-success !default;
$color-ste-shipped-text: $color-1 !default;
$color-ste-balance_due-bg: $color-notice !default;
$color-ste-balance_due-text: $color-1 !default;
$color-ste-backorder-bg: $color-notice !default;
$color-ste-backorder-text: $color-1 !default;
$color-ste-none-bg: $color-error !default;
$color-ste-none-text: $color-1 !default;
$color-ste-ready-bg: $color-success !default;
$color-ste-ready-text: $color-1 !default;
$color-ste-void-bg: $color-error !default;
$color-ste-void-text: $color-1 !default;
$color-ste-canceled-bg: $color-error !default;
$color-ste-canceled-text: $color-1 !default;
$color-ste-address-bg: $color-error !default;
$color-ste-address-text: $color-1 !default;
$color-ste-checkout-bg: $color-notice !default;
$color-ste-checkout-text: $color-1 !default;
$color-ste-cart-bg: $color-notice !default;
$color-ste-cart-text: $color-1 !default;
$color-ste-payment-bg: $color-error !default;
$color-ste-payment-text: $color-1 !default;
$color-ste-delivery-bg: $color-success !default;
$color-ste-delivery-text: $color-1 !default;
$color-ste-confirmation-bg: $color-error !default;
$color-ste-confirmation-text: $color-1 !default;
$color-ste-active-bg: $color-success !default;
$color-ste-active-text: $color-1 !default;
$color-ste-inactive-bg: $color-notice !default;
$color-ste-inactive-text: $color-1 !default;
// Available states
$states: completed, complete, sold, pending, awaiting_return, returned, credit_owed, paid, shipped, balance_due, backorder, checkout, cart, address, delivery, payment, confirmation, canceled, ready, void, requires_authorization, active, inactive !default;
$states-bg-colors: $color-ste-completed-bg, $color-ste-complete-bg, $color-ste-sold-bg, $color-ste-pending-bg, $color-ste-awaiting_return-bg, $color-ste-returned-bg, $color-ste-credit_owed-bg, $color-ste-paid-bg, $color-ste-shipped-bg, $color-ste-balance_due-bg, $color-ste-backorder-bg, $color-ste-checkout-bg, $color-ste-cart-bg, $color-ste-address-bg, $color-ste-delivery-bg, $color-ste-payment-bg, $color-ste-confirmation-bg, $color-ste-canceled-bg, $color-ste-ready-bg, $color-ste-void-bg, $color-ste-requires_authorization-bg, $color-ste-active-bg, $color-ste-inactive-bg !default;
$states-text-colors: $color-ste-completed-text, $color-ste-complete-text, $color-ste-sold-text, $color-ste-pending-text, $color-ste-awaiting_return-text, $color-ste-returned-text, $color-ste-credit_owed-text, $color-ste-paid-text, $color-ste-shipped-text, $color-ste-balance_due-text, $color-ste-backorder-text, $color-ste-checkout-text, $color-ste-cart-text, $color-ste-address-text, $color-ste-delivery-text, $color-ste-payment-text, $color-ste-confirmation-text, $color-ste-canceled-text, $color-ste-ready-text, $color-ste-void-text, $color-ste-requires_authorization-text, $color-ste-active-text, $color-ste-inactive-text !default;
// Available actions
$actions: edit, clone, remove, void, capture, save, cancel, mail !default;
$actions-bg-colors: $color-action-edit-bg, $color-action-clone-bg, $color-action-remove-bg, $color-action-void-bg, $color-action-capture-bg, $color-action-save-bg, $color-action-cancel-bg, $color-action-mail-bg !default;
$actions-brd-colors: $color-action-edit-brd, $color-action-clone-brd, $color-action-remove-brd, $color-action-void-brd, $color-action-capture-brd, $color-action-save-brd, $color-action-cancel-brd, $color-action-mail-brd !default;
// Sizes
//--------------------------------------------------------------
$body-font-size: 13px !default;
$h6-size: $body-font-size + 2 !default;
$h5-size: $h6-size + 2 !default;
$h4-size: $h5-size + 2 !default;
$h3-size: $h4-size + 2 !default;
$h2-size: $h3-size + 2 !default;
$h1-size: $h2-size + 2 !default;
$border-radius: 3px !default;
$border-input: 1px solid #2e3132; // Copied over from admin_v3 variables as a temporary solution
$font-weight-bold: 600 !default;
$font-weight-normal: 400 !default;
// z-index
//--------------------------------------------------------------
$tos-banner-z-index: 102;

View File

@@ -1,68 +0,0 @@
$background-grey: #eceef1;
$background-blue: $color-3;
// scss-lint:disable SelectorFormat
.flatpickr-calendar {
border-radius: 0;
// Disable animation
&.animate.open {
animation: none;
}
&.arrowBottom::after {
border-top-color: $background-grey;
}
&.arrowTop::after {
border-bottom-color: $background-blue;
}
.flatpickr-months .flatpickr-month {
border-radius: 0;
}
.flatpickr-months .flatpickr-month,
.flatpickr-current-month .flatpickr-monthDropdown-months {
background: $background-blue;
}
.flatpickr-weekdays {
background: $background-blue;
.flatpickr-weekday {
background: $background-blue;
}
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
background: $background-blue;
border-color: $background-blue;
}
}
// scss-lint:enable SelectorFormat
// customization for shortcut-buttons
.shortcut-buttons-flatpickr-wrapper > .shortcut-buttons-flatpickr-buttons {
justify-content: space-between;
flex-grow: 1;
}

View File

@@ -1,118 +0,0 @@
#powerTip {
background-color: $color-3;
padding: 5px 15px;
@include border-radius($border-radius);
&.n:before,
&.ne:before,
&.nw:before {
border-top-width: 5px;
border-top-color: $color-3;
bottom: -5px;
}
&.e:before {
border-right-width: 5px;
border-right-color: $color-3;
left: -5px;
}
&.s:before,
&.se:before,
&.sw:before {
border-bottom-width: 5px;
border-bottom-color: $color-3;
top: -5px;
}
&.w:before {
border-left-width: 5px;
border-left-color: $color-3;
right: -5px;
}
&.ne:before,
&.se:before {
border-right-width: 5px;
border-right-color: $color-3;
left: -5px;
}
&.nw:before,
&.sw:before {
border-left-width: 5px;
border-right-color: $color-3;
right: -5px;
}
&.clone,
&.yellow,
&.cancel {
background-color: $color-notice;
&.n:before,
&.ne:before,
&.nw:before {
border-top-color: $color-notice;
}
&.e:before,
&.nw:before,
&.sw:before {
border-right-color: $color-notice;
}
&.s:before,
&.se:before,
&.sw:before {
border-bottom-color: $color-notice;
}
&.w:before {
border-left-color: $color-notice;
}
}
&.edit,
&.green,
&.capture,
&.save,
&.add {
background-color: $color-success;
&.n:before,
&.ne:before,
&.nw:before {
border-top-color: $color-success;
}
&.e:before,
&.nw:before,
&.sw:before {
border-right-color: $color-success;
}
&.s:before,
&.se:before,
&.sw:before {
border-bottom-color: $color-success;
}
&.w:before {
border-left-color: $color-success;
}
}
&.remove,
&.red,
&.void {
background-color: $color-error;
&.n:before,
&.ne:before,
&.nw:before {
border-top-color: $color-error;
}
&.e:before,
&.nw:before,
&.sw:before {
border-right-color: $color-error;
}
&.s:before,
&.se:before,
&.sw:before {
border-bottom-color: $color-error;
}
&.w:before {
border-left-color: $color-error;
}
}
}

View File

@@ -1,65 +0,0 @@
// Customize orders filter
.admin-orders-index-search {
select[data-placeholder="Status"] {
width: 100%;
}
.select2-container {
width: 100% !important;
}
}
// Order-total
.order-details-total {
text-align: center;
.order-total {
font-size: 35px;
font-weight: 600;
color: $color-success;
}
}
.admin-order-form-fields {
legend.stock-location {
color: $color-body-text;
.shipment-number {
color: $color-success;
}
.stock-location-name {
color: $color-success;
}
}
}
.insufficient-stock-items {
legend {
color: $color-error;
}
table tr:last-child th {
border-bottom: 1px solid $color-tbl-border;
}
}
// Customize orduct add fieldset
#add-line-item {
fieldset {
padding: 10px 0;
.field {
margin-bottom: 0;
input[type="text"],
input[type="number"] {
width: 100%;
}
}
.actions {
.button {
margin-top: 28px;
}
}
}
}

View File

@@ -1,261 +0,0 @@
$text-inputs: "input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel]";
#{$text-inputs},
input[type="date"],
input[type="datetime"],
input[type="time"],
input[type="number"],
textarea,
fieldset {
@include border-radius($border-radius);
padding: $vpadding-txt $hpadding-txt;
border: 1px solid $color-txt-brd;
color: $color-txt-text;
font-size: 90%;
&:focus {
outline: none;
border-color: $color-txt-hover-brd;
}
&[disabled] {
opacity: 0.7;
}
}
textarea {
line-height: 19px;
}
.fullwidth {
width: 100%;
}
label {
font-weight: 600;
text-transform: uppercase;
font-size: 85%;
display: inline;
margin-bottom: 5px;
color: $color-4;
&.inline {
display: inline-block !important;
}
&.block {
display: block !important;
}
}
.label-block label {
display: block;
}
span.info {
font-style: italic;
font-size: 85%;
color: lighten($color-body-text, 15);
display: block;
line-height: 20px;
margin: 5px 0;
}
.field {
padding: 10px 0;
&.checkbox {
min-height: 70px;
input[type="checkbox"] {
display: inline-block;
width: auto;
}
label {
cursor: pointer;
display: block;
}
}
ul {
border-top: 1px solid $color-border;
list-style: none;
padding-top: 5px;
li {
display: inline-block;
padding-right: 10px;
label {
font-weight: normal;
text-transform: none;
}
&.white-space-nowrap {
white-space: nowrap;
}
}
}
// Errors described by default form builder
.field_with_errors {
label {
color: $color-error;
}
input {
border-color: $color-error;
}
}
// Errors described by Spree::Admin::BaseHelper
.formError {
color: $color-error;
font-style: italic;
font-size: 85%;
}
}
fieldset {
box-shadow: none;
box-sizing: border-box;
border-color: $color-border;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
margin-left: 0;
margin-right: 0;
position: relative;
margin-bottom: 35px;
padding: 10px 0 15px 0;
background-color: transparent;
border-left: none;
border-right: none;
border-radius: 0;
&.no-border-bottom {
border-bottom: none;
margin-bottom: 0;
}
&.no-border-top {
border-top: none;
padding-top: 0;
}
legend {
background-color: $color-1;
color: $color-2;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
text-align: center;
padding: 8px 15px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
i {
color: $color-link;
}
}
label {
color: lighten($color-body-text, 8);
}
.filter-actions {
margin-bottom: -32px;
margin-top: 15px;
text-align: center;
form {
display: inline-block;
}
button,
.button,
input[type="submit"],
input[type="button"],
span.or {
@include border-radius($border-radius);
-webkit-box-shadow: 0 0 0 15px $color-1;
-moz-box-shadow: 0 0 0 15px $color-1;
-ms-box-shadow: 0 0 0 15px $color-1;
-o-box-shadow: 0 0 0 15px $color-1;
box-shadow: 0 0 0 15px $color-1;
&:hover {
border-color: $color-1;
}
&:first-of-type {
margin-right: 1.25em;
}
}
span.or {
background-color: $color-1;
border-width: 5px;
margin-left: 5px;
margin-right: 5px;
position: relative;
-webkit-box-shadow: 0 0 0 5px $color-1;
-moz-box-shadow: 0 0 0 5px $color-1;
-ms-box-shadow: 0 0 0 5px $color-1;
-o-box-shadow: 0 0 0 5px $color-1;
box-shadow: 0 0 0 5px $color-1;
}
}
&.labels-inline {
.field {
margin-bottom: 0;
display: table;
width: 100%;
label,
input {
display: table-cell !important;
}
input {
width: 100%;
}
&.checkbox {
input {
width: auto !important;
}
}
}
.actions {
padding: 0;
text-align: right;
}
}
}
.form-buttons {
text-align: center;
}
select {
@extend input, [type="text"];
background-color: white;
}
.inline-checkbox {
display: inline-flex;
align-items: center;
margin-top: 3px;
input,
label {
cursor: pointer;
}
label {
margin: 0;
padding-left: 0.4rem;
}
}

View File

@@ -1,25 +0,0 @@
// Some fixes for fontwesome stylesheets
[class^="icon-"], [class*=" icon-"] {
&:before {
padding-right: 5px;
}
&.button, &.icon_link {
width: auto;
&:before {
padding-top: 3px;
}
}
}
.icon-email:before { @extend .icon-envelope, :before; }
.icon-resend_authorization_email:before { @extend .icon-envelope, :before; }
.icon-resume:before { @extend .icon-refresh, :before; }
.icon-cancel:before,
.icon-void:before { @extend .icon-remove, :before; }
.icon-capture,
.icon-capture_and_complete_order { @extend .icon-ok }
.icon-credit:before { @extend .icon-ok, :before ; }

View File

@@ -1,134 +0,0 @@
// Basics
//---------------------------------------------------
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.admin {
&__section-header {
padding: 15px 0;
background-color: very-light($color-3, 4);
border-bottom: 1px solid $color-border;
.ofn-drop-down {
border: 0;
background-color: $spree-blue;
color: $color-1;
float: none;
margin-left: 3px;
&:hover,
&.expanded {
border: 0;
color: $color-1;
}
}
&__content {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
@media all and (min-width: $tablet_breakpoint) {
flex-wrap: nowrap;
}
}
&__title {
width: 100%;
margin-bottom: 10px;
@media all and (min-width: $tablet_breakpoint) {
margin-bottom: 0;
}
}
&__actions {
display: flex;
flex: 1 0 auto;
align-items: center;
list-style: none;
@media all and (min-width: $tablet_breakpoint) {
justify-content: flex-end;
}
> li {
display: flex;
margin-right: 10px;
&:empty {
display: none;
}
&:last-child {
margin-right: 0;
}
}
}
}
}
.hidden {
display: none;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
.mr-0 {
margin-right: 0 !important;
}
.ml-0 {
margin-left: 0 !important;
}
@media print {
.print-hidden {
display: none !important;
}
}
// Header
//---------------------------------------------------
#header {
background-color: $color-1;
padding: 5px 0;
}
#logo {
height: 40px;
}
.page-title {
i {
color: $color-2;
}
}
// Content
//---------------------------------------------------
#content {
background-color: $color-1;
position: relative;
z-index: 0;
padding: 0;
margin-top: 15px;
}
// Footer
//---------------------------------------------------
#footer {
margin-top: 15px;
border-top: 1px solid $color-border;
padding: 10px 0;
}
@media print {
header,
nav {
display: none;
}
}

View File

@@ -1,208 +0,0 @@
table {
width: 100%;
margin-bottom: 15px;
border-collapse: separate;
th, td {
padding: 7px 5px;
border-right: 1px solid $color-border;
border-bottom: 1px solid $color-border;
vertical-align: middle;
text-overflow: ellipsis;
img {
border: 1px solid transparent;
}
&:first-child {
border-left: 1px solid $color-border;
}
a {
border-bottom: 1px dotted lighten($color-link, 10);
&:hover {
border-color: lighten($color-link-hover, 10);
}
}
.handle {
display: block !important;
text-align: center;
padding-right: 0;
}
&.actions {
background-color: transparent;
border: none !important;
text-align: center;
span.text {
font-size: $body-font-size;
}
[class*='icon-'].no-text {
font-size: 120%;
background-color: very-light($color-3);
border: 1px solid $color-border;
border-radius: 15px;
width: 29px;
height: 29px;
display: inline-block;
padding-top: 2px;
&:before {
text-align: center !important;
width: 27px;
display: inline-block;
}
&:hover {
border-color: transparent;
}
}
button[class*='icon-'] {
color: $color-link;
padding: 0 !important;
}
.icon-envelope-alt, .icon-eye-open {
color: $color-link;
padding-left: 0px;
&:hover {
background-color: $color-3;
color: $color-1;
}
}
.icon-trash:hover, .icon-void:hover {
background-color: $color-error;
color: $color-1;
}
.icon-cancel:hover {
background-color: $color-notice;
color: $color-1;
}
.icon-edit:hover, .icon-capture:hover, .icon-capture_and_complete_order:hover, .icon-ok:hover, .icon-plus:hover, .icon-road:hover {
background-color: $color-success;
color: $color-1;
}
.icon-copy:hover {
background-color: $color-notice;
color: $color-1;
}
}
input[type="number"],
input[type="text"] {
width: 100%;
}
&.no-border {
border-right: none;
}
.handle {
@extend .icon-reorder;
font-family: FontAwesome;
text-decoration: inherit;
display: inline-block;
speak: none;
cursor: move;
}
}
&.no-borders {
td, th {
border: none !important;
}
}
thead {
th {
padding: 10px;
border-top: 1px solid $color-border;
border-bottom: none;
background-color: $color-tbl-thead;
text-transform: uppercase;
font-size: 85%;
font-weight: $font-weight-bold;
}
}
tbody {
tr {
&:first-child th,
&:first-child td {
border-top: 1px solid $color-border;
}
&.even td {
background-color: $color-tbl-even;
img {
border: 1px solid very-light($color-3, 6);
}
}
&:hover td {
background-color: very-light($color-3, 5);
img {
border: 1px solid $color-border;
}
}
&.deleted td {
background-color: very-light($color-error, 6);
border-color: very-light($color-error, 15);
}
&.ui-sortable-placeholder td {
border: 1px solid $color-2 !important;
visibility: visible !important;
&.actions {
background-color: transparent;
border-right: none !important;
border-top: none !important;
border-bottom: none !important;
border-left: 1px solid $color-2 !important;
}
}
&.ui-sortable-helper {
width: 100%;
td {
background-color: lighten($color-3, 33);
border-bottom: 1px solid $color-border;
&.actions {
display: none;
}
}
}
}
&.no-border-top tr:first-child td {
border-top: none;
}
&.grand-total {
td {
border-color: $color-2 !important;
text-transform: uppercase;
font-size: 110%;
font-weight: 600;
background-color: lighten($color-2, 50);
}
.total {
background-color: $color-2;
color: $color-1;
}
}
}
}

View File

@@ -1,158 +0,0 @@
// Base
//--------------------------------------------------------------
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; font-size: $body-font-size; }
body {
font-family: $base-font-family;
font-size: $body-font-size;
font-weight: 400;
color: $color-body-text;
text-rendering: optimizeLegibility;
}
hr {
border-top: 1px solid $color-border;
border-bottom: 1px solid white;
border-left: none;
}
strong, b {
font-weight: 600;
}
// links
//--------------------------------------------------------------
a {
color: $color-link;
text-decoration: none;
line-height: inherit;
&, &:hover, &:active, &:visited, &:focus {
outline: none;
}
&:visited {
color: $color-link-visited;
}
&:focus {
color: $color-link-focus;
}
&:active {
color: $color-link-active;
}
&:hover {
color: $color-link-hover;
}
}
// Headings
//--------------------------------------------------------------
h1,h2,h3,h4,h5,h6 {
font-weight: 600;
color: $color-headers;
line-height: 1.1;
}
h1 { font-size: $h1-size; line-height: $h1-size + 6 }
h2 { font-size: $h2-size; line-height: $h1-size + 4 }
h3 { font-size: $h3-size; line-height: $h1-size + 2 }
h4 { font-size: $h4-size; line-height: $h1-size }
h5 { font-size: $h5-size; line-height: $h1-size }
h6 { font-size: $h6-size; line-height: $h1-size }
// Lists
//--------------------------------------------------------------
ul {
&.inline-menu {
li {
display: inline-block;
}
}
&.fields {
list-style: none;
padding: 0;
margin: 0;
}
}
dl {
width: 100%;
overflow: hidden;
margin: 5px 0;
color: lighten($color-body-text, 15);
dt, dd {
float: left;
line-height: 16px;
padding: 5px;
text-align: justify;
}
dt {
width: 40%;
font-weight: 600;
padding-left: 0;
text-transform: uppercase;
font-size: 85%;
}
dd {
width: 60%;
padding-right: 0;
}
dd:after {
content: '';
clear: both;
}
}
// Helpers
.align-center { text-align: center }
.align-right { text-align: right }
.align-left { text-align: left }
.align-justify { text-align: justify }
.uppercase { text-transform: uppercase }
.green { color: $color-2 }
.blue { color: $color-3 }
.red { color: $color-5 }
.yellow { color: $color-6 }
.no-objects-found {
text-align: center;
font-size: 120%;
text-transform: uppercase;
padding: 40px 0px;
color: lighten($color-body-text, 15);
}
.text-normal {
font-size: 1rem;
font-weight: 300;
}
.text-big {
font-size: 1.2rem;
font-weight: 300;
}
.text-red {
color: $color-warning;
}
input.text-big {
font-size: 1.1rem;
}
.pad-top {
padding-top: 1em;
}
.white-space-nowrap {
white-space: nowrap;
}

View File

@@ -1,27 +0,0 @@
#banner-container {
position: fixed;
bottom: 0px;
left: 0;
width: 100%;
z-index: $tos-banner-z-index;
.terms-of-service-banner {
padding: 18px;
text-align: center;
font-size: 120%;
color: white;
font-weight: 600;
margin-top: 0;
background-color: rgba($color-notice, 0.8);
display: flex;
.column-left {
width: 70%;
}
.column-right {
width: 30%;
text-align: center;
}
}
}

View File

@@ -1,2 +0,0 @@
@import "../css/admin/all.scss";
@import "../../../node_modules/trix/dist/trix.css";

View File

@@ -55,12 +55,30 @@ Openfoodnetwork::Application.configure do
# config.active_support.deprecation = :stderr
# Fail tests on deprecated code unless it's a known case to solve.
ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) do
Rails.application.deprecators.behavior = ->(message, callstack, deprecator) do
allowed_warnings = [
# List strings here to allow matching deprecations.
#
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
"config.active_support.cache_format_version",
# `Rails.application.secrets` is deprecated in favor of `Rails.application.credentials` and will be removed in Rails 7.2
"Rails.application.secrets",
"Passing the class as positional argument",
# Spree::Order model aliases `bill_address`, but `bill_address` is not an attribute. Starting in Rails 7.2, alias_attribute with non-attribute targets will raise. Use `alias_method :billing_address, :bill_address` or define the method manually. (called from initialize at app/models/spree/order.rb:188)
"alias_attribute with non-attribute targets will raise",
# Spree::CreditCard model aliases `cc_type` and has a method called `cc_type=` defined. Starting in Rails 7.2 `brand=` will not be calling `cc_type=` anymore. You may want to additionally define `brand=` to preserve the current behavior.
"model aliases",
# Setting action_dispatch.show_exceptions to true is deprecated. Set to :all instead.
# spec/requests/errors_spec.rb
"action_dispatch.show_exceptions",
]
unless allowed_warnings.any? { |pattern| message.match(pattern) }
ActiveSupport::Deprecation::DEFAULT_BEHAVIORS[:raise].call(message, callstack, deprecation_horizon, gem_name)
ActiveSupport::Deprecation::DEFAULT_BEHAVIORS[:raise].call(message, callstack, deprecator)
end
end

View File

@@ -25,6 +25,12 @@ end
Flipper.register(:new_2024_07_03) do |actor|
actor.respond_to?(:created_at?) && actor.created_at >= "2024-07-03".to_time
end
Flipper.register(:enterprise_created_before_2025_08_11) do |actor|
# This group applies to enterprises only, so we return false if the actor is not an Enterprise
next false unless actor.actor.instance_of? Enterprise
actor.respond_to?(:created_at?) && actor.created_at < "2025-08-11".to_time
end
Flipper::UI.configure do |config|
config.descriptions_source = ->(_keys) do

View File

@@ -110,6 +110,8 @@ ca:
message_html: "<p>El canvi que volies va ser rebutjat. Potser heu intentat canviar alguna cosa a la qual no teniu accés.<br><h3> <a href='/' >Tornar a l'inici</a></h3></p>"
general_error:
message: "Ens sap greu, però alguna cosa ha anat malament.\n\n Pot ser que sigui un problema temporal, així que si us plau, torna-ho a provar o torna a carregar la pàgina.\n Enregistrem tots els errors i és possible que estiguem treballant en una solució.\n Si el problema persisteix o és urgent, poseu-vos en contacte amb nosaltres."
unauthorized:
message: "No rens permisos per a realitzar aquesta acció"
stripe:
error_code:
incorrect_number: "El número de la targeta és incorrecte."
@@ -275,6 +277,7 @@ ca:
Ho analitzarem, però si el problema persisteix, si us plau, aviseu-nos.
backorder_mailer:
backorder_failed:
stock: "Estoc"
product: "Producte"
enterprise_mailer:
confirmation_instructions:

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