Compare commits

...

225 Commits

Author SHA1 Message Date
Maikel Linke
3c3c3394a5 Add spanish translations manually
A change in the Transifex API made the automated commit fail. Hence
doing it manually here.
2017-07-12 11:30:47 +10:00
Transifex-Openfoodnetwork
5b3816fa9e Updating translations for config/locales/fr.yml [skip ci] 2017-07-01 16:17:12 +10:00
Transifex-Openfoodnetwork
3507bae002 Updating translations for config/locales/nb.yml [skip ci] 2017-06-30 17:54:18 +10:00
Maikel Linke
b7f99c3185 Merge branch 'transifex' 2017-06-30 16:28:08 +10:00
Rob Harrington
ce85027b79 Update rubocop_todo.yml using rubocop 0.49.1 2017-06-29 10:47:56 +10:00
Rob Harrington
94b6a61665 Bump rubocop to latest version (0.49.1) 2017-06-29 10:47:56 +10:00
Rob Harrington
f84acf44f3 Disable scss-lint for CodeClimate
Until we can tweak #1393 to work with CodeClimate
2017-06-28 18:09:03 +10:00
Diogo Matsubara
386d651678 Fix #1611 (Rephrase error message for people logging in with an existing user email)
update test to use new rspec syntax
2017-06-28 15:35:21 +10:00
Matt-Yorkley
23a6e2dd8f Changed modal redirect behaviour 2017-06-28 14:35:17 +10:00
Transifex-Openfoodnetwork
6c69eebcbf Updating translations for config/locales/nb.yml [skip ci] 2017-06-27 05:41:31 +10:00
Rob Harrington
d8a158c56d Rubocop: Update comment on Rails/DynamicFindBy, justifying Enabled: false 2017-06-24 13:37:47 +10:00
Rob Harrington
dfda30f4cb Rubocop: Regenerate rubocop_todo.yml using --exclude-limit 100 2017-06-24 13:33:57 +10:00
Rob Harrington
d26970d04c Rubocop: Move Style/ClassAndModuleChildren back to rubocop_todo.yml 2017-06-24 12:59:03 +10:00
Transifex-Openfoodnetwork
63e453e2c7 Updating translations for config/locales/fr.yml [skip ci] 2017-06-23 18:09:39 +10:00
Rob Harrington
c7ed27286a Rubocop: Correct Lint/ParenthesesAsGroupedExpression offences 2017-06-23 17:02:20 +10:00
Rob Harrington
e5340cb53a Rubocop: Correct Lint/UselessAssignment offences, disable for /spec in main config 2017-06-23 16:49:08 +10:00
Rob Harrington
7079952f6b Add rubocop to development group in gemfile 2017-06-23 16:49:08 +10:00
Rob Harrington
f79dcaba15 Rubocop: Add Rails cops to config and todo 2017-06-23 16:49:08 +10:00
Rob Harrington
33a1d97d5e Rubocop: Disable Style/ClassAndModuleChildren in main config 2017-06-23 16:49:08 +10:00
Rob Harrington
28ea23965b Rubocop: Correct Lint/AmbiguousOperator offences 2017-06-23 16:49:08 +10:00
Rob Harrington
fbdbbb980f Rubocop: Disable Lint/AmbiguousBlockAssociation in main config 2017-06-23 16:49:08 +10:00
Rob Harrington
fc5b339e2a Rubocop: Correct Lint/DuplicateMethods offences 2017-06-23 16:49:08 +10:00
Rob Harrington
4acb3f1962 Rubocop: Use Relaxed.Ruby.Style as starting point for settings
Updated .rubocop_todo.yml
2017-06-23 16:49:08 +10:00
Rob Harrington
7bb58342fc Rubocop: Auto-correct Style/EmptyLineBetweenDefs offences 2017-06-23 16:49:08 +10:00
Rob Harrington
b277ff03ea Rubocop: Auto-correct Style/TrailingBlankLines offences 2017-06-23 16:49:07 +10:00
Rob Harrington
5061d0c4cd Rubocop: Tweak config, and add todo file 2017-06-23 16:49:07 +10:00
Continuous Integration
931a5be162 Merge remote-tracking branch 'origin/master' into HEAD 2017-06-22 13:19:25 +10:00
Maikel Linke
e437cba155 Make style flexible for all sizes of logos
Trying to support logos that are big. But logos that are too small
should not be stretched either, better small than ugly.

Remove static size declaration from HTML template since it doesn't
reflect the logo size.

Unfortunately, there are other CSS files that are in conflict. The file
mail/email.css.scss defines this globally:

    img {
      max-width: 100%;
    }
2017-06-16 16:49:43 +10:00
Maikel Linke
ec53d9df7a Merge tag 'v1.8.11' into transifex 2017-06-15 09:59:59 +10:00
Maikel Linke
3330e9b219 Init registration form with default country 2017-06-14 16:47:02 +10:00
Maikel Linke
884743ce97 Filter countries without states for registration 2017-06-14 16:47:01 +10:00
Maikel Linke
b482d1e57c Spec current behaviour of country selector
The enterprise sign-up form shows invalid countries without states and
does not choose the default country. See:

https://github.com/openfoodfoundation/openfoodnetwork/issues/1330
2017-06-14 16:47:01 +10:00
Maikel Linke
78f5002be5 Separate variant names by spaces
To enable the search for the beginning of a variant name, they need to
be separated.

See: https://github.com/openfoodfoundation/openfoodnetwork/pull/1545
2017-06-14 16:37:46 +10:00
Maikel Linke
a42799dff4 Initialise product.variant_names
Not initialising `product.variant_names` causes it to become the string
"undefinedVariant1Variant2" while it should just be "Variant1Variant2".

This mistake did not matter when `variant_names` was just used to search
for a sub-string. A recent change to search only for the beginning of a
word, caused this to fail.

See: https://github.com/openfoodfoundation/openfoodnetwork/pull/1545
2017-06-14 16:28:55 +10:00
Maikel Linke
1196cd7df4 Add style guide to the source locale
There has been a lack of rules how to structure our locale en.yml. This
is a start to document some guidelines which can evolve over time.

Rules can be discussed in the community forum or in pull requests. Once
there is a strong agreement, we should add it to this guide.
2017-06-14 15:20:01 +10:00
Enrico Stano
3c1eae1f47 Do not blindly clear the whole Rails cache 2017-06-09 10:32:57 +10:00
Matt-Yorkley
ba27d63f9c Fix logo sizing 2017-06-06 13:59:42 +01:00
Julius Pabrinkis
0083733c4c #1471 Do no let long producers' name go under icon 2017-06-02 16:29:43 +10:00
Julius Pabrinkis
a1ffc869f3 Refactor code by suggestion to use RegExp and add tests coverage 2017-06-02 15:42:35 +10:00
Rhodri Karim
7a07e8fa16 Issue 1233: product filter should only match if name begins withs search term 2017-06-02 15:42:35 +10:00
enricostano
699da16049 Remove unused spec support method 2017-05-31 15:22:30 +10:00
sseerrggii
18c7b03f3d Add plurals 2017-05-26 16:51:29 +10:00
Rob Harrington
8b560e6cee Cleaning up angular variant units controllers a little bit 2017-05-24 15:05:33 +10:00
Matt-Yorkley
7974ac45f2 Add unit description placeholder 2017-05-24 15:05:33 +10:00
Matt-Yorkley
41b5cf10dd Quick testing update 2017-05-24 15:05:33 +10:00
Matt-Yorkley
b6955cb98c Updated variant feature spec 2017-05-24 15:05:33 +10:00
Matt-Yorkley
6c36c269c8 Product Edit UX adjustments 2017-05-24 15:05:33 +10:00
Pierre de Lacroix
b879ea5a96 add comments 2017-05-24 14:37:54 +10:00
Pierre de Lacroix
a6ed4a2c6a fix bad return value
in method Spree::Adjustment#find_closest_tax_rates_from_included_tax
2017-05-24 14:37:54 +10:00
Pierre de Lacroix
6972822c49 fix escaping problems on ticket view 2017-05-24 13:35:59 +10:00
Pierre de Lacroix
db63f30a76 cosmetic fixes to the ticket view 2017-05-24 13:35:59 +10:00
Rob Harrington
848193434b Show item single_display_amount instead of variant price in order details table
Also has the effect of taking the inventory price into account (since it is stored against the line item price)
2017-05-19 17:27:04 +10:00
stveep
570f0344da Remove unused method in wrong controller ^_^ 2017-05-19 16:14:22 +10:00
stveep
38c25c412f Remove duplicated spec, better error message suppression, fix shipping test to wait correctly 2017-05-19 16:14:22 +10:00
stveep
3b4ffe1f70 Suppress line item validation error for newly-created orders 2017-05-19 16:10:06 +10:00
stveep
4699c654fc Fix feature specs for new 'new admin order' flow 2017-05-19 16:10:06 +10:00
stveep
c1c5d00f45 Add new view to set distribution, ensure the scoped variant single item price is used. 2017-05-19 16:10:06 +10:00
stveep
220693f4e3 Starting alternative flow for new manual order 2017-05-19 16:06:43 +10:00
stveep
fcce858ea4 Fix OC with override factory 2017-05-19 16:06:43 +10:00
stveep
1f9698f7a2 Test to reproduce bug with overridden price not being displayed when an order is added manually 2017-05-19 16:06:43 +10:00
Rob Harrington
1766da9d60 Adding ng-cloak directive to changeable orders banner 2017-05-19 15:54:55 +10:00
Rob Harrington
15ee62aaa8 Fixing race condition on link click in spec/features/consumer/shops_spec.rb 2017-05-19 13:29:47 +10:00
Rob Harrington
6fc4a297a0 Fixing race condition on link click in spec/features/consumer/shopping/products_spec.rb 2017-05-19 13:15:30 +10:00
Rob Harrington
74d8dc48b4 Rewriting shopfront alert regarding changeable orders to be dynamic 2017-05-18 15:35:07 +10:00
Rob Harrington
b6f4ce373e Preloading variants for VariantOverride.indexed 2017-05-18 15:34:22 +10:00
Rob Harrington
c9f186f48f Fixing SlideOutUp animation for darkswarm 2017-05-18 15:33:14 +10:00
Rob Harrington
daab0dfd7a Refactor: Splitting changeable_order_from_number out into separate method 2017-05-17 12:38:17 +10:00
Rob Harrington
22b5dafad2 Further tweaks to LineItemsController 2017-05-17 12:27:29 +10:00
Transifex-Openfoodnetwork
110f74eee8 Updating translations for config/locales/es.yml [skip ci] 2017-05-12 20:49:15 +10:00
Rob Harrington
61cb78fc93 Tweaks to LineItemsController, renaming #index to #bought 2017-05-12 16:29:15 +10:00
Rob Harrington
38d3b446cc Removing unused #tag_list method on VariantSerializer 2017-05-12 16:11:16 +10:00
Rob Harrington
e47e10d267 Removing n+1 query from #items_bought_by_user 2017-05-12 16:11:16 +10:00
Rob Harrington
77c8c85775 Memoizing the result of OrdersController#order_to_update 2017-05-12 16:11:16 +10:00
Rob Harrington
fab6d70832 Changeable orders only returns orders if the shop allows changes 2017-05-12 16:11:16 +10:00
Rob Harrington
9586666248 Updating translation for allow_changes option 2017-05-12 16:11:16 +10:00
Continuous Integration
f5ab9a3445 Merge remote-tracking branch 'origin/master' into HEAD 2017-05-12 10:39:06 +10:00
Continuous Integration
1ac8c85113 Merge remote-tracking branch 'origin/master' into HEAD 2017-05-12 09:55:25 +10:00
Rob Harrington
56c2350d36 Updating enterprise fees on completed order from OrdersController#update 2017-05-10 12:04:32 +10:00
Transifex-Openfoodnetwork
6eafed00f5 Updating translations for config/locales/fr.yml [skip ci] 2017-05-05 21:35:28 +10:00
Transifex-Openfoodnetwork
74661c0b77 Updating translations for config/locales/nb.yml [skip ci] 2017-05-05 18:10:25 +10:00
Rob Harrington
c4fbcb19d0 Only show Admin & Handling fees once on completed order form 2017-05-05 15:28:01 +10:00
Rob Harrington
08e391856c Ask user to confirm cancellation of order 2017-05-05 15:17:14 +10:00
Rob Harrington
8c8b40c5a8 Showing order cycle close time on banner when multiple open orders are present 2017-05-05 14:20:57 +10:00
Rob Harrington
c3eda435eb Hiding 'Back to Cart' and 'Back to Store' buttons on completed orders when distributor does not match cart 2017-05-05 14:20:57 +10:00
Rob Harrington
5eadb33db9 Using changes_allowed? to authorize cancellation of line_items 2017-05-05 14:20:57 +10:00
Rob Harrington
f977a05b08 Fixing broken spec for fetching list of bought items as json 2017-05-05 14:20:57 +10:00
Rob Harrington
217eda8362 Shipping and payment fees are updated for completed orders when the order changes 2017-05-05 14:20:57 +10:00
Rob Harrington
579f3bf90a Changing quantity and deleting line_items of completed orders works with inventory where present 2017-05-05 14:20:57 +10:00
Rob Harrington
348ab81c42 Overriding #increment! using variant overrides 2017-05-05 14:20:57 +10:00
Rob Harrington
3df629bc6e Prevent users from removing the final line item of an order, suggest cancelling instead 2017-05-05 14:20:57 +10:00
Rob Harrington
07b2f0a7c2 Showing banner for distributor of order on order summary, regardless of current_distributor 2017-05-05 14:20:57 +10:00
Rob Harrington
c0445d46f3 Changeable orders ignores cancelled orders 2017-05-05 14:20:57 +10:00
Rob Harrington
6c90b4e6d0 Adding alert to order summary, notifying user of remaining time to make changes 2017-05-05 14:20:57 +10:00
Rob Harrington
e79a23a554 Disabling save button on order page until changes are made to the order 2017-05-05 14:20:57 +10:00
Rob Harrington
314ccc2f27 Ensuring that #items_bought_by_user doesn't return items from cancelled orders 2017-05-05 14:20:57 +10:00
Rob Harrington
68c8759af1 Count of items in cart form looks is consistent with rest of UI (ie. uses quantities) 2017-05-05 14:20:57 +10:00
Rob Harrington
314e9a4f15 Open shopfront existing orders flash link in a new window 2017-05-05 14:20:57 +10:00
Rob Harrington
0029a1b6cf Ensure order adjustments are displayed in edit form for customers 2017-05-05 14:20:57 +10:00
Rob Harrington
bfcde72855 Use explicit format for dates on front-end account page 2017-05-05 14:20:57 +10:00
Rob Harrington
3d0ada803f Test presence of 'Edit' link for previous orders in cart 2017-05-05 14:20:57 +10:00
Rob Harrington
840c936a6f OrderSerializer#changes_allowed_until returns 'Not allowed' unless changes are allowed 2017-05-05 14:20:57 +10:00
Rob Harrington
7ea74ccf4a Order confirmation redirects 'Back to Cart' if cart is non-empty 2017-05-05 14:20:57 +10:00
Rob Harrington
b55036e165 Adding alert to shopfront to alert user to presence of orders open for editing 2017-05-05 14:20:57 +10:00
Rob Harrington
47011e11ff order_cycle.items_bought_by_user actually scopes to the current distributor 2017-05-05 14:20:57 +10:00
Rob Harrington
ae28d7a96b Adding a link in cart to allow user to edit existing + open orders for same OC 2017-05-05 14:20:57 +10:00
Rob Harrington
0dd8959bf7 Use an actual completed order for OrdersController#order_to_update spec 2017-05-05 14:20:57 +10:00
Rob Harrington
316b0915e4 Just display the date that orders can be changed until
No fancy moment.js stuff
2017-05-05 14:20:57 +10:00
Rob Harrington
e21bfd95f4 Renaming order.editable? to order.changes_allowed? 2017-05-05 14:20:57 +10:00
Rob Harrington
893331c7bb Adding 'Open Orders' section to the top of customer accounts page 2017-05-05 14:20:57 +10:00
Rob Harrington
b94bcd697f Restructuring customer accounts spec 2017-05-05 14:19:32 +10:00
Rob Harrington
b0ff7ca767 Making accounts page orders listing full-width 2017-05-05 14:19:32 +10:00
Rob Harrington
493a537f2c Making out-of-stock products in the cart more visible 2017-05-05 14:19:32 +10:00
Rob Harrington
cda43f075b Moving bought items listing up into main section of cart 2017-05-05 14:19:32 +10:00
Rob Harrington
5d9f92eaa7 Adding 'Cancel Order' button to order confimation interface 2017-05-05 14:19:32 +10:00
Rob Harrington
c6afa1849c Basic routing for front-end OrdersController#cancel action 2017-05-05 14:19:32 +10:00
Rob Harrington
eec3a21c89 Allow editing of completed orders from confirmation page where distributor allows it 2017-05-05 14:19:32 +10:00
Rob Harrington
768240a5ba Reorganising darkswarm order views into partials 2017-05-05 14:19:32 +10:00
Rob Harrington
5af8668560 Rewriting Spree::Taxons.distributed_taxons for better performance 2017-05-05 14:19:32 +10:00
Rob Harrington
630b8a2577 Add OrdersController#order_to_update method, to allow updating a complete order where appropriate 2017-05-05 14:19:32 +10:00
Maikel Linke
12a6f652ad Warn when allowing guest orders and order changes 2017-05-05 14:19:32 +10:00
Maikel Linke
db4a528ba3 Update shipment to re-calculate included tax
And re-create tax charges on the order. (untested)
2017-05-05 14:19:32 +10:00
Maikel Linke
479c7ba24b Update transaction fees of completed orders
The adjustments associated to incomplete payments are re-calculated when
a line item is removed from a completed order.
2017-05-05 14:19:32 +10:00
Maikel Linke
1f08729df3 Update shipping fees after removing item
After removing an item from a completed order, update the shipping fees
of all shipments that are pending or ready (not completed).
2017-05-05 14:19:32 +10:00
Maikel Linke
811671661e Remove item from dropdown cart after deletion 2017-05-05 14:19:32 +10:00
Maikel Linke
936df71d0d Link to bought products on cart page
The checkout page was just linking to the cart page, but not scrolling
down.
2017-05-05 14:19:32 +10:00
Maikel Linke
f3f6714472 Add message about previous orders on checkout page
The message appears if the user ordered before within the same order
cycle and the shop allows to change previous orders.
2017-05-05 14:19:32 +10:00
Maikel Linke
3bce2eb7b5 Rename translation key checkout to avoid conflicts
A top-level translation key `checkout` can't co-exist with translation
keys in the path `checkout`. Moving the old key to
`shared.menu.cart.checkout` avoids conflicts. It also structures the
locale better by view.
2017-05-05 14:19:32 +10:00
Maikel Linke
bf05866f92 Change headline of already ordered items 2017-05-05 14:19:32 +10:00
Maikel Linke
219ad4a3a7 Reload bought products after choosing order cycle 2017-05-05 14:19:32 +10:00
Maikel Linke
a7d8028d5a Add retry: 3 to intermittently failing spec 2017-05-05 14:19:32 +10:00
Maikel Linke
d49469a3e6 Show bought items only if changes are allowed
An enterprise can decide to allow changes to orders in open order
cycles. The items of these orders are then displayed in the shopping
cart and can be removed on the cart edit page.
2017-05-05 14:19:32 +10:00
Maikel Linke
4835ef067f Add feature to remove line items from open order cycle
- Add JS controller to send delete requests.
- Add resource controller to destroy items.
- Add authorisation check to abilities.
- Update fees after removing line item.
2017-05-05 14:19:32 +10:00
Maikel Linke
4112c3cc75 Set auth token for all JS HTTP requests 2017-05-05 14:19:32 +10:00
Maikel Linke
0fe4030d71 Display bought items only if present 2017-05-05 14:19:32 +10:00
Maikel Linke
e8d2d4d96f Display already bought items in edit cart view 2017-05-05 14:19:32 +10:00
Maikel Linke
88c3f414fb Handle missing order in cart 2017-05-05 14:18:20 +10:00
Maikel Linke
c0d6b68233 Apply some style guidelines to older code 2017-05-05 14:18:20 +10:00
Maikel Linke
65f62c42b9 Display products already ordered in this oc
Github issue #1083, part of standing orders.

