Compare commits

...

153 Commits

Author SHA1 Message Date
Pau Perez
a6ba956355 Update all locales with the latest Transifex translations 2019-11-07 12:47:10 +01:00
Luis Ramos
76cffe4c7f Merge pull request #4301 from Matt-Yorkley/ruby-2.2.10
Use Ruby 2.2.10
2019-11-07 10:17:54 +00:00
Pau Pérez Fabregat
26477a8d4b Merge pull request #4421 from openfoodfoundation/transifex
Transifex
2019-11-06 18:04:23 +01:00
Luis Ramos
52ab6c52bf Merge pull request #4057 from luisramos0/remove_spree_api_2
Remove dependency to spree_api - step 2 - routes and views
2019-11-06 13:23:32 +00:00
Luis Ramos
413e93fe40 Merge pull request #4431 from Matt-Yorkley/fix_migration_clash
Fix migration mismatch
2019-11-06 09:49:15 +00:00
Matt-Yorkley
9e1b2eb4ca Fix migration mismatch
It looks like this was probably changed whilst resolving a merge conflict somewhere. The number doesn't match the last migration file, and it's breaking the ofn-install CI build (as well as migrations on fresh servers).
2019-11-05 22:58:14 +00:00
Luis Ramos
1ceae6cf8d Merge pull request #4225 from HugsDaniel/4106-edit-product-missing-translations
Add missing translations on product edit page
2019-11-05 19:30:08 +00:00
Transifex-Openfoodnetwork
5183d93601 Updating translations for config/locales/en_DE.yml 2019-11-05 23:45:05 +11:00
Luis Ramos
247854b7fe Merge pull request #4423 from openfoodfoundation/dependabot/bundler/i18n-js-3.4.1
Bump i18n-js from 3.4.0 to 3.4.1
2019-11-04 11:47:13 +00:00
dependabot-preview[bot]
85bd803785 Bump i18n-js from 3.4.0 to 3.4.1
Bumps [i18n-js](https://github.com/fnando/i18n-js) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/fnando/i18n-js/releases)
- [Changelog](https://github.com/fnando/i18n-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/fnando/i18n-js/compare/v3.4.0...v3.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-01 19:39:31 +00:00
Transifex-Openfoodnetwork
c6543edc85 Updating translations for config/locales/en_GB.yml 2019-11-02 01:42:11 +11:00
Transifex-Openfoodnetwork
a91d0973d4 Updating translations for config/locales/en_GB.yml 2019-11-02 01:39:02 +11:00
Kristina Lim
61c581ae52 Update all locales with the latest Transifex translations 2019-11-01 20:38:13 +08:00
Luis Ramos
db8e2891d4 Merge pull request #4417 from Matt-Yorkley/shopfront_patchination
Ensure variants returned in #variant_relation are DISTINCT
2019-11-01 12:16:01 +00:00
Luis Ramos
a947d8df6c Merge pull request #4397 from luisramos0/delete_option_types_page
Remove option types menu entry
2019-11-01 12:14:59 +00:00
Maikel
818d41ffac Merge pull request #4418 from AndrewCleve/readme-grammar-fix
Improve readability of README.md
2019-11-01 16:48:12 +11:00
Maikel
14f67053c8 Merge pull request #4400 from coopdevs/task-to-remove-enterprise-limit
Task to remove limit of ent. that can be created
2019-11-01 15:57:12 +11:00
luisramos0
76e32b126f Add a few more missing translation keys for product images and variants display as and display name 2019-11-01 10:40:02 +11:00
luisramos0
c234cfb890 Add missing translation in the new variant page 2019-11-01 10:40:02 +11:00
luisramos0
38bda1697d Add missing translations in the product variants list page 2019-11-01 10:40:02 +11:00
luisramos0
5fb9ebc594 Add missing translations for variants edit page 2019-11-01 10:40:02 +11:00
luisramos0
acbd4b076d Move product page tabs to a separate translations key namespace 2019-11-01 10:40:01 +11:00
luisramos0
123f7aa68e Use root translation keys for basic actions 2019-11-01 10:39:09 +11:00
luisramos0
2c4af84a81 Move product seo translation keys to specific namespace 2019-11-01 10:39:09 +11:00
luisramos0
34ed16ff30 Add missing translation keys to product properties list 2019-11-01 10:39:09 +11:00
Hugo Daniel
0b20b80219 Add missing translations on product edit page 2019-11-01 10:38:06 +11:00
Luis Ramos
bef745378e Merge pull request #4395 from luisramos0/delete_prototypes
Delete prototypes tables and all references in pages
2019-10-31 20:06:38 +00:00
luisramos0
a6cb5903d6 Delete prototypes tables and all references in pages 2019-10-31 13:23:42 +00:00
Luis Ramos
d1b36aded0 Merge pull request #4359 from daningenthron/daningenthron/required-fields-new-order-cycle
Add asterisks to required fields in New Order Cycle form
2019-10-31 12:50:48 +00:00
Luis Ramos
9c3c74aa93 Merge pull request #4394 from luisramos0/fix_product_sub_menu
Merge duplicated product_sub_menu partials
2019-10-31 12:48:27 +00:00
Matt-Yorkley
67a5a1cdc2 Fix incorrectly ordered entries with duplicate product names in OC and custom taxon ordering applied 2019-10-31 12:14:14 +00:00
Matt-Yorkley
9723e2cd49 Add failing spec for taxon ordering issue 2019-10-31 12:07:16 +00:00
Pau Pérez Fabregat
4af014df6b Merge pull request #4342 from JacksonBates/edit-button-new-window
makes edit button action open a new tab
2019-10-31 11:45:11 +01:00
Pau Pérez Fabregat
be6c64db75 Merge pull request #4354 from agustinariq/3832-WYSIWYG-highlight-links
#3832 - Added color to links in WYSIWYG editor
2019-10-31 11:44:11 +01:00
AndrewCleve
1aebc30128 Improve readability of README.md
Made minor changes to grammar to improve consistency and readability.
2019-10-30 19:43:38 -07:00
Matt-Yorkley
f8209ac7d5 Ensure results in #products_relation are DISTINCT 2019-10-30 19:04:28 +00:00
Matt-Yorkley
6d50176e6b Ensure results in #variants_relation are DISTINCT 2019-10-30 17:13:52 +00:00
Pau Pérez Fabregat
d6d2c19dc7 Merge pull request #4398 from Matt-Yorkley/db_indexes
Add order_cycle_id and distributor_id indexes to spree_orders table
2019-10-30 16:36:23 +01:00
Pau Pérez Fabregat
f897478736 Merge pull request #4402 from coopdevs/task-to-remove-unused-enterprise
Task to remove an unused enterprise
2019-10-30 16:34:28 +01:00
Pau Pérez Fabregat
064b86da5d Merge pull request #4407 from luisramos0/fix_sample_data
Add tax category to sample products
2019-10-30 15:37:53 +01:00
Pau Pérez Fabregat
f7b58300f9 Merge pull request #4362 from openfoodfoundation/dependabot/bundler/activerecord-import-1.0.3
Bump activerecord-import from 1.0.2 to 1.0.3
2019-10-30 15:13:47 +01:00
luisramos0
0fe4edfbf5 Make product_tag_rules_filterer_spec a bit more flexible and not test for the order of the elements 2019-10-30 11:06:25 +00:00
luisramos0
f45eb35eb1 Make user_registrations_controller_spec keep I18n.locale as it was before the spec, so that other specs wont fail 2019-10-30 10:43:46 +00:00
Luis Ramos
1a0e99dce2 Merge pull request #4414 from openfoodfoundation/dependabot/bundler/fuubar-2.5.0
Bump fuubar from 2.4.1 to 2.5.0
2019-10-30 10:12:19 +00:00
dependabot-preview[bot]
d7caf91de1 Bump fuubar from 2.4.1 to 2.5.0
Bumps [fuubar](https://github.com/thekompanee/fuubar) from 2.4.1 to 2.5.0.
- [Release notes](https://github.com/thekompanee/fuubar/releases)
- [Changelog](https://github.com/thekompanee/fuubar/blob/master/CHANGELOG.md)
- [Commits](https://github.com/thekompanee/fuubar/compare/releases/v2.4.1...releases/v2.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-29 19:30:45 +00:00
Luis Ramos
1e2ab27cda Merge pull request #4327 from kristinalim/feature/4315-prefill_dates_in_orders_and_fulfillment_report
4315 Set default date range for Orders and Fulfillment report
2019-10-29 11:11:59 +00:00
Luis Ramos
3e7bd7dc19 Merge pull request #4376 from luisramos0/ghosts
Remove product from Order Cycles if product supplier changes
2019-10-28 21:17:48 +00:00
luisramos0
c60261a847 Make taxonomies edit page use api route instead of spree_api route that is now gone. 2019-10-28 16:01:23 +00:00
luisramos0
11fea650d6 Remove inexistent routes api/enterprises/managed and api/enterprises/acessible
The :managed endpoint was delete here 1d92d6cc33
2019-10-28 16:01:23 +00:00
luisramos0
92f1fa3b52 Remove dead route api/orders/managed 2019-10-28 16:01:23 +00:00
luisramos0
ad52022927 Add feature spec to cover api api generation in admin user edit form 2019-10-28 16:01:22 +00:00
luisramos0
9cffe48c70 Do not use Spree.t in spree admin users 2019-10-28 16:01:22 +00:00
luisramos0
d006ded439 Convert spree admin users api fields partial from erb to haml 2019-10-28 16:01:22 +00:00
luisramos0
b2e5ff46a8 Bring missing admin user edit form api_fields partial from spree_api and insert into the form (there was a deface override in spree_api injecting it before) 2019-10-28 16:01:22 +00:00
luisramos0
f46e0a2a31 Remove outdated comment, action managed has already been removed 2019-10-28 16:01:22 +00:00
luisramos0
dd66df6379 Remove dead routes api/order_cycles, these endpoints were removed in PR 4059 because they were unused, implemented in rabl and not correctly named for the future 2019-10-28 16:01:22 +00:00
luisramos0
d7b9dc1190 Fix some rubocop issues 2019-10-28 16:00:36 +00:00
luisramos0
5b6efaf687 Delete now unused rabl template authorise_api 2019-10-28 16:00:36 +00:00
luisramos0
8aab9bacbe Delete now irrelevant authorize_api endpoint and logic
OFN API is now authenticating all users, if no session and no key is provided an anonymous user will be created so that user can access public endpoints, authorization is then done at each individual endpoint. This makes this spree api auth call irrelevant
2019-10-28 16:00:36 +00:00
luisramos0
66fdbe4379 Remove spree_api dependency. Spree_api will keep being a dependency until we remove spree_backend as a dependency but now ofn works without spree_api 2019-10-28 16:00:36 +00:00
luisramos0
abcc22c34b Merge Spree::Api::BaseController with Api::BaseController. All api controllers inherit from Api::BaseController now. We can probably simplify this controller even more now 2019-10-28 16:00:36 +00:00
luisramos0
0d34b607c3 Move spree/api/base_controller#find_product to api/product_controller where it is used exclusively
Also, product_scope stops being an override
2019-10-28 16:00:36 +00:00
luisramos0
f840179573 Remove .json from Spree.routes.orders_api. This path is only used to compose the order shipments path and thus cannot have the .json 2019-10-28 16:00:36 +00:00
luisramos0
b5a521476b Remove unused method from api/base_controller 2019-10-28 16:00:36 +00:00
luisramos0
7c64777a50 Remove requires_authentication check from api/base_controller. OFN api does not require auth, it always generates an anonymous user for public endpoints 2019-10-28 16:00:36 +00:00
luisramos0
c98b4b276b Adapt spree/admin/shared/_routes.html.erb to new location of the api/taxons routes AND move spree/api/orders route to ofn api/orders route 2019-10-28 16:00:36 +00:00
luisramos0
f1138709aa Add spree_api translation keys, mostly from api standard errors and api fields in admin/users 2019-10-28 16:00:36 +00:00
luisramos0
b29983ac60 Add AMS versions of the error responses in api/base_controller and cover not_found case with a unit test 2019-10-28 16:00:36 +00:00
luisramos0
91188c5724 Adapt api/base_controller_spec from spree/api/base_controller_spec 2019-10-28 16:00:36 +00:00
luisramos0
bf291ec318 Move spree/api/base_controller_spec to api/base_controller_spec 2019-10-28 16:00:36 +00:00
luisramos0
5846593637 Remove dependency to spree_api rabl responders, we should only use AMS from now on 2019-10-28 16:00:36 +00:00
Luis Ramos
688dad2334 Merge pull request #4384 from chaserx/4378_display_customer_name_on_orders_table
adds the full_name of the customer ordering beneath email
2019-10-28 15:52:54 +00:00
Luis Ramos
6d419d60ae Merge pull request #4405 from openfoodfoundation/dependabot/bundler/bugsnag-6.12.2
Bump bugsnag from 6.12.1 to 6.12.2
2019-10-28 11:37:30 +00:00
luisramos0
0dc8ae1561 Merging find_product and find_variant into one single method
This fixes rubocop issue, class has too many lines
2019-10-27 20:09:42 +00:00
luisramos0
f396f6bebd Add tax category to sample products 2019-10-27 19:59:35 +00:00
luisramos0
07fcc8f361 Refactor ExchangeVariantDeleter.new.delete out of update_product_only_attributes into correct place update_product
Also extracted find_product from update_attributes and find_variant out of create_or_update_variant to make code simpler
2019-10-27 19:13:57 +00:00
Pau Pérez Fabregat
237cd5438b Merge pull request #4373 from openfoodfoundation/dependabot/bundler/i18n-js-3.4.0
Bump i18n-js from 3.3.0 to 3.4.0
2019-10-25 13:47:25 +02:00
Pau Pérez Fabregat
4e366d0f2e Merge pull request #4390 from Matt-Yorkley/3831_flaky_spec
Fix flaky product import spec
2019-10-25 13:42:54 +02:00
dependabot-preview[bot]
fb3af77d0b Bump bugsnag from 6.12.1 to 6.12.2
Bumps [bugsnag](https://github.com/bugsnag/bugsnag-ruby) from 6.12.1 to 6.12.2.
- [Release notes](https://github.com/bugsnag/bugsnag-ruby/releases)
- [Changelog](https://github.com/bugsnag/bugsnag-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bugsnag/bugsnag-ruby/compare/v6.12.1...v6.12.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-24 19:19:06 +00:00
Luis Ramos
1fa6e4bea8 Merge pull request #4385 from chaserx/4304_fixes_missing_translation
adds missing :spree to i18n scopes
2019-10-24 17:06:50 +01:00
Luis Ramos
498a2b7462 Merge pull request #4386 from andrewjprice/missing-translations-inventory-page
Missing translations inventory page
2019-10-24 17:06:43 +01:00
Pau Perez
80edfe469c Task to remove limit of ent. that can be created
It does so by updating a user's enterprise_limit attribute to the
maximum integer the database supports.

This is used at least in Katuma to remove the limitation of the number
of enterprises a user can create. This is the agreement the community
reached for the pricing plans.

Eventually, this logic could be triggered with a button from the UI but
for now this is for internal usage only.
2019-10-24 15:53:43 +02:00
Pau Perez
b2616d317f Task to remove an unused enterprise
Note this task is still rather naive and only covers the simple case
where an enterprise was created but never used and thus, does not have
any associated entities like orders.

This is enough for the case I have at hand where a hub's manager created
an enterprise while he wanted to create a user account #ux. He ended up
with an enterprise named after him and now he asked us to clean that up.
2019-10-24 15:23:54 +02:00
Matt-Yorkley
0ac0bdc381 Update all locales with the latest Transifex translations 2019-10-23 21:20:11 +01:00
Matt-Yorkley
1a756cbc6b Merge pull request #4387 from openfoodfoundation/transifex
Transifex
2019-10-23 21:17:20 +01:00
Matt-Yorkley
34466c8218 Add order_cycle_id and distributor_id indexes to spree_orders table 2019-10-23 18:29:34 +01:00
Matt-Yorkley
00e869f40c Merge pull request #4393 from mkllnk/4392-rescue-network-fails
Avoid asking the cloud if an image exists
2019-10-23 16:55:49 +01:00
luisramos0
42cd9a5152 Remove menu entry option types because this page is not really usable
We keep the underlying data structures because option types and option values are used internally to keep units in OFN: weight, volume and items, but otherwise, custom option types are no longer possible in OFN
2019-10-23 12:57:47 +01:00
luisramos0
cc342387cc Make all calls to content_for use dash not equals when setting content_for 2019-10-23 12:42:29 +01:00
luisramos0
6715f872e3 Merge duplicated product_sub_menu partials and make all references point to the one located in the spree folder (we keep this one because we still have some views in spree backend referencing this partial 2019-10-23 12:26:18 +01:00
Transifex-Openfoodnetwork
e849c4867b Updating translations for config/locales/nb.yml 2019-10-23 18:58:07 +11:00
Maikel Linke
ada34d27ca Avoid asking the cloud if an image exists
https://github.com/openfoodfoundation/openfoodnetwork/issues/4392

Amazon's DNS is failing at the moment and some users can't access the
admin panel because of this error.

While `exists?` asks the storage server if the file is actually there,
`file?` just checks if we have the file name stored in the database
and the file should be there. It's much faster and less error prone.
2019-10-23 11:36:20 +11:00
Dan Ingenthron
40c329ba68 Distinguish between simple create form and full interface in OC feature spec 2019-10-22 17:41:38 -05:00
Dan Ingenthron
3c7af90dfa Revert I18n change and reflect correct required fields on OC form 2019-10-22 17:41:38 -05:00
Dan Ingenthron
241e581779 Add required fields to order cycle spec 2019-10-22 17:41:38 -05:00
Dan Ingenthron
85dede84cc Update order cycle forms & en.yml to include asterisks 2019-10-22 17:41:37 -05:00
Transifex-Openfoodnetwork
0288dfc992 Updating translations for config/locales/ar.yml 2019-10-23 07:18:46 +11:00
luisramos0
3372339907 Fix default stock translation by using t filter instead of t function 2019-10-22 17:29:04 +01:00
Matt-Yorkley
f7bb609546 Fix flaky product import spec 2019-10-22 12:02:11 +01:00
Luis Ramos
02c0b89fa0 Merge pull request #4345 from Matt-Yorkley/shopfront_pagination
Shopfront pagination
2019-10-21 16:02:12 +01:00
luisramos0
f57c9d4a25 Fix rubocop issue in product_set.update_product_only_attributes: method has too many lines 2019-10-21 15:20:53 +01:00
luisramos0
783c3c9e90 Add spec to product set to cover case where product and variants attributes are both provided and the product supplier is not, in that case, ExchangeVariantDeleter would not execute and update_product_only_attributes would return nil cancelling update_product_variants from being executed. Now, update_product_only_attributes always returns true if product.save suceeeds, no matter what ExchangeVariantDeleter returns 2019-10-21 14:45:27 +01:00
luisramos0
44753d0320 Add spec coverage for case in product_set where variants_attributes are used 2019-10-21 13:27:21 +01:00
luisramos0
dd7d5803ba Add new context to spec so that some basic setup can be shared with new specs that will be added, it's mostly indentation here 2019-10-21 13:01:39 +01:00
Luis Ramos
c9e23154d8 Merge pull request #4352 from tkusuki/fix-translation-in-enterprise-welcome-email
Add missing translation in non English enterprise welcome email
2019-10-21 11:41:53 +01:00
Rachel Arnould
279b633513 Add welcome board to the contributing page 2019-10-21 12:19:01 +02:00
Transifex-Openfoodnetwork
9d0ac79983 Updating translations for config/locales/pt_BR.yml 2019-10-21 11:29:14 +11:00
Andrew
deb17f47a7 Add default_stock to en.yml and _products_variants 2019-10-19 16:33:31 -07:00
Chase Southard
61ee0f04a6 adds missing :spree to i18n scopes 2019-10-19 13:55:11 -04:00
Chase Southard
353804a3fa adds the full_name of the customer ordering beneath email 2019-10-19 12:32:59 -04:00
Andrew
598426a5e9 Add Save Changes button translation 2019-10-18 22:18:54 -07:00
Matt-Yorkley
7b0c55e15a Ensure producer properties are included in properties results 2019-10-18 21:15:13 +01:00
Matt-Yorkley
cbe2477d04 Fix property filters and improve test coverage 2019-10-18 21:15:13 +01:00
Matt-Yorkley
c730958fe4 Restrict search params passed to ProductsRenderer 2019-10-18 21:15:13 +01:00
Matt-Yorkley
37e5e1923c Improve filter buttons UX 2019-10-18 21:15:13 +01:00
Matt-Yorkley
542c1bf684 #slice :params in controller and rename to :args in service context 2019-10-18 21:15:13 +01:00
Matt-Yorkley
06c896b93b Add spec for Api::ProductSerializer 2019-10-18 21:15:13 +01:00
Matt-Yorkley
6433d69d02 Add comments on scoping ProductsRenderer results 2019-10-18 21:15:13 +01:00
Matt-Yorkley
bf8c632fce Refactor ProductsCtrl request params 2019-10-18 21:15:13 +01:00
Matt-Yorkley
b3c89a9d6c Move OpenFoodNetwork::ProductsRenderer (lib) to ProductsRenderer (service) and refactor 2019-10-18 21:15:13 +01:00
Matt-Yorkley
d45403f1d4 Add specs for Api::OrderCyclesController 2019-10-18 21:15:13 +01:00
Matt-Yorkley
f2affe80cd Reduce assignment branching and complexity for ProductsRenderer#products 2019-10-18 21:15:13 +01:00
Matt-Yorkley
573a69477f Fix filters not updating on OC change 2019-10-18 21:15:13 +01:00
Matt-Yorkley
c6ce516129 Fix prices not updating with new exchange fees when changing OC 2019-10-18 21:15:13 +01:00
Matt-Yorkley
2539b84b33 Fix product.meta_keywords not searchable 2019-10-18 21:15:13 +01:00
Matt-Yorkley
dd6d0d25da Fix problematic feature specs 2019-10-18 21:15:13 +01:00
Matt-Yorkley
c54cff10d4 Adjust API endpoint params 2019-10-18 21:15:13 +01:00
Matt-Yorkley
ab330e882e Remove product cache 2019-10-18 21:15:11 +01:00
luisramos0
a50ae3f8ce Clarify the API of product_set class by making all other methods to private 2019-10-18 10:37:23 +01:00
luisramos0
9f3b4100c3 Improve code by incorporating code review feedback 2019-10-18 10:26:03 +01:00
luisramos0
b625ea0c61 Extract to class ExchangeVariantDeleter 2019-10-17 19:13:45 +01:00
luisramos0
8857404ddf Remove product variants from all Order Cycles if supplier is changed 2019-10-17 19:12:24 +01:00
luisramos0
49f98422fd Remove product from Order Cycles if supplier changes as with a new supplier the rules/permissions to add a product to an Order Cycle may be different 2019-10-17 17:47:15 +01:00
Jackson Bates
4d49dc3689 adds line breaks to methods for readability 2019-10-16 23:01:38 +11:00
Jackson Bates
8d30dc997f adds better description to specs and refactors editProductUrl and confirm_unsaved_changes 2019-10-16 21:55:52 +11:00
luisramos0
cf40bfa58e Simbolize keys in opts hash in xero report so that opts access works again 2019-10-15 18:05:40 +01:00
Matt-Yorkley
8e03f402b1 Use Ruby 2.2.10 2019-10-15 14:59:24 +01:00
dependabot-preview[bot]
5a84a3688b Bump i18n-js from 3.3.0 to 3.4.0
Bumps [i18n-js](https://github.com/fnando/i18n-js) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/fnando/i18n-js/releases)
- [Changelog](https://github.com/fnando/i18n-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/fnando/i18n-js/compare/v3.3.0...v3.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-15 04:57:00 +00:00
dependabot-preview[bot]
614dc5d255 Bump activerecord-import from 1.0.2 to 1.0.3
Bumps [activerecord-import](https://github.com/zdennis/activerecord-import) from 1.0.2 to 1.0.3.
- [Release notes](https://github.com/zdennis/activerecord-import/releases)
- [Changelog](https://github.com/zdennis/activerecord-import/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zdennis/activerecord-import/compare/v1.0.2...v1.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-09 21:47:12 +00:00
Matt-Yorkley
da7456e6e0 Remove old shop/products route, action, and spec 2019-10-09 17:27:00 +01:00
Matt-Yorkley
f134cd9473 Extract tag_rule filtering into separate service 2019-10-09 17:26:58 +01:00
Matt-Yorkley
e96252f2ed Add tag_rules logic to main query before pagination 2019-10-08 10:11:24 +01:00
Matt-Yorkley
06e1f56ae9 Extract filter list fetching into a separate endpoint 2019-10-08 10:11:24 +01:00
Matt-Yorkley
fe0de98821 Add pagination in Angular and views 2019-10-08 10:11:22 +01:00
Thais Kusuki
d997b8f5ee Change translations from enterprise_mailer to lazy lookup 2019-10-07 23:29:11 -03:00
agustina
cf3f321632 Added color to links in WYSIWYG editor 2019-10-07 12:15:37 -03:00
Thais Kusuki
797a3ad091 Add missing translation in non English enterprise welcome email 2019-10-05 12:50:12 -03:00
Matt-Yorkley
01d1e8243c Add pagination to ProductsRenderer 2019-10-04 10:38:43 +01:00
Matt-Yorkley
a1a5c3b7fe Add new Angular OrderCycleResource 2019-10-04 10:38:41 +01:00
Matt-Yorkley
bc826f73a1 Add temporary placeholder for API endpoint 2019-10-03 18:11:40 +01:00
Jackson Bates
7c264af0c2 updates specs for new edit button behaviour 2019-10-03 22:32:43 +10:00
Jackson Bates
4c4bdd78e7 makes edit button action open a new tab 2019-10-01 22:13:28 +10:00
Kristina Lim
bb56e9a5b9 Set default date range for Orders and Fulfillment report 2019-09-28 02:39:41 +08:00
187 changed files with 8529 additions and 3417 deletions

View File

@@ -139,7 +139,6 @@ Metrics/LineLength:
- lib/open_food_network/payments_report.rb
- lib/open_food_network/permalink_generator.rb
- lib/open_food_network/products_cache.rb
- lib/open_food_network/products_renderer.rb
- lib/open_food_network/reports/bulk_coop_allocation_report.rb
- lib/open_food_network/reports/line_items.rb
- lib/open_food_network/sales_tax_report.rb
@@ -249,13 +248,10 @@ Metrics/LineLength:
- spec/helpers/order_cycles_helper_spec.rb
- spec/helpers/spree/admin/base_helper_spec.rb
- spec/jobs/confirm_order_job_spec.rb
- spec/jobs/products_cache_integrity_checker_job_spec.rb
- spec/jobs/refresh_products_cache_job_spec.rb
- spec/jobs/subscription_confirm_job_spec.rb
- spec/jobs/subscription_placement_job_spec.rb
- spec/lib/open_food_network/address_finder_spec.rb
- spec/lib/open_food_network/bulk_coop_report_spec.rb
- spec/lib/open_food_network/cached_products_renderer_spec.rb
- spec/lib/open_food_network/customers_report_spec.rb
- spec/lib/open_food_network/enterprise_fee_applicator_spec.rb
- spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
@@ -272,7 +268,6 @@ Metrics/LineLength:
- spec/lib/open_food_network/permissions_spec.rb
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
- spec/lib/open_food_network/products_cache_spec.rb
- spec/lib/open_food_network/products_renderer_spec.rb
- spec/lib/open_food_network/proxy_order_syncer_spec.rb
- spec/lib/open_food_network/scope_variant_to_hub_spec.rb
- spec/lib/open_food_network/subscription_payment_updater_spec.rb
@@ -615,7 +610,6 @@ Metrics/MethodLength:
- lib/open_food_network/payments_report.rb
- lib/open_food_network/permissions.rb
- lib/open_food_network/products_and_inventory_report.rb
- lib/open_food_network/products_renderer.rb
- lib/open_food_network/rack_request_blocker.rb
- lib/open_food_network/reports/bulk_coop_allocation_report.rb
- lib/open_food_network/reports/bulk_coop_supplier_report.rb
@@ -670,7 +664,6 @@ Metrics/ModuleLength:
- spec/controllers/api/orders_controller_spec.rb
- spec/controllers/spree/api/products_controller_spec.rb
- spec/lib/open_food_network/address_finder_spec.rb
- spec/lib/open_food_network/cached_products_renderer_spec.rb
- spec/lib/open_food_network/customers_report_spec.rb
- spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
- spec/lib/open_food_network/option_value_namer_spec.rb

View File

@@ -1 +1 @@
2.1.9
2.2.10

View File

@@ -19,6 +19,10 @@ If you want to run the whole test suite, we recommend using a free CI service to
bundle exec rspec spec
## Which issue to pick first?
We have curated all issues interesting for new members of the community within the [Welcome New Developers project board][welcome-dev]. Have a look and pick the one you would prefer working on!
## Internationalisation (i18n)
The locale `en` is maintained in the source code, but other locales are managed at [Transifex][ofn-transifex]. Read more about [internationalisation][i18n] in the developer wiki.
@@ -62,3 +66,4 @@ From here, your pull request will progress through the [Review, Test, Merge & De
[slack-dev]: https://openfoodnetwork.slack.com/messages/C2GQ45KNU
[ofn-transifex]: https://www.transifex.com/open-food-foundation/open-food-network/
[i18n]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/i18n
[welcome-dev]: https://github.com/openfoodfoundation/openfoodnetwork/projects/27

12
Gemfile
View File

@@ -1,9 +1,9 @@
source 'https://rubygems.org'
ruby "2.1.9"
ruby "2.2.10"
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
gem 'i18n', '~> 0.6.11'
gem 'i18n-js', '~> 3.3.0'
gem 'i18n-js', '~> 3.4.1'
gem 'rails', '~> 3.2.22'
gem 'rails-i18n', '~> 3.0.0'
gem 'rails_safe_tasks', '~> 1.0'
@@ -15,12 +15,12 @@ gem 'nokogiri', '>= 1.6.7.1'
gem "order_management", path: "./engines/order_management"
gem 'web', path: './engines/web'
gem 'pg'
gem 'activerecord-postgresql-adapter'
gem 'pg', '~> 0.21.0'
# OFN-maintained and patched version of Spree v2.0.4. See
# https://github.com/openfoodfoundation/openfoodnetwork/wiki/Spree-2.0-upgrade
# for details.
gem 'spree_api', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
gem 'spree_backend', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
gem 'spree_core', github: 'openfoodfoundation/spree', branch: '2-0-4-stable'
@@ -98,6 +98,8 @@ gem 'roo-xls', '~> 1.1.0'
gem 'whenever', require: false
gem 'test-unit', '~> 3.0'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
@@ -136,7 +138,7 @@ group :test, :development do
gem 'capybara', '>= 2.15.4'
gem 'database_cleaner', '0.7.1', require: false
gem "factory_bot_rails", require: false
gem 'fuubar', '~> 2.4.1'
gem 'fuubar', '~> 2.5.0'
gem 'json_spec', '~> 1.1.4'
gem 'knapsack'
gem 'letter_opener', '>= 1.4.1'

View File

@@ -129,8 +129,10 @@ GEM
activesupport (= 3.2.22.5)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activerecord-import (1.0.2)
activerecord-import (1.0.3)
activerecord (>= 3.2)
activerecord-postgresql-adapter (0.0.1)
pg
activeresource (3.2.22.5)
activemodel (= 3.2.22.5)
activesupport (= 3.2.22.5)
@@ -164,7 +166,7 @@ GEM
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
blockenspiel (0.5.0)
bugsnag (6.12.1)
bugsnag (6.12.2)
concurrent-ruby (~> 1.0)
builder (3.0.4)
byebug (9.0.6)
@@ -424,7 +426,7 @@ GEM
foundation-rails (5.5.2.1)
railties (>= 3.1.0)
sass (>= 3.3.0, < 3.5)
fuubar (2.4.1)
fuubar (2.5.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
geocoder (1.1.8)
@@ -437,7 +439,7 @@ GEM
httparty (0.16.2)
multi_xml (>= 0.5.2)
i18n (0.6.11)
i18n-js (3.3.0)
i18n-js (3.4.1)
i18n (>= 0.6.6)
immigrant (0.3.6)
activerecord (>= 3.0)
@@ -522,6 +524,7 @@ GEM
polyamorous (0.5.0)
activerecord (~> 3.0)
polyglot (0.3.5)
power_assert (1.1.5)
powerpack (0.1.1)
pry (0.12.2)
coderay (~> 1.1.0)
@@ -671,6 +674,8 @@ GEM
stripe (4.24.0)
faraday (~> 0.13)
net-http-persistent (~> 3.0)
test-unit (3.3.3)
power_assert
thor (0.20.3)
tilt (1.4.1)
timecop (0.9.1)
@@ -722,6 +727,7 @@ DEPENDENCIES
active_model_serializers (= 0.8.4)
activemerchant (~> 1.78)
activerecord-import
activerecord-postgresql-adapter
acts-as-taggable-on (~> 3.4)
andand
angular-rails-templates (~> 0.3.0)
@@ -757,12 +763,12 @@ DEPENDENCIES
foundation-icons-sass-rails
foundation-rails
foundation_rails_helper!
fuubar (~> 2.4.1)
fuubar (~> 2.5.0)
geocoder
gmaps4rails
haml
i18n (~> 0.6.11)
i18n-js (~> 3.3.0)
i18n-js (~> 3.4.1)
immigrant
jquery-migrate-rails
jquery-rails (= 3.0.4)
@@ -783,7 +789,7 @@ DEPENDENCIES
order_management!
paper_trail (~> 5.2.3)
paperclip (~> 3.4.1)
pg
pg (~> 0.21.0)
pry-byebug (>= 3.4.3)
rabl
rack-mini-profiler (< 1.0.0)
@@ -806,7 +812,6 @@ DEPENDENCIES
simple_form!
simplecov
spinjs-rails
spree_api!
spree_backend!
spree_core!
spree_i18n!
@@ -814,6 +819,7 @@ DEPENDENCIES
spring (= 1.7.2)
spring-commands-rspec
stripe
test-unit (~> 3.0)
timecop
truncate_html
turbo-sprockets-rails3
@@ -828,7 +834,7 @@ DEPENDENCIES
wkhtmltopdf-binary
RUBY VERSION
ruby 2.1.9p490
ruby 2.2.10p489
BUNDLED WITH
1.17.2

View File

@@ -3,20 +3,20 @@
# Open Food Network
The Open Food Network is an online marketplace for local food. It enables a network of independent online food stores that connect farmers and food hubs (including coops, online farmers' markets, independent food businesses etc); with individuals and local businesses. It gives farmers and food hubs an easier and fairer way to distribute their food.
The Open Food Network is an online marketplace for local food. It enables a network of independent online food stores that connects farmers and food hubs (including co-ops, online farmers markets, independent food businesses, etc) with individuals and local businesses. It gives farmers and food hubs an easier and fairer way to distribute their food.
Supported by the Open Food Foundation and a network of global affiliates, we are proudly open source and not-for-profit - we're trying to seriously disrupt the concentration of power in global agri-food systems, and we need as many smart people working together on this as possible.
We're part of global movement - get involved!
* Join the conversation [on Slack][slack-invite]. Make sure you introduce yourself in the #general channel
* Join the conversation [on Slack][slack-invite]. Make sure you introduce yourself in the #general channel.
* Head to [https://openfoodnetwork.org](https://openfoodnetwork.org) for more information about the global OFN project.
* Check out the [User Guide](https://guide.openfoodnetwork.org/) for a list of features and tutorials.
* Join our [discussion forum](https://community.openfoodnetwork.org).
## Contributing
If you are interested in contributing to the OFN in any capacity, please introducing yourself [on Slack][slack-invite], and have a look through our [Contributor Guide][contributor-guide]
If you are interested in contributing to the OFN in any capacity, please introduce yourself [on Slack][slack-invite], and have a look through our [Contributor Guide][contributor-guide].
Our [GETTING_STARTED](GETTING_STARTED.md) and [CONTRIBUTING](CONTRIBUTING.md) guides are the best place to start for developers looking to set up a development environment and make contributions to the codebase.

View File

@@ -1,4 +1,4 @@
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories, RequestMonitor) ->
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor) ->
$scope.StatusMessage = StatusMessage
$scope.columns = Columns.columns
@@ -39,12 +39,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.DisplayProperties = DisplayProperties
$scope.initialise = ->
SpreeApiAuth.authorise()
.then ->
$scope.spree_api_key_ok = true
$scope.fetchProducts()
.catch (message) ->
$scope.api_error_msg = message
$scope.fetchProducts()
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter, per_page]', ->
$scope.page = 1 # Reset page when changing filters for new search
@@ -108,9 +103,15 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.categoryFilter = "0"
$scope.importDateFilter = "0"
confirm_unsaved_changes = () ->
(DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
editProductUrl = (product, variant) ->
"/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
$scope.editWarn = (product, variant) ->
if (DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
if confirm_unsaved_changes()
window.open(editProductUrl(product, variant), "_blank")
$scope.toggleShowAllVariants = ->

View File

@@ -1,16 +0,0 @@
angular.module("admin.indexUtils").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) ->
new class SpreeApiAuth
authorise: ->
deferred = $q.defer()
$http.get("/api/users/authorise_api?token=" + SpreeApiKey)
.success (response) ->
if response?.success == "Use of API Authorised"
$http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey
deferred.resolve()
.error (response) ->
error = response?.error || t('js.unauthorized')
deferred.reject(error)
deferred.promise

View File

@@ -1,4 +1,4 @@
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, Views, SpreeApiAuth, PagedFetcher, StatusMessage, RequestMonitor, hubs, producers, hubPermissions, InventoryItems, VariantOverrides, DirtyVariantOverrides) ->
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, Views, PagedFetcher, StatusMessage, RequestMonitor, hubs, producers, hubPermissions, InventoryItems, VariantOverrides, DirtyVariantOverrides) ->
$scope.hubs = Indexer.index hubs
$scope.hub_id = if hubs.length == 1 then hubs[0].id else null
$scope.products = []
@@ -39,13 +39,7 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
$scope.producerFilter != 0 || $scope.query != ''
$scope.initialise = ->
SpreeApiAuth.authorise()
.then ->
$scope.spree_api_key_ok = true
$scope.fetchProducts()
.catch (message) ->
$scope.api_error_msg = message
$scope.fetchProducts()
$scope.fetchProducts = ->
url = "/api/products/overridable?page=::page::;per_page=100"

View File

@@ -1,4 +1,4 @@
Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) ->
Darkswarm.controller "OrderCycleCtrl", ($scope, $rootScope, $timeout, OrderCycle) ->
$scope.order_cycle = OrderCycle.order_cycle
$scope.OrderCycle = OrderCycle
@@ -6,11 +6,12 @@ Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) ->
# This is a hack. We should probably write our own "popover" directive
# That takes an expression instead of a trigger, and binds to that
$timeout =>
$rootScope.$broadcast 'orderCycleSelected'
if !$scope.OrderCycle.selected()
$("#order_cycle_id").trigger("openTrigger")
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart, ChangeableOrdersAlert) ->
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $rootScope, $timeout, OrderCycle, Products, Variants, Cart, ChangeableOrdersAlert) ->
# Track previous order cycle id for use with revertOrderCycle()
$scope.previous_order_cycle_id = OrderCycle.order_cycle.order_cycle_id
$scope.$watch 'order_cycle.order_cycle_id', (newValue, oldValue)->
@@ -32,3 +33,4 @@ Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Prod
Products.update()
Cart.reloadFinalisedLineItems()
ChangeableOrdersAlert.reload()
$rootScope.$broadcast 'orderCycleSelected'

View File

@@ -1,41 +1,65 @@
Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, FilterSelectorsService, Cart, Taxons, Properties) ->
Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, OrderCycleResource, FilterSelectorsService, Cart, Dereferencer, Taxons, Properties, currentHub, $timeout) ->
$scope.Products = Products
$scope.Cart = Cart
$scope.query = ""
$scope.taxonSelectors = FilterSelectorsService.createSelectors()
$scope.propertySelectors = FilterSelectorsService.createSelectors()
$scope.filtersActive = true
$scope.limit = 10
$scope.page = 1
$scope.per_page = 10
$scope.order_cycle = OrderCycle.order_cycle
# $scope.infiniteDisabled = true
$scope.supplied_taxons = null
$scope.supplied_properties = null
# All of this logic basically just replicates the functionality filtering an ng-repeat
# except that it allows us to filter a separate list before rendering, meaning that
# we can get much better performance when applying filters by resetting the limit on the
# number of products being rendered each time a filter is changed.
$rootScope.$on "orderCycleSelected", ->
$scope.update_filters()
$scope.clearAll()
$scope.$watch "Products.loading", (newValue, oldValue) ->
$scope.updateFilteredProducts()
$scope.$broadcast("loadFilterSelectors") if !newValue
$scope.update_filters = ->
order_cycle_id = OrderCycle.order_cycle.order_cycle_id
$scope.incrementLimit = ->
if $scope.limit < Products.products.length
$scope.limit += 10
$scope.updateVisibleProducts()
return unless order_cycle_id
$scope.$watch 'query', -> $scope.updateFilteredProducts()
$scope.$watchCollection 'activeTaxons', -> $scope.updateFilteredProducts()
$scope.$watchCollection 'activeProperties', -> $scope.updateFilteredProducts()
params = {
id: order_cycle_id,
distributor: currentHub.id
}
OrderCycleResource.taxons params, (data)=>
$scope.supplied_taxons = {}
data.map( (taxon) ->
$scope.supplied_taxons[taxon.id] = Taxons.taxons_by_id[taxon.id]
)
OrderCycleResource.properties params, (data)=>
$scope.supplied_properties = {}
data.map( (property) ->
$scope.supplied_properties[property.id] = Properties.properties_by_id[property.id]
)
$scope.updateFilteredProducts = ->
$scope.limit = 10
f1 = $filter('products')(Products.products, $scope.query)
f2 = $filter('taxons')(f1, $scope.activeTaxons)
$scope.filteredProducts = $filter('properties')(f2, $scope.activeProperties)
$scope.updateVisibleProducts()
$scope.loadMore = ->
if ($scope.page * $scope.per_page) <= Products.products.length
$scope.loadMoreProducts()
$scope.updateVisibleProducts = ->
$scope.visibleProducts = $filter('limitTo')($scope.filteredProducts, $scope.limit)
$scope.$watch 'query', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
$scope.$watchCollection 'activeTaxons', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
$scope.$watchCollection 'activeProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
$scope.loadProducts = ->
$scope.page = 1
Products.update($scope.queryParams())
$scope.loadMoreProducts = ->
Products.update($scope.queryParams($scope.page + 1), true)
$scope.page += 1
$scope.queryParams = (page = null) ->
{
id: $scope.order_cycle.order_cycle_id,
page: page || $scope.page,
per_page: $scope.per_page,
'q[name_or_meta_keywords_or_supplier_name_cont]': $scope.query,
'q[properties_id_or_supplier_properties_id_in_any][]': $scope.activeProperties,
'q[primary_taxon_id_in_any][]': $scope.activeTaxons
}
$scope.searchKeypress = (e)->
code = e.keyCode || e.which

View File

@@ -0,0 +1,21 @@
Darkswarm.factory 'OrderCycleResource', ($resource) ->
$resource('/api/order_cycles/:id', {}, {
'products':
method: 'GET'
isArray: true
url: '/api/order_cycles/:id/products'
params:
id: '@id'
'taxons':
method: 'GET'
isArray: true
url: '/api/order_cycles/:id/taxons'
params:
id: '@id'
'properties':
method: 'GET'
isArray: true
url: '/api/order_cycles/:id/properties'
params:
id: '@id'
})

View File

@@ -1,26 +1,34 @@
Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Properties, Cart, Variants) ->
Darkswarm.factory 'Products', (OrderCycleResource, OrderCycle, Shopfront, currentHub, Dereferencer, Taxons, Properties, Cart, Variants) ->
new class Products
constructor: ->
@update()
# TODO: don't need to scope this into object
# Already on object as far as controller scope is concerned
products: null
products: []
fetched_products: []
loading: true
update: =>
update: (params = {}, load_more = false) =>
@loading = true
@products = []
$resource("/shop/products").query (products)=>
@products = products
order_cycle_id = OrderCycle.order_cycle.order_cycle_id
if order_cycle_id == undefined
@loading = false
return
params['id'] = order_cycle_id
params['distributor'] = currentHub.id
OrderCycleResource.products params, (data)=>
@products = [] unless load_more
@fetched_products = data
@extend()
@dereference()
@registerVariants()
@products = @products.concat(@fetched_products)
@loading = false
extend: ->
for product in @products
for product in @fetched_products
if product.variants?.length > 0
prices = (v.price for v in product.variants)
product.price = Math.min.apply(null, prices)
@@ -30,7 +38,7 @@ Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Prope
product.largeImage = product.images[0]?.large_url if product.images
dereference: ->
for product in @products
for product in @fetched_products
product.supplier = Shopfront.producers_by_id[product.supplier.id]
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
@@ -40,7 +48,7 @@ Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Prope
# May return different objects! If the variant has already been registered
# by another service, we fetch those
registerVariants: ->
for product in @products
for product in @fetched_products
if product.variants
product.variant_names = ""
product.variants = for variant in product.variants

View File

@@ -17,7 +17,7 @@
.small-4.medium-2.large-2.columns.variant-price
.table-cell.price
%i.ofn-i_009-close
{{ ::variant.price_with_fees | localizeCurrency }}
{{ variant.price_with_fees | localizeCurrency }}
-# Now in a template in app/assets/javascripts/templates !
%price-breakdown{"price-breakdown" => "_", variant: "variant",

View File

@@ -255,6 +255,9 @@ text-angular {
background-color: #4583bf;
}
}
a {
color: $spree-green
}
}
span.required {

View File

@@ -1,27 +0,0 @@
require 'open_food_network/products_cache_integrity_checker'
module Admin
class CacheSettingsController < Spree::Admin::BaseController
def edit
@results = Exchange.cachable.map do |exchange|
checker = OpenFoodNetwork::ProductsCacheIntegrityChecker
.new(exchange.receiver, exchange.order_cycle)
{
distributor: exchange.receiver,
order_cycle: exchange.order_cycle,
status: checker.ok?,
diff: checker.diff
}
end
end
def update
Spree::Config.set(params[:preferences])
respond_to do |format|
format.html { redirect_to main_app.edit_admin_cache_settings_path }
end
end
end
end

View File

@@ -1,16 +1,44 @@
# Base controller for OFN's API
# Includes the minimum machinery required by ActiveModelSerializers
require_dependency 'spree/api/controller_setup'
module Api
class BaseController < Spree::Api::BaseController
# Need to include these because Spree::Api::BaseContoller inherits
# from ActionController::Metal rather than ActionController::Base
# and they are required by ActiveModelSerializers
class BaseController < ActionController::Metal
include Spree::Api::ControllerSetup
include Spree::Core::ControllerHelpers::SSL
include ::ActionController::Head
respond_to :json
attr_accessor :current_api_user
before_filter :set_content_type
before_filter :authenticate_user
after_filter :set_jsonp_format
rescue_from Exception, with: :error_during_processing
rescue_from CanCan::AccessDenied, with: :unauthorized
rescue_from ActiveRecord::RecordNotFound, with: :not_found
helper Spree::Api::ApiHelpers
ssl_allowed
# Include these because we inherit from ActionController::Metal
# rather than ActionController::Base and these are required for AMS
include ActionController::Serialization
include ActionController::UrlFor
include Rails.application.routes.url_helpers
use_renderers :json
check_authorization
def set_jsonp_format
return unless params[:callback] && request.get?
self.response_body = "#{params[:callback]}(#{response_body})"
headers["Content-Type"] = 'application/javascript'
end
def respond_with_conflict(json_hash)
render json: json_hash, status: :conflict
end
@@ -19,16 +47,62 @@ module Api
# Use logged in user (spree_current_user) for API authentication (current_api_user)
def authenticate_user
@current_api_user = try_spree_current_user
super
return if @current_api_user = try_spree_current_user
if api_key.blank?
# An anonymous user
@current_api_user = Spree.user_class.new
return
end
return if @current_api_user = Spree.user_class.find_by_spree_api_key(api_key.to_s)
invalid_api_key
end
# Allows API access without authentication, but only for OFN controllers which inherit
# from Api::BaseController. @current_api_user will now initialize an empty Spree::User
# unless one is present. We now also apply devise's `check_authorization`. See here for
# details: https://github.com/CanCanCommunity/cancancan/wiki/Ensure-Authorization
def requires_authentication?
false
def set_content_type
content_type = case params[:format]
when "json"
"application/json"
when "xml"
"text/xml"
end
headers["Content-Type"] = content_type
end
def error_during_processing(exception)
render(text: { exception: exception.message }.to_json,
status: :unprocessable_entity) && return
end
def current_ability
Spree::Ability.new(current_api_user)
end
def api_key
request.headers["X-Spree-Token"] || params[:token]
end
helper_method :api_key
def invalid_resource!(resource)
@resource = resource
render(json: { error: I18n.t(:invalid_resource, scope: "spree.api"),
errors: @resource.errors },
status: :unprocessable_entity)
end
def invalid_api_key
render(json: { error: I18n.t(:invalid_api_key, key: api_key, scope: "spree.api") },
status: :unauthorized) && return
end
def unauthorized
render(json: { error: I18n.t(:unauthorized, scope: "spree.api") },
status: :unauthorized) && return
end
def not_found
render(json: { error: I18n.t(:resource_not_found, scope: "spree.api") },
status: :not_found) && return
end
end
end

View File

@@ -0,0 +1,88 @@
module Api
class OrderCyclesController < BaseController
include EnterprisesHelper
respond_to :json
skip_authorization_check
def products
products = ProductsRenderer.new(
distributor,
order_cycle,
customer,
search_params
).products_json
render json: products
rescue ProductsRenderer::NoProducts
render status: :not_found, json: ''
end
def taxons
taxons = Spree::Taxon.
joins(:products).
where(spree_products: { id: distributed_products }).
select('DISTINCT spree_taxons.*')
render json: ActiveModel::ArraySerializer.new(taxons, each_serializer: Api::TaxonSerializer)
end
def properties
render json: ActiveModel::ArraySerializer.new(
product_properties | producer_properties, each_serializer: Api::PropertySerializer
)
end
private
def product_properties
Spree::Property.
joins(:products).
where(spree_products: { id: distributed_products }).
select('DISTINCT spree_properties.*')
end
def producer_properties
producers = Enterprise.
joins(:supplied_products).
where(spree_products: { id: distributed_products })
Spree::Property.
joins(:producer_properties).
where(producer_properties: { producer_id: producers }).
select('DISTINCT spree_properties.*')
end
def search_params
permitted_search_params = params.slice :q, :page, :per_page
if permitted_search_params.key? :q
permitted_search_params[:q].slice!(*permitted_ransack_params)
end
permitted_search_params
end
def permitted_ransack_params
[:name_or_meta_keywords_or_supplier_name_cont,
:properties_id_or_supplier_properties_id_in_any,
:primary_taxon_id_in_any]
end
def distributor
Enterprise.find_by_id(params[:distributor])
end
def order_cycle
OrderCycle.find_by_id(params[:id])
end
def customer
@current_api_user.andand.customer_of(distributor) || nil
end
def distributed_products
OrderCycleDistributedProducts.new(distributor, order_cycle, customer).products_relation
end
end
end

View File

@@ -47,7 +47,6 @@ module Api
render json: @product, serializer: Api::Admin::ProductSerializer, status: 204
end
# TODO: This should be named 'managed'. Is the action above used? Maybe we should remove it.
def bulk_products
product_query = OpenFoodNetwork::Permissions.new(current_api_user).
editable_products.merge(product_scope)
@@ -94,10 +93,13 @@ module Api
private
# Copied and modified from SpreeApi::BaseController to allow
# enterprise users to access inactive products
def find_product(id)
product_scope.find_by_permalink!(id.to_s)
rescue ActiveRecord::RecordNotFound
product_scope.find(id)
end
def product_scope
# This line modified
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present?
scope = Spree::Product
if params[:show_deleted]

View File

@@ -1,5 +1,3 @@
require 'open_food_network/cached_products_renderer'
class ShopController < BaseController
layout "darkswarm"
before_filter :require_distributor_chosen, :set_order_cycles, except: :changeable_orders_alert
@@ -9,19 +7,6 @@ class ShopController < BaseController
redirect_to main_app.enterprise_shop_path(current_distributor)
end
def products
renderer = OpenFoodNetwork::CachedProductsRenderer.new(current_distributor,
current_order_cycle)
# If we add any more filtering logic, we should probably
# move it all to a lib class like 'CachedProductsFilterer'
products_json = filter(renderer.products_json)
render json: products_json
rescue OpenFoodNetwork::CachedProductsRenderer::NoProducts
render status: :not_found, json: ''
end
def order_cycle
if request.post?
if oc = OrderCycle.with_distributor(@distributor).active.find_by_id(params[:order_cycle_id])
@@ -39,27 +24,4 @@ class ShopController < BaseController
def changeable_orders_alert
render layout: false
end
private
def filtered_json(products_json)
if applicator.rules.any?
filter(products_json)
else
products_json
end
end
def filter(products_json)
products_hash = JSON.parse(products_json)
applicator.filter!(products_hash)
JSON.unparse(products_hash)
end
def applicator
return @applicator unless @applicator.nil?
@applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor,
"FilterProducts",
current_customer.andand.tag_list)
end
end

View File

@@ -46,8 +46,11 @@ Spree::Admin::ProductsController.class_eval do
end
def update
original_supplier_id = @product.supplier_id
delete_stock_params_and_set_after do
super
ExchangeVariantDeleter.new.delete(@product) if original_supplier_id != @product.supplier_id
end
end

View File

@@ -109,7 +109,7 @@ Spree::Admin::ReportsController.class_eval do
end
def orders_and_fulfillment
params[:q] ||= {}
params[:q] ||= orders_and_fulfillment_default_filters
# -- Prepare Form Options
permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
@@ -278,4 +278,10 @@ Spree::Admin::ReportsController.class_eval do
def timestamp
Time.zone.now.strftime("%Y%m%d")
end
def orders_and_fulfillment_default_filters
now = Time.zone.now
{ completed_at_gt: (now - 1.month).beginning_of_day,
completed_at_lt: (now + 1.day).beginning_of_day }
end
end

View File

@@ -58,14 +58,14 @@ module Spree
def generate_api_key
if @user.generate_spree_api_key!
flash[:success] = Spree.t('api.key_generated')
flash[:success] = t('spree.api.key_generated')
end
redirect_to edit_admin_user_path(@user)
end
def clear_api_key
if @user.clear_spree_api_key!
flash[:success] = Spree.t('api.key_cleared')
flash[:success] = t('spree.api.key_cleared')
end
redirect_to edit_admin_user_path(@user)
end

View File

@@ -1,130 +0,0 @@
require_dependency 'spree/api/controller_setup'
module Spree
module Api
class BaseController < ActionController::Metal
include Spree::Api::ControllerSetup
include Spree::Core::ControllerHelpers::SSL
include ::ActionController::Head
self.responder = Spree::Api::Responders::AppResponder
respond_to :json
attr_accessor :current_api_user
before_filter :set_content_type
before_filter :check_for_user_or_api_key, :if => :requires_authentication?
before_filter :authenticate_user
after_filter :set_jsonp_format
rescue_from Exception, :with => :error_during_processing
rescue_from CanCan::AccessDenied, :with => :unauthorized
rescue_from ActiveRecord::RecordNotFound, :with => :not_found
helper Spree::Api::ApiHelpers
ssl_allowed
def set_jsonp_format
if params[:callback] && request.get?
self.response_body = "#{params[:callback]}(#{response_body})"
headers["Content-Type"] = 'application/javascript'
end
end
def map_nested_attributes_keys(klass, attributes)
nested_keys = klass.nested_attributes_options.keys
attributes.inject({}) do |h, (k, v)|
key = nested_keys.include?(k.to_sym) ? "#{k}_attributes" : k
h[key] = v
h
end.with_indifferent_access
end
private
def set_content_type
content_type = case params[:format]
when "json"
"application/json"
when "xml"
"text/xml"
end
headers["Content-Type"] = content_type
end
def check_for_user_or_api_key
# User is already authenticated with Spree, make request this way instead.
return true if @current_api_user = try_spree_current_user ||
!requires_authentication?
return if api_key.present?
render("spree/api/errors/must_specify_api_key", status: :unauthorized) && return
end
def authenticate_user
return if @current_api_user
if requires_authentication? || api_key.present?
unless @current_api_user = Spree.user_class.find_by_spree_api_key(api_key.to_s)
render("spree/api/errors/invalid_api_key", status: :unauthorized) && return
end
else
# An anonymous user
@current_api_user = Spree.user_class.new
end
end
def unauthorized
render("spree/api/errors/unauthorized", status: :unauthorized) && return
end
def error_during_processing(exception)
render(text: { exception: exception.message }.to_json,
status: :unprocessable_entity) && return
end
def requires_authentication?
true
end
def not_found
render("spree/api/errors/not_found", status: :not_found) && return
end
def current_ability
Spree::Ability.new(current_api_user)
end
def invalid_resource!(resource)
@resource = resource
render "spree/api/errors/invalid_resource", status: :unprocessable_entity
end
def api_key
request.headers["X-Spree-Token"] || params[:token]
end
helper_method :api_key
def find_product(id)
product_scope.find_by_permalink!(id.to_s)
rescue ActiveRecord::RecordNotFound
product_scope.find(id)
end
def product_scope
if current_api_user.has_spree_role?("admin")
scope = Product
if params[:show_deleted]
scope = scope.with_deleted
end
else
scope = Product.active
end
scope.includes(:master)
end
end
end
end

View File

@@ -1,7 +0,0 @@
module Spree
module Api
class UsersController < Spree::Api::BaseController
respond_to :json
end
end
end

View File

@@ -1,30 +0,0 @@
require 'open_food_network/products_cache_integrity_checker'
ProductsCacheIntegrityCheckerJob = Struct.new(:distributor_id, :order_cycle_id) do
def perform
unless checker.ok?
exception = RuntimeError.new(
"Products JSON differs from cached version for distributor: #{distributor_id}, " \
"order cycle: #{order_cycle_id}"
)
Bugsnag.notify(exception) do |report|
report.add_tab(:products_cache, diff: checker.diff.to_s(:text))
end
end
end
private
def checker
OpenFoodNetwork::ProductsCacheIntegrityChecker.new(distributor, order_cycle)
end
def distributor
Enterprise.find distributor_id
end
def order_cycle
OrderCycle.find order_cycle_id
end
end

View File

@@ -1,21 +0,0 @@
require 'open_food_network/products_renderer'
RefreshProductsCacheJob = Struct.new(:distributor_id, :order_cycle_id) do
def perform
Rails.cache.write(key, products_json)
rescue ActiveRecord::RecordNotFound
true
end
private
def key
"products-json-#{distributor_id}-#{order_cycle_id}"
end
def products_json
distributor = Enterprise.find distributor_id
order_cycle = OrderCycle.find order_cycle_id
OpenFoodNetwork::ProductsRenderer.new(distributor, order_cycle).products_json
end
end

View File

@@ -1,13 +1,4 @@
class CoordinatorFee < ActiveRecord::Base
belongs_to :order_cycle
belongs_to :enterprise_fee
after_save :refresh_products_cache
after_destroy :refresh_products_cache
private
def refresh_products_cache
order_cycle.refresh_products_cache
end
end

View File

@@ -10,10 +10,6 @@ class EnterpriseFee < ActiveRecord::Base
has_many :exchange_fees, dependent: :destroy
has_many :exchanges, through: :exchange_fees
after_save :refresh_products_cache
# After destroy, the products cache is refreshed via the after_destroy hook for
# coordinator_fees and exchange_fees
attr_accessible :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type, :inherits_tax_category
FEE_TYPES = %w(packing transport admin sales fundraising).freeze
@@ -59,8 +55,4 @@ class EnterpriseFee < ActiveRecord::Base
end
true
end
def refresh_products_cache
OpenFoodNetwork::ProductsCache.enterprise_fee_changed self
end
end

View File

@@ -23,9 +23,6 @@ class Exchange < ActiveRecord::Base
validates :order_cycle, :sender, :receiver, presence: true
validates :sender_id, uniqueness: { scope: [:order_cycle_id, :receiver_id, :incoming] }
after_save :refresh_products_cache
after_destroy :refresh_products_cache_from_destroy
accepts_nested_attributes_for :variants
scope :in_order_cycle, lambda { |order_cycle| where(order_cycle_id: order_cycle) }
@@ -98,12 +95,4 @@ class Exchange < ActiveRecord::Base
def participant
incoming? ? sender : receiver
end
def refresh_products_cache
OpenFoodNetwork::ProductsCache.exchange_changed self
end
def refresh_products_cache_from_destroy
OpenFoodNetwork::ProductsCache.exchange_destroyed self
end
end

View File

@@ -1,13 +1,4 @@
class ExchangeFee < ActiveRecord::Base
belongs_to :exchange
belongs_to :enterprise_fee
after_save :refresh_products_cache
after_destroy :refresh_products_cache
private
def refresh_products_cache
exchange.refresh_products_cache
end
end

View File

@@ -1,5 +1,3 @@
require 'open_food_network/products_cache'
class InventoryItem < ActiveRecord::Base
attr_accessible :enterprise, :enterprise_id, :variant, :variant_id, :visible
@@ -13,12 +11,4 @@ class InventoryItem < ActiveRecord::Base
scope :visible, -> { where(visible: true) }
scope :hidden, -> { where(visible: false) }
after_save :refresh_products_cache
private
def refresh_products_cache
OpenFoodNetwork::ProductsCache.inventory_item_changed self
end
end

View File

@@ -23,8 +23,6 @@ class OrderCycle < ActiveRecord::Base
validates :name, :coordinator_id, presence: true
validate :orders_close_at_after_orders_open_at?
after_save :refresh_products_cache
preference :product_selection_from_coordinator_inventory_only, :boolean, default: false
scope :active, lambda {
@@ -247,10 +245,6 @@ class OrderCycle < ActiveRecord::Base
coordinator.users.include? user
end
def refresh_products_cache
OpenFoodNetwork::ProductsCache.order_cycle_changed self
end
def items_bought_by_user(user, distributor)
# The Spree::Order.complete scope only checks for completed_at date
# it does not ensure state is "complete"

View File

@@ -4,9 +4,6 @@ class ProducerProperty < ActiveRecord::Base
default_scope order("#{table_name}.position")
after_save :refresh_products_cache
after_destroy :refresh_products_cache_from_destroy
scope :ever_sold_by, ->(shop) {
joins(producer: { supplied_products: { variants: { exchanges: :order_cycle } } }).
merge(Exchange.outgoing).
@@ -29,14 +26,4 @@ class ProducerProperty < ActiveRecord::Base
Spree::Property.create(name: name, presentation: name)
end
end
private
def refresh_products_cache
OpenFoodNetwork::ProductsCache.producer_property_changed self
end
def refresh_products_cache_from_destroy
OpenFoodNetwork::ProductsCache.producer_property_destroyed self
end
end

View File

@@ -2,16 +2,9 @@ Spree::Classification.class_eval do
belongs_to :product, class_name: "Spree::Product", touch: true
before_destroy :dont_destroy_if_primary_taxon
after_destroy :refresh_products_cache
after_save :refresh_products_cache
private
def refresh_products_cache
product = Spree::Product.with_deleted.find(product_id) if product.blank?
product.refresh_products_cache
end
def dont_destroy_if_primary_taxon
if product.primary_taxon == taxon
errors.add :base, I18n.t(:spree_classification_primary_taxon_error, taxon: taxon.name, product: product.name)

View File

@@ -1,7 +1,4 @@
Spree::Image.class_eval do
after_save :refresh_products_cache
after_destroy :refresh_products_cache
# Spree stores attachent definitions in JSON. This converts the style name and format to
# strings. However, when paperclip encounters these, it doesn't recognise the format.
# Here we solve that problem by converting format and style name to symbols.
@@ -23,10 +20,4 @@ Spree::Image.class_eval do
end
reformat_styles
private
def refresh_products_cache
viewable.try :refresh_products_cache
end
end

View File

@@ -1,12 +1,5 @@
module Spree
OptionType.class_eval do
has_many :products, through: :product_option_types
after_save :refresh_products_cache
private
def refresh_products_cache
products(:reload).each(&:refresh_products_cache)
end
end
end

View File

@@ -1,18 +0,0 @@
module Spree
OptionValue.class_eval do
after_save :refresh_products_cache
around_destroy :refresh_products_cache_from_destroy
private
def refresh_products_cache
variants(:reload).each(&:refresh_products_cache)
end
def refresh_products_cache_from_destroy
vs = variants(:reload).to_a
yield
vs.each(&:refresh_products_cache)
end
end
end

View File

@@ -1,30 +0,0 @@
require 'open_food_network/products_cache'
module Spree
Preference.class_eval do
after_save :refresh_products_cache
# When the setting preferred_product_selection_from_inventory_only has changed, we want to
# refresh all active exchanges for this enterprise.
def refresh_products_cache
if product_selection_from_inventory_only_changed?
OpenFoodNetwork::ProductsCache.distributor_changed(enterprise)
end
end
private
def product_selection_from_inventory_only_changed?
!!(key =~ product_selection_from_inventory_only_regex)
end
def enterprise
enterprise_id = key.match(product_selection_from_inventory_only_regex)[1]
Enterprise.find(enterprise_id)
end
def product_selection_from_inventory_only_regex
/^enterprise\/product_selection_from_inventory_only\/(\d+)$/
end
end
end

View File

@@ -2,8 +2,6 @@ module Spree
Price.class_eval do
acts_as_paranoid without_default_scope: true
after_save :refresh_products_cache
# Allow prices to access associated soft-deleted variants.
def variant
Spree::Variant.unscoped { super }
@@ -16,9 +14,5 @@ module Spree
self.currency = Spree::Config[:currency]
end
end
def refresh_products_cache
variant.andand.refresh_products_cache
end
end
end

View File

@@ -38,7 +38,6 @@ Spree::Product.class_eval do
after_save :remove_previous_primary_taxon_from_taxons
after_save :ensure_standard_variant
after_save :update_units
after_save :refresh_products_cache
# -- Joins
scope :with_order_cycles_outer, -> {
@@ -192,23 +191,17 @@ Spree::Product.class_eval do
def destroy_with_delete_from_order_cycles
transaction do
OpenFoodNetwork::ProductsCache.product_deleted(self) do
touch_distributors
touch_distributors
ExchangeVariant.
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted.
select(:id)).destroy_all
ExchangeVariant.
where('exchange_variants.variant_id IN (?)', variants_including_master.with_deleted.
select(:id)).destroy_all
destroy_without_delete_from_order_cycles
end
destroy_without_delete_from_order_cycles
end
end
alias_method_chain :destroy, :delete_from_order_cycles
def refresh_products_cache
OpenFoodNetwork::ProductsCache.product_changed self
end
private
def set_available_on_to_now

View File

@@ -1,10 +1,5 @@
module Spree
ProductProperty.class_eval do
belongs_to :product, class_name: "Spree::Product", touch: true
after_save :refresh_products_cache
after_destroy :refresh_products_cache
delegate :refresh_products_cache, to: :product
end
end

View File

@@ -3,6 +3,20 @@ class Spree::ProductSet < ModelSet
super(Spree::Product, [], attributes, proc { |attrs| attrs[:product_id].blank? })
end
def save
@collection_hash.each_value.all? do |product_attributes|
update_attributes(product_attributes)
end
end
def collection_attributes=(attributes)
@collection = Spree::Product
.where(id: attributes.each_value.map { |product| product[:id] })
@collection_hash = attributes
end
private
# A separate method of updating products was required due to an issue with
# the way Rails' assign_attributes and updates_attributes behave when
# delegated attributes of a nested object are updated via the parent object
@@ -19,14 +33,11 @@ class Spree::ProductSet < ModelSet
def update_attributes(attributes)
split_taxon_ids!(attributes)
found_model = @collection.find do |model|
model.id.to_s == attributes[:id].to_s && model.persisted?
end
if found_model.nil?
product = find_model(@collection, attributes[:id])
if product.nil?
@klass.new(attributes).save unless @reject_if.andand.call(attributes)
else
update_product(found_model, attributes)
update_product(product, attributes)
end
end
@@ -34,28 +45,34 @@ class Spree::ProductSet < ModelSet
attributes[:taxon_ids] = attributes[:taxon_ids].split(',') if attributes[:taxon_ids].present?
end
def update_product(found_model, attributes)
update_product_only_attributes(found_model, attributes) &&
update_product_variants(found_model, attributes) &&
update_product_master(found_model, attributes)
def update_product(product, attributes)
original_supplier = product.supplier_id
return false unless update_product_only_attributes(product, attributes)
ExchangeVariantDeleter.new.delete(product) if original_supplier != product.supplier_id
update_product_variants(product, attributes) &&
update_product_master(product, attributes)
end
def update_product_only_attributes(product, attributes)
variant_related_attrs = [:id, :variants_attributes, :master_attributes]
product_related_attrs = attributes.except(*variant_related_attrs)
return true if product_related_attrs.blank?
product.assign_attributes(product_related_attrs)
product.variants.each do |variant|
validate_presence_of_unit_value(product, variant)
end
validate_presence_of_unit_value_in_product(product)
product.save if errors.empty?
product.errors.empty? && product.save
end
def validate_presence_of_unit_value(product, variant)
def validate_presence_of_unit_value_in_product(product)
product.variants.each do |variant|
validate_presence_of_unit_value_in_variant(product, variant)
end
end
def validate_presence_of_unit_value_in_variant(product, variant)
return unless %w(weight volume).include?(product.variant_unit)
return if variant.unit_value.present?
@@ -79,12 +96,9 @@ class Spree::ProductSet < ModelSet
end
def create_or_update_variant(product, variant_attributes)
found_variant = product.variants_including_master.find do |variant|
variant.id.to_s == variant_attributes[:id].to_s && variant.persisted?
end
if found_variant.present?
found_variant.update_attributes(variant_attributes.except(:id))
variant = find_model(product.variants_including_master, variant_attributes[:id])
if variant.present?
variant.update_attributes(variant_attributes.except(:id))
else
create_variant(product, variant_attributes)
end
@@ -115,15 +129,9 @@ class Spree::ProductSet < ModelSet
end
end
def collection_attributes=(attributes)
@collection = Spree::Product
.where(id: attributes.each_value.map { |product| product[:id] })
@collection_hash = attributes
end
def save
@collection_hash.each_value.all? do |product_attributes|
update_attributes(product_attributes)
def find_model(collection, model_id)
collection.find do |model|
model.id.to_s == model_id.to_s && model.persisted?
end
end
end

View File

@@ -20,19 +20,8 @@ module Spree
merge(OrderCycle.active)
}
after_save :refresh_products_cache
# When a Property is destroyed, dependent-destroy will destroy all ProductProperties,
# which will take care of refreshing the products cache
def property
self
end
private
def refresh_products_cache
product_properties(:reload).each(&:refresh_products_cache)
end
end
end

View File

@@ -1,10 +0,0 @@
Spree::StockMovement.class_eval do
after_save :refresh_products_cache
private
def refresh_products_cache
return if stock_item.variant.blank?
OpenFoodNetwork::ProductsCache.variant_changed stock_item.variant
end
end

View File

@@ -4,8 +4,6 @@ Spree::Taxon.class_eval do
attachment_definitions[:icon][:path] = 'public/images/spree/taxons/:id/:style/:basename.:extension'
attachment_definitions[:icon][:url] = '/images/spree/taxons/:id/:style/:basename.:extension'
after_save :refresh_products_cache
# Indicate which filters should be used for this taxon
def applicable_filters
fs = []
@@ -49,10 +47,4 @@ Spree::Taxon.class_eval do
ts[t.enterprise_id.to_i] << t.id
end
end
private
def refresh_products_cache
products(:reload).each(&:refresh_products_cache)
end
end

View File

@@ -1,7 +1,6 @@
require 'open_food_network/enterprise_fee_calculator'
require 'open_food_network/variant_and_line_item_naming'
require 'concerns/variant_stock'
require 'open_food_network/products_cache'
Spree::Variant.class_eval do
extend Spree::LocalizedNumber
@@ -30,7 +29,6 @@ Spree::Variant.class_eval do
before_validation :update_weight_from_unit_value, if: ->(v) { v.product.present? }
after_save :update_units
after_save :refresh_products_cache
around_destroy :destruction
scope :with_order_cycles_inner, -> { joins(exchanges: :order_cycle) }
@@ -108,14 +106,6 @@ Spree::Variant.class_eval do
OpenFoodNetwork::EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for self
end
def refresh_products_cache
if is_master?
product.refresh_products_cache
else
OpenFoodNetwork::ProductsCache.variant_changed self
end
end
private
def update_weight_from_unit_value
@@ -123,21 +113,7 @@ Spree::Variant.class_eval do
end
def destruction
if is_master?
exchange_variants(:reload).destroy_all
yield
product.refresh_products_cache
else
OpenFoodNetwork::ProductsCache.variant_destroyed(self) do
# Remove this association here instead of using dependent: :destroy because
# dependent-destroy acts before this around_filter is called, so ProductsCache
# has no way of knowing which exchanges the variant was a member of.
exchange_variants(:reload).destroy_all
# Destroy the variant
yield
end
end
exchange_variants(:reload).destroy_all
yield
end
end

View File

@@ -12,9 +12,6 @@ class VariantOverride < ActiveRecord::Base
# Default stock can be nil, indicating stock should not be reset or zero, meaning reset to zero. Need to ensure this can be set by the user.
validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
after_save :refresh_products_cache_from_save
after_destroy :refresh_products_cache_from_destroy
default_scope where(permission_revoked_at: nil)
scope :for_hubs, lambda { |hubs|
@@ -73,14 +70,4 @@ class VariantOverride < ActiveRecord::Base
end
self
end
private
def refresh_products_cache_from_save
OpenFoodNetwork::ProductsCache.variant_override_changed self
end
def refresh_products_cache_from_destroy
OpenFoodNetwork::ProductsCache.variant_override_destroyed self
end
end

View File

@@ -62,7 +62,7 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
# # medium: LOGO_MEDIUM_URL
# # }
def attachment_urls(attachment, versions)
return unless attachment.exists?
return unless attachment.file?
versions.each_with_object({}) do |version, urls|
urls[version] = attachment.url(version)

View File

@@ -1,43 +1,11 @@
require 'open_food_network/scope_variant_to_hub'
class Api::ProductSerializer < ActiveModel::Serializer
# TODO
# Prices can't be cached? How?
def serializable_hash
cached_serializer_hash.merge(uncached_serializer_hash)
end
private
def cached_serializer_hash
Api::CachedProductSerializer.new(object, @options).serializable_hash
end
def uncached_serializer_hash
Api::UncachedProductSerializer.new(object, @options).serializable_hash
end
end
class Api::UncachedProductSerializer < ActiveModel::Serializer
attributes :price
def price
if options[:enterprise_fee_calculator]
object.master.price + options[:enterprise_fee_calculator].indexed_fees_for(object.master)
else
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
end
end
end
class Api::CachedProductSerializer < ActiveModel::Serializer
# cached
# delegate :cache_key, to: :object
include ActionView::Helpers::SanitizeHelper
attributes :id, :name, :permalink, :meta_keywords
attributes :group_buy, :notes, :description, :description_html
attributes :properties_with_values
attributes :properties_with_values, :price
has_many :variants, serializer: Api::VariantSerializer
has_one :master, serializer: Api::VariantSerializer
@@ -70,4 +38,12 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
def master
options[:master_variants][object.id].andand.first
end
def price
if options[:enterprise_fee_calculator]
object.master.price + options[:enterprise_fee_calculator].indexed_fees_for(object.master)
else
object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle])
end
end
end

View File

@@ -0,0 +1,7 @@
class ExchangeVariantDeleter
def delete(product)
ExchangeVariant.
where(variant_id: product.variants.select(:id)).
delete_all
end
end

View File

@@ -1,36 +1,42 @@
# Returns a (paginatable) AR object for the products or variants in stock for a given shop and OC.
# The stock-checking includes on_demand and stock level overrides from variant_overrides.
class OrderCycleDistributedProducts
def initialize(distributor, order_cycle)
def initialize(distributor, order_cycle, customer)
@distributor = distributor
@order_cycle = order_cycle
@customer = customer
end
def products_relation
Spree::Product.where(id: stocked_products)
Spree::Product.where(id: stocked_products).group("spree_products.id")
end
def variants_relation
@order_cycle.
variants_distributed_by(@distributor).
merge(stocked_variants_and_overrides)
order_cycle.
variants_distributed_by(distributor).
merge(stocked_variants_and_overrides).
select("DISTINCT spree_variants.*")
end
private
attr_reader :distributor, :order_cycle, :customer
def stocked_products
@order_cycle.
variants_distributed_by(@distributor).
order_cycle.
variants_distributed_by(distributor).
merge(stocked_variants_and_overrides).
select("DISTINCT spree_variants.product_id")
end
def stocked_variants_and_overrides
Spree::Variant.
stocked_variants = Spree::Variant.
joins("LEFT OUTER JOIN variant_overrides ON variant_overrides.variant_id = spree_variants.id
AND variant_overrides.hub_id = #{@distributor.id}").
AND variant_overrides.hub_id = #{distributor.id}").
joins(:stock_items).
where(query_stock_with_overrides)
ProductTagRulesFilterer.new(distributor, customer, stocked_variants).call
end
def query_stock_with_overrides

View File

@@ -0,0 +1,111 @@
# Takes a Spree::Variant AR object and filters results based on applicable tag rules.
# Tag rules exists in the context of enterprise, customer, and variant_overrides,
# and are applied to variant_overrides only. Returns a Spree::Variant AR object.
class ProductTagRulesFilterer
def initialize(distributor, customer, variants_relation)
@distributor = distributor
@customer = customer
@variants_relation = variants_relation
end
def call
return variants_relation unless distributor_rules.any?
filter(variants_relation)
end
private
attr_accessor :distributor, :customer, :variants_relation
def distributor_rules
@distributor_rules ||= TagRule::FilterProducts.prioritised.for(distributor).all
end
def filter(variants_relation)
return variants_relation unless overrides_to_hide.any?
variants_relation.where(query_with_tag_rules)
end
def query_with_tag_rules
"#{variant_not_overriden} OR ( #{variant_overriden}
AND ( #{override_not_hidden_by_rule}
OR #{override_shown_by_rule} ) )"
end
def variant_not_overriden
"variant_overrides.id IS NULL"
end
def variant_overriden
"variant_overrides.id IS NOT NULL"
end
def override_not_hidden_by_rule
return "FALSE" unless overrides_to_hide.any?
"variant_overrides.id NOT IN (#{overrides_to_hide.join(',')})"
end
def override_shown_by_rule
return "FALSE" unless overrides_to_show.any?
"variant_overrides.id IN (#{overrides_to_show.join(',')})"
end
def overrides_to_hide
@overrides_to_hide ||= VariantOverride.where(hub_id: distributor.id).
tagged_with(default_rule_tags + hide_rule_tags, any: true).
pluck(:id)
end
def overrides_to_show
@overrides_to_show ||= VariantOverride.where(hub_id: distributor.id).
tagged_with(show_rule_tags, any: true).
pluck(:id)
end
def default_rule_tags
default_rules.map(&:preferred_variant_tags)
end
def hide_rule_tags
hide_rules.map(&:preferred_variant_tags)
end
def show_rule_tags
show_rules.map(&:preferred_variant_tags)
end
def default_rules
# These rules hide a variant_override with tag X and apply to all customers
distributor_rules.select(&:is_default?)
end
def non_default_rules
# These rules show or hide a variant_override with tag X for customer with tag Y
distributor_rules.reject(&:is_default?)
end
def customer_applicable_rules
# Rules which apply specifically to the current customer
@customer_applicable_rules ||= non_default_rules.select{ |rule| customer_tagged?(rule) }
end
def hide_rules
@hide_rules ||= customer_applicable_rules.
select{ |rule| rule.preferred_matched_variants_visibility == 'hidden' }
end
def show_rules
customer_applicable_rules - hide_rules
end
def customer_tagged?(rule)
customer_tag_list.include? rule.preferred_customer_tags
end
def customer_tag_list
customer.andand.tag_list || []
end
end

View File

@@ -0,0 +1,98 @@
require 'open_food_network/scope_product_to_hub'
class ProductsRenderer
class NoProducts < RuntimeError; end
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 10
def initialize(distributor, order_cycle, customer, args = {})
@distributor = distributor
@order_cycle = order_cycle
@customer = customer
@args = args
end
def products_json
raise NoProducts unless order_cycle && distributor && products
ActiveModel::ArraySerializer.new(products,
each_serializer: Api::ProductSerializer,
current_order_cycle: order_cycle,
current_distributor: distributor,
variants: variants_for_shop_by_id,
master_variants: master_variants_for_shop_by_id,
enterprise_fee_calculator: enterprise_fee_calculator).to_json
end
private
attr_reader :order_cycle, :distributor, :customer, :args
def products
return unless order_cycle
@products ||= begin
results = distributed_products.products_relation.order(taxon_order)
filter_and_paginate(results).
each { |product| product_scoper.scope(product) } # Scope results with variant_overrides
end
end
def product_scoper
OpenFoodNetwork::ScopeProductToHub.new(distributor)
end
def enterprise_fee_calculator
OpenFoodNetwork::EnterpriseFeeCalculator.new distributor, order_cycle
end
def filter_and_paginate(query)
query.
ransack(args[:q]).
result.
page(args[:page] || DEFAULT_PAGE).
per(args[:per_page] || DEFAULT_PER_PAGE)
end
def distributed_products
OrderCycleDistributedProducts.new(distributor, order_cycle, customer)
end
def taxon_order
if distributor.preferred_shopfront_taxon_order.present?
distributor
.preferred_shopfront_taxon_order
.split(",").map { |id| "spree_products.primary_taxon_id=#{id} DESC" }
.join(", ") + ", spree_products.name ASC, spree_products.id ASC"
else
"spree_products.name ASC"
end
end
def variants_for_shop
@variants_for_shop ||= begin
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
distributed_products.variants_relation.
includes(:default_price, :stock_locations, :product).
where(product_id: products).
each { |v| scoper.scope(v) } # Scope results with variant_overrides
end
end
def variants_for_shop_by_id
index_by_product_id variants_for_shop.reject(&:is_master)
end
def master_variants_for_shop_by_id
index_by_product_id variants_for_shop.select(&:is_master)
end
def index_by_product_id(variants)
variants.each_with_object({}) do |v, vs|
vs[v.product_id] ||= []
vs[v.product_id] << v
end
end
end

View File

@@ -1,31 +0,0 @@
- content_for :page_title do
= t(:cache_settings)
= form_tag main_app.admin_cache_settings_path, :method => :put do
.field
= hidden_field_tag 'preferences[enable_products_cache?]', '0'
= check_box_tag 'preferences[enable_products_cache?]', '1', Spree::Config[:enable_products_cache?]
= label_tag nil, t('.enable_products_cache')
.form-buttons
= button t(:update), 'icon-refresh'
%br
%br
%h4= t(:cache_state)
%br
%table.index
%thead
%tr
%th= t('.distributor')
%th= t('.order_cycle')
%th= t('.status')
%th= t('.diff')
%tbody
- @results.each do |result|
%tr
%td= result[:distributor].name
%td= result[:order_cycle].name
%td= result[:status] ? t(:ok) : t('.error')
%td
%pre= result[:diff].to_s(:text)

View File

@@ -1,4 +1,4 @@
= content_for :page_title do
- content_for :page_title do
= t 'admin_enterprise_groups'
- if admin_user?

View File

@@ -1,6 +1,7 @@
.row
.alpha.two.columns
= f.label :name, t('.name')
%span.required *
.six.columns.omega
- if viewing_as_coordinator_of?(@order_cycle)
= f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true, 'ng-disabled' => '!loaded()'

View File

@@ -3,6 +3,7 @@
.row
.alpha.two.columns
= label_tag t('.ready_for')
%span.required *
.six.columns
= text_field_tag 'order_cycle_outgoing_exchange_0_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_time', 'required' => 'required', 'placeholder' => t('.ready_for_placeholder'), 'ng-model' => 'outgoing_exchange.pickup_time', 'size' => 30, 'maxlength' => 35
%span.icon-question-sign{'ofn-with-tip' => t('admin.order_cycles.exchange_form.pickup_time_tip')}

View File

@@ -1,4 +1,4 @@
= content_for :page_title do
- content_for :page_title do
= t :admin_order_cycles
- content_for :main_ng_app_name do

View File

@@ -2,7 +2,7 @@
#{t('admin.product_import.title')}
= render partial: 'ams_data'
= render partial: 'admin/shared/product_sub_menu'
= render partial: 'spree/admin/shared/product_sub_menu'
.import-wrapper{ng: {app: 'admin.productImport', controller: 'ImportFormCtrl'}}

View File

@@ -1,7 +1,7 @@
- content_for :page_title do
#{t('admin.product_import.title')}
= render partial: 'admin/shared/product_sub_menu'
= render partial: 'spree/admin/shared/product_sub_menu'
= render 'upload_sidebar'

View File

@@ -1,4 +1,4 @@
= content_for :sub_menu do
- content_for :sub_menu do
%ul#sub_nav.inline-menu{"data-hook" => "admin_enterprise_sub_tabs"}
= tab :enterprises, url: main_app.admin_enterprises_path
= tab :enterprise_relationships, url: main_app.admin_enterprise_relationships_path

View File

@@ -1,8 +0,0 @@
= content_for :sub_menu do
%ul#sub_nav.inline-menu
= tab :products, match_path: '/products'
= tab :option_types, match_path: '/option_types'
= tab :properties
= tab :prototypes
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory'
= tab :import, url: main_app.admin_product_import_path, match_path: '/product_import'

View File

@@ -1,4 +1,4 @@
= content_for :sub_menu do
- content_for :sub_menu do
%ul#sub_nav.inline-menu{"data-hook" => "admin_user_sub_tabs"}
= tab :users, url: spree.admin_users_path
= tab :roles, url: main_app.admin_enterprise_roles_path, match_path: '/enterprise_roles'

View File

@@ -5,4 +5,4 @@
%h1.page-title= t("admin.variant_overrides.index.title")
%a.with-tip{ 'data-powertip' => "#{t("admin.variant_overrides.index.description")}" }=t('admin.whats_this')
= render partial: 'admin/shared/product_sub_menu'
= render partial: 'spree/admin/shared/product_sub_menu'

View File

@@ -1,6 +1,6 @@
%form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } }
%save-bar{ dirty: "customers_form.$dirty", persist: "false" }
%input.red{ type: "button", value: "Save Changes", ng: { click: "update()", disabled: "!variant_overrides_form.$dirty" } }
%input.red{ type: "button", value: t(:save_changes), ng: { click: "update()", disabled: "!variant_overrides_form.$dirty" } }
%table.index.bulk#variant-overrides
%col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } }
%col.product{ width: "20%", ng: { show: 'columns.product.visible' } }

View File

@@ -14,7 +14,7 @@
%td.reset{ ng: { show: 'columns.reset.visible' } }
%input{name: 'variant-overrides-{{ variant.id }}-resettable', type: 'checkbox', ng: {model: 'variantOverrides[hub_id][variant.id].resettable'}, placeholder: '{{ variant.resettable }}', 'ofn-track-variant-override' => 'resettable'}
%td.reset{ ng: { show: 'columns.reset.visible' } }
%input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : "Default stock"}}', 'ofn-track-variant-override' => 'default_stock'}
%input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : ("admin.variant_overrides.index.default_stock" | t)}}', 'ofn-track-variant-override' => 'default_stock'}
%td.inheritance{ ng: { show: 'columns.inheritance.visible' } }
%input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true }
%td.tags{ ng: { show: 'columns.tags.visible' } }

View File

@@ -1,18 +1,17 @@
%h3
= "#{t(:email_welcome)}!"
= "#{t(".email_welcome")}!"
%p.lead
%strong
= @enterprise.name
= "#{t(:email_registered)} #{ Spree::Config.site_name }!"
= "#{t(".email_registered")} #{ Spree::Config.site_name }!"
%p
= t :email_userguide_html, link: link_to('Open Food Network User Guide', ContentConfig.user_guide_link)
= t(".email_userguide_html", link: link_to(t(".userguide"), ContentConfig.user_guide_link))
%p
= t(".email_admin_html", link: link_to(t(".admin_panel"), spree.admin_url))
%p
= t :email_admin_html, link: link_to('Admin Panel', spree.admin_url)
%p
= t :email_community_html, link: link_to(t(:join_community), ContentConfig.community_forum_url)
= t(".email_community_html", link: link_to(t(".join_community"), ContentConfig.community_forum_url))
= render 'shared/mailers/signoff'

View File

@@ -1,5 +1,5 @@
.filter-shopfront.taxon-selectors.text-right
%single-line-selectors{ selectors: "taxonSelectors", objects: "Products.products | products:query | properties:activeProperties | taxonsOf", "active-selectors" => "activeTaxons"}
.filter-shopfront.taxon-selectors.text-right{ng: {show: 'supplied_taxons != null'}}
%single-line-selectors{ selectors: "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"}
.filter-shopfront.property-selectors.text-right
%single-line-selectors{ selectors: "propertySelectors", objects: "Products.products | products:query | taxons:activeTaxons | propertiesOf", "active-selectors" => "activeProperties"}
.filter-shopfront.property-selectors.text-right{ng: {show: 'supplied_properties != null'}}
%single-line-selectors{ selectors: "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"}

View File

@@ -22,14 +22,14 @@
.small-12.medium-6.large-5.columns
%input#search.text{"ng-model" => "query",
placeholder: t(:products_search),
"ng-debounce" => "100",
"ng-debounce" => "200",
"ofn-disable-enter" => true}
.small-12.medium-6.large-6.large-offset-1.columns
= render partial: "shop/products/filters"
%div.pad-top{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'filteredProducts.length <= limit' }
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in visibleProducts track by product.id", "id" => "product-{{ product.id }}"}
%div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' }
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"}
= render "shop/products/summary"
%shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"}
@@ -41,7 +41,7 @@
.small-12.columns.text-center
%img.spinner{ src: "/assets/spinning-circles.svg" }
%div{"ng-show" => "filteredProducts.length == 0 && !Products.loading"}
%div{"ng-show" => "Products.products.length == 0 && !Products.loading"}
.row.summary
.small-12.columns
%p.no-results

View File

@@ -10,7 +10,7 @@
= line_item.single_money.to_html
%td.item-qty-show.align-center
- item.states.each do |state,count|
= "#{count} x #{t(state.humanize.downcase)}"
= "#{count} x #{t(state.humanize.downcase, scope: [:spree, :shipment_states], default: [:missing, "none"])}"
- unless shipment.shipped?
%td.item-qty-edit.hidden
= number_field_tag :quantity, item.quantity, :min => 0, :class => "line_item_quantity", :size => 5

View File

@@ -73,6 +73,8 @@
{{'js.admin.orders.shipment_states.' + order.shipment_state | t}}
%td
= mail_to "{{order.email}}"
%br
{{order.full_name}}
%td.align-center
%span{'ng-bind-html' => 'order.display_total'}
%td.actions

View File

@@ -7,15 +7,11 @@
- content_for :page_actions do
%ul.tollbar.inline-menu
%li
= link_to_add_fields Spree.t(:add_product_properties), 'tbody#product_properties', class: 'icon-plus button'
%li
%span#new_ptype_link
= link_to Spree.t(:select_from_prototype), available_admin_prototypes_url, remote: true, 'data-update' => 'prototypes', class: 'button icon-copy'
= link_to_add_fields t('.add_product_properties'), 'tbody#product_properties', class: 'icon-plus button'
= form_for @product, url: admin_product_url(@product), method: :put do |f|
%fieldset.no-border-top
.add_product_properties
#prototypes
= image_tag 'select2-spinner.gif', plugin: 'spree', style: 'display:none;', id: 'busy_indicator'
%table.index.sortable{"data-sortable-link" => update_positions_admin_product_product_properties_url}
@@ -31,7 +27,7 @@
= render partial: 'product_property_fields', locals: { f: pp_form }
= f.check_box :inherits_properties
= f.label :inherits_properties, t(".inherits_properties_checkbox_hint", supplier: @product.supplier.name)
= f.label :inherits_properties, t('.inherits_properties_checkbox_hint', supplier: @product.supplier.name)
%br
%br

View File

@@ -1,17 +1,17 @@
.row{"data-hook" => "admin_product_meta_form"}
.alpha.eleven.columns
= f.field_container :meta_keywords do
= f.label :meta_keywords, t(:product_search_keywords)
%span.icon-question-sign{ 'ofn-with-tip' => t('admin.products.product_search_tip') }
= f.label :meta_keywords, t('admin.products.seo.product_search_keywords')
%span.icon-question-sign{ 'ofn-with-tip' => t('admin.products.seo.product_search_tip') }
%br/
= f.text_field :meta_keywords, :class => 'fullwidth', :rows => 6
= f.field_container :meta_description do
= f.label :meta_description, t(:SEO_keywords)
%span.icon-question-sign{ 'ofn-with-tip' => t('admin.products.seo_tip') }
= f.label :meta_description, t('admin.products.seo.SEO_keywords')
%span.icon-question-sign{ 'ofn-with-tip' => t('admin.products.seo.seo_tip') }
%br/
= f.text_field :meta_description, :class => 'fullwidth', :rows => 6
.alpha.eleven.columns
= f.field_container :notes do
= f.label :notes, t(:notes)
= f.text_area :notes, { :class => 'fullwidth', rows: 5 }
= f.error_message_on :notes
= f.error_message_on :notes

View File

@@ -1,4 +1,4 @@
= render partial: 'admin/shared/product_sub_menu'
= render partial: 'spree/admin/shared/product_sub_menu'
= render :partial => 'spree/admin/shared/product_tabs', :locals => { :current => 'Group Buy Options' }
= render :partial => 'spree/shared/error_messages', :locals => { :target => @product }

View File

@@ -7,6 +7,6 @@
%li#new_product_link
= button_link_to t(:new_product), new_object_url, { :remote => true, :icon => 'icon-plus', :id => 'admin_new_product' }
= render partial: 'admin/shared/product_sub_menu'
= render partial: 'spree/admin/shared/product_sub_menu'
%div#new_product(data-hook)

View File

@@ -1,6 +1,3 @@
%div{ 'ng-show' => '!spree_api_key_ok' }
{{ api_error_msg }}
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
%br
%img.spinner{ src: "/assets/spinning-circles.svg" }

View File

@@ -101,19 +101,3 @@
angular.element(document.getElementById("new_product")).ready(function() {
angular.bootstrap(document.getElementById("new_product"), ['admin.products']);
});
:javascript
(function($){
var base_url = "#{admin_prototypes_url}";
var prototype_select = $('#product_prototype_id');
prototype_select.change(function() {
var id = prototype_select.val();
if (id.length) {
$('#product-from-prototype').load([ base_url, id ].join("/"));
} else {
$('#product-from-prototype').empty();
}
})
if (prototype_select.html() == "") {
prototype_select.change();
}
})(jQuery);

View File

@@ -1,5 +1,5 @@
= render partial: 'admin/shared/product_sub_menu'
= render :partial => 'spree/admin/shared/product_tabs', :locals => { :current => t(:Search) }
= render partial: 'spree/admin/shared/product_sub_menu'
= render :partial => 'spree/admin/shared/product_tabs', :locals => { :current => t(:search) }
= render :partial => 'spree/shared/error_messages', :locals => { :target => @product }
%div{ 'ng-app' => 'ofn.admin' }

View File

@@ -21,7 +21,6 @@
= configurations_sidebar_menu_item Spree.t(:shipping_categories), admin_shipping_categories_path
= configurations_sidebar_menu_item t(:enterprise_fees), main_app.admin_enterprise_fees_path
= configurations_sidebar_menu_item Spree.t(:analytics_trackers), admin_trackers_path
= configurations_sidebar_menu_item t('admin.cache_settings.edit.title'), main_app.edit_admin_cache_settings_path
= configurations_sidebar_menu_item t('admin.contents.edit.title'), main_app.edit_admin_contents_path
= configurations_sidebar_menu_item t('admin.invoice_settings.edit.title'), main_app.edit_admin_invoice_settings_path
= configurations_sidebar_menu_item t('admin.matomo_settings.edit.title'), main_app.edit_admin_matomo_settings_path

View File

@@ -1,4 +1,4 @@
.form-buttons.filter-actions.actions
= button Spree.t('actions.update'), 'icon-refresh'
%span.or= Spree.t(:or)
= button_link_to Spree.t('actions.cancel'), collection_url, icon: 'icon-remove'
= button t(:update), 'icon-refresh'
%span.or= t(:or)
= button_link_to t(:cancel), collection_url, icon: 'icon-remove'

View File

@@ -33,14 +33,14 @@
%dd#shipment_status
- shipment_state_classes = "state #{@order.shipment_state}"
%span{ class: shipment_state_classes }
= t(@order.shipment_state, scope: :shipment_states, default: [:missing, "none"])
= t(@order.shipment_state, scope: [:spree, :shipment_states], default: [:missing, "none"])
%dt
= t(:payment)
\:
%dd#payment_status
- payment_state_classes = "state #{@order.payment_state}"
%span{ class: payment_state_classes }
= t(@order.payment_state, scope: :payment_states, default: [:missing, "none"])
= t(@order.payment_state, scope: [:spree, :payment_states], default: [:missing, "none"])
%dt
= t(:date_completed)
\:

View File

@@ -1,6 +1,6 @@
- content_for :sub_menu do
%ul#sub_nav.inline-menu
= tab :products, match_path: '/products'
= tab :option_types, match_path: '/option_types'
= tab :properties
= tab :prototypes
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory'
= tab :import, url: main_app.admin_product_import_path, match_path: '/product_import'

View File

@@ -1,33 +1,33 @@
= content_for :page_title do
= Spree.t(:editing_product)
- content_for :page_title do
= t('admin.products.editing_product')
= "\"#{@product.name}\""
= content_for :sidebar_title do
- content_for :sidebar_title do
%span.sku
= @product.sku
= content_for :sidebar do
- content_for :sidebar do
%nav.menu
%ul
- if can?(:admin, Spree::Product)
- klass = current == 'Product Details' ? 'active' : ''
%li{:class => klass}
= link_to_with_icon 'icon-edit', Spree.t(:product_details), edit_admin_product_url(@product)
= link_to_with_icon 'icon-edit', t('admin.products.tabs.product_details'), edit_admin_product_url(@product)
- if can?(:admin, Spree::Image)
- klass = current == 'Images' ? 'active' : ''
%li{:class => klass}
= link_to_with_icon 'icon-picture', Spree.t(:images), admin_product_images_url(@product)
= link_to_with_icon 'icon-picture', t('admin.products.tabs.images'), admin_product_images_url(@product)
- if can?(:admin, Spree::Variant)
- klass = current == 'Variants' ? 'active' : ''
%li{:class => klass}
= link_to_with_icon 'icon-th-large', Spree.t(:variants), admin_product_variants_url(@product)
= link_to_with_icon 'icon-th-large', t('admin.products.tabs.variants'), admin_product_variants_url(@product)
- if can?(:admin, Spree::ProductProperty)
- klass = current == 'Product Properties' ? 'active' : ''
%li{:class => klass}
= link_to_with_icon 'icon-tasks', Spree.t(:product_properties), admin_product_product_properties_url(@product)
= link_to_with_icon 'icon-tasks', t('admin.products.tabs.product_properties'), admin_product_product_properties_url(@product)
- klass = current == 'Group Buy Options' ? 'active' : ''
%li{:class => klass}
= link_to_with_icon 'icon-tasks', t('admin.products.group_buy_options'), group_buy_options_admin_product_url(@product)
- klass = current == t(:Search) ? 'active' : ''
= link_to_with_icon 'icon-tasks', t('admin.products.tabs.group_buy_options'), group_buy_options_admin_product_url(@product)
- klass = current == t(:search) ? 'active' : ''
%li{:class => klass}
= link_to_with_icon 'icon-tasks', t(:Search), seo_admin_product_url(@product)
= link_to_with_icon 'icon-tasks', t(:search), seo_admin_product_url(@product)

View File

@@ -3,6 +3,6 @@
:variants_search => spree.admin_search_variants_path(:format => 'json'),
:taxons_search => main_app.api_taxons_path(:format => 'json'),
:user_search => spree.admin_search_users_path(:format => 'json'),
:orders_api => spree.api_orders_path(:format => 'json')
:orders_api => main_app.api_orders_path
}.to_json %>;
</script>

