Compare commits

...

122 Commits

Author SHA1 Message Date
Maikel Linke
32c96b72ad Update all locales with the latest Transifex translations 2025-08-08 10:24:04 +10:00
Maikel
30701d61e2 Merge pull request #13232 from chitty/rails7.1
Bump Rails to 7.1
2025-08-08 10:09:58 +10:00
Carlos Chitty
45b712ddcd Set latest invoce date explicitly in Orders::GenerateInvoiceService test
Solves CI failure:
https://github.com/openfoodfoundation/openfoodnetwork/actions/runs/14760883756/job/41441014958?pr=13232
2025-08-07 17:44:52 +02:00
Carlos Chitty
3153e99497 Update OpenOrderCycleJob test "syncing remote products" to expect 58 queries instead of 59
The main point of the test is to alert us if the query count increased (https://github.com/openfoodfoundation/openfoodnetwork/pull/13232#discussion_r2199896280).
The missing query in rails 7.1:

Spree::StockItem Load  SELECT "spree_stock_items"."id", "spree_stock_items"."variant_id", "spree_stock_items"."count_on_hand", "spree_stock_items"."created_at", "spree_stock_items"."updated_at", "spree_stock_items"."backorderable", "spree_stock_items"."deleted_at", "spree_stock_items"."lock_version" FROM "spree_stock_items" WHERE "spree_stock_items"."id" = $1 LIMIT $2 FOR UPDATE
2025-08-07 17:44:52 +02:00
Carlos Chitty
a2f263e081 User Rails.env.local?
https://github.com/rails/rails/pull/46786
Solves rubocop failure in rails 7.1 bump branch https://github.com/openfoodfoundation/openfoodnetwork/actions/runs/14739687970/job/41374340281?pr=13232
2025-08-07 17:44:52 +02:00
Carlos Chitty
3cb6a2617b Do not fail tests on deprecation warnings for the next rails version (7.2) 2025-08-07 17:44:52 +02:00
Carlos Chitty
420deca437 Bump rails from 7.0.8 to 7.1.5.1 2025-08-07 17:44:52 +02:00
Filipe
76aebf8a72 Merge pull request #13436 from chahmedejaz/task/13432-decommission-old-products-screen
Decommission Old Products UI and Related Code
2025-08-07 17:39:43 +02:00
Maikel
1bfff91c72 Merge pull request #13449 from openfoodfoundation/dependabot/npm_and_yarn/tmp-0.2.4
Bump tmp from 0.2.1 to 0.2.4
2025-08-07 10:24:15 +10:00
Maikel Linke
d469552afc Fix schema version 2025-08-07 10:13:14 +10:00
dependabot[bot]
e6cffde8fb Bump tmp from 0.2.1 to 0.2.4
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.1 to 0.2.4.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.1...v0.2.4)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Comments out the DOCKER variable

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

@@ -4,17 +4,11 @@
SimpleCov.start 'rails' do
add_filter '/bin/'
add_filter '/config/'
add_filter '/jobs/application_job.rb'
add_filter '/schemas/'
add_filter '/lib/generators'
add_filter '/spec/'
add_filter '/vendor/'
add_filter '/public'
add_filter '/swagger'
add_filter '/script'
add_filter '/log'
add_filter '/db'
add_filter '/lib/tasks/sample_data/'
# We haven't managed to make simplecov recognise rake coverage accurately.
add_filter '/lib/tasks/'
formatter SimpleCov::Formatter::SimpleFormatter
end

View File

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

View File

@@ -171,9 +171,9 @@ end
group :test do
gem 'pdf-reader'
gem 'puffing-billy'
gem 'rails-controller-testing'
gem 'simplecov', require: false
gem 'simplecov-lcov', require: false
gem 'undercover', require: false
gem 'vcr', require: false
gem 'webmock', require: false

View File