For now, just display already bought products within the same order
cycle in the popup cart view. The edit cart view should follow. Later,
it should be possible to remove items as well.
2017-05-05 14:18:20 +10:00
Maikel Linke
fe7bd5e2cd Serialise tag_list only for variant overrides
Don't try to call tag_list on just variants since that will fail.

Normally, all variants of `current_order` should be extended to
VariantOverrides of the current order cycle. But in development
environment, it can happen that the variants are reloaded without being
extended again.
2017-05-05 14:18:20 +10:00
Maikel Linke
e1b40142b8 Remove unused translations 2017-05-05 14:18:20 +10:00
Maikel Linke
963b4617a9 Add allow-order-changes option to enterprise 2017-05-05 14:18:20 +10:00
Maikel Linke
95ddfc4e40 Merge tag 'v1.8.10' into transifex 2017-05-05 12:28:16 +10:00
François Turbelin
01746ed470 Add missing translations on views 2017-05-04 21:33:17 +02:00
Julius Pabrinkis
f25e3bc6f7 Implement with_currency rspec helper for money amounts 2017-05-04 18:02:38 +01:00
Julius Pabrinkis
e63f1c2991 Refactor time and currency symbol getters 2017-05-03 12:59:16 +01:00
Lynne
29e2886b05 Update contributing.md
Include link to discourse post: How to raise a github issue
2017-05-03 11:50:07 +01:00
Julius Pabrinkis
a97bcf74de Use more simple getter to retrieve currency symbol 2017-05-03 11:25:37 +01:00
Julius Pabrinkis
1e6f4aa73d Restore bundler version 2017-05-03 11:25:37 +01:00
Julius Pabrinkis
7c7933f8bb Use local time zone in tests instead of hardcoded 2017-05-03 11:25:37 +01:00
Julius Pabrinkis
dc69c6e825 Use currency symbol from config in tests 2017-05-03 11:25:37 +01:00
Julius Pabrinkis
41e91765ca Add capybara-screenshot gem for integration tests debugging 2017-05-03 11:25:37 +01:00
Julius Pabrinkis
19569f9316 #1291 Fix dropdown font-size not to zoom in iOS 2017-05-03 10:57:23 +01:00
Maikel Linke
d91c3d1241 Format reset password instructions like Spree 2017-05-03 11:06:48 +10:00
Maikel Linke
b302deb7a3 Add UserMailer specs adapted from Spree code 2017-05-03 11:06:48 +10:00
François Turbelin
2fae467e9a Translate ResetPasswordConfirmation mail 2017-05-03 11:06:48 +10:00
Transifex-Openfoodnetwork
6a5e4bb592 Updating translations for config/locales/sv.yml [skip ci] 2017-04-30 23:38:53 +10:00
Em-AK
c72d17dc83 Run karma task only in test environment
and fails otherwise, as it needs the defaults defined in test environment
2017-04-28 11:28:24 +10:00
Em-AK
78ffdec693 Force english locale in test environment 2017-04-28 11:28:24 +10:00
Pierre de Lacroix
49c19a1d6a update wkhtmltopdf-binary and wicked_pdf 2017-04-27 18:38:47 +10:00
Pierre de Lacroix
e854eb0426 add tests
for methods Spree::Order#tax_adjustment_totals
and Spree::Adjustment#find_closest_tax_rate_from_included_tax
2017-04-27 17:32:36 +10:00
Pierre de Lacroix
4a9c17cb28 better tax aggregation 2017-04-27 17:32:36 +10:00
Matt-Yorkley
0d1547f439 Require OC ready_for in simple OC UI 2017-04-21 09:34:50 +10:00
Matt-Yorkley
fa5ed529cb Added OC form validation 2017-04-21 09:32:49 +10:00
Matt-Yorkley
accb3076e9 Updated translations 2017-04-21 09:32:49 +10:00
Matt-Yorkley
a4e4e1ec68 Require OC name 2017-04-21 09:32:49 +10:00
Transifex-Openfoodnetwork
507e12adba Updating translations for config/locales/fr.yml [skip ci] 2017-04-20 22:05:19 +10:00
Maikel Linke
4809237ecc Fast fail set_order_cycles if distributor not ready 2017-04-19 14:28:17 +10:00
Maikel Linke
81877fedb6 Remove useless andand called on scope 2017-04-19 14:28:17 +10:00
Matt-Yorkley
1f2c6f2a85 Ensure shops display as closed when not configured for sales
squashme

squashme

squash

squash

squash
2017-04-19 14:28:17 +10:00
Matt-Yorkley
4fe5e60967 Updated controller stub to pass test 2017-04-19 14:28:17 +10:00
Matt-Yorkley
f4eb9cb790 Hubs display as closed when not configured for payment or shipping 2017-04-19 14:28:16 +10:00
Maikel Linke
775f9b3ada Move text from view to locale (i18n) 2017-04-12 10:06:53 +10:00
Maikel Linke
188b33921c Remove test entry in locale 2017-04-12 09:56:24 +10:00
Maikel Linke
20c033317f Remove unnecessary string interpolation from view 2017-04-12 09:49:24 +10:00
Matt-Yorkley
e7a5d063ac Update simple OC form 2017-04-12 09:41:40 +10:00
Matt-Yorkley
1fda781d7e Set maxlength on OC displayname field 2017-04-12 09:41:39 +10:00
Matt-Yorkley
45fc801a08 Added tooltips to OC form 2017-04-12 09:41:39 +10:00
Transifex-Openfoodnetwork
5fa45c0716 Updating translations for config/locales/es.yml [skip ci] 2017-04-08 01:22:10 +10:00
Maikel Linke
21337a5b50 Merge tag 'v1.8.9' into transifex 2017-04-06 10:36:14 +10:00
Maikel Linke
ec36a843cf Merge tag 'v1.8.8' into transifex 2017-04-06 10:31:26 +10:00
Transifex-Openfoodnetwork
e99dbaf4d8 Updating translations for config/locales/es.yml [skip ci] 2017-04-05 19:40:18 +10:00
Rob Harrington
c83ad2ecc4 Fixing broken limited reached page in registration flow 2017-04-05 17:02:40 +10:00
Matt-Yorkley
80d8d18eb2 Update terms of service config 2017-04-05 15:48:50 +10:00
Rob Harrington
903d1afb53 Stripping html tags from products description on new form as well 2017-04-05 14:29:23 +10:00
Matt-Yorkley
cd55d2e2ff Product Description - strip weird tags on paste
squashme
2017-04-05 11:29:35 +10:00
Rob Harrington
05cf8c4351 Sanitizing product description for textAngular input 2017-04-05 11:29:35 +10:00
Matt-Yorkley
b04d815408 Changes for code review
Fixed spec
2017-04-05 11:29:35 +10:00
Matt-Yorkley
7b370a2eb6 Removed underline option 2017-04-05 11:29:35 +10:00
Matt-Yorkley
5808b601b8 Added specs for HTML product description 2017-04-05 11:29:35 +10:00
Matt-Yorkley
fdcd3dc3e3 Fixed Capybara not interacting with textAngular 2017-04-05 11:29:35 +10:00
Matt-Yorkley
c4bd085393 Added Angular and textAngular to edit product page 2017-04-05 11:29:35 +10:00
Matt-Yorkley
0e91d01412 UX improvement for selected formatting options 2017-04-05 11:29:35 +10:00
Matt-Yorkley
fcb9e9fa56 Changed buttons 2017-04-05 11:29:35 +10:00
Matt-Yorkley
3591354cb1 Minor tweaks 2017-04-05 11:29:35 +10:00
Matt-Yorkley
b38eab11eb Fixed frontend HTML display 2017-04-05 11:29:35 +10:00
Matt-Yorkley
c43dea60b7 Product Descriptions formatting 2017-04-05 11:29:35 +10:00
Pierre de Lacroix
268bea25d0 add qz/ folder from ofn-qz gem to list of assets 2017-03-30 20:37:20 +02:00
Pierre de Lacroix
e94ae20b31 fix print_ticket authorization 2017-03-30 20:37:20 +02:00
Maikel Linke
a94961c0a7 Fixup merge conflicts and remove unused text 2017-03-29 14:58:19 +11:00
Lynne Davis
0d5fde919b Property name spans signle col heading 2017-03-29 14:47:17 +11:00
Matt-Yorkley
429ef4e2ba Altered product property headings for issue #522 2017-03-29 14:44:30 +11:00
Lynne Davis
e8999d23e1 Updated translations 2017-03-29 13:12:29 +11:00
Keir Osborn
209c9242d9 remove word-wrap class from enterprise.email_address and enterprise.website in javascripts/templates/partials/contact.html.haml 2017-03-24 12:55:30 +11:00
Transifex-Openfoodnetwork
6defb09d2e Updating translations for config/locales/fr.yml [skip ci] 2017-03-21 09:03:15 +11:00
Transifex-Openfoodnetwork
2774c09d7a Updating translations for config/locales/nb.yml [skip ci] 2017-03-20 07:10:53 +11:00
Matt-Yorkley
c62a044598 PI highlight invalid fields in feedback tables 2017-03-17 16:11:52 +11:00
Matt-Yorkley
f73fbe7f23 SpreadsheetEntry Class and PI refactor 2017-03-17 16:11:52 +11:00
Matt-Yorkley
5e1e4c1d19 Product Import UX review updates
Minor tweaks