View File

@@ -1,5 +1,5 @@
= tab :dashboard, :route => :admin, :icon => 'icon-dashboard'
= tab :products , :option_types, :properties, :prototypes, :variants, :product_properties, :taxons, :url => admin_products_path, :icon => 'icon-th-large'
= tab :products, :option_types, :properties, :variants, :product_properties, :taxons, :url => admin_products_path, :icon => 'icon-th-large'
= tab :order_cycles, :url => main_app.admin_order_cycles_path, :icon => 'icon-refresh'
= tab :orders, :payments, :creditcard_payments, :shipments, :credit_cards, :return_authorizations, :url => admin_orders_path('q[s]' => 'completed_at desc'), :icon => 'icon-shopping-cart'
= tab :reports, :icon => 'icon-file'

View File

@@ -17,7 +17,7 @@
= label_tag nil, t("spree.tree")
%br/
:javascript
Spree.routes.taxonomy_taxons_path = "#{spree.api_taxonomy_taxons_path(@taxonomy)}";
Spree.routes.taxonomy_taxons_path = "#{main_app.api_taxonomy_taxons_path(@taxonomy)}";
Spree.routes.admin_taxonomy_taxons_path = "#{spree.admin_taxonomy_taxons_path(@taxonomy)}";
#taxonomy_tree.tree
#progress{style: "display:none;"}

