Compare commits

...

209 Commits

Author SHA1 Message Date
Rachel Arnould
b5020cc740 Update en.yml
Removing mentions about UFC Que choisir and changing the link
2024-11-25 13:31:36 +01:00
filipefurtad0
7a2a6fab21 Update all locales with the latest Transifex translations 2024-11-22 16:36:15 -06:00
Filipe
0f4ca50d0e Merge pull request #12962 from bouaik/Scroll-to-top-when-using-pagination
Scroll to top when using pagination
2024-11-22 15:13:38 -06:00
Maikel
3ec8cd24d3 Merge pull request #12960 from mkllnk/dfc-link-backorder
Store link to open backorder
2024-11-22 10:22:43 +11:00
Maikel
d0dcc92ca7 Merge pull request #12976 from dacook/update-docker-readme
Update docker readme
2024-11-21 13:25:42 +11:00
Filipe
22f3afc7f7 Merge pull request #12930 from chahmedejaz/task/12878-add-variant-name-in-od-report
Report Orders and Distributors should display variant
2024-11-20 12:23:36 -06:00
Maikel Linke
46048dcd18 Handle empty backorder
Backorder can become empty after a customer cancels their order. Then we
don't want to fail but also don't need to place an order.
2024-11-19 15:53:59 +11:00
Maikel Linke
a8fb6492f4 Lookup backorder for updates with saved link 2024-11-19 15:53:59 +11:00
Maikel Linke
4610141ed8 Add shortcut to order's exchange 2024-11-19 15:53:59 +11:00
Maikel Linke
8098131dba Store link to open backorder
We don't use the link yet, but it's there.
2024-11-19 15:53:59 +11:00
Maikel Linke
597d9ad314 Add semantic links to Exchange 2024-11-19 15:53:59 +11:00
Maikel Linke
1ce0b25bb0 Switch SemanticLink to use new association
And ActiveRecord magic does the rest when used correctly.
2024-11-19 15:53:58 +11:00
Maikel Linke
c07ec6cdfd Polymorphically associate SemanticLinks to variant 2024-11-19 15:53:58 +11:00
Maikel Linke
48e8ad3dd0 Add subject column to semantic links 2024-11-19 15:53:58 +11:00
David Cook
60d4cd60ff Merge pull request #12972 from duleorlovic/12971_add_serbian_lang
Add Serbian lang: tx pull -l sr fix #12971
2024-11-19 09:46:59 +11:00
Ahmed Ejaz
d62d3041b4 12878: add relations in model 2024-11-18 11:45:02 +05:00
Ahmed Ejaz
42fc0f7230 12878: add model in migration 2024-11-18 11:13:02 +05:00
filipefurtad0
328aee6a03 Update all locales with the latest Transifex translations 2024-11-17 20:47:52 -06:00
Rachel Arnould
db79af45fb Merge pull request #12879 from chahmedejaz/task/12776-pay-suppliers-report
[Flower Farms] - Pay Suppliers Report
2024-11-15 11:36:29 +01:00
Ahmed Ejaz
ed7685222e 12776: fix included tax on fees 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
4965e2bb9a Update lib/reporting/reports/suppliers/helpers/line_items_access_helper.rb
Co-authored-by: David Cook <david@redcliffs.net>
2024-11-15 11:09:56 +01:00
Ahmed Ejaz
6b3b29ac39 12776: refactor spec 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
9bcdac8f30 12776: rename vat to tax 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
e2d999da8d 12776: use EnterpriseFeeCalculator in specs 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
bc57447d54 12776: refactor supplier_adjustments method 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
f3e086ad59 12776: remove unnecessary include 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
298c0e8d7f fix reported issues:
- wrong enterprise fees
- always 0 tax on fees
2024-11-15 11:09:56 +01:00
Ahmed Ejaz
ed559b5257 update specs to have more line items 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
1fbdf25296 12776: fix missing order numbers 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
ec0d2d346b use to_date for locale based formating 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
68c0d98736 add slash for abn acn 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
458c8f7608 Update lib/reporting/reports/suppliers/helpers/columns_helper.rb
Co-authored-by: David Cook <david@redcliffs.net>
2024-11-15 11:09:56 +01:00
Ahmed Ejaz
654263a823 add systems spec 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
77f9c6587c fix specs 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
13a614a5aa fix rubocop lines issue 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
add973f1ff 12776: add new line 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
4349e42a84 12776: add specs 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
7cb28fd064 12776: add supplier report 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
122a64e488 12776: add new report option 2024-11-15 11:09:56 +01:00
Ahmed Ejaz
39fa8e0ace 12878: fix migration class name 2024-11-14 11:04:28 +05:00
Maikel
8c6c1e28ff Merge pull request #12967 from rioug/fix-bugsnag-notify
Fix Bugsnag call to notify
2024-11-14 13:42:23 +11:00
David Cook
d9809fc1f4 Update docker readme
I think it was misleading, and considering the challenges everyone seems to have, we need to be more realistic.