Minor fix
2017-03-17 16:11:52 +11:00
Matt-Yorkley
cc5a335fb7 Refactor and additional permissions checks
Don't include non-permitted enterprises in existin product count

Tweaked feedback
2017-03-17 16:11:52 +11:00
Matt-Yorkley
91fc3f33a0 PI reset and save step improvements 2017-03-17 16:11:52 +11:00
Matt-Yorkley
648753b412 Improved save step UI 2017-03-17 16:11:52 +11:00
Matt-Yorkley
24fcc3dd34 PI reset absent products 2017-03-17 16:11:52 +11:00
Matt-Yorkley
14fb40a996 Product Import options and defaults
Added available_on test

Obscure case fix and extra spec
2017-03-17 16:11:52 +11:00
Matt-Yorkley
f4511fc74d PI permission test 2017-03-17 16:11:52 +11:00
Matt-Yorkley
3d0f192490 Product Import update 2017-03-17 16:11:52 +11:00
Matt-Yorkley
6b7cdf3a37 Product Import Refactoring 2017-03-17 16:11:52 +11:00
Matt-Yorkley
052d6c6b02 Product Import basic specs 2017-03-17 16:11:52 +11:00
Matt-Yorkley
6e5c878491 Product Import cancan permissions and ui tab 2017-03-17 16:11:52 +11:00
Matt-Yorkley
c0c6cd1a60 Product Import feature 2017-03-17 16:11:52 +11:00
Matt-Yorkley
2ad433590d Add roo gem 2017-03-17 16:11:52 +11:00
Rohan Mitchell
2cb3da56ab Fix regression: Transaction fee double-charged 2017-03-17 12:02:13 +11:00
Rohan Mitchell
8582e6d6b4 Add robustness check against intermittent spec failure 2017-03-17 12:02:13 +11:00
Rohan Mitchell
170101cbfe Avoid reloading order during checkout request, which clears credit card number 2017-03-17 12:02:13 +11:00
Maikel Linke
8107f49373 Merge remote-tracking branch 'origin/master' into transifex
Conflicts:
	config/locales/fr.yml
2017-03-16 09:30:22 +11:00
Transifex-Openfoodnetwork
f235099859 Updating translations for config/locales/fr.yml [skip ci] 2017-03-11 16:50:17 +11:00
297 changed files with 9938 additions and 1718 deletions

View File

@@ -1,11 +1,8 @@
engines:
rubocop:
enabled: true
exclude_fingerprints:
- ac41db8d4ec4cbf508c353d9b65a024f
- 8e3b6322aef5be9f38700b3fd0cd347e
scss-lint:
enabled: true
enabled: false
ratings:
paths:
- app/**

View File

@@ -1,4 +1,9 @@
inherit_from:
- .rubocop_todo.yml
AllCops:
TargetRubyVersion: 2.1
TargetRailsVersion: 3.2
Include:
- '**/Rakefile'
- '**/config.ru'
@@ -6,24 +11,163 @@ AllCops:
- 'db/**/*'
- 'config/**/*'
- 'script/**/*'
- 'spec/**/*'
- !ruby/regexp /old_and_unused\.rb$/
Documentation:
# OFN SETTINGS
# Cop settings that have been agreed upon by the OFN community
Rails:
Enabled: true
Style/Documentation:
Enabled: false
Style/EmptyLinesAroundClassBody:
Style/StringLiterals:
Enabled: false
Style/BracesAroundHashParameters:
# TEMPORARY/CONTESTED SETTINGS
# These are still to be decided upon, but recommended for inclusion by
# oeoeaio after scrutinising offenses the codebase
# Don't think this is a big issue, mostly picking up RPSEC scope definitions
# with lamdas and RSpec '.to change{}' blocks
Lint/AmbiguousBlockAssociation:
Enabled: false
# Heaps of offences (> 100) in specs, mostly in situations where two or more
# instances of a model are required, but only one is referenced. Difficult to
# fix without making the spec look messy or rewriting it.
# Should definitely fix at some point.
Lint/UselessAssignment:
Exclude:
- spec/**/*
# AFAIK, there is no good alternative to dynamic matchers until we upgrade
# to Rails 4 and can use #find_by. If there is a better approach, let's do it.
Rails/DynamicFindBy:
Enabled: false
# This should be the programmer's discretion, perhaps we should review all of
# the uses of it an make specific exceptions though.
Rails/SkipsModelValidations:
Enabled: false
# Relaxed.Ruby.Style SETTINGS
# These styles are a starting point for the conversation around conventions
# They should be removed or tweaked and moved above as decisions are made
# NOTE: Cops which did not fail at the time of writing were removed
Layout/DotPosition:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#styledotposition
Layout/SpaceBeforeBlockBraces:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylespacebeforeblockbraces
Layout/SpaceInsideParens:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylespaceinsideparens
Style/Alias:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylealias
Style/BlockDelimiters:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#styleblockdelimiters
Style/CommentAnnotation:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylecommentannotation
Style/DoubleNegation:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#styledoublenegation
Style/FormatString:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#styleformatstring
Style/IfUnlessModifier:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#styleifunlessmodifier
Style/Lambda:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylelambda
Style/MultilineBlockChain:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylemultilineblockchain
Style/NegatedIf:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylenegatedif
Style/NegatedWhile:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylenegatedwhile
Style/ParallelAssignment:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#styleparallelassignment
Style/PercentLiteralDelimiters:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylepercentliteraldelimiters
Style/Semicolon:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylesemicolon
Style/SingleLineMethods:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylesinglelinemethods
Style/TrailingCommaInArguments:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#styletrailingcommainarguments
Style/TrailingCommaInLiteral:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#styletrailingcommainliteral
Style/WordArray:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#stylewordarray
Lint/AmbiguousRegexpLiteral:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#lintambiguousregexpliteral
Lint/AssignmentInCondition:
Enabled: false
StyleGuide: http://relaxed.ruby.style/#lintassignmentincondition
Metrics/AbcSize:
Enabled: false
Metrics/BlockNesting:
Enabled: false
Metrics/ClassLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/LineLength:
Enabled: false
Max: 120
MethodLength:
Metrics/MethodLength:
Enabled: false
StringLiterals:
Metrics/ParameterLists:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false

2187
.rubocop_todo.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,6 @@
See this here post on raising a github issue:
https://community.openfoodnetwork.org/t/how-to-raise-a-github-issue/912
# Contributing
We love pull requests from everyone. Here are some instructions for

View File

@@ -63,6 +63,7 @@ gem 'wkhtmltopdf-binary'
gem 'foreigner'
gem 'immigrant'
gem 'roo', '~> 2.7.0'
gem 'whenever', require: false
@@ -114,7 +115,6 @@ end
group :test do
gem 'webmock'
# See spec/spec_helper.rb for instructions
#gem 'perftools.rb'
end
@@ -129,4 +129,5 @@ group :development do
gem 'guard-zeus'
gem 'guard-rspec'
gem 'parallel_tests'
gem 'rubocop', '>= 0.49.1'
end

View File

@@ -165,6 +165,7 @@ GEM
angularjs-rails (1.5.5)
ansi (1.4.2)
arel (3.0.3)
ast (2.3.0)
atomic (1.1.99)
awesome_nested_set (2.1.5)
activerecord (>= 3.0.0)
@@ -477,9 +478,11 @@ GEM
activesupport (>= 3.0.0)
cocaine (~> 0.5.3)
mime-types
parallel (1.4.1)
parallel_tests (1.3.7)
parallel (1.11.2)
parallel_tests (2.14.1)
parallel
parser (2.4.0.0)
ast (~> 2.2)
paypal-sdk-core (0.2.10)
multi_json (~> 1.0)
xml-simple
@@ -494,6 +497,7 @@ GEM
polyamorous (0.5.0)
activerecord (~> 3.0)
polyglot (0.3.5)
powerpack (0.1.1)
pry (0.9.12.2)
coderay (~> 1.0.5)
method_source (~> 0.8)
@@ -531,8 +535,10 @@ GEM
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rainbow (2.2.2)
rake
raindrops (0.13.0)
rake (11.1.2)
rake (11.3.0)
ransack (0.7.2)
actionpack (~> 3.0)
activerecord (~> 3.0)
@@ -558,6 +564,9 @@ GEM
roadie-rails (1.0.3)
rails (>= 3.0, < 4.2)
roadie (~> 3.0)
roo (2.7.1)
nokogiri (~> 1)
rubyzip (~> 1.1, < 2.0.0)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
@@ -575,7 +584,15 @@ GEM
rspec-mocks (~> 2.14.0)
rspec-retry (0.4.2)
rspec-core
ruby-progressbar (1.7.1)
rubocop (0.49.1)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.8.1)
rubyzip (1.2.0)
safe_yaml (0.9.5)
sass (3.3.14)
sass-rails (3.2.6)
@@ -616,6 +633,7 @@ GEM
uglifier (2.7.1)
execjs (>= 0.3.0)
json (>= 1.8.0)
unicode-display_width (1.3.0)
unicorn (4.9.0)
kgio (~> 2.6)
rack
@@ -639,9 +657,8 @@ GEM
whenever (0.9.2)
activesupport (>= 2.3.4)
chronic (>= 0.6.3)
wicked_pdf (0.11.0)
rails
wkhtmltopdf-binary (0.9.9.3)
wicked_pdf (1.1.0)
wkhtmltopdf-binary (0.12.3.1)
xml-simple (1.1.4)
xpath (2.0.0)
nokogiri (~> 1.3)
@@ -717,8 +734,10 @@ DEPENDENCIES
redcarpet
representative_view
roadie-rails (~> 1.0.3)
roo (~> 2.7.0)
rspec-rails
rspec-retry
rubocop (>= 0.49.1)
sass (~> 3.3)
sass-rails (~> 3.2.3)
shoulda-matchers
@@ -745,4 +764,4 @@ RUBY VERSION
ruby 2.1.5p273
BUNDLED WITH
1.14.3
1.15.1

View File

@@ -47,4 +47,3 @@ end
#watch(%r{^spec/acceptance/(.+)\.feature$})
#watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
#end

View File

@@ -98,6 +98,14 @@ The site is configured to use
startup time while Rails loads. See the Zeus github page for
usage instructions.
Once [npm dependencies are
installed](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Karma), AngularJS tests can be run with:
./script/karma run
If you want karma to automatically rerun the tests on file modification, use:
./script/karma start
## Credits

View File

@@ -12,7 +12,10 @@ angular.module('admin.orderCycles')
$scope.StatusMessage = StatusMessage
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
StatusMessage.setValidation(isValid)
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded

View File

@@ -13,7 +13,10 @@ angular.module('admin.orderCycles')
$scope.StatusMessage = StatusMessage
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
StatusMessage.setValidation(isValid)
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded

View File

@@ -8,7 +8,10 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl"
$scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id)
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
StatusMessage.setValidation(isValid)
$scope.init = (enterprises) ->
enterprise = enterprises[Object.keys(enterprises)[0]]

View File

@@ -10,7 +10,10 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
$scope.init()
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
StatusMessage.display 'notice', t("admin.unsaved_changes") if newValue
$scope.$watch 'order_cycle_form.$valid', (isValid) ->
StatusMessage.setValidation(isValid)
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded

View File

@@ -0,0 +1,5 @@
angular.module("ofn.admin").controller "DropdownPanelsCtrl", ($scope) ->
$scope.active = false
$scope.togglePanel = ->
$scope.active = !$scope.active

View File

@@ -0,0 +1,15 @@
angular.module("ofn.admin").controller "ImportOptionsFormCtrl", ($scope, $rootScope, ProductImportService) ->
$scope.toggleResetAbsent = () ->
confirmed = confirm 'This will set stock level to zero on all products for this \n' +
'enterprise that are not present in the uploaded file.' if $scope.resetAbsent
if confirmed or !$scope.resetAbsent
ProductImportService.updateResetAbsent($scope.supplierId, $scope.resetCount, $scope.resetAbsent)
else
$scope.resetAbsent = false
$scope.resetTotal = ProductImportService.resetTotal
$rootScope.$watch 'resetTotal', (newValue) ->
$scope.resetTotal = newValue if newValue || newValue == 0

View File

@@ -0,0 +1,15 @@
angular.module("ofn.admin").factory "ProductImportService", ($rootScope) ->
new class ProductImportService
suppliers: {}
resetTotal: 0
updateResetAbsent: (supplierId, resetCount, resetAbsent) ->
if resetAbsent
@suppliers[supplierId] = resetCount
@resetTotal += resetCount
else
@suppliers[supplierId] = null
@resetTotal -= resetCount
$rootScope.resetTotal = @resetTotal