View File

@@ -0,0 +1,18 @@
%fieldset.omega.six.columns
%legend= t('spree.api.access')
- if @user.spree_api_key.present?
.field
= label_tag t('spree.api.key')
= ":"
= @user.spree_api_key
.filter-actions.actions
= form_tag spree.clear_api_key_admin_user_path(@user), method: :put do
= button t('spree.api.clear_key'), 'icon-trash'
%span.or= t(:or)
= form_tag spree.generate_api_key_admin_user_path(@user), method: :put do
= button t('spree.api.regenerate_key'), 'icon-refresh'
- else
.no-objects-found= t('spree.api.no_key')
.filter-actions.actions
= form_tag spree.generate_api_key_admin_user_path(@user), method: :put do
= button t('spree.api.generate_key'), 'icon-key'

View File

@@ -13,3 +13,5 @@
= render partial: "form", locals: { f: f }
%div{"data-hook" => "admin_user_edit_form_button"}
= render partial: "spree/admin/shared/edit_resource_links"
= render partial: 'spree/admin/users/api_fields'

View File

@@ -1,7 +1,7 @@
= content_for :page_title do
- content_for :page_title do
= Spree.t(:new_user)
= content_for :page_actions do
- content_for :page_actions do
%li
= button_link_to Spree.t(:back_to_users_list), spree.admin_users_path, icon: 'icon-arrow-left'