@@ -49,53 +49,57 @@ GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.1.0)
actioncable (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
actioncable (7.1.5.1)
actionpack (= 7.1.5.1)
activesupport (= 7.1.5.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
zeitwerk (~> 2.6)
actionmailbox (7.1.5.1)
actionpack (= 7.1.5.1)
activejob (= 7.1.5.1)
activerecord (= 7.1.5.1)
activestorage (= 7.1.5.1)
activesupport (= 7.1.5.1)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.8)
actionpack (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activesupport (= 7.0.8)
actionmailer (7.1.5.1)
actionpack (= 7.1.5.1)
actionview (= 7.1.5.1)
activejob (= 7.1.5.1)
activesupport (= 7.1.5.1)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.8)
actionview (= 7.0.8)
activesupport (= 7.0.8)
rack (~> 2.0, >= 2.2.4)
rails-dom-testing (~> 2.2)
actionpack (7.1.5.1)
actionview (= 7.1.5.1)
activesupport (= 7.1.5.1)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actionpack-action_caching (1.2.2)
actionpack (>= 4.0.0)
actiontext (7.0.8)
actionpack (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
actiontext (7.1.5.1)
actionpack (= 7.1.5.1)
activerecord (= 7.1.5.1)
activestorage (= 7.1.5.1)
activesupport (= 7.1.5.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.8)
activesupport (= 7.0.8)
actionview (7.1.5.1)
activesupport (= 7.1.5.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
active_model_serializers (0.8.4)
activemodel (>= 3.0)
active_storage_validations (1.1.4)
@@ -103,8 +107,8 @@ GEM
activemodel (>= 5.2.0)
activestorage (>= 5.2.0)
activesupport (>= 5.2.0)
activejob (7.0.8)
activesupport (= 7.0.8)
activejob (7.1.5.1)
activesupport (= 7.1.5.1)
globalid (>= 0.3.6)
activemerchant (1.133.0)
activesupport (>= 4.2)
@@ -112,11 +116,12 @@ GEM
i18n (>= 0.6.9)
nokogiri (~> 1.4)
rexml (~> 3.2.5)
activemodel (7.0.8)
activesupport (= 7.0.8)
activerecord (7.0.8)
activemodel (= 7.0.8)
activesupport (= 7.0.8)
activemodel (7.1.5.1)
activesupport (= 7.1.5.1)
activerecord (7.1.5.1)
activemodel (= 7.1.5.1)
activesupport (= 7.1.5.1)
timeout (>= 0.4.0)
activerecord-import (1.6.0)
activerecord (>= 4.2)
activerecord-postgresql-adapter (0.0.1)
@@ -128,17 +133,24 @@ GEM
multi_json (~> 1.11, >= 1.11.2)
rack (>= 2.0.8, < 4)
railties (>= 6.1)
activestorage (7.0.8)
actionpack (= 7.0.8)
activejob (= 7.0.8)
activerecord (= 7.0.8)
activesupport (= 7.0.8)
activestorage (7.1.5.1)
actionpack (= 7.1.5.1)
activejob (= 7.1.5.1)
activerecord (= 7.1.5.1)
activesupport (= 7.1.5.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.8)
activesupport (7.1.5.1)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
securerandom (>= 0.3)
tzinfo (~> 2.0)
acts-as-taggable-on (10.0.0)
activerecord (>= 6.1, < 7.2)
@@ -159,7 +171,7 @@ GEM
angularjs-rails (1.8.0)
arel-helpers (2.14.0)
activerecord (>= 3.1.0, < 8)
ast (2.4.2)
ast (2.4.3)
attr_required (1.0.2)
aws-eventstream (1.3.0)
aws-partitions (1.929.0)
@@ -180,7 +192,8 @@ GEM
base64 (0.2.0)
bcp47_spec (0.2.1)
bcrypt (3.1.20)
bigdecimal (3.1.8)
benchmark (0.4.1)
bigdecimal (3.2.2)
bindata (2.5.0)
bindex (0.8.1)
bootsnap (1.18.3)
@@ -230,6 +243,7 @@ GEM
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.3.1)
connection_pool (2.4.1)
cookiejar (0.3.4)
crack (1.0.0)
bigdecimal
rexml
@@ -269,11 +283,25 @@ GEM
digest (3.1.1)
docile (1.4.0)
dotenv (3.1.2)
drb (2.2.3)
em-http-request (1.1.7)
addressable (>= 2.3.4)
cookiejar (!= 0.3.1)
em-socksify (>= 0.3)
eventmachine (>= 1.0.3)
http_parser.rb (>= 0.6.0)
em-socksify (0.3.3)
base64
eventmachine (>= 1.0.0.beta.4)
em-synchrony (1.0.6)
eventmachine (>= 1.0.0.beta.1)
email_validator (2.2.4)
activemodel
erubi (1.12.0)
et-orbi (1.2.7)
tzinfo
eventmachine (1.2.7)
eventmachine_httpserver (0.2.1)
excon (0.81.0)
execjs (2.7.0)
factory_bot (6.2.0)
@@ -347,6 +375,7 @@ GEM
hashie (5.0.0)
highline (2.0.3)
htmlentities (4.3.4)
http_parser.rb (0.8.0)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
i18n-js (3.9.2)
@@ -420,6 +449,7 @@ GEM
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.7.0)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@@ -448,6 +478,7 @@ GEM
msgpack (1.7.2)
multi_json (1.15.0)
multi_xml (0.6.0)
mutex_m (0.3.0)
net-http (0.4.1)
uri
net-imap (0.4.10)
@@ -505,7 +536,7 @@ GEM
parallel (1.24.0)
paranoia (2.6.3)
activerecord (>= 5.1, < 7.2)
parser (3.3.2.0)
parser (3.3.8.0)
ast (~> 2.4.1)
racc
paypal-sdk-core (0.3.4)
@@ -527,13 +558,21 @@ GEM
psych (5.1.2)
stringio
public_suffix (5.0.5)
puffing-billy (4.0.2)
addressable (~> 2.5)
em-http-request (~> 1.1, >= 1.1.0)
em-synchrony
eventmachine (~> 1.2)
eventmachine_httpserver
http_parser.rb (~> 0.8.0)
multi_json
puma (6.5.0)
nio4r (~> 2.0)
query_count (1.1.1)
activerecord (>= 4.2)
railties (>= 4.2)
raabro (1.4.0)
racc (1.8.0)
racc (1.8.1)
rack (2.2.11)
rack-mini-profiler (2.3.4)
rack (>= 1.2.0)
@@ -555,20 +594,23 @@ GEM
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.7.0)
rails (7.0.8)
actioncable (= 7.0.8)
actionmailbox (= 7.0.8)
actionmailer (= 7.0.8)
actionpack (= 7.0.8)
actiontext (= 7.0.8)
actionview (= 7.0.8)
activejob (= 7.0.8)
activemodel (= 7.0.8)
activerecord (= 7.0.8)
activestorage (= 7.0.8)
activesupport (= 7.0.8)
rackup (1.0.1)
rack (< 3)
webrick
rails (7.1.5.1)
actioncable (= 7.1.5.1)
actionmailbox (= 7.1.5.1)
actionmailer (= 7.1.5.1)
actionpack (= 7.1.5.1)
actiontext (= 7.1.5.1)
actionview (= 7.1.5.1)
activejob (= 7.1.5.1)
activemodel (= 7.1.5.1)
activerecord (= 7.1.5.1)
activestorage (= 7.1.5.1)
activesupport (= 7.1.5.1)
bundler (>= 1.15.0)
railties (= 7.0.8)
railties (= 7.1.5.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -589,13 +631,14 @@ GEM
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
rails_safe_tasks (1.0.0)
railties (7.0.8)
actionpack (= 7.0.8)
activesupport (= 7.0.8)
method_source
railties (7.1.5.1)
actionpack (= 7.1.5.1)
activesupport (= 7.1.5.1)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.2.1)
ransack (4.1.1)
@@ -717,7 +760,7 @@ GEM
rubyzip (2.3.2)
rufus-scheduler (3.8.2)
fugit (~> 1.1, >= 1.1.6)
rugged (1.7.2)
rugged (1.9.0)
sanitize (6.1.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@@ -729,6 +772,7 @@ GEM
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sd_notify (0.1.1)
securerandom (0.4.1)
semantic_range (3.0.0)
shoulda-matchers (6.2.0)
activesupport (>= 5.2.0)
@@ -746,7 +790,6 @@ GEM
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
spreadsheet_architect (5.0.0)
caxlsx (>= 3.3.0, < 4)
@@ -808,11 +851,14 @@ GEM
turbo-rails (>= 1.3.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
undercover (0.6.3)
undercover (0.7.4)
base64
bigdecimal
imagen (>= 0.2.0)
rainbow (>= 2.1, < 4.0)
rugged (>= 0.27, < 1.8)
rugged (>= 0.27, < 1.10)
simplecov
simplecov_json_formatter
unicode-display_width (2.5.0)
uniform_notifier (1.16.0)
uri (0.13.0)
@@ -958,6 +1004,7 @@ DEPENDENCIES
pg (~> 1.2.3)
private_address_check
pry (~> 0.13.0)
puffing-billy
puma
query_count
rack-mini-profiler (< 3.0.0)
@@ -989,7 +1036,6 @@ DEPENDENCIES
sidekiq
sidekiq-scheduler
simplecov
simplecov-lcov
spreadsheet_architect
spring
spring-commands-rspec

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,11 +19,6 @@ module Spree
before_action :load_spree_api_key, only: [:index, :variant_overrides]
before_action :strip_new_properties, only: [:create, :update]
def index
@current_user = spree_current_user
@show_latest_import = params[:latest_import] || false
end
def show
session[:return_to] ||= request.referer
redirect_to( action: :edit )
@@ -65,28 +60,6 @@ module Spree
end
end
def bulk_update
product_set = product_set_from_params
product_set.collection.each { |p| authorize! :update, p }
if product_set.save
redirect_to main_app.bulk_products_api_v0_products_path(bulk_index_query)
elsif product_set.errors.present?
render json: { errors: product_set.errors }, status: :bad_request
else
render body: nil, status: :internal_server_error
end
end
def clone
@new = @product.duplicate
raise "Clone failed" unless @new.save
flash[:success] = t('.success')
redirect_to spree.admin_products_url
end
def group_buy_options
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
end

View File

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

View File

@@ -18,7 +18,7 @@ module Admin
enterprise.in?(spree_current_user.enterprises)
end
def enterprise_side_menu_items(enterprise)
def enterprise_side_menu_items(enterprise) # rubocop:disable Metrics/CyclomaticComplexity
is_shop = enterprise.sells != "none"
show_properties = !!enterprise.is_primary_producer
show_shipping_methods = can?(:manage_shipping_methods, enterprise) && is_shop
@@ -28,15 +28,18 @@ module Admin
show_connected_apps = can?(:manage_connected_apps, enterprise) &&
feature?(:connected_apps, spree_current_user, enterprise) &&
Spree::Config.connected_apps_enabled.present?
show_inventory_settings = feature?(:inventory, spree_current_user.enterprises) && is_shop
build_enterprise_side_menu_items(
is_shop:,
show_options = {
show_properties:,
show_shipping_methods:,
show_payment_methods:,
show_enterprise_fees:,
show_connected_apps:,
)
show_inventory_settings:,
}
build_enterprise_side_menu_items(is_shop:, show_options:)
end
def connected_apps_enabled
@@ -62,14 +65,7 @@ module Admin
private
def build_enterprise_side_menu_items(
is_shop:,
show_properties:,
show_shipping_methods:,
show_payment_methods:,
show_enterprise_fees:,
show_connected_apps:
)
def build_enterprise_side_menu_items(is_shop:, show_options: ) # rubocop:disable Metrics/MethodLength
[
{ name: 'primary_details', icon_class: "icon-home", show: true, selected: 'selected' },
{ name: 'address', icon_class: "icon-map-marker", show: true },
@@ -78,19 +74,24 @@ module Admin
{ name: 'about', icon_class: "icon-pencil", show: true, form_name: "about_us" },
{ name: 'business_details', icon_class: "icon-briefcase", show: true },
{ name: 'images', icon_class: "icon-picture", show: true },
{ name: 'properties', icon_class: "icon-tags", show: show_properties },
{ name: 'shipping_methods', icon_class: "icon-truck", show: show_shipping_methods },
{ name: 'payment_methods', icon_class: "icon-money", show: show_payment_methods },
{ name: 'enterprise_fees', icon_class: "icon-tasks", show: show_enterprise_fees },
{ name: 'properties', icon_class: "icon-tags", show: show_options[:show_properties] },
{ name: 'shipping_methods', icon_class: "icon-truck",
show: show_options[:show_shipping_methods] },
{ name: 'payment_methods', icon_class: "icon-money",
show: show_options[:show_payment_methods] },
{ name: 'enterprise_fees', icon_class: "icon-tasks",
show: show_options[:show_enterprise_fees] },
{ name: 'vouchers', icon_class: "icon-ticket", show: is_shop },
{ name: 'enterprise_permissions', icon_class: "icon-plug", show: true,
href: admin_enterprise_relationships_path },
{ name: 'inventory_settings', icon_class: "icon-list-ol", show: is_shop },
{ name: 'inventory_settings', icon_class: "icon-list-ol",
show: show_options[:show_inventory_settings] },
{ name: 'tag_rules', icon_class: "icon-random", show: is_shop },
{ name: 'shop_preferences', icon_class: "icon-shopping-cart", show: is_shop },
{ name: 'white_label', icon_class: "icon-leaf", show: true },
{ name: 'users', icon_class: "icon-user", show: true },
{ name: 'connected_apps', icon_class: "icon-puzzle-piece", show: show_connected_apps },
{ name: 'connected_apps', icon_class: "icon-puzzle-piece",
show: show_options[:show_connected_apps] },
]
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -54,15 +54,5 @@ Openfoodnetwork::Application.configure do
# Print deprecation notices to the stderr
# config.active_support.deprecation = :stderr
# Fail tests on deprecated code unless it's a known case to solve.
ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) do
allowed_warnings = [
# List strings here to allow matching deprecations.
]
unless allowed_warnings.any? { |pattern| message.match(pattern) }
ActiveSupport::Deprecation::DEFAULT_BEHAVIORS[:raise].call(message, callstack, deprecation_horizon, gem_name)
end
end
config.active_job.queue_adapter = :test
end

View File

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

View File

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

View File

@@ -3462,6 +3462,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
product_importer_products_save_error: did not save any products successfully
product_import_file_not_found_notice: 'File not found or could not be opened'
product_import_no_data_in_spreadsheet_notice: 'No data found in spreadsheet'
product_import_inventory_disable: Importing into inventories is not available
order_choosing_hub_notice: Your hub has been selected.
order_cycle_selecting_notice: Your order cycle has been selected.
adjustments_tax_rate_error: "^Please check that the tax rate for this adjustment is correct."

View File

@@ -1413,9 +1413,9 @@ en_CA:
Your Open Food Network account is connected to the Waterloo Food Directory.
Changes made to your OFN profile will update your Waterloo Food Directory listing when you click the 'Update listing' button. For any other changes please contact OFN Support at support@openfoodnetwork.ca.
link_label: "Update listing"
description: "Eligible producers can showcase their regenerative credentials, farming practices and more through a profile listing. Simplifying how buyers can find regenerative produce and connect with producers of interest."
link_label_html: "Learn more about Discover Regenerative <i class=\"icon-external-link\"></i>"
link_url: "https://about.openfoodnetwork.org.au/regen-produce-portal/"
description: "Eligible producers can showcase call to actions to Eat Local, Give Local, and/or Grow Local. This simplifies how producers can connect in the Waterloo region around food justice and sovereignty."
link_label_html: "Learn more about the Waterloo Food Directory <i class=\"icon-external-link\"></i>"
link_url: "https://waterlooregionfood.ca/"
vine:
title: "Voucher Integration Engine (VINE)"
tagline: "Allow redemption of VINE vouchers in your shopfront."
@@ -3315,6 +3315,7 @@ en_CA:
product_importer_products_save_error: did not save any products successfully
product_import_file_not_found_notice: 'File not found or could not be opened'
product_import_no_data_in_spreadsheet_notice: 'No data found in spreadsheet'
product_import_inventory_disable: Importing into inventories is not available
order_choosing_hub_notice: Your hub has been selected.
order_cycle_selecting_notice: Your order cycle has been selected.
adjustments_tax_rate_error: "^Please check that the tax rate for this adjustment is correct."

View File

@@ -3315,6 +3315,7 @@ en_FR:
product_importer_products_save_error: did not save any products successfully
product_import_file_not_found_notice: 'File not found or could not be opened'
product_import_no_data_in_spreadsheet_notice: 'No data found in spreadsheet'
product_import_inventory_disable: Importing into inventories is not available
order_choosing_hub_notice: Your hub has been selected.
order_cycle_selecting_notice: Your order cycle has been selected.
adjustments_tax_rate_error: "^Please check that the tax rate for this adjustment is correct."

View File

@@ -2081,6 +2081,8 @@ en_GB:
select_a_shipping_method: Select a shipping method
select_a_payment_method: Select a payment method
no_shipping_methods_available: Checkout is not possible due to absence of shipping options. Please contact the shop owner.
voucher_code_blank: Please enter a valid voucher code
voucher_code_not_found: invalid. It either doesn't exist or has been redeemed fully
add_voucher_error: There was an error while adding the voucher
create_voucher_error: "There was an error while creating the voucher: %{error}"
voucher_redeeming_error: There was an error while trying to redeem your voucher

View File

@@ -141,8 +141,10 @@ es:
message_html: "<b>Por favor, inténtelo de nuevo</b> <p>Esto puede ser un problema temporal. Haga clic en el botón Atrás para volver a la pantalla anterior o vuelva a <a href='/'>Inicio</a> e inténtelo de nuevo</p>. <b>Póngase en contacto con el servicio de asistencia</b> <p>Si el problema persiste o es urgente, comuníquenoslo. Encuentre nuestros datos de contacto en la <a href='https://openfoodnetwork.org/ofn-local/' target='blank'>página local de Open Food Network</a>. </p><p>Nos será de gran ayuda que nos facilite todos los detalles posibles sobre el motivo de la página que falta.</p>"
internal_server_error:
title: "Lo sentimos pero algo fue mal (500)"
message_html: "<b>Por favor, inténtalo de nuevo.</b><p> Puede que se trate de un problema temporal. Haz clic en el botón «Atrás» para volver a la pantalla anterior o vuelve a la <a href='/''>página de inicio</a> e inténtalo de nuevo.</p><b> Estamos en ello.</b> <p>Si ya has tenido este problema anteriormente, es probable que ya lo conozcamos y estemos trabajando para solucionarlo. Registramos todos los errores que se producen.</p><b> Póngase en contacto con el servicio de asistencia</b><p> Si el problema persiste o es urgente, infórmenos al respecto. Encontrará nuestros datos de contacto en la página <a href='https://openfoodnetwork.org/ofn-local/' target='blank'>global de Open Food Network Local.</a></p><p> Nos será de gran ayuda que nos facilite tantos detalles como sea posible sobre lo que estaba haciendo cuando se produjo el error.</p>"
unprocessable_entity:
title: "El cambio deseado ha sido rechazado (422)"
message_html: "<p>El cambio que deseabas ha sido rechazado. Quizás hayas intentado cambiar algo a lo que no tienes acceso.<br><h3><a href='/' > Vuelve a la página de inicio.</a></h3></p>"
general_error:
message: "Lo sentimos, pero algo ha ido mal.\n\nPuede que se trate de un problema temporal, así que inténtalo de nuevo o recarga la página.\nRegistramos todos los errores y es posible que estemos trabajando en una solución.\nSi el problema persiste o es urgente, póngase en contacto con nosotras."
unauthorized:
@@ -277,6 +279,7 @@ es:
send_instructions: "Recibirás un correo electrónico con instrucciones sobre cómo confirmar su cuenta en unos minutos."
oidc:
failure: "No se ha podido iniciar sesión: %{error}"
record_not_unique: "%{uid} ya está asociado a otra cuenta."
home_page_alert_html: "HTML de alerta de página de inicio"
hub_signup_case_studies_html: "HTML casos de estudio de registro de hub"
hub_signup_detail_html: "HTML detalle de registro de hub"
@@ -566,6 +569,8 @@ es:
selected_categories: "%{count} categorías seleccionadas"
producers:
title: Productoras
selected_producers: "%{count} productores seleccionados"
per_page: "%{count} elementos por página"
colums: Columnas
columns:
image: Imagen
@@ -710,6 +715,7 @@ es:
status: Estado
ok: Ok
instance_secret_key: Clave secreta de instancia
instance_publishable_key: Clave publicable de instancia
account_id: Account ID
business_name: Nombre de la Organización
charges_enabled: Cargos habilitados
@@ -728,6 +734,7 @@ es:
connected_app_settings:
edit:
title: "Ajustes de la aplicación conectada"
info_html: "Las aplicaciones habilitadas aparecerán en Configuración organización > Aplicaciones conectadas."
enabled_legend: "Aplicaciones conectadas habilitadas"
connected_apps_enabled:
discover_regen: Descubrir el Portal Regenerativo
@@ -755,6 +762,8 @@ es:
guest_label: "Pedido hecho como invitada"
credit_owed: "Crédito debido"
balance_due: "Saldo adeudado"
destroy:
has_associated_subscriptions: "Eliminación fallida: este consumidor tiene suscripciones activas. Cancélelas primero."
column_preferences:
bulk_update:
success: "Preferencias de columna guardadas"
@@ -775,6 +784,16 @@ es:
absent_variant:
reset: "Restablecer stock"
index:
title: "Catálogo de productos DFC"
catalog_url: "%{count} productos para importar de: %{catalog_url}"
absent_products:
one: |
Un productos no ya no esta en este catálogo
Se le marcara como no disponible poniendo su stock a cero.
other: |
%{count} productos ya no están en el catálogo.
Se marcarán como no disponibles restableciendo el stock a cero.
enterprise: "Importar a la empresa: %{enterprise_name}"
select_all: "Seleccionar/deseleccionar todo"
update: Actualizar
new: Nuevo
@@ -784,6 +803,10 @@ es:
other: "%{count} seleccionados"
import: Importar
invalid_url: Esta URL de catálogo no es correcta
import:
title: "Importación del catálogo de productos DFC"
imported_products: "Productos importados: %{count}"
reset_products: "Restablecimiento del stock para productos ausentes: %{count}"
enterprise_fees:
index:
title: "Comisiones de la Organización"
@@ -854,10 +877,12 @@ es:
delete_modal:
delete_product_modal:
heading: "Borrar producto"
prompt: "Esto lo eliminará permanentemente de tu lista."
confirmation_text: "Borrar producto"
cancellation_text: "Mantener producto"
delete_variant_modal:
heading: "Borrar variante"
prompt: "Esto lo eliminará permanentemente de tu lista."
confirmation_text: "Borrar variante"
cancellation_text: "Mantener variante"
filters:
@@ -878,13 +903,30 @@ es:
search: Buscar
sort:
pagination:
products_total_html:
one: "<strong>%{count}producto</strong> encontrado para tus criterios de búsqueda. Mostrando %{from} de %{to}"
many: "<strong> %{count}productos </strong>encontrados para tus criterios de búsqueda. Mostrando %{from} de %{to}"
other: "<strong>%{count}productos</strong> encontrados para tus criterios de búsqueda. Mostrando %{from} de %{to}"
per_page:
show: Mostrar
per_page: "%{num} por página"
clear_search: Limpiar la búsqueda
no_products:
no_products_found: No se encontraron productos
import_products: Importar varios productos
no_products_found_for_search: No se han encontrado productos que coincidan con tus criterios de búsqueda.
table:
changed_summary:
one: "%{count} producto modificado."
other: "%{count} productos modificados."
error_summary:
saved:
one: "%{count} producto ha sido guardado correctamente, pero"
other: "%{count} productos han sido guardados correctamente, pero"
invalid:
one: "No se ha podido guardar %{count} producto. Revise los errores e inténtelo de nuevo."
many: "No se han podido guardar %{count} productos. Revise los errores e inténtelo de nuevo."
other: "No se han podido guardar %{count} productos. Revise los errores e inténtelo de nuevo."
reset: Desechar Cambios
save: Guardar Cambios
product_variant_row:
@@ -899,6 +941,7 @@ es:
error: No se puede eliminar la variante
variant_row:
none_tax_category: Ninguno
search_for_tax_categories: "Buscar categorías fiscales"
category_field_name: "Categoría"
tax_category_field_name: "Categoría de impuestos"
producer_field_name: "Productora"
@@ -950,6 +993,7 @@ es:
shipping_categories: Categorías de envío
dfc_import_form:
title: "Importar del catálogo DFC"
enterprise: "Crear productos para organización"
catalog_url: "URL del catálogo DFC"
preview: Vista previa
import:
@@ -971,6 +1015,7 @@ es:
no_name: Sin nombre
blank_enterprise: Algunos productos no tienen una organización definida.
reset_absent?: Restablecer productos ausentes
reset_absent_tip: Establecer el stock en cero para todos los productos existentes que no estén presentes en el archivo.
overwrite_all: Sobrescribir todo
overwrite_empty: Sobrescribir si está vacío
default_stock: Establecer nivel de existencias
@@ -1191,6 +1236,7 @@ es:
own: Propio
sells: Vende
sells_tip: "Ninguno - La organización no vende directamente a los consumidores.<br />Popios - La organización vende sus propios productos a los consumidores.<br />Cualquiera - La organización vende sus propios productos o de otros.<br />"
external_billing_id_tip: "Este es el ID utilizado por el sistema de facturación externo para identificar esta organización."
visible_in_search: ¿Visible en la búsqueda?
visible: Pública
not_visible: Oculto
@@ -1234,8 +1280,12 @@ es:
enable_subscriptions_true: "Habilitado"
customer_names_in_reports: "Nombres de consumidoras en informes"
customer_names_tip: "Permita que sus proveedores vean los nombres de sus consumidoras en los informes"
producers_to_edit_orders: "Posibilidad de que los productores editen los pedidos."
producers_to_edit_orders_tip: "Permita a sus proveedores ver los pedidos que contienen sus productos y editar la cantidad y el peso solo de sus propios productos."
customer_names_false: "Deshabilitado"
customer_names_true: "Habilitado"
customer_contacts_in_reports: "Datos de contacto del cliente en los informes"
customer_contacts_tip: "Permita que sus proveedores vean los correos electrónicos y números de teléfono de sus consumidoras en los informes."
customer_contacts_false: "Deshabilitado"
customer_contacts_true: "Habilitado"
producers_edit_orders_false: "Deshabilitado"

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