Compare commits

...

281 Commits

Author SHA1 Message Date
Matt-Yorkley
fc9f61ecf8 Revert PR #4204
Temporarily reverting these changes for a quick release
2019-09-05 13:23:59 +01:00
Luis Ramos
07d4528276 Merge pull request #4174 from openfoodfoundation/dependabot/bundler/delayed_job_active_record-4.1.4
Bump delayed_job_active_record from 4.1.3 to 4.1.4
2019-09-02 22:24:04 +01:00
Luis Ramos
4ace780431 Merge pull request #4187 from openfoodfoundation/dependabot/bundler/knapsack-1.18.0
Bump knapsack from 1.17.2 to 1.18.0
2019-09-02 22:23:15 +01:00
dependabot-preview[bot]
51df8de64f Bump knapsack from 1.17.2 to 1.18.0
Bumps [knapsack](https://github.com/ArturT/knapsack) from 1.17.2 to 1.18.0.
- [Release notes](https://github.com/ArturT/knapsack/releases)
- [Changelog](https://github.com/ArturT/knapsack/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ArturT/knapsack/compare/v1.17.2...v1.18.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-02 16:16:32 +00:00
Matt-Yorkley
d4a5829858 Merge pull request #4081 from Matt-Yorkley/products_pagination
Bulk Edit Products pagination
2019-09-02 17:14:14 +01:00
Matt-Yorkley
ff5fe66994 Fix route after big API refactor merge resolution 2019-09-02 16:29:03 +01:00
Matt-Yorkley
37e50a68e4 Fix timezone date parsing from Angular form fields 2019-09-02 16:29:00 +01:00
Matt-Yorkley
a72c662b97 Update per_page dropdown UX 2019-09-02 14:35:00 +01:00
Matt-Yorkley
ff2db0c5f8 Update spec routes, namespacing, and test content
Resolving new issues after a big merge
2019-09-02 14:35:00 +01:00
Matt-Yorkley
e9c60a33b9 Hide search in dropdown for per_page options 2019-09-02 14:35:00 +01:00
Matt-Yorkley
8e059d3c69 Define a limit on paginated queries with no supplied value for per_page 2019-09-02 14:35:00 +01:00
Matt-Yorkley
806ba94a2e Clarify dependency on kaminari gem 2019-09-02 14:35:00 +01:00
Matt-Yorkley
4bec583bff Refactor import_date_scope 2019-09-02 14:35:00 +01:00
Matt-Yorkley
90256f9c28 Add defaults and pages tests 2019-09-02 14:35:00 +01:00
Matt-Yorkley
eb284c1742 Use constants for defaults 2019-09-02 14:35:00 +01:00
Matt-Yorkley
b614e17f48 Add test coverage for #bulk_products endpoint and test all filtering functions 2019-09-02 14:34:54 +01:00
Matt-Yorkley
5259eaae5f Merge pull request #4204 from Matt-Yorkley/cartastrophe
Cartastrophe averted :)
2019-09-02 12:26:08 +01:00
Matt-Yorkley
b0ad0fccfa Add some defaults to avoid returning zero values when not supplied in query 2019-09-02 12:00:32 +01:00
Matt-Yorkley
2a83ad8689 Improve UX and consistency in orders pagination and page changing 2019-09-02 11:59:13 +01:00
Matt-Yorkley
c127110192 Make import_date query modification conditional 2019-09-02 11:59:13 +01:00
Matt-Yorkley
0470725112 Refactor pagination data hash 2019-09-02 11:57:32 +01:00
Matt-Yorkley
0623bab084 Don't respond to a successful update by querying 500 arbitrary products 2019-09-02 11:56:00 +01:00
Matt-Yorkley
4a0df684c7 Adjust specs 2019-09-02 11:56:00 +01:00
Matt-Yorkley
7dccb5ba90 Changing per_page should also reset the query 2019-09-02 11:56:00 +01:00
Matt-Yorkley
5a4be24df0 Add "filter results" button 2019-09-02 11:56:00 +01:00
Matt-Yorkley
5cb5967977 Fix cleared filters submitting "0" as value in queries 2019-09-02 11:56:00 +01:00
Matt-Yorkley
aeb8d30dae Fix server-side import_date filtering 2019-09-02 11:56:00 +01:00
Matt-Yorkley
1822fd97a6 Tidy up filters 2019-09-02 11:54:28 +01:00
Matt-Yorkley
4ff3e9fe10 Update Angular loading conditionals 2019-09-02 11:54:28 +01:00
Matt-Yorkley
a63994440d Add pagination to UI 2019-09-02 11:54:28 +01:00
Matt-Yorkley
f6d0de1454 Improve pagination data in bulk products 2019-09-02 11:54:28 +01:00
Matt-Yorkley
9b0e27a9d1 Add new ProductResource 2019-09-02 11:46:42 +01:00
Matt-Yorkley
415d88f302 Fix indentation in bulk product controller 2019-09-02 11:46:42 +01:00
Pau Pérez Fabregat
f9c98ea9a1 Merge pull request #4199 from openfoodfoundation/transifex
Transifex
2019-09-02 12:33:12 +02:00
Pau Pérez Fabregat
369a5a8a2f Merge pull request #4101 from luisramos0/remove_variants_rabl
Convert spree/api/products and spree/api/variants views from rabl to AMS
2019-09-02 12:14:26 +02:00
Matt-Yorkley
62341c6381 Unit test access to associated objects after soft-delete 2019-08-31 10:06:47 +01:00
Luis Ramos
fa1becb791 Merge pull request #4063 from luisramos0/dead_code
Remove dead code under views/spree/shared
2019-08-30 22:39:01 +01:00
Matt-Yorkley
50a1704994 Make prices soft-deletable 2019-08-30 20:11:32 +01:00
Matt-Yorkley
302538c370 Add failing spec for cart issue 2019-08-30 15:32:45 +01:00
Maikel
0f80b6ce12 Merge pull request #4197 from kristinalim/fix/4195-fix_invalid_date_in_firefox
4195 Specify API date format when converting date to string in JS
2019-08-30 08:36:17 +10:00
Transifex-Openfoodnetwork
1df8fc903e Updating translations for config/locales/en_ZA.yml 2019-08-30 04:22:54 +10:00
Transifex-Openfoodnetwork
9a2dcb89af Updating translations for config/locales/en_ZA.yml 2019-08-30 04:22:46 +10:00
Transifex-Openfoodnetwork
1661591f6c Updating translations for config/locales/ca.yml 2019-08-29 23:12:46 +10:00
Transifex-Openfoodnetwork
6dde720039 Updating translations for config/locales/es.yml 2019-08-29 23:10:30 +10:00
Transifex-Openfoodnetwork
a54b725d6d Updating translations for config/locales/ca.yml 2019-08-29 23:09:39 +10:00
Luis Ramos
265e76e8ca Merge pull request #4074 from HugsDaniel/defacepocalypse
[Defacepocalypse] De-deface product properties index
2019-08-28 22:18:18 +01:00
Kristina Lim
1516069888 Specify API date format when converting date to string in JS 2019-08-29 02:44:14 +08:00
Luis Ramos
cd263b761c Merge pull request #4055 from luisramos0/remove_spree_api
Remove dependency to spree_api - step 1 - controllers and routes
2019-08-28 15:34:08 +01:00
Maikel
c952ad16ad Merge pull request #4163 from luisramos0/swagger
Add swagger.yaml to codebase
2019-08-28 14:46:51 +10:00
Maikel
ca09c58f26 Merge pull request #3985 from jonleighton/string-to-text
Convert several fields from string to text
2019-08-28 11:53:49 +10:00
Maikel Linke
e876a25d59 Fix flaky proxy order spec 2019-08-28 10:48:57 +10:00
Maikel Linke
2a780151be Update all locales with the latest Transifex translations 2019-08-28 09:54:31 +10:00
Maikel
9d2009d2af Merge pull request #4188 from openfoodfoundation/transifex
Transifex
2019-08-28 09:43:02 +10:00
Maikel
f887533dda Merge pull request #4157 from mkllnk/4138-update-products
4138 Avoid infinite loop when updating products
2019-08-28 09:42:28 +10:00
Transifex-Openfoodnetwork
bef3f154d6 Updating translations for config/locales/fr_CA.yml 2019-08-28 09:12:59 +10:00
Transifex-Openfoodnetwork
6fb775d5ed Updating translations for config/locales/en_CA.yml 2019-08-28 09:05:39 +10:00
Transifex-Openfoodnetwork
b5a8563725 Updating translations for config/locales/en_GB.yml 2019-08-27 23:28:34 +10:00
Transifex-Openfoodnetwork
1a9ade6de9 Updating translations for config/locales/en_GB.yml 2019-08-27 23:25:26 +10:00
Maikel Linke
48df853ff5 Skip cascading callbacks when touching distributors 2019-08-27 10:44:19 +10:00
Maikel Linke
2137a2addb Add spec for current bug
https://github.com/openfoodfoundation/openfoodnetwork/issues/4138
2019-08-27 10:44:19 +10:00
Transifex-Openfoodnetwork
e6a7239716 Updating translations for config/locales/en_AU.yml 2019-08-27 10:09:05 +10:00
Transifex-Openfoodnetwork
25bed92f2e Updating translations for config/locales/en_AU.yml 2019-08-27 10:08:32 +10:00
Maikel
909cd407dd Merge pull request #4149 from openfoodfoundation/transifex
Transifex
2019-08-27 09:58:31 +10:00
Luis Ramos
f2d25748b1 Merge pull request #4078 from HugsDaniel/defacepocalypse-variants
[Defacepocalypse] De-deface variants
2019-08-25 22:51:15 +01:00
Luis Ramos
6396e6e970 Merge pull request #4152 from openfoodfoundation/dependabot/bundler/stripe-4.24.0
Bump stripe from 4.19.0 to 4.24.0
2019-08-25 22:49:47 +01:00
Luis Ramos
e52f813dae Merge pull request #4159 from luisramos0/fix_prod_set
Add bugsnag notifications to both product_set and products_controller when stock update fails
2019-08-25 22:49:06 +01:00
Luis Ramos
9ab2eec30c Merge pull request #4185 from kristinalim/fix/4176-fix_saving_of_shipping_method
4176 Fix saving of shipping method when automatically advancing subscription order state
2019-08-25 22:48:39 +01:00
Luis Ramos
f96b37dae3 Merge pull request #4162 from luisramos0/checkout_cache
Checkout is not refreshing products cache
2019-08-25 21:52:55 +01:00
Kristina Lim
5b68b2f707 Fix ship method when advancing subscription order 2019-08-22 21:32:41 +08:00
Kristina Lim
ff634bd870 Test ship method when advancing subscription order 2019-08-22 21:30:21 +08:00
Hugo Daniel
7d21d88dc9 Force hide the select2 close cross 2019-08-22 10:32:07 +02:00
dependabot-preview[bot]
31b62d6296 Bump delayed_job_active_record from 4.1.3 to 4.1.4
Bumps [delayed_job_active_record](https://github.com/collectiveidea/delayed_job_active_record) from 4.1.3 to 4.1.4.
- [Release notes](https://github.com/collectiveidea/delayed_job_active_record/releases)
- [Commits](https://github.com/collectiveidea/delayed_job_active_record/compare/v4.1.3...v4.1.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-20 19:14:59 +00:00
Transifex-Openfoodnetwork
5e68604f11 Updating translations for config/locales/nb.yml 2019-08-20 17:42:09 +10:00
Transifex-Openfoodnetwork
c4edd3a683 Updating translations for config/locales/fr.yml 2019-08-20 17:41:25 +10:00
Transifex-Openfoodnetwork
574781e901 Updating translations for config/locales/nb.yml 2019-08-20 17:39:01 +10:00
Transifex-Openfoodnetwork
8ea4f933da Updating translations for config/locales/fr.yml 2019-08-20 17:38:12 +10:00
Transifex-Openfoodnetwork
1e9820f291 Updating translations for config/locales/fr.yml 2019-08-20 17:35:05 +10:00
Luis Ramos
34ed86cf2d Merge pull request #4132 from Matt-Yorkley/import_launch
Product Import beta
2019-08-19 17:17:16 +01:00
Kevin Christianson
2dfcedad56 Add swagger.yaml 2019-08-18 18:26:44 +01:00
Kristina Lim
706168f2f0 Refresh variant cache when changing stock movements 2019-08-18 16:22:12 +08:00
Kristina Lim
3ecb5c0c75 Flush background jobs in failing spec 2019-08-18 18:16:28 +10:00
luisramos0
249a3c4e18 Make product set raise error and inform the user something went wrong and keep bugsnag notification so we can get more information about what's going on 2019-08-17 19:19:46 +01:00
luisramos0
2b8ebba233 Fix some rubocop issues in product_set and admin/products_controller 2019-08-17 19:15:32 +01:00
luisramos0
758394464b Add bugsnag notification to products_controller create and update when on_hand or on_demand update fails with exception 2019-08-17 19:15:32 +01:00
luisramos0
d3c624ae10 Add bugsnag notification to product_set create_variant when on_hand or on_demand update fails with exception 2019-08-17 19:15:31 +01:00
luisramos0
163c65849e Make product set a bit more robust by not failing to update on_hand when variant is not valid. This will make the overall set update work 2019-08-17 19:14:25 +01:00
luisramos0
99ff714913 Enable cache on checkout spec to test cache after checkout 2019-08-16 20:17:14 +01:00
luisramos0
c2f302450f Add spec to checkout to validate product is removed from shopfront just after all available quantity is checked out 2019-08-16 19:35:57 +01:00
luisramos0
9186bcd455 Dry out some code in checkout_spec 2019-08-16 19:35:08 +01:00
Transifex-Openfoodnetwork
3d074b530f Updating translations for config/locales/nb.yml 2019-08-15 19:16:43 +10:00
Transifex-Openfoodnetwork
20783db373 Updating translations for config/locales/en_GB.yml 2019-08-15 19:14:53 +10:00
Luis Ramos
d5b37a5171 Merge pull request #4079 from HugsDaniel/defacepocalypse-payments
[Defacepocalypse] De-deface payments
2019-08-14 19:30:33 +01:00
luisramos0
b9ddb39edc Re-add taxons jstree action to make taxonomies config page work again 2019-08-14 16:31:34 +01:00
Luis Ramos
3efe2f878d Merge pull request #4156 from coopdevs/fix-styling-in-paypal-tests
Fix Rubocop violations in PayPal tests
2019-08-14 15:59:14 +01:00
Luis Ramos
064fee79b3 Merge pull request #4151 from Matt-Yorkley/datadog_apm
Enable (optional) APM with Datadog
2019-08-14 15:58:48 +01:00
Luis Ramos
d13f182801 Merge pull request #4154 from Matt-Yorkley/fix_deployments2
Adjust user creation syntax
2019-08-14 15:56:32 +01:00
Luis Ramos
ee34935223 Fix a typo in docker.md 2019-08-14 13:55:58 +01:00
Pau Perez
0b7ce0d6db Fix Rubocop violations in PayPal tests 2019-08-14 13:33:53 +02:00
Matt-Yorkley
9dd02044a5 Fix "can't modify frozen string" error in validation callbacks 2019-08-14 10:24:30 +01:00
Matt-Yorkley
d8a7190f43 Adjust user creation syntax
#skip_confirmation! can't be chained...
2019-08-14 09:22:11 +01:00
Matt-Yorkley
e5e0fcc887 Enable DelayedJob APM 2019-08-14 08:20:47 +01:00
Pau Pérez Fabregat
53496ff9eb Merge branch 'master' into transifex 2019-08-14 09:08:40 +02:00
Pau Pérez Fabregat
6635a89af7 Merge pull request #4153 from Matt-Yorkley/fix_deployments
Fix deployments
2019-08-14 08:59:23 +02:00
Transifex-Openfoodnetwork
c1248857b8 Updating translations for config/locales/en_CA.yml 2019-08-14 09:15:20 +10:00
Matt-Yorkley
8c95399292 Don't prompt if admin user already exists 2019-08-13 21:02:12 +01:00
Matt-Yorkley
14c1abb861 Skip confirmation email on initial admin user creation 2019-08-13 21:00:30 +01:00
dependabot-preview[bot]
e2d61f5e89 Bump stripe from 4.19.0 to 4.24.0
Bumps [stripe](https://github.com/stripe/stripe-ruby) from 4.19.0 to 4.24.0.
- [Release notes](https://github.com/stripe/stripe-ruby/releases)
- [Changelog](https://github.com/stripe/stripe-ruby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-ruby/compare/v4.19.0...v4.24.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-13 19:18:17 +00:00
Transifex-Openfoodnetwork
5a1ef04c67 Updating translations for config/locales/en_ZA.yml 2019-08-14 04:57:27 +10:00
Transifex-Openfoodnetwork
e9e73ef0e4 Updating translations for config/locales/en_ZA.yml 2019-08-14 04:43:50 +10:00
Matt-Yorkley
be844253eb Enable (optional) Rails APM with Datadog 2019-08-13 18:57:49 +01:00
Transifex-Openfoodnetwork
001e3688da Updating translations for config/locales/en_GB.yml 2019-08-14 03:28:13 +10:00
luisramos0
b4ffd4dcc2 Update all locales with the latest Transifex translations 2019-08-13 18:25:23 +01:00
Transifex-Openfoodnetwork
e5a9606449 Updating translations for config/locales/en_GB.yml 2019-08-14 03:25:02 +10:00
Pau Pérez Fabregat
9eabbb8dab Merge pull request #4140 from openfoodfoundation/transifex
Transifex
2019-08-13 12:00:48 +02:00
Hugo Daniel
1217811402 Refactor and remove empty lines 2019-08-13 11:03:04 +02:00
Pau Pérez Fabregat
d4f2fcb98a Merge pull request #4126 from Matt-Yorkley/cart_dropdown
Cart dropdown now closes when clicking outside it.
2019-08-13 10:57:56 +02:00
Matt-Yorkley
0b2417849c Merge pull request #4142 from mkllnk/seed-user
Seed user
2019-08-13 08:42:07 +01:00
Matt-Yorkley
3e8369c8f5 Merge pull request #4123 from Matt-Yorkley/navigation-css
Navigation css
2019-08-13 08:41:47 +01:00
Maikel Linke
cbaedcec92 Fix seeding of default user without asking 2019-08-13 09:47:54 +10:00
Hugo Daniel
bf2c1a0c1d Add ng directive for setting on demand 2019-08-09 14:42:39 +02:00
Matt-Yorkley
0284eebf35 Fix cart dropdown positioning 2019-08-09 11:50:33 +01:00
Maikel Linke
e4ba515109 Seed admin user without asking by default 2019-08-09 14:16:01 +10:00
Maikel Linke
af1f6f6c6d Skip confirmation when creating first admin user 2019-08-09 14:16:01 +10:00
Maikel Linke
b0e8ee42c1 Restore spree_auth_devise user seeding 2019-08-09 13:48:50 +10:00
Maikel
86aeb6a3c7 Merge pull request #4135 from coopdevs/remove-bugherd-tests
Remove bugherd specs
2019-08-09 12:53:23 +10:00
Matt-Yorkley
5d83414e9b Disable rubocop for spree tabs helper 2019-08-08 20:09:35 +01:00
Transifex-Openfoodnetwork
9820d7de38 Updating translations for config/locales/ca.yml 2019-08-09 02:36:02 +10:00
Pau Pérez Fabregat
910eca810a Merge pull request #4124 from Matt-Yorkley/order_email
Add SKU to order confirmation email
2019-08-08 18:35:20 +02:00
Transifex-Openfoodnetwork
6feaddf747 Updating translations for config/locales/es.yml 2019-08-09 02:33:35 +10:00
Transifex-Openfoodnetwork
10f6fb91f6 Updating translations for config/locales/ca.yml 2019-08-09 02:32:54 +10:00
Pau Pérez Fabregat
c7bd4b94b8 Merge pull request #4137 from openfoodfoundation/transifex
Transifex
2019-08-08 18:31:47 +02:00
Transifex-Openfoodnetwork
dd223a21f2 Updating translations for config/locales/nb.yml 2019-08-08 20:58:24 +10:00
Transifex-Openfoodnetwork
549366ff15 Updating translations for config/locales/fr.yml 2019-08-08 20:52:28 +10:00
Transifex-Openfoodnetwork
e92f60fb1c Updating translations for config/locales/fr.yml 2019-08-08 20:49:20 +10:00
Pau Pérez Fabregat
92b05c76a3 Merge pull request #4068 from luisramos0/replace_oc_rabl
Convert order_cycle.rabl to Api::OrderCycleSerializer
2019-08-08 12:23:09 +02:00
Hugo Daniel
fa6fa0be64 Add missing translation 2019-08-08 11:11:32 +02:00
Kristina Lim
752efac7cf Merge pull request #4051 from luisramos0/subs_unpause
Sync subscription (create/update orders) when subscription is unpaused
2019-08-08 01:09:09 +08:00
Pau Perez
2b1f1f748b Remove bugherd specs
I don't think these are worth the time it takes to execute them every
time but most importantly I believe we are not using Bugherd anymore.
2019-08-07 16:49:32 +02:00
Pau Pérez Fabregat
798cd9e778 Merge pull request #4114 from kristinalim/fix/4103-add_specs
4103 Add specs for referencing of shipments to deleted variants
2019-08-07 12:52:11 +02:00
Matt-Yorkley
2f5b0a5afb Remove product import FeatureFlag 2019-08-07 09:59:55 +01:00
Maikel
de2d4a5870 Merge pull request #4125 from Matt-Yorkley/variant_overrides
Variant overrides with on_demand
2019-08-07 10:25:32 +10:00
Matt-Yorkley
b3728568a8 Enable product import for non-superadmin users
This class_eval hack for premissions on the #tab method was really hard to find, and is obviously quite ugly, but refactoring it is maybe outside of the scope here...
2019-08-06 18:59:12 +01:00
Matt-Yorkley
6ba98b4b2c Add beta notice to Product Import 2019-08-06 17:02:38 +01:00
Matt-Yorkley
d3d6921a0f Use light grey colour with transparency. 2019-08-06 08:23:38 +01:00
Maikel
68393f1444 Merge pull request #4037 from luisramos0/subs_stock
Subscriptions - When editing subscriptions, check stock levels when changing orders in current OC
2019-08-06 15:16:00 +10:00
Maikel
229e6fa0a3 Merge pull request #4073 from luisramos0/kill_more_rabl
Replace update_prod_image.rabl with ImageSerializer in api/product_image_controller
2019-08-06 15:00:33 +10:00
Matt-Yorkley
8a069787d1 Cart dropdown now closes when clicking outside it. 2019-08-05 21:48:37 +01:00
Matt-Yorkley
29ed38a6cc Add unit tests for overriding #move 2019-08-05 17:01:56 +01:00
Matt-Yorkley
4c7d3a491a Don't decrement variant stock if override is on_demand 2019-08-05 17:01:56 +01:00
Matt-Yorkley
b0f90cf43c Add test for variant override with :on_demand 2019-08-05 17:01:54 +01:00
Pau Pérez Fabregat
02ec0634b3 Merge pull request #4120 from openfoodfoundation/dependabot/bundler/webmock-3.6.2
Bump webmock from 3.6.0 to 3.6.2
2019-08-05 15:13:31 +02:00
Pau Pérez Fabregat
64d21969f0 Merge pull request #4064 from luisramos0/convert_search_Rabl
Replace views/admin/variants/search.rabl with Api::Admin::VariantSerializer
2019-08-05 15:07:10 +02:00
Pau Pérez Fabregat
0995adeb59 Merge pull request #4085 from openfoodfoundation/dependabot/bundler/devise-encryptable-0.2.0
Bump devise-encryptable from 0.1.2 to 0.2.0
2019-08-05 15:01:46 +02:00
Pau Pérez Fabregat
073a3fe2b4 Merge pull request #4119 from openfoodfoundation/transifex
Transifex
2019-08-05 13:18:12 +02:00
Matt-Yorkley
c07df6a5a6 Fix table column alignments 2019-08-04 20:45:00 +01:00
Matt-Yorkley
a7d109833d Add SKU to order confirmation email 2019-08-04 20:43:55 +01:00
Matt-Yorkley
0364a14073 Add navigation bottom border on mobile view 2019-08-04 16:56:43 +01:00
Matt-Yorkley
ddc8d33356 Update border colour on navigation bar 2019-08-04 16:56:03 +01:00
Kristina Lim
cd81dfaead Move deleted variant to let block in feature spec 2019-08-02 21:52:36 +08:00
Kristina Lim
25073ada84 Move order to a let block in feature spec 2019-08-02 21:52:33 +08:00
Kristina Lim
15b6f9dd5e Add specs testing edit order page still okay when variant deleted
Note that the wrapping example group also loads the edit order page
before this "before" block. This will be fixed in the next commit.
2019-08-02 21:45:39 +08:00
Kristina Lim
0377e02dc1 Add specs testing that shipments see deleted variants 2019-08-02 21:45:38 +08:00
dependabot-preview[bot]
f679708a4e Bump webmock from 3.6.0 to 3.6.2
Bumps [webmock](https://github.com/bblimke/webmock) from 3.6.0 to 3.6.2.
- [Release notes](https://github.com/bblimke/webmock/releases)
- [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bblimke/webmock/compare/v3.6.0...v3.6.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-01 19:15:26 +00:00
Transifex-Openfoodnetwork
0f748a3333 Updating translations for config/locales/en_GB.yml 2019-08-02 04:32:50 +10:00
Transifex-Openfoodnetwork
bdedaa06d8 Updating translations for config/locales/en_GB.yml 2019-08-02 04:29:40 +10:00
Pau Pérez Fabregat
6a41d9be87 Merge pull request #4092 from coopdevs/remove-admin-products-N+1
Remove N+1 while fetching distributors of OCs
2019-08-01 19:50:49 +02:00
luisramos0
4aa6c673ff Adapt api products and variants controllers to new namespace outside of Spree 2019-08-01 18:34:19 +01:00
luisramos0
aa3c1aa0fe Remove Spree module declaration from these files as they were moved out of the spree namespace 2019-08-01 14:30:11 +01:00
luisramos0
31bac9641f Move api products and variants routes and ctrl out of spree namespace 2019-08-01 14:28:55 +01:00
Hugo Daniel
60bdde6349 Convert variants/new from erb to haml and import new.js.erb 2019-08-01 11:23:44 +02:00
Hugo Daniel
5faf33fabe Import variants/new.html.erb from spree_backend 2019-08-01 10:50:13 +02:00
Hugo Daniel
f3b1a5dd35 Convert variants/edit from erb to haml 2019-08-01 10:45:04 +02:00
Hugo Daniel
07ccbf7f98 Import variants/edit.html.erb from spree_backend 2019-07-31 16:55:47 +02:00
Hugo Daniel
a3af6617a9 Convert payments/show from erb to haml 2019-07-31 16:20:32 +02:00
Hugo Daniel
9f7fb654c8 Import payments/show.html.erb from spree_backend 2019-07-31 16:16:26 +02:00
Hugo Daniel
b4a6686eac Convert payments/_list from erb to haml 2019-07-31 16:15:38 +02:00
Hugo Daniel
ac13ff114c Import payments/_list.html.erb from spree_backend 2019-07-31 16:09:48 +02:00
Hugo Daniel
af9187947f Convert erb to haml 2019-07-31 16:02:48 +02:00
luisramos0
b7f7038934 Remove api/enterprises rabl template, it was only used as a member in the now removed rabl variants/products templates 2019-07-31 14:36:36 +01:00
Hugo Daniel
f0fdbf7cf4 Import payments/index from spree_backend 2019-07-31 14:33:38 +02:00
Hugo Daniel
74ab31c0e6 Import gateway.html.erb and convert to haml 2019-07-31 14:28:15 +02:00
luisramos0
6c054e6078 Add bulk_products and overridable to skip_authorization_check so these endpoints work with AMS 2019-07-31 12:18:27 +01:00
luisramos0
18974c68e1 Remove orphan price check from price model
This is a quick fix. This check is breaking product deletion in some situations and orphan Prices are not really a problem in the DB
2019-07-31 11:24:55 +01:00
luisramos0
78ab852141 Make spree/api/products_controller work with AMS 2019-07-31 11:23:43 +01:00
luisramos0
4497173213 Adapt spree/api/products_controller_spec to AMS serializer 2019-07-31 11:23:18 +01:00
luisramos0
4d74d246e8 Remove spree/api/products_controller index and new actions, not used 2019-07-31 10:32:45 +01:00
luisramos0
cc51537e93 Convert spree/api/products_controller from rabl to ams 2019-07-31 09:50:34 +01:00
luisramos0
07aececdcf Remove unused route api/products managed 2019-07-31 09:50:31 +01:00
luisramos0
c3fbf9cdf9 Remove unused pagination from index and respective specs, fix spec for search by sku by adding sku to the serializer and adapt a few specs to pass with AMS attrivbutes, 2019-07-31 09:40:19 +01:00
luisramos0
180598c603 Convert spree/api/variants_controller to AMS by changing base_controller, using render json instad of respond with, deleting rabl templates and adapting specs
Delete unused pagination spec
2019-07-31 09:40:19 +01:00
luisramos0
69a5527e24 Update/regenarate .rubocop_todo.yml 2019-07-31 09:36:48 +01:00
luisramos0
e4a6b3880f Fix some more simple rubocop issues 2019-07-31 09:36:48 +01:00
luisramos0
96ce4deb45 Transpec spec/support/api_helper.rb 2019-07-31 09:36:48 +01:00
luisramos0
a3c179bd3f Fix some more simple rubocop issues 2019-07-31 09:36:48 +01:00
luisramos0
a57504ba1f Bring api_helper.image from spree_api to support spree/api/products_controller_spec 2019-07-31 09:36:48 +01:00
luisramos0
25451eed6b Bring api spec helpers from spree_api into ofn/api_helper 2019-07-31 09:36:48 +01:00
luisramos0
50765563f8 Bring spree/api_helpers from spree_api 2019-07-31 09:35:46 +01:00
luisramos0
2ae75ce13e Add ControllerSetup from spree_api as it is used in spree/api/base_controller 2019-07-31 09:35:46 +01:00
luisramos0
18aa16650d Remove dependency to Spree::ApiConfiguration, overall requires_authentication? is true, exceptions will be endpoint specific 2019-07-31 09:35:46 +01:00
luisramos0
314ed50e0f Fix a rubocop issue in spree/api/products_controller 2019-07-31 09:34:20 +01:00
luisramos0
7346a49982 Move routes in ofn api namespace to separate routes file 2019-07-31 09:34:20 +01:00
luisramos0
5182286218 Add necessary spree api routes related to api keys for users and bring respective implementations from spree_api 2019-07-31 09:34:20 +01:00
luisramos0
a267848394 Remove unused api routes from views/spree/admin/shared/routes view 2019-07-31 09:32:33 +01:00
luisramos0
104bd31f9b Add necessary spree api routes: taxons, variants and shipments 2019-07-31 09:32:33 +01:00
luisramos0
8bc9985edb Transpec and fix rubocop issues in spree/api/variants_controller_spec 2019-07-31 09:32:33 +01:00
luisramos0
6dfc927730 Make spree/api/variant_controllers_spec pass 2019-07-31 09:32:33 +01:00
luisramos0
3771e26eba Bring tests from spree/api/variants_controller_spec from spree_api 2019-07-31 09:32:33 +01:00
luisramos0
fd21d35aee Transpec and fix rubocop issues in spree/api/shipments_controller_spec 2019-07-31 09:32:33 +01:00
luisramos0
1417b924d2 Bring and adapt tests from spree/api/shipments_controller_spec and mix them with exiting tests in OFN 2019-07-31 09:32:33 +01:00
luisramos0
2912c1b87d Transpec and fix rubocop issues in spree/api/product_controller_spec 2019-07-31 09:32:33 +01:00
luisramos0
e746a0db7d Bring tests from spree/api/products_controller_spec and add them to existing ones on the ofn side
Adapt these tests to have a green build
2019-07-31 09:32:33 +01:00
luisramos0
84a2886003 Improve auth code in spree/api/taxons_controller_spec 2019-07-31 09:32:33 +01:00
luisramos0
c668677b8a Bring spree/api/taxons_controller_spec from spree_api, adapt it, transpec it and fix rubocop issues 2019-07-31 09:32:33 +01:00
luisramos0
2490cbfccb Transpec and fix rubocop issues in spree/api/base_controller_spec 2019-07-31 09:32:33 +01:00
luisramos0
20a46a791c Bring and adapt spree/api/base_controller_spec from spree_api 2019-07-31 09:32:33 +01:00
luisramos0
0e4fe08ac4 Fix logical problem in spree/api/base_controller and in spree/checkout_controller
See this stack overflow post for more info: https://stackoverflow.com/questions/39629976/ruby-return-vs-and-return
2019-07-31 09:32:33 +01:00
luisramos0
cf0f716534 Fix easy rubocop issues in spree/api/taxons_controller 2019-07-31 09:32:33 +01:00
luisramos0
b70cfa5968 Bring spree/api/taxons controller from spree_api as it is needed in OFN admin 2019-07-31 09:32:33 +01:00
luisramos0
f77beb50ff Fix class scope in spree/api/products_controller, should not use Spree namespace here
Also, add missing dependency to spree/admin/products_controller_decorator
2019-07-31 09:32:33 +01:00
luisramos0
a941280982 Fix easy rubocop issues in spree/api/base_controller 2019-07-31 09:32:33 +01:00
luisramos0
9d40ee49e6 Bring spree/api/base_controller from spree_api 2019-07-31 09:32:33 +01:00
luisramos0
6abbdecb97 Fix the easy rubocop issues in the new spree api controllers 2019-07-31 09:32:33 +01:00
luisramos0
660ce92c27 Merge spree api controllers and its decorators 2019-07-31 09:32:33 +01:00
luisramos0
c5bcef6ae4 Delete unused spree/api/line_items_controller_decorator.rb 2019-07-31 09:32:33 +01:00
luisramos0
d26a0b6b73 Bring from spree_api the api controllers that are overriden in OFN so that we can merge the original and the override afterwards 2019-07-31 09:32:33 +01:00
luisramos0
9400516b56 Fix broken (was always green) bulk_product_update_spec updating image spec where the old src url was not stored and the expect command was wrong with != 2019-07-30 20:37:26 +01:00
luisramos0
6ebfb02d0e Update ImageSerializer with missing image size urls: mini/thumb_url, product/image_url that is used in the product image modal 2019-07-30 20:15:21 +01:00
luisramos0
11a77043eb Switch from Spree::Api::BaseController to Api::BaseController so that AMS is activated 2019-07-30 12:20:14 +01:00
luisramos0
58b43c7bc9 Remove update_product_image.v1.rabl and switch controller from respond_with to render json to switch from rabl to AMS 2019-07-30 12:19:42 +01:00
luisramos0
be41271038 Make spelling coherent, as we have serializers with Z we get rid of the few serialisers with S we have 2019-07-29 19:57:12 +01:00
luisramos0
04e13e1136 Change css styling from id to class to make rubocop happy. Keeping id name as it may be needed in spree autocomplete css or js code 2019-07-29 19:46:12 +01:00
luisramos0
8868b7eb12 Fix rubocop issue: use nested class/module definition 2019-07-29 19:05:39 +01:00
Pau Perez
c85d00fcb8 Remove N+1 while fetching distributors of OCs
What used to be done as

```sql
SELECT "order_cycles".* FROM "order_cycles"
WHERE (order_cycles.orders_open_at <= '2019-07-29 17:45:20.137294'
  AND order_cycles.orders_close_at >= '2019-07-29 17:45:20.137333')

SELECT DISTINCT "enterprises".* FROM "enterprises"
INNER JOIN "exchanges" ON "enterprises"."id" = "exchanges"."receiver_id"
WHERE "exchanges"."order_cycle_id" = 1
  AND "exchanges"."incoming" = 'f'
(...)
SELECT DISTINCT "enterprises".* FROM "enterprises"
INNER JOIN "exchanges" ON "enterprises"."id" = "exchanges"."receiver_id"
WHERE "exchanges"."order_cycle_id" = 4
  AND "exchanges"."incoming" = 'f'
```

it became

```sql
SELECT "order_cycles".* FROM "order_cycles"
WHERE (order_cycles.orders_open_at <= '2019-07-29 17:45:20.137294'
  AND order_cycles.orders_close_at >= '2019-07-29 17:45:20.137333')

SELECT "exchanges".* FROM "exchanges"
WHERE "exchanges"."incoming" = 'f'
  AND "exchanges"."order_cycle_id" IN (1, 2, 3, 4)

SELECT "enterprises".* FROM "enterprises"
WHERE "enterprises"."id" IN (3, 4, 5, 6)
```

I haven't got any perf numbers yet but each of the N+1 queries took as
long as the single `enterprises` query on my dev machine.

This should have a noticeable perf impact since the changed method
belongs to the `BaseController` seems to be executed in all HTML
requests as it gets called by

```ruby
before_filter :warn_invalid_order_cycles, if: :html_request?
```
2019-07-29 20:02:47 +02:00
luisramos0
2c6dab9c85 Add spec for Api::OrderCycleSerializer 2019-07-29 18:56:49 +01:00
luisramos0
c45194473b Add spec to cover inject_current_order_cycle 2019-07-29 18:14:00 +01:00
luisramos0
3c0e6eeee2 Make inject_current_order_cycle render {} instad of null if current_order_cycle is null 2019-07-29 18:13:29 +01:00
luisramos0
ca5a5bf301 Convert oc.rabl to oc serializer and use it in both haml file and controller 2019-07-29 18:13:29 +01:00
dependabot-preview[bot]
8c7a7348f4 Bump devise-encryptable from 0.1.2 to 0.2.0
Bumps [devise-encryptable](https://github.com/plataformatec/devise-encryptable) from 0.1.2 to 0.2.0.
- [Release notes](https://github.com/plataformatec/devise-encryptable/releases)
- [Changelog](https://github.com/plataformatec/devise-encryptable/blob/master/Changelog.md)
- [Commits](https://github.com/plataformatec/devise-encryptable/compare/v0.1.2...v0.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-26 23:12:41 +00:00
Hugo Daniel
3c2b6f4ed1 Remove data-hooks 2019-07-25 14:38:09 +02:00
Hugo Daniel
ec6f6056a8 Remove data-hooks 2019-07-25 14:34:06 +02:00
Hugo Daniel
c464b21d76 Remove data-hooks 2019-07-25 14:27:53 +02:00
Hugo Daniel
c83d249147 Impor missing partials from spree to ofn and convert to haml 2019-07-25 14:23:24 +02:00
Hugo Daniel
aae5ae9f1e De-deface payments/new 2019-07-25 13:27:41 +02:00
Hugo Daniel
888e0e0bb4 Convert erb partials to haml 2019-07-25 13:22:55 +02:00
Hugo Daniel
a0b0fb05a6 Import payments/new from spree_backend to ofn 2019-07-25 13:22:32 +02:00
Hugo Daniel
a11562e4dd Fix incorrect indentation in variants table 2019-07-25 12:05:50 +02:00
Hugo Daniel
2d872c25bf Use Haml javascript tag to make autocomplete work 2019-07-25 11:20:51 +02:00
Hugo Daniel
986837d601 Import variants/_form.html.erb from spree_backend to ofn and de-deface it 2019-07-25 11:07:35 +02:00
Hugo Daniel
353d6fbc5f Import variants/index from spree_backend to ofn and convert to Haml 2019-07-24 16:57:11 +02:00
Hugo Daniel
0a88738faa Replace old ruby syntax with new 2019-07-24 14:37:00 +02:00
Hugo Daniel
4d6af57f79 De-deface product_properties/index 2019-07-24 13:17:45 +02:00
Hugo Daniel
110fd3ecdf Convert erb to haml 2019-07-24 13:12:58 +02:00
Hugo Daniel
1cb065f829 Import product_properties/index.html.erb from spree_backend to ofn 2019-07-24 13:09:04 +02:00
Hugo Daniel
1cfa499b0e De-deface _product_propery_fields 2019-07-24 13:01:50 +02:00
Hugo Daniel
3fc0d4a666 Convert _product_properties_fields from ERB to Haml 2019-07-24 12:40:24 +02:00
Hugo Daniel
de6c96d138 Import product_properties/_product_properties_fields.html.erb from Spree to OFN 2019-07-24 12:38:29 +02:00
luisramos0
ffb8edef0b Fix rubocop css issues 2019-07-23 22:37:10 +01:00
luisramos0
7a8b5e89be Add specs to cover the most important new fields in api/admin/variant_serializer 2019-07-23 22:11:00 +01:00
luisramos0
90690cd238 Move api/admin/variant_serializer_spec to the correct folder under /spec 2019-07-23 22:03:40 +01:00
luisramos0
90c621ac07 Improve translations, css and add button aligment in subscriptions_line_items variant autocomplete 2019-07-23 21:38:29 +01:00
luisramos0
2d5eccbf97 Add :in_stock, :stock_location_id, :stock_location_name to admin variant serializer, these are needed for the variant_autocomplete js code from spree 2019-07-23 21:38:29 +01:00
luisramos0
7e6259da31 Replace admin/variants/search rabl template with existing admin variant serializer with a few added attributes
This commit fixes a bug as the image of the variatn was not being rendered, we need to fetch the image of the product, not the image of the variant.
2019-07-23 21:37:45 +01:00
luisramos0
11974689ef Remove dead code under views/spree/shared 2019-07-23 16:42:00 +01:00
luisramos0
5739a82c19 Remove unused admin variants search rabl template 2019-07-23 12:30:00 +01:00
luisramos0
3d6fd10e59 Add warning about creation of orders when unpausing a subscription 2019-07-17 12:14:36 +01:00
luisramos0
81482683cf Extract method in subs controller 2019-07-17 12:08:51 +01:00
luisramos0
57f1742f24 Save subscription form on subs unpause so that proxy orders and orders are synced 2019-07-17 12:08:48 +01:00
luisramos0
3b467dbae8 Adapt line_item_syncer to the case where item is not added to the completed order because of insufficient stock and the subscription quantity is updated for that item afterwards 2019-07-16 12:08:08 +01:00
luisramos0
306390440a Revert "Swith line_item_syncer.create_new_items from deleting line item in the case of stock issue, to simply set the quantity to zero. This makes things easier later as the line item is already present if the user tries to change quantity of the subscription_line_item"
This reverts commit d153e58933.
2019-07-16 11:00:21 +01:00
luisramos0
267131626e Fix typo and add test case to cover out of stock case 2019-07-16 10:58:25 +01:00
luisramos0
02099ebdae Remove details about stock in line_item_syncer so that we avoid confusing the user with stock numbers, this way the user will know what is the problem and can check stock levels and the order for more details 2019-07-14 21:40:57 +01:00
luisramos0
d153e58933 Swith line_item_syncer.create_new_items from deleting line item in the case of stock issue, to simply set the quantity to zero. This makes things easier later as the line item is already present if the user tries to change quantity of the subscription_line_item 2019-07-14 20:59:42 +01:00
luisramos0
c2a59a374c Fix rubocop issues by simplifying method 2019-07-14 17:21:15 +01:00
luisramos0
2a1011921b Reuse line_item_syncer.add_order_update_issue in line_item_syncer.update_item_quantities to add stock info to the update_quantity order issues 2019-07-14 16:43:46 +01:00
luisramos0
df27ee0d3e Refactor line_item_syncer.add_order_update_issue out of line_item_syncer.create_new_items 2019-07-14 16:06:09 +01:00
luisramos0
c2851015ce Fix deleting created item (destroy doesnt work before save) and add order_update_issue with message so that user can understand what happened 2019-07-14 15:58:00 +01:00
luisramos0
d0c246c345 Fix some rubocop issues 2019-07-14 12:28:32 +01:00
luisramos0
6aed9ba549 Make line_item_syncer delete new line items if stock is insufficient 2019-07-13 22:57:13 +01:00
luisramos0
4f2bc33ec3 Change line_item_syncer to verify stock if order is already complete, this will happen for orders in the current OC when a subscription is changed 2019-07-13 20:56:34 +01:00
luisramos0
0f3404ca27 Rename order_syncer.future_and_undated_orders to explicitly include completed orders from current OC: orders_in_order_cycles_not_closed 2019-07-12 22:20:09 +01:00
Jon Leighton
4398ea12b8 Convert several fields from string to text
See discussion here:
https://github.com/openfoodfoundation/openfoodnetwork/pull/3751#issuecomment-503416955

Fixes #3192.

I have also done a pass over the schema to identify other fields which
would benefit from being a text rather than a string. However, I ignored
all `spree_*` tables because I didn’t want to mess up the ‘default’
Spree schema.
2019-07-09 13:11:30 +10:00
199 changed files with 5080 additions and 1840 deletions

View File

@@ -49,7 +49,6 @@ Metrics/LineLength:
- app/controllers/spree/admin/orders_controller_decorator.rb
- app/controllers/spree/admin/payments_controller_decorator.rb
- app/controllers/spree/admin/payment_methods_controller_decorator.rb
- app/controllers/spree/admin/products_controller_decorator.rb
- app/controllers/spree/admin/reports_controller_decorator.rb
- app/controllers/spree/api/products_controller_decorator.rb
- app/controllers/spree/credit_cards_controller.rb
@@ -123,14 +122,11 @@ Metrics/LineLength:
- app/serializers/api/admin/subscription_serializer.rb
- app/serializers/api/admin/tag_rule_serializer.rb
- app/serializers/api/admin/variant_override_serializer.rb
- app/serializers/api/admin/variant_serializer.rb
- app/services/cart_service.rb
- app/services/default_stock_location.rb
- app/services/embedded_page_service.rb
- app/services/line_item_syncer.rb
- app/services/order_cycle_form.rb
- app/services/order_factory.rb
- app/services/order_syncer.rb
- app/services/subscriptions_count.rb
- app/services/variants_stock_levels.rb
- app/views/json/_groups.rabl
@@ -157,7 +153,6 @@ Metrics/LineLength:
- 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/proxy_order_syncer.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
@@ -344,10 +339,8 @@ Metrics/LineLength:
- spec/models/tag_rule/filter_shipping_methods_spec.rb
- spec/models/variant_override_spec.rb
- spec/performance/orders_controller_spec.rb
- spec/performance/proxy_order_syncer_spec.rb
- spec/performance/shop_controller_spec.rb
- spec/requests/checkout/failed_checkout_spec.rb
- spec/requests/checkout/paypal_spec.rb
- spec/requests/checkout/stripe_connect_spec.rb
- spec/requests/embedded_shopfronts_headers_spec.rb
- spec/requests/shop_spec.rb
@@ -443,7 +436,6 @@ Metrics/AbcSize:
- app/models/spree/order_decorator.rb
- app/models/spree/payment_decorator.rb
- app/models/spree/product_decorator.rb
- app/models/spree/product_set.rb
- app/models/spree/taxon_decorator.rb
- app/serializers/api/admin/enterprise_serializer.rb
- app/serializers/api/product_serializer.rb
@@ -565,6 +557,7 @@ Metrics/CyclomaticComplexity:
- app/helpers/checkout_helper.rb
- app/helpers/i18n_helper.rb
- app/helpers/order_cycles_helper.rb
- app/helpers/spree/admin/navigation_helper_decorator.rb
- app/models/enterprise.rb
- app/models/enterprise_relationship.rb
- app/models/product_import/entry_processor.rb
@@ -572,7 +565,6 @@ Metrics/CyclomaticComplexity:
- app/models/spree/ability_decorator.rb
- app/models/spree/payment_decorator.rb
- app/models/spree/product_decorator.rb
- app/models/spree/product_set.rb
- app/models/variant_override_set.rb
- app/services/cart_service.rb
- lib/discourse/single_sign_on.rb
@@ -600,7 +592,6 @@ Metrics/PerceivedComplexity:
- app/models/spree/ability_decorator.rb
- app/models/spree/order_decorator.rb
- app/models/spree/product_decorator.rb
- app/models/spree/product_set.rb
- lib/discourse/single_sign_on.rb
- lib/open_food_network/bulk_coop_report.rb
- lib/open_food_network/enterprise_issue_validator.rb
@@ -656,7 +647,6 @@ Metrics/MethodLength:
- app/models/spree/payment_decorator.rb
- app/models/spree/payment_method_decorator.rb
- app/models/spree/product_decorator.rb
- app/models/spree/product_set.rb
- app/serializers/api/admin/order_cycle_serializer.rb
- app/serializers/api/cached_enterprise_serializer.rb
- app/services/order_cycle_form.rb

View File

@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 1400`
# on 2019-05-28 16:29:07 +0100 using RuboCop version 0.57.2.
# on 2019-07-23 14:09:18 +0100 using RuboCop version 0.57.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -32,15 +32,6 @@ Layout/EndAlignment:
Layout/IndentHash:
EnforcedStyle: consistent
# Offense count: 7
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: aligned, indented, indented_relative_to_receiver
Layout/MultilineMethodCallIndentation:
Exclude:
- 'app/models/spree/line_item_decorator.rb'
- 'app/models/spree/product_decorator.rb'
# Offense count: 4
Lint/AmbiguousOperator:
Exclude:
@@ -55,7 +46,7 @@ Lint/DuplicateMethods:
- 'lib/discourse/single_sign_on.rb'
- 'lib/open_food_network/subscription_summary.rb'
# Offense count: 15
# Offense count: 8
Lint/IneffectiveAccessModifier:
Exclude:
- 'app/models/column_preference.rb'
@@ -79,7 +70,13 @@ Lint/UnderscorePrefixedVariableName:
Exclude:
- 'spec/support/cancan_helper.rb'
# Offense count: 6
# Offense count: 1
# Cop supports --auto-correct.
Lint/UnneededCopDisableDirective:
Exclude:
- 'app/models/product_import/entry_validator.rb'
# Offense count: 5
# Configuration parameters: ContextCreatingMethods, MethodCreatingMethods.
Lint/UselessAccessModifier:
Exclude:
@@ -89,28 +86,47 @@ Lint/UselessAccessModifier:
- 'lib/open_food_network/reports/bulk_coop_report.rb'
- 'spec/lib/open_food_network/reports/report_spec.rb'
# Offense count: 91
# Offense count: 8
# Configuration parameters: CheckForMethodsWithNoSideEffects.
Lint/Void:
Exclude:
- 'app/serializers/api/enterprise_serializer.rb'
- 'spec/features/admin/bulk_product_update_spec.rb'
- 'spec/features/admin/enterprise_groups_spec.rb'
- 'spec/features/admin/enterprises/index_spec.rb'
- 'spec/features/admin/enterprises_spec.rb'
- 'spec/features/admin/order_cycles_spec.rb'
- 'spec/features/admin/payment_method_spec.rb'
- 'spec/features/admin/products_spec.rb'
- 'spec/features/admin/variant_overrides_spec.rb'
- 'spec/features/admin/variants_spec.rb'
- 'spec/features/consumer/shopping/checkout_spec.rb'
- 'spec/features/consumer/shopping/shopping_spec.rb'
- 'spec/features/consumer/shopping/variant_overrides_spec.rb'
# Offense count: 109
# Offense count: 15
Metrics/AbcSize:
Max: 36
# Offense count: 13
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/BlockLength:
Max: 774
Max: 115
# Offense count: 2
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 169
# Offense count: 1
Metrics/CyclomaticComplexity:
Max: 8
# Offense count: 8
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 31
# Offense count: 2
# Configuration parameters: CountComments.
Metrics/ModuleLength:
Max: 208
# Offense count: 2
Metrics/PerceivedComplexity:
Max: 11
# Offense count: 7
Naming/AccessorMethodName:
@@ -160,7 +176,7 @@ Naming/PredicateName:
- 'lib/open_food_network/packing_report.rb'
- 'lib/tasks/data.rake'
# Offense count: 12
# Offense count: 11
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
# AllowedNames: io, id, to, by, on, in, at
Naming/UncommunicativeMethodParamName:
@@ -288,13 +304,12 @@ Style/CaseEquality:
- 'app/helpers/angular_form_helper.rb'
- 'spec/models/spree/payment_spec.rb'
# Offense count: 79
# Offense count: 78
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle.
# SupportedStyles: nested, compact
Style/ClassAndModuleChildren:
Exclude:
- 'app/controllers/spree/store_controller_decorator.rb'
- 'app/helpers/angular_form_helper.rb'
- 'app/models/calculator/flat_percent_per_item.rb'
- 'app/models/spree/concerns/payment_method_distributors.rb'
@@ -379,11 +394,27 @@ Style/CommentedKeyword:
Exclude:
- 'app/controllers/application_controller.rb'
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
# SupportedStyles: assign_to_condition, assign_inside_condition
Style/ConditionalAssignment:
Exclude:
- 'app/controllers/spree/api/products_controller.rb'
- 'app/controllers/spree/api/taxons_controller.rb'
- 'app/controllers/spree/api/variants_controller.rb'
# Offense count: 2
Style/DateTime:
Exclude:
- 'lib/open_food_network/users_and_enterprises_report.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/EachWithObject:
Exclude:
- 'app/controllers/spree/api/base_controller.rb'
# Offense count: 5
# Configuration parameters: EnforcedStyle.
# SupportedStyles: annotated, template, unannotated
@@ -393,7 +424,7 @@ Style/FormatStringToken:
- 'lib/open_food_network/sales_tax_report.rb'
- 'spec/models/enterprise_spec.rb'
# Offense count: 69
# Offense count: 68
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Exclude:
@@ -410,7 +441,9 @@ Style/GuardClause:
- 'app/controllers/spree/admin/products_controller_decorator.rb'
- 'app/controllers/spree/admin/resource_controller_decorator.rb'
- 'app/controllers/spree/admin/variants_controller_decorator.rb'
- 'app/controllers/spree/orders_controller_decorator.rb'
- 'app/controllers/spree/api/base_controller.rb'
- 'app/controllers/spree/checkout_controller.rb'
- 'app/controllers/spree/orders_controller.rb'
- 'app/controllers/spree/paypal_controller_decorator.rb'
- 'app/jobs/products_cache_integrity_checker_job.rb'
- 'app/models/enterprise.rb'
@@ -434,12 +467,23 @@ Style/GuardClause:
- 'spec/support/request/distribution_helper.rb'
- 'spec/support/request/shop_workflow.rb'
# Offense count: 3
# Offense count: 6
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols.
# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys
Style/HashSyntax:
Exclude:
- 'app/controllers/spree/api/base_controller.rb'
- 'app/controllers/spree/checkout_controller.rb'
- 'app/controllers/spree/orders_controller.rb'
# Offense count: 4
Style/IfInsideElse:
Exclude:
- 'app/controllers/admin/column_preferences_controller.rb'
- 'app/controllers/admin/variant_overrides_controller.rb'
- 'app/controllers/spree/admin/products_controller_decorator.rb'
- 'app/controllers/spree/api/taxons_controller.rb'
# Offense count: 1
Style/MissingRespondToMissing:
@@ -492,9 +536,10 @@ Style/RegexpLiteral:
- 'spec/mailers/subscription_mailer_spec.rb'
- 'spec/models/content_configuration_spec.rb'
# Offense count: 243
# Offense count: 244
Style/Send:
Exclude:
- 'app/controllers/spree/checkout_controller.rb'
- 'app/models/spree/shipping_method_decorator.rb'
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
- 'spec/controllers/checkout_controller_spec.rb'
@@ -541,3 +586,9 @@ Style/Send:
Style/StructInheritance:
Exclude:
- 'lib/open_food_network/enterprise_fee_applicator.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/UnlessElse:
Exclude:
- 'app/controllers/spree/api/variants_controller.rb'

View File

@@ -42,6 +42,6 @@ $ docker-compose up
```
This command will setup the database and seed it with sample data. The default admin user is 'ofn@example.com' with 'ofn123' password.
Check the app in the browser at `http:://localhost:3000`.
Check the app in the browser at `http://localhost:3000`.
You will then get the trace of the containers in the terminal. You can stop the containers using Ctrl-C in the terminal.

10
Gemfile
View File

@@ -37,7 +37,7 @@ gem 'stripe'
gem 'activemerchant', '~> 1.78'
gem 'devise', '~> 2.2.5'
gem 'devise-encryptable', '0.1.2'
gem 'devise-encryptable', '0.2.0'
gem 'jwt', '~> 2.2'
gem 'oauth2', '~> 1.4.1' # Used for Stripe Connect
@@ -49,6 +49,10 @@ gem 'delayed_job_web'
# When merged, revert to upstream gem
gem 'simple_form', github: 'RohanM/simple_form'
# Spree's default pagination gem (locked to the current version used by Spree)
# We use it's methods in OFN code as well, so this is a direct dependency
gem 'kaminari', '~> 0.14.1'
gem 'andand'
gem 'angularjs-rails', '1.5.5'
gem 'aws-sdk'
@@ -119,6 +123,10 @@ gem 'jquery-rails', '3.0.4'
gem 'ofn-qz', github: 'openfoodfoundation/ofn-qz', ref: '60da2ae4c44cbb4c8d602f59fb5fff8d0f21db3c'
group :production, :staging do
gem 'ddtrace'
end
group :test, :development do
# Pretty printed test output
gem 'atomic'

View File

@@ -223,16 +223,18 @@ GEM
activerecord (>= 3.2.0, < 5.0)
fog (~> 1.0)
rails (>= 3.2.0, < 5.0)
ddtrace (0.26.0)
msgpack
debugger-linecache (1.2.0)
deface (1.0.2)
colorize (>= 0.5.8)
nokogiri (~> 1.6.0)
polyglot
rails (>= 3.1)
delayed_job (4.1.5)
activesupport (>= 3.0, < 5.3)
delayed_job_active_record (4.1.3)
activerecord (>= 3.0, < 5.3)
delayed_job (4.1.8)
activesupport (>= 3.0, < 6.1)
delayed_job_active_record (4.1.4)
activerecord (>= 3.0, < 6.1)
delayed_job (>= 3.0, < 5)
delayed_job_web (1.4.3)
activerecord (> 3.0.0)
@@ -244,7 +246,7 @@ GEM
orm_adapter (~> 0.1)
railties (~> 3.1)
warden (~> 1.2.1)
devise-encryptable (0.1.2)
devise-encryptable (0.2.0)
devise (>= 2.1.0)
diff-lcs (1.3)
diffy (3.3.0)
@@ -454,7 +456,7 @@ GEM
rspec (>= 2.99.0, < 4.0)
haml (4.0.7)
tilt
hashdiff (0.4.0)
hashdiff (1.0.0)
highline (1.6.18)
hike (1.2.3)
http_parser.rb (0.6.0)
@@ -483,7 +485,7 @@ GEM
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.11.2)
knapsack (1.17.2)
knapsack (1.18.0)
rake
launchy (2.4.3)
addressable (~> 2.3)
@@ -505,11 +507,12 @@ GEM
railties (>= 3.1)
money (5.1.1)
i18n (~> 0.6.0)
msgpack (1.3.1)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.1.1)
nenv (0.3.0)
net-http-persistent (3.0.1)
net-http-persistent (3.1.0)
connection_pool (~> 2.2)
newrelic_rpm (3.18.1.330)
nokogiri (1.6.8.1)
@@ -555,7 +558,7 @@ GEM
pry-byebug (3.4.3)
byebug (>= 9.0, < 9.1)
pry (~> 0.10)
public_suffix (3.0.3)
public_suffix (3.1.1)
rabl (0.8.4)
activesupport (>= 2.3.14)
rack (1.4.7)
@@ -591,7 +594,7 @@ GEM
thor (>= 0.14.6, < 2.0)
rainbow (3.0.0)
raindrops (0.19.0)
rake (12.3.2)
rake (12.3.3)
ransack (0.7.2)
actionpack (~> 3.0)
activerecord (~> 3.0)
@@ -698,7 +701,7 @@ GEM
tilt (~> 1.1, != 1.3.0)
state_machine (1.2.0)
stringex (1.5.1)
stripe (4.19.0)
stripe (4.24.0)
faraday (~> 0.13)
net-http-persistent (~> 3.0)
therubyracer (0.12.0)
@@ -736,7 +739,7 @@ GEM
nokogiri (~> 1.6)
rubyzip (~> 1.0)
selenium-webdriver (~> 3.0)
webmock (3.6.0)
webmock (3.6.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@@ -775,12 +778,13 @@ DEPENDENCIES
dalli
database_cleaner (= 0.7.1)
db2fog
ddtrace
debugger-linecache
deface (= 1.0.2)
delayed_job_active_record
delayed_job_web
devise (~> 2.2.5)
devise-encryptable (= 0.1.2)
devise-encryptable (= 0.2.0)
diffy
eventmachine (>= 1.2.3)
factory_bot_rails
@@ -804,6 +808,7 @@ DEPENDENCIES
jquery-rails (= 3.0.4)
json_spec (~> 1.1.4)
jwt (~> 2.2)
kaminari (~> 0.14.1)
knapsack
letter_opener (>= 1.4.1)
listen (= 3.0.8)

View File

@@ -1,267 +1,287 @@
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, $window, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories) ->
$scope.loading = true
$scope.loadingAllPages = true
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories, RequestMonitor) ->
$scope.StatusMessage = StatusMessage
$scope.StatusMessage = StatusMessage
$scope.columns = Columns.columns
$scope.columns = Columns.columns
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
$scope.RequestMonitor = RequestMonitor
$scope.pagination = BulkProducts.pagination
$scope.per_page_options = [
{id: 15, name: t('js.admin.orders.index.per_page', results: 15)},
{id: 50, name: t('js.admin.orders.index.per_page', results: 50)},
{id: 100, name: t('js.admin.orders.index.per_page', results: 100)}
]
$scope.filterableColumns = [
{ name: t("label_producers"), db_column: "producer_name" },
{ name: t("name"), db_column: "name" }
]
$scope.filterableColumns = [
{ name: t("label_producers"), db_column: "producer_name" },
{ name: t("name"), db_column: "name" }
]
$scope.filterTypes = [
{ name: t("equals"), predicate: "eq" },
{ name: t("contains"), predicate: "cont" }
]
$scope.filterTypes = [
{ name: t("equals"), predicate: "eq" },
{ name: t("contains"), predicate: "cont" }
]
$scope.optionTabs =
filters: { title: t("filter_products"), visible: false }
$scope.optionTabs =
filters: { title: t("filter_products"), visible: false }
$scope.producers = producers
$scope.taxons = Taxons.all
$scope.tax_categories = tax_categories
$scope.producerFilter = ""
$scope.categoryFilter = ""
$scope.importDateFilter = ""
$scope.page = 1
$scope.per_page = 15
$scope.products = BulkProducts.products
$scope.query = ""
$scope.DisplayProperties = DisplayProperties
$scope.initialise = ->
SpreeApiAuth.authorise()
.then ->
$scope.spree_api_key_ok = true
$scope.fetchProducts()
.catch (message) ->
$scope.api_error_msg = message
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter, per_page]', ->
$scope.page = 1 # Reset page when changing filters for new search
$scope.changePage = (newPage) ->
$scope.page = newPage
$scope.fetchProducts()
$scope.fetchProducts = ->
removeClearedValues()
params = {
'q[name_cont]': $scope.query,
'q[supplier_id_eq]': $scope.producerFilter,
'q[primary_taxon_id_eq]': $scope.categoryFilter,
import_date: $scope.importDateFilter,
page: $scope.page,
per_page: $scope.per_page
}
RequestMonitor.load(BulkProducts.fetch(params).$promise).then ->
$scope.resetProducts()
removeClearedValues = ->
delete $scope.producerFilter if $scope.producerFilter == "0"
delete $scope.categoryFilter if $scope.categoryFilter == "0"
delete $scope.importDateFilter if $scope.importDateFilter == "0"
$timeout ->
if $scope.showLatestImport
$scope.importDateFilter = $scope.importDates[1].id
$scope.resetProducts = ->
DirtyProducts.clear()
StatusMessage.clear()
$scope.updateOnHand = (product) ->
on_demand_variants = []
if product.variants
on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
unless product.on_demand || on_demand_variants.length > 0
product.on_hand = $scope.onHand(product)
$scope.producers = producers
$scope.taxons = Taxons.all
$scope.tax_categories = tax_categories
$scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers
$scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons
$scope.onHand = (product) ->
onHand = 0
if product.hasOwnProperty("variants") and product.variants instanceof Object
for id, variant of product.variants
onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
else
onHand = "error"
onHand
$scope.shiftTab = (tab) ->
$scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined
tab.visible = !tab.visible
$scope.visibleTab = tab
$scope.resetSelectFilters = ->
$scope.query = ""
$scope.producerFilter = "0"
$scope.categoryFilter = "0"
$scope.importDateFilter = "0"
$scope.products = BulkProducts.products
$scope.filteredProducts = []
$scope.currentFilters = []
$scope.limit = 15
$scope.query = ""
$scope.DisplayProperties = DisplayProperties
$scope.initialise = ->
SpreeApiAuth.authorise()
.then ->
$scope.spree_api_key_ok = true
$scope.fetchProducts()
.catch (message) ->
$scope.api_error_msg = message
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter]', ->
$scope.limit = 15 # Reset limit whenever searching
$scope.fetchProducts = ->
$scope.loading = true
$scope.loadingAllPages = true
BulkProducts.fetch($scope.currentFilters, ->
$scope.loadingAllPages = false
).then ->
$scope.resetProducts()
$scope.loading = false
$timeout ->
if $scope.showLatestImport
$scope.importDateFilter = $scope.importDates[1].id
$scope.resetProducts = ->
DirtyProducts.clear()
StatusMessage.clear()
$scope.updateOnHand = (product) ->
on_demand_variants = []
if product.variants
on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
unless product.on_demand || on_demand_variants.length > 0
product.on_hand = $scope.onHand(product)
$scope.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"
$scope.onHand = (product) ->
onHand = 0
if product.hasOwnProperty("variants") and product.variants instanceof Object
for id, variant of product.variants
onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
else
onHand = "error"
onHand
$scope.toggleShowAllVariants = ->
showVariants = !DisplayProperties.showVariants 0
$scope.products.forEach (product) ->
DisplayProperties.setShowVariants product.id, showVariants
DisplayProperties.setShowVariants 0, showVariants
$scope.shiftTab = (tab) ->
$scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined
tab.visible = !tab.visible
$scope.visibleTab = tab
$scope.resetSelectFilters = ->
$scope.query = ""
$scope.producerFilter = "0"
$scope.categoryFilter = "0"
$scope.importDateFilter = "0"
$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"
$scope.addVariant = (product) ->
product.variants.push
id: $scope.nextVariantId()
unit_value: null
unit_description: null
on_demand: false
display_as: null
display_name: null
on_hand: null
price: null
DisplayProperties.setShowVariants product.id, true
$scope.toggleShowAllVariants = ->
showVariants = !DisplayProperties.showVariants 0
$scope.filteredProducts.forEach (product) ->
DisplayProperties.setShowVariants product.id, showVariants
DisplayProperties.setShowVariants 0, showVariants
$scope.nextVariantId = ->
$scope.variantIdCounter = 0 unless $scope.variantIdCounter?
$scope.variantIdCounter -= 1
$scope.variantIdCounter
$scope.addVariant = (product) ->
product.variants.push
id: $scope.nextVariantId()
unit_value: null
unit_description: null
on_demand: false
display_as: null
display_name: null
on_hand: null
price: null
DisplayProperties.setShowVariants product.id, true
$scope.nextVariantId = ->
$scope.variantIdCounter = 0 unless $scope.variantIdCounter?
$scope.variantIdCounter -= 1
$scope.variantIdCounter
$scope.deleteProduct = (product) ->
if confirm("Are you sure?")
$http(
method: "DELETE"
url: "/api/products/" + product.id + "/soft_delete"
).success (data) ->
$scope.products.splice $scope.products.indexOf(product), 1
DirtyProducts.deleteProduct product.id
$scope.displayDirtyProducts()
$scope.deleteVariant = (product, variant) ->
if product.variants.length > 1
if !$scope.variantSaved(variant)
$scope.removeVariant(product, variant)
else
if confirm(t("are_you_sure"))
$http(
method: "DELETE"
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete"
).success (data) ->
$scope.removeVariant(product, variant)
else
alert(t("delete_product_variant"))
$scope.removeVariant = (product, variant) ->
product.variants.splice product.variants.indexOf(variant), 1
DirtyProducts.deleteVariant product.id, variant.id
$scope.displayDirtyProducts()
$scope.cloneProduct = (product) ->
BulkProducts.cloneProduct product
$scope.hasVariants = (product) ->
product.variants.length > 0
$scope.hasUnit = (product) ->
product.variant_unit_with_scale?
$scope.variantSaved = (variant) ->
variant.hasOwnProperty('id') && variant.id > 0
$scope.hasOnDemandVariants = (product) ->
(variant for id, variant of product.variants when variant.on_demand).length > 0
$scope.submitProducts = ->
# Pack pack $scope.products, so they will match the list returned from the server,
# then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
$scope.packProduct product for id, product of $scope.products
$scope.packProduct product for id, product of DirtyProducts.all()
productsToSubmit = filterSubmitProducts(DirtyProducts.all())
if productsToSubmit.length > 0
$scope.updateProducts productsToSubmit # Don't submit an empty list
else
StatusMessage.display 'alert', t("products_change")
$scope.updateProducts = (productsToSubmit) ->
$scope.displayUpdating()
$scope.deleteProduct = (product) ->
if confirm("Are you sure?")
$http(
method: "POST"
url: "/admin/products/bulk_update"
data:
products: productsToSubmit
filters: $scope.currentFilters
).success((data) ->
DirtyProducts.clear()
BulkProducts.updateVariantLists(data.products || [])
$timeout -> $scope.displaySuccess()
).error (data, status) ->
if status == 400 && data.errors? && data.errors.length > 0
errors = error + "\n" for error in data.errors
alert t("products_update_error") + "\n" + errors
$scope.displayFailure t("products_update_error")
else
$scope.displayFailure t("products_update_error_data") + status
method: "DELETE"
url: "/api/products/" + product.id + "/soft_delete"
).success (data) ->
$scope.products.splice $scope.products.indexOf(product), 1
DirtyProducts.deleteProduct product.id
$scope.displayDirtyProducts()
$scope.cancel = (destination) ->
$window.location = destination
$scope.packProduct = (product) ->
if product.variant_unit_with_scale
match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
product.variant_unit = match[1]
product.variant_unit_scale = parseFloat(match[2])
else
product.variant_unit = product.variant_unit_with_scale
product.variant_unit_scale = null
$scope.deleteVariant = (product, variant) ->
if product.variants.length > 1
if !$scope.variantSaved(variant)
$scope.removeVariant(product, variant)
else
product.variant_unit = product.variant_unit_scale = null
if confirm(t("are_you_sure"))
$http(
method: "DELETE"
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id + "/soft_delete"
).success (data) ->
$scope.removeVariant(product, variant)
else
alert(t("delete_product_variant"))
$scope.packVariant product, product.master if product.master
if product.variants
for id, variant of product.variants
$scope.packVariant product, variant
$scope.removeVariant = (product, variant) ->
product.variants.splice product.variants.indexOf(variant), 1
DirtyProducts.deleteVariant product.id, variant.id
$scope.displayDirtyProducts()
$scope.packVariant = (product, variant) ->
if variant.hasOwnProperty("unit_value_with_description")
match = variant.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/)
if match
product = BulkProducts.find product.id
variant.unit_value = parseFloat(match[1])
variant.unit_value = null if isNaN(variant.unit_value)
variant.unit_value *= product.variant_unit_scale if variant.unit_value && product.variant_unit_scale
variant.unit_description = match[3]
$scope.cloneProduct = (product) ->
BulkProducts.cloneProduct product
$scope.incrementLimit = ->
if $scope.limit < $scope.products.length
$scope.limit = $scope.limit + 5
$scope.hasVariants = (product) ->
product.variants.length > 0
$scope.displayUpdating = ->
StatusMessage.display 'progress', t("saving")
$scope.hasUnit = (product) ->
product.variant_unit_with_scale?
$scope.displaySuccess = ->
StatusMessage.display 'success',t("products_changes_saved")
$scope.bulk_product_form.$setPristine()
$scope.variantSaved = (variant) ->
variant.hasOwnProperty('id') && variant.id > 0
$scope.displayFailure = (failMessage) ->
StatusMessage.display 'failure', t("products_update_error_msg") + "#{failMessage}"
$scope.hasOnDemandVariants = (product) ->
(variant for id, variant of product.variants when variant.on_demand).length > 0
$scope.displayDirtyProducts = ->
count = DirtyProducts.count()
switch count
when 0 then StatusMessage.clear()
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
else StatusMessage.display 'notice', t("products_unsaved", n: count)
$scope.submitProducts = ->
# Pack pack $scope.products, so they will match the list returned from the server,
# then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
$scope.packProduct product for id, product of $scope.products
$scope.packProduct product for id, product of DirtyProducts.all()
productsToSubmit = filterSubmitProducts(DirtyProducts.all())
if productsToSubmit.length > 0
$scope.updateProducts productsToSubmit # Don't submit an empty list
else
StatusMessage.display 'alert', t("products_change")
$scope.updateProducts = (productsToSubmit) ->
$scope.displayUpdating()
$http(
method: "POST"
url: "/admin/products/bulk_update"
data:
products: productsToSubmit
filters:
'q[name_cont]': $scope.query
'q[supplier_id_eq]': $scope.producerFilter
'q[primary_taxon_id_eq]': $scope.categoryFilter
import_date: $scope.importDateFilter
page: $scope.page
per_page: $scope.per_page
).success((data) ->
DirtyProducts.clear()
BulkProducts.updateVariantLists(data.products || [])
$timeout -> $scope.displaySuccess()
).error (data, status) ->
if status == 400 && data.errors? && data.errors.length > 0
errors = error + "\n" for error in data.errors
alert t("products_update_error") + "\n" + errors
$scope.displayFailure t("products_update_error")
else
$scope.displayFailure t("products_update_error_data") + status
$scope.cancel = (destination) ->
$window.location = destination
$scope.packProduct = (product) ->
if product.variant_unit_with_scale
match = product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
product.variant_unit = match[1]
product.variant_unit_scale = parseFloat(match[2])
else
product.variant_unit = product.variant_unit_with_scale
product.variant_unit_scale = null
else
product.variant_unit = product.variant_unit_scale = null
$scope.packVariant product, product.master if product.master
if product.variants
for id, variant of product.variants
$scope.packVariant product, variant
$scope.packVariant = (product, variant) ->
if variant.hasOwnProperty("unit_value_with_description")
match = variant.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/)
if match
product = BulkProducts.find product.id
variant.unit_value = parseFloat(match[1])
variant.unit_value = null if isNaN(variant.unit_value)
variant.unit_value *= product.variant_unit_scale if variant.unit_value && product.variant_unit_scale
variant.unit_description = match[3]
$scope.incrementLimit = ->
if $scope.limit < $scope.products.length
$scope.limit = $scope.limit + 5
$scope.displayUpdating = ->
StatusMessage.display 'progress', t("saving")
$scope.displaySuccess = ->
StatusMessage.display 'success',t("products_changes_saved")
$scope.bulk_product_form.$setPristine()
$scope.displayFailure = (failMessage) ->
StatusMessage.display 'failure', t("products_update_error_msg") + "#{failMessage}"
$scope.displayDirtyProducts = ->
count = DirtyProducts.count()
switch count
when 0 then StatusMessage.clear()
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
else StatusMessage.display 'notice', t("products_unsaved", n: count)
filterSubmitProducts = (productsToFilter) ->

View File

@@ -23,7 +23,7 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor,
$scope.fetchResults = (page=1) ->
$scope.resetSelected()
Orders.index({
params = {
'q[completed_at_lt]': $scope['q']['completed_at_lt'],
'q[completed_at_gt]': $scope['q']['completed_at_gt'],
'q[state_eq]': $scope['q']['state_eq'],
@@ -39,7 +39,8 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, RequestMonitor,
'q[s]': $scope.sorting || 'completed_at desc',
per_page: $scope.per_page,
page: page
})
}
RequestMonitor.load(Orders.index(params).$promise)
$scope.resetSelected = ->
$scope.selected_orders.length = 0

View File

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

View File

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

View File

@@ -1,15 +1,13 @@
angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher, $http) ->
angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetcher, $http) ->
new class BulkProducts
products: []
pagination: {}
fetch: (filters, onComplete) ->
queryString = filters.reduce (qs,f) ->
return qs + "q[#{f.property.db_column}_#{f.predicate.predicate}]=#{f.value};"
, ""
url = "/api/products/bulk_products?page=::page::;per_page=20;#{queryString}"
processData = (data) => @addProducts data.products
PagedFetcher.fetch url, processData, onComplete
fetch: (params) ->
ProductResource.index params, (data) =>
@products.length = 0
@addProducts data.products
angular.extend(@pagination, data.pagination)
cloneProduct: (product) ->
$http.post("/api/products/" + product.id + "/clone").success (data) =>

View File

@@ -27,8 +27,6 @@ angular.module("admin.utils").directive "variantAutocomplete", ($timeout) ->
window.variants = data # this is how spree auto complete JS code picks up variants
results: data
formatResult: (variant) ->
if variant["images"][0] != undefined && variant["images"][0].image != undefined
variant.image = variant.images[0].image.mini_url
variantTemplate variant: variant
formatSelection: (variant) ->
element.parent().children(".options_placeholder").html variant.options_text

View File

@@ -1,8 +0,0 @@
Darkswarm.directive "cart", ->
# Toggles visibility of the "cart" popover
restrict: 'A'
link: (scope, elem, attr)->
scope.open = false
elem.bind 'click', ->
scope.$apply ->
scope.open = !scope.open

View File

@@ -0,0 +1,19 @@
Darkswarm.directive "cartToggle", ($document) ->
# Toggles visibility of the "cart" popover
restrict: 'A'
link: (scope, elem, attr)->
scope.open = false
$document.bind 'click', (event) ->
cart_button = elem[0]
element_and_parents = [event.target, event.target.parentElement, event.target.parentElement.parentElement]
cart_button_clicked = (element_and_parents.indexOf(cart_button) != -1)
if cart_button_clicked
scope.$apply ->
scope.open = !scope.open
else
scope.$apply ->
scope.open = false
return

View File

@@ -1,10 +1,15 @@
@API_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:SS Z"
Darkswarm.filter "date_in_words", ->
(date) ->
moment(date).fromNow()
(date, dateFormat) ->
dateFormat ?= @API_DATETIME_FORMAT
moment(date, dateFormat).fromNow()
Darkswarm.filter "sensible_timeframe", (date_in_wordsFilter)->
(date) ->
if moment().add(2, 'days') < moment(date)
(date, dateFormat) ->
dateFormat ?= @API_DATETIME_FORMAT
if moment().add(2, 'days') < moment(date, dateFormat)
t 'orders_open'
else
t('closing') + date_in_wordsFilter(date)

View File

@@ -0,0 +1,11 @@
.add-line-item {
fieldset {
.vertical-align-top {
vertical-align: top;
}
.actions {
padding-top: 18px;
}
}
}

View File

@@ -3,7 +3,7 @@
.select2-container {
.select2-choice {
.select2-search-choice-close {
display: none;
display: none !important;
}
.select2-arrow {
width: 22px;

View File

@@ -35,5 +35,6 @@ $med-grey: #666;
$med-drk-grey: #444;
$dark-grey: #333;
$light-grey: #ddd;
$light-grey-transparency: rgba(0, 0, 0, .1);
$black: #000;
$white: #fff;

View File

@@ -42,7 +42,7 @@ nav.top-bar {
}
.top-bar-section {
border-bottom: 1px solid $ofn-grey;
border-bottom: 1px solid $light-grey-transparency;
a.icon {
&:hover {
@@ -170,9 +170,10 @@ nav.top-bar {
.tab-bar {
background-color: white;
border-bottom: 1px solid $light-grey-transparency;
height: 2.8em;
position: fixed;
width: 100%;
height: 2.8em;
z-index: 1;
.cart-span {

View File

@@ -1,6 +1,7 @@
@import "mixins";
@import "branding";
@import "compass/css3/user-interface";
@import 'variables';
.cart {
@include user-select(none);
@@ -15,8 +16,8 @@
.joyride-tip-guide {
display: block;
right: 10px;
top: 55px;
right: 0;
top: $topbar-height;
width: 480px;
@media screen and (min-width: 641px) {

View File

@@ -1,4 +1,5 @@
require 'open_food_network/permissions'
require 'open_food_network/proxy_order_syncer'
module Admin
class SubscriptionsController < ResourceController
@@ -32,21 +33,11 @@ module Admin
end
def create
form = SubscriptionForm.new(@subscription, params[:subscription])
if form.save
render_as_json @subscription
else
render json: { errors: form.json_errors }, status: :unprocessable_entity
end
save_form_and_render(false)
end
def update
form = SubscriptionForm.new(@subscription, params[:subscription])
if form.save
render_as_json @subscription, order_update_issues: form.order_update_issues
else
render json: { errors: form.json_errors }, status: :unprocessable_entity
end
save_form_and_render
end
def cancel
@@ -67,12 +58,26 @@ module Admin
end
def unpause
@subscription.update_attributes(paused_at: nil)
render_as_json @subscription
params[:subscription][:paused_at] = nil
save_form_and_render
end
private
def save_form_and_render(render_issues = true)
form = SubscriptionForm.new(@subscription, params[:subscription])
unless form.save
render json: { errors: form.json_errors }, status: :unprocessable_entity
return
end
if render_issues
render_as_json @subscription, order_update_issues: form.order_update_issues
else
render_as_json @subscription
end
end
def permissions
return @permissions unless @permissions.nil?
@permissions = OpenFoodNetwork::Permissions.new(spree_current_user)

View File

@@ -9,8 +9,8 @@ module Admin
def map_by_tag
respond_to do |format|
format.json do
serialiser = ActiveModel::ArraySerializer.new(collection)
render json: serialiser.to_json
serializer = ActiveModel::ArraySerializer.new(collection)
render json: serializer.to_json
end
end
end

View File

@@ -1,5 +1,5 @@
module Api
class ProductImagesController < Spree::Api::BaseController
class ProductImagesController < BaseController
respond_to :json
def update_product_image
@@ -8,11 +8,11 @@ module Api
if @product.images.first.nil?
@image = Spree::Image.create(attachment: params[:file], viewable_id: @product.master.id, viewable_type: 'Spree::Variant')
respond_with(@image, status: 201)
render json: @image, serializer: ImageSerializer, status: :created
else
@image = @product.images.first
@image.update_attributes(attachment: params[:file])
respond_with(@image, status: 200)
render json: @image, serializer: ImageSerializer, status: :ok
end
end
end

View File

@@ -0,0 +1,143 @@
require 'open_food_network/permissions'
module Api
class ProductsController < Api::BaseController
respond_to :json
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 15
skip_authorization_check only: [:show, :bulk_products, :overridable]
def show
@product = find_product(params[:id])
render json: @product, serializer: Api::Admin::ProductSerializer
end
def create
authorize! :create, Spree::Product
params[:product][:available_on] ||= Time.zone.now
@product = Spree::Product.new(params[:product])
begin
if @product.save
render json: @product, serializer: Api::Admin::ProductSerializer, status: 201
else
invalid_resource!(@product)
end
rescue ActiveRecord::RecordNotUnique
@product.permalink = nil
retry
end
end
def update
authorize! :update, Spree::Product
@product = find_product(params[:id])
if @product.update_attributes(params[:product])
render json: @product, serializer: Api::Admin::ProductSerializer, status: 200
else
invalid_resource!(@product)
end
end
def destroy
authorize! :delete, Spree::Product
@product = find_product(params[:id])
@product.update_attribute(:deleted_at, Time.zone.now)
@product.variants_including_master.update_all(deleted_at: Time.zone.now)
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)
if params[:import_date].present?
product_query = product_query.imported_on(params[:import_date]).group_by_products_id
end
@products = product_query.order('created_at DESC').
ransack(params[:q]).result.
page(params[:page] || DEFAULT_PAGE).per(params[:per_page] || DEFAULT_PER_PAGE)
render_paged_products @products
end
def overridable
producers = OpenFoodNetwork::Permissions.new(current_api_user).
variant_override_producers.by_name
@products = paged_products_for_producers producers
render_paged_products @products
end
def soft_delete
authorize! :delete, Spree::Product
@product = find_product(params[:product_id])
authorize! :delete, @product
@product.destroy
render json: @product, serializer: Api::Admin::ProductSerializer, status: 204
end
# POST /api/products/:product_id/clone
#
def clone
authorize! :create, Spree::Product
original_product = find_product(params[:product_id])
authorize! :update, original_product
@product = original_product.duplicate
render json: @product, serializer: Api::Admin::ProductSerializer, status: 201
end
private
# Copied and modified from SpreeApi::BaseController to allow
# enterprise users to access inactive products
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]
scope = scope.with_deleted
end
else
scope = Spree::Product.active
end
scope.includes(:master)
end
def paged_products_for_producers(producers)
Spree::Product.scoped.
merge(product_scope).
where(supplier_id: producers).
by_producer.by_name.
ransack(params[:q]).result.
page(params[:page]).per(params[:per_page])
end
def render_paged_products(products)
serializer = ActiveModel::ArraySerializer.new(
products,
each_serializer: ::Api::Admin::ProductSerializer
)
render text: {
products: serializer,
pagination: pagination_data(products)
}.to_json
end
def pagination_data(results)
{
results: results.total_count,
pages: results.num_pages,
page: (params[:page] || DEFAULT_PAGE).to_i,
per_page: (params[:per_page] || DEFAULT_PER_PAGE).to_i
}
end
end
end

View File

@@ -0,0 +1,79 @@
module Api
class VariantsController < Api::BaseController
respond_to :json
skip_authorization_check only: [:index, :show]
before_filter :product
def index
@variants = scope.includes(:option_values).ransack(params[:q]).result
render json: @variants, each_serializer: Api::VariantSerializer
end
def show
@variant = scope.includes(:option_values).find(params[:id])
render json: @variant, serializer: Api::VariantSerializer
end
def create
authorize! :create, Spree::Variant
@variant = scope.new(params[:variant])
if @variant.save
render json: @variant, serializer: Api::VariantSerializer, status: 201
else
invalid_resource!(@variant)
end
end
def update
authorize! :update, Spree::Variant
@variant = scope.find(params[:id])
if @variant.update_attributes(params[:variant])
render json: @variant, serializer: Api::VariantSerializer, status: 200
else
invalid_resource!(@product)
end
end
def soft_delete
@variant = scope.find(params[:variant_id])
authorize! :delete, @variant
VariantDeleter.new.delete(@variant)
render json: @variant, serializer: Api::VariantSerializer, status: 204
end
def destroy
authorize! :delete, Spree::Variant
@variant = scope.find(params[:id])
@variant.destroy
render json: @variant, serializer: Api::VariantSerializer, status: 204
end
private
def product
@product ||= Spree::Product.find_by_permalink(params[:product_id]) if params[:product_id]
end
def scope
if @product
unless current_api_user.has_spree_role?("admin") || params[:show_deleted]
variants = @product.variants_including_master
else
variants = @product.variants_including_master.with_deleted
end
else
variants = Spree::Variant.scoped
if current_api_user.has_spree_role?("admin")
unless params[:show_deleted]
variants = Spree::Variant.active
end
else
variants = variants.active
end
end
variants
end
end
end

View File

@@ -27,12 +27,12 @@ class ShopController < BaseController
if oc = OrderCycle.with_distributor(@distributor).active.find_by_id(params[:order_cycle_id])
current_order(true).set_order_cycle! oc
@current_order_cycle = oc
render partial: "json/order_cycle"
render json: @current_order_cycle, serializer: Api::OrderCycleSerializer
else
render status: :not_found, json: ""
end
else
render partial: "json/order_cycle"
render json: current_order_cycle, serializer: Api::OrderCycleSerializer
end
end

View File

@@ -61,7 +61,7 @@ Spree::Admin::BaseController.class_eval do
def active_distributors_not_ready_for_checkout
ocs = OrderCycle.managed_by(spree_current_user).active
distributors = ocs.map(&:distributors).flatten.uniq
distributors = ocs.includes(:distributors).map(&:distributors).flatten.uniq
Enterprise.where('enterprises.id IN (?)', distributors).not_ready_for_checkout
end

View File

@@ -1,5 +1,6 @@
require 'open_food_network/spree_api_key_loader'
require 'open_food_network/referer_parser'
require 'open_food_network/permissions'
Spree::Admin::ProductsController.class_eval do
include OpenFoodNetwork::SpreeApiKeyLoader
@@ -48,19 +49,13 @@ Spree::Admin::ProductsController.class_eval do
end
def bulk_update
collection_hash = Hash[params[:products].each_with_index.map { |p, i| [i, p] }]
product_set = Spree::ProductSet.new(collection_attributes: collection_hash)
params[:filters] ||= {}
bulk_index_query = params[:filters].reduce("") do |string, filter|
"#{string}q[#{filter[:property][:db_column]}_#{filter[:predicate][:predicate]}]=#{filter[:value]};"
end
product_set = product_set_from_params(params)
# Ensure we're authorised to update all products
product_set.collection.each { |p| authorize! :update, p }
if product_set.save
redirect_to "/api/products/bulk_products?page=1;per_page=500;#{bulk_index_query}"
redirect_to main_app.bulk_products_api_products_path( bulk_index_query(params) )
else
if product_set.errors.present?
render json: { errors: product_set.errors }, status: :bad_request
@@ -73,7 +68,8 @@ Spree::Admin::ProductsController.class_eval do
protected
def collection
# This method is copied directly from the spree product controller, except where we narrow the search below with the managed_by search to support
# This method is copied directly from the spree product controller
# except where we narrow the search below with the managed_by search to support
# enterprise users.
# TODO: There has to be a better way!!!
return @collection if @collection.present?
@@ -108,14 +104,32 @@ Spree::Admin::ProductsController.class_eval do
private
def product_set_from_params(params)
collection_hash = Hash[params[:products].each_with_index.map { |p, i| [i, p] }]
Spree::ProductSet.new(collection_attributes: collection_hash)
end
def bulk_index_query(params)
params[:filters].to_h.merge(page: params[:page], per_page: params[:per_page])
end
def load_form_data
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).managed_product_enterprises.is_primary_producer.by_name
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).
managed_product_enterprises.is_primary_producer.by_name
@taxons = Spree::Taxon.order(:name)
@import_dates = product_import_dates.uniq.to_json
end
def product_import_dates
import_dates = Spree::Variant.
options = [{ id: '0', name: '' }]
product_import_dates_query.collect(&:import_date).
map { |i| options.push(id: i.to_date, name: i.to_date.to_formatted_s(:long)) }
options
end
def product_import_dates_query
Spree::Variant.
select('DISTINCT spree_variants.import_date').
joins(:product).
where('spree_products.supplier_id IN (?)', editable_enterprises.collect(&:id)).
@@ -123,18 +137,14 @@ Spree::Admin::ProductsController.class_eval do
where(spree_variants: { is_master: false }).
where(spree_variants: { deleted_at: nil }).
order('spree_variants.import_date DESC')
options = [{ id: '0', name: '' }]
import_dates.collect(&:import_date).map { |i| options.push(id: i.to_date, name: i.to_date.to_formatted_s(:long)) }
options
end
def strip_new_properties
unless spree_current_user.admin? || params[:product][:product_properties_attributes].nil?
names = Spree::Property.pluck(:name)
params[:product][:product_properties_attributes].each do |key, property|
params[:product][:product_properties_attributes].delete key unless names.include? property[:property_name]
return if spree_current_user.admin? || params[:product][:product_properties_attributes].nil?
names = Spree::Property.pluck(:name)
params[:product][:product_properties_attributes].each do |key, property|
unless names.include? property[:property_name]
params[:product][:product_properties_attributes].delete key
end
end
end
@@ -149,12 +159,32 @@ Spree::Admin::ProductsController.class_eval do
end
def set_stock_levels(product, on_hand, on_demand)
variant = product.master
if product.variants.any?
variant = product.variants.first
variant = product_variant(product)
begin
variant.on_demand = on_demand if on_demand.present?
variant.on_hand = on_hand.to_i if on_hand.present?
rescue StandardError => error
notify_bugsnag(error, product, variant)
raise error
end
end
def notify_bugsnag(error, product, variant)
Bugsnag.notify(error) do |report|
report.add_tab(:product, product.attributes)
report.add_tab(:product_error, product.errors.first) unless product.valid?
report.add_tab(:variant, variant.attributes)
report.add_tab(:variant_error, variant.errors.first) unless variant.valid?
end
end
def product_variant(product)
if product.variants.any?
product.variants.first
else
product.master
end
variant.on_demand = on_demand if on_demand.present?
variant.on_hand = on_hand.to_i if on_hand.present?
end
def set_product_master_variant_price_to_zero

View File

@@ -18,6 +18,7 @@ Spree::Admin::VariantsController.class_eval do
def search
scoper = OpenFoodNetwork::ScopeVariantsForSearch.new(params)
@variants = scoper.search
render json: @variants, each_serializer: Api::Admin::VariantSerializer
end
def destroy

View File

@@ -0,0 +1,130 @@
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,13 +0,0 @@
Spree::Api::LineItemsController.class_eval do
around_filter :apply_enterprise_fees_with_lock, only: :update
private
def apply_enterprise_fees_with_lock
authorize! :read, order
order.with_lock do
yield
order.update_distribution_charge!
end
end
end

View File

@@ -1,86 +0,0 @@
require 'open_food_network/permissions'
Spree::Api::ProductsController.class_eval do
def managed
authorize! :admin, Spree::Product
authorize! :read, Spree::Product
@products = product_scope.ransack(params[:q]).result.managed_by(current_api_user).page(params[:page]).per(params[:per_page])
respond_with(@products, default_template: :index)
end
# TODO: This should be named 'managed'. Is the action above used? Maybe we should remove it.
def bulk_products
@products = OpenFoodNetwork::Permissions.new(current_api_user).editable_products.
merge(product_scope).
order('created_at DESC').
ransack(params[:q]).result.
page(params[:page]).per(params[:per_page])
render_paged_products @products
end
def overridable
producers = OpenFoodNetwork::Permissions.new(current_api_user).
variant_override_producers.by_name
@products = paged_products_for_producers producers
render_paged_products @products
end
def soft_delete
authorize! :delete, Spree::Product
@product = find_product(params[:product_id])
authorize! :delete, @product
@product.destroy
respond_with(@product, status: 204)
end
# POST /api/products/:product_id/clone
#
def clone
authorize! :create, Spree::Product
original_product = find_product(params[:product_id])
authorize! :update, original_product
@product = original_product.duplicate
respond_with(@product, status: 201, default_template: :show)
end
private
# Copied and modified from Spree::Api::BaseController to allow
# enterprise users to access inactive products
def product_scope
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present? # This line modified
scope = Spree::Product
if params[:show_deleted]
scope = scope.with_deleted
end
else
scope = Spree::Product.active
end
scope.includes(:master)
end
def paged_products_for_producers(producers)
Spree::Product.scoped.
merge(product_scope).
where(supplier_id: producers).
by_producer.by_name.
ransack(params[:q]).result.
page(params[:page]).per(params[:per_page])
end
def render_paged_products(products)
serializer = ActiveModel::ArraySerializer.new(
products,
each_serializer: Api::Admin::ProductSerializer
)
render text: { products: serializer, pages: products.num_pages }.to_json
end
end

View File

@@ -0,0 +1,108 @@
require 'open_food_network/scope_variant_to_hub'
module Spree
module Api
class ShipmentsController < Spree::Api::BaseController
respond_to :json
before_filter :find_order
before_filter :find_and_update_shipment, only: [:ship, :ready, :add, :remove]
def create
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@shipment = get_or_create_shipment(params[:stock_location_id])
@order.contents.add(variant, quantity, nil, @shipment)
@shipment.refresh_rates
@shipment.save!
respond_with(@shipment.reload, default_template: :show)
end
def update
authorize! :read, Shipment
@shipment = @order.shipments.find_by_number!(params[:id])
params[:shipment] ||= []
unlock = params[:shipment].delete(:unlock)
if unlock == 'yes'
@shipment.adjustment.open
end
@shipment.update_attributes(params[:shipment])
if unlock == 'yes'
@shipment.adjustment.close
end
@shipment.reload
respond_with(@shipment, default_template: :show)
end
def ready
authorize! :read, Shipment
unless @shipment.ready?
if @shipment.can_ready?
@shipment.ready!
else
render "spree/api/shipments/cannot_ready_shipment", status: :unprocessable_entity
return
end
end
respond_with(@shipment, default_template: :show)
end
def ship
authorize! :read, Shipment
unless @shipment.shipped?
@shipment.ship!
end
respond_with(@shipment, default_template: :show)
end
def add
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.add(variant, quantity, nil, @shipment)
respond_with(@shipment, default_template: :show)
end
def remove
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.remove(variant, quantity, @shipment)
@shipment.reload if @shipment.persisted?
respond_with(@shipment, default_template: :show)
end
private
def find_order
@order = Spree::Order.find_by_number!(params[:order_id])
authorize! :read, @order
end
def find_and_update_shipment
@shipment = @order.shipments.find_by_number!(params[:id])
@shipment.update_attributes(params[:shipment])
@shipment.reload
end
def scoped_variant(variant_id)
variant = Spree::Variant.find(variant_id)
OpenFoodNetwork::ScopeVariantToHub.new(@order.distributor).scope(variant)
variant
end
def get_or_create_shipment(stock_location_id)
@order.shipment || @order.shipments.create(stock_location_id: stock_location_id)
end
end
end
end

View File

@@ -1,47 +0,0 @@
require 'open_food_network/scope_variant_to_hub'
Spree::Api::ShipmentsController.class_eval do
def create
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@shipment = get_or_create_shipment(params[:stock_location_id])
@order.contents.add(variant, quantity, nil, @shipment)
@shipment.refresh_rates
@shipment.save!
respond_with(@shipment.reload, default_template: :show)
end
def add
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.add(variant, quantity, nil, @shipment)
respond_with(@shipment, default_template: :show)
end
def remove
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.remove(variant, quantity, @shipment)
@shipment.reload if @shipment.persisted?
respond_with(@shipment, default_template: :show)
end
private
def scoped_variant(variant_id)
variant = Spree::Variant.find(variant_id)
OpenFoodNetwork::ScopeVariantToHub.new(@order.distributor).scope(variant)
variant
end
def get_or_create_shipment(stock_location_id)
@order.shipment || @order.shipments.create(stock_location_id: stock_location_id)
end
end

View File

@@ -0,0 +1,75 @@
module Spree
module Api
class TaxonsController < Spree::Api::BaseController
respond_to :json
def index
if taxonomy
@taxons = taxonomy.root.children
else
if params[:ids]
@taxons = Taxon.where(id: params[:ids].split(","))
else
@taxons = Taxon.ransack(params[:q]).result
end
end
respond_with(@taxons)
end
def show
@taxon = taxon
respond_with(@taxon)
end
def jstree
show
end
def create
authorize! :create, Taxon
@taxon = Taxon.new(params[:taxon])
@taxon.taxonomy_id = params[:taxonomy_id]
taxonomy = Taxonomy.find_by_id(params[:taxonomy_id])
if taxonomy.nil?
@taxon.errors[:taxonomy_id] = I18n.t(:invalid_taxonomy_id, scope: 'spree.api')
invalid_resource!(@taxon) && return
end
@taxon.parent_id = taxonomy.root.id unless params[:taxon][:parent_id]
if @taxon.save
respond_with(@taxon, status: 201, default_template: :show)
else
invalid_resource!(@taxon)
end
end
def update
authorize! :update, Taxon
if taxon.update_attributes(params[:taxon])
respond_with(taxon, status: 200, default_template: :show)
else
invalid_resource!(taxon)
end
end
def destroy
authorize! :delete, Taxon
taxon.destroy
respond_with(taxon, status: 204)
end
private
def taxonomy
return if params[:taxonomy_id].blank?
@taxonomy ||= Taxonomy.find(params[:taxonomy_id])
end
def taxon
@taxon ||= taxonomy.taxons.find(params[:id])
end
end
end
end

View File

@@ -1,9 +0,0 @@
Spree::Api::VariantsController.class_eval do
def soft_delete
@variant = scope.find(params[:variant_id])
authorize! :delete, @variant
VariantDeleter.new.delete(@variant)
respond_with @variant, status: 204
end
end

View File

@@ -29,7 +29,7 @@ module Spree
def load_order
@order = current_order
redirect_to main_app.cart_path && return unless @order
redirect_to(main_app.cart_path) && return unless @order
if params[:state]
redirect_to checkout_state_path(@order.state) if @order.can_go_to_state?(params[:state])

View File

@@ -61,6 +61,12 @@ module InjectionHelper
inject_json_ams "currentOrder", current_order, Api::CurrentOrderSerializer, current_distributor: current_distributor, current_order_cycle: current_order_cycle
end
def inject_current_order_cycle
serializer = Api::OrderCycleSerializer.new(current_order_cycle)
json = serializer.object.present? ? serializer.to_json : "{}"
render partial: "json/injection_ams", locals: { name: "orderCycleData", json: json }
end
def inject_available_shipping_methods
inject_json_ams "shippingMethods", available_shipping_methods,
Api::ShippingMethodSerializer, current_order: current_order

View File

@@ -18,6 +18,7 @@ module Spree
klass = Spree::Order if klass == :bulk_order_management
klass = EnterpriseGroup if klass == :group
klass = VariantOverride if klass == :Inventory
klass = ProductImport::ProductImporter if klass == :import
klass = Spree::Admin::ReportsController if klass == :report
klass
end

View File

@@ -0,0 +1,120 @@
module Spree
module Api
module ApiHelpers
def required_fields_for(model)
required_fields = model._validators.select do |_field, validations|
validations.any? { |v| v.is_a?(ActiveModel::Validations::PresenceValidator) }
end.map(&:first) # get fields that are invalid
# Permalinks presence is validated, but are really automatically generated
# Therefore we shouldn't tell API clients that they MUST send one through
required_fields.map!(&:to_s).delete("permalink")
required_fields
end
def product_attributes
[:id, :name, :description, :price, :available_on, :permalink, :meta_description,
:meta_keywords, :shipping_category_id, :taxon_ids]
end
def product_property_attributes
[:id, :product_id, :property_id, :value, :property_name]
end
def variant_attributes
[:id, :name, :sku, :price, :weight, :height, :width, :depth,
:is_master, :cost_price, :permalink]
end
def image_attributes
[:id, :position, :attachment_content_type, :attachment_file_name,
:type, :attachment_updated_at, :attachment_width, :attachment_height, :alt]
end
def option_value_attributes
[:id, :name, :presentation, :option_type_name, :option_type_id]
end
def order_attributes
[:id, :number, :item_total, :total, :state, :adjustment_total, :user_id,
:created_at, :updated_at, :completed_at, :payment_total,
:shipment_state, :payment_state, :email, :special_instructions, :token]
end
def line_item_attributes
[:id, :quantity, :price, :variant_id]
end
def option_type_attributes
[:id, :name, :presentation, :position]
end
def payment_attributes
[:id, :source_type, :source_id, :amount, :payment_method_id,
:response_code, :state, :avs_response, :created_at, :updated_at]
end
def payment_method_attributes
[:id, :name, :description]
end
def shipment_attributes
[:id, :tracking, :number, :cost, :shipped_at, :state]
end
def taxonomy_attributes
[:id, :name]
end
def taxon_attributes
[:id, :name, :pretty_name, :permalink, :position, :parent_id, :taxonomy_id]
end
def inventory_unit_attributes
[:id, :lock_version, :state, :variant_id, :shipment_id, :return_authorization_id]
end
def return_authorization_attributes
[:id, :number, :state, :amount, :order_id, :reason, :created_at, :updated_at]
end
def country_attributes
[:id, :iso_name, :iso, :iso3, :name, :numcode]
end
def state_attributes
[:id, :name, :abbr, :country_id]
end
def adjustment_attributes
[:id, :source_type, :source_id, :adjustable_type, :adjustable_id, :originator_type,
:originator_id, :amount, :label, :mandatory, :locked, :eligible, :created_at, :updated_at]
end
def creditcard_attributes
[:id, :month, :year, :cc_type, :last_digits, :first_name, :last_name,
:gateway_customer_profile_id, :gateway_payment_profile_id]
end
def user_attributes
[:id, :email, :created_at, :updated_at]
end
def property_attributes
[:id, :name, :presentation]
end
def stock_location_attributes
[:id, :name, :address1, :address2, :city,
:state_id, :state_name, :country_id, :zipcode, :phone, :active]
end
def stock_movement_attributes
[:id, :quantity, :stock_item_id]
end
def stock_item_attributes
[:id, :count_on_hand, :backorderable, :lock_version, :stock_location_id, :variant_id]
end
end
end
end

View File

@@ -59,7 +59,7 @@ class SubscriptionPlacementJob
end
def move_to_completion(order)
until order.completed? do order.next! end
AdvanceOrderService.new(order).call!
end
def unavailable_stock_lines_for(order)

View File

@@ -90,7 +90,6 @@ class Enterprise < ActiveRecord::Base
validates :permalink, uniqueness: true, presence: true
validate :shopfront_taxons
validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? }
validates :description, length: { maximum: 255 }
before_validation :initialize_permalink, if: lambda { permalink.nil? }
before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? && !owner_id.nil? }
@@ -464,9 +463,11 @@ class Enterprise < ActiveRecord::Base
self.permalink = Enterprise.find_available_permalink(name)
end
# Touch distributors without them touching their distributors.
# We avoid an infinite loop and don't need to touch the whole distributor tree.
def touch_distributors
Enterprise.distributing_products(supplied_products.select(:id)).
where('enterprises.id != ?', id).
find_each(&:touch)
update_all(updated_at: Time.zone.now)
end
end

View File

@@ -1,20 +0,0 @@
# Tells whether a particular feature is enabled or not
class FeatureFlags
# Constructor
#
# @param user [User]
def initialize(user)
@user = user
end
# Checks whether product import is enabled for the specified user
#
# @return [Boolean]
def product_import_enabled?
user.superadmin?
end
private
attr_reader :user
end

View File

@@ -4,6 +4,12 @@ module Spree
private
def check_price
if currency.nil?
self.currency = Spree::Config[:currency]
end
end
def refresh_products_cache
variant.andand.refresh_products_cache
end

View File

@@ -56,6 +56,12 @@ Spree::Product.class_eval do
ON (o_order_cycles.id = o_exchanges.order_cycle_id)")
}
scope :imported_on, lambda { |import_date|
import_date = Time.zone.parse import_date if import_date.is_a? String
import_date = import_date.to_date
joins(:variants).merge(Spree::Variant.where(import_date: import_date.beginning_of_day..import_date.end_of_day))
}
scope :with_order_cycles_inner, -> {
joins(variants_including_master: { exchanges: :order_cycle })
}

View File

@@ -17,9 +17,7 @@ class Spree::ProductSet < ModelSet
# variant.update_attributes( { price: xx.x } )
#
def update_attributes(attributes)
if attributes[:taxon_ids].present?
attributes[:taxon_ids] = attributes[:taxon_ids].split(',')
end
split_taxon_ids!(attributes)
found_model = @collection.find do |model|
model.id.to_s == attributes[:id].to_s && model.persisted?
@@ -28,12 +26,20 @@ class Spree::ProductSet < ModelSet
if found_model.nil?
@klass.new(attributes).save unless @reject_if.andand.call(attributes)
else
update_product_only_attributes(found_model, attributes) &&
update_product_variants(found_model, attributes) &&
update_product_master(found_model, attributes)
update_product(found_model, attributes)
end
end
def split_taxon_ids!(attributes)
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)
end
def update_product_only_attributes(product, attributes)
variant_related_attrs = [:id, :variants_attributes, :master_attributes]
product_related_attrs = attributes.except(*variant_related_attrs)
@@ -90,8 +96,23 @@ class Spree::ProductSet < ModelSet
variant = product.variants.create(variant_attributes)
variant.on_demand = on_demand if on_demand.present?
variant.on_hand = on_hand.to_i if on_hand.present?
begin
variant.on_demand = on_demand if on_demand.present?
variant.on_hand = on_hand.to_i if on_hand.present?
rescue StandardError => error
notify_bugsnag(error, product, variant, variant_attributes)
raise error
end
end
def notify_bugsnag(error, product, variant, variant_attributes)
Bugsnag.notify(error) do |report|
report.add_tab(:product, product.attributes)
report.add_tab(:product_error, product.errors.first) unless product.valid?
report.add_tab(:variant_attributes, variant_attributes)
report.add_tab(:variant, variant.attributes)
report.add_tab(:variant_error, variant.errors.first) unless variant.valid?
end
end
def collection_attributes=(attributes)

View File

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

@@ -136,6 +136,16 @@ module Spree
has_spree_role?('admin')
end
def generate_spree_api_key!
self.spree_api_key = SecureRandom.hex(24)
save!
end
def clear_spree_api_key!
self.spree_api_key = nil
save!
end
protected
def password_required?

View File

@@ -1,2 +0,0 @@
/replace 'code[erb-loud]:contains("form_for @payment")'
= form_for @payment, :url => admin_order_payments_path(@order), :html => {"ng-app" => "admin.payments", "ng-controller" => "PaymentCtrl"} do |f|

View File

@@ -1,3 +0,0 @@
/ insert_before "fieldset.no-border-top"
%save-bar{ persist: "true" }

View File

@@ -1,2 +0,0 @@
/replace 'code[erb-loud]:contains("button @order.cart?")'
= button_tag t(:update), type: 'button', "ng-click" => "submitPayment()"

View File

@@ -1,6 +0,0 @@
/ replace_contents "td.property_name"
- if spree_current_user.admin?
= f.text_field :property_name, :class => 'autocomplete'
- else
= f.select :property_name, @properties, { :include_blank => true }, { class: 'select2 fullwidth' }

View File

@@ -1,26 +0,0 @@
/ insert_after 'table.index.sortable'
=f.check_box :inherits_properties
=f.label :inherits_properties, t(".inherits_properties_checkbox_hint", supplier: @product.supplier.name)
%br
%br
#inherited_properties
%table.index
%thead
%tr{"data-hook" => "producer_properties_header"}
%th= t('admin.products.properties.inherited_property')
%th= t('admin.description')
%th.actions
%tbody#producer_properties{"data-hook" => ""}
- @product.supplier.producer_properties.each do |producer_property|
%tr
%td= producer_property.property.presentation
%td= producer_property.value
%td.actions
:coffee
$(document).ready ->
$("#inherited_properties").toggle $("input#product_inherits_properties").is(':checked')
$("input#product_inherits_properties").change ->
$("#inherited_properties").toggle $(this).is(':checked')

View File

@@ -1,5 +0,0 @@
/ replace "tr[data-hook='product_properties_header']"
%tr{"data-hook" => "product_properties_header"}
%th= t('admin.products.properties.property_name')
%th= t('admin.description')
%th.actions

View File

@@ -1,2 +0,0 @@
add_to_attributes '[data-hook="admin_variant_form_fields"]'
attributes 'ng-app' => 'admin.products'

View File

@@ -1,25 +0,0 @@
/ insert_top "[data-hook='admin_variant_form_fields']"
.field
= f.label :display_name, t(:display_name)
= f.text_field :display_name, class: "fullwidth"
.field
= f.label :display_as, t(:display_as)
= f.text_field :display_as, class: "fullwidth"
- if product_has_variant_unit_option_type?(@product)
- if @product.variant_unit != 'items'
.field{"data-hook" => "unit_value", 'ng-controller' => 'variantUnitsCtrl'}
= f.label :unit_value, "#{t('admin.'+@product.variant_unit)} ({{unitName(#{@product.variant_unit_scale}, '#{@product.variant_unit}')}})"
= hidden_field_tag 'product_variant_unit_scale', @product.variant_unit_scale
= text_field_tag :unit_value_human, nil, {class: "fullwidth", 'ng-model' => 'unit_value_human', 'ng-change' => 'updateValue()'}
= f.text_field :unit_value, {hidden: true, 'ng-value' => 'unit_value'}
.field{"data-hook" => "unit_description"}
= f.label :unit_description, t(:spree_admin_unit_description)
= f.text_field :unit_description, class: "fullwidth", placeholder: t('admin.products.unit_name_placeholder')
:javascript
angular.element(document.getElementById("new_variant")).ready(function() {
angular.bootstrap(document.getElementById("new_variant"), ['admin.products']);
});

View File

@@ -1,11 +0,0 @@
/ insert_bottom "[data-hook='admin_variant_form_fields']"
- if Spree::Config[:track_inventory_levels]
.field.checkbox
%label
= f.check_box :on_demand
= t(:on_demand)
.field
= f.label :on_hand, t(:on_hand)
.fullwidth
= f.text_field :on_hand

View File

@@ -1,10 +0,0 @@
/ replace "[data-hook='presentation']"
- unless variant_unit_option_type?(option_type)
.field{"data-hook" => "presentation"}
= label :new_variant, option_type.presentation
- if @variant.new_record?
= select(:new_variant, option_type.presentation, option_type.option_values.collect {|ov| [ ov.presentation, ov.id ] }, {}, {:class => 'select2 fullwidth'})
- else
- 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')

View File

@@ -1,33 +0,0 @@
/ insert_bottom "[data-hook='admin_variant_form_fields']"
:javascript
$(document).ready(function() {
var on_demand = $('input#variant_on_demand');
var on_hand = $('input#variant_on_hand');
disableOnHandIfOnDemand = function() {
on_demand_checked = on_demand.attr('checked')
if ( on_demand_checked == undefined )
on_demand_checked = false;
on_hand.attr('disabled', on_demand_checked);
if(on_demand_checked) {
on_hand.attr('data-stock', on_hand.val());
on_hand.val(t('admin.products.variants.infinity'));
}
}
disableOnHandIfOnDemand();
on_demand.change(function(){
disableOnHandIfOnDemand();
if(!this.checked) {
if(on_hand.attr('data-stock') !== undefined) {
on_hand.val(on_hand.attr('data-stock'));
} else {
on_hand.val("0");
}
}
});
});

View File

@@ -1,3 +0,0 @@
/ insert_bottom "[data-hook='on_demand']"
%div{'ofn-with-tip' => t('admin.products.variants.to_order_tip')}
%a= t('admin.whats_this')

View File

@@ -1,14 +0,0 @@
/ replace "[data-hook='admin_variant_form_additional_fields']"
.right.six.columns.omega.label-block{"data-hook" => "admin_variant_form_additional_fields"}
- if @product.variant_unit != 'weight'
.field{"data-hook" => 'weight'}
= f.label 'weight', t('weight')+' (kg)'
- value = number_with_precision(@variant.weight, :precision => 2)
= f.text_field 'weight', :value => value, :class => 'fullwidth'
- [:height, :width, :depth].each do |field|
.field{"data-hook" => field}
= f.label field, t(field)
- value = number_with_precision(@variant.send(field), :precision => 2)
= f.text_field field, :value => value, :class => 'fullwidth'

View File

@@ -1,3 +0,0 @@
/ replace "code[erb-loud]:contains('variant.options_text')"
= variant.full_name

View File

@@ -1,15 +1,49 @@
class Api::Admin::VariantSerializer < ActiveModel::Serializer
attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display, :sku
attributes :on_hand, :price, :import_date
attributes :id, :name, :producer_name, :image, :sku, :import_date
attributes :options_text, :unit_value, :unit_description, :unit_to_display
attributes :display_as, :display_name, :name_to_display
attributes :price, :on_demand, :on_hand, :in_stock, :stock_location_id, :stock_location_name
has_many :variant_overrides
def name
if object.full_name.present?
"#{object.name} - #{object.full_name}"
else
object.name
end
end
def on_hand
return 0 if object.on_hand.nil?
object.on_hand
end
def price
# Decimals are passed to json as strings, we need to run parseFloat.toFixed(2) on the client side.
# Decimals are passed to json as strings, we need to run parseFloat.toFixed(2) on the client.
object.price.nil? ? 0.to_f : object.price
end
def producer_name
object.product.supplier.name
end
def image
return if object.product.images.empty?
object.product.images.first.mini_url
end
def in_stock
object.in_stock?
end
def stock_location_id
return if object.stock_items.empty?
object.stock_items.first.stock_location.id
end
def stock_location_name
return if object.stock_items.empty?
object.stock_items.first.stock_location.name
end
end

View File

@@ -1,10 +1,18 @@
class Api::ImageSerializer < ActiveModel::Serializer
attributes :id, :alt, :small_url, :large_url
attributes :id, :alt, :thumb_url, :small_url, :image_url, :large_url
def thumb_url
object.attachment.url(:mini, false)
end
def small_url
object.attachment.url(:small, false)
end
def image_url
object.attachment.url(:product, false)
end
def large_url
object.attachment.url(:large, false)
end

View File

@@ -0,0 +1,13 @@
module Api
class OrderCycleSerializer < ActiveModel::Serializer
attributes :order_cycle_id, :orders_close_at
def order_cycle_id
object.id
end
def orders_close_at
object.orders_close_at.to_s
end
end
end

View File

@@ -1,6 +1,8 @@
class Api::VariantSerializer < ActiveModel::Serializer
attributes :id, :is_master, :on_hand, :name_to_display, :unit_to_display, :unit_value
attributes :options_text, :on_demand, :price, :fees, :price_with_fees, :product_name
attributes :id, :is_master, :product_name, :sku
attributes :options_text, :unit_value, :unit_description, :unit_to_display
attributes :display_as, :display_name, :name_to_display
attributes :price, :on_demand, :on_hand, :fees, :price_with_fees
attributes :tag_list
delegate :price, to: :object

View File

@@ -22,20 +22,36 @@ class LineItemSyncer
def update_item_quantities(order)
changed_subscription_line_items.each do |sli|
line_item = order.line_items.find_by_variant_id(sli.variant_id)
next if update_quantity(line_item, sli)
product_name = "#{line_item.product.name} - #{line_item.full_name}"
order_update_issues.add(order, product_name)
if line_item.blank?
order_update_issues.add(order, sli.variant.product_and_full_name)
next
end
unless update_quantity(line_item, sli)
add_order_update_issue(order, line_item)
end
end
end
def create_new_items(order)
new_subscription_line_items.each do |sli|
order.line_items.create(variant_id: sli.variant_id, quantity: sli.quantity, skip_stock_check: true)
new_line_item = order.line_items.create(variant_id: sli.variant_id,
quantity: sli.quantity,
skip_stock_check: skip_stock_check?(order))
next if skip_stock_check?(order) || new_line_item.sufficient_stock?
order.line_items.delete(new_line_item)
add_order_update_issue(order, new_line_item)
end
end
def destroy_obsolete_items(order)
order.line_items.where(variant_id: subscription_line_items.select(&:marked_for_destruction?).map(&:variant_id)).destroy_all
order.line_items.
where(variant_id: subscription_line_items.
select(&:marked_for_destruction?).
map(&:variant_id)).
destroy_all
end
def changed_subscription_line_items
@@ -48,8 +64,27 @@ class LineItemSyncer
def update_quantity(line_item, sli)
if line_item.quantity == sli.quantity_was
return line_item.update_attributes(quantity: sli.quantity, skip_stock_check: true)
return line_item.update_attributes(quantity: sli.quantity,
skip_stock_check: skip_stock_check?(line_item.order))
end
line_item.quantity == sli.quantity
end
def skip_stock_check?(order)
!order.complete?
end
def add_order_update_issue(order, line_item)
issue_description = "#{line_item.product.name} - #{line_item.variant.full_name}"
issue_description << " - #{stock_issue_description(line_item)}" if line_item.insufficient_stock?
order_update_issues.add(order, issue_description)
end
def stock_issue_description(line_item)
if line_item.variant.in_stock?
I18n.t("admin.subscriptions.stock.insufficient_stock")
else
I18n.t("admin.subscriptions.stock.out_of_stock")
end
end
end

View File

@@ -1,6 +1,5 @@
# Responsible for ensuring that any updates to a Subscription are propagated to any
# orders belonging to that Subscription which have been instantiated
class OrderSyncer
attr_reader :order_update_issues
@@ -11,8 +10,9 @@ class OrderSyncer
end
def sync!
future_and_undated_orders.all? do |order|
order.assign_attributes(customer_id: customer_id, email: customer.andand.email, distributor_id: shop_id)
orders_in_order_cycles_not_closed.all? do |order|
order.assign_attributes(customer_id: customer_id, email: customer.andand.email,
distributor_id: shop_id)
update_associations_for(order)
line_item_syncer.sync!(order)
order.save
@@ -25,7 +25,8 @@ class OrderSyncer
delegate :orders, :bill_address, :ship_address, :subscription_line_items, to: :subscription
delegate :shop_id, :customer, :customer_id, to: :subscription
delegate :shipping_method, :shipping_method_id, :payment_method, :payment_method_id, to: :subscription
delegate :shipping_method, :shipping_method_id,
:payment_method, :payment_method_id, to: :subscription
delegate :shipping_method_id_changed?, :shipping_method_id_was, to: :subscription
delegate :payment_method_id_changed?, :payment_method_id_was, to: :subscription
@@ -36,9 +37,10 @@ class OrderSyncer
update_payment_for(order) if payment_method_id_changed?
end
def future_and_undated_orders
return @future_and_undated_orders unless @future_and_undated_orders.nil?
@future_and_undated_orders = orders.joins(:order_cycle).merge(OrderCycle.not_closed).readonly(false)
def orders_in_order_cycles_not_closed
return @orders_in_order_cycles_not_closed unless @orders_in_order_cycles_not_closed.nil?
@orders_in_order_cycles_not_closed = orders.joins(:order_cycle).
merge(OrderCycle.not_closed).readonly(false)
end
def update_bill_address_for(order)
@@ -49,7 +51,8 @@ class OrderSyncer
end
def update_payment_for(order)
payment = order.payments.with_state('checkout').where(payment_method_id: payment_method_id_was).last
payment = order.payments.
with_state('checkout').where(payment_method_id: payment_method_id_was).last
if payment
payment.andand.void_transaction!
order.payments.create(payment_method_id: payment_method_id, amount: order.reload.total)

View File

@@ -5,4 +5,11 @@
= render 'upload_sidebar'
%h5
= t('.notice')
%br
%p
= t('.beta_notice')
%br
= render 'upload_form'

View File

@@ -1,4 +1,4 @@
#add-line-item
#add-line-item.add-line-item
%fieldset
%legend{ align: 'center'}= t(:products)
%table.no-borders.layout
@@ -6,15 +6,15 @@
%col{ width: '20%' }
%col{ width: '20%' }
%tr
%td{ style: "vertical-align:top" }
%td.vertical-align-top
.field
= label_tag :add_variant_id, t(:name_or_sku)
= label_tag :add_variant_id, t('.name_or_sku')
%input#add_variant_id.variant_autocomplete.fullwidth{ type: 'number', ng: { model: 'newItem.variant_id' } }
%td{ style: "vertical-align:top" }
%td.vertical-align-top
.field
= label_tag :add_quantity, t(:qty)
= label_tag :add_quantity, t('.quantity')
%input#add_quantity.fullwidth{ type: 'number', min: 1, ng: { model: 'newItem.quantity' } }
%td{ style: "vertical-align:top" }
%td
.actions
%a.icon-plus.button.fullwidth{ href: 'javascript:void(0)', method: :post, ng: { click: 'addSubscriptionLineItem()' } }
= t(:add)
= t('.add')

View File

@@ -1,3 +0,0 @@
object @enterprise
attributes :id, :name

View File

@@ -1,5 +0,0 @@
object @image
attributes(*image_attributes)
attributes :viewable_type, :viewable_id
node( :thumb_url ) { @product.images.first.attachment.url(:mini) }
node( :image_url ) { @product.images.first.attachment.url(:product) }

View File

@@ -1,3 +0,0 @@
object current_order_cycle
attributes :orders_close_at
attribute id: :order_cycle_id

View File

@@ -1,5 +1,5 @@
%span.cart-span{"ng-controller" => "CartCtrl", "ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"}
%a#cart.icon{cart: true}
%a#cart.icon{"cart-toggle" => true}
%span
= t '.cart'
%span.count

View File

@@ -1,6 +1,5 @@
- content_for :scripts do
:javascript
angular.module('Darkswarm').value('orderCycleData', #{render "json/order_cycle"})
- content_for :injection_data do
= inject_current_order_cycle
%ordercycle{"ng-controller" => "OrderCycleCtrl"}

View File

@@ -1,5 +1,5 @@
.per-page{'ng-show' => '!RequestMonitor.loading && orders.length > 0'}
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'ng-model' => 'per_page', 'ng-change' => 'fetchResults()'}
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'min-search' => 999, 'ng-model' => 'per_page', 'ng-change' => 'fetchResults()'}
%span.per-page-feedback
{{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }}

View File

@@ -1,37 +0,0 @@
<%= admin_inject_json "admin.payments", "currentOrderNumber", @order.number %>
<%= admin_inject_json_ams_array "admin.payments", "paymentMethods", @payment_methods, Api::PaymentMethodSerializer, current_order: @order %>
<div data-hook="admin_payment_form_fields" class="row">
<div class="alpha three columns">
<div class="field">
<%= f.label :amount, t(:amount) %>
<%= f.text_field :amount, :value => @order.outstanding_balance, :class => 'fullwidth', "watch-value-as" => 'form_data.amount' %>
</div>
</div>
<div class="omega nine columns">
<div class="field">
<label><%= t(:payment_method) %></label>
<ul>
<% @payment_methods.each do |method| %>
<li>
<label data-hook="payment_method_field">
<%= radio_button_tag 'payment[payment_method_id]', method.id, method == @payment_method, { class: "payment_methods_radios", "ng-model" => 'form_data.payment_method' } %>
<%= t(method.name, :scope => :payment_methods, :default => method.name) %>
</label>
</li>
<% end %>
</ul>
<div class="payment-method-settings">
<% @payment_methods.each do |method| %>
<div class="payment-methods" id="payment_method_<%= method.id %>">
<% if method.source_required? %>
<br />
<%= render :partial => "spree/admin/payments/source_forms/#{method.method_type}", :locals => { :payment_method => method } %>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,23 @@
= admin_inject_json "admin.payments", "currentOrderNumber", @order.number
= admin_inject_json_ams_array "admin.payments", "paymentMethods", @payment_methods, Api::PaymentMethodSerializer, current_order: @order
.row
.alpha.three.columns
.field
= f.label :amount, t(:amount)
= f.text_field :amount, value: @order.outstanding_balance, class: 'fullwidth', "watch-value-as" => 'form_data.amount'
.omega.nine.columns
.field
%label= t(:payment_method)
%ul
- @payment_methods.each do |method|
%li
%label
= radio_button_tag 'payment[payment_method_id]', method.id, method == @payment_method, { class: "payment_methods_radios", "ng-model" => 'form_data.payment_method' }
= t(method.name, scope: :payment_methods, default: method.name)
.payment-method-settings
- @payment_methods.each do |method|
.payment-methods{id: "payment_method_#{method.id}"}
- if method.source_required?
%br/
= render partial: "spree/admin/payments/source_forms/#{method.method_type}", locals: { payment_method: method }

View File

@@ -0,0 +1,19 @@
%table.index
%thead
%tr
%th= "#{Spree.t('date')}/#{Spree.t('time')}"
%th= Spree.t(:amount)
%th= Spree.t(:payment_method)
%th= Spree.t(:payment_state)
%th.actions
%tbody
- payments.each do |payment|
%tr{class: "#{cycle('odd', 'even')}"}
%td= pretty_time(payment.created_at)
%td.align-center= payment.display_amount.to_html
%td.align-center= link_to payment_method_name(payment), spree.admin_order_payment_path(@order, payment)
%td.align-center
%span{class: "state #{payment.state}"}= Spree.t(payment.state, scope: :payment_states, default: payment.state.capitalize)
%td.actions
- payment.actions.each do |action|
= link_to_with_icon "icon-#{action}", Spree.t(action), fire_admin_order_payment_path(@order, payment, e: action), method: :put, no_text: true, data: {action: action}

View File

@@ -0,0 +1,22 @@
= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Payments' }
- content_for :page_actions do
- if @order.outstanding_balance?
%li#new_payment_section
= button_link_to Spree.t(:new_payment), new_admin_order_payment_url(@order), icon: 'icon-plus'
%li= button_link_to Spree.t(:back_to_orders_list), admin_orders_path, icon: 'icon-arrow-left'
- content_for :page_title do
%i.icon-arrow-right
= Spree.t(:payments)
- if @order.outstanding_balance?
%h5.outstanding-balance
= @order.outstanding_balance < 0 ? Spree.t(:credit_owed) : Spree.t(:balance_due)
\:
%strong= @order.display_outstanding_balance
- if @payments.any?
= render partial: 'list', locals: { payments: @payments }
- else
.alpha.twelve.columns.no-objects-found= Spree.t(:order_has_no_payments)

View File

@@ -0,0 +1,22 @@
= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Payments' }
- content_for :page_title do
%i.icon-arrow-right
= Spree.t(:new_payment)
- content_for :page_actions do
%li= button_link_to Spree.t(:back_to_payments_list), spree.admin_order_payments_url(@order), icon: 'icon-arrow-left'
- if @payment_methods.any?
= render partial: 'spree/shared/error_messages', locals: { target: @payment }
= form_for @payment, url: admin_order_payments_path(@order), html: {"ng-app" => "admin.payments", "ng-controller" => "PaymentCtrl"} do |f|
%save-bar{ persist: "true" }
%fieldset.no-border-top
= render partial: 'form', locals: { f: f }
.filter-actions.actions
= button_tag t(:update), type: 'button', "ng-click" => "submitPayment()"
- else
= Spree.t(:cannot_create_payment_without_payment_methods)
= link_to Spree.t(:please_define_payment_methods), admin_payment_methods_url

View File

@@ -0,0 +1,18 @@
= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Payments' }
- content_for :page_title do
%i.icon-arrow-right
= Spree.t("activerecord.models.#{@payment.class.to_s.underscore}.one")
%i.icon-arrow-right
= payment_method_name(@payment)
- content_for :page_actions do
%li= button_link_to Spree.t(:back_to_payments_list), spree.admin_order_payments_url(@order), icon: 'icon-arrow-left'
= render partial: "spree/admin/payments/source_views/#{@payment.payment_method.method_type}", locals: { payment: @payment.source.is_a?(Spree::Payment) ? @payment.source : @payment }
.align-center
%h5
= label_tag nil, Spree.t(:amount)
\:
%span.green= @payment.amount

View File

@@ -1,58 +0,0 @@
<fieldset class="no-border-bottom">
<div class="field" data-hook="previous_cards">
<% @previous_cards.each do |card| %>
<label><%= radio_button_tag :card, card.id, card == @previous_cards.first %> <%= card.display_number %><br /></label>
<% end %>
<label><%= radio_button_tag :card, 'new', @previous_cards.none?, { class: "card_new" } %> <%= t(:use_new_cc) %></label>
</div>
<div class="card_form" data-hook>
<% param_prefix = "payment_source[#{payment_method.id}]" %>
<div class="clear"></div>
<div class="alpha four columns">
<div data-hook="card_number">
<div class="field">
<%= hidden_field_tag "#{param_prefix}[cc_type]", '', {class: 'ccType'} %>
<%= label_tag "card_number#{payment_method.id}", raw(t(:card_number) + content_tag(:span, ' *', :class => 'required')) %>
<%= text_field_tag "#{param_prefix}[number]", '', :class => 'required fullwidth cardNumber', :id => "card_number#{payment_method.id}", :maxlength => 19 %>
<span id="card_type" style="display:none;">
( <span id="looks_like" ><%= t(:card_type_is) %> <span id="type"></span></span>
<span id="unrecognized"><%= t(:unrecognized_card_type) %></span>
)
</span>
</div>
</div>
</div>
<div class="alpha four columns">
<div data-hook="card_name">
<div class="field">
<%= label_tag "card_name#{payment_method.id}", raw(t(:name) + content_tag(:span, ' *', :class => 'required')) %>
<%= text_field_tag "#{param_prefix}[name]", '', id: "card_name#{payment_method.id}", class: 'required fullwidth', maxlength: 19 %>
</div>
</div>
</div>
<div class="three columns">
<div data-hook="card_expiration" class="field">
<%= label_tag "card_expiry#{payment_method.id}", raw(t(:expiration) + content_tag(:span, ' *', :class => 'required')) %><br>
<%= text_field_tag "#{param_prefix}[expiry]", '', id: "card_expiry#{payment_method.id}", class: "required cardExpiry", placeholder: "MM / YY" %>
</div>
</div>
<div class="omega two columns">
<div data-hook="card_code" class="field">
<%= label_tag "card_code#{payment_method.id}", raw(t(:card_code) + content_tag(:span, ' *', :class => "required")) %>
<%= text_field_tag "#{param_prefix}[verification_value]", '', id: "card_code#{payment_method.id}", class: 'required fullwidth cardCode', size: 5 %>
<a href="/content/cvv" class="info cvvLink" target="_blank">
(<%= t(:what_is_this) %>)
</a>
</div>
</div>
<div class="nine columns">
<%= image_tag 'credit_cards/credit_card.gif', :class => 'credit-card-image' %>
</div>
<div class="clear"></div>
</div>
</fieldset>

View File

@@ -0,0 +1,45 @@
%fieldset.no-border-bottom
.field
- @previous_cards.each do |card|
%label
= radio_button_tag :card, card.id, card == @previous_cards.first
= card.display_number
%br/
%label
= radio_button_tag :card, 'new', @previous_cards.none?, { class: "card_new" }
= t(:use_new_cc)
.card_form
- param_prefix = "payment_source[#{payment_method.id}]"
.clear
.alpha.four.columns
%div
.field
= hidden_field_tag "#{param_prefix}[cc_type]", '', {class: 'ccType'}
= label_tag "card_number#{payment_method.id}", raw(t(:card_number) + content_tag(:span, ' *', class: 'required'))
= text_field_tag "#{param_prefix}[number]", '', class: 'required fullwidth cardNumber', id: "card_number#{payment_method.id}", maxlength: 19
%span#card_type{style: "display:none;"}
(
%span#looks_like
= t(:card_type_is)
%span#type
%span#unrecognized= t(:unrecognized_card_type)
)
.alpha.four.columns
%div
.field
= label_tag "card_name#{payment_method.id}", raw(t(:name) + content_tag(:span, ' *', class: 'required'))
= text_field_tag "#{param_prefix}[name]", '', id: "card_name#{payment_method.id}", class: 'required fullwidth', maxlength: 19
.three.columns
.field
= label_tag "card_expiry#{payment_method.id}", raw(t(:expiration) + content_tag(:span, ' *', class: 'required'))
%br/
= text_field_tag "#{param_prefix}[expiry]", '', id: "card_expiry#{payment_method.id}", class: "required cardExpiry", placeholder: "MM / YY"
.omega.two.columns
.field
= label_tag "card_code#{payment_method.id}", raw(t(:card_code) + content_tag(:span, ' *', class: "required"))
= text_field_tag "#{param_prefix}[verification_value]", '', id: "card_code#{payment_method.id}", class: 'required fullwidth cardCode', size: 5
%a.info.cvvLink{href: "/content/cvv", target: "_blank"}
(#{t(:what_is_this)})
.nine.columns
= image_tag 'credit_cards/credit_card.gif', class: 'credit-card-image'
.clear

View File

@@ -0,0 +1,33 @@
%fieldset
%legend{:align => "center"}= Spree.t(:credit_card)
.row
.alpha.six.columns
%dl
%dt
= Spree.t(:card_number)
\:
%dd= payment.source.display_number
%dt
= Spree.t(:expiration)
\:
%dd
= payment.source.month
\/
= payment.source.year
%dt
= Spree.t(:card_code)
\:
%dd= payment.source.verification_value
.omega.six.columns
%dl
%dt
= t(:maestro_or_solo_cards)
\:
%dd= payment.source.issue_number
%dt
= Spree.t(:start_date)
\:
%dd
= payment.source.start_month
\/
= payment.source.start_year

View File

@@ -0,0 +1,14 @@
%tr.product_property.fields{"data-hook" => "product_property", id: "spree_#{dom_id(f.object)}"}
%td.no-border
%span.handle
= f.hidden_field :id
%td.property_name
- if spree_current_user.admin?
= f.text_field :property_name, class: 'autocomplete'
- else
= f.select :property_name, @properties, { include_blank: true }, { class: 'select2 fullwidth' }
%td.value
= f.text_field :value, class: 'autocomplete'
%td.actions
- if f.object.persisted?
= link_to_delete f.object, no_text: true

View File

@@ -0,0 +1,71 @@
= render partial: 'spree/admin/shared/product_sub_menu'
= render partial: 'spree/admin/shared/product_tabs', locals: { current: 'Product Properties' }
= render partial: 'spree/shared/error_messages', locals: { target: @product }
- 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'
= 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}
%thead
%tr
%th.no-border
%th= t('admin.products.properties.property_name')
%th= t('admin.description')
%th.actions
%tbody#product_properties
= f.fields_for :product_properties do |pp_form|
= 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)
%br
%br
#inherited_properties
%table.index
%thead
%tr
%th= t('admin.products.properties.inherited_property')
%th= t('admin.description')
%th.actions
%tbody#producer_properties
- @product.supplier.producer_properties.each do |producer_property|
%tr
%td= producer_property.property.presentation
%td= producer_property.value
%td.actions
= render partial: 'spree/admin/shared/edit_resource_links'
= hidden_field_tag 'clear_product_properties', 'true'
:coffee
$(document).ready ->
$("#inherited_properties").toggle $("input#product_inherits_properties").is(':checked')
$("input#product_inherits_properties").change ->
$("#inherited_properties").toggle $(this).is(':checked')
:javascript
var properties = #{raw(@properties.to_json)};
$("#product_properties input.autocomplete").live("keydown", function(){
already_auto_completed = $(this).is('ac_input');
if (!already_auto_completed) {
$(this).autocomplete({source: properties});
$(this).focus();
}
});

View File

@@ -4,7 +4,9 @@
%div{ ng: { app: 'ofn.admin', controller: 'AdminProductEditCtrl', init: 'initialise()' } }
= render 'spree/admin/products/index/filters'
%hr.divider.sixteen.columns.alpha.omega
= render 'spree/admin/products/index/actions'
= render 'spree/admin/products/index/indicators'
= render 'spree/admin/products/index/products'
%div{'ng-show' => "!RequestMonitor.loading && products.length > 0" }
= render partial: 'admin/shared/angular_pagination'

View File

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

View File

@@ -1,26 +1,35 @@
.filters.sixteen.columns.alpha.omega
.quick_search.three.columns.alpha
%label{ for: 'quick_filter' }
%br
%input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search') }
.one.columns &nbsp;
.filter_select.three.columns
%label{ for: 'producer_filter' }= t 'producer'
%br
%select.fullwidth{ id: 'producer_filter', 'ofn-select2-min-search' => 5, ng: {model: 'producerFilter', options: 'producer.id as producer.name for producer in filterProducers'} }
.filter_select.three.columns
%label{ for: 'category_filter' }= t 'category'
%br
%select.fullwidth{ id: 'category_filter', 'ofn-select2-min-search' => 5, ng: {model: 'categoryFilter', options: 'taxon.id as taxon.name for taxon in filterTaxons'} }
- if FeatureFlags.new(@current_user).product_import_enabled?
%fieldset
%legend{align: 'center'}= t(:search)
.filters.sixteen.columns.alpha.omega
.quick_search.three.columns.alpha
%label{ for: 'quick_filter' }
%br
%input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search') }
.one.columns &nbsp;
.filter_select.three.columns
%label{ for: 'producer_filter' }= t 'producer'
%br
%select.fullwidth{ id: 'producer_filter', 'ofn-select2-min-search' => 5, ng: {model: 'producerFilter', options: 'producer.id as producer.name for producer in producers'} }
.filter_select.three.columns
%label{ for: 'category_filter' }= t 'category'
%br
%select.fullwidth{ id: 'category_filter', 'ofn-select2-min-search' => 5, ng: {model: 'categoryFilter', options: 'taxon.id as taxon.name for taxon in taxons'} }
.filter_select.three.columns
%label{ for: 'import_filter' } Import Date
%br
%select.fullwidth{ id: 'import_date_filter', 'ofn-select2-min-search' => 5, ng: {model: 'importDateFilter', init: "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}"}}
%option{value: "{{date.id}}", ng: {repeat: "date in importDates track by date.id" }}
%option{value: "{{date.id}}", ng: {repeat: "date in importDates" }}
{{date.name}}
.filter_clear.three.columns.omega
%label{ for: 'clear_all_filters' }
%br
%input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" }
.filter_clear.three.columns.omega
%label{ for: 'clear_all_filters' }
%br
%input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" }
.clearfix
.actions.filter-actions
%div
%a.button.icon-search{'ng-click' => 'fetchProducts()'}
= t(:filter_results)

View File

@@ -1,14 +1,15 @@
%div{ 'ng-show' => '!spree_api_key_ok' }
{{ api_error_msg }}
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'loading' }
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
%br
%img.spinner{ src: "/assets/spinning-circles.svg" }
%h1= t('.title')
%div.sixteen.columns.alpha{ 'ng-show' => '!loadingAllPages && filteredProducts.length == 0 && query.length==0' }
%div.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && query.length==0' }
%h1#no_results= t('.no_products')
%div.sixteen.columns.alpha{ 'ng-show' => '!loadingAllPages && filteredProducts.length == 0 && query.length!=0' }
%div.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && query.length!=0' }
%h1#no_results
= t('.no_results')
'

View File

@@ -1,14 +1,14 @@
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || filteredProducts.length == 0' }
%div.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' }
%form{ name: 'bulk_product_form' }
%save-bar{ dirty: "bulk_product_form.$dirty", persist: "false" }
%input.red{ type: "button", value: t(:save_changes), ng: { click: "submitProducts()", disabled: "!bulk_product_form.$dirty" } }
%input{ type: "button", value: t(:close), 'ng-click' => "cancel('#{admin_products_path}')" }
%table.index#listing_products.bulk{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1" }
%table.index#listing_products.bulk
= render 'spree/admin/products/index/products_head'
%tbody{ 'ng-repeat' => 'product in filteredProducts = ( products | filter:query | producer: producerFilter | category: categoryFilter | importDate: importDateFilter | limitTo:limit )', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
%tbody{ 'ng-repeat' => 'product in products', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
= render 'spree/admin/products/index/products_product'
= render 'spree/admin/products/index/products_variant'

View File

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

View File

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

View File

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

View File

@@ -3,11 +3,6 @@
:variants_search => spree.admin_search_variants_path(:format => 'json'),
:taxons_search => spree.api_taxons_path(:format => 'json'),
:user_search => spree.admin_search_users_path(:format => 'json'),
:product_search => spree.api_products_path(:format => 'json'),
:option_type_search => spree.api_option_types_path(:format => 'json'),
:states_search => spree.api_states_path(:format => 'json'),
:orders_api => spree.api_orders_path(:format => 'json'),
:stock_locations_api => spree.api_stock_locations_path(:format => 'json'),
:variants_api => spree.api_variants_path(:format => 'json')
:orders_api => spree.api_orders_path(:format => 'json')
}.to_json %>;
</script>

View File

@@ -16,16 +16,13 @@
</ul>
<ul class='variant-data'>
<li class='variant-sku'><strong><%= t('admin.sku') %>:</strong> {{variant.sku}}</li>
<li class='variant-on_hand'><strong><%= t('on_hand') %>:</strong> {{variant.on_hand}}</li>
{{#if variant.on_demand }}
<li class='variant-on_demand'><strong><%= t('on_demand') %></strong></li>
{{ else }}
<li class='variant-on_hand'><strong><%= t('on_hand') %>:</strong> {{variant.on_hand}}</li>
{{/if}}
<li class='variant-options_text'><strong><%= t('.unit') %>:</strong> {{variant.options_text}}</li>
</ul>
{{#if variant.option_values}}
<ul class='variant-options'>
{{#each variant.option_values}}
<li><strong>{{this.option_type.presentation}}:</strong> {{this.presentation}}</li>
{{/each}}
</ul>
{{/if}}
</div>
</div>
</script>
@@ -45,7 +42,7 @@
</thead>
<tbody>
<tr>
{{#if variant.[in_stock?]}}
{{#if variant.in_stock}}
{{#if variant.on_demand}}
<td><%= Spree.t(:on_demand) %></td>
{{else}}

View File

@@ -0,0 +1,67 @@
.label-block.left.six.columns.alpha{'ng-app' => 'admin.products'}
.field
= f.label :display_name, t(:display_name)
= f.text_field :display_name, class: "fullwidth"
.field
= f.label :display_as, t(:display_as)
= f.text_field :display_as, class: "fullwidth"
- if product_has_variant_unit_option_type?(@product)
- if @product.variant_unit != 'items'
.field{'ng-controller' => 'variantUnitsCtrl'}
= f.label :unit_value, "#{t('admin.'+@product.variant_unit)} ({{unitName(#{@product.variant_unit_scale}, '#{@product.variant_unit}')}})"
= hidden_field_tag 'product_variant_unit_scale', @product.variant_unit_scale
= text_field_tag :unit_value_human, nil, {class: "fullwidth", 'ng-model' => 'unit_value_human', 'ng-change' => 'updateValue()'}
= f.text_field :unit_value, {hidden: true, 'ng-value' => 'unit_value'}
.field
= f.label :unit_description, t(:spree_admin_unit_description)
= f.text_field :unit_description, class: "fullwidth", placeholder: t('admin.products.unit_name_placeholder')
%div
- @product.option_types.each do |option_type|
- unless variant_unit_option_type?(option_type)
.field
= label :new_variant, option_type.presentation
- if @variant.new_record?
= select(:new_variant, option_type.presentation, option_type.option_values.collect {|ov| [ ov.presentation, ov.id ] }, {}, {class: 'select2 fullwidth'})
- else
- 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.text_field :sku, class: 'fullwidth'
.field
= f.label :price, Spree.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.text_field :cost_price, value: number_to_currency(@variant.cost_price, unit: ''), class: 'fullwidth'
- if Spree::Config[:track_inventory_levels]
%div{ 'set-on-demand' => '' }
.field.checkbox
%label
= f.check_box :on_demand
= t(:on_demand)
%div{'ofn-with-tip' => t('admin.products.variants.to_order_tip')}
%a= t('admin.whats_this')
.field
= f.label :on_hand, t(:on_hand)
.fullwidth
= f.text_field :on_hand
.right.six.columns.omega.label-block
- if @product.variant_unit != 'weight'
.field
= f.label 'weight', t('weight')+' (kg)'
- value = number_with_precision(@variant.weight, precision: 2)
= f.text_field 'weight', value: value, class: 'fullwidth'
- [:height, :width, :depth].each do |field|
.field
= f.label field, t(field)
- value = number_with_precision(@variant.send(field), precision: 2)
= f.text_field field, value: value, class: 'fullwidth'
.clear

View File

@@ -0,0 +1,11 @@
= render partial: 'spree/admin/shared/product_sub_menu'
= render partial: 'spree/admin/shared/product_tabs', locals: { current: 'Variants' }
= render partial: 'spree/shared/error_messages', locals: { target: @variant }
= form_for [:admin, @product, @variant] do |f|
%fieldset.no-border-top
%div
= render partial: 'form', locals: { f: f }
= render partial: 'spree/admin/shared/edit_resource_links'

View File

@@ -0,0 +1,54 @@
= render partial: 'spree/admin/shared/product_sub_menu'
= render partial: 'spree/admin/shared/product_tabs', locals: {current: 'Variants'}
#new_variant
- if @variants.any?
%table.index.sortable{"data-sortable-link" => update_positions_admin_product_variants_path(@product)}
%colgroup
%col{style: "width: 5%"}/
%col{style: "width: 25%"}/
%col{style: "width: 20%"}/
%col{style: "width: 20%"}/
%col{style: "width: 15%"}/
%col{style: "width: 15%"}/
%thead
%tr
%th{colspan: "2"}= Spree.t(:options)
%th= Spree.t(:price)
%th= Spree.t(:sku)
%th.actions
%tbody
- @variants.each do |variant|
%tr{id: spree_dom_id(variant), class: cycle('odd', 'even'), style: "#{"color:red;" if variant.deleted? }" }
%td.no-border
%span.handle
%td= variant.full_name
%td.align-center= variant.display_price.to_html
%td.align-center= variant.sku
%td.actions
= link_to_edit(variant, no_text: true) unless variant.deleted?
= link_to_delete(variant, no_text: true) unless variant.deleted?
- unless @product.has_variants?
%tr
%td{colspan: "5"}= Spree.t(:none)
- else
.alpha.twelve.columns.no-objects-found
= Spree.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
- 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')
%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')

View File

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

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