View File

@@ -1,9 +1,9 @@
.label-block.left.six.columns.alpha{'ng-app' => 'admin.products'}
.field
= f.label :display_name, t(:display_name)
= f.label :display_name, t('.display_name')
= f.text_field :display_name, class: "fullwidth"
.field
= f.label :display_as, t(:display_as)
= f.label :display_as, t('.display_as')
= f.text_field :display_as, class: "fullwidth"
- if product_has_variant_unit_option_type?(@product)
@@ -29,13 +29,13 @@
- if opt = @variant.option_values.detect {|o| o.option_type == option_type }.try(:presentation)
= text_field(:new_variant, option_type.presentation, value: opt, disabled: 'disabled', class: 'fullwidth')
.field
= f.label :sku, Spree.t(:sku)
= f.label :sku, t('.sku')
= f.text_field :sku, class: 'fullwidth'
.field
= f.label :price, Spree.t(:price)
= f.label :price, t('.price')
= f.text_field :price, value: number_to_currency(@variant.price, unit: ''), class: 'fullwidth'
.field
= f.label :cost_price, Spree.t(:cost_price)
= f.label :cost_price, t('.cost_price')
= f.text_field :cost_price, value: number_to_currency(@variant.cost_price, unit: ''), class: 'fullwidth'
%div{ 'set-on-demand' => '' }
@@ -53,7 +53,7 @@
.right.six.columns.omega.label-block
- if @product.variant_unit != 'weight'
.field
= f.label 'weight', t('weight')+' (kg)'
= f.label 'weight', t(:weight)+' (kg)'
- value = number_with_precision(@variant.weight, precision: 2)
= f.text_field 'weight', value: value, class: 'fullwidth'