View File

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

View File

@@ -0,0 +1,14 @@
angular.module("admin.products").controller "variantUnitsCtrl", ($scope, VariantUnitManager, $timeout) ->
$scope.unitName = (scale, type) ->
VariantUnitManager.getUnitName(scale, type)
$scope.scale = angular.element('#product_variant_unit_scale').val()
$scope.updateValue = ->
unit_value_human = angular.element('#unit_value_human').val()
$scope.unit_value = unit_value_human * $scope.scale
variant_unit_value = angular.element('#variant_unit_value').val()
$scope.unit_value_human = variant_unit_value / $scope.scale
$timeout -> $scope.updateValue()

View File

@@ -1 +1 @@
angular.module("admin.products", ["admin.utils"])
angular.module("admin.products", ["textAngular", "admin.utils"])

View File

@@ -0,0 +1,5 @@
angular.module("admin.utils").directive "textangularStrip", () ->
restrict: 'CA'
link: (scope, element, attrs) ->
scope.stripFormatting = ($html) ->
return String($html).replace(/<[^>]+>/gm, '')

View File

@@ -11,6 +11,14 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) ->
text: ""
style: {}
invalidMessage: ""
setValidation: (isValid) ->
if isValid
StatusMessage.invalidMessage = ''
else
StatusMessage.invalidMessage = t("admin.form_invalid")
active: ->
@statusMessage.text != ''

View File

@@ -1,8 +1,8 @@
$ ->
if ($ 'form#update-cart').is('*')
($ 'form#update-cart a.delete').show().one 'click', ->
($ this).parents('.line-item').first().find('input.line_item_quantity').val 0
($ this).parents('form').first().submit()
if $('form#update-cart').is('*') || $('form#update-order').is('*')
$('form#update-cart a.delete, form#update-order a.delete').show().one 'click', ->
$(this).parents('.line-item').first().find('input.line_item_quantity').val 0
$(this).parents('form').first().submit()
false
($ 'form#update-cart').submit ->

View File

@@ -0,0 +1,12 @@
Darkswarm.controller "EditBoughtOrderController", ($scope, $resource, Cart) ->
$scope.showBought = false
$scope.deleteLineItem = (id) ->
params = {id: id}
success = (response) ->
$(".line-item-" + id).remove()
Cart.removeFinalisedLineItem(id)
fail = (error) ->
console.log error
$resource("/line_items/:id").delete(params, success, fail)

View File

@@ -10,7 +10,7 @@ Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) ->
$("#order_cycle_id").trigger("openTrigger")
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart) ->
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart, ChangeableOrdersAlert) ->
# Track previous order cycle id for use with revertOrderCycle()
$scope.previous_order_cycle_id = OrderCycle.order_cycle.order_cycle_id
$scope.$watch 'order_cycle.order_cycle_id', (newValue, oldValue)->
@@ -30,3 +30,5 @@ Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Prod
Variants.clear()
Cart.clear()
Products.update()
Cart.reloadFinalisedLineItems()
ChangeableOrdersAlert.reload()

View File

@@ -5,7 +5,20 @@ Darkswarm.controller "RegistrationCtrl", ($scope, RegistrationService, Enterpris
$scope.steps = ['details', 'contact', 'type', 'about', 'images', 'social']
$scope.countries = availableCountries
# Filter countries without states since the form requires a state to be selected.
# Consider changing the form to require a state only if a country requires them (Spree option).
# Invalid countries still need to be filtered (better server-side).
$scope.countries = availableCountries.filter (country) ->
country.states.length > 0
$scope.countriesById = $scope.countries.reduce (obj, country) ->
obj[country.id] = country
obj
, {}
$scope.setDefaultCountry = (id) ->
country = $scope.countriesById[id]
$scope.enterprise.country = country if country
$scope.countryHasStates = ->
$scope.enterprise.country.states.length > 0

View File

@@ -11,8 +11,7 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
'angularFileUpload',
'angularSlideables'
]).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
$httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
$httpProvider.defaults.headers.put['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
$httpProvider.defaults.headers['common']['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
$httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest'
$httpProvider.defaults.headers.common.Accept = "application/json, text/javascript, */*"

View File

@@ -0,0 +1,5 @@
Darkswarm.directive "changeableOrdersAlert", (ChangeableOrdersAlert) ->
restrict: "C"
scope: true
link: (scope, element, attrs) ->
scope.alert = ChangeableOrdersAlert

View File

@@ -0,0 +1,9 @@
Darkswarm.directive "confirmLinkClick", ($window) ->
restrict: 'A'
scope:
confirmMsg: '@confirmLinkClick'
link: (scope, elem, attr) ->
elem.bind 'click', (event) ->
unless confirm(scope.confirmMsg)
event.preventDefault()
event.stopPropagation()

View File

@@ -6,7 +6,11 @@ Darkswarm.directive "ofnOnHand", ->
# In cases where this field gets its value from the HTML element rather than the model,
# initialise the model with the HTML value.
if scope.$eval(attr.ngModel) == undefined
ngModel.$setViewValue elem.val()
# Don't dirty the model when we do this
setDirty = ngModel.$setDirty
ngModel.$setDirty = angular.noop
ngModel.$setViewValue(elem.val())
ngModel.$setDirty = setDirty
ngModel.$parsers.push (viewValue) ->
on_hand = parseInt(attr.ofnOnHand)

View File

@@ -5,4 +5,4 @@ Darkswarm.filter 'products', (Matcher) ->
return products if text == ""
products.filter (product) =>
propertiesToMatch = [product.name, product.variant_names, product.supplier.name, product.primary_taxon.name]
Matcher.match propertiesToMatch, text
Matcher.matchBeginning propertiesToMatch, text

View File

@@ -25,9 +25,7 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir
isActive: Navigation.isActive
close: ->
if location.pathname in ["/", "/checkout"]
Navigation.navigate "/"
else
if location.pathname in ["/register", "/register/auth"]
Loading.message = t 'going_back_to_home_page'
location.hash = ""
location.pathname = "/"

View File

@@ -1,4 +1,4 @@
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, localStorageService)->
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService) ->
# Handles syncing of current cart/order state to server
new class Cart
dirty: false
@@ -6,11 +6,15 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
update_enqueued: false
order: CurrentOrder.order
line_items: CurrentOrder.order?.line_items || []
line_items_finalised: CurrentOrder.order?.finalised_line_items || []
constructor: ->
for line_item in @line_items
line_item.variant.line_item = line_item
Variants.register line_item.variant
for line_item in @line_items_finalised
line_item.variant.line_item = line_item
Variants.extend line_item.variant
adjust: (line_item) =>
line_item.total_price = line_item.variant.price_with_fees * line_item.quantity
@@ -115,3 +119,15 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
clear: ->
@line_items = []
localStorageService.clearAll() # One day this will have to be moar GRANULAR
removeFinalisedLineItem: (id) =>
@line_items_finalised = @line_items_finalised.filter (item) ->
item.id != id
reloadFinalisedLineItems: =>
@line_items_finalised = []
$resource("/line_items/bought").query (items) =>
for line_item in items
line_item.variant.line_item = line_item
Variants.extend line_item.variant
@line_items_finalised = items

View File

@@ -0,0 +1,14 @@
Darkswarm.factory 'ChangeableOrdersAlert', ($http) ->
new class ChangeableOrdersAlert
html: ''
visible: true
constructor: ->
@reload()
reload: ->
$http.get('/shop/changeable_orders_alert').then (response) =>
@html = response.data.trim()
close: =>
@visible = false

View File

@@ -1,7 +1,14 @@
Darkswarm.factory "Matcher", ->
# Match text fragment in an array of strings.
new class Matcher
# Match text fragment in an array of strings.
match: (properties, text)->
properties.some (prop)->
prop ||= ""
prop.toLowerCase().indexOf(text.toLowerCase()) != -1
# Return true if text occurs at the beginning of any word present in an array of strings
matchBeginning: (properties, text) ->
text = text.trim()
regexp = new RegExp("(?:^|[\\s-])#{text}", "i")
properties.some (prop) -> (prop || "").search(regexp) >= 0

View File