[skip ci]
2024-11-14 09:56:59 +11:00
David Cook
889bec7404 Merge pull request #12961 from mkllnk/total-on-hand
Remove unused stock aggregation
2024-11-13 16:35:55 +11:00
David Cook
3f91027c51 Merge pull request #12953 from mkllnk/test-secret-collision
Avoid collision of test secrets
2024-11-13 15:58:41 +11:00
Gaetan Craig-Riou
2f9200b68b Per review, use a better name 2024-11-11 14:03:10 +11:00
filipefurtad0
5d9bb9a8d5 Update all locales with the latest Transifex translations 2024-11-10 18:03:57 -06:00
Filipe
7b677796c1 Merge pull request #12797 from rioug/report-fix-supplier
Fix supplier loading on Product & inventory report
2024-11-10 18:03:20 -06:00
Dusan Orlovic
528c851e89 Add Serbian lang: tx pull -l sr fix #12971 2024-11-10 20:43:13 +01:00
Gaetan Craig-Riou
eb66244b74 Fix Bugsnag call to notify
Make sure we add metadata as expected:
https://docs.bugsnag.com/platforms/ruby/rails/reporting-handled-errors/#add_metadata
2024-11-06 14:01:48 +11:00
Gaetan Craig-Riou
9afd545897 Merge pull request #12959 from openfoodfoundation/dependabot/npm_and_yarn/jquery-ui-1.14.1
Bump jquery-ui from 1.14.0 to 1.14.1
2024-11-04 10:25:26 +11:00
Gaetan Craig-Riou
36f7063897 Merge pull request #12958 from openfoodfoundation/dependabot/npm_and_yarn/elliptic-6.6.0
Bump elliptic from 6.5.7 to 6.6.0
2024-11-04 10:00:23 +11:00
Gaetan Craig-Riou
a53a697e66 Merge pull request #12956 from openfoodfoundation/dependabot/npm_and_yarn/trix-2.1.8
Bump trix from 2.1.7 to 2.1.8
2024-11-04 09:58:42 +11:00
Gaetan Craig-Riou
e9349ce79d Merge pull request #12955 from openfoodfoundation/dependabot/npm_and_yarn/floating-ui/dom-1.6.12
Bump @floating-ui/dom from 1.6.11 to 1.6.12
2024-11-04 09:57:39 +11:00
bouaik
8709c137c7 Scroll to top when using pagination 2024-11-01 11:39:41 +01:00
Maikel Linke
271475893d Remove unused stock aggregation 2024-11-01 16:46:35 +11:00
filipefurtad0
49a24ebd33 Update all locales with the latest Transifex translations 2024-10-31 19:05:25 -06:00
Filipe
a08b0a8b32 Merge pull request #12917 from nicogaldamez/ignore-name-column-for-customers
Ignores name column on customer model
2024-10-31 17:35:47 -06:00
Filipe
0d97f992b9 Merge pull request #12943 from mkllnk/sanitise
Sanitise HTML attributes in the database
2024-10-31 17:32:56 -06:00
Filipe
996d2f0d46 Merge pull request #12947 from mkllnk/staging-baseline
Add scripts to save and restore baseline data
2024-10-31 16:47:23 -06:00
dependabot[bot]
f01a33c545 Bump jquery-ui from 1.14.0 to 1.14.1
Bumps [jquery-ui](https://github.com/jquery/jquery-ui) from 1.14.0 to 1.14.1.
- [Release notes](https://github.com/jquery/jquery-ui/releases)
- [Commits](https://github.com/jquery/jquery-ui/compare/1.14.0...1.14.1)

---
updated-dependencies:
- dependency-name: jquery-ui
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-31 09:58:26 +00:00
dependabot[bot]
48c88d426e Bump elliptic from 6.5.7 to 6.6.0
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.7 to 6.6.0.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.7...v6.6.0)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-31 02:16:59 +00:00
dependabot[bot]
f646a30dca Bump trix from 2.1.7 to 2.1.8
Bumps [trix](https://github.com/basecamp/trix) from 2.1.7 to 2.1.8.
- [Release notes](https://github.com/basecamp/trix/releases)
- [Commits](https://github.com/basecamp/trix/compare/v2.1.7...v2.1.8)

---
updated-dependencies:
- dependency-name: trix
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-30 09:47:49 +00:00
dependabot[bot]
1e21939963 Bump @floating-ui/dom from 1.6.11 to 1.6.12
Bumps [@floating-ui/dom](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/dom) from 1.6.11 to 1.6.12.
- [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.6.12/packages/dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-30 09:47:37 +00:00
Maikel Linke
337113000f Avoid collision of test secrets
A test was failing locally because I have the OpenID client secret set
in my environment. And the dummy value was the same as another test key.
So it got replaced with the wrong value.
2024-10-30 16:03:29 +11:00
David Cook
3756e368c8 Merge pull request #12921 from rioug/12908-error-when-tax-refund
Add Bugsnag notification if we reach tax rate refund code
2024-10-30 14:51:40 +11:00
Gaetan Craig-Riou
54acc97fa1 Merge pull request #12951 from MichaelDimmitt/fix-typo
fix typo
2024-10-30 11:08:12 +11:00
michael
0f6f7b332c fix typo 2024-10-29 08:18:14 -04:00
Maikel
946471923a Merge pull request #12895 from dacook/update-release-template
Update release template
2024-10-29 16:11:18 +11:00
Maikel Linke
3f353690c7 Load staging baseline even if db in use 2024-10-29 12:44:46 +11:00
Maikel
b8822ee179 Merge pull request #12945 from mkllnk/dfc-amend-after-cancel
Amend backorder after cancellations
2024-10-29 11:26:18 +11:00
Gaetan Craig-Riou
701504fbb3 Merge pull request #12938 from mkllnk/restock
Spec restock after order cancellation
2024-10-28 09:56:01 +11:00
filipefurtad0
e9900ec1c7 Update all locales with the latest Transifex translations 2024-10-25 09:35:51 -06:00
Maikel Linke
8a0d9d99e5 Add scripts to save and restore baseline data 2024-10-25 15:07:39 +11:00
Maikel Linke
decf1e6f03 Move old Buildkite scripts to archive folder
We could delete them all but I want use some of their wisdom for new CI
scripts.
2024-10-25 14:23:33 +11:00
Maikel Linke
e0638b1765 Amend backorder after cancellations
The new job class blends code from the BackorderJob and the
CompleteBackorderJob for the specific case of adjusting quantities after
an order has been cancelled.

I would like to write a more general class which can be used for any
order amendmends but this was the quickest solution to cater for
currently running pilots.
2024-10-24 17:08:50 +11:00
Maikel Linke
a5f677f748 Create OidcAccount factory for simpler specs 2024-10-24 17:08:45 +11:00
Maikel Linke
63c83a19d6 Fix backorder spec with incomplete test data 2024-10-24 16:21:39 +11:00
Maikel
762e6ec568 Merge pull request #12940 from dacook/bug-12939
Bug 12939
2024-10-24 10:30:37 +11:00
Maikel Linke
d2e5087668 Remove redundant HTML sanitisation
We don't need to run the sanitiser each time we read an attribute. It's
a waste of time.
2024-10-24 08:47:11 +11:00
Maikel Linke
169e1cf288 Sanitise HTML attributes in the database 2024-10-24 08:47:11 +11:00
David Cook
45ca2961ec Avoid crash 2024-10-23 22:06:53 +11:00
David Cook
1d75aa45ef spec 2024-10-23 21:55:49 +11:00
David Cook
a123369f8d Merge pull request #12935 from mkllnk/dfc-doc-deterministic
Make DFC API docs deterministic
2024-10-23 16:59:55 +11:00
Maikel Linke
90589ae868 Spec restock after order cancellation 2024-10-23 16:35:59 +11:00
Maikel Linke
167a69d2ef Spec change in order state more precisely 2024-10-23 14:46:12 +11:00
Maikel Linke
09524e266f Fix method name description 2024-10-23 14:42:02 +11:00
Maikel
1c58b061b4 Merge pull request #12934 from rioug/fix-unit-price-spec
Use the correct spanish translation
2024-10-22 10:21:26 +11:00
Gaetan Craig-Riou
24df29ddf5 Use the correct spanish translation
Translation has been updated so we need to use the correct spanish
word
2024-10-22 10:10:00 +11:00
drummer83
9f084057a1 Update all locales with the latest Transifex translations 2024-10-21 11:11:39 +02:00
Gaetan Craig-Riou
3f22e8cca7 Fix Bugsnag payload data 2024-10-21 11:13:42 +11:00
Konrad
c3b5456433 Merge pull request #12912 from chahmedejaz/task/12911-remove-admin-v3-toggle
Remove the admin_style_v3 toggle from Production and Staging environments
2024-10-19 13:41:14 +02:00
Konrad
7b0519dab9 Merge pull request #12927 from mkllnk/report-dates
Filter reports by last 3 months by default
2024-10-19 13:12:20 +02:00
Ahmed Ejaz
355541e8de Update db/migrate/20241011071014_update_item_name_to_product_in_od_report.rb
Co-authored-by: David Cook <david@redcliffs.net>
2024-10-18 12:32:14 +05:00
Ahmed Ejaz
e10c3dc59b add specs for migration 2024-10-18 12:31:05 +05:00
Maikel
2609298d88 Merge pull request #12929 from mkllnk/dfc-backorder-fix
Handle case of BackorderJob having no work
2024-10-18 10:04:36 +11:00
Maikel
783de09987 Merge pull request #12932 from mkllnk/dfc-offline-token
Request offline access when connecting OIDC account
2024-10-18 10:03:25 +11:00
Maikel Linke
7b8aeb7ef8 Request offline access when connecting OIDC account 2024-10-18 09:39:49 +11:00
Maikel Linke
9c7105e764 Handle case of BackorderJob having no work 2024-10-17 15:39:32 +11:00
Maikel
afff200680 Merge pull request #12923 from openfoodfoundation/dependabot/npm_and_yarn/trix-2.1.7
Bump trix from 2.1.6 to 2.1.7
2024-10-17 15:26:18 +11:00
Maikel Linke
6c431d4052 Fixup specs to use the new datepicker tools 2024-10-17 15:08:02 +11:00
Maikel Linke
2b8487cc6d Parse given datetime for reports properly 2024-10-17 15:08:02 +11:00
Maikel Linke
b9a72381fc Fix datepicker infinite loop
The new default dates were not aligned with the assumption that the
datepicker would open on the current date. The datepicker helper would
try to navigate to the previous month or next month in relation to the
reference date. Now with the wrong reference date, it would infinitely
go into the past or future, not finding the right year and month.

I chose a more robust approach of setting the year and month directly
which the user can do as well. Then we don't need a reference date.
2024-10-17 15:06:56 +11:00
Maikel Linke
ea8e925077 Show default date range to user in date picker 2024-10-17 13:16:12 +11:00
Maikel Linke
a13e5ced3d Select default dates for Packing report, too 2024-10-17 13:16:12 +11:00
Maikel Linke
aa7fffa5a2 Filter reports by last 3 months by default
The values are not shown on the screen and the user doesn't know which
default dates are applied but the filtering works.
2024-10-17 13:16:12 +11:00
Maikel Linke
3227922c76 Use reports helper to DRY 2024-10-17 13:16:12 +11:00
Maikel Linke
aa2a5757ec Move Customers report spec to own file 2024-10-17 13:16:12 +11:00
David Cook
197363b199 Merge pull request #12924 from openfoodfoundation/dependabot/npm_and_yarn/hotwired/turbo-8.0.12
Bump @hotwired/turbo from 8.0.11 to 8.0.12
2024-10-17 09:49:22 +11:00
Konrad
a023443c75 Merge pull request #12880 from rioug/5574-fix-checkout-order-total-calc
Fix checkout order total and payment fees calculation
2024-10-16 21:16:34 +02:00
Nicolás Galdámez
2e29426834 Deletes failing test
It was a test associated with the migration from name to first_name +
last_name
2024-10-16 08:32:18 -03:00
Nicolás Galdámez
8e4d306901 Ignores name column on customer model
It's not being used because now there are columns for first name and
last name
2024-10-16 08:32:18 -03:00
dependabot[bot]
38196e8ff3 Bump @hotwired/turbo from 8.0.11 to 8.0.12
Bumps [@hotwired/turbo](https://github.com/hotwired/turbo) from 8.0.11 to 8.0.12.
- [Release notes](https://github.com/hotwired/turbo/releases)
- [Commits](https://github.com/hotwired/turbo/commits)

---
updated-dependencies:
- dependency-name: "@hotwired/turbo"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-16 09:12:53 +00:00
dependabot[bot]
475c9fb4ab Bump trix from 2.1.6 to 2.1.7
Bumps [trix](https://github.com/basecamp/trix) from 2.1.6 to 2.1.7.
- [Release notes](https://github.com/basecamp/trix/releases)
- [Commits](https://github.com/basecamp/trix/compare/v2.1.6...v2.1.7)

---
updated-dependencies:
- dependency-name: trix
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-16 09:12:41 +00:00
Ahmed Ejaz
c48162388c 12911: remove admin_style_v3 toggle for prod and staging 2024-10-16 13:14:42 +05:00
Gaetan Craig-Riou
f024aff45d Add Bugsnag notification if we reach tax rate refund code
The original Spree code allow for a tax adjustment to be considered a
refund in a specific scenario:
- instance is using inclusive tax
- instance that applies different tax rate in different tax zones

This scenario should not happen with how our instances are configured
More info: https://github.com/openfoodfoundation/openfoodnetwork/pull/6565#discussion_r566535431
2024-10-16 11:37:21 +11:00
Maikel
ed668ded0a Merge pull request #12913 from mkllnk/dfc-import-items
Import product's invalid weight as 1 item
2024-10-16 10:47:12 +11:00
Gaetan Craig-Riou
b461d499ad Merge pull request #12914 from mkllnk/remove-stock-location-from-return-authorization
Remove StockLocation from ReturnAuthorization
2024-10-16 10:26:09 +11:00
Gaetan Craig-Riou
c1c281122f Merge pull request #12898 from dacook/buu-producer-specs
[BUU] Add missing specs
2024-10-16 10:05:20 +11:00
David Cook
8c4cc051a4 Merge pull request #12916 from openfoodfoundation/dependabot/npm_and_yarn/hotwired/turbo-8.0.11
Bump @hotwired/turbo from 8.0.10 to 8.0.11
2024-10-16 10:01:50 +11:00
David Cook
d5b2408947 Reveal un-implemented tests 2024-10-16 09:51:04 +11:00
Maikel Linke
1eb70370c7 Import product's invalid weight as 1 item
We previously stored a scale which made the product screen believe that
we are dealing with weight.
2024-10-16 09:27:49 +11:00
Filipe
97b6289263 Merge pull request #12787 from rioug/move-variant-unit-attributes-to-variant
[Product Refactor] Move variant unit sizes to variant
2024-10-15 19:58:45 +01:00
dependabot[bot]
bda28dfaf7 Bump @hotwired/turbo from 8.0.10 to 8.0.11
Bumps [@hotwired/turbo](https://github.com/hotwired/turbo) from 8.0.10 to 8.0.11.
- [Release notes](https://github.com/hotwired/turbo/releases)
- [Commits](https://github.com/hotwired/turbo/compare/v8.0.10...v8.0.11)

---
updated-dependencies:
- dependency-name: "@hotwired/turbo"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-15 09:13:53 +00:00
Maikel Linke
d1ebe4e1d1 Make DFC API docs deterministic 2024-10-15 15:47:31 +11:00
Maikel Linke
a64aea4b9c Remove StockLocation from ReturnAuthorization
We have only one default location and don't need it associated to
anything.
2024-10-15 13:36:57 +11:00
David Cook
cc9b764f0f Fix rubocop 2024-10-15 10:37:20 +11:00
David Cook
ac5fa21ff2 Clean up 2024-10-15 10:33:20 +11:00
Gaetan Craig-Riou
01337c12f0 Post rebase, fix product cloning spec 2024-10-14 15:02:35 +11:00
Gaetan Craig-Riou
f8eeca856e Fix invoice print specs 2024-10-14 15:02:35 +11:00
Gaetan Craig-Riou
67c11333f3 Use AdminTooltipComponent, instead of partial 2024-10-14 15:02:35 +11:00
Gaetan Craig-Riou
40afe7e0ab Fix rebase issue 2024-10-14 15:02:34 +11:00
Gaetan Craig-Riou
ef1f3207f7 Update spec/system/admin/products_v3/update_spec.rb
Co-authored-by: Maikel <maikel@email.org.au>
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
b0433bd8f5 Re add test for invalid cloned products
There is no easy way to make the original product invalid, but we can
make sure the cloned product will be invalid. The cloned product add
"COPe OF " in front of the product's name, so by starting with a name
that's long enough, the cloned product will have a name longer that 255
char and will then be invalid.
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
755a394704 Fix spec to remove reliance on browser's message
Client side validation messages depend on the browser's locale, which
we have no controll over. Now we just check a message is set.
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
04e14bf38b Per review, check value are saved in the database 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
ce0c7929a7 Per review, remove the use of raw 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
2a671d491d Remove commented out code 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
7c2c614f90 Update spec/models/spree/variant_spec.rb
Co-authored-by: David Cook <david@redcliffs.net>
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
3bb2232bc1 Remove non updatable check when updating a product
After the product redactor it only checked for the "description" on
product, which is actually skipped when doing an update.
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
377f035ea8 Fix bulk coop report
The current spec is useless, but it has been addressed on master
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
dbca2e2b56 Add all columns moved to variant to ignored_columns 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
0695b434a2 Fix rebase issue 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
9db417319d Improve variant related validation when creating product
I disabled Metrics/AbcSize for ensure_standard_variant as I don't think
that's hard to understand the code. And utimately it will be removed
once product actually becomes optional.
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
a500c75ee9 Add stying for the unit pop out 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
630c398b12 Move unit popout css to a partial 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
64f60d1c8c Fix small bug on edit variant page
- make sure the weight is only cleared when needed
- make sure the displayed unit is up to date
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
218d07c90d Fix product import controller 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
83a619b097 Fix bulk order management page 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
fa986f3fc2 Fix orders and fulfillment report 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
977b6e6c2a Fix minor differences in local env and CI 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
f7446749ff Fix Unit price system spec 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
844cab458e Post rebase fix product import system spec 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
8ec1f61cd7 Fix legacy bulk edit product system spec 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
893b541dca Fix product system spec
The pending spec are to be fix after a rebase, master currently as
some changes which will make this easier
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
4ae392490b Fix variant system spec 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
cda57fdb44 Add toggleOnHand action
It replicate the behavior of setOnDemand angular directive
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
25171413ef Update Spree::Price parsing to match LocalizedNumber.parse
Spree::Price parsing was returning 0.0 when given a an empty string as
price, resulting in a variant being valid even if no price was given. It
only happened if `Spree::LocalizedNumber` wasn't used.
Spree::LocalizedNumber` return nil if given a blank number.
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
4ad6971121 Fix Bulk product edit system spec after rebase 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
8f38762393 Add missing translations for variant form 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
d55950a3c5 Fix rebase issue 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
45075a0ccd Fix rebase typo 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
144a09916c Use instance_double instead of double
Instance double, amongst other thing,  verifies that any methods being
stubbed would be present on an instance of the class
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
00dfe6810f Fix ProductDuplicator
There isn't away I could think of to create an invalid product, so I
removed those test
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
058d7eeb69 Use unit_value_with_description 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
324a4ff591 Backport fix for hungarian instance 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
7f16b6acde Update variant form and rip out angular 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
ce268ec175 Add systemOfMeasurement to VariantUnitManager 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
cc85fed7cc Add localizeCurrency and specs 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
45b0686130 Add PriceParser and UnitPrices and specs
This is in preparation for removing angular from the variant update
page.

Converted using  https://www.codeconvert.ai/coffeescript-to-javascript-converter
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
4cd83d3fd4 Prettify javascript
Also update .prettierignore so that spec files get prettified as well
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
768825d689 Fix Bulk Edit product part 2
Note, the empty entry for unit scale need a css fix , currently showing
at half height
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
e8234ee4a0 Fix Bulk products edit page , part 1 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
94030527a4 Update jest configuration to include webpacker dir
This allows the test environment to correctly resolve import of
services in controller ie: `import OptionValueNamer from
"js/services/option_value_namer";`

The added benefit is we can now import package to be tested directly
without having to specify the whole relative path ie in test file you
can do : `import variant_controller from "controllers/variant_controller";`
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
6ff9650eaf Fix legacy bulk edit products UI 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
b1b534aa1b Fix product and variant api serializer 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
cd74a73680 Consolidate angular option value namer spec
Merge the two spec files into the correct one.
2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
36c4d24c93 Fix angular option value namer 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
9b4cd014bf Fix DFC supplied product builder 2024-10-14 15:01:18 +11:00
Gaetan Craig-Riou
c8bf23bdc2 Fix UnitPrice spec 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
df82dd0759 Fix API v0 variants controller spec 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
5ec39f994a Fix spree admin products controller spec 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
8a31153d6d Fix API v0 products controller spec 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
4109fbde70 Fix variant controller spec 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
37ae217afc Fix product set spec 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
4fd115897a Refactor ProductImport::EntryValidator
Move comparaison function to ProductImport::SpreadsheetEntry
2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
e22804712e Fix product importer 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
d7d253e58d Fix Unit Price service 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
e2c762f06b Refactor, use instance_double in variant spec 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
1ad7123a9d Fix Spree::LineItem 2024-10-14 14:56:47 +11:00
Gaetan Craig-Riou
1793aa3532 Migrate unit sizes to variant 2024-10-14 14:56:46 +11:00
Gaetan Craig-Riou
d0fe1585d7 Move variant unit attributes to variant 2
Update Spree::Product and spec
2024-10-14 14:56:46 +11:00
Gaetan Craig-Riou
f58a3a859f Move variant unit attributes to variant 1
Update Spree::Variant model and spec
2024-10-14 14:56:46 +11:00
Gaetan Craig-Riou
3b89cd5957 Fix option value namer
Uses the variant  variant_unit, variant_unit_name, variant_unit_scale
2024-10-14 14:56:46 +11:00
Gaetan Craig-Riou
e33ed5141b Fix weigths and measures
Use variant_unit, variant_unit_scale from the variant
2024-10-14 14:56:46 +11:00
Gaetan Craig-Riou
4d81b145ca Add variant_unit_scale, variant_unit_name to variant 2024-10-14 14:56:46 +11:00
Ahmed Ejaz
641b7beee3 add specs 2024-10-12 04:47:45 +05:00
Ahmed Ejaz
60e8db9adc 12878: add migration to show new column product column 2024-10-12 03:52:00 +05:00
Ahmed Ejaz
b7285e48b3 12878: update unit to full_name to display in variant column 2024-10-12 03:50:11 +05:00
David Cook
bc87c98e92 Add some specs for the producer dropdowns 2024-10-09 13:02:39 +11:00
David Cook
216883101e Update release template
[skip ci]
2024-10-08 21:02:02 +11:00
Gaetan Craig-Riou
aa5feb6605 Remove system spec
It's covered by unit test of order updater
2024-10-02 09:33:02 +10:00
Gaetan Craig-Riou
b2b6847882 Fix test data
The future is now ! :D
2024-10-01 10:38:33 +10:00
Gaetan Craig-Riou
d01d312b4f Fix updating pending payment
Check if payment actually have an adjustment before trying to update it
2024-10-01 10:22:47 +10:00
Gaetan Craig-Riou
a74cf97083 Fix spec when adding a product with transaction fee
Previous iteration did not actually check the payment fee had been
updated. It also checks the order total get correctly updated.
Spec is passing, so fixing the order updater also fix this bug
: https://github.com/openfoodfoundation/openfoodnetwork/issues/12512
2024-10-01 09:49:44 +10:00
Gaetan Craig-Riou
03dbd54b25 Fix order updater to update payment fees
The order updater did not take into account payment fees on pending
payment.
2024-09-30 16:15:59 +10:00
Gaetan Craig-Riou
fafd86a2db Revert change made in https://github.com/openfoodfoundation/openfoodnetwork/pull/12538
Although the change fix the issue in the back office scenario, it has
the side effect of getting the order total out of sync. Updating a
payment adjustment need to be followed by udpating the order total and
payment amount to keep everything in sync.
2024-09-30 16:04:44 +10:00
Gaetan Craig-Riou
eb8050d61d Add spec reproducing the bug 2024-09-25 15:58:28 +10:00
Gaetan Craig-Riou
ef6e37e7ca Fix suppliers_of_products_distributed_by
Plus spec

Left over from product refactor, it was missed because it's not covered
by unit or integration test
2024-08-21 13:05:34 +10:00
Gaetan Craig-Riou
ffc2fed9b5 Remove unused code 2024-08-20 16:43:22 +10:00
256 changed files with 11104 additions and 3609 deletions

View File

@@ -16,7 +16,7 @@ STRIPE_PUBLIC_TEST_API_KEY="bogus_stripe_publishable_key"
SITE_URL="test.host"
OPENID_APP_ID="test-provider"
OPENID_APP_SECRET="12345"
OPENID_APP_SECRET="dummy-openid-app-secret-token"
OPENID_REFRESH_TOKEN="dummy-refresh-token"
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="test_primary_key"

View File

@@ -7,7 +7,7 @@ assignees: ''
---
## 1. Preparation on Thursday
## 1. Drafting on Friday
- [ ] Merge pull requests in the [Ready To Go] column
- [ ] Include translations: `script/release/update_locales`
@@ -26,8 +26,9 @@ assignees: ''
- [ ] Move this issue to Test Ready.
- [ ] Notify `@testers` in [#testing].
- [ ] Test build: [Deploy to Staging] with release tag.
- [ ] Notify a deployer to deploy it
## 3. Finish on Tuesday
## 3. Deployment at beginning of week
- [ ] Publish and notify [#global-community] (this is automatically posted with a plugin)
- [ ] Deploy the new release to all managed instances.
@@ -40,7 +41,7 @@ assignees: ''
</details>
- [ ] Notify [#instance-managers]:
> @instance_managers The new release has been deployed.
- [ ] [Create issue] for next release and confirm with next release manager in [#core-devs].
- [ ] [Create issue] for next release and confirm with next release drafter in [#delivery-circle].
The full process is described at https://github.com/openfoodfoundation/openfoodnetwork/wiki/Releasing.
@@ -53,5 +54,5 @@ The full process is described at https://github.com/openfoodfoundation/openfoodn
[Deploy to Staging]: https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/stage.yml
[#global-community]: https://app.slack.com/client/T02G54U79/C59ADD8F2
[Create issue]: https://github.com/openfoodfoundation/openfoodnetwork/issues/new?assignees=&labels=&projects=&template=release.md&title=Release
[#core-devs]: https://openfoodnetwork.slack.com/archives/GK2T38QPJ
[#delivery-circle]: https://openfoodnetwork.slack.com/archives/C01T75H6G0Z
[Transifex Client]: https://developers.transifex.com/docs/cli

View File

@@ -5,6 +5,7 @@
*.yaml
*.json
*.html
**/*.rb
# JS
# Enabled: app/webpacker/controllers/*.js and app/webpacker/packs/*.js
@@ -27,6 +28,5 @@ postcss.config.js
/coverage/
/engines/
/public/
/spec/
/tmp/
/vendor/

View File

@@ -31,7 +31,7 @@ This project needs specific ruby/bundler versions as well as node/yarn specific
* Install or change your Ruby version according to the one specified at [.ruby-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.ruby-version) file.
- To manage versions, it's recommended to use [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://rvm.io/).
* Install [nodenv](https://github.com/nodenv/nodenv) to ensure the correct [.node-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.node-version) is used.
- [nodevn](https://github.com/nodenv/nodenv) is recommended as a node version manager.
- [nodenv](https://github.com/nodenv/nodenv) is recommended as a node version manager.
* PostgreSQL database
* Redis (for background jobs)
* Chrome (for testing)

View File

@@ -187,9 +187,8 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
product.variants.length > 0
$scope.hasUnit = (product) ->
product.variant_unit_with_scale?
$scope.hasUnit = (variant) ->
variant.variant_unit_with_scale?
$scope.variantSaved = (variant) ->
variant.hasOwnProperty('id') && variant.id > 0
@@ -242,32 +241,28 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$window.location = destination
$scope.packProduct = (product) ->
if product.variant_unit_with_scale
match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
product.variant_unit = match[1]
product.variant_unit_scale = parseFloat(match[2])
else
product.variant_unit = product.variant_unit_with_scale
product.variant_unit_scale = null
else
product.variant_unit = product.variant_unit_scale = null
if product.variants
for id, variant of product.variants
$scope.packVariant product, variant
$scope.packVariant variant
$scope.packVariant = (product, 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
product = BulkProducts.find product.id
variant.unit_value = parseFloat(match[1].replace(",", "."))
variant.unit_value = null if isNaN(variant.unit_value)
if variant.unit_value && product.variant_unit_scale
variant.unit_value = parseFloat(window.bigDecimal.multiply(variant.unit_value, product.variant_unit_scale, 2))
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 = ->
@@ -321,13 +316,6 @@ filterSubmitProducts = (productsToFilter) ->
if product.hasOwnProperty("price")
filteredProduct.price = product.price
hasUpdatableProperty = true
if product.hasOwnProperty("variant_unit_with_scale")
filteredProduct.variant_unit = product.variant_unit
filteredProduct.variant_unit_scale = product.variant_unit_scale
hasUpdatableProperty = true
if product.hasOwnProperty("variant_unit_name")
filteredProduct.variant_unit_name = product.variant_unit_name
hasUpdatableProperty = true
if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present
filteredProduct.on_hand = product.on_hand
hasUpdatableProperty = true
@@ -383,6 +371,14 @@ filterSubmitVariant = (variant) ->
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}

View File

@@ -4,31 +4,30 @@ angular.module("ofn.admin").directive "ofnDisplayAs", (OptionValueNamer) ->
scope.$watchCollection ->
return [
scope.$eval(attrs.ofnDisplayAs).unit_value_with_description
scope.product.variant_unit_name
scope.product.variant_unit_with_scale
scope.variant.variant_unit_name
scope.variant.variant_unit_with_scale
]
, ->
[variant_unit, variant_unit_scale] = productUnitProperties()
[unit_value, unit_description] = variantUnitProperties(variant_unit_scale)
variant_object =
variant_object =
unit_value: unit_value
unit_description: unit_description
product:
variant_unit_scale: variant_unit_scale
variant_unit: variant_unit
variant_unit_name: scope.product.variant_unit_name
variant_unit_scale: variant_unit_scale
variant_unit: variant_unit
variant_unit_name: scope.variant.variant_unit_name
scope.placeholder_text = new OptionValueNamer(variant_object).name()
productUnitProperties = ->
# get relevant product properties
if scope.product.variant_unit_with_scale?
match = scope.product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if scope.variant.variant_unit_with_scale?
match = scope.variant.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
variant_unit = match[1]
variant_unit_scale = parseFloat(match[2])
else
variant_unit = scope.product.variant_unit_with_scale
variant_unit = scope.variant.variant_unit_with_scale
variant_unit_scale = null
else
variant_unit = variant_unit_scale = null
@@ -45,4 +44,4 @@ angular.module("ofn.admin").directive "ofnDisplayAs", (OptionValueNamer) ->
unit_value = null if isNaN(unit_value)
unit_value *= variant_unit_scale if unit_value && variant_unit_scale
unit_description = match[3]
[unit_value, unit_description]
[unit_value, unit_description]

View File

@@ -1,8 +0,0 @@
angular.module("ofn.admin").directive "ofnMaintainUnitScale", ->
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
scope.$watch 'product.variant_unit_with_scale', (newValue, oldValue) ->
if not (oldValue == newValue)
# Triggers track-variant directive to track the unit_value, so that changes to the unit are passed to the server
ngModel.$setViewValue ngModel.$viewValue

View File

@@ -1,8 +0,0 @@
angular.module("ofn.admin").directive "ofnTrackMaster", (DirtyProducts) ->
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
ngModel.$parsers.push (viewValue) ->
if ngModel.$dirty
DirtyProducts.addMasterProperty scope.product.id, scope.product.master.id, attrs.ofnTrackMaster, viewValue
scope.displayDirtyProducts()
viewValue

View File

@@ -199,14 +199,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.refreshData()
$scope.getLineItemScale = (lineItem) ->
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
lineItem.units_product.variant_unit_scale
if lineItem.units_variant && lineItem.units_variant.variant_unit_scale && (lineItem.units_variant.variant_unit == "weight" || lineItem.units_variant.variant_unit == "volume")
lineItem.units_variant.variant_unit_scale
else
1
$scope.sumUnitValues = ->
sum = $scope.filteredLineItems?.reduce (sum, lineItem) ->
if lineItem.units_product.variant_unit == "items"
if lineItem.units_variant.variant_unit == "items"
sum + lineItem.quantity
else
sum + $scope.roundToThreeDecimals(lineItem.final_weight_volume / $scope.getLineItemScale(lineItem))
@@ -214,7 +214,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.sumMaxUnitValues = ->
sum = $scope.filteredLineItems?.reduce (sum,lineItem) ->
if lineItem.units_product.variant_unit == "items"
if lineItem.units_variant.variant_unit == "items"
sum + lineItem.max_quantity
else
sum + lineItem.max_quantity * $scope.roundToThreeDecimals(lineItem.units_variant.unit_value / $scope.getLineItemScale(lineItem))
@@ -228,39 +228,41 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
return false if !lineItem.hasOwnProperty('final_weight_volume') || !(lineItem.final_weight_volume > 0)
true
$scope.getScale = (unitsProduct, unitsVariant) ->
if unitsProduct.hasOwnProperty("variant_unit") && (unitsProduct.variant_unit == "weight" || unitsProduct.variant_unit == "volume")
unitsProduct.variant_unit_scale
else if unitsProduct.hasOwnProperty("variant_unit") && unitsProduct.variant_unit == "items"
$scope.getScale = (unitsVariant) ->
if unitsVariant.hasOwnProperty("variant_unit") && (unitsVariant.variant_unit == "weight" || unitsVariant.variant_unit == "volume")
unitsVariant.variant_unit_scale
else if unitsVariant.hasOwnProperty("variant_unit") && unitsVariant.variant_unit == "items"
1
else
null
$scope.getFormattedValueWithUnitName = (value, unitsProduct, unitsVariant, scale) ->
unit_name = VariantUnitManager.getUnitName(scale, unitsProduct.variant_unit)
$scope.getFormattedValueWithUnitName = (value, unitsVariant, scale) ->
unit_name = VariantUnitManager.getUnitName(scale, unitsVariant.variant_unit)
$scope.roundToThreeDecimals(value) + " " + unit_name
$scope.getGroupBySizeFormattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
scale = $scope.getScale(unitsProduct, unitsVariant)
$scope.getGroupBySizeFormattedValueWithUnitName = (value, unitsVariant) ->
scale = $scope.getScale(unitsVariant)
if scale && value
value = value / scale if scale != 28.35 && scale != 1 && scale != 453.6 # divide by scale if not smallest unit
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
$scope.getFormattedValueWithUnitName(value, unitsVariant, scale)
else
''
$scope.formattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
scale = $scope.getScale(unitsProduct, unitsVariant)
$scope.formattedValueWithUnitName = (value, unitsVariant) ->
scale = $scope.getScale(unitsVariant)
if scale
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
$scope.getFormattedValueWithUnitName(value, unitsVariant, scale)
else
''
$scope.fulfilled = (sumOfUnitValues) ->
# A Units Variant is an API object which holds unit properies of a variant
if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size")&& $scope.selectedUnitsProduct.group_buy_unit_size > 0 &&
$scope.selectedUnitsProduct.hasOwnProperty("variant_unit")
if $scope.selectedUnitsProduct.variant_unit == "weight" || $scope.selectedUnitsProduct.variant_unit == "volume"
scale = $scope.selectedUnitsProduct.variant_unit_scale
if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size") && $scope.selectedUnitsProduct.group_buy_unit_size > 0 &&
$scope.selectedUnitsVariant.hasOwnProperty("variant_unit")
if $scope.selectedUnitsVariant.variant_unit == "weight" || $scope.selectedUnitsVariant.variant_unit == "volume"
scale = $scope.selectedUnitsVariant.variant_unit_scale
sumOfUnitValues = sumOfUnitValues * scale unless scale == 28.35 || scale == 453.6
$scope.roundToThreeDecimals(sumOfUnitValues / $scope.selectedUnitsProduct.group_buy_unit_size)
else

View File

@@ -1,24 +0,0 @@
angular.module("admin.products").controller "editUnitsCtrl", ($scope, VariantUnitManager) ->
$scope.product =
variant_unit: angular.element('#variant_unit').val()
variant_unit_scale: angular.element('#variant_unit_scale').val()
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
if $scope.product.variant_unit == 'items'
$scope.variant_unit_with_scale = 'items'
else
$scope.variant_unit_with_scale = $scope.product.variant_unit + '_' + $scope.product.variant_unit_scale.replace(/\.0$/, '');
$scope.setFields = ->
if $scope.variant_unit_with_scale == 'items'
variant_unit = 'items'
variant_unit_scale = null
else
options = $scope.variant_unit_with_scale.split('_')
variant_unit = options[0]
variant_unit_scale = options[1]
$scope.product.variant_unit = variant_unit
$scope.product.variant_unit_scale = variant_unit_scale

View File

@@ -1,15 +1,14 @@
# Controller for "New Products" form (spree/admin/products/new)
angular.module("admin.products")
.controller "unitsCtrl", ($scope, VariantUnitManager, OptionValueNamer, UnitPrices, PriceParser) ->
$scope.product = { master: {} }
$scope.product.master.product = $scope.product
$scope.product = {}
$scope.placeholder_text = ""
$scope.$watchCollection '[product.variant_unit_with_scale, product.master.unit_value_with_description, product.price, product.variant_unit_name]', ->
$scope.$watchCollection '[product.variant_unit_with_scale, product.unit_value_with_description, product.price, product.variant_unit_name]', ->
$scope.processVariantUnitWithScale()
$scope.processUnitValueWithDescription()
$scope.processUnitPrice()
$scope.placeholder_text = new OptionValueNamer($scope.product.master).name() if $scope.product.variant_unit_scale
$scope.placeholder_text = new OptionValueNamer($scope.product).name() if $scope.product.variant_unit_scale
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
@@ -38,24 +37,24 @@ angular.module("admin.products")
# Extract unit_value and unit_description from text field unit_value_with_description,
# and update hidden variant fields
$scope.processUnitValueWithDescription = ->
if $scope.product.master.hasOwnProperty("unit_value_with_description")
match = $scope.product.master.unit_value_with_description.match(/^([\d\.,]+(?= *|$)|)( *)(.*)$/)
if $scope.product.hasOwnProperty("unit_value_with_description")
match = $scope.product.unit_value_with_description.match(/^([\d\.,]+(?= *|$)|)( *)(.*)$/)
if match
$scope.product.master.unit_value = PriceParser.parse(match[1])
$scope.product.master.unit_value = null if isNaN($scope.product.master.unit_value)
$scope.product.master.unit_value = window.bigDecimal.multiply($scope.product.master.unit_value, $scope.product.variant_unit_scale, 2) if $scope.product.master.unit_value && $scope.product.variant_unit_scale
$scope.product.master.unit_description = match[3]
$scope.product.unit_value = PriceParser.parse(match[1])
$scope.product.unit_value = null if isNaN($scope.product.unit_value)
$scope.product.unit_value = window.bigDecimal.multiply($scope.product.unit_value, $scope.product.variant_unit_scale, 2) if $scope.product.unit_value && $scope.product.variant_unit_scale
$scope.product.unit_description = match[3]
else
value = $scope.product.master.unit_value
value = window.bigDecimal.divide(value, $scope.product.variant_unit_scale, 2) if $scope.product.master.unit_value && $scope.product.variant_unit_scale
$scope.product.master.unit_value_with_description = value + " " + $scope.product.master.unit_description
value = $scope.product.unit_value
value = window.bigDecimal.divide(value, $scope.product.variant_unit_scale, 2) if $scope.product.unit_value && $scope.product.variant_unit_scale
$scope.product.unit_value_with_description = value + " " + $scope.product.unit_description
# Calculate unit price based on product price and variant_unit_scale
$scope.processUnitPrice = ->
price = $scope.product.price
scale = $scope.product.variant_unit_scale
unit_type = $scope.product.variant_unit
unit_value = $scope.product.master.unit_value
unit_value = $scope.product.unit_value
variant_unit_name = $scope.product.variant_unit_name
$scope.unit_price = UnitPrices.displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name)

View File

@@ -1,32 +0,0 @@
angular.module("admin.products").controller "variantUnitsCtrl", ($scope, VariantUnitManager, $timeout, UnitPrices, PriceParser) ->
$scope.unitName = (scale, type) ->
VariantUnitManager.getUnitName(scale, type)
$scope.$watchCollection "[unit_value_human, variant.price]", ->
$scope.processUnitPrice()
$scope.processUnitPrice = ->
if ($scope.variant)
price = $scope.variant.price
scale = $scope.scale
unit_type = angular.element("#product_variant_unit").val()
if (unit_type != "items")
$scope.updateValue()
unit_value = $scope.unit_value
else
unit_value = 1
variant_unit_name = angular.element("#product_variant_unit_name").val()
$scope.unit_price = UnitPrices.displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name)
$scope.scale = angular.element('#product_variant_unit_scale').val()
$scope.updateValue = ->
unit_value_human = angular.element('#unit_value_human').val()
$scope.unit_value = bigDecimal.multiply(PriceParser.parse(unit_value_human), $scope.scale, 2)
variant_unit_value = angular.element('#variant_unit_value').val()
$scope.unit_value_human = parseFloat(bigDecimal.divide(variant_unit_value, $scope.scale, 2))
$timeout -> $scope.processUnitPrice()
$timeout -> $scope.updateValue()

View File

@@ -1,19 +0,0 @@
angular.module("admin.products").directive "setOnDemand", ->
link: (scope, element, attr) ->
onHand = element.context.querySelector("#variant_on_hand")
onDemand = element.context.querySelector("#variant_on_demand")
disableOnHandIfOnDemand = ->
if onDemand.checked
onHand.disabled = 'disabled'
onHand.dataStock = onHand.value
onHand.value = t('admin.products.variants.infinity')
disableOnHandIfOnDemand()
onDemand.addEventListener 'change', (event) ->
disableOnHandIfOnDemand()
if !onDemand.checked
onHand.removeAttribute('disabled')
onHand.value = onHand.dataStock

View File

@@ -13,16 +13,16 @@ angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager
name_fields.join ' '
value_scaled: ->
@variant.product.variant_unit_scale?
@variant.variant_unit_scale?
option_value_value_unit: ->
if @variant.unit_value?
if @variant.product.variant_unit in ["weight", "volume"]
if @variant.variant_unit in ["weight", "volume"]
[value, unit_name] = @option_value_value_unit_scaled()
else
value = @variant.unit_value
unit_name = @pluralize(@variant.product.variant_unit_name, value)
unit_name = @pluralize(@variant.variant_unit_name, value)
value = parseInt(value, 10) if value == parseInt(value, 10)
@@ -58,14 +58,13 @@ angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager
# to >= 1 when expressed in it.
# If there is none available where this is true, use the smallest
# available unit.
product = @variant.product
scales = VariantUnitManager.compatibleUnitScales(product.variant_unit_scale, product.variant_unit)
scales = VariantUnitManager.compatibleUnitScales(@variant.variant_unit_scale, @variant.variant_unit)
variantUnitValue = @variant.unit_value
# sets largestScale = last element in filtered scales array
[_, ..., largestScale] = (scales.filter (s) -> variantUnitValue / s >= 1)
if (largestScale)
[largestScale, VariantUnitManager.getUnitName(largestScale, product.variant_unit)]
[largestScale, VariantUnitManager.getUnitName(largestScale, @variant.variant_unit)]
else
[scales[0], VariantUnitManager.getUnitName(scales[0], product.variant_unit)]
[scales[0], VariantUnitManager.getUnitName(scales[0], @variant.variant_unit)]

View File

@@ -19,7 +19,7 @@ angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetche
for server_product in serverProducts
product = @findProductInList(server_product.id, @products)
product.variants = server_product.variants
@loadVariantUnitValues product
@loadVariantUnitValues product.variants
find: (id) ->
@findProductInList id, @products
@@ -38,34 +38,32 @@ angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetche
@products.splice(index + 1, 0, newProduct)
unpackProduct: (product) ->
#$scope.matchProducer product
@loadVariantUnit product
loadVariantUnit: (product) ->
product.variant_unit_with_scale =
if product.variant_unit && product.variant_unit_scale && product.variant_unit != 'items'
"#{product.variant_unit}_#{product.variant_unit_scale}"
else if product.variant_unit
product.variant_unit
@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
@loadVariantUnitValues product if product.variants
@loadVariantUnitValue product, product.master if product.master
loadVariantUnitValues: (product) ->
for variant in product.variants
@loadVariantUnitValue product, variant
loadVariantUnitValue: (product, variant) ->
unit_value = @variantUnitValue product, variant
unit_value = @variantUnitValue variant
unit_value = if unit_value? then unit_value else ''
variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim()
variantUnitValue: (product, variant) ->
variantUnitValue: (variant) ->
if variant.unit_value?
if product.variant_unit_scale
variant_unit_value = @divideAsInteger variant.unit_value, product.variant_unit_scale
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

View File

@@ -21,8 +21,7 @@ module Admin
@importer = ProductImport::ProductImporter.new(File.new(@filepath), spree_current_user,
params[:settings])
@original_filename = params[:file].try(:original_filename)
@non_updatable_fields = ProductImport::EntryValidator.non_updatable_fields
@non_updatable_fields = ProductImport::EntryValidator.non_updatable_variant_fields
return if contains_errors? @importer
@ams_data = ams_data

View File

@@ -21,7 +21,7 @@ module Api
authorize! :create, Spree::Product
@product = Spree::Product.new(product_params)
if @product.save
if @product.save(context: :create_and_create_standard_variant)
render json: @product, serializer: Api::Admin::ProductSerializer, status: :created
else
invalid_resource!(@product)

View File

@@ -51,7 +51,7 @@ module OrderCompletion
def order_invalid!
Bugsnag.notify("Notice: invalid order loaded during checkout") do |payload|
payload.add_metadata :order, @order
payload.add_metadata :order, :order, @order
end
flash[:error] = t('checkout.order_not_loaded')

View File

@@ -21,7 +21,7 @@ module OrderStockCheck
return unless current_order_cycle&.closed?
Bugsnag.notify("Notice: order cycle closed during checkout completion") do |payload|
payload.add_metadata :order, current_order
payload.add_metadata :order, :order, current_order
end
current_order.empty!
current_order.set_order_cycle! nil

View File

@@ -7,7 +7,7 @@ class ErrorsController < ApplicationController
Bugsnag.notify("404") do |event|
event.severity = "info"
event.add_metadata(:request, request.env)
event.add_metadata(:request, :env, request.env)
end
render status: :not_found, formats: :html
end

View File

@@ -39,7 +39,7 @@ module Spree
def create
delete_stock_params_and_set_after do
@object.attributes = permitted_resource_params
if @object.save
if @object.save(context: :create_and_create_standard_variant)
flash[:success] = flash_message_for(@object, :successfully_created)
redirect_after_save
else
@@ -214,10 +214,10 @@ module Spree
def notify_bugsnag(error, product, variant)
Bugsnag.notify(error) do |report|
report.add_metadata(:product, product.attributes)
report.add_metadata(:product_error, product.errors.first) unless product.valid?
report.add_metadata(:variant, variant.attributes)
report.add_metadata(:variant_error, variant.errors.first) unless variant.valid?
report.add_metadata(:product,
{ product: product.attributes, variant: variant.attributes })
report.add_metadata(:product, :product_error, product.errors.first) unless product.valid?
report.add_metadata(:product, :variant_error, variant.errors.first) unless variant.valid?
end
end

View File

@@ -23,7 +23,7 @@ module Spree
def permitted_resource_params
params.require(:return_authorization).
permit(:amount, :reason, :stock_location_id)
permit(:amount, :reason)
end
end
end

View File

@@ -5,6 +5,8 @@ require 'open_food_network/scope_variants_for_search'
module Spree
module Admin
class VariantsController < ::Admin::ResourceController
helper ::Admin::ProductsHelper
belongs_to 'spree/product'
before_action :load_data, only: [:new, :edit]

View File

@@ -18,17 +18,15 @@ module Admin
end
def unit_value_with_description(variant)
precised_unit_value = nil
return variant.unit_description.to_s if variant.unit_value.nil?
if variant.unit_value
scaled_unit_value = variant.unit_value / (variant.product.variant_unit_scale || 1)
precised_unit_value = number_with_precision(
scaled_unit_value,
precision: nil,
strip_insignificant_zeros: true,
significant: false,
)
end
scaled_unit_value = variant.unit_value / (variant.variant_unit_scale || 1)
precised_unit_value = number_with_precision(
scaled_unit_value,
precision: nil,
strip_insignificant_zeros: true,
significant: false,
)
[precised_unit_value, variant.unit_description].compact_blank.join(" ")
end

View File

@@ -58,4 +58,9 @@ module ReportsHelper
.where(order_id: orders.map(&:id))
.pluck(:originator_id)
end
def datepicker_time(datetime)
datetime = Time.zone.parse(datetime) if datetime.is_a? String
datetime.strftime('%Y-%m-%d %H:%M')
end
end

View File

@@ -0,0 +1,98 @@
# frozen_string_literal: true
# When orders are cancelled, we need to amend
# an existing backorder as well.
# We're not dealing with line item changes just yet.
class AmendBackorderJob < ApplicationJob
sidekiq_options retry: 0
def perform(order)
OrderLocker.lock_order_and_variants(order) do
amend_backorder(order)
end
end
# The following is a mix of the BackorderJob and the CompleteBackorderJob.
# TODO: Move the common code into a re-usable service class.
def amend_backorder(order)
order_cycle = order.order_cycle
distributor = order.distributor
user = distributor.owner
items = backorderable_items(order)
return if items.empty?
# We are assuming that all variants are linked to the same wholesale
# shop and its catalog:
reference_link = items[0].variant.semantic_links[0].semantic_id
urls = FdcUrlBuilder.new(reference_link)
orderer = FdcBackorderer.new(user, urls)
backorder = orderer.find_open_order(order)
variants = order_cycle.variants_distributed_by(distributor)
adjust_quantities(order_cycle, user, backorder, urls, variants)
FdcBackorderer.new(user, urls).send_order(backorder)
end
# Check if we have enough stock to reduce the backorder.
#
# Our local stock can increase when users cancel their orders.
# But stock levels could also have been adjusted manually. So we review all
# quantities before finalising the order.
def adjust_quantities(order_cycle, user, order, urls, variants)
broker = FdcOfferBroker.new(user, urls)
order.lines.each do |line|
line.quantity = line.quantity.to_i
wholesale_product_id = line.offer.offeredItem.semanticId
transformation = broker.wholesale_to_retail(wholesale_product_id)
linked_variant = variants.linked_to(transformation.retail_product_id)
# Assumption: If a transformation is present then we only sell the retail
# variant. If that can't be found, it was deleted and we'll ignore that
# for now.
next if linked_variant.nil?
# Find all line items for this order cycle
# Update quantity accordingly
if linked_variant.on_demand
release_superfluous_stock(line, linked_variant, transformation)
else
aggregate_final_quantities(order_cycle, line, linked_variant, transformation)
end
end
# Clean up empty lines:
order.lines.reject! { |line| line.quantity.zero? }
end
# We look at all linked variants.
def backorderable_items(order)
order.line_items.select do |item|
# TODO: scope variants to hub.
# We are only supporting producer stock at the moment.
item.variant.semantic_links.present?
end
end
def release_superfluous_stock(line, linked_variant, transformation)
# Note that a division of integers dismisses the remainder, like `floor`:
wholesale_items_contained_in_stock = linked_variant.on_hand / transformation.factor
# But maybe we didn't actually order that much:
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
line.quantity -= deductable_quantity
retail_stock_changes = deductable_quantity * transformation.factor
linked_variant.on_hand -= retail_stock_changes
end
def aggregate_final_quantities(order_cycle, line, variant, transformation)
orders = order_cycle.orders.invoiceable
quantity = Spree::LineItem.where(order: orders, variant:).sum(:quantity)
wholesale_quantity = (quantity.to_f / transformation.factor).ceil
line.quantity = wholesale_quantity
end
end

View File

@@ -13,14 +13,14 @@ class BackorderJob < ApplicationJob
sidekiq_options retry: 0
def self.check_stock(order)
links = SemanticLink.where(variant_id: order.line_items.select(:variant_id))
links = SemanticLink.where(subject: order.variants)
perform_later(order) if links.exists?
rescue StandardError => e
# Errors here shouldn't affect the checkout. So let's report them
# separately:
Bugsnag.notify(e) do |payload|
payload.add_metadata(:order, order)
payload.add_metadata(:order, :order, order)
end
end
@@ -40,6 +40,8 @@ class BackorderJob < ApplicationJob
user = order.distributor.owner
items = backorderable_items(order)
return if items.empty?
# We are assuming that all variants are linked to the same wholesale
# shop and its catalog:
reference_link = items[0].variant.semantic_links[0].semantic_id
@@ -131,5 +133,7 @@ class BackorderJob < ApplicationJob
.perform_later(
user, order.distributor, order.order_cycle, placed_order.semanticId
)
order.exchange.semantic_links.create!(semantic_id: placed_order.semanticId)
end
end

View File

@@ -19,12 +19,18 @@ class CompleteBackorderJob < ApplicationJob
# someone else's order.
def perform(user, distributor, order_cycle, order_id)
order = FdcBackorderer.new(user, nil).find_order(order_id)
return if order&.lines.blank?
urls = FdcUrlBuilder.new(order.lines[0].offer.offeredItem.semanticId)
variants = order_cycle.variants_distributed_by(distributor)
adjust_quantities(order_cycle, user, order, urls, variants)
FdcBackorderer.new(user, urls).complete_order(order)
exchange = order_cycle.exchanges.outgoing.find_by(receiver: distributor)
exchange.semantic_links.find_by(semantic_id: order_id)&.destroy!
rescue StandardError
BackorderMailer.backorder_incomplete(user, distributor, order_cycle, order_id).deliver_later
@@ -45,6 +51,11 @@ class CompleteBackorderJob < ApplicationJob
transformation = broker.wholesale_to_retail(wholesale_product_id)
linked_variant = variants.linked_to(transformation.retail_product_id)
# Assumption: If a transformation is present then we only sell the retail
# variant. If that can't be found, it was deleted and we'll ignore that
# for now.
next if linked_variant.nil?
# Find all line items for this order cycle
# Update quantity accordingly
if linked_variant.on_demand

View File

@@ -17,7 +17,7 @@ class StockSyncJob < ApplicationJob
# Errors here shouldn't affect the shopping. So let's report them
# separately:
Bugsnag.notify(e) do |payload|
payload.add_metadata(:order, order)
payload.add_metadata(:order, :order, order)
end
end
@@ -30,13 +30,13 @@ class StockSyncJob < ApplicationJob
# Errors here shouldn't affect the shopping. So let's report them
# separately:
Bugsnag.notify(e) do |payload|
payload.add_metadata(:order, order)
payload.add_metadata(:order, :order, order)
end
end
def self.catalog_ids(order)
stock_controlled_variants = order.variants.reject(&:on_demand)
links = SemanticLink.where(variant_id: stock_controlled_variants.map(&:id))
links = SemanticLink.where(subject: stock_controlled_variants)
semantic_ids = links.pluck(:semantic_id)
semantic_ids.map do |product_id|
FdcUrlBuilder.new(product_id).catalog_url

View File

@@ -56,7 +56,7 @@ class SubscriptionConfirmJob < ApplicationJob
send_failed_payment_email(order)
else
Bugsnag.notify(e) do |payload|
payload.add_metadata :order, order
payload.add_metadata :order, :order, order
end
send_failed_payment_email(order, e.message)
end
@@ -109,8 +109,7 @@ class SubscriptionConfirmJob < ApplicationJob
SubscriptionMailer.failed_payment_email(order).deliver_now
rescue StandardError => e
Bugsnag.notify(e) do |payload|
payload.add_metadata :order, order
payload.add_metadata :error_message, error_message
payload.add_metadata :subscription_data, { order:, error_message: }
end
end
end

View File

@@ -5,11 +5,6 @@ class CustomTab < ApplicationRecord
validates :title, presence: true, length: { maximum: 20 }
# Remove any unsupported HTML.
def content
HtmlSanitizer.sanitize(super)
end
# Remove any unsupported HTML.
def content=(html)
super(HtmlSanitizer.sanitize(html))

View File

@@ -10,6 +10,8 @@
class Customer < ApplicationRecord
include SetUnusedAddressFields
self.ignored_columns += ['name']
acts_as_taggable
searchable_attributes :first_name, :last_name, :email, :code

View File

@@ -74,11 +74,6 @@ class EnterpriseGroup < ApplicationRecord
permalink
end
# Remove any unsupported HTML.
def long_description
HtmlSanitizer.sanitize_and_enforce_link_target_blank(super)
end
# Remove any unsupported HTML.
def long_description=(html)
super(HtmlSanitizer.sanitize_and_enforce_link_target_blank(html))

View File

@@ -22,6 +22,10 @@ class Exchange < ApplicationRecord
has_many :exchange_fees, dependent: :destroy
has_many :enterprise_fees, through: :exchange_fees
# Links to open backorders of a distributor (outgoing exchanges only)
# Don't allow removal of distributor from OC while we have an open backorder.
has_many :semantic_links, as: :subject, dependent: :restrict_with_error
validates :sender_id, uniqueness: { scope: [:order_cycle_id, :receiver_id, :incoming] }
before_destroy :delete_related_exchange_variants, prepend: true

View File

@@ -224,6 +224,9 @@ module ProductImport
# Ensure attributes are correctly copied to a new product's variant
variant = product.variants.first
variant.display_name = entry.display_name if entry.display_name
variant.variant_unit = entry.variant_unit if entry.variant_unit
variant.variant_unit_name = entry.variant_unit_name if entry.variant_unit_name
variant.variant_unit_scale = entry.variant_unit_scale if entry.variant_unit_scale
variant.import_date = @import_time
variant.supplier_id = entry.producer_id
variant.save

View File

@@ -6,8 +6,6 @@
module ProductImport
class EntryValidator
SKIP_VALIDATE_ON_UPDATE = [:description].freeze
# rubocop:disable Metrics/ParameterLists
def initialize(current_user, import_time, spreadsheet_data, editable_enterprises,
inventory_permissions, reset_counts, import_settings, all_entries)
@@ -22,9 +20,8 @@ module ProductImport
end
# rubocop:enable Metrics/ParameterLists
def self.non_updatable_fields
def self.non_updatable_variant_fields
{
description: :description,
unit_type: :variant_unit_scale,
variant_unit_name: :variant_unit_name,
}
@@ -67,8 +64,7 @@ module ProductImport
def mark_as_new_variant(entry, product_id)
variant_attributes = entry.assignable_attributes.except(
'id', 'product_id', 'on_hand', 'on_demand', 'variant_unit', 'variant_unit_name',
'variant_unit_scale'
'id', 'product_id', 'on_hand', 'on_demand'
)
# Variant needs a product. Product needs to be assigned first in order for
# delegate to work. name= will fail otherwise.
@@ -297,11 +293,11 @@ module ProductImport
end
products.flat_map(&:variants).each do |existing_variant|
unit_scale = existing_variant.product.variant_unit_scale
unit_scale = existing_variant.variant_unit_scale
unscaled_units = entry.unscaled_units.to_f || 0
entry.unit_value = unscaled_units * unit_scale unless unit_scale.nil?
if entry_matches_existing_variant?(entry, existing_variant)
if entry.match_inventory_variant?(existing_variant)
variant_override = create_inventory_item(entry, existing_variant)
return validate_inventory_item(entry, variant_override)
end
@@ -311,17 +307,6 @@ module ProductImport
error: I18n.t('admin.product_import.model.not_found'))
end
def entry_matches_existing_variant?(entry, existing_variant)
display_name_are_the_same?(entry, existing_variant) &&
existing_variant.unit_value == entry.unit_value.to_f
end
def display_name_are_the_same?(entry, existing_variant)
return true if entry.display_name.blank? && existing_variant.display_name.blank?
existing_variant.display_name == entry.display_name
end
def category_validation(entry)
category_name = entry.category
@@ -364,13 +349,13 @@ module ProductImport
return
end
products.each { |product| product_field_errors(entry, product) }
products.flat_map(&:variants).each do |existing_variant|
if entry_matches_existing_variant?(entry, existing_variant) &&
existing_variant.deleted_at.nil?
return mark_as_existing_variant(entry, existing_variant)
end
next unless entry.match_variant?(existing_variant) &&
existing_variant.deleted_at.nil?
variant_field_errors(entry, existing_variant)
return mark_as_existing_variant(entry, existing_variant)
end
mark_as_new_variant(entry, products.first.id)
@@ -392,8 +377,7 @@ module ProductImport
def mark_as_existing_variant(entry, existing_variant)
existing_variant.assign_attributes(
entry.assignable_attributes.except('id', 'product_id', 'variant_unit', 'variant_unit_name',
'variant_unit_scale')
entry.assignable_attributes.except('id', 'product_id')
)
check_on_hand_nil(entry, existing_variant)
@@ -406,11 +390,10 @@ module ProductImport
end
end
def product_field_errors(entry, existing_product)
EntryValidator.non_updatable_fields.each do |display_name, attribute|
next if attributes_match?(attribute, existing_product, entry) ||
attributes_blank?(attribute, existing_product, entry)
next if ignore_when_updating_product?(attribute)
def variant_field_errors(entry, existing_variant)
EntryValidator.non_updatable_variant_fields.each do |display_name, attribute|
next if attributes_match?(attribute, existing_variant, entry) ||
attributes_blank?(attribute, existing_variant, entry)
mark_as_invalid(entry, attribute: display_name,
error: I18n.t('admin.product_import.model.not_updatable'))
@@ -423,10 +406,6 @@ module ProductImport
existing_product_value == convert_to_trusted_type(entry_value, existing_product_value)
end
def ignore_when_updating_product?(attribute)
SKIP_VALIDATE_ON_UPDATE.include? attribute
end
def convert_to_trusted_type(untrusted_attribute, trusted_attribute)
case trusted_attribute
when Integer

View File

@@ -84,6 +84,14 @@ module ProductImport
invalid_attrs.except(* NON_PRODUCT_ATTRIBUTES, *NON_DISPLAY_ATTRIBUTES)
end
def match_variant?(variant)
match_display_name?(variant) && variant.unit_value.to_d == unscaled_units.to_d
end
def match_inventory_variant?(variant)
match_display_name?(variant) && variant.unit_value.to_d == unit_value.to_d
end
private
def remove_empty_skus(attrs)
@@ -99,5 +107,11 @@ module ProductImport
end
end
end
def match_display_name?(variant)
return true if display_name.blank? && variant.display_name.blank?
variant.display_name == display_name
end
end
end

View File

@@ -2,7 +2,9 @@
# Link a Spree::Variant to an external DFC SuppliedProduct.
class SemanticLink < ApplicationRecord
belongs_to :variant, class_name: "Spree::Variant"
self.ignored_columns += [:variant_id]
belongs_to :subject, polymorphic: true
validates :semantic_id, presence: true
end

View File

@@ -244,7 +244,7 @@ module Spree
can [:admin, :index, :show, :create], ::Admin::ReportsController
can [:admin, :show, :create, :customers, :orders_and_distributors, :group_buys, :payments,
:orders_and_fulfillment, :products_and_inventory, :order_cycle_management,
:packing, :enterprise_fee_summary, :bulk_coop], :report
:packing, :enterprise_fee_summary, :bulk_coop, :suppliers], :report
end
def add_order_cycle_management_abilities(user)

View File

@@ -45,7 +45,8 @@ module Spree
after_destroy :update_order
after_save :update_order
delegate :product, :variant_unit, :unit_description, :display_name, :display_as, to: :variant
delegate :product, :variant_unit, :unit_description, :display_name, :display_as,
:variant_unit_scale, :variant_unit_name, to: :variant
# Allows manual skipping of Stock::AvailabilityValidator
attr_accessor :skip_stock_check, :target_shipment

View File

@@ -67,8 +67,12 @@ module Spree
class_name: 'Spree::Adjustment',
dependent: :destroy
has_many :invoices, dependent: :restrict_with_exception
belongs_to :order_cycle, optional: true
has_one :exchange, ->(order) {
outgoing.to_enterprise(order.distributor)
}, through: :order_cycle, source: :exchanges
has_many :semantic_links, through: :exchange
belongs_to :distributor, class_name: 'Enterprise', optional: true
belongs_to :customer, optional: true
has_one :proxy_order, dependent: :destroy

View File

@@ -142,6 +142,8 @@ module Spree
OrderMailer.cancel_email(id).deliver_later if send_cancellation_email
update(payment_state: updater.update_payment_state)
AmendBackorderJob.perform_later(self)
end
def after_resume

View File

@@ -155,7 +155,6 @@ module Spree
if adjustment
adjustment.originator = payment_method
adjustment.label = adjustment_label
adjustment.amount = payment_method.compute_amount(self)
adjustment.save
elsif !processing_refund? && payment_method.present?
payment_method.create_adjustment(adjustment_label, self, true)

View File

@@ -38,6 +38,7 @@ module Spree
# strips all non-price-like characters from the price, taking into account locale settings
def parse_price(price)
return nil if price.blank?
return price unless price.is_a?(String)
separator, _delimiter = I18n.t([:'number.currency.format.separator',

View File

@@ -22,7 +22,12 @@ module Spree
include LogDestroyPerformer
self.belongs_to_required_by_default = false
self.ignored_columns += [:supplier_id]
# These columns have been moved to variant. Currently this is only for documentation purposes,
# because they are declared as attr_accessor below, declaring them as ignored columns has no
# effect
self.ignored_columns += [
:supplier_id, :primary_taxon_id, :variant_unit, :variant_unit_scale, :variant_unit_name
]
acts_as_paranoid
@@ -45,20 +50,30 @@ module Spree
validates_lengths_from_database
validates :name, presence: true
validates :variant_unit, presence: true
validates :unit_value, numericality: {
greater_than: 0,
if: ->(p) { p.variant_unit.in?(%w(weight volume)) && new_record? }
}
validates :variant_unit_scale,
presence: { if: ->(p) { %w(weight volume).include? p.variant_unit } }
validates :variant_unit_name,
presence: { if: ->(p) { p.variant_unit == 'items' } }
validate :validate_image
validates :price, numericality: { greater_than_or_equal_to: 0, if: ->{ new_record? } }
accepts_nested_attributes_for :variants, allow_destroy: true
# These validators are used to make sure the standard variant created via
# `ensure_standard_variant` will be valid. The are only used when creating a new product
with_options on: :create_and_create_standard_variant do
validates :supplier_id, presence: true
validates :primary_taxon_id, presence: true
validates :variant_unit, presence: true
validates :unit_value, presence: true, if: ->(product) {
%w(weight volume).include?(product.variant_unit)
}
validates :unit_value, numericality: { greater_than: 0 }, allow_blank: true
validates :unit_description, presence: true, if: ->(product) {
product.variant_unit.present? && product.unit_value.nil?
}
validates :variant_unit_scale, presence: true, if: ->(product) {
%w(weight volume).include?(product.variant_unit)
}
validates :variant_unit_name, presence: true, if: ->(product) {
product.variant_unit == 'items'
}
end
accepts_nested_attributes_for :image
accepts_nested_attributes_for :product_properties,
allow_destroy: true,
@@ -66,14 +81,12 @@ module Spree
# Transient attributes used temporarily when creating a new product,
# these values are persisted on the product's variant
attr_accessor :price, :display_as, :unit_value, :unit_description, :tax_category_id,
:shipping_category_id, :primary_taxon_id, :supplier_id
attr_accessor :price, :display_as, :unit_value, :unit_description, :variant_unit,
:variant_unit_name, :variant_unit_scale, :tax_category_id, :shipping_category_id,
:primary_taxon_id, :supplier_id
after_validation :validate_variant_attrs, on: :create
after_create :ensure_standard_variant
after_update :touch_supplier, if: :saved_change_to_primary_taxon_id?
around_destroy :destruction
after_save :update_units
after_touch :touch_supplier
# -- Scopes
@@ -198,10 +211,6 @@ module Spree
end
end
def total_on_hand
stock_items.sum(&:count_on_hand)
end
def properties_including_inherited
# Product properties override producer properties
ps = product_properties.all
@@ -245,6 +254,7 @@ module Spree
end
end
# rubocop:disable Metrics/AbcSize
def ensure_standard_variant
return unless variants.empty?
@@ -254,36 +264,16 @@ module Spree
variant.display_as = display_as
variant.unit_value = unit_value
variant.unit_description = unit_description
variant.variant_unit = variant_unit
variant.variant_unit_name = variant_unit_name
variant.variant_unit_scale = variant_unit_scale
variant.tax_category_id = tax_category_id
variant.shipping_category_id = shipping_category_id
variant.primary_taxon_id = primary_taxon_id
variant.supplier_id = supplier_id
variants << variant
end
# Format as per WeightsAndMeasures (todo: re-orgnaise maybe after product/variant refactor)
def variant_unit_with_scale
# Our code is based upon English based number formatting with a period `.`
scale_clean = ActiveSupport::NumberHelper.number_to_rounded(variant_unit_scale,
precision: nil,
significant: false,
strip_insignificant_zeros: true,
locale: :en)
[variant_unit, scale_clean].compact_blank.join("_")
end
def variant_unit_with_scale=(variant_unit_with_scale)
values = variant_unit_with_scale.split("_")
assign_attributes(
variant_unit: values[0],
variant_unit_scale: values[1] || nil
)
end
# Remove any unsupported HTML.
def description
HtmlSanitizer.sanitize(super)
end
# rubocop:enable Metrics/AbcSize
# Remove any unsupported HTML.
def description=(html)
@@ -292,27 +282,6 @@ module Spree
private
def validate_variant_attrs
# Avoid running validation when we can't set variant attrs
# eg clone product. Will raise error if clonning a product with no variant
return if variants.first&.valid?
errors.add(:primary_taxon_id, :blank) unless Spree::Taxon.find_by(id: primary_taxon_id)
errors.add(:supplier_id, :blank) unless Enterprise.find_by(id: supplier_id)
end
def update_units
return unless saved_change_to_variant_unit? || saved_change_to_variant_unit_name?
variants.each do |v|
if v.persisted?
v.update_units
else
v.assign_units
end
end
end
def touch_supplier
return if variants.empty?

View File

@@ -2,12 +2,12 @@
module Spree
class ReturnAuthorization < ApplicationRecord
self.ignored_columns += [:stock_location_id]
acts_as_paranoid
belongs_to :order, class_name: 'Spree::Order', inverse_of: :return_authorizations
has_many :inventory_units, inverse_of: :return_authorization, dependent: :nullify
has_one :stock_location, dependent: nil
before_save :force_positive_amount
before_create :generate_number

View File

@@ -105,6 +105,15 @@ module Spree
if default_zone_or_zone_match?(item.order)
calculator.compute(item)
else
# Tax refund should not be possible with the way our production server are configured
Bugsnag.notify(
"Notice: Tax refund should not be possible, please check the default zone and " \
"the tax rate zone configuration"
) do |payload|
payload.add_metadata :order_tax_zone, item.order.tax_zone
payload.add_metadata :tax_rate_zone, zone
payload.add_metadata :default_zone, Zone.default_tax
end
# In this case, it's a refund.
calculator.compute(item) * - 1
end

View File

@@ -60,7 +60,7 @@ module Spree
has_many :exchanges, through: :exchange_variants
has_many :variant_overrides, dependent: :destroy
has_many :inventory_items, dependent: :destroy
has_many :semantic_links, dependent: :delete_all
has_many :semantic_links, as: :subject, dependent: :delete_all
has_many :supplier_properties, through: :supplier, source: :properties
localize_number :price, :weight
@@ -71,21 +71,25 @@ module Spree
validates :tax_category, presence: true,
if: proc { Spree::Config.products_require_tax_category }
validates :variant_unit, presence: true
validates :unit_value, presence: true, if: ->(variant) {
%w(weight volume).include?(variant.product&.variant_unit)
%w(weight volume).include?(variant.variant_unit)
}
validates :unit_value, numericality: { greater_than: 0 }, allow_blank: true
validates :price, numericality: { greater_than_or_equal_to: 0 }
validates :unit_description, presence: true, if: ->(variant) {
variant.product&.variant_unit.present? && variant.unit_value.nil?
variant.variant_unit.present? && variant.unit_value.nil?
}
validates :variant_unit_scale, presence: true, if: ->(variant) {
%w(weight volume).include?(variant.variant_unit)
}
validates :variant_unit_name, presence: true, if: ->(variant) {
variant.variant_unit == 'items'
}
before_validation :set_cost_currency
before_validation :ensure_shipping_category
before_validation :ensure_unit_value
before_validation :update_weight_from_unit_value, if: ->(v) { v.product.present? }
before_validation :update_weight_from_unit_value
before_validation :convert_variant_weight_to_decimal
before_save :assign_units, if: ->(variant) {
@@ -95,6 +99,9 @@ module Spree
after_create :create_stock_items
around_destroy :destruction
after_save :save_default_price
after_save :update_units, if: -> {
saved_change_to_variant_unit? || saved_change_to_variant_unit_name?
}
# default variant scope only lists non-deleted variants
scope :deleted, -> { where.not(deleted_at: nil) }
@@ -219,6 +226,25 @@ module Spree
Spree::Stock::Quantifier.new(self).total_on_hand
end
# Format as per WeightsAndMeasures
def variant_unit_with_scale
# Our code is based upon English based number formatting with a period `.`
scale_clean = ActiveSupport::NumberHelper.number_to_rounded(variant_unit_scale,
precision: nil,
significant: false,
strip_insignificant_zeros: true,
locale: :en)
[variant_unit, scale_clean].compact_blank.join("_")
end
def variant_unit_with_scale=(variant_unit_with_scale)
values = variant_unit_with_scale.split("_")
assign_attributes(
variant_unit: values[0],
variant_unit_scale: values[1] || nil
)
end
private
def check_currency
@@ -248,7 +274,7 @@ module Spree
end
def update_weight_from_unit_value
return unless product.variant_unit == 'weight' && unit_value.present?
return unless variant_unit == 'weight' && unit_value.present?
self.weight = weight_from_unit_value
end
@@ -268,7 +294,7 @@ module Spree
def ensure_unit_value
Bugsnag.notify("Trying to set unit_value to NaN") if unit_value&.nan?
return unless (product&.variant_unit == "items" && unit_value.nil?) || unit_value&.nan?
return unless (variant_unit == "items" && unit_value.nil?) || unit_value&.nan?
self.unit_value = 1.0
end

View File

@@ -3,8 +3,7 @@
module Api
module Admin
class ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :sku, :variant_unit, :variant_unit_scale, :variant_unit_name,
:inherits_properties, :on_hand, :price, :import_date, :image_url,
attributes :id, :name, :sku, :inherits_properties, :on_hand, :price, :import_date, :image_url,
:thumb_url, :variants
def variants

View File

@@ -3,7 +3,7 @@
module Api
module Admin
class UnitsProductSerializer < ActiveModel::Serializer
attributes :id, :name, :group_buy_unit_size, :variant_unit, :variant_unit_scale
attributes :id, :name, :group_buy_unit_size
end
end
end

View File

@@ -3,7 +3,7 @@
module Api
module Admin
class UnitsVariantSerializer < ActiveModel::Serializer
attributes :id, :full_name, :unit_value
attributes :id, :full_name, :unit_value, :variant_unit, :variant_unit_scale
def full_name
full_name = object.full_name

View File

@@ -6,7 +6,8 @@ module Api
attributes :id, :name, :producer_name, :image, :sku, :import_date, :tax_category_id,
:options_text, :unit_value, :unit_description, :unit_to_display,
:display_as, :display_name, :name_to_display, :variant_overrides_count,
:price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name
:price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name,
:variant_unit, :variant_unit_scale, :variant_unit_name, :variant_unit_with_scale
has_one :primary_taxon, key: :category_id, embed: :id
has_one :supplier, key: :producer_id, embed: :id

View File

@@ -10,7 +10,7 @@ class FdcBackorderer
end
def find_or_build_order(ofn_order)
find_open_order || build_new_order(ofn_order)
find_open_order(ofn_order) || build_new_order(ofn_order)
end
def build_new_order(ofn_order)
@@ -19,7 +19,37 @@ class FdcBackorderer
end
end
def find_open_order
# Try the new method and fall back to old method.
def find_open_order(ofn_order)
lookup_open_order(ofn_order) || find_last_open_order
end
def lookup_open_order(ofn_order)
# There should be only one link at the moment but we may support
# ordering from multiple suppliers one day.
semantic_ids = ofn_order.semantic_links.pluck(:semantic_id)
semantic_ids.lazy
# Make sure we select an order from the right supplier:
.select { |id| id.starts_with?(urls.orders_url) }
# Fetch the order from the remote DFC server, lazily:
.map { |id| find_order(id) }
.compact
# Just in case someone completed the order without updating our database:
.select { |o| o.orderStatus[:path] == "Held" }
.first
# The DFC Connector doesn't recognise status values properly yet.
# So we are overriding the value with something that can be exported.
&.tap { |o| o.orderStatus = "dfc-v:Held" }
end
# DEPRECATED
#
# We now store links to orders we placed. So we don't need to search
# through all orders and pick a random open one.
# But for compatibility with currently open order cycles that don't have
# a stored link yet, we keep this method as well.
def find_last_open_order
graph = import(urls.orders_url)
open_orders = graph&.select do |o|
o.semanticType == "dfc-b:Order" && o.orderStatus[:path] == "Held"

View File

@@ -4,11 +4,10 @@ module PermittedAttributes
class Variant
def self.attributes
[
:id, :sku, :on_hand, :on_demand, :shipping_category_id,
:price, :unit_value, :unit_description,
:display_name, :display_as, :tax_category_id,
:weight, :height, :width, :depth, :taxon_ids, :primary_taxon_id,
:supplier_id
:id, :sku, :on_hand, :on_demand, :shipping_category_id, :price, :unit_value,
:unit_description, :variant_unit, :variant_unit_name, :variant_unit_scale, :display_name,
:display_as, :tax_category_id, :weight, :height, :width, :depth, :taxon_ids,
:primary_taxon_id, :supplier_id
]
end
end

View File

@@ -25,7 +25,7 @@ class PlaceProxyOrder
rescue StandardError => e
summarizer.record_and_log_error(:processing, order, e.message)
Bugsnag.notify(e) do |payload|
payload.add_metadata :order, order
payload.add_metadata :order, :order, order
end
end
@@ -57,8 +57,7 @@ class PlaceProxyOrder
true
rescue StandardError => e
Bugsnag.notify(e) do |payload|
payload.add_metadata :subscription, subscription
payload.add_metadata :proxy_order, proxy_order
payload.add_metadata(:proxy_order, { subscription:, proxy_order: })
end
false
end

View File

@@ -146,11 +146,11 @@ module Sets
def notify_bugsnag(error, product, variant, variant_attributes)
Bugsnag.notify(error) do |report|
report.add_metadata(:product, product.attributes)
report.add_metadata(:product_error, product.errors.first) unless product.valid?
report.add_metadata(:variant_attributes, variant_attributes)
report.add_metadata(:variant, variant.attributes)
report.add_metadata(:variant_error, variant.errors.first) unless variant.valid?
report.add_metadata( :product_set,
{ product: product.attributes, variant_attributes:,
variant: variant.attributes } )
report.add_metadata(:product_set, :product_error, product.errors.first) if !product.valid?
report.add_metadata(:product_set, :variant_error, variant.errors.first) if !variant.valid?
end
end
end

View File

@@ -3,12 +3,11 @@
class UnitPrice
def initialize(variant)
@variant = variant
@product = variant.product
end
def denominator
# catches any case where unit is not kg, lb, or L.
return @variant.unit_value if @product&.variant_unit == "items"
return @variant.unit_value if @variant.variant_unit == "items"
case unit
when "lb"
@@ -23,13 +22,13 @@ class UnitPrice
def unit
return "lb" if WeightsAndMeasures.new(@variant).system == "imperial"
case @product&.variant_unit
case @variant.variant_unit
when "weight"
"kg"
when "volume"
"L"
else
@product.variant_unit_name.presence || I18n.t("item")
@variant.variant_unit_name.presence || I18n.t("item")
end
end
end

View File

@@ -32,16 +32,18 @@ module VariantUnits
private
def value_scaled?
@nameable.product.variant_unit_scale.present?
@nameable.variant_unit_scale.present?
end
def option_value_value_unit
if @nameable.unit_value.present? && @nameable.product&.persisted?
if %w(weight volume).include? @nameable.product.variant_unit
if @nameable.unit_value.present?
if %w(weight volume).include? @nameable.variant_unit
value, unit_name = option_value_value_unit_scaled
else
value = @nameable.unit_value
unit_name = pluralize(@nameable.product.variant_unit_name, value)
unit_name = @nameable.variant_unit_name
unit_name = pluralize(unit_name, value) if unit_name.present?
end
value = value.to_i if value == value.to_i

View File

@@ -64,12 +64,12 @@ module VariantUnits
def unit_value_attributes
units = { unit_presentation: option_value_name }
units.merge!(variant_unit: product.variant_unit) if has_attribute?(:variant_unit)
units.merge!(variant_unit:) if has_attribute?(:variant_unit)
units
end
def weight_from_unit_value
(unit_value || 0) / 1000 if product.variant_unit == 'weight'
(unit_value || 0) / 1000 if variant_unit == 'weight'
end
private

View File

@@ -16,10 +16,10 @@ class WeightsAndMeasures
def system
return "custom" unless scales = scales_for_variant_unit(ignore_available_units: true)
product_scale = @variant.product.variant_unit_scale&.to_f
return "custom" unless product_scale.present? && product_scale.positive?
variant_scale = @variant.variant_unit_scale&.to_f
return "custom" unless variant_scale.present? && variant_scale.positive?
scales[product_scale]['system']
scales[variant_scale]['system']
end
# @returns enumerable with label and value for select
@@ -92,9 +92,9 @@ class WeightsAndMeasures
}.freeze
def scales_for_variant_unit(ignore_available_units: false)
return @units[@variant.product.variant_unit] if ignore_available_units
return @units[@variant.variant_unit] if ignore_available_units
@units[@variant.product.variant_unit]&.reject { |_scale, unit_info|
@units[@variant.variant_unit]&.reject { |_scale, unit_info|
self.class.available_units.exclude?(unit_info['name'])
}
end

View File

@@ -7,18 +7,8 @@
%td.col-sku.field.naked_inputs
= f.text_field :sku, 'aria-label': t('admin.products_page.columns.sku')
= error_message_on product, :sku
%td.col-unit_scale.field.naked_inputs{ 'data-controller': 'toggle-control', 'data-toggle-control-match-value': 'items' }
= f.hidden_field :variant_unit
= f.hidden_field :variant_unit_scale
= f.select :variant_unit_with_scale,
options_for_select(WeightsAndMeasures.variant_unit_options, product.variant_unit_with_scale),
{},
class: "fullwidth no-input",
'aria-label': t('admin.products_page.columns.unit_scale'),
data: { "controller": "tom-select", "tom-select-options-value": '{ "plugins": [] }', action: "change->toggle-control#displayIfMatch"}
.field
= f.text_field :variant_unit_name, 'aria-label': t('items'), 'data-toggle-control-target': 'control', style: (product.variant_unit == "items" ? "" : "display: none")
= error_message_on product, :variant_unit_name, 'data-toggle-control-target': 'control'
%td.col-unit_scale.align-right
-# empty
%td.col-unit.align-right
-# empty
%td.col-price.align-right

View File

@@ -47,7 +47,7 @@
.form-buttons
%a.button.reset.medium{ href: admin_products_path(page: @page, per_page: @per_page, search_term: @search_term, producer_id: @producer_id, category_id: @category_id), 'data-turbo': "false" }
= t('.reset')
= form.submit t('.save'), class: "medium"
= form.submit t('.save'), { class: "medium", data: { action: "click->bulk-form#popoutEmptyVariantUnit" }}
%tr
%th.col-image.align-left= # image
= render partial: 'spree/admin/shared/stimulus_sortable_header',

View File

@@ -7,8 +7,17 @@
%td.col-sku.field.naked_inputs
= f.text_field :sku, 'aria-label': t('admin.products_page.columns.sku')
= error_message_on variant, :sku
%td.col-unit_scale
-# empty
%td.col-unir_scale.field.naked_inputs{ 'data-controller': 'toggle-control', 'data-toggle-control-match-value': 'items' }
= f.hidden_field :variant_unit
= f.hidden_field :variant_unit_scale
= f.select :variant_unit_with_scale,
options_for_select(WeightsAndMeasures.variant_unit_options, variant.variant_unit_with_scale),
{ include_blank: true },
{ class: "fullwidth no-input", 'aria-label': t('admin.products_page.columns.unit_scale'), data: { "controller": "tom-select", "tom-select-options-value": '{ "plugins": [] }', action: "change->toggle-control#displayIfMatch" }, required: true }
= error_message_on variant, :variant_unit, 'data-toggle-control-target': 'control'
.field
= f.text_field :variant_unit_name, 'aria-label': t('items'), 'data-toggle-control-target': 'control', style: (variant.variant_unit == "items" ? "" : "display: none")
= error_message_on variant, :variant_unit_name, 'data-toggle-control-target': 'control'
%td.col-unit.field.popout{'data-controller': "popout", 'data-popout-update-display-value': "false"}
= f.button :unit_to_display, class: "popout__button", 'aria-label': t('admin.products_page.columns.unit'), 'data-popout-target': "button" do
= variant.unit_to_display # Show the generated summary of unit values
@@ -18,7 +27,7 @@
= f.hidden_field :unit_value
= f.hidden_field :unit_description
= f.text_field :unit_value_with_description,
value: unit_value_with_description(variant), 'aria-label': t('admin.products_page.columns.unit_value')
value: unit_value_with_description(variant), 'aria-label': t('admin.products_page.columns.unit_value'), required: true
.field
= f.label :display_as, t('admin.products_page.columns.display_as')
= f.text_field :display_as, placeholder: VariantUnits::OptionValueNamer.new(variant).name

View File

@@ -11,7 +11,7 @@
= render partial: 'spree/admin/shared/product_sub_menu'
#products_v3_page{ "data-controller": "products", 'data-turbo': true }
#products_v3_page{ 'data-turbo': true }
= render partial: "content", locals: { products: @products, pagy: @pagy, search_term: @search_term,
producer_options: producers, producer_id: @producer_id,
category_options: categories, category_id: @category_id,

View File

@@ -1,14 +1,17 @@
-# Field used for ransack search. This date range is mostly used for Spree::Order
-# so default field is 'completed_at'
- field ||= 'completed_at'
- start_date ||= params[:q].try(:[], :completed_at_gt)
- end_date ||= params[:q].try(:[], :completed_at_lt)
- start_field = "#{field}_gt"
- end_field = "#{field}_lt"
- query = params[:q].to_h
- start_date = datepicker_time(query[start_field].presence || 3.months.ago.beginning_of_day)
- end_date = datepicker_time(query[end_field].presence || Time.zone.tomorrow.beginning_of_day)
.row.date-range-filter
.alpha.two.columns= label_tag nil, t(:date_range)
.omega.fourteen.columns
.field-block.omega.four.columns
.date-range-fields{ data: { controller: "flatpickr", "flatpickr-mode-value": "range", "flatpickr-enable-time-value": true , "flatpickr-default-hour": 0 } }
.date-range-fields{ data: { controller: "flatpickr", "flatpickr-mode-value": "range", "flatpickr-enable-time-value": true , "flatpickr-default-hour": 0, "flatpickr-default-date": [start_date, end_date] } }
= text_field_tag nil, nil, class: "datepicker fullwidth", data: { "flatpickr-target": "instance", action: "flatpickr_clear@window->flatpickr#clear" }
= text_field_tag "q[#{field}_gt]", nil, data: { "flatpickr-target": "start" }, style: "display: none", value: start_date
= text_field_tag "q[#{field}_lt]", nil, data: { "flatpickr-target": "end" }, style: "display: none", value: end_date
= text_field_tag "q[#{start_field}]", nil, data: { "flatpickr-target": "start" }, style: "display: none", value: start_date
= text_field_tag "q[#{end_field}]", nil, data: { "flatpickr-target": "end" }, style: "display: none", value: end_date

View File

@@ -1,5 +1,5 @@
= render partial: 'admin/reports/date_range_form',
locals: { f: f, field: 'order_completed_at', start_date: params[:q].try(:[], :order_completed_at_gt), end_date: params[:q].try(:[], :order_completed_at_lt) }
locals: { f: f, field: 'order_completed_at' }
.row
.alpha.two.columns= label_tag nil, t(:report_hubs)
@@ -14,4 +14,4 @@
.row
.alpha.two.columns= label_tag nil, t(:report_customers_cycle)
.omega.fourteen.columns
= select_tag("q[order_cycle_id_in]", options_for_select(report_order_cycle_options(@data.order_cycles), params.dig(:q, :order_cycle_id_in)), {class: "select2 fullwidth", multiple: true})
= select_tag("q[order_cycle_id_in]", options_for_select(report_order_cycle_options(@data.order_cycles), params.dig(:q, :order_cycle_id_in)), {class: "select2 fullwidth", multiple: true})

View File

@@ -0,0 +1,14 @@
= render 'admin/reports/date_range_form', f: f
.row
.alpha.two.columns= label_tag nil, t(:report_hubs)
.omega.fourteen.columns= f.collection_select(:distributor_id_in, @data.orders_distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true})
.row
.alpha.two.columns= label_tag nil, t(:report_producers)
.omega.fourteen.columns= select_tag(:supplier_id_in, options_from_collection_for_select(@data.orders_suppliers, :id, :name, params[:supplier_id_in]), {class: "select2 fullwidth", multiple: true})
.row
.alpha.two.columns= label_tag nil, t(:report_customers_cycle)
.omega.fourteen.columns
= f.select(:order_cycle_id_in, report_order_cycle_options(@data.order_cycles), {selected: params.dig(:q, :order_cycle_id_in)}, {class: "select2 fullwidth", multiple: true})

View File

@@ -80,15 +80,15 @@
.three.columns
.text-center
= t("admin.orders.bulk_management.group_buy_unit_size")
.text-center {{ getGroupBySizeFormattedValueWithUnitName(selectedUnitsProduct.group_buy_unit_size , selectedUnitsProduct, selectedUnitsVariant ) }}
.text-center {{ getGroupBySizeFormattedValueWithUnitName(selectedUnitsProduct.group_buy_unit_size , selectedUnitsVariant ) }}
.three.columns
.text-center
= t("admin.orders.bulk_management.total_qtt_ordered")
.text-center {{ formattedValueWithUnitName( sumUnitValues(), selectedUnitsProduct, selectedUnitsVariant ) }}
.text-center {{ formattedValueWithUnitName( sumUnitValues(), selectedUnitsVariant ) }}
.three.columns
.text-center
= t("admin.orders.bulk_management.max_qtt_ordered")
.text-center {{ formattedValueWithUnitName( sumMaxUnitValues(), selectedUnitsProduct, selectedUnitsVariant ) }}
.text-center {{ formattedValueWithUnitName( sumMaxUnitValues(), selectedUnitsVariant ) }}
.three.columns
.text-center
= t("admin.orders.bulk_management.current_fulfilled_units")

View File

@@ -1,5 +1,5 @@
%div.admin-product-form-fields
.left.twelve.columns.alpha
.left.sixteen.columns.alpha
= f.field_container :name do
= f.label :name, raw(t(:name) + content_tag(:span, ' *', :class => 'required'))
= f.text_field :name, :class => 'fullwidth title'
@@ -10,25 +10,6 @@
= f.hidden_field :description, id: "product_description", value: @product.description
%trix-editor{ input: "product_description", "data-controller": "trixeditor" }
.right.four.columns.omega
.variant_units_form{ 'ng-app' => 'admin.products', 'ng-controller' => 'editUnitsCtrl' }
= f.field_container :units do
= f.label :variant_unit_with_scale, t(:spree_admin_variant_unit_scale)
%select.select2.fullwidth{ id: 'product_variant_unit_with_scale', 'ng-model' => 'variant_unit_with_scale', 'ng-change' => 'setFields()', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' }
%option{'value' => ''}
= f.text_field :variant_unit, {'id' => 'variant_unit', 'ng-value' => 'product.variant_unit', 'hidden' => true}
= f.text_field :variant_unit_scale, {'id' => 'variant_unit_scale', 'ng-value' => 'product.variant_unit_scale', 'hidden' => true}
.variant_unit_name{'ng-show' => 'product.variant_unit == "items"'}
= f.field_container :variant_unit_name do
= f.label :variant_unit_name, t(:spree_admin_variant_unit_name)
= f.text_field :variant_unit_name, {placeholder: t('admin.products.unit_name_placeholder')}
= f.error_message_on :variant_unit_name
.clear
.clear
%div

View File

@@ -11,9 +11,6 @@
%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' }
%select.no-search{ "data-controller": "tom-select", 'ng-model' => 'product.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-product' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' }
%input{ 'ng-model' => 'product.master.unit_value_with_description', :name => 'master_unit_value_with_description', 'ofn-track-master' => 'unit_value_with_description', :type => 'text', :placeholder => 'value', 'ng-show' => "!hasVariants(product) && hasUnit(product)", 'ofn-maintain-unit-scale' => true }
%input{ 'ng-model' => 'product.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-product' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "product.variant_unit_with_scale == 'items'", :type => 'text' }
%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)' }

View File

@@ -10,7 +10,9 @@
%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' }
%input{ 'ng-model' => 'variant.unit_value_with_description', :name => 'variant_unit_value_with_description', 'ofn-track-variant' => 'unit_value_with_description', :type => 'text', 'ofn-maintain-unit-scale' => true }
%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' }

View File

@@ -48,9 +48,9 @@
= f.field_container :unit_value do
= f.label :unit_value, t(".value"), 'ng-disabled' => "!hasUnit(product)"
%span.required *
= f.text_field :unit_value, placeholder: "eg. 2", 'ng-model' => 'product.master.unit_value_with_description', class: 'fullwidth', 'ng-disabled' => "!hasUnit(product)"
%input{ type: 'hidden', 'ng-value': 'product.master.unit_value', "ng-init": "product.master.unit_value='#{@product.unit_value}'", name: 'product[unit_value]' }
%input{ type: 'hidden', 'ng-value': 'product.master.unit_description', "ng-init": "product.master.unit_description='#{@product.unit_description}'", name: 'product[unit_description]' }
= f.text_field :unit_value, placeholder: "eg. 2", 'ng-model' => 'product.unit_value_with_description', class: 'fullwidth', 'ng-disabled' => "!hasUnit(product)"
%input{ type: 'hidden', 'ng-value': 'product.unit_value', "ng-init": "product.unit_value='#{@product.unit_value}'", name: 'product[unit_value]' }
%input{ type: 'hidden', 'ng-value': 'product.unit_description', "ng-init": "product.unit_description='#{@product.unit_description}'", name: 'product[unit_description]' }
= f.error_message_on :unit_value
= render 'display_as', f: f
.six.columns.omega{ 'ng-show' => "product.variant_unit_with_scale == 'items'" }

View File

@@ -41,8 +41,3 @@
= f.label :reason, t('.reason')
= f.text_area :reason, { style: 'height:100px;', class: 'fullwidth' }
= f.error_message_on :reason
= f.field_container :stock_location do
= f.label :stock_location, t('.stock_location')
= f.select :stock_location_id, Spree::StockLocation.all.collect{ |l| [l.name, l.id] }, { style: 'height:100px;', class: 'fullwidth' }
= f.error_message_on :reason

View File

@@ -1,83 +1,112 @@
.label-block.left.six.columns.alpha{'ng-app' => 'admin.products', 'ng-controller' => 'variantUnitsCtrl'}
.field
= f.label :display_name, t('.display_name')
= f.text_field :display_name, class: "fullwidth", placeholder: t('.display_name_placeholder')
.field
= f.label :display_as, t('.display_as')
= f.text_field :display_as, class: "fullwidth", placeholder: t('.display_as_placeholder')
%div{'data-controller': "edit-variant", id: "edit_variant"}
.label-block.left.six.columns.alpha
%script= render partial: "admin/shared/global_var_ofn", formats: :js,
locals: { name: :available_units_sorted, value: WeightsAndMeasures.available_units_sorted }
- if @product.variant_unit != 'items'
.field
= label_tag :unit_value_human, "#{t('admin.'+@product.variant_unit)} ({{unitName(#{@product.variant_unit_scale}, '#{@product.variant_unit}')}})"
= hidden_field_tag 'product_variant_unit_scale', @product.variant_unit_scale
= number_field_tag :unit_value_human, nil, {class: "fullwidth", step: 0.01, 'ng-model' => 'unit_value_human', 'ng-change' => 'updateValue()'}
= f.number_field :unit_value, {hidden: true, 'ng-value' => 'unit_value'}
%script= render partial: "admin/shared/global_var_ofn", formats: :js,
locals: { name: :currency_config, value: Api::CurrencyConfigSerializer.new({}) }
.field
= f.label :unit_description, t(:spree_admin_unit_description)
= f.text_field :unit_description, class: "fullwidth", placeholder: t('admin.products.unit_name_placeholder')
.field
= f.label :display_name, t('.display_name')
= f.text_field :display_name, class: "fullwidth", placeholder: t('.display_name_placeholder')
%div
.field
= f.label :sku, t('.sku')
= f.text_field :sku, class: 'fullwidth'
.field
= f.label :price, t('.price')
= f.text_field :price, class: 'fullwidth', "ng-model" => "variant.price", "ng-init" => "variant.price = '#{number_to_currency(@variant.price, unit: '')&.strip}'"
.field
= hidden_field_tag 'product_variant_unit', @product.variant_unit
= hidden_field_tag 'product_variant_unit_name', @product.variant_unit_name
= f.field_container :unit_price do
%div{style: "display: flex"}
= f.label :unit_price, t(".unit_price"), {style: "display: inline-block"}
%question-mark-with-tooltip{"question-mark-with-tooltip" => "_",
"question-mark-with-tooltip-append-to-body" => "true",
"question-mark-with-tooltip-placement" => "top",
"question-mark-with-tooltip-animation" => true,
key: "'js.admin.unit_price_tooltip'"}
%input{ "type" => "text", "id" => "variant_unit_price", "name" => "variant[unit_price]",
"class" => 'fullwidth', "disabled" => true, "ng-model" => "unit_price"}
%div{style: "color: black"}
= t("spree.admin.products.new.unit_price_legend")
%div{ 'set-on-demand' => '' }
.field.checkbox
%label
= f.check_box :on_demand
= t(:on_demand)
%div{'ofn-with-tip' => t('admin.products.variants.to_order_tip')}
%a= t('admin.whats_this')
.field{ 'data-controller': 'toggle-control', 'data-toggle-control-match-value': 'items' }
= f.label :unit_scale do
= t('.unit_scale')
= content_tag(:span, ' *', class: 'required')
= f.hidden_field :variant_unit
= f.hidden_field :variant_unit_scale
= f.select :variant_unit_with_scale,
options_for_select(WeightsAndMeasures.variant_unit_options, @variant.variant_unit_with_scale),
{ include_blank: true },
{ class: "fullwidth no-input", 'aria-label': t('.unit_scale'), data: { "controller": "tom-select", "tom-select-options-value": '{ "plugins": [] }', action: "change->toggle-control#displayIfMatch" } }
= error_message_on @variant, :variant_unit, 'data-toggle-control-target': 'control'
.field
= f.label :on_hand, t(:on_hand)
.fullwidth
= f.text_field :on_hand
= f.text_field :variant_unit_name, 'aria-label': t('items'), 'data-toggle-control-target': 'control', style: (@variant.variant_unit == "items" ? "" : "display: none")
= error_message_on @variant, :variant_unit_name, 'data-toggle-control-target': 'control'
.field.popout{'data-controller': "popout", 'data-popout-update-display-value': "false"}
= f.label :unit do
= t('.unit')
= content_tag(:span, ' *', class: 'required')
= f.button :unit_to_display, class: "popout__button", 'aria-label': t('.unit'), 'data-popout-target': "button" do
= @variant.unit_to_display # Show the generated summary of unit values
%div.popout__container{ style: 'display: none;', 'data-controller': 'toggle-control', 'data-popout-target': "dialog" }
.field
-# Show a composite field for unit_value and unit_description
= f.hidden_field :unit_value
= f.hidden_field :unit_description
= f.text_field :unit_value_with_description,
value: unit_value_with_description(@variant), 'aria-label': t('.unit_value'), required: true
.field
= f.label :display_as, t('.display_as')
= f.text_field :display_as, placeholder: VariantUnits::OptionValueNamer.new(@variant).name
= error_message_on @variant, :unit_value
.right.six.columns.omega.label-block
- if @product.variant_unit != 'weight'
%div
.field
= f.label :sku, t('.sku')
= f.text_field :sku, class: 'fullwidth'
.field
= f.label :price do
= t('.price')
= content_tag(:span, ' *', class: 'required')
= f.text_field :price, class: 'fullwidth', value: number_to_currency(@variant.price, unit: '')&.strip
.field
= hidden_field_tag 'variant_variant_unit', @variant.variant_unit
= hidden_field_tag 'variant_variant_unit_name', @variant.variant_unit_name
= f.field_container :unit_price do
%div{style: "display: flex"}
= f.label :unit_price, t(".unit_price"), {style: "display: inline-block"}
%question-mark-with-tooltip{"question-mark-with-tooltip" => "_",
"question-mark-with-tooltip-append-to-body" => "true",
"question-mark-with-tooltip-placement" => "top",
"question-mark-with-tooltip-animation" => true,
key: "'js.admin.unit_price_tooltip'"}
%input{ "type" => "text", "id" => "variant_unit_price", "name" => "variant[unit_price]", "class" => 'fullwidth', "disabled" => true}
%div{style: "color: black"}
= t("spree.admin.products.new.unit_price_legend")
%div
.field.checkbox
%label
= f.check_box :on_demand, data: { "action": "click->edit-variant#toggleOnHand" }
= t(:on_demand)
= render AdminTooltipComponent.new(text: t('admin.products.variants.to_order_tip'), link_text: t('admin.whats_this'), placement: "right")
.field
= f.label :on_hand, t(:on_hand)
.fullwidth
= f.text_field :on_hand, data: { "edit-variant-target": "onHand" }
.right.six.columns.omega.label-block
.field
= f.label 'weight', t(:weight)+' (kg)'
- value = number_with_precision(@variant.weight, precision: 3)
= f.number_field 'weight', value: value, class: 'fullwidth', step: 0.001
- [:height, :width, :depth].each do |field|
- [:height, :width, :depth].each do |field|
.field
= f.label field, t(field)
- value = number_with_precision(@variant.send(field), precision: 2)
= f.number_field field, value: value, class: 'fullwidth', step: 0.01
.field
= f.label field, t(field)
- value = number_with_precision(@variant.send(field), precision: 2)
= f.number_field field, value: value, class: 'fullwidth', step: 0.01
= f.label :tax_category, t(:tax_category), for: :tax_category_id
= f.collection_select(:tax_category_id, @tax_categories, :id, :name, { include_blank: t(:none) }, { class: 'select2 fullwidth' })
.field
= f.label :tax_category, t(:tax_category), for: :tax_category_id
= f.collection_select(:tax_category_id, @tax_categories, :id, :name, { include_blank: t(:none) }, { class: 'select2 fullwidth' })
.field
= f.label :shipping_category_id, t(:shipping_categories)
= f.collection_select(:shipping_category_id, @shipping_categories, :id, :name, {}, { class: 'select2 fullwidth' })
.field
= f.label :shipping_category_id, t(:shipping_categories)
= f.collection_select(:shipping_category_id, @shipping_categories, :id, :name, {}, { class: 'select2 fullwidth' })
.field
= f.label :primary_taxon, t('.variant_category')
= f.collection_select(:primary_taxon_id, Spree::Taxon.order(:name), :id, :name, { include_blank: true }, { class: "select2 fullwidth" })
.field
= f.label :primary_taxon, t('spree.admin.products.primary_taxon_form.product_category')
= f.collection_select(:primary_taxon_id, Spree::Taxon.order(:name), :id, :name, { include_blank: true }, { class: "select2 fullwidth" })
.field
= f.label :supplier do
= t(:spree_admin_supplier)
= content_tag(:span, ' *', class: 'required')
.field
= f.label :supplier, t(:spree_admin_supplier)
= f.collection_select(:supplier_id, @producers, :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"})
= f.collection_select(:supplier_id, @producers, :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"})
.clear
.clear

View File

@@ -93,6 +93,16 @@ export default class BulkFormController extends Controller {
}
}
// Pop out empty variant unit to allow browser side validation to focus the element
popoutEmptyVariantUnit() {
this.variantUnits = this.element.querySelectorAll("button.popout__button");
this.variantUnits.forEach((element) => {
if (element.textContent == "") {
element.click();
}
});
}
// private
#registerSubmit() {
@@ -135,7 +145,7 @@ export default class BulkFormController extends Controller {
// Check if changed, and mark with class if it is.
#checkIsChanged(element) {
if(!element.isConnected) return false;
if (!element.isConnected) return false;
const changed = this.#isChanged(element);
element.classList.toggle("changed", changed);
@@ -143,9 +153,8 @@ export default class BulkFormController extends Controller {
}
#isChanged(element) {
if (element.type == "checkbox") {
if (element.type == "checkbox") {
return element.defaultChecked !== undefined && element.checked != element.defaultChecked;
} else if (element.type == "select-one") {
// (weird) Behavior of select element's include_blank option in Rails:
// If a select field has include_blank option selected (its value will be ''),
@@ -155,42 +164,49 @@ export default class BulkFormController extends Controller {
opt.hasAttribute("selected"),
);
const selectedOption = element.selectedOptions[0];
const areBothBlank = selectedOption.value === '' && defaultSelected === undefined
const areBothBlank = selectedOption.value === "" && defaultSelected === undefined;
return !areBothBlank && selectedOption !== defaultSelected;
} else {
return element.defaultValue !== undefined && element.value != element.defaultValue;
}
}
#removeAnimationClasses(productRowElement) {
productRowElement.classList.remove('slide-in');
productRowElement.removeEventListener('animationend', this.#removeAnimationClasses.bind(this, productRowElement));
productRowElement.classList.remove("slide-in");
productRowElement.removeEventListener(
"animationend",
this.#removeAnimationClasses.bind(this, productRowElement),
);
}
#observeProductsTableRows() {
this.productsTableObserver = new MutationObserver((mutationList, _observer) => {
const mutationRecord = mutationList[0];
if(mutationRecord) {
if (mutationRecord) {
// Right now we are only using it for product clone, so it's always first
const productRowElement = mutationRecord.addedNodes[0];
if (productRowElement) {
productRowElement.addEventListener('animationend', this.#removeAnimationClasses.bind(this, productRowElement));
productRowElement.addEventListener(
"animationend",
this.#removeAnimationClasses.bind(this, productRowElement),
);
// This is equivalent to form.elements.
const productRowFormElements = productRowElement.querySelectorAll('input, select, textarea, button');
const productRowFormElements = productRowElement.querySelectorAll(
"input, select, textarea, button",
);
this.#registerElements(productRowFormElements);
this.toggleFormChanged();
}
}
});
const productsTable = document.querySelector('.products');
const productsTable = document.querySelector(".products");
// Above mutation function will trigger,
// whenever +products+ table rows (first level children) are mutated i.e. added or removed
// right now we are using this for product clone
// right now we are using this for product clone
this.productsTableObserver.observe(productsTable, { childList: true });
}
}

View File

@@ -0,0 +1,189 @@
import { Controller } from "stimulus";
import OptionValueNamer from "js/services/option_value_namer";
import UnitPrices from "js/services/unit_prices";
// Dynamically update related variant fields
//
// TODO refactor so we can extract what's common with Bulk product page
export default class EditVariantController extends Controller {
static targets = ["onHand"];
connect() {
this.unitPrices = new UnitPrices();
// idea: create a helper that includes a nice getter/setter for Rails model attr values, just pass it the attribute name.
// It could automatically find (and cache a ref to) each dom element and get/set the values.
this.variantUnit = this.element.querySelector('[id="variant_variant_unit"]');
this.variantUnitScale = this.element.querySelector('[id="variant_variant_unit_scale"]');
this.variantUnitName = this.element.querySelector('[id="variant_variant_unit_name"]');
this.variantUnitWithScale = this.element.querySelector(
'[id="variant_variant_unit_with_scale"]',
);
this.variantPrice = this.element.querySelector('[id="variant_price"]');
// on variant_unit_with_scale changed; update variant_unit and variant_unit_scale
this.variantUnitWithScale.addEventListener("change", this.#updateUnitAndScale.bind(this), {
passive: true,
});
this.unitValue = this.element.querySelector('[id="variant_unit_value"]');
this.unitDescription = this.element.querySelector('[id="variant_unit_description"]');
this.unitValueWithDescription = this.element.querySelector(
'[id="variant_unit_value_with_description"]',
);
this.displayAs = this.element.querySelector('[id="variant_display_as"]');
this.unitToDisplay = this.element.querySelector('[id="variant_unit_to_display"]');
// on unit changed; update display_as:placeholder and unit_to_display
[this.variantUnit, this.variantUnitScale, this.variantUnitName].forEach((element) => {
element.addEventListener("change", this.#unitChanged.bind(this), { passive: true });
});
this.variantUnitName.addEventListener("input", this.#unitChanged.bind(this), { passive: true });
// on unit_value_with_description changed; update unit_value and unit_description
// on unit_value and/or unit_description changed; update display_as:placeholder and unit_to_display
this.unitValueWithDescription.addEventListener("input", this.#unitChanged.bind(this), {
passive: true,
});
// on display_as changed; update unit_to_display
// TODO: optimise to avoid unnecessary OptionValueNamer calc
this.displayAs.addEventListener("input", this.#updateUnitDisplay.bind(this), { passive: true });
// update Unit price when variant_unit_with_scale or price changes
[this.variantUnitWithScale, this.variantPrice].forEach((element) => {
element.addEventListener("change", this.#processUnitPrice.bind(this), { passive: true });
});
this.unitValueWithDescription.addEventListener("input", this.#processUnitPrice.bind(this), {
passive: true,
});
// on variantUnit change we need to check if weight needs to be toggled
this.variantUnit.addEventListener("change", this.#toggleWeight.bind(this), { passive: true });
// make sure the unit is correct when page is reload after an error
this.#updateUnitDisplay();
// update unit price on page load
this.#processUnitPrice();
if (this.variantUnit.value === "weight") {
return this.#hideWeight();
}
}
disconnect() {
// Make sure to clean up anything that happened outside
// TODO remove all added event
this.variantUnit.removeEventListener("change", this.#toggleWeight.bind(this), {
passive: true,
});
}
toggleOnHand(event) {
if (event.target.checked === true) {
this.onHandTarget.dataStock = this.onHandTarget.value;
this.onHandTarget.value = I18n.t("admin.products.variants.infinity");
this.onHandTarget.disabled = "disabled";
} else {
this.onHandTarget.removeAttribute("disabled");
this.onHandTarget.value = this.onHandTarget.dataStock;
}
}
// private
// Extract variant_unit and variant_unit_scale from dropdown variant_unit_with_scale,
// and update hidden product fields
#unitChanged(event) {
//Hmm in hindsight the logic in product_controller should be inn this controller already. then we can do everything in one event, and store the generated name in an instance variable.
this.#extractUnitValues();
this.#updateUnitDisplay();
}
// Extract unit_value and unit_description
#extractUnitValues() {
// Extract a number (optional) and text value, separated by a space.
const match = this.unitValueWithDescription.value.match(/^([\d\.\,]+(?= |$)|)( |)(.*)$/);
if (match) {
let unit_value = parseFloat(match[1].replace(",", "."));
unit_value = isNaN(unit_value) ? null : unit_value;
unit_value *= this.variantUnitScale.value ? this.variantUnitScale.value : 1; // Normalise to default scale
this.unitValue.value = unit_value;
this.unitDescription.value = match[3];
}
}
// Update display_as placeholder and unit_to_display
#updateUnitDisplay() {
const unitDisplay = new OptionValueNamer(this.#variant()).name();
this.displayAs.placeholder = unitDisplay;
this.unitToDisplay.textContent = this.displayAs.value || unitDisplay;
}
// A representation of the variant model to satisfy OptionValueNamer.
#variant() {
return {
unit_value: parseFloat(this.unitValue.value),
unit_description: this.unitDescription.value,
variant_unit: this.variantUnit.value,
variant_unit_scale: parseFloat(this.variantUnitScale.value),
variant_unit_name: this.variantUnitName.value,
};
}
// Extract variant_unit and variant_unit_scale from dropdown variant_unit_with_scale,
// and update hidden product fields
#updateUnitAndScale(event) {
const variant_unit_with_scale = this.variantUnitWithScale.value;
const match = variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/); // eg "weight_1000"
if (match) {
this.variantUnit.value = match[1];
this.variantUnitScale.value = parseFloat(match[2]);
} else {
// "items"
this.variantUnit.value = variant_unit_with_scale;
this.variantUnitScale.value = "";
}
this.variantUnit.dispatchEvent(new Event("change"));
this.variantUnitScale.dispatchEvent(new Event("change"));
}
#processUnitPrice() {
const unit_type = this.variantUnit.value;
// TODO double check this
let unit_value = 1;
if (unit_type != "items") {
unit_value = this.unitValue.value;
}
const unit_price = this.unitPrices.displayableUnitPrice(
this.variantPrice.value,
parseFloat(this.variantUnitScale.value),
unit_type,
unit_value,
this.variantUnitName.value,
);
this.element.querySelector('[id="variant_unit_price"]').value = unit_price;
}
#hideWeight() {
this.weight = this.element.querySelector('[id="variant_weight"]');
this.weight.parentElement.style.display = "none";
}
#toggleWeight() {
if (this.variantUnit.value === "weight") {
return this.#hideWeight();
}
// Show weight
this.weight = this.element.querySelector('[id="variant_weight"]');
this.weight.parentElement.style.display = "block";
// Clearing weight value to remove calculated weight for a variant with unit set to "weight"
// See Spree::Variant hook update_weight_from_unit_value
this.weight.value = "";
}
}

View File

@@ -1,38 +0,0 @@
import { Controller } from "stimulus";
// Dynamically update related Product unit fields (expected to move to Variant due to Product Refactor)
//
export default class ProductController extends Controller {
connect() {
// idea: create a helper that includes a nice getter/setter for Rails model attr values, just pass it the attribute name.
// It could automatically find (and cache a ref to) each dom element and get/set the values.
this.variantUnit = this.element.querySelector('[name$="[variant_unit]"]');
this.variantUnitScale = this.element.querySelector('[name$="[variant_unit_scale]"]');
this.variantUnitWithScale = this.element.querySelector('[name$="[variant_unit_with_scale]"]');
// on variant_unit_with_scale changed; update variant_unit and variant_unit_scale
this.variantUnitWithScale.addEventListener("change", this.#updateUnitAndScale.bind(this), {
passive: true,
});
}
// private
// Extract variant_unit and variant_unit_scale from dropdown variant_unit_with_scale,
// and update hidden product fields
#updateUnitAndScale(event) {
const variant_unit_with_scale = this.variantUnitWithScale.value;
const match = variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/); // eg "weight_1000"
if (match) {
this.variantUnit.value = match[1];
this.variantUnitScale.value = parseFloat(match[2]);
} else {
// "items"
this.variantUnit.value = variant_unit_with_scale;
this.variantUnitScale.value = "";
}
this.variantUnit.dispatchEvent(new Event("change"));
this.variantUnitScale.dispatchEvent(new Event("change"));
}
}

View File

@@ -9,6 +9,8 @@ export default class extends Controller {
}
changePage(event) {
const productsForm = document.querySelector("#products-form");
productsForm.scrollIntoView({ behavior: "smooth" });
this.page.value = event.target.dataset.page;
this.submitSearch();
this.page.value = 1;

View File

@@ -5,11 +5,17 @@ import OptionValueNamer from "js/services/option_value_namer";
//
export default class VariantController extends Controller {
connect() {
// Assuming these will be available on the variant soon, just a quick hack to find the product fields:
const product = this.element.closest("[data-record-id]");
this.variantUnit = product.querySelector('[name$="[variant_unit]"]');
this.variantUnitScale = product.querySelector('[name$="[variant_unit_scale]"]');
this.variantUnitName = product.querySelector('[name$="[variant_unit_name]"]');
// idea: create a helper that includes a nice getter/setter for Rails model attr values, just pass it the attribute name.
// It could automatically find (and cache a ref to) each dom element and get/set the values.
this.variantUnit = this.element.querySelector('[name$="[variant_unit]"]');
this.variantUnitScale = this.element.querySelector('[name$="[variant_unit_scale]"]');
this.variantUnitName = this.element.querySelector('[name$="[variant_unit_name]"]');
this.variantUnitWithScale = this.element.querySelector('[name$="[variant_unit_with_scale]"]');
// on variant_unit_with_scale changed; update variant_unit and variant_unit_scale
this.variantUnitWithScale.addEventListener("change", this.#updateUnitAndScale.bind(this), {
passive: true,
});
this.unitValue = this.element.querySelector('[name$="[unit_value]"]');
this.unitDescription = this.element.querySelector('[name$="[unit_description]"]');
@@ -76,11 +82,27 @@ export default class VariantController extends Controller {
return {
unit_value: parseFloat(this.unitValue.value),
unit_description: this.unitDescription.value,
product: {
variant_unit: this.variantUnit.value,
variant_unit_scale: parseFloat(this.variantUnitScale.value),
variant_unit_name: this.variantUnitName.value,
},
variant_unit: this.variantUnit.value,
variant_unit_scale: parseFloat(this.variantUnitScale.value),
variant_unit_name: this.variantUnitName.value,
};
}
// Extract variant_unit and variant_unit_scale from dropdown variant_unit_with_scale,
// and update hidden product fields
#updateUnitAndScale(event) {
const variant_unit_with_scale = this.variantUnitWithScale.value;
const match = variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/); // eg "weight_1000"
if (match) {
this.variantUnit.value = match[1];
this.variantUnitScale.value = parseFloat(match[2]);
} else {
// "items"
this.variantUnit.value = variant_unit_with_scale;
this.variantUnitScale.value = "";
}
this.variantUnit.dispatchEvent(new Event("change"));
this.variantUnitScale.dispatchEvent(new Event("change"));
}
}

View File

@@ -1,5 +1,7 @@
// Customisations for the new Bulk Edit Products page only
// Scoped to containing div, because Turbo messes with body classes
@import "../admin_v3/pages/unit_popout";
#products_v3_page {
#content > .row:first-child > .container:first-child {
// Allow table to extend to full width of available screen space
@@ -311,89 +313,7 @@
// Popout widget (todo: move to separate fiel)
.popout {
position: relative;
&__button {
// override button styles
&.popout__button {
background: $color-tbl-cell-bg;
color: $color-txt-text;
white-space: nowrap;
border-color: transparent;
font-weight: normal;
padding-left: $border-radius; // Super compact
padding-right: 1rem; // Retain space for arrow
height: auto;
min-width: 2em;
min-height: 1lh; // Line height of parent
&:hover,
&:active,
&:focus {
background: $color-tbl-cell-bg;
color: $color-txt-text;
position: relative;
}
&.changed {
border-color: $color-txt-changed-brd;
}
}
&:hover:not(:active):not(:focus):not(.changed) {
border-color: transparent;
}
&:hover,
&:active,
&:focus {
// for some reason, sass ignores &:active, &:focus here. we could make this a mixin and include it in multiple rules instead
&:before {
// for some reason, sass seems to extends the selector to include every other :before selector in the app! probably causing the above, and potentially breaking other styles.
// extending .icon-chevron-down causes infinite loop in compilation. does @include work for classes?
font-family: FontAwesome;
text-decoration: inherit;
display: inline-block;
speak: none;
content: "\f078";
position: absolute;
top: 0; // Required for empty buttons
right: $border-radius;
font-size: 0.67em;
}
}
}
&__container {
position: absolute;
top: -0.6em;
left: -0.2em;
z-index: 1; // Cover below row when hover
width: 9em;
padding: $padding-tbl-cell;
background: $color-tbl-cell-bg;
border-radius: $border-radius;
box-shadow: 0px 0px 8px 0px rgba($near-black, 0.25);
.field {
margin-bottom: 0.75em;
&:last-child {
margin-bottom: 0;
}
}
input {
height: auto;
&[disabled] {
color: transparent; // hide value completely
}
}
}
@include unit_popout;
}
a.image-field {

View File

@@ -91,6 +91,7 @@
@import "../admin/dialog";
@import "../admin/disabled";
@import "components/dropdown"; // admin_v3
@import "pages/edit_variant"; // admin_v3
@import "pages/enterprise_index_panels"; // admin_v3
@import "../admin/enterprises";
@import "../admin/filters_and_controls";

View File

@@ -0,0 +1,87 @@
// Popout widget
@mixin unit_popout {
position: relative;
&__button {
// override button styles
&.popout__button {
background: $color-tbl-cell-bg;
color: $color-txt-text;
white-space: nowrap;
border-color: transparent;
font-weight: normal;
padding-left: $border-radius; // Super compact
padding-right: 1rem; // Retain space for arrow
height: auto;
min-width: 2em;
min-height: 1lh; // Line height of parent
&:hover,
&:active,
&:focus {
background: $color-tbl-cell-bg;
color: $color-txt-text;
position: relative;
}
&.changed {
border-color: $color-txt-changed-brd;
}
}
&:hover:not(:active):not(:focus):not(.changed) {
border-color: transparent;
}
&:hover,
&:active,
&:focus {
// for some reason, sass ignores &:active, &:focus here. we could make this a mixin and include it in multiple rules instead
&:before {
// for some reason, sass seems to extends the selector to include every other :before selector in the app! probably causing the above, and potentially breaking other styles.
// extending .icon-chevron-down causes infinite loop in compilation. does @include work for classes?
font-family: FontAwesome;
text-decoration: inherit;
display: inline-block;
speak: none;
content: "\f078";
position: absolute;
top: 0; // Required for empty buttons
right: $border-radius;
font-size: 0.67em;
}
}
}
&__container {
position: absolute;
top: -0.6em;
left: -0.2em;
z-index: 1; // Cover below row when hover
width: 9em;
padding: $padding-tbl-cell;
background: $color-tbl-cell-bg;
border-radius: $border-radius;
box-shadow: 0px 0px 8px 0px rgba($near-black, 0.25);
.field {
margin-bottom: 0.75em;
&:last-child {
margin-bottom: 0;
}
}
input {
height: auto;
&[disabled] {
color: transparent; // hide value completely
}
}
}
}

View File

@@ -0,0 +1,55 @@
@import "unit_popout";
#edit_variant {
.popout {
@include unit_popout;
&__button {
// override popout button styles
&.popout__button {
// Reapplying button style from buttons.css
background-color: $color-btn-bg;
border: 1px solid $color-btn-bg;
color: $color-btn-text;
font-weight: bold;
&:before {
font-family: FontAwesome;
text-decoration: inherit;
display: inline-block;
speak: none;
content: "\f078";
position: absolute;
top: 0; // Required for empty buttons
right: $border-radius;
font-size: 0.67em;
}
// Reapplying button style from buttons.css
&:active,
&:focus {
outline: none;
border: 1px solid $color-btn-hover-border;
}
&:active:focus {
box-shadow: none;
}
&:hover {
background-color: $color-btn-hover-bg;
border: 1px solid $color-btn-hover-bg;
color: $color-btn-hover-text;
}
}
}
&__container {
width: max-content;
top: auto;
left: auto;
}
}
}

View File

@@ -0,0 +1,24 @@
// Convert number to string currency using injected currency configuration.
// Requires global variable from page: ofn_currency_config
export default function (amount) {
// Set country code (eg. "US").
const currency_code = ofn_currency_config.display_currency
? " " + ofn_currency_config.currency
: "";
// Set decimal points, 2 or 0 if hide_cents.
const decimals = ofn_currency_config.hide_cents === "true" ? 0 : 2;
// Set format if the currency symbol should come after the number, otherwise (default) use the locale setting.
const format = ofn_currency_config.symbol_position === "after" ? "%n %u" : undefined;
// We need to use parseFloat as the amount should come in as a string.
amount = parseFloat(amount);
// Build the final price string.
return (
I18n.toCurrency(amount, {
precision: decimals,
unit: ofn_currency_config.symbol,
format: format,
}) + currency_code
);
}

View File

@@ -1,4 +1,4 @@
import VariantUnitManager from "../../js/services/variant_unit_manager";
import VariantUnitManager from "js/services/variant_unit_manager";
// Javascript clone of VariantUnits::OptionValueNamer, for bulk product editing.
export default class OptionValueNamer {
@@ -9,7 +9,7 @@ export default class OptionValueNamer {
name() {
const [value, unit] = this.option_value_value_unit();
const separator = this.value_scaled() ? '' : ' ';
const separator = this.value_scaled() ? "" : " ";
const name_fields = [];
if (value && unit) {
name_fields.push(`${value}${separator}${unit}`);
@@ -20,21 +20,21 @@ export default class OptionValueNamer {
if (this.variant.unit_description) {
name_fields.push(this.variant.unit_description);
}
return name_fields.join(' ');
return name_fields.join(" ");
}
value_scaled() {
return !!this.variant.product.variant_unit_scale;
return !!this.variant.variant_unit_scale;
}
option_value_value_unit() {
let value, unit_name;
if (this.variant.unit_value) {
if (this.variant.product.variant_unit === "weight" || this.variant.product.variant_unit === "volume") {
if (this.variant.variant_unit === "weight" || this.variant.variant_unit === "volume") {
[value, unit_name] = this.option_value_value_unit_scaled();
} else {
value = this.variant.unit_value;
unit_name = this.pluralize(this.variant.product.variant_unit_name, value);
unit_name = this.pluralize(this.variant.variant_unit_name, value);
}
if (value == parseInt(value, 10)) {
value = parseInt(value, 10);
@@ -55,7 +55,7 @@ export default class OptionValueNamer {
}
return I18n.t(["inflections", unit_key], {
count: count,
defaultValue: unit_name
defaultValue: unit_name,
});
}
@@ -83,17 +83,21 @@ export default class OptionValueNamer {
// to >= 1 when expressed in it.
// If there is none available where this is true, use the smallest
// available unit.
const product = this.variant.product;
const scales = this.variantUnitManager.compatibleUnitScales(product.variant_unit_scale, product.variant_unit);
const scales = this.variantUnitManager.compatibleUnitScales(
this.variant.variant_unit_scale,
this.variant.variant_unit,
);
const variantUnitValue = this.variant.unit_value;
// sets largestScale = last element in filtered scales array
const largestScale = scales.filter(s => variantUnitValue / s >= 1).slice(-1)[0];
const largestScale = scales.filter((s) => variantUnitValue / s >= 1).slice(-1)[0];
if (largestScale) {
return [largestScale, this.variantUnitManager.getUnitName(largestScale, product.variant_unit)];
return [
largestScale,
this.variantUnitManager.getUnitName(largestScale, this.variant.variant_unit),
];
} else {
return [scales[0], this.variantUnitManager.getUnitName(scales[0], product.variant_unit)];
return [scales[0], this.variantUnitManager.getUnitName(scales[0], this.variant.variant_unit)];
}
}
}

View File

@@ -0,0 +1,45 @@
export default class PriceParser {
parse(price) {
if (!price) {
return null;
}
// used decimal and thousands separators from currency configuration
const decimalSeparator = I18n.toCurrency(0.1, { precision: 1, unit: "" }).substring(1, 2);
const thousandsSeparator = I18n.toCurrency(1000, { precision: 1, unit: "" }).substring(1, 2);
// Replace comma used as a decimal separator and remplace by "."
price = this.replaceCommaByFinalPoint(price);
// Remove configured thousands separator if it is actually a thousands separator
price = this.removeThousandsSeparator(price, thousandsSeparator);
if (decimalSeparator === ",") {
price = price.replace(",", ".");
}
price = parseFloat(price);
if (isNaN(price)) {
return null;
}
return price;
}
replaceCommaByFinalPoint(price) {
if (price.match(/^[0-9]*(,{1})[0-9]{1,2}$/g)) {
return price.replace(",", ".");
} else {
return price;
}
}
removeThousandsSeparator(price, thousandsSeparator) {
if (new RegExp(`^([0-9]*(${thousandsSeparator}{1})[0-9]{3}[0-9\.,]*)*$`, "g").test(price)) {
return price.replaceAll(thousandsSeparator, "");
} else {
return price;
}
}
}

View File

@@ -0,0 +1,51 @@
import PriceParser from "js/services/price_parser";
import VariantUnitManager from "js/services/variant_unit_manager";
import localizeCurrency from "js/services/localize_currency";
export default class UnitPrices {
constructor() {
this.variantUnitManager = new VariantUnitManager();
this.priceParser = new PriceParser();
}
displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name) {
price = this.priceParser.parse(price);
if (price && !isNaN(price) && unit_type && unit_value) {
const value = localizeCurrency(
this.price(price, scale, unit_type, unit_value, variant_unit_name),
);
const unit = this.unit(scale, unit_type, variant_unit_name);
return `${value} / ${unit}`;
}
return null;
}
price(price, scale, unit_type, unit_value) {
return price / this.denominator(scale, unit_type, unit_value);
}
denominator(scale, unit_type, unit_value) {
const unit = this.unit(scale, unit_type);
if (unit === "lb") {
return unit_value / 453.6;
} else if (unit === "kg") {
return unit_value / 1000;
} else {
return unit_value;
}
}
unit(scale, unit_type, variant_unit_name = "") {
if (variant_unit_name.length > 0 && unit_type === "items") {
return variant_unit_name;
} else if (unit_type === "items") {
return "item";
} else if (this.variantUnitManager.systemOfMeasurement(scale, unit_type) === "imperial") {
return "lb";
} else if (unit_type === "weight") {
return "kg";
} else if (unit_type === "volume") {
return "L";
}
}
}

View File

@@ -7,33 +7,41 @@ export default class VariantUnitManager {
getUnitName(scale, unitType) {
if (this.units[unitType][scale]) {
return this.units[unitType][scale]['name'];
return this.units[unitType][scale]["name"];
} else {
return '';
return "";
}
};
}
// Filter by measurement system
// Filter by measurement system
compatibleUnitScales(scale, unitType) {
const scaleSystem = this.units[unitType][scale]['system'];
const scaleSystem = this.units[unitType][scale]["system"];
return Object.entries(this.units[unitType])
.filter(([scale, scaleInfo]) => {
return scaleInfo['system'] == scaleSystem;
return scaleInfo["system"] == scaleSystem;
})
.map(([scale, _]) => parseFloat(scale))
.sort();
}
systemOfMeasurement(scale, unitType) {
if (this.units[unitType][scale]) {
return this.units[unitType][scale]["system"];
} else {
return "custom";
}
}
// private
#loadUnits(units) {
// Transform unit scale to a JS Number for compatibility. This would be way simpler in Ruby or Coffeescript!!
const unitsTransformed = Object.entries(units).map(([measurement, measurementInfo]) => {
const measurementInfoTransformed = Object.fromEntries(Object.entries(measurementInfo).map(([scale, unitInfo]) =>
[ parseFloat(scale), unitInfo ]
));
return [ measurement, measurementInfoTransformed ];
const measurementInfoTransformed = Object.fromEntries(
Object.entries(measurementInfo).map(([scale, unitInfo]) => [parseFloat(scale), unitInfo]),
);
return [measurement, measurementInfoTransformed];
});
return Object.fromEntries(unitsTransformed);
}

View File

@@ -155,7 +155,7 @@ if ENV["OPENID_APP_ID"].present? && ENV["OPENID_APP_SECRET"].present?
config.omniauth :openid_connect, {
name: :openid_connect,
issuer: "https://login.lescommuns.org/auth/realms/data-food-consortium",
scope: [:openid, :profile, :email],
scope: [:openid, :profile, :email, :offline_access],
response_type: :code,
uid_field: "email",
discovery: true,

View File

@@ -46,12 +46,12 @@ ar:
price: "السعر"
primary_taxon_id: "نوع المنتج "
shipping_category_id: "نوع الشحن"
variant_unit_name: "اسم وحدة النوع"
unit_value: "قيمةالوحدة"
spree/variant:
primary_taxon: "نوع المنتج "
shipping_category_id: "نوع الشحن"
supplier: "المورد"
variant_unit_name: "اسم وحدة النوع"
unit_value: "قيمةالوحدة"
spree/credit_card:
base: "بطاقة ائتمان"
number: "رقم "
@@ -1146,6 +1146,8 @@ ar:
<a href="https://regenerative.org.au/" target="_blank"><b>Visit Discover Regenerative</b>
<i class="icon-external-link"></i></a>
</p>
vine:
enable: "الاتصال"
actions:
edit_profile: الإعدادات
properties: الخصائص
@@ -2802,6 +2804,7 @@ ar:
report_header_quantity: الكمية
report_header_max_quantity: اعلى كمية
report_header_variant: النوع
report_header_variant_unit_name: اسم وحدة النوع
report_header_variant_value: قيمة النوع
report_header_variant_unit: وحدة النوع
report_header_total_available: القيمة الكلية متاحة
@@ -4190,12 +4193,15 @@ ar:
new_variant: "نوع جديد"
form:
sku: "SKU"
price: "السعر"
unit_price: "سعر الوحدة"
display_as: "عرض ب"
display_name: "اسم العرض"
display_as_placeholder: 'على سبيل المثال 2 كجم'
display_name_placeholder: 'على سبيل المثال طماطم'
unit: وحدة
price: السعر
unit_value: قيمةالوحدة
variant_category: الفئة
autocomplete:
out_of_stock: "غير متوفر"
producer_name: "المنتج"

View File

@@ -46,12 +46,12 @@ ca:
price: "Preu"
primary_taxon_id: "Categoria del producte"
shipping_category_id: "Categoria d'enviament"
variant_unit_name: "Nom de la unitat de la variant"
unit_value: "Valor de la unitat"
spree/variant:
primary_taxon: "Categoria del producte"
shipping_category_id: "Categoria d'enviament"
supplier: "Proveïdora"
variant_unit_name: "Nom de la unitat de la variant"
unit_value: "Valor de la unitat"
spree/credit_card:
base: "Targeta de crèdit"
number: "Número"
@@ -1200,6 +1200,8 @@ ca:
loading: "S'està carregant"
discover_regen:
loading: "S'està carregant"
vine:
enable: "Connecta"
actions:
edit_profile: Configuració
properties: Propietats
@@ -2789,6 +2791,7 @@ ca:
report_header_quantity: Quantitat
report_header_max_quantity: Quantitat màxima
report_header_variant: Variant
report_header_variant_unit_name: Nom de la unitat de la variant
report_header_variant_value: Valor de la variant
report_header_variant_unit: Unitat de la variant
report_header_total_available: Total disponible
@@ -4068,12 +4071,15 @@ ca:
new_variant: "Nova variant"
form:
sku: "Número de referència (SKU)"
price: "Preu"
unit_price: "Preu unitari"
display_as: "Mostra com"
display_name: "Nom de visualització"
display_as_placeholder: 'per exemple. 2 kg'
display_name_placeholder: 'per exemple. Tomàquets'
unit: Unitat
price: Preu
unit_value: Valor de la unitat
variant_category: Categoria
autocomplete:
out_of_stock: "Fora d'existència"
producer_name: "Productor"

View File

@@ -49,12 +49,12 @@ cy:
price: "Pris"
primary_taxon_id: "Categori Cynnyrch"
shipping_category_id: "Categori dosbarthu."
variant_unit_name: "Enw Uned Amrywiol"
unit_value: "Gwerth yr uned"
spree/variant:
primary_taxon: "Categori Cynnyrch"
shipping_category_id: "Categori Anfon"
supplier: "Cyflenwr"
variant_unit_name: "Enw Uned Amrywiolyn"
unit_value: "Gwerth yr uned"
spree/credit_card:
base: "Cerdyn credyd"
number: "Rhif"
@@ -1260,6 +1260,8 @@ cy:
disable: "Rhoir gorau i rannu"
loading: "Yn llwytho"
link_label: "Rheolir rhestru"
vine:
enable: "Adnoddau"
actions:
edit_profile: Gosodiadau
properties: Manylion
@@ -2947,6 +2949,7 @@ cy:
report_header_quantity: Nifer
report_header_max_quantity: Uchafswm nifer
report_header_variant: Amrywiolyn
report_header_variant_unit_name: Enw Uned Amrywiolyn
report_header_variant_value: Gwerth Amrywiolyn
report_header_variant_unit: Uned Amrywiolyn
report_header_total_available: Cyfanswm ar gael
@@ -4320,12 +4323,15 @@ cy:
new_variant: "Amrywiolyn newydd"
form:
sku: "Cod y Cynnyrch"
price: "Pris"
unit_price: "Pris Uned"
display_as: "Arddangos fel"
display_name: "Enw Arddangos"
display_as_placeholder: 'e.e. 2 kg'
display_name_placeholder: 'e.e. Tomatos'
unit: Uned
price: Pris
unit_value: Gwerth yr uned
variant_category: Categori
autocomplete:
out_of_stock: "Allan o stoc"
producer_name: "Cynhyrchydd"

View File

@@ -35,11 +35,11 @@ de_CH:
price: "Preis"
primary_taxon_id: "Produktkategorie"
shipping_category_id: "Lieferkategorie"
variant_unit_name: "Name der Varianteneinheit"
spree/variant:
primary_taxon: "Produktkategorie"
shipping_category_id: "Lieferkategorie"
supplier: "Lieferant"
variant_unit_name: "Name der Varianteneinheit"
spree/credit_card:
base: "Kreditkarte"
number: "Kreditkartennummer"
@@ -1112,6 +1112,8 @@ de_CH:
loading: "Wird geladen ..."
discover_regen:
loading: "Wird geladen ..."
vine:
enable: "Über OFN"
actions:
edit_profile: Einstellungen
properties: Eigenschaften
@@ -2710,6 +2712,7 @@ de_CH:
report_header_quantity: Menge
report_header_max_quantity: Max Menge
report_header_variant: Produktvariante
report_header_variant_unit_name: Name der Varianteneinheit
report_header_variant_value: Wert der Produktvarianten
report_header_variant_unit: Varianteneinheit
report_header_total_available: Insgesamt verfügbar
@@ -3974,12 +3977,14 @@ de_CH:
new_variant: "Neue Produktvariante"
form:
sku: "Artikelnummer"
price: "Preis"
unit_price: "Grundpreis"
display_as: "Anzeigen als"
display_name: "Variantenname"
display_as_placeholder: 'z. B. 2 kg'
display_name_placeholder: 'z. B. Tomaten'
unit: Einheit
price: Preis
variant_category: Kategorie
autocomplete:
out_of_stock: "nicht vorrätig"
producer_name: "Produzent"

View File

@@ -49,12 +49,12 @@ de_DE:
price: "Preis"
primary_taxon_id: "Produktkategorie"
shipping_category_id: "Lieferkategorie"
variant_unit_name: "Name der Varianteneinheit"
unit_value: "Menge"
spree/variant:
primary_taxon: "Produktkategorie"
shipping_category_id: "Lieferkategorie"
supplier: "Lieferant"
variant_unit_name: "Name der Varianteneinheit"
unit_value: "Menge"
spree/credit_card:
base: "Kreditkarte"
number: "Kreditkartennummer"
@@ -1240,6 +1240,8 @@ de_DE:
loading: "Wird geladen ..."
discover_regen:
loading: "Wird geladen ..."
vine:
enable: "Über OFN"
actions:
edit_profile: Einstellungen
properties: Eigenschaften
@@ -2927,6 +2929,7 @@ de_DE:
report_header_quantity: Menge
report_header_max_quantity: Max Menge
report_header_variant: Produktvariante
report_header_variant_unit_name: Name der Varianteneinheit
report_header_variant_value: Wert der Produktvarianten
report_header_variant_unit: Varianteneinheit
report_header_total_available: Insgesamt verfügbar
@@ -4250,12 +4253,15 @@ de_DE:
new_variant: "Neue Produktvariante"
form:
sku: "Artikelnummer"
price: "Preis"
unit_price: "Grundpreis"
display_as: "Anzeigen als"
display_name: "Variantenname"
display_as_placeholder: 'z. B. 2 kg'
display_name_placeholder: 'z. B. Tomaten'
unit: Einheit
price: Preis
unit_value: Menge
variant_category: Kategorie
autocomplete:
out_of_stock: "nicht vorrätig"
producer_name: "Produzent"

View File

@@ -46,12 +46,12 @@ el:
price: "Τιμή"
primary_taxon_id: "Κατηγορία προϊόντος"
shipping_category_id: "Κατηγορία μεταφορικών"
variant_unit_name: "Όνομα μεταβλητής"
unit_value: "Τιμή μεταβλητής"
spree/variant:
primary_taxon: "Κατηγορία προϊόντος"
shipping_category_id: "Κατηγορία μεταφοράς"
supplier: "Προμηθευτής"
variant_unit_name: "Όνομα μεταβλητής"
unit_value: "Τιμή μεταβλητής"
spree/credit_card:
base: "Κάρτα πιστωτική/χρεωστική"
number: "Αριθμός"
@@ -1314,6 +1314,10 @@ el:
target="_blank"><b>Μάθετε περισσότερα για το Discover Regenerative</b>
<i class="icon-external-link"></i></a>
</p>
vine:
enable: "Σύνδεση"
disable: "Αποσύνδεση."
need_to_be_manager: "Μόνο οι διαχειριστές μπορούν να συνδέουν εφαρμογές."
actions:
edit_profile: Ρυθμήσεις
properties: Ιδιότητες
@@ -2993,6 +2997,7 @@ el:
report_header_quantity: Ποσότητα
report_header_max_quantity: Μέγιστη Ποσότητα
report_header_variant: Παραλαγή
report_header_variant_unit_name: Όνομα μεταβλητής
report_header_variant_value: Παραλλαγή Αξίας
report_header_variant_unit: Μονάδα μέτρησης μεταβλητής
report_header_total_available: Σύνολο διαθέσιμο
@@ -4326,12 +4331,16 @@ el:
new_variant: "Νέα παραλλαγή"
form:
sku: "SKU"
price: "Τιμή"
unit_price: "Τιμή Μονάδας"
display_as: "Εμφάνιση Ως"
display_name: "Εμφανιζόμενο όνομα"
display_as_placeholder: 'π.χ. 2 κιλά'
display_name_placeholder: 'π.χ. Ντομάτες'
unit_scale: "Κλίμακα μονάδας"
unit: Μονάδα
price: Τιμή
unit_value: Τιμή μεταβλητής
variant_category: Κατηγορία
autocomplete:
out_of_stock: "Εκτός αποθέματος"
producer_name: "Παραγωγός"

View File

@@ -70,13 +70,13 @@ en:
price: "Price"
primary_taxon_id: "Product Category"
shipping_category_id: "Shipping Category"
variant_unit: "Unit Scale"
variant_unit_name: "Variant Unit Name"
unit_value: "Unit value"
spree/variant:
primary_taxon: "Product Category"
shipping_category_id: "Shipping Category"
supplier: "Supplier"
variant_unit: "Unit Scale"
variant_unit_name: "Variant Unit Name"
unit_value: "Unit value"
spree/credit_card:
base: "Credit Card"
number: "Number"
@@ -1412,7 +1412,7 @@ en:
connected_apps:
legend: "Connected apps"
affiliate_sales_data:
title: "INRAE / UFC QUE CHOISIR Research"
title: "INRAE Research"
tagline: "Allow this research project to access your orders data anonymously"
enable: "Allow data sharing"
disable: "Stop sharing"
@@ -1420,10 +1420,10 @@ en:
need_to_be_manager: "Only managers can connect apps."
description_html: |
<p>
INRAE and UFC QUE CHOISIR are teaming up to study food prices in short food systems and compare them with prices in the supermarket, for a given set of products. The data that is used by INRAE is mixed with data coming from other short food chain platforms in France. No individual product prices will be publicly disclosed through this project.
INRAE are studiying food prices in short food systems and compare them with prices in the supermarket, for a given set of products. The data that is used by INRAE is mixed with data coming from other short food chain platforms in France. No individual product prices will be publicly disclosed through this project.
</p>
<p>
<a href="https://apropos.coopcircuits.fr/"
<a href="https://pepr-sams.fr/2024/03/12/plat4terfood/"
target="_blank"><b>Learn more about this research project</b>
<i class="icon-external-link"></i></a>
</p>
@@ -1768,6 +1768,7 @@ en:
pack_by_customer: Pack By Customer
pack_by_supplier: Pack By Supplier
pack_by_product: Pack By Product
pay_your_suppliers: Pay your suppliers
display:
report_is_big: "This report is big and may slow down your device."
display_anyway: "Display anyway"
@@ -1814,6 +1815,8 @@ en:
enterprise_fee_summary:
name: "Enterprise Fee Summary"
description: "Summary of Enterprise Fees collected"
suppliers:
name: Suppliers
enterprise_fees_with_tax_report_by_order: "Enterprise Fees With Tax Report By Order"
enterprise_fees_with_tax_report_by_producer: "Enterprise Fees With Tax Report By Producer"
errors:
@@ -3172,6 +3175,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using
report_render_options: Rendering Options
report_header_ofn_uid: OFN UID
report_header_order_cycle: Order Cycle
report_header_order_cycle_start_date: OC Start Date
report_header_order_cycle_end_date: OC End Date
report_header_user: User
report_header_email: Email
report_header_status: Status
@@ -3192,6 +3197,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
report_header_hub_legal_name: "Hub Legal Name"
report_header_hub_contact_name: "Hub Contact Name"
report_header_hub_email: "Hub Public Email"
report_header_hub_contact_email: Hub Contact Email
report_header_hub_owner_email: Hub Owner Email
report_header_hub_phone: "Hub Phone Number"
report_header_hub_address_line1: "Hub Address Line 1"
@@ -3252,6 +3258,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
report_header_quantity: Quantity
report_header_max_quantity: Max Quantity
report_header_variant: Variant
report_header_variant_unit_name: Variant Unit Name
report_header_variant_value: Variant Value
report_header_variant_unit: Variant Unit
report_header_total_available: Total available
@@ -3263,6 +3270,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using
report_header_producer_suburb: Producer Suburb
report_header_producer_tax_status: Producer Tax Status
report_header_producer_charges_sales_tax?: GST/VAT Registered
report_header_producer_abn_acn: Producer ABN/ACN
report_header_producer_address: Producer Address
report_header_unit: Unit
report_header_group_buy_unit_quantity: Group Buy Unit Quantity
report_header_cost: Cost
@@ -3323,7 +3332,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using
report_header_total_units: Total Units
report_header_sum_max_total: "Sum Max Total"
report_header_total_excl_vat: "Total excl. tax (%{currency_symbol})"
report_header_total_fees_excl_tax: "Total fees excl. tax (%{currency_symbol})"
report_header_total_tax_on_fees: "Total tax on fees (%{currency_symbol})"
report_header_total: "Total (%{currency_symbol})"
report_header_total_incl_vat: "Total incl. tax (%{currency_symbol})"
report_header_total_excl_fees_and_tax: "Total excl. fees and tax (%{currency_symbol})"
report_header_temp_controlled: TempControlled?
report_header_is_producer: Producer?
report_header_not_confirmed: Not Confirmed
@@ -4639,6 +4652,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using
display_name: "Display Name"
display_as_placeholder: 'eg. 2 kg'
display_name_placeholder: 'eg. Tomatoes'
unit_scale: "Unit scale"
unit: Unit
price: Price
unit_value: Unit value
variant_category: Category
autocomplete:
out_of_stock: "Out of Stock"
producer_name: "Producer"

View File

@@ -30,11 +30,11 @@ en_AU:
price: "Price"
primary_taxon_id: "Product Category"
shipping_category_id: "Shipping Category"
variant_unit_name: "Variant Unit Name"
spree/variant:
primary_taxon: "Product Category"
shipping_category_id: "Shipping Category"
supplier: "Supplier"
variant_unit_name: "Variant Unit Name"
spree/credit_card:
base: "Credit Card"
number: "Number"
@@ -955,6 +955,8 @@ en_AU:
<a href="https://regenerative.org.au/" target="_blank"><b>Visit Discover Regenerative</b>
<i class="icon-external-link"></i></a>
</p>
vine:
enable: "Open Road"
actions:
edit_profile: Settings
properties: Properties
@@ -2461,6 +2463,7 @@ en_AU:
report_header_quantity: Quantity
report_header_max_quantity: Max Quantity
report_header_variant: Variant
report_header_variant_unit_name: Variant Unit Name
report_header_variant_value: Variant Value
report_header_variant_unit: Variant Unit
report_header_total_available: Total available
@@ -3630,12 +3633,14 @@ en_AU:
new_variant: "New Variant"
form:
sku: "SKU"
price: "Price"
unit_price: "Unit Price"
display_as: "Display As"
display_name: "Display Name"
display_as_placeholder: 'eg. 2 kg'
display_name_placeholder: 'eg. Tomatoes'
unit: Unit
price: Price
variant_category: Category
autocomplete:
out_of_stock: "Out of Stock"
producer_name: "Producer"

View File

@@ -29,11 +29,11 @@ en_BE:
price: "Price"
primary_taxon_id: "Product Category"
shipping_category_id: "Shipping Category"
variant_unit_name: "Variant Unit Name"
spree/variant:
primary_taxon: "Product Category"
shipping_category_id: "Shipping Category"
supplier: "Supplier"
variant_unit_name: "Variant Unit Name"
spree/credit_card:
base: "Credit Card"
number: "Number"
@@ -885,6 +885,8 @@ en_BE:
loading: "Loading"
discover_regen:
loading: "Loading"
vine:
enable: "Connect"
actions:
edit_profile: Settings
properties: Properties
@@ -2319,6 +2321,7 @@ en_BE:
report_header_quantity: Quantity
report_header_max_quantity: Max Quantity
report_header_variant: Variant
report_header_variant_unit_name: Variant Unit Name
report_header_variant_value: Variant Value
report_header_variant_unit: Variant Unit
report_header_total_available: Total available
@@ -3288,9 +3291,11 @@ en_BE:
option_types: "Option Types"
form:
sku: "SKU"
price: "Price"
unit_price: "Unit Price"
display_as: "Display As"
unit: Unit
price: Price
variant_category: Category
autocomplete:
out_of_stock: "Out of Stock"
producer_name: "Producer"

View File

@@ -49,13 +49,13 @@ en_CA:
price: "Price"
primary_taxon_id: "Product Category"
shipping_category_id: "Shipping Category"
variant_unit: "Unit Scale"
variant_unit_name: "Variant Unit Name"
unit_value: "Unit value"
spree/variant:
primary_taxon: "Product Category"
shipping_category_id: "Shipping Category"
supplier: "Supplier"
variant_unit: "Unit Scale"
variant_unit_name: "Variant Unit Name"
unit_value: "Unit value"
spree/credit_card:
base: "Credit Card"
number: "Number"
@@ -1372,6 +1372,10 @@ en_CA:
<a href="https://waterlooregionfood.ca/" target="_blank">Learn more about the Waterloo Food Directory.
<i class="icon-external-link"></i></a>
</p>
vine:
enable: "Donate"
disable: "Disconnect"
need_to_be_manager: "Only managers can connect apps."
actions:
edit_profile: Settings
properties: Properties
@@ -3079,6 +3083,7 @@ en_CA:
report_header_quantity: Quantity
report_header_max_quantity: Max Quantity
report_header_variant: Variant
report_header_variant_unit_name: Variant Unit Name
report_header_variant_value: Variant Value
report_header_variant_unit: Variant Unit
report_header_total_available: Total available
@@ -4429,12 +4434,16 @@ en_CA:
new_variant: "New Variant"
form:
sku: "SKU"
price: "Price"
unit_price: "Unit Price"
display_as: "Display As"
display_name: "Display Name"
display_as_placeholder: 'eg. 2 kg'
display_name_placeholder: 'eg. Tomatoes'
unit_scale: "Unit scale"
unit: Unit
price: Price
unit_value: Unit value
variant_category: Category
autocomplete:
out_of_stock: "Out of Stock"
producer_name: "Producer"

View File

@@ -29,11 +29,11 @@ en_DE:
price: "Price"
primary_taxon_id: "Product Category"
shipping_category_id: "Shipping Category"
variant_unit_name: "Variant Unit Name"
spree/variant:
primary_taxon: "Product Category"
shipping_category_id: "Shipping Category"
supplier: "Supplier"
variant_unit_name: "Variant Unit Name"
spree/credit_card:
base: "Credit Card"
number: "Number"
@@ -893,6 +893,8 @@ en_DE:
loading: "Loading"
discover_regen:
loading: "Loading"
vine:
enable: "Connect"
actions:
edit_profile: Settings
properties: Properties
@@ -2327,6 +2329,7 @@ en_DE:
report_header_quantity: Quantity
report_header_max_quantity: Max Quantity
report_header_variant: Variant
report_header_variant_unit_name: Variant Unit Name
report_header_variant_value: Variant Value
report_header_variant_unit: Variant Unit
report_header_total_available: Total available
@@ -3303,9 +3306,11 @@ en_DE:
option_types: "Option Types"
form:
sku: "SKU"
price: "Price"
unit_price: "Unit Price"
display_as: "Display As"
unit: Unit
price: Price
variant_category: Category
autocomplete:
out_of_stock: "Out of Stock"
producer_name: "Producer"

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