View File

@@ -14,9 +14,9 @@
%col{style: "width: 15%"}/
%thead
%tr
%th{colspan: "2"}= Spree.t(:options)
%th= Spree.t(:price)
%th= Spree.t(:sku)
%th{colspan: "2"}= t('.options')
%th= t('.price')
%th= t('.sku')
%th.actions
%tbody
- @variants.each do |variant|
@@ -31,24 +31,24 @@
= link_to_delete(variant, no_text: true) unless variant.deleted?
- unless @product.has_variants?
%tr
%td{colspan: "5"}= Spree.t(:none)
%td{colspan: "5"}= t(:none)
- else
.alpha.twelve.columns.no-objects-found
= Spree.t(:no_results)
= t('.no_results')
\.
- if @product.empty_option_values?
%p.first_add_option_types.no-objects-found
= Spree.t(:to_add_variants_you_must_first_define)
= link_to Spree.t(:option_types), admin_product_url(@product)
= Spree.t(:and)
= link_to Spree.t(:option_values), admin_option_types_url
= t('.to_add_variants_you_must_first_define')
= link_to t('.option_types'), admin_product_url(@product)
= t('.and')
= link_to t('.option_values'), admin_option_types_url
- else
- content_for :page_actions do
%ul.inline-menu
%li#new_var_link
= link_to_with_icon('icon-plus', Spree.t(:new_variant), new_admin_product_variant_url(@product), remote: true, 'data-update' => 'new_variant', class: 'button')
= link_to_with_icon('icon-plus', t('.new_variant'), new_admin_product_variant_url(@product), remote: true, 'data-update' => 'new_variant', class: 'button')
%li= link_to_with_icon('icon-filter', @deleted.blank? ? Spree.t(:show_deleted) : Spree.t(:show_active), admin_product_variants_url(@product, deleted: @deleted.blank? ? "on" : "off"), class: 'button')
%li= link_to_with_icon('icon-filter', @deleted.blank? ? t('.show_deleted') : t('.show_active'), admin_product_variants_url(@product, deleted: @deleted.blank? ? "on" : "off"), class: 'button')