@@ -3,10 +3,12 @@ Darkswarm.factory 'Orders', (orders_by_distributor, currencyConfig, CurrentHub,
constructor: ->
# Populate Orders.orders from json in page.
@orders_by_distributor = orders_by_distributor
@changeable_orders = []
@currency_symbol = currencyConfig.symbol
for distributor in @orders_by_distributor
@updateRunningBalance(distributor.distributed_orders)
@findChangeableOrders(distributor.distributed_orders)
@updateRunningBalance(distributor.distributed_orders)
updateRunningBalance: (orders) ->
@@ -14,3 +16,7 @@ Darkswarm.factory 'Orders', (orders_by_distributor, currencyConfig, CurrentHub,
balances = orders.slice(i,orders.length).map (o) -> parseFloat(o.outstanding_balance)
running_balance = balances.reduce (a,b) -> a+b
order.running_balance = running_balance.toFixed(2)
findChangeableOrders: (orders) ->
for order in orders when order.changes_allowed
@changeable_orders.push(order)

View File

@@ -42,9 +42,10 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro
registerVariants: ->
for product in @products
if product.variants
product.variant_names = ""
product.variants = for variant in product.variants
variant = Variants.register variant
if product.name != variant.name_to_display
product.variant_names += variant.name_to_display
product.variant_names += variant.name_to_display + " "
variant.product = product
variant

View File

@@ -1,6 +1,9 @@
#save-bar.animate-show{ ng: { show: 'dirty || persist || StatusMessage.active()' } }
.container
.eight.columns.alpha
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
%h5#status-message{ ng: { show: "StatusMessage.invalidMessage == ''", style: 'StatusMessage.statusMessage.style' } }
{{ StatusMessage.statusMessage.text || "&nbsp;" }}
%h5#status-message{ ng: { show: "StatusMessage.invalidMessage !== ''" }, style: 'color: #da5354' }
{{ StatusMessage.invalidMessage || "&nbsp;" }}
.eight.columns.omega.text-right{ ng: { transclude: true } }

View File

@@ -1,4 +1,4 @@
%div{ ng: { show: "data.length > limit" } }
%input{ type: 'button', value: t(:show_more), ng: { click: 'limit = limit + increment' } }
or
%input{ type: 'button', value: t(:show_more_with_more, num: '{{ data.length - limit }}'), ng: { click: 'limit = data.length' } }
%input{ type: 'button', value: t(:show_all_with_more, num: '{{ data.length - limit }}'), ng: { click: 'limit = data.length' } }

View File

@@ -3,9 +3,9 @@
%p.modal-header {{'contact' | t}}
%p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"}
%p.word-wrap{"ng-if" => "::enterprise.email_address"}
%p{"ng-if" => "::enterprise.email_address"}
%a{"ng-href" => "{{::enterprise.email_address | stripUrl}}", target: "_blank", mailto: true}
%span.email{"ng-bind" => "::enterprise.email_address | stripUrl"}
%p.word-wrap{"ng-if" => "enterprise.website"}
%p{"ng-if" => "enterprise.website"}
%a{"ng-href" => "http://{{::enterprise.website | stripUrl}}", target: "_blank", "ng-bind" => "::enterprise.website | stripUrl"}

View File

@@ -14,9 +14,9 @@
.filter-shopfront.property-selectors.inline-block
%filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" }
%div{"ng-if" => "product.description"}
%div{"ng-if" => "product.description_html"}
%hr
%p.text-small{"ng-bind" => "::product.description"}
%p.text-small{"ng-bind-html" => "::product.description_html"}
%hr
.columns.small-12.large-6

View File

@@ -0,0 +1,3 @@
form[name="enterprise_form"] div.row.warning {
color: #DA7F52;
}

View File

@@ -74,6 +74,9 @@ form.order_cycle {
margin-bottom: 0.5em;
}
}
.icon-question-sign {
font-size: 18px;
}
table.exchanges {
tr td.active {
width: 20px;
@@ -113,6 +116,14 @@ form.order_cycle {
margin-right: 2em;
}
}
.collection-details {
input {
width: 90%
}
span {
font-size: 1rem
}
}
}
.coordinator-fees {
margin-top: 1em;
@@ -200,6 +211,13 @@ table#listing_enterprise_groups {
// textAngular wysiwyg
text-angular {
.ta-toolbar {
border: 1px solid #cdd9e4;
padding: 0.4em;
margin-bottom: -1px;
background-color: #f1f1f1;
border-radius: 0.25em 0.25em 0 0;
}
.ta-scroll-window > .ta-bind {
max-height: 400px;
min-height: 100px;
@@ -210,12 +228,18 @@ text-angular {
}
.ta-scroll-window.form-control {
min-height: 100px;
box-shadow: none !important;
}
.btn-group {
display: inline;
margin-right: 8px;
button {
padding: 5px 10px;
margin-right: 0.25em;
}
button.active:not(:hover) {
box-shadow: 0 0 0.7em rgba(0,0,0,0.3) inset;
background-color: #4583bf;
}
}
}

View File

@@ -0,0 +1,225 @@
div.panel-section {
.neutral {
color: #bfbfbf;
}
.warning {
color: #da5354;
}
.success {
color: #86d83a;
}
.info {
color: #68b7c0;
}
div.panel-header {
width: 100%;
//font-size: 1.5em;
clear: both;
//border: 1px solid #ccc;
float: left;
padding: 0.5em;
div {
font-size: 1.25em;
float: left;
}
div.header-caret {
width: 2em;
text-align: center;
min-height: 0.1em; //Empty div fix
}
div.header-icon {
width: 2.5em;
text-align: center;
padding-top: 0.18em;
i {
font-size: 1.5em;
line-height: 0.9em;
}
}
div.header-count {
min-width: 2em;
text-align: right;
padding-right: 0.5em;
}
div.header-description {
width: auto;
}
}
div.panel-header:hover {
cursor: pointer;
background-color: #f7f7f7;
}
div.panel-header.active {
background-color: #efefef;
text-shadow: 1px 1px 0px rgba(255,255,255,0.75);
}
div.panel-content {
width: 100%;
clear: both;
//border: 1px solid #ccc;
margin-bottom: 0.5em;
background-color: #f9f9f9;
padding: 1.5em;
div.table-wrap {
width: 100%;
overflow: auto;
border-right: 1px solid #ceede3;
max-height: 23em;
}
table {
background-color: white;
margin-bottom: 0;
td, th {
white-space: nowrap;
}
tr.error {
//background-color: #ffe6e4;
//color: #ee4728;
color: #c84C4c;
}
tr:hover td.invalid {
background-color: #ed5135;
}
tr i {
display: block;
margin-bottom: -0.2em;
font-size: 1.4em !important;
}
td.invalid {
background-color: #f05c51;
box-shadow: inset 0px 0px 1px red;
color: white;
}
}
div.import-errors {
margin-bottom: 0.85em;
p.line {
font-size: 1.15em;
margin-bottom: 0.2em;
color: #577084;
}
p.error {
color: #cb1b1b;
margin-bottom: 0.2em;
}
}
}
}
br.panels.clearfix {
clear: both;
}
table.import-settings {
background-color: transparent !important;
width: auto;
//select {
// width: 100%;
//}
tr {
}
tbody tr:hover td {
background-color: #f3f3f3;
}
td {
border: 0;
border-bottom: 1px solid #eee;
text-align: left;
input {
width: 15em;
}
input[type="checkbox"] {
width: auto;
}
}
td.description {
font-weight: bold;
padding-right: 2.5em;
}
tr:first-child td {
//border-top: 1px solid #eee;
border-top: 0;
}
tr:last-child td {
//border-top: 1px solid #eee;
border-bottom: 0;
}
}
.panel-section.import-settings {
.header-description {
padding-left: 1em;
}
span.header-error {
font-size: 0.85em;
color: #da5354;
}
.select2-search {
display: none;
}
.select2-results {
margin: 0;
}
}
.post-save-results {
p {
font-size: 1.25em;
margin-bottom: 0.5em;
strong {
margin-right: 0.2em;
min-width: 1.8em;
display: inline-block;
text-align: right;
}
i {
font-size: 1.4em;
vertical-align: middle;
position: relative;
}
i.fa-check-circle {
color: #86d83a;
}
i.fa-info-circle {
color: #68b7c0;
}
}
p.save-error {
color: #ee4728;
font-size: 1.05em;
margin-top: 0.4em;
}
}

View File

@@ -19,7 +19,7 @@
height: 100px
width: 100px
margin-right: 12px
location
@include headingFont
@media all and (max-width: 768px)
@@ -30,7 +30,7 @@
margin-top: 0
@media all and (max-width: 768px)
margin-bottom: 8px
ordercycle
text-align: right
@@ -47,24 +47,26 @@
p
max-width: 100%
float: right
form.custom
form.custom
text-align: right
& > strong
line-height: 2.5
font-size: 1.29em
padding-right: 14px
@media all and (max-width: 768px)
select
select
width: inherit
display: inline-block
border-width: 1px
border-color: #999
color: #666
font-size: 1em
font-size: 1em
margin-bottom: 0
padding: 8px 20px 8px 12px
@media all and (max-width: 768px)
font-size: 0.875em
@media screen and (-webkit-min-device-pixel-ratio:0)
font-size: 16px
closing
@include headingFont
@media all and (max-width: 768px)

View File

@@ -1,16 +1,14 @@
@import "branding";
@import "mixins";
.account-summary {
color: #4a4a4a;
}
.orders {
@include sidepaddingSm;
@include panepadding;
padding-top: 10px;
h3 {
padding-top: 2em;
}
margin-top: 50px;
margin-bottom: 100px;
a {
color: $clr-brick;
@@ -26,6 +24,12 @@
height: auto;
}
.active_table_row {
h3 {
margin-top: 0.5em;
}
}
i.ofn-i_059-producer, i.ofn-i_060-producer-reversed {
font-size: 3rem;
display: inline-block;
@@ -61,6 +65,7 @@
.transaction-group {}
table {
width: 100%;
border-radius: 0.5em 0.5em 0 0;
tr:nth-of-type(even) {

View File

@@ -33,7 +33,9 @@
@-webkit-keyframes slideOutUp {
0% {
opacity: 1;
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0);
}
@@ -46,10 +48,18 @@
@keyframes slideOutUp {
0% {
opacity: 1;
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0);
}
100% {
opacity: 0;
-webkit-transform: translateY(-20px);
-ms-transform: translateY(-20px);
transform: translateY(-20px);
}
}
@-webkit-keyframes fadeIn {

View File

@@ -6,23 +6,25 @@
background-color: #e1f0f5
padding: 1em
width: 100%
border: none
color: inherit
checkout
display: block
@media all and (max-width: 640px)
&.row .row
@media all and (max-width: 640px)
&.row .row
margin-left: 0
margin-right: 0
orderdetails
.button, table
width: 100%
@media all and (max-width: 640px)
@media all and (max-width: 640px)
form.edit_order
border: 1px solid $disabled-bright
margin-bottom: 2rem
#details, #billing, #shipping, #payment
border: 0
margin: 1em 0
@@ -34,20 +36,20 @@ checkout
margin: 0
padding: 0.65em
background: #f7f7f7
.label
.label
font-size: 1em
padding: 0.3rem 0.35rem 0.275rem
// Logic to turn on & off the alerts for success against each fieldset
label, label.alert, label.success, &.valid label.alert, &.dirty label.success
label, label.alert, label.success, &.valid label.alert, &.dirty label.success
display: none
&.dirty label.alert
&.dirty label.alert
display: inline
&.dirty.valid label.alert
display: none
&.dirty.valid label.alert
display: none
&.valid label.success
display: inline
@@ -60,7 +62,7 @@ checkout
text-align: left
// Logic to swap out up / down accordion icons
//Foundation overrides
//Foundation overrides
dd > a
@include csstrans
background: $disabled-light !important
@@ -68,7 +70,7 @@ checkout
dd > a:hover
background: $disabled-v-dark !important
color: white
dd
span.accordion-up
display: none
@@ -79,4 +81,3 @@ checkout
display: inline
span.accordion-down
display: none

View File

@@ -117,6 +117,13 @@ nav {
padding: 9px 0 0 9px;
}
.top-bar .ofn-logo img {
height: auto;
width: auto;
max-height: 51px;
max-width: 250px;
}
.left-off-canvas-menu {
background-color: white;
}

View File

@@ -32,6 +32,11 @@
color: $clr-turquoise;
}
.vertical-align-middle {
display: flex;
align-items: center;
}
a {
&:hover, &:active, &:focus {
color: $clr-turquoise-bright;

View File

@@ -110,6 +110,10 @@
}
}
.alert-box.changeable-orders-alert {
margin-bottom: 0px;
}
.alert-box.shopfront-message {
border: 2px solid $clr-turquoise;
border-radius: 5px;

View File

@@ -72,11 +72,35 @@
// Shopping cart
#cart-detail {
.cart-item-delete {
a.delete {
.cart-item-delete, .bought-item-delete {
a {
font-size: 1.125em;
}
}
.out-of-stock {
color: $clr-brick;
}
button, .button {
margin: 0;
}
.toggle-bought {
cursor: pointer;
}
tr.bought td {
color: $med-grey;
h5 {
color: $med-grey;
}
.already-confirmed {
float: right;
}
}
}
.item-thumb-image {
@@ -90,9 +114,3 @@
height: 36px;
}
}
#edit-cart {
button, .button {
margin: 0;
}
}

View File

@@ -52,7 +52,6 @@ module Admin
@owner_email = @enterprise_group.andand.owner.andand.email || ""
end
def collection
EnterpriseGroup.by_position
end

View File

@@ -115,7 +115,7 @@ module Admin
end
def collection_actions
[:index]
[:index, :bulk_update]
end
private
@@ -179,9 +179,5 @@ module Admin
def ams_prefix_whitelist
[:basic]
end
def collection_actions
[:index, :bulk_update]
end
end
end

View File

@@ -0,0 +1,62 @@
require 'roo'
class Admin::ProductImportController < Spree::Admin::BaseController
before_filter :validate_upload_presence, except: :index
def import
# Save uploaded file to tmp directory
@filepath = save_uploaded_file(params[:file])
@importer = ProductImporter.new(File.new(@filepath), editable_enterprises)
check_file_errors @importer
check_spreadsheet_has_data @importer
@tax_categories = Spree::TaxCategory.order('is_default DESC, name ASC')
@shipping_categories = Spree::ShippingCategory.order('name ASC')
end
def save
@importer = ProductImporter.new(File.new(params[:filepath]), editable_enterprises, params[:settings])
@importer.save_all if @importer.has_valid_entries?
end
private
def validate_upload_presence
unless params[:file] || (params[:filepath] && File.exist?(params[:filepath]))
redirect_to '/admin/product_import', notice: 'File not found or could not be opened'
return
end
end
def check_file_errors(importer)
if importer.errors.present?
redirect_to '/admin/product_import', notice: @importer.errors.full_messages.to_sentence
return
end
end
def check_spreadsheet_has_data(importer)
unless importer.item_count
redirect_to '/admin/product_import', notice: 'No data found in spreadsheet'
return
end
end
def save_uploaded_file(upload)
filename = 'import' + Time.now.strftime('%d-%m-%Y-%H-%M-%S')
extension = '.' + upload.original_filename.split('.').last
directory = 'tmp/product_import'
Dir.mkdir(directory) unless File.exists?(directory)
File.open(Rails.root.join(directory, filename+extension), 'wb') do |f|
f.write(upload.read)
f.path
end
end
# Define custom model class for Cancan permissions
def model_class
ProductImporter
end
end

View File

@@ -4,7 +4,6 @@ module Admin
"#{controller_name.classify}".constantize
end
# URL helpers
def new_object_url(options = {})
if parent_data.present?

View File

@@ -12,7 +12,6 @@ module Admin
def index
end
def bulk_update
# Ensure we're authorised to update all variant overrides
@vo_set.collection.each { |vo| authorize! :update, vo }

View File

@@ -24,7 +24,6 @@ class ApplicationController < ActionController::Base
params[:action].to_sym
end
def require_distributor_chosen
unless @distributor = current_distributor
redirect_to spree.root_path
@@ -62,7 +61,6 @@ class ApplicationController < ActionController::Base
end
end
# All render calls within the block will be performed with the specified format
# Useful for rendering html within a JSON response, particularly if the specified
# template or partial then goes on to render further partials without specifying

View File

@@ -23,6 +23,11 @@ class BaseController < ApplicationController
private
def set_order_cycles
unless @distributor.ready_for_checkout?
@order_cycles = OrderCycle.where('false')
return
end
@order_cycles = OrderCycle.with_distributor(@distributor).active
.order(@distributor.preferred_shopfront_order_cycle_order)

View File

@@ -123,7 +123,6 @@ class CheckoutController < Spree::CheckoutController
false
end
def update_failed
clear_ship_address
respond_to do |format|
@@ -168,8 +167,8 @@ class CheckoutController < Spree::CheckoutController
customer_preferred_bill_address, customer_preferred_ship_address = @order.customer.bill_address, @order.customer.ship_address if @order.customer
@order.bill_address ||= customer_preferred_bill_address ||= preferred_bill_address || last_used_bill_address || Spree::Address.default
@order.ship_address ||= customer_preferred_ship_address ||= preferred_ship_address || last_used_ship_address || Spree::Address.default
@order.bill_address ||= customer_preferred_bill_address || preferred_bill_address || last_used_bill_address || Spree::Address.default
@order.ship_address ||= customer_preferred_ship_address || preferred_ship_address || last_used_ship_address || Spree::Address.default
end
def after_payment

View File

@@ -30,7 +30,6 @@ class DiscourseSsoController < ApplicationController
def sso_url
secret = discourse_sso_secret!
discourse_url = discourse_url!
sso = Discourse::SingleSignOn.parse(request.query_string, secret)
sso.email = spree_current_user.email
sso.username = spree_current_user.login

View File

@@ -0,0 +1,48 @@
class LineItemsController < BaseController
respond_to :json
before_filter :load_line_item, only: :destroy
def bought
respond_with bought_items, each_serializer: Api::LineItemSerializer
end
def destroy
authorize! :destroy, @line_item
destroy_with_lock @line_item
respond_with(@line_item)
end
private
def load_line_item
@line_item = Spree::LineItem.find_by_id(params[:id])
not_found unless @line_item
end
# List all items the user already ordered in the current order cycle
def bought_items
return [] unless current_order_cycle && spree_current_user && current_distributor
current_order_cycle.items_bought_by_user(spree_current_user, current_distributor)
end
def unauthorized
status = spree_current_user ? 403 : 401
render nothing: true, status: status and return
end
def not_found
render nothing: true, status: 404 and return
end
def destroy_with_lock(item)
order = item.order
order.with_lock do
item.destroy
order.update_shipping_fees!
order.update_payment_fees!
order.update_distribution_charge!
order.create_tax_charge!
end
end
end

View File

@@ -37,6 +37,10 @@ class ShopController < BaseController
end
end
def changeable_orders_alert
render layout: false
end
private
def filtered_json(products_json)

View File

@@ -28,7 +28,6 @@ module Spree
end
end
def set_included_tax
if params[:tax_rate_id].present?
tax_rate = TaxRate.find params[:tax_rate_id]

View File

@@ -12,6 +12,9 @@ Spree::Admin::OrdersController.class_eval do
before_filter :load_distribution_choices, only: [:new, :edit, :update]
# Ensure that the distributor is set for an order when
before_filter :ensure_distribution, only: :new
# After updating an order, the fees should be updated as well
# Currently, adding or deleting line items does not trigger updating the
# fees! This is a quick fix for that.
@@ -131,4 +134,16 @@ Spree::Admin::OrdersController.class_eval do
ocs.closed +
ocs.undated
end
def ensure_distribution
unless @order
@order = Spree::Order.new
@order.generate_order_number
@order.save!
end
unless @order.distribution_set?
render 'set_distribution', locals: { order: @order }
end
end
end

View File

@@ -101,7 +101,6 @@ Spree::Admin::ReportsController.class_eval do
# -- Build Report with Order Grouper
@report = OpenFoodNetwork::OrderCycleManagementReport.new spree_current_user, params
@table = @report.table_items
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
render_report(@report.header, @table, params[:csv], "order_cycle_management_#{timestamp}.csv")
end
@@ -126,7 +125,6 @@ Spree::Admin::ReportsController.class_eval do
@report = OpenFoodNetwork::PackingReport.new spree_current_user, params
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
@table = order_grouper.table(@report.table_items)
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
render_report(@report.header, @table, params[:csv], "packing_#{timestamp}.csv")
end
@@ -231,7 +229,7 @@ Spree::Admin::ReportsController.class_eval do
@report_types = REPORT_TYPES[:orders_and_fulfillment]
@report_type = params[:report_type]
@include_blank = 'All'
@include_blank = I18n.t(:all)
# -- Build Report with Order Grouper
@report = OpenFoodNetwork::OrdersAndFulfillmentsReport.new spree_current_user, params
@@ -273,7 +271,6 @@ Spree::Admin::ReportsController.class_eval do
render_report(@report.header, @report.table, params[:csv], "xero_invoices_#{timestamp}.csv")
end
def render_report(header, table, create_csv, csv_file_name)
unless create_csv
render :html => table

View File

@@ -8,10 +8,12 @@ Spree::OrdersController.class_eval do
prepend_before_filter :require_order_cycle, only: :edit
prepend_before_filter :require_distributor_chosen, only: :edit
before_filter :check_hub_ready_for_checkout, only: :edit
before_filter :check_at_least_one_line_item, only: :update
include OrderCyclesHelper
layout 'darkswarm'
respond_to :json
# Patching to redirect to shop if order is empty
def edit
@@ -29,10 +31,9 @@ Spree::OrdersController.class_eval do
end
end
def update
@insufficient_stock_lines = []
@order = current_order
@order = order_to_update
unless @order
flash[:error] = t(:order_not_found)
redirect_to root_path and return
@@ -43,12 +44,19 @@ Spree::OrdersController.class_eval do
render :edit and return unless apply_coupon_code
fire_event('spree.order.contents_changed')
if @order == current_order
fire_event('spree.order.contents_changed')
else
@order.update_distribution_charge!
end
respond_with(@order) do |format|
format.html do
if params.has_key?(:checkout)
@order.next_transition.run_callbacks if @order.cart?
redirect_to checkout_state_path(@order.checkout_steps.first)
elsif @order.complete?
redirect_to order_path(@order)
else
redirect_to cart_path
end
@@ -62,7 +70,6 @@ Spree::OrdersController.class_eval do
end
end
def populate
# Without intervention, the Spree::Adjustment#update_adjustable callback is called many times
# during cart population, for both taxation and enterprise fees. This operation triggers a
@@ -90,7 +97,6 @@ Spree::OrdersController.class_eval do
end
end
# Report the stock levels in the order for all variant ids requested
def stock_levels(order, variant_ids)
stock_levels = li_stock_levels(order)
@@ -162,6 +168,18 @@ Spree::OrdersController.class_eval do
@order_cycle = OrderCycle.find session[:expired_order_cycle_id]
end
def cancel
@order = Spree::Order.find_by_number!(params[:id])
authorize! :cancel, @order
if @order.cancel
flash[:success] = I18n.t(:orders_your_order_has_been_cancelled)
else
flash[:error] = I18n.t(:orders_could_not_cancel)
end
redirect_to request.referer || order_path(@order)
end
private
@@ -202,4 +220,30 @@ Spree::OrdersController.class_eval do
def wrap_json_infinity(n)
n == Float::INFINITY ? 2147483647 : n
end
def order_to_update
return @order_to_update if defined? @order_to_update
return @order_to_update = current_order unless params[:id]
@order_to_update = changeable_order_from_number
end
# If a specific order is requested, return it if it is COMPLETE and
# changes are allowed and the user has access. Return nil if not.
def changeable_order_from_number
order = Spree::Order.complete.find_by_number(params[:id])
return nil unless order.andand.changes_allowed? && can?(:update, order)
order
end
def check_at_least_one_line_item
return unless order_to_update.andand.complete?
items = params[:order][:line_items_attributes]
.andand.select{ |k,attrs| attrs["quantity"].to_i > 0 }
if items.empty?
flash[:error] = I18n.t(:orders_cannot_remove_the_final_item)
redirect_to order_path(order_to_update)
end
end
end

View File

@@ -5,12 +5,12 @@ module Admin
end
def admin_image_settings_geometry_from_style(style)
geometry, format = admin_image_settings_split_style style
geometry, _format = admin_image_settings_split_style style
geometry
end
def admin_image_settings_format_from_style(style)
geometry, format = admin_image_settings_split_style style
_geometry, format = admin_image_settings_split_style style
format
end

View File

@@ -11,7 +11,6 @@ module ApplicationHelper
form_for(name, *(args << options.merge(:builder => AngularFormBuilder)), &block)
end
# Pass URL helper calls on to spree where applicable so that we don't need to use
# spree.foo_path in any view rendered from non-spree-namespaced controllers.
def method_missing(method, *args, &block)

View File

@@ -16,7 +16,7 @@ module CheckoutHelper
enterprise_fee_adjustments = adjustments.select { |a| a.originator_type == 'EnterpriseFee' && a.source_type != 'Spree::LineItem' }
adjustments.reject! { |a| a.originator_type == 'EnterpriseFee' && a.source_type != 'Spree::LineItem' }
unless exclude.include? :admin_and_handling
adjustments << Spree::Adjustment.new(label: 'Admin & Handling', amount: enterprise_fee_adjustments.sum(&:amount))
adjustments << Spree::Adjustment.new(label: I18n.t(:orders_form_admin), amount: enterprise_fee_adjustments.sum(&:amount))
end
adjustments
@@ -54,12 +54,8 @@ module CheckoutHelper
end
def display_adjustment_tax_rates(adjustment)
tax_rate = (adjustment.included_tax / (adjustment.amount - adjustment.included_tax)).round(2)
if tax_rate == 0 || tax_rate.infinite?
""
else
number_to_percentage(tax_rate * 100, :precision => 1)
end
tax_rates = adjustment.tax_rates
tax_rates.map { |tr| number_to_percentage(tr.amount * 100, :precision => 1) }.join(", ")
end
def display_adjustment_amount(adjustment)
@@ -84,7 +80,6 @@ module CheckoutHelper
available_countries.map { |c| [c.name, c.id] }
end
def validated_input(name, path, args = {})
attributes = {
required: true,

View File

@@ -88,4 +88,12 @@ module EnterprisesHelper
def remaining_trial_days(enterprise)
distance_of_time_in_words(Time.zone.now, enterprise.shop_trial_start_date + Spree::Config[:shop_trial_length_days].days)
end
def order_changes_allowed?
current_order.andand.distributor.andand.allow_order_changes?
end
def show_bought_items?
order_changes_allowed? && current_order.finalised_line_items.present?
end
end

View File

@@ -43,7 +43,6 @@ module OrderCyclesHelper
end
end
def order_cycle_options
@order_cycles.
with_distributor(current_distributor).

View File

@@ -16,5 +16,31 @@ module Spree
def cart_count
current_order.andand.line_items.andand.count || 0
end
def changeable_orders
# Only returns open order for the current user + shop + oc combo
return @changeable_orders unless @changeable_orders.nil?
return @changeable_orders = [] unless spree_current_user && current_distributor && current_order_cycle
return @changeable_orders = [] unless current_distributor.allow_order_changes?
@changeable_orders = Spree::Order.complete.where(
state: 'complete',
user_id: spree_current_user.id,
distributor_id: current_distributor.id,
order_cycle_id: current_order_cycle.id)
end
def changeable_orders_link_path
changeable_orders.one? ? spree.order_path(changeable_orders.first) : spree.account_path
end
def shop_changeable_orders_alert_html
return "" unless changeable_orders.any?
t(:shop_changeable_orders_alert_html,
count: changeable_orders.count,
path: changeable_orders_link_path,
order: changeable_orders.first.number,
shop: current_distributor.name,
oc_close: l(current_order_cycle.orders_close_at, format: "%A, %b %d, %Y @ %H:%M"))
end
end
end

View File

@@ -6,17 +6,14 @@ module Spree
"(#{Spree::Money.new(variant.price).to_s})"
end
def product_has_variant_unit_option_type?(product)
product.option_types.any? { |option_type| variant_unit_option_type? option_type }
end
def variant_unit_option_type?(option_type)
Spree::Product.all_variant_unit_option_types.include? option_type
end
def product_variant_unit_options
[['Weight', 'weight'],
['Volume', 'volume'],

View File

@@ -37,7 +37,7 @@ class UpdateAccountInvoices
if billable_periods.any?
oldest_enterprise = billable_periods.first.enterprise
address = oldest_enterprise.address.dup
first, space, last = (oldest_enterprise.contact || "").partition(' ')
first, _space, last = (oldest_enterprise.contact || "").partition(' ')
address.update_attributes(phone: oldest_enterprise.phone) if oldest_enterprise.phone.present?
address.update_attributes(firstname: first, lastname: last) if first.present? && last.present?
account_invoice.order.update_attributes(bill_address: address, ship_address: address)

View File

@@ -12,7 +12,6 @@ Spree::BaseMailer.class_eval do
'test@example.com'
end
def roadie_options
# This lets us specify assets using relative paths in email templates
super.merge(url_options: {host: URI(spree.root_url).host })

View File

@@ -3,8 +3,6 @@ class Cart < ActiveRecord::Base
belongs_to :user, :class_name => Spree.user_class
def add_variant variant_id, quantity, distributor, order_cycle, currency
variant = Spree::Variant.find(variant_id)
order = create_or_find_order_for_distributor distributor, order_cycle, currency
@populator = Spree::OrderPopulator.new(order, currency)

View File

@@ -61,7 +61,6 @@ class EnterpriseRelationship < ActiveRecord::Base
relatives
end
def permissions_list=(perms)
if perms.nil?
permissions.destroy_all

View File

@@ -36,8 +36,8 @@ class ModelSet
end
def save
collection_to_delete.each &:destroy
collection_to_keep.all? &:save
collection_to_delete.each(&:destroy)
collection_to_keep.all?(&:save)
end
def collection_to_delete

View File

@@ -4,7 +4,7 @@ module OpenFoodNetwork
attr_accessible :preferred_per_kg
def self.description
"Weight (per kg)"
I18n.t('spree.weight')
end
def compute(object)

View File

@@ -115,7 +115,6 @@ class OrderCycle < ActiveRecord::Base
]
end
def clone!
oc = self.dup
oc.name = "COPY OF #{oc.name}"
@@ -250,6 +249,13 @@ class OrderCycle < ActiveRecord::Base
OpenFoodNetwork::ProductsCache.order_cycle_changed self
end
def items_bought_by_user(user, distributor)
# The Spree::Order.complete scope only checks for completed_at date, does not ensure state is "complete"
orders = Spree::Order.complete.where(state: "complete", user_id: user, distributor_id: distributor, order_cycle_id: self)
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
items = Spree::LineItem.joins(:order).merge(orders)
items.each { |li| scoper.scope(li.variant) }
end
private

View File

@@ -0,0 +1,464 @@
require 'roo'
class ProductImporter
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_reader :total_supplier_products
def initialize(file, editable_enterprises, import_settings={})
if file.is_a?(File)
@file = file
@sheet = open_spreadsheet
@entries = []
@valid_entries = {}
@invalid_entries = {}
@products_to_create = {}
@variants_to_create = {}
@variants_to_update = {}
@products_created = 0
@variants_created = 0
@variants_updated = 0
@import_settings = import_settings
@editable_enterprises = {}
editable_enterprises.map { |e| @editable_enterprises[e.name] = e.id }
@total_supplier_products = 0
@products_to_reset = {}
@updated_ids = []
init_product_importer if @sheet
else
self.errors.add(:importer, 'error: no file uploaded')
end
end
def persisted?
false #ActiveModel, not ActiveRecord
end
def has_valid_entries?
valid_count and valid_count > 0
end
def item_count
@sheet ? @sheet.last_row - 1 : 0
end
def products_to_reset
# Return indexed data about existing product count, reset count, and updates count per supplier
@products_to_reset.each do |supplier_id, values|
values[:updates_count] = 0 if values[:updates_count].blank?
if values[:updates_count] and values[:existing_products]
@products_to_reset[supplier_id][:reset_count] = values[:existing_products] - values[:updates_count]
end
end
@products_to_reset
end
def valid_count
@valid_entries.count
end
def invalid_count
@invalid_entries.count
end
def products_create_count
@products_to_create.count + @variants_to_create.count
end
def products_update_count
@variants_to_update.count
end
def suppliers_index
index = @suppliers_index || build_suppliers_index
index.sort_by{ |k,v| v.to_i }.reverse.to_h
end
def all_entries
invalid_entries.merge(products_to_create).merge(products_to_update).sort.to_h
end
def invalid_entries
@invalid_entries
end
def products_to_create
@products_to_create.merge(@variants_to_create)
end
def products_to_update
@variants_to_update
end
def products_created_count
@products_created + @variants_created
end
def products_updated_count
@variants_updated
end
def products_reset_count
@products_reset_count || 0
end
def total_saved_count
@products_created + @variants_created + @variants_updated
end
def save_all
save_all_valid
delete_uploaded_file
end
def permission_by_name?(supplier_name)
@editable_enterprises.has_key?(supplier_name)
end
def permission_by_id?(supplier_id)
@editable_enterprises.has_value?(Integer(supplier_id))
end
private
def init_product_importer
build_entries
build_categories_index
build_suppliers_index
validate_all
end
def open_spreadsheet
if accepted_mimetype
Roo::Spreadsheet.open(@file, extension: accepted_mimetype)
else
self.errors.add(:importer, 'could not process file: invalid filetype')
delete_uploaded_file
nil
end
end
def accepted_mimetype
File.extname(@file.path).in?('.csv', '.xls', '.xlsx', '.ods') ? @file.path.split('.').last.to_sym : false
end
def headers
@sheet.row(1)
end
def rows
return [] unless @sheet and @sheet.last_row
(2..@sheet.last_row).map do |i|
@sheet.row(i)
end
end
def build_entries
rows.each_with_index do |row, i|
row_data = Hash[[headers, row].transpose]
entry = SpreadsheetEntry.new(row_data)
entry.line_number = i+2
@entries.push entry
end
@entries
end
def validate_all
@entries.each do |entry|
supplier_validation(entry)
category_validation(entry)
set_update_status(entry)
mark_as_valid(entry) unless entry_invalid?(entry.line_number)
end
count_existing_products
delete_uploaded_file if item_count.zero? or valid_count.zero?
end
def count_existing_products
@suppliers_index.each do |supplier_name, supplier_id|
if supplier_id and permission_by_id?(supplier_id)
products_count = Spree::Variant.joins(:product).
where('spree_products.supplier_id IN (?)
AND spree_variants.is_master = false
AND spree_variants.deleted_at IS NULL', supplier_id).
count
if @products_to_reset[supplier_id]
@products_to_reset[supplier_id][:existing_products] = products_count
else
@products_to_reset[supplier_id] = {existing_products: products_count}
end
@total_supplier_products += products_count
end
end
end
def entry_invalid?(line_number)
!!@invalid_entries[line_number]
end
def supplier_validation(entry)
supplier_name = entry.supplier
if supplier_name.blank?
mark_as_invalid(entry, attribute: "supplier", error: "can't be blank")
return
end
unless supplier_exists?(supplier_name)
mark_as_invalid(entry, attribute: "supplier", error: "\"#{supplier_name}\" not found in database")
return
end
unless permission_by_name?(supplier_name)
mark_as_invalid(entry, attribute: "supplier", error: "\"#{supplier_name}\": you do not have permission to manage products for this enterprise")
return
end
entry.supplier_id = @suppliers_index[supplier_name]
end
def supplier_exists?(supplier_name)
@suppliers_index[supplier_name]
end
def category_validation(entry)
category_name = entry.category
if category_name.blank?
mark_as_invalid(entry, attribute: "category", error: "can't be blank")
return
end
if category_exists?(category_name)
entry.primary_taxon_id = @categories_index[category_name]
else
mark_as_invalid(entry, attribute: "category", error: "\"#{category_name}\" not found in database")
end
end
def category_exists?(category_name)
@categories_index[category_name]
end
def mark_as_valid(entry)
@valid_entries[entry.line_number] = entry
end
def mark_as_invalid(entry, options={})
entry.errors.add(options[:attribute], options[:error]) if options[:attribute] and options[:error]
entry.product_validations = options[:product_validations] if options[:product_validations]
@invalid_entries[entry.line_number] = entry
end
# Minimise db queries by getting a list of suppliers to look
# up, instead of doing a query for each entry in the spreadsheet
def build_suppliers_index
@suppliers_index = {}
@entries.each do |entry|
supplier_name = entry.supplier
supplier_id = @suppliers_index[supplier_name] ||
Enterprise.find_by_name(supplier_name, :select => 'id, name').try(:id)
@suppliers_index[supplier_name] = supplier_id
end
@suppliers_index
end
def build_categories_index
@categories_index = {}
@entries.each do |entry|
category_name = entry.category
category_id = @categories_index[category_name] ||
Spree::Taxon.find_by_name(category_name, :select => 'id, name').try(:id)
@categories_index[category_name] = category_id
end
@categories_index
end
def save_all_valid
already_created = {}
@products_to_create.each do |line_number, entry|
# If we've already added a new product with these attributes
# from this spreadsheet, mark this entry as a new variant with
# the new product id, as this is a now variant of that product...
if already_created[entry.supplier_id] and already_created[entry.supplier_id][entry.name]
product_id = already_created[entry.supplier_id][entry.name]
mark_as_new_variant(entry, product_id)
next
end
product = Spree::Product.new()
product.assign_attributes(entry.attributes.except('id'))
assign_defaults(product, entry.attributes)
if product.save
ensure_variant_updated(product, entry)
@products_created += 1
@updated_ids.push product.variants.first.id
else
self.errors.add("Line #{line_number}:", product.errors.full_messages) #TODO: change
end
already_created[entry.supplier_id] = {entry.name => product.id}
end
@variants_to_update.each do |line_number, entry|
variant = entry.product_object
assign_defaults(variant, entry.attributes)
if variant.valid? and variant.save
@variants_updated += 1
@updated_ids.push variant.id
else
self.errors.add("Line #{line_number}:", variant.errors.full_messages) #TODO: change
end
end
@variants_to_create.each do |line_number, entry|
new_variant = entry.product_object
assign_defaults(new_variant, entry.attributes)
if new_variant.valid? and new_variant.save
@variants_created += 1
@updated_ids.push new_variant.id
else
self.errors.add("Line #{line_number}:", new_variant.errors.full_messages)
end
end
self.errors.add(:importer, "did not save any products successfully") if total_saved_count.zero?
reset_absent_products
total_saved_count
end
def reset_absent_products
return if total_saved_count.zero?
enterprises_to_reset = []
@import_settings.each do |enterprise_id, settings|
enterprises_to_reset.push enterprise_id if settings['reset_all_absent'] and permission_by_id?(enterprise_id)
end
unless enterprises_to_reset.empty? or @updated_ids.empty?
# For selected enterprises; set stock to zero for all products
# that were not present in the uploaded spreadsheet
@products_reset_count = Spree::Variant.joins(:product).
where('spree_products.supplier_id IN (?)
AND spree_variants.id NOT IN (?)
AND spree_variants.is_master = false
AND spree_variants.deleted_at IS NULL', enterprises_to_reset, @updated_ids).
update_all(count_on_hand: 0)
end
end
def assign_defaults(object, entry)
@import_settings[entry['supplier_id'].to_s]['defaults'].each do |attribute, setting|
case setting['mode']
when 'overwrite_all'
object.assign_attributes(attribute => setting['value'])
when 'overwrite_empty'
if object.send(attribute).blank? or (attribute == 'on_hand' and entry['on_hand_nil'])
object.assign_attributes(attribute => setting['value'])
end
end
end
end
def ensure_variant_updated(product, entry)
# Ensure display_name and on_demand are copied to new product's variant
if entry.display_name || entry.on_demand
variant = product.variants.first
variant.display_name = entry.display_name if entry.display_name
variant.on_demand = entry.on_demand if entry.on_demand
variant.save
end
end
def set_update_status(entry)
# Find product with matching supplier and name
match = Spree::Product.where(supplier_id: entry.supplier_id, name: entry.name, deleted_at: nil).first
# If no matching product was found, create a new product
if match.nil?
mark_as_new_product(entry)
return
end
# Otherwise, if a variant exists with matching display_name and unit_value, update it
match.variants.each do |existing_variant|
if existing_variant.display_name == entry.display_name && existing_variant.unit_value == Float(entry.unit_value)
mark_as_existing_variant(entry, existing_variant)
return
end
end
# Otherwise, a variant with sufficiently matching attributes doesn't exist; create a new one
mark_as_new_variant(entry, match.id)
end
def mark_as_new_product(entry)
new_product = Spree::Product.new()
new_product.assign_attributes(entry.attributes.except('id'))
if new_product.valid?
@products_to_create[entry.line_number] = entry unless entry_invalid?(entry.line_number)
else
mark_as_invalid(entry, product_validations: new_product.errors)
end
end
def mark_as_existing_variant(entry, existing_variant)
existing_variant.assign_attributes(entry.attributes.except('id', 'product_id'))
check_on_hand_nil(entry, existing_variant)
if existing_variant.valid?
entry.product_object = existing_variant
@variants_to_update[entry.line_number] = entry unless entry_invalid?(entry.line_number)
updates_count_per_supplier(entry.supplier_id) unless entry_invalid?(entry.line_number)
else
mark_as_invalid(entry, product_validations: existing_variant.errors)
end
end
def mark_as_new_variant(entry, product_id)
new_variant = Spree::Variant.new(entry.attributes.except('id', 'product_id'))
new_variant.product_id = product_id
check_on_hand_nil(entry, new_variant)
if new_variant.valid?
entry.product_object = new_variant
@variants_to_create[entry.line_number] = entry unless entry_invalid?(entry.line_number)
else
mark_as_invalid(entry, product_validations: new_variant.errors)
end
end
def updates_count_per_supplier(supplier_id)
if @products_to_reset[supplier_id] and @products_to_reset[supplier_id][:updates_count]
@products_to_reset[supplier_id][:updates_count] += 1
else
@products_to_reset[supplier_id] = {updates_count: 1}
end
end
def check_on_hand_nil(entry, variant)
if entry.on_hand.blank?
variant.on_hand = 0
entry.on_hand_nil = true
end
end
def delete_uploaded_file
# Only delete if file is in '/tmp/product_import' directory
if @file.path == Rails.root.join('tmp', 'product_import').to_s
File.delete(@file)
end
end
end

View File

@@ -0,0 +1,68 @@
# Class for defining spreadsheet entry objects for use in ProductImporter
class SpreadsheetEntry
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_accessor :line_number, :valid, :product_object, :product_validations, :save_type, :on_hand_nil
attr_accessor :id, :product_id, :supplier, :supplier_id, :name, :display_name, :sku,
:unit_value, :unit_description, :variant_unit, :variant_unit_scale, :variant_unit_name,
:display_as, :category, :primary_taxon_id, :price, :on_hand, :on_demand,
:tax_category_id, :shipping_category_id, :description
def initialize(attrs)
@product_validations = {}
attrs.each do |k, v|
if self.respond_to?("#{k}=")
send("#{k}=", v) unless non_product_attributes.include?(k)
else
# Trying to assign unknown attribute. Record this and give feedback or just ignore silently?
end
end
end
def persisted?
false #ActiveModel
end
def has_errors?
self.errors.count > 0 or @product_validations.count > 0
end
def attributes
attrs = {}
self.instance_variables.each do |var|
attrs[var.to_s.delete("@")] = self.instance_variable_get(var)
end
attrs.except(*non_product_attributes)
end
def displayable_attributes
# Modified attributes list for displaying in user feedback
attrs = {}
self.instance_variables.each do |var|
attrs[var.to_s.delete("@")] = self.instance_variable_get(var)
end
attrs.except(*non_product_attributes, *non_display_attributes)
end
def invalid_attributes
invalid_attrs = {}
@product_validations.messages.merge(self.errors.messages).each do |attr, message|
invalid_attrs[attr.to_s] = "#{attr.to_s.capitalize} #{message.first}"
end
invalid_attrs.except(*non_product_attributes, *non_display_attributes)
end
private
def non_display_attributes
['id', 'product_id', 'variant_id', 'supplier_id', 'primary_taxon', 'primary_taxon_id', 'category_id', 'shipping_category_id', 'tax_category_id']
end
def non_product_attributes
['line_number', 'valid', 'errors', 'product_object', 'product_validations', 'save_type', 'on_hand_nil']
end
end

View File

@@ -4,6 +4,7 @@ class AbilityDecorator
# All abilites are allocated from this initialiser, currently in 5 chunks.
# Spree also defines other abilities.
def initialize(user)
add_shopping_abilities user
add_base_abilities user if is_new_user? user
add_enterprise_management_abilities user if can_manage_enterprises? user
add_group_management_abilities user if can_manage_groups? user
@@ -50,6 +51,16 @@ class AbilityDecorator
can_manage_enterprises? user
end
def add_shopping_abilities(user)
can [:destroy], Spree::LineItem do |item|
user == item.order.user &&
item.order.changes_allowed?
end
can [:cancel], Spree::Order do |order|
order.user == user
end
end
# New users can create an enterprise, and gain other permissions from doing this.
def add_base_abilities(user)
can [:create], Enterprise
@@ -155,6 +166,8 @@ class AbilityDecorator
can [:admin, :index, :read, :search], Spree::Taxon
can [:admin, :index, :read, :create, :edit], Spree::Classification
can [:admin, :index, :import, :save], ProductImporter
# Reports page
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], :report
end
@@ -173,7 +186,7 @@ class AbilityDecorator
def add_order_management_abilities(user)
# Enterprise User can only access orders that they are a distributor for
can [:index, :create], Spree::Order
can [:read, :update, :fire, :resend, :invoice, :print], Spree::Order do |order|
can [:read, :update, :fire, :resend, :invoice, :print, :print_ticket], Spree::Order do |order|
# We allow editing orders with a nil distributor as this state occurs
# during the order creation process from the admin backend
order.distributor.nil? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)
@@ -182,6 +195,7 @@ class AbilityDecorator
can [:admin , :for_line_items], Enterprise
can [:admin, :index, :create], Spree::LineItem
can [:destroy, :update], Spree::LineItem do |item|
order = item.order
user.admin? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)
end
@@ -226,7 +240,6 @@ class AbilityDecorator
can [:admin, :index, :update, :destroy], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id)
end
def add_relationship_management_abilities(user)
can [:admin, :index, :create], EnterpriseRelationship
can [:destroy], EnterpriseRelationship do |enterprise_relationship|

View File

@@ -34,8 +34,31 @@ module Spree
included_tax > 0
end
def display_included_tax
Spree::Money.new(included_tax, { :currency => currency })
# @return [Array<Spree::TaxRate>]
def tax_rates
case originator
when Spree::TaxRate
[originator]
when EnterpriseFee
case source
when Spree::LineItem
tax_category = originator.inherits_tax_category? ? source.product.tax_category : originator.tax_category
return tax_category ? tax_category.tax_rates.match(source.order) : []
when Spree::Order
return originator.tax_category ? originator.tax_category.tax_rates.match(source) : []
end
else
find_closest_tax_rates_from_included_tax
end
end
# shipping fees and adjustments created from the admin panel have
# taxes set at creation in the included_tax field without relation
# to the corresponding TaxRate, so we look for the closest one
def find_closest_tax_rates_from_included_tax
approximation = (included_tax / (amount - included_tax))
return [] if approximation.infinite? or approximation.zero?
[Spree::TaxRate.order("ABS(amount - #{approximation})").first]
end
def self.without_callbacks

View File

@@ -6,7 +6,6 @@ Spree::AppConfiguration.class_eval do
# Terms of Service Preferences
preference :enterprises_require_tos, :boolean, default: false
preference :enterprise_tos_link, :string, default: "/Terms-of-service.pdf"
# Tax Preferences
preference :products_require_tax_category, :boolean, default: false

View File

@@ -47,7 +47,6 @@ Spree::LineItem.class_eval do
update_attributes!(quantity: variant.on_hand) if quantity > variant.on_hand
end
def has_tax?
adjustments.included_tax.any?
end
@@ -94,8 +93,41 @@ Spree::LineItem.class_eval do
(final_weight_volume || 0) / quantity
end
# MONKEYPATCH of Spree method
# Enables scoping of variant to hub/shop, stock drawn down from inventory
def update_inventory
return true unless order.completed?
scoper.scope(variant) # this line added
if new_record?
Spree::InventoryUnit.increase(order, variant, quantity)
elsif old_quantity = self.changed_attributes['quantity']
if old_quantity < quantity
Spree::InventoryUnit.increase(order, variant, (quantity - old_quantity))
elsif old_quantity > quantity
Spree::InventoryUnit.decrease(order, variant, (old_quantity - quantity))
end
end
end
# MONKEYPATCH of Spree method
# Enables scoping of variant to hub/shop, stock replaced to inventory
def remove_inventory
return true unless order.completed?
scoper.scope(variant) # this line added
Spree::InventoryUnit.decrease(order, variant, quantity)
end
private
def scoper
return @scoper unless @scoper.nil?
@scoper = OpenFoodNetwork::ScopeVariantToHub.new(order.distributor)
end
def calculate_final_weight_volume
if final_weight_volume.present? && quantity_was > 0
self.final_weight_volume = final_weight_volume * quantity / quantity_was

View File

@@ -7,7 +7,7 @@ module Spree
private
def refresh_products_cache
products(:reload).each &:refresh_products_cache
products(:reload).each(&:refresh_products_cache)
end
end
end

View File

@@ -7,13 +7,13 @@ module Spree
private
def refresh_products_cache
variants(:reload).each &:refresh_products_cache
variants(:reload).each(&:refresh_products_cache)
end
def refresh_products_cache_from_destroy
vs = variants(:reload).to_a
yield
vs.each &:refresh_products_cache
vs.each(&:refresh_products_cache)
end
end

View File

@@ -9,7 +9,7 @@ end
Spree::Order.class_eval do
belongs_to :order_cycle
belongs_to :distributor, :class_name => 'Enterprise'
belongs_to :distributor, class_name: 'Enterprise'
belongs_to :cart
belongs_to :customer
@@ -21,6 +21,9 @@ Spree::Order.class_eval do
before_validation :associate_customer, unless: :customer_id?
before_validation :ensure_customer, unless: :customer_is_valid?
before_save :update_shipping_fees!, if: :complete?
before_save :update_payment_fees!, if: :complete?
checkout_flow do
go_to_state :address
go_to_state :delivery
@@ -39,7 +42,6 @@ Spree::Order.class_eval do
remove_transition :from => :delivery, :to => :confirm
end
# -- Scopes
scope :managed_by, lambda { |user|
if user.has_spree_role?('admin')
@@ -88,7 +90,7 @@ Spree::Order.class_eval do
unless self.order_cycle == order_cycle
self.order_cycle = order_cycle
self.distributor = nil unless order_cycle.nil? || order_cycle.has_distributor?(distributor)
self.empty!
empty!
save!
end
end
@@ -99,7 +101,6 @@ Spree::Order.class_eval do
current_item.andand.destroy
end
# Overridden to support max_quantity
def add_variant(variant, quantity = 1, max_quantity = nil, currency = nil)
line_items(:reload)
@@ -126,7 +127,7 @@ Spree::Order.class_eval do
current_item.currency = currency unless currency.nil?
current_item.save
else
current_item = Spree::LineItem.new(:quantity => quantity, max_quantity: max_quantity)
current_item = Spree::LineItem.new(quantity: quantity, max_quantity: max_quantity)
current_item.variant = variant
if currency
current_item.currency = currency unless currency.nil?
@@ -134,17 +135,34 @@ Spree::Order.class_eval do
else
current_item.price = variant.price
end
self.line_items << current_item
line_items << current_item
end
self.reload
reload
current_item
end
def cap_quantity_at_stock!
line_items.each &:cap_quantity_at_stock!
# After changing line items of a completed order
def update_shipping_fees!
shipments.each do |shipment|
next if shipment.shipped?
update_adjustment! shipment.adjustment
shipment.save # updates included tax
end
end
# After changing line items of a completed order
def update_payment_fees!
payments.each do |payment|
next if payment.completed?
update_adjustment! payment.adjustment
payment.save
end
end
def cap_quantity_at_stock!
line_items.each(&:cap_quantity_at_stock!)
end
def set_distributor!(distributor)
self.distributor = distributor
@@ -158,6 +176,10 @@ Spree::Order.class_eval do
save!
end
def distribution_set?
distributor && order_cycle
end
def update_distribution_charge!
with_lock do
EnterpriseFee.clear_all_adjustments_on_order self
@@ -195,6 +217,12 @@ Spree::Order.class_eval do
line_items.map(&:variant)
end
# Show already bought line items of this order cycle
def finalised_line_items
return [] unless order_cycle && user && distributor
order_cycle.items_bought_by_user(user, distributor)
end
def admin_and_handling_total
adjustments.eligible.where("originator_type = ? AND source_type != ?", 'EnterpriseFee', 'Spree::LineItem').sum(&:amount)
end
@@ -205,26 +233,26 @@ Spree::Order.class_eval do
# Does this order have shipments that can be shipped?
def ready_to_ship?
self.shipments.any?{|s| s.can_ship?}
shipments.any?(&:can_ship?)
end
# Ship all pending orders
def ship
self.shipments.each do |s|
shipments.each do |s|
s.ship if s.can_ship?
end
end
def shipping_tax
adjustments(:reload).shipping.sum &:included_tax
adjustments(:reload).shipping.sum(&:included_tax)
end
def enterprise_fee_tax
adjustments(:reload).enterprise_fee.sum &:included_tax
adjustments(:reload).enterprise_fee.sum(&:included_tax)
end
def total_tax
(adjustments + price_adjustments).sum &:included_tax
(adjustments + price_adjustments).sum(&:included_tax)
end
def tax_adjustments
@@ -234,12 +262,12 @@ Spree::Order.class_eval do
def tax_adjustment_totals
tax_adjustments.each_with_object(Hash.new) do |adjustment, hash|
if adjustment.originator_type == "Spree::TaxRate"
tax_rate = adjustment.originator.amount
else
tax_rate = (adjustment.included_tax / (adjustment.amount - adjustment.included_tax)).round(2)
end
hash.update({tax_rate => adjustment.included_tax}) { |_tax_rate, amount1, amount2| amount1 + amount2 }
tax_rates = adjustment.tax_rates
tax_rates_hash = Hash[tax_rates.collect do |tax_rate|
tax_amount = tax_rates.one? ? adjustment.included_tax : tax_rate.compute_tax(adjustment.amount)
[tax_rate.amount, tax_amount]
end]
hash.update(tax_rates_hash) { |_tax_rate, amount1, amount2| amount1 + amount2 }
end
end
@@ -254,11 +282,12 @@ Spree::Order.class_eval do
# Overrride of Spree method, that allows us to send separate confirmation emails to user and shop owners
# And separately, to skip sending confirmation email completely for user invoice orders
def deliver_order_confirmation_email
unless account_invoice?
Delayed::Job.enqueue ConfirmOrderJob.new(id)
end
Delayed::Job.enqueue ConfirmOrderJob.new(id) unless account_invoice?
end
def changes_allowed?
complete? && distributor.andand.allow_order_changes? && order_cycle.andand.open?
end
private
@@ -272,9 +301,9 @@ Spree::Order.class_eval do
self.ship_address = distributor.address.clone
if bill_address
self.ship_address.firstname = bill_address.firstname
self.ship_address.lastname = bill_address.lastname
self.ship_address.phone = bill_address.phone
ship_address.firstname = bill_address.firstname
ship_address.lastname = bill_address.lastname
ship_address.phone = bill_address.phone
end
end
end
@@ -286,11 +315,11 @@ Spree::Order.class_eval do
end
def product_distribution_for(line_item)
line_item.variant.product.product_distribution_for self.distributor
line_item.variant.product.product_distribution_for distributor
end
def require_customer?
return true unless new_record? or state == 'cart'
return true unless new_record? || state == 'cart'
end
def customer_is_valid?
@@ -313,4 +342,11 @@ Spree::Order.class_eval do
self.customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user, name: customer_name, bill_address: bill_address.andand.clone, ship_address: ship_address.andand.clone)
end
end
def update_adjustment!(adjustment)
locked = adjustment.locked
adjustment.locked = false
adjustment.update!(self)
adjustment.locked = locked
end
end

View File

@@ -82,7 +82,6 @@ Spree::OrderPopulator.class_eval do
[quantity_to_add, max_quantity_to_add]
end
def cart_remove(variant_id)
variant = Spree::Variant.find(variant_id)
@order.remove_variant(variant)

View File

@@ -16,7 +16,7 @@ module Spree
adjustment.save
else
payment_method.create_adjustment(adjustment_label, order, self, true)
reload
association(:adjustment).reload
end
end

View File

@@ -21,7 +21,7 @@ module Spree
def enterprise
enterprise_id = key.match(product_selection_from_inventory_only_regex)[1]
enterprise = Enterprise.find enterprise_id
Enterprise.find(enterprise_id)
end
def product_selection_from_inventory_only_regex

View File

@@ -13,7 +13,6 @@ module Spree::Preferences
end
end
def get_preference(key)
if !has_preference?(key) && has_attachment?(key)
send key
@@ -32,7 +31,6 @@ module Spree::Preferences
end
end
# Spree's Configuration responds to preference methods via method_missing, but doesn't
# override respond_to?, which consequently reports those methods as unavailable. Paperclip
# errors if respond_to? isn't correct, so we override it here.
@@ -41,7 +39,6 @@ module Spree::Preferences
super(self.class.preference_getter_method(name), include_all) || super(method, include_all)
end
def has_attachment?(name)
self.class.respond_to?(:attachment_definitions) &&
self.class.attachment_definitions.keys.include?(name.to_sym)

View File

@@ -216,7 +216,7 @@ Spree::Product.class_eval do
if variant_unit_changed?
option_types.delete self.class.all_variant_unit_option_types
option_types << variant_unit_option_type if variant_unit.present?
variants_including_master.each &:update_units
variants_including_master.each(&:update_units)
end
end

View File

@@ -4,7 +4,7 @@ Spree::ProductOptionType.class_eval do
def remove_option_values
self.product.variants_including_master.each do |variant|
option_values = variant.option_values.where(option_type_id: self.option_type)
variant.option_values.destroy *option_values
variant.option_values.destroy(*option_values)
end
end
end

View File

@@ -30,7 +30,7 @@ module Spree
private
def refresh_products_cache
product_properties(:reload).each &:refresh_products_cache
product_properties(:reload).each(&:refresh_products_cache)
end
end
end

View File

@@ -42,7 +42,6 @@ Spree::ShippingMethod.class_eval do
]
end
def available_to_order_with_distributor_check?(order, display_on=nil)
available_to_order_without_distributor_check?(order, display_on) &&
self.distributors.include?(order.distributor)

View File

@@ -12,7 +12,8 @@ module Spree
def adjust_with_included_tax(order)
adjust_without_included_tax(order)
order.reload
order.adjustments(:reload)
order.line_items(:reload)
(order.adjustments.tax + order.price_adjustments).each do |a|
a.set_absolute_included_tax! a.amount
end

View File

@@ -37,13 +37,14 @@ Spree::Taxon.class_eval do
#
# Format: {enterprise_id => [taxon_id, ...]}
def self.distributed_taxons(which_taxons=:all)
# TODO: Why can't we merge(Spree::Product.with_order_cycles_inner) here?
taxons = Spree::Taxon.
joins(products: {variants_including_master: {exchanges: :order_cycle}}).
merge(Exchange.outgoing).
select('spree_taxons.*, exchanges.receiver_id AS enterprise_id')
ents_and_vars = ExchangeVariant.joins(exchange: :order_cycle).merge(Exchange.outgoing)
.select("DISTINCT variant_id, receiver_id AS enterprise_id")
taxons = taxons.merge(OrderCycle.active) if which_taxons == :current
ents_and_vars = ents_and_vars.merge(OrderCycle.active) if which_taxons == :current
taxons = Spree::Taxon
.select("DISTINCT spree_taxons.id, ents_and_vars.enterprise_id").joins(products: :variants_including_master)
.joins("INNER JOIN (#{ents_and_vars.to_sql}) AS ents_and_vars ON spree_variants.id = ents_and_vars.variant_id")
taxons.inject({}) do |ts, t|
ts[t.enterprise_id.to_i] ||= Set.new
@@ -56,6 +57,6 @@ Spree::Taxon.class_eval do
private
def refresh_products_cache
products(:reload).each &:refresh_products_cache
products(:reload).each(&:refresh_products_cache)
end
end

View File

@@ -70,7 +70,6 @@ Spree::Variant.class_eval do
]
end
def price_with_fees(distributor, order_cycle)
price + fees_for(distributor, order_cycle)
end

View File

@@ -19,7 +19,7 @@ class VariantOverride < ActiveRecord::Base
def self.indexed(hub)
Hash[
for_hubs(hub).map { |vo| [vo.variant, vo] }
for_hubs(hub).preload(:variant).map { |vo| [vo.variant, vo] }
]
end
@@ -45,7 +45,6 @@ class VariantOverride < ActiveRecord::Base
end
end
def stock_overridden?
count_on_hand.present?
end
@@ -58,6 +57,14 @@ class VariantOverride < ActiveRecord::Base
end
end
def increment_stock!(quantity)
if stock_overridden?
increment! :count_on_hand, quantity
else
Bugsnag.notify RuntimeError.new "Attempting to decrement stock level on a VariantOverride without a count_on_hand specified."
end
end
def default_stock?
default_stock.present?
end

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