View File

@@ -2,6 +2,6 @@
= form_for [:admin, @product, @variant] do |f|
%fieldset{'data-hook' => "admin_variant_new_form"}
%legend{align: "center"}= Spree.t(:new_variant)
%legend{align: "center"}= t('.new_variant')
= render partial: 'form', locals: { f: f }
= render partial: 'spree/admin/shared/new_resource_links'

View File

@@ -1,2 +0,0 @@
object false
node(:success) { "Use of API Authorised" }

3157
config/locales/ar.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -111,6 +111,14 @@ ca:
subject: "Sisplau, confirma l'adreça electrònica d'%{enterprise}"
welcome:
subject: "%{enterprise} és ara %{sitename}"
email_welcome: "Benvinguda"
email_registered: "ara forma part de"
email_userguide_html: "La Guia d'usuari amb suport detallat per configurar una productora o grup de consum és aquí: %{link}"
userguide: "Guia d'usuaris d'Open Food Network"
email_admin_html: "Pots gestionar el teu compte iniciant sessió a l'%{link} o fent clic a la pestanya a la part superior dreta de la pàgina d'inici i seleccionant Administració."
admin_panel: "Tauler dadministració"
email_community_html: "També tenim un fòrum en línia per debats de la comunitat relacionada amb el programari OFN i els desafiaments únics de triar endavant una organització alimentària. T'animem a unir-t'hi. Estem en constant evolució i les teves contribucions en aquest fòrum donaran forma al que passi a en el futur. %{link}"
join_community: "Uneix-te a la comunitat"
invite_manager:
subject: "%{enterprise} t'ha convidat a ser administrador"
producer_mailer:
@@ -320,15 +328,6 @@ ca:
number_localization:
number_localization_settings: "Configuració de localització numèrica"
enable_localized_number: "Utilitzeu l'estàndard internacional per separar milers/decimals"
cache_settings:
edit:
title: "Emmagatzematge ocult"
distributor: "Distribuïdora"
order_cycle: "Cicle de comanda"
status: "Estat"
diff: "Diferència"
error: "Error"
enable_products_cache: "Habilitar la memòria cau de productes?"
invoice_settings:
edit:
title: "Configuració de la factura"
@@ -422,19 +421,23 @@ ca:
av_on: "Disp. via"
import_date: S'ha importat
upload_an_image: Penja una imatge
product_search_keywords: Paraules clau de cerca de producte
product_search_tip: Escriviu paraules per ajudar-vos a cercar els vostres productes a les botigues. Utilitzeu espai per separar cada paraula clau.
SEO_keywords: Paraules clau de SEO
seo_tip: Escriviu paraules per ajudar-vos a cercar els vostres productes a la web. Utilitzeu espai per separar cada paraula clau.
Search: Cerca
seo:
product_search_keywords: "Paraules clau de cerca de producte"
product_search_tip: "Escriviu paraules per ajudar-vos a cercar els vostres productes a les botigues. Utilitzeu espai per separar cada paraula clau."
SEO_keywords: "Paraules clau de SEO"
seo_tip: "Escriviu paraules per ajudar-vos a cercar els vostres productes a la web. Utilitzeu espai per separar cada paraula clau."
search: "Cerca"
properties:
property_name: Nom de la propietat
inherited_property: Propietat heretada
property_name: "Nom de la propietat"
inherited_property: "Propietat heretada"
variants:
infinity: "Infinit"
to_order_tip: "Els articles preparats per encàrrec no tenen un nivell fixat d'existències, com ara pa fet sota comanda."
group_buy_options: "Opcions de compra en grup"
back_to_products_list: "Torna a la llista de productes"
tabs:
group_buy_options: "Opcions de compra en grup"
images: "Imatges"
product_properties: "Propietats del producte"
product_import:
title: Importació de productes
file_not_found: No s'ha trobat el fitxer o no s'ha pogut obrir
@@ -536,6 +539,7 @@ ca:
title: Inventari
description: Utilitzeu aquesta pàgina per administrar els inventaris de la vostres organitzacions. Tots els detalls del producte aquí establerts substituiran els establerts a la pàgina "Productes"
enable_reset?: Habilitar la restauració de valors de stock?
default_stock: "Estoc per defecte"
inherit?: Heredar?
add: Afegeix
hide: Amaga
@@ -1379,13 +1383,7 @@ ca:
products_in: "en %{oc}"
products_at: "a %{distributor}"
products_elsewhere: "Productes trobats en altres llocs"
email_welcome: "Benvinguda"
email_confirmed: "Gràcies per confirmar la teva adreça de correu electrònic."
email_registered: "ara forma part de"
email_userguide_html: "La Guia d'usuari amb suport detallat per configurar una productora o grup de consum és aquí: %{link}"
email_admin_html: "Pots gestionar el teu compte iniciant sessió a l'%{link} o fent clic a la pestanya a la part superior dreta de la pàgina d'inici i seleccionant Administració."
email_community_html: "També tenim un fòrum en línia per debats de la comunitat relacionada amb el programari OFN i els desafiaments únics de triar endavant una organització alimentària. T'animem a unir-t'hi. Estem en constant evolució i les teves contribucions en aquest fòrum donaran forma al que passi a en el futur. %{link}"
join_community: "Uneix-te a la comunitat"
email_confirmation_activate_account: "Abans de poder activar el compte nou hem de confirmar la teva adreça de correu electrònic."
email_confirmation_greeting: "Hola, %{contact}!"
email_confirmation_profile_created: "S'ha creat exitosament un perfil per %{name}. Per activar el teu perfil hem de confirmar aquesta adreça de correu electrònic."
@@ -2814,7 +2812,6 @@ ca:
products: "Productes "
option_types: "Tipus d'opcions"
properties: "Propietats"
prototypes: "Prototips"
variant_overrides: "Inventari"
reports: "Informes"
configuration: "Configuració"
@@ -3015,6 +3012,15 @@ ca:
email_confirmation:
confirmation_pending: "La confirmació de correu electrònic està pendent. Hem enviat un correu electrònic de confirmació a %{address}."
variants:
index:
sku: "Número de referència (SKU)"
price: "Preu"
no_results: "Sense resultats"
option_types: "Tipus d'opcions"
form:
sku: "Número de referència (SKU)"
price: "Preu"
display_as: "Mostra com"
autocomplete:
producer_name: "Productor"
unit: "Unitat"

View File

@@ -111,6 +111,10 @@ de_DE:
subject: "Bitte bestätigen Sie die E-Mail-Adresse für %{enterprise}"
welcome:
subject: "%{enterprise} ist jetzt auf %{sitename}"
email_welcome: "Willkommen"
email_registered: "ist jetzt Teil von"
email_community_html: "Wir haben auch ein Online-Forum für Community-Diskussionen in Bezug auf OFN-Software und die einzigartigen Herausforderungen eines Lebensmittelunternehmens. Reden Sie doch mit. Wir entwickeln uns ständig weiter und Ihr Beitrag in diesem Forum prägt, was als nächstes passiert. %{link}"
join_community: "Treten Sie der Community bei"
invite_manager:
subject: "%{enterprise} hat Sie eingeladen, ein Manager zu sein"
producer_mailer:
@@ -320,15 +324,6 @@ de_DE:
number_localization:
number_localization_settings: "Nummernlokalisierungseinstellungen"
enable_localized_number: "Verwenden Sie die internationale Tausendertrennungslogik"
cache_settings:
edit:
title: "Cachen"
distributor: "Verteiler"
order_cycle: "Bestellrunde"
status: "Status"
diff: "Diff"
error: "Fehler"
enable_products_cache: "Produktcache aktivieren?"
invoice_settings:
edit:
title: "Rechnungseinstellungen"
@@ -422,19 +417,23 @@ de_DE:
av_on: "Verfüg. am"
import_date: Importiert
upload_an_image: Bild hochladen
product_search_keywords: Stichwörter für die Produktsuche
product_search_tip: Geben Sie Wörter ein, um Ihre Produkte in den Geschäften zu suchen. Verwenden Sie Leerzeichen, um jedes Keyword zu trennen.
SEO_keywords: SEO Schlüsselwörter
seo_tip: Geben Sie Wörter ein, um Ihre Produkte im Internet zu durchsuchen. Verwenden Sie Leerzeichen, um jedes Keyword zu trennen.
Search: Suche
seo:
product_search_keywords: "Stichwörter für die Produktsuche"
product_search_tip: "Geben Sie Wörter ein, um Ihre Produkte in den Geschäften zu suchen. Verwenden Sie Leerzeichen, um jedes Keyword zu trennen."
SEO_keywords: "SEO Schlüsselwörter"
seo_tip: "Geben Sie Wörter ein, um Ihre Produkte im Internet zu durchsuchen. Verwenden Sie Leerzeichen, um jedes Keyword zu trennen."
search: "Suche"
properties:
property_name: Name der Eigenschaft
inherited_property: Vererbte Eigenschaft
property_name: "Name der Eigenschaft"
inherited_property: "Vererbte Eigenschaft"
variants:
infinity: "Unendlichkeit"
to_order_tip: "Artikel, die auf Bestellung hergestellt werden, haben keinen festgelegten Lagerbestand."
group_buy_options: "Gruppenkaufoptionen"
back_to_products_list: "Zurück zur Produktliste"
tabs:
group_buy_options: "Gruppenkaufoptionen"
images: "Bilder"
product_properties: "Produkteigenschaften"
product_import:
title: Produkte importieren
file_not_found: Datei nicht gefunden oder konnte nicht geöffnet werden
@@ -1379,13 +1378,7 @@ de_DE:
products_in: "in %{oc}"
products_at: "bei %{distributor}"
products_elsewhere: "Produkte an anderer Stelle"
email_welcome: "Willkommen"
email_confirmed: "Vielen Dank für die Bestätigung Ihrer E-Mail-Adresse."
email_registered: "ist jetzt Teil von"
email_userguide_html: "Das Benutzerhandbuch mit ausführlicher Unterstützung für die Einrichtung Ihres Producer oder Hub finden Sie hier: %{link}"
email_admin_html: "Sie können Ihr Konto verwalten, indem Sie sich bei %{link} anmelden oder indem Sie oben rechts auf der Startseite auf das Zahnrad klicken und Administration auswählen."
email_community_html: "Wir haben auch ein Online-Forum für Community-Diskussionen in Bezug auf OFN-Software und die einzigartigen Herausforderungen eines Lebensmittelunternehmens. Reden Sie doch mit. Wir entwickeln uns ständig weiter und Ihr Beitrag in diesem Forum prägt, was als nächstes passiert. %{link}"
join_community: "Treten Sie der Community bei"
email_confirmation_activate_account: "Bevor wir Ihr neues Konto aktivieren können, müssen wir Ihre E-Mail-Adresse bestätigen."
email_confirmation_greeting: "Hallo, %{contact}!"
email_confirmation_profile_created: "Ein Profil für %{name} wurde erfolgreich erstellt! Um Ihr Profil zu aktivieren, müssen wir diese E-Mail-Adresse bestätigen."
@@ -2814,7 +2807,6 @@ de_DE:
products: "Produkte"
option_types: "Optionstypen"
properties: "Eigenschaften"
prototypes: "Prototypen"
variant_overrides: "Katalog"
reports: "Berichte"
configuration: "Aufbau"
@@ -3015,6 +3007,13 @@ de_DE:
email_confirmation:
confirmation_pending: "E-Mail-Bestätigung steht aus. Wir haben eine Bestätigungs-E-Mail an %{address} gesendet."
variants:
index:
sku: "Artikelnummer"
price: "Preis"
form:
sku: "Artikelnummer"
price: "Preis"
display_as: "Angezeigt als"
autocomplete:
producer_name: "Produzent"
unit: "Einheit"

View File

@@ -136,6 +136,14 @@ en:
subject: "Please confirm the email address for %{enterprise}"
welcome:
subject: "%{enterprise} is now on %{sitename}"
email_welcome: "Welcome"
email_registered: "is now part of"
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
userguide: "Open Food Network User Guide"
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
admin_panel: "Admin Panel"
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
join_community: "Join the community"
invite_manager:
subject: "%{enterprise} has invited you to be a manager"
producer_mailer:
@@ -365,16 +373,6 @@ en:
number_localization_settings: "Number Localization Settings"
enable_localized_number: "Use the international thousand/decimal separator logic"
cache_settings:
edit:
title: "Caching"
distributor: "Distributor"
order_cycle: "Order Cycle"
status: "Status"
diff: "Diff"
error: "Error"
enable_products_cache: "Enable Products Cache?"
invoice_settings:
edit:
title: "Invoice Settings"
@@ -476,19 +474,26 @@ en:
av_on: "Av. On"
import_date: Imported
upload_an_image: Upload an image
product_search_keywords: Product Search Keywords
product_search_tip: Type words to help search your products in the shops. Use space to separate each keyword.
SEO_keywords: SEO Keywords
seo_tip: Type words to help search your products in the web. Use space to separate each keyword.
Search: Search
seo:
product_search_keywords: "Product Search Keywords"
product_search_tip: "Type words to help search your products in the shops. Use space to separate each keyword."
SEO_keywords: "SEO Keywords"
seo_tip: "Type words to help search your products in the web. Use space to separate each keyword."
search: "Search"
properties:
property_name: Property Name
inherited_property: Inherited Property
property_name: "Property Name"
inherited_property: "Inherited Property"
variants:
infinity: "Infinity"
to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order."
group_buy_options: "Group Buy Options"
back_to_products_list: "Back to products list"
editing_product: "Editing Product"
tabs:
product_details: "Product Details"
group_buy_options: "Group Buy Options"
images: "Images"
variants: "Variants"
product_properties: "Product Properties"
product_import:
title: Product Import
@@ -592,6 +597,7 @@ en:
title: Inventory
description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page
enable_reset?: Enable Stock Reset?
default_stock: "Default stock"
inherit?: Inherit?
add: Add
hide: Hide
@@ -1476,15 +1482,7 @@ en:
products_at: "at %{distributor}"
products_elsewhere: "Products found elsewhere"
email_welcome: "Welcome"
email_confirmed: "Thank you for confirming your email address."
email_registered: "is now part of"
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here:
%{link}"
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next.
%{link}"
join_community: "Join the community"
email_confirmation_activate_account: "Before we can activate your new account, we need to confirm your email address."
email_confirmation_greeting: "Hi, %{contact}!"
email_confirmation_profile_created: "A profile for %{name} has been successfully created!
@@ -2940,6 +2938,13 @@ See the %{link} to find out more about %{sitename}'s features and to start using
normal_amount: "Normal Amount"
discount_amount: "Discount Amount"
no_images_found: "No Images Found"
new_image: "New Image"
filename: "Filename"
alt_text: "Alternative Text"
thumbnail: "Thumbnail"
back_to_images_list: "Back To Images List"
# TODO: remove `email` key once we get to Spree 2.0
email: Email
# TODO: remove 'account_updated' key once we get to Spree 2.0
@@ -2953,6 +2958,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
zipcode: Postcode
weight: Weight (per kg)
error_user_destroy_with_orders: "Users with completed orders may not be deleted"
options: "Options"
actions:
update: "Update"
@@ -2972,7 +2978,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using
products: "Products"
option_types: "Option Types"
properties: "Properties"
prototypes: "Prototypes"
variant_overrides: "Inventory"
reports: "Reports"
configuration: "Configuration"
@@ -2986,6 +2991,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using
product_properties:
index:
inherits_properties_checkbox_hint: "Inherit properties from %{supplier}? (unless overridden above)"
add_product_properties: "Add Product Properties"
select_from_prototype: "Select From Prototype"
orders:
index:
listing_orders: "Listing Orders"
@@ -3173,6 +3180,26 @@ See the %{link} to find out more about %{sitename}'s features and to start using
email_confirmation:
confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}."
variants:
index:
sku: "SKU"
price: "Price"
options: "Options"
no_results: "No results"
to_add_variants_you_must_first_define: "To add variants, you must first define"
option_types: "Option Types"
option_values: "Option Values"
and: "and"
new_variant: "New Variant"
show_active: "Show Active"
show_deleted: "Show Deleted"
new:
new_variant: "New Variant"
form:
cost_price: "Cost Price"
sku: "SKU"
price: "Price"
display_as: "Display As"
display_name: "Display Name"
autocomplete:
producer_name: "Producer"
unit: "Unit"
@@ -3312,3 +3339,19 @@ See the %{link} to find out more about %{sitename}'s features and to start using
allow_charges?: "Allow Charges?"
localized_number:
invalid_format: has an invalid format. Please enter a number.
api:
invalid_api_key: "Invalid API key (%{key}) specified."
unauthorized: "You are not authorized to perform that action."
invalid_resource: "Invalid resource. Please fix errors and try again."
resource_not_found: "The resource you were looking for could not be found."
access: "API Access"
key: "Key"
clear_key: "Clear key"
regenerate_key: "Regenerate Key"
no_key: "No key"
generate_key: "Generate API key"
key_generated: "Key generated"
key_cleared: "Key cleared"
shipment:
cannot_ready: "Cannot ready shipment."
invalid_taxonomy_id: "Invalid taxonomy id."

View File

@@ -111,6 +111,12 @@ en_AU:
subject: "Please confirm the email address for %{enterprise}"
welcome:
subject: "%{enterprise} is now on %{sitename}"
email_welcome: "Welcome"
email_registered: "is now part of"
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
join_community: "Join the community"
invite_manager:
subject: "%{enterprise} has invited you to be a manager"
producer_mailer:
@@ -319,15 +325,6 @@ en_AU:
number_localization:
number_localization_settings: "Number Localization Settings"
enable_localized_number: "Use the international thousand/decimal separator logic"
cache_settings:
edit:
title: "Caching"
distributor: "Distributor"
order_cycle: "Order Cycle"
status: "Status"
diff: "Diff"
error: "Error"
enable_products_cache: "Enable Products Cache?"
invoice_settings:
edit:
title: "Invoice Settings"
@@ -421,19 +418,23 @@ en_AU:
av_on: "Av. On"
import_date: Imported
upload_an_image: Upload an image
product_search_keywords: Product Search Keywords
product_search_tip: Type words to help search your products in the shops. Use space to separate each keyword.
SEO_keywords: SEO Keywords
seo_tip: Type words to help search your products in the web. Use space to separate each keyword.
Search: Search
seo:
product_search_keywords: "Product Search Keywords"
product_search_tip: "Type words to help search your products in the shops. Use space to separate each keyword."
SEO_keywords: "SEO Keywords"
seo_tip: "Type words to help search your products in the web. Use space to separate each keyword."
search: "Search"
properties:
property_name: Property Name
inherited_property: Inherited Property
property_name: "Property Name"
inherited_property: "Inherited Property"
variants:
infinity: "Infinity"
to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order."
group_buy_options: "Group Buy Options"
back_to_products_list: "Back to products list"
tabs:
group_buy_options: "Group Buy Options"
images: "Images"
product_properties: "Product Properties"
product_import:
title: Product Import
file_not_found: File not found or could not be opened
@@ -1376,13 +1377,7 @@ en_AU:
products_in: "in %{oc}"
products_at: "at %{distributor}"
products_elsewhere: "Products found elsewhere"
email_welcome: "Welcome"
email_confirmed: "Thank you for confirming your email address."
email_registered: "is now part of"
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
join_community: "Join the community"
email_confirmation_activate_account: "Before we can activate your new account, we need to confirm your email address."
email_confirmation_greeting: "Hi, %{contact}!"
email_confirmation_profile_created: "A profile for %{name} has been successfully created! To activate your Profile we need to confirm this email address."
@@ -2805,7 +2800,6 @@ en_AU:
products: "Products"
option_types: "Option Types"
properties: "Properties"
prototypes: "Prototypes"
variant_overrides: "Inventory"
reports: "Reports"
configuration: "Configuration"
@@ -3006,6 +3000,15 @@ en_AU:
email_confirmation:
confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}."
variants:
index:
sku: "SKU"
price: "Price"
no_results: "No results"
option_types: "Option Types"
form:
sku: "SKU"
price: "Price"
display_as: "Display As"
autocomplete:
producer_name: "Producer"
unit: "Unit"

View File

@@ -110,6 +110,12 @@ en_BE:
subject: "Please confirm the email address for %{enterprise}"
welcome:
subject: "%{enterprise} is now on %{sitename}"
email_welcome: "Welcome"
email_registered: "is now part of"
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
join_community: "Join the community"
invite_manager:
subject: "%{enterprise} has invited you to be a manager"
producer_mailer:
@@ -317,15 +323,6 @@ en_BE:
number_localization:
number_localization_settings: "Number Localization Settings"
enable_localized_number: "Use the international thousand/decimal separator logic"
cache_settings:
edit:
title: "Caching"
distributor: "Distributor"
order_cycle: "Order Cycle"
status: "Status"
diff: "Diff"
error: "Error"
enable_products_cache: "Enable Products Cache?"
invoice_settings:
edit:
title: "Invoice Settings"
@@ -419,19 +416,23 @@ en_BE:
av_on: "Av. On"
import_date: Imported
upload_an_image: Upload an image
product_search_keywords: Product Search Keywords
product_search_tip: Type words to help search your products in the shops. Use space to separate each keyword.
SEO_keywords: SEO Keywords
seo_tip: Type words to help search your products in the web. Use space to separate each keyword.
Search: Search
seo:
product_search_keywords: "Product Search Keywords"
product_search_tip: "Type words to help search your products in the shops. Use space to separate each keyword."
SEO_keywords: "SEO Keywords"
seo_tip: "Type words to help search your products in the web. Use space to separate each keyword."
search: "Search"
properties:
property_name: Property Name
inherited_property: Inherited Property
property_name: "Property Name"
inherited_property: "Inherited Property"
variants:
infinity: "Infinity"
to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order."
group_buy_options: "Group Buy Options"
back_to_products_list: "Back to products list"
tabs:
group_buy_options: "Group Buy Options"
images: "Images"
product_properties: "Product Properties"
product_import:
title: Product Import
file_not_found: File not found or could not be opened
@@ -1297,12 +1298,12 @@ en_BE:
home_shop: Shop Now
brandstory_headline: "Food, unincorporated."
brandstory_intro: "Sometimes the best way to fix the system is to start a new one…"
brandstory_part1: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can seriously change the world."
brandstory_part2: "Then we need a way to make it real. A way to empower everyone who grows, sells and buys food. A way to tell all the stories, to handle all the logistics. A way to turn transaction into transformation every day."
brandstory_part3: "So we build an online marketplace that levels the playing field. Its transparent, so it creates real relationships. Its open source, so its owned by everyone. It scales to regions and nations, so people start versions across the world."
brandstory_part4: "It works everywhere. It changes everything."
brandstory_part1: "This is what we do by creating a platform that wants to connect:\n● passionate farmers, committed to sustainable and regenerative agriculture,\n● distributors of local products, followers of short circuits, who act in complete transparency and ensure fair remuneration of producers,\n● buyers who want to change the world by eating better,"
brandstory_part2: "This platform is called the Open Food Network."
brandstory_part3: "Transparent and open source, it promotes fair relations between farmers and consumers. Its objective is to create a quality food network thanks to an efficient and constantly evolving IT tool.\nIt is deployed in several countries. In Belgium, it is developed by Oxfam-World Stores to empower those who grow, sell and buy food."
brandstory_part4: "You are\n● A producer: register and present your products to promote them in your region. You energize the platform and connect with new customers.\n● A distributor: register and present your project to make it known and convince new customers.\n● A consumer: find products near you and discover their stories."
brandstory_part5_strong: "We call it Open Food Network."
brandstory_part6: "We all love food. Now we can love our food system too."
brandstory_part6: "We can all participate in the construction of a fairer food system that respects mankind and preserves the planet."
learn_body: "Explore models, stories and resources to support you to develop your fair food business or organisation. Find training, events and other opportunities to learn from peers."
learn_cta: "Get Inspired"
connect_body: "Search our full directories of producers, hubs and groups to find fair food traders near you. List your business or organisation on the OFN so buyers can find you. Join the community to get advice and solve problems together."
@@ -1370,13 +1371,7 @@ en_BE:
products_in: "in %{oc}"
products_at: "at %{distributor}"
products_elsewhere: "Products found elsewhere"
email_welcome: "Welcome"
email_confirmed: "Thank you for confirming your email address."
email_registered: "is now part of"
email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}"
email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}"
join_community: "Join the community"
email_confirmation_activate_account: "Before we can activate your new account, we need to confirm your email address."
email_confirmation_greeting: "Hi, %{contact}!"
email_confirmation_profile_created: "A profile for %{name} has been successfully created! To activate your Profile we need to confirm this email address."
@@ -2798,7 +2793,6 @@ en_BE:
products: "Products"
option_types: "Option Types"
properties: "Properties"
prototypes: "Prototypes"
variant_overrides: "Inventory"
reports: "Reports"
configuration: "Configuration"
@@ -2999,6 +2993,15 @@ en_BE:
email_confirmation:
confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}."
variants:
index:
sku: "SKU"
price: "Price"
no_results: "No results"
option_types: "Option Types"
form:
sku: "SKU"
price: "Price"
display_as: "Display As"
autocomplete:
producer_name: "Producer"
unit: "Unit"

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