diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6a43ed5f48..ad1d8b7f15 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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
diff --git a/app/assets/javascripts/darkswarm/cart.js.coffee b/app/assets/javascripts/darkswarm/cart.js.coffee
index 1e971c8bb4..03419a2c87 100644
--- a/app/assets/javascripts/darkswarm/cart.js.coffee
+++ b/app/assets/javascripts/darkswarm/cart.js.coffee
@@ -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 ->
diff --git a/app/assets/javascripts/darkswarm/controllers/edit_bought_order_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/edit_bought_order_controller.js.coffee
new file mode 100644
index 0000000000..3ed9f9af38
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/controllers/edit_bought_order_controller.js.coffee
@@ -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)
diff --git a/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee
index 42d4c1c44d..79c826165c 100644
--- a/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee
@@ -30,3 +30,4 @@ Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Prod
Variants.clear()
Cart.clear()
Products.update()
+ Cart.reloadFinalisedLineItems()
diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee
index 2b51bc309c..6d75916894 100644
--- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee
+++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee
@@ -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, */*"
diff --git a/app/assets/javascripts/darkswarm/directives/confirm_link_click.js.coffee b/app/assets/javascripts/darkswarm/directives/confirm_link_click.js.coffee
new file mode 100644
index 0000000000..b5da68d4dc
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/directives/confirm_link_click.js.coffee
@@ -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()
diff --git a/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee b/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee
index 610b11d3ca..3c51f86c2f 100644
--- a/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee
+++ b/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee
@@ -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)
diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee
index 70cbe316e8..b80d9a14dd 100644
--- a/app/assets/javascripts/darkswarm/services/cart.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee
@@ -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").query (items) =>
+ for line_item in items
+ line_item.variant.line_item = line_item
+ Variants.extend line_item.variant
+ @line_items_finalised = items
diff --git a/app/assets/javascripts/darkswarm/services/orders.js.coffee b/app/assets/javascripts/darkswarm/services/orders.js.coffee
index 78ba65f79e..049c3baa5e 100644
--- a/app/assets/javascripts/darkswarm/services/orders.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/orders.js.coffee
@@ -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)
diff --git a/app/assets/javascripts/templates/admin/show_more.html.haml b/app/assets/javascripts/templates/admin/show_more.html.haml
index 0800710927..8e8630ba65 100644
--- a/app/assets/javascripts/templates/admin/show_more.html.haml
+++ b/app/assets/javascripts/templates/admin/show_more.html.haml
@@ -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' } }
diff --git a/app/assets/stylesheets/admin/enterprises.css.scss b/app/assets/stylesheets/admin/enterprises.css.scss
new file mode 100644
index 0000000000..5768ebaf27
--- /dev/null
+++ b/app/assets/stylesheets/admin/enterprises.css.scss
@@ -0,0 +1,3 @@
+form[name="enterprise_form"] div.row.warning {
+ color: #DA7F52;
+}
diff --git a/app/assets/stylesheets/darkswarm/_shop-navigation.css.sass b/app/assets/stylesheets/darkswarm/_shop-navigation.css.sass
index 144a5049f4..174935c2ce 100644
--- a/app/assets/stylesheets/darkswarm/_shop-navigation.css.sass
+++ b/app/assets/stylesheets/darkswarm/_shop-navigation.css.sass
@@ -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)
diff --git a/app/assets/stylesheets/darkswarm/account.css.scss b/app/assets/stylesheets/darkswarm/account.css.scss
index 0d44ea1eaa..4c60033a2a 100644
--- a/app/assets/stylesheets/darkswarm/account.css.scss
+++ b/app/assets/stylesheets/darkswarm/account.css.scss
@@ -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) {
diff --git a/app/assets/stylesheets/darkswarm/checkout.css.sass b/app/assets/stylesheets/darkswarm/checkout.css.sass
index d5a2cb8104..f13d2d1bc6 100644
--- a/app/assets/stylesheets/darkswarm/checkout.css.sass
+++ b/app/assets/stylesheets/darkswarm/checkout.css.sass
@@ -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
-
diff --git a/app/assets/stylesheets/darkswarm/shopping-cart.css.scss b/app/assets/stylesheets/darkswarm/shopping-cart.css.scss
index 25ed0ba930..def151a63b 100644
--- a/app/assets/stylesheets/darkswarm/shopping-cart.css.scss
+++ b/app/assets/stylesheets/darkswarm/shopping-cart.css.scss
@@ -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;
- }
-}
diff --git a/app/controllers/line_items_controller.rb b/app/controllers/line_items_controller.rb
new file mode 100644
index 0000000000..04fe14fc30
--- /dev/null
+++ b/app/controllers/line_items_controller.rb
@@ -0,0 +1,39 @@
+class LineItemsController < BaseController
+ respond_to :json
+
+ def index
+ respond_with bought_items, each_serializer: Api::LineItemSerializer
+ end
+
+ def destroy
+ item = Spree::LineItem.find(params[:id])
+ authorize! :destroy, item
+ destroy_with_lock item
+ respond_with(item)
+ end
+
+ private
+
+ # 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
+
+ # Override default which just redirects
+ # See Spree::BaseController and Spree::Core::ControllerHelpers::Auth
+ def unauthorized
+ render text: '', status: 403
+ 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
diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb
index 8a5a5fa2b2..caf81baff2 100644
--- a/app/controllers/spree/admin/reports_controller_decorator.rb
+++ b/app/controllers/spree/admin/reports_controller_decorator.rb
@@ -231,7 +231,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
diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb
index dd4f2eba81..54b08aed8b 100644
--- a/app/controllers/spree/orders_controller_decorator.rb
+++ b/app/controllers/spree/orders_controller_decorator.rb
@@ -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
@@ -32,7 +34,7 @@ Spree::OrdersController.class_eval do
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 +45,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
@@ -162,6 +171,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 +223,23 @@ Spree::OrdersController.class_eval do
def wrap_json_infinity(n)
n == Float::INFINITY ? 2147483647 : n
end
+
+ def order_to_update
+ order = Spree::Order.complete.find_by_number(params[:id])
+ return order if order.andand.changes_allowed? && can?(:update, order)
+ current_order
+ end
+
+ def check_at_least_one_line_item
+ order = order_to_update
+ return unless order.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)
+ end
+ end
end
diff --git a/app/helpers/checkout_helper.rb b/app/helpers/checkout_helper.rb
index eb4cb1fb65..193c4cef4d 100644
--- a/app/helpers/checkout_helper.rb
+++ b/app/helpers/checkout_helper.rb
@@ -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
diff --git a/app/helpers/enterprises_helper.rb b/app/helpers/enterprises_helper.rb
index 0e2a630fff..5987b20475 100644
--- a/app/helpers/enterprises_helper.rb
+++ b/app/helpers/enterprises_helper.rb
@@ -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
diff --git a/app/helpers/spree/orders_helper.rb b/app/helpers/spree/orders_helper.rb
index 5fd0f74fa8..790ff1b155 100644
--- a/app/helpers/spree/orders_helper.rb
+++ b/app/helpers/spree/orders_helper.rb
@@ -16,5 +16,28 @@ 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 [] unless spree_current_user && current_distributor && current_order_cycle
+ 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
+ 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
diff --git a/app/models/open_food_network/calculator/weight.rb b/app/models/open_food_network/calculator/weight.rb
index 7cd44eb7f7..7f0e8807ba 100644
--- a/app/models/open_food_network/calculator/weight.rb
+++ b/app/models/open_food_network/calculator/weight.rb
@@ -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)
diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb
index 894cbac7f7..64fcce4baf 100644
--- a/app/models/order_cycle.rb
+++ b/app/models/order_cycle.rb
@@ -250,6 +250,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)
+ items = orders.map(&:line_items).flatten
+ scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
+ items.each { |li| scoper.scope(li.variant) }
+ end
private
diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb
index 3814365447..2b3843bb6e 100644
--- a/app/models/spree/ability_decorator.rb
+++ b/app/models/spree/ability_decorator.rb
@@ -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
@@ -184,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
diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb
index 31d263761f..cdbae883fc 100644
--- a/app/models/spree/line_item_decorator.rb
+++ b/app/models/spree/line_item_decorator.rb
@@ -94,8 +94,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
diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb
index e1135853ee..4ed49728ff 100644
--- a/app/models/spree/order_decorator.rb
+++ b/app/models/spree/order_decorator.rb
@@ -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,13 +135,31 @@ 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
+ # 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
@@ -195,6 +214,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 +230,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
@@ -254,11 +279,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 +298,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 +312,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 +339,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
diff --git a/app/models/spree/taxon_decorator.rb b/app/models/spree/taxon_decorator.rb
index 1878a20e49..fcc71a92dc 100644
--- a/app/models/spree/taxon_decorator.rb
+++ b/app/models/spree/taxon_decorator.rb
@@ -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
diff --git a/app/models/variant_override.rb b/app/models/variant_override.rb
index e96b56db56..07b2c78cc8 100644
--- a/app/models/variant_override.rb
+++ b/app/models/variant_override.rb
@@ -58,6 +58,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
diff --git a/app/overrides/spree/admin/shared/_product_sub_menu/add_variant_overrides_tab.html.haml.deface b/app/overrides/spree/admin/shared/_product_sub_menu/add_variant_overrides_tab.html.haml.deface
index f0e507234a..2915a5a9f6 100644
--- a/app/overrides/spree/admin/shared/_product_sub_menu/add_variant_overrides_tab.html.haml.deface
+++ b/app/overrides/spree/admin/shared/_product_sub_menu/add_variant_overrides_tab.html.haml.deface
@@ -1,3 +1,3 @@
/ insert_bottom "[data-hook='admin_product_sub_tabs']"
-= tab :variant_overrides, label: "Inventory", url: main_app.admin_inventory_path, match_path: '/inventory'
+= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory'
diff --git a/app/serializers/api/admin/enterprise_serializer.rb b/app/serializers/api/admin/enterprise_serializer.rb
index 9fd20c1d78..c9b4c50aa1 100644
--- a/app/serializers/api/admin/enterprise_serializer.rb
+++ b/app/serializers/api/admin/enterprise_serializer.rb
@@ -4,7 +4,7 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
attributes :preferred_shopfront_message, :preferred_shopfront_closed_message, :preferred_shopfront_taxon_order, :preferred_shopfront_order_cycle_order
attributes :preferred_product_selection_from_inventory_only
attributes :owner, :users, :tag_groups, :default_tag_group
- attributes :require_login
+ attributes :require_login, :allow_guest_orders, :allow_order_changes
has_one :owner, serializer: Api::Admin::UserSerializer
has_many :users, serializer: Api::Admin::UserSerializer
diff --git a/app/serializers/api/current_order_serializer.rb b/app/serializers/api/current_order_serializer.rb
index 9f939b09c1..77927614f8 100644
--- a/app/serializers/api/current_order_serializer.rb
+++ b/app/serializers/api/current_order_serializer.rb
@@ -1,11 +1,12 @@
class Api::CurrentOrderSerializer < ActiveModel::Serializer
attributes :id, :item_total, :email, :shipping_method_id,
- :display_total, :payment_method_id
+ :display_total, :payment_method_id
has_one :bill_address, serializer: Api::AddressSerializer
has_one :ship_address, serializer: Api::AddressSerializer
has_many :line_items, serializer: Api::LineItemSerializer
+ has_many :finalised_line_items, serializer: Api::LineItemSerializer
def payment_method_id
object.payments.first.andand.payment_method_id
diff --git a/app/serializers/api/order_serializer.rb b/app/serializers/api/order_serializer.rb
index acbd0fdd01..7aa53a0d6b 100644
--- a/app/serializers/api/order_serializer.rb
+++ b/app/serializers/api/order_serializer.rb
@@ -1,11 +1,26 @@
module Api
class OrderSerializer < ActiveModel::Serializer
- attributes :number, :completed_at, :total, :state, :shipment_state, :payment_state, :outstanding_balance, :payments, :path
+ attributes :number, :completed_at, :total, :state, :shipment_state, :payment_state
+ attributes :outstanding_balance, :payments, :path, :cancel_path, :changes_allowed, :changes_allowed_until
+ attributes :shop_name, :item_count
has_many :payments, serializer: Api::PaymentSerializer
+ def shop_name
+ object.distributor.andand.name
+ end
+
+ def item_count
+ object.line_items.sum(&:quantity)
+ end
+
def completed_at
- object.completed_at.blank? ? "" : I18n.l(object.completed_at, format: :long)
+ object.completed_at.blank? ? "" : I18n.l(object.completed_at, format: "%b %d, %Y %H:%M")
+ end
+
+ def changes_allowed_until
+ return I18n.t(:not_allowed) unless object.changes_allowed?
+ I18n.l(object.order_cycle.andand.orders_close_at, format: "%b %d, %Y %H:%M")
end
def total
@@ -25,7 +40,16 @@ module Api
end
def path
- Spree::Core::Engine.routes_url_helpers.order_url(object.number, only_path: true)
+ Spree::Core::Engine.routes_url_helpers.order_path(object)
+ end
+
+ def cancel_path
+ return nil unless object.changes_allowed?
+ Spree::Core::Engine.routes_url_helpers.cancel_order_path(object)
+ end
+
+ def changes_allowed
+ object.changes_allowed?
end
end
end
diff --git a/app/serializers/api/variant_serializer.rb b/app/serializers/api/variant_serializer.rb
index 6a38fe9b93..c1fa8d213b 100644
--- a/app/serializers/api/variant_serializer.rb
+++ b/app/serializers/api/variant_serializer.rb
@@ -1,7 +1,6 @@
class Api::VariantSerializer < ActiveModel::Serializer
attributes :id, :is_master, :count_on_hand, :name_to_display, :unit_to_display
attributes :options_text, :on_demand, :price, :fees, :price_with_fees, :product_name
- attributes :tag_list
def price
object.price
@@ -23,4 +22,8 @@ class Api::VariantSerializer < ActiveModel::Serializer
def product_name
object.product.name
end
+
+ def tag_list
+ object.tag_list if object.is_a? VariantOverride
+ end
end
diff --git a/app/views/admin/account/show.html.haml b/app/views/admin/account/show.html.haml
index b052b5b0a0..07c540ba1f 100644
--- a/app/views/admin/account/show.html.haml
+++ b/app/views/admin/account/show.html.haml
@@ -25,7 +25,7 @@
%col{ width: '62.5%' }
%col{ width: '12.5%' }
%thead
- %th Date
+ %th= t('admin.date')
%th= t(:description)
%th= t(:charge)
- if order = invoice.order
diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml
index 7e08c2e4b3..485a8eb280 100644
--- a/app/views/admin/customers/index.html.haml
+++ b/app/views/admin/customers/index.html.haml
@@ -20,7 +20,7 @@
.filter_select.five.columns.alpha
%label{ :for => 'quick_search', ng: {class: '{disabled: !shop_id}'} }=t('admin.quick_search')
%br
- %input.fullwidth{ :type => "text", :id => 'quick_search', ng: { model: 'quickSearch', disabled: '!shop_id' }, :placeholder => "Search by email/code..." }
+ %input.fullwidth{ :type => "text", :id => 'quick_search', ng: { model: 'quickSearch', disabled: '!shop_id' }, :placeholder => t('.search_by_email') }
.filter_select.four.columns
%label{ :for => 'hub_id', ng: { bind: "shop_id ? '#{t('admin.shop')}' : '#{t('admin.variant_overrides.index.select_a_shop')}'" } }
%br
@@ -94,6 +94,6 @@
-# %show-more.text-center{ data: "filteredCustomers", limit: "customerLimit", increment: "20" }
%div.text-center{ ng: { show: "filteredCustomers.length > customerLimit" } }
- %input{ type: 'button', value: 'Show More', ng: { click: 'customerLimit = customerLimit + 20' } }
+ %input{ type: 'button', value: t(:show_more), ng: { click: 'customerLimit = customerLimit + 20' } }
or
- %input{ type: 'button', value: "Show All ({{ filteredCustomers.length - customerLimit }} More)", ng: { click: 'customerLimit = filteredCustomers.length' } }
+ %input{ type: 'button', value: t(:show_all_with_more, num: '{{ filteredCustomers.length - customerLimit }}'), ng: { click: 'customerLimit = filteredCustomers.length' } }
diff --git a/app/views/admin/enterprises/form/_business_details.html.haml b/app/views/admin/enterprises/form/_business_details.html.haml
index e14254ef5d..c5953ba5ef 100644
--- a/app/views/admin/enterprises/form/_business_details.html.haml
+++ b/app/views/admin/enterprises/form/_business_details.html.haml
@@ -23,11 +23,11 @@
= f.label :charges_sales_tax, t(:say_no), value: 'false'
.row
.alpha.three.columns
- = f.label :display_invoice_logo, 'Display logo in invoices'
+ = f.label :display_invoice_logo, t('.display_invoice_logo')
.omega.eight.columns
= f.check_box :display_invoice_logo
.row
.alpha.three.columns
- = f.label :invoice_text, 'Add customized text at the end of invoices'
+ = f.label :invoice_text, t('.invoice_text')
.omega.eight.columns
= f.text_area :invoice_text, style: "width: 100%; height: 100px;"
diff --git a/app/views/admin/enterprises/form/_shipping_methods.html.haml b/app/views/admin/enterprises/form/_shipping_methods.html.haml
index db7daa5b98..877b650c9e 100644
--- a/app/views/admin/enterprises/form/_shipping_methods.html.haml
+++ b/app/views/admin/enterprises/form/_shipping_methods.html.haml
@@ -10,7 +10,7 @@
%tr{ ng: { controller: 'shippingMethodsCtrl', init: "findShippingMethodByID(#{shipping_method.id})" } }
%td= shipping_method.name
%td= f.check_box :shipping_method_ids, { :multiple => true, 'ng-model' => 'ShippingMethod.selected' }, shipping_method.id, nil
- %td= link_to "Edit", edit_admin_shipping_method_path(shipping_method)
+ %td= link_to t(:edit), edit_admin_shipping_method_path(shipping_method)
%br
.row
.six.columns.alpha
diff --git a/app/views/admin/enterprises/form/_shop_preferences.html.haml b/app/views/admin/enterprises/form/_shop_preferences.html.haml
index 0e5a1096f0..c34fbdfe54 100644
--- a/app/views/admin/enterprises/form/_shop_preferences.html.haml
+++ b/app/views/admin/enterprises/form/_shop_preferences.html.haml
@@ -1,7 +1,7 @@
.row
.alpha.eleven.columns
.three.columns.alpha
- = f.label "enterprise_preferred_shopfront_message", t(:shopfront_message)
+ = f.label "enterprise_preferred_shopfront_message", t('.shopfront_message')
.eight.columns.omega
%text-angular{'ng-model' => 'Enterprise.preferred_shopfront_message', 'id' => 'enterprise_preferred_shopfront_message', 'name' => 'enterprise[preferred_shopfront_message]', 'class' => 'text-angular',
'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]",
@@ -9,7 +9,7 @@
.row
.alpha.eleven.columns
.three.columns.alpha
- = f.label "enterprise_preferred_shopfront_closed_message", t(:shopfront_closed_message)
+ = f.label "enterprise_preferred_shopfront_closed_message", t('.shopfront_closed_message')
.eight.columns.omega
%text-angular{'ng-model' => 'Enterprise.preferred_shopfront_closed_message', 'id' => 'enterprise_preferred_shopfront_closed_message', 'name' => 'enterprise[preferred_shopfront_closed_message]', 'class' => 'text-angular',
'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]",
@@ -17,7 +17,7 @@
.row
.alpha.eleven.columns
.three.columns.alpha
- = f.label "enterprise_preferred_shopfront_taxon_order", t(:shopfront_category_ordering)
+ = f.label "enterprise_preferred_shopfront_taxon_order", t('.shopfront_category_ordering')
%br
(top to bottom)
.eight.columns.omega
@@ -51,9 +51,27 @@
%label= t '.allow_guest_orders'
%div{'ofn-with-tip' => t('.allow_guest_orders_tip')}
%a= t 'admin.whats_this'
+ .eight.columns.omega
+ .row
+ .three.columns.alpha
+ = f.radio_button :allow_guest_orders, true, "ng-model" => "Enterprise.allow_guest_orders", "ng-value" => "true"
+ = f.label :allow_guest_orders, t('.allow_guest_orders_true'), value: :true
+ .five.columns.omega
+ = f.radio_button :allow_guest_orders, false, "ng-model" => "Enterprise.allow_guest_orders", "ng-value" => "false"
+ = f.label :allow_guest_orders, t('.allow_guest_orders_false'), value: :false
+ .row.warning{ng: {show: 'Enterprise.allow_guest_orders && Enterprise.allow_order_changes'}}
+ .eight.columns.alpha.omega
+ %i.icon-warning-sign
+ = t '.recommend_require_login'
+.row
+ .alpha.eleven.columns
+ .three.columns.alpha
+ %label= t '.allow_order_changes'
+ %div{'ofn-with-tip' => t('.allow_order_changes_tip')}
+ %a= t 'admin.whats_this'
.three.columns
- = f.radio_button :allow_guest_orders, true
- = f.label :allow_guest_orders, t('.allow_guest_orders_true'), value: :true
+ = f.radio_button :allow_order_changes, false, "ng-model" => "Enterprise.allow_order_changes", "ng-value" => "false"
+ = f.label :allow_order_changes, t('.allow_order_changes_false'), value: :false
.five.columns.omega
- = f.radio_button :allow_guest_orders, false
- = f.label :allow_guest_orders, t('.allow_guest_orders_false'), value: :false
+ = f.radio_button :allow_order_changes, true, "ng-model" => "Enterprise.allow_order_changes", "ng-value" => "true"
+ = f.label :allow_order_changes, t('.allow_order_changes_true'), value: :true
diff --git a/app/views/admin/variant_overrides/_filters.html.haml b/app/views/admin/variant_overrides/_filters.html.haml
index a1772ed222..b7a1c948ba 100644
--- a/app/views/admin/variant_overrides/_filters.html.haml
+++ b/app/views/admin/variant_overrides/_filters.html.haml
@@ -11,7 +11,7 @@
.filter_select.four.columns
%label{ :for => 'producer_filter', ng: {class: '{disabled: !hub_id}'} }=t('admin.producer')
%br
- %input.ofn-select2.fullwidth{ :id => 'producer_filter', type: 'number', data: 'producers', blank: "{id: 0, name: 'All'}", ng: { model: 'producerFilter', disabled: '!hub_id' } }
+ %input.ofn-select2.fullwidth{ :id => 'producer_filter', type: 'number', data: 'producers', blank: "{id: 0, name: '#{t(:all)}'}", ng: { model: 'producerFilter', disabled: '!hub_id' } }
-# .filter_select{ :class => "three columns" }
-# %label{ :for => 'distributor_filter' }Hub
-# %br
diff --git a/app/views/checkout/_already_ordered.html.haml b/app/views/checkout/_already_ordered.html.haml
new file mode 100644
index 0000000000..01c4265252
--- /dev/null
+++ b/app/views/checkout/_already_ordered.html.haml
@@ -0,0 +1,2 @@
+%p.alert-box.info
+ = t '.message_html', cart: link_to(t('.cart'), "#{cart_path}#bought-products")
diff --git a/app/views/checkout/_form.html.haml b/app/views/checkout/_form.html.haml
index 65ed90b069..42f31f4a35 100644
--- a/app/views/checkout/_form.html.haml
+++ b/app/views/checkout/_form.html.haml
@@ -11,6 +11,7 @@
= render "checkout/billing", f: f
= render "checkout/shipping", f: f
= render "checkout/payment", f: f
+ = render "checkout/already_ordered", f: f if show_bought_items?
%p
%button.button.primary{type: :submit}
= t :checkout_send
diff --git a/app/views/enterprises/shop.html.haml b/app/views/enterprises/shop.html.haml
index 569009db09..23c31e69aa 100644
--- a/app/views/enterprises/shop.html.haml
+++ b/app/views/enterprises/shop.html.haml
@@ -8,6 +8,11 @@
= inject_shop_enterprises
%shop.darkswarm
+ - if changeable_orders.any?
+ .alert-box.info{ "ofn-inline-alert" => true, ng: { show: "visible" } }
+ = shop_changeable_orders_alert_html
+ %a.close{ ng: { click: "close()" } } ×
+
- content_for :order_cycle_form do
%div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true}
diff --git a/app/views/producers/signup.html.haml b/app/views/producers/signup.html.haml
index 0afead9415..268c02a593 100644
--- a/app/views/producers/signup.html.haml
+++ b/app/views/producers/signup.html.haml
@@ -38,7 +38,7 @@
.small-12.medium-6.medium-offset-3.columns.text-center
%h2
= t :producers_signup_cta_headline
- %p.text-big Start with a free profile, and expand when you're ready!
+ %p.text-big= t('.start_free_profile')
%a.button.transparent{href: "/register"}
= t :producers_signup_cta_action
diff --git a/app/views/shared/menu/_cart.html.haml b/app/views/shared/menu/_cart.html.haml
index 73e9fe14be..37c67fd067 100644
--- a/app/views/shared/menu/_cart.html.haml
+++ b/app/views/shared/menu/_cart.html.haml
@@ -15,7 +15,7 @@
%a.button.secondary.tiny.add_to_cart{ href: cart_path, type: :submit, "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }" }
= "{{ Cart.dirty ? '#{t(:cart_updating)}' : (Cart.empty() ? '#{t(:cart_empty)}' : '#{t(:cart_edit)}' ) }}"
%a.button.primary.tiny{href: checkout_path, "ng-disabled" => "Cart.dirty || Cart.empty()"}
- = t 'checkout'
+ = t '.checkout'
%table
%tr.product-cart{"ng-repeat" => "line_item in Cart.line_items", "id" => "cart-variant-{{ line_item.variant.id }}"}
%td
@@ -47,4 +47,25 @@
%a.button.secondary.tiny.add_to_cart{ href: cart_path, type: :submit, "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }" }
= "{{ Cart.dirty ? '#{t(:cart_updating)}' : (Cart.empty() ? '#{t(:cart_empty)}' : '#{t(:cart_edit)}' ) }}"
%a.button.primary.tiny{href: checkout_path, "ng-disabled" => "Cart.dirty || Cart.empty()"}
- = t 'checkout'
+ = t '.checkout'
+ - if order_changes_allowed?
+ %h5{"ng-if" => "Cart.line_items_finalised.length", style: 'margin-top: 1em'}
+ = t '.already_ordered_products'
+ %table
+ %tr.product-cart{"ng-repeat" => "line_item in Cart.line_items_finalised",
+ "id" => "cart-variant-{{ line_item.variant.id }}"}
+ %td
+ %small
+ %strong
+ {{ line_item.variant.extended_name }}
+ %td.text-right
+ %small
+ %span.quantity {{ line_item.quantity }}
+ %i.ofn-i_009-close
+ %span.price {{ line_item.variant.price_with_fees | localizeCurrency }}
+
+ %td
+ %small
+ \=
+ %strong
+ .total-price.right {{ line_item.total_price | localizeCurrency }}
diff --git a/app/views/shopping_shared/_details.html.haml b/app/views/shopping_shared/_details.html.haml
index d060c130ca..ec73b2f5af 100644
--- a/app/views/shopping_shared/_details.html.haml
+++ b/app/views/shopping_shared/_details.html.haml
@@ -1,14 +1,18 @@
+- distributor = @order.andand.distributor || current_distributor
+
%navigation
%distributor.details.row
.small-12.medium-6.large-6.columns
#distributor_title
- - if current_distributor.logo?
- %img.left{src: current_distributor.logo.url(:thumb)}
+ - if distributor.logo?
+ %img.left{src: distributor.logo.url(:thumb)}
%h3
- = current_distributor.name
- %location= current_distributor.address.city
+ = distributor.name
+ %location= distributor.address.city
/ Will this needs to be a drop-down to choose either pick-up point or delivery once shipping methods are implemented
+
.small-12.medium-6.large-6.columns
= render partial: "shopping_shared/order_cycles"
-= render partial: "shopping_shared/tabs"
+
+= render partial: "shopping_shared/tabs" if distributor == current_distributor
diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml
index 5f6764e8d9..9efb7b4e28 100644
--- a/app/views/spree/admin/orders/bulk_management.html.haml
+++ b/app/views/spree/admin/orders/bulk_management.html.haml
@@ -31,21 +31,21 @@
%label{ :for => 'supplier_filter' }
= t("admin.producer")
%br
- %input#supplier_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'suppliers', blank: "{ id: 0, name: 'All' }", ng: { model: 'supplierFilter' } }
+ %input#supplier_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'suppliers', blank: "{ id: 0, name: '#{t(:all)}' }", ng: { model: 'supplierFilter' } }
.filter_select{ :class => "three columns" }
%label{ :for => 'distributor_filter' }
= t("admin.shop")
%br
- %input#distributor_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'distributors', blank: "{ id: 0, name: 'All' }", ng: { model: 'distributorFilter' } }
+ %input#distributor_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'distributors', blank: "{ id: 0, name: '#{t(:all)}' }", ng: { model: 'distributorFilter' } }
.filter_select{ :class => "three columns" }
%label{ :for => 'order_cycle_filter' }
= t("admin.order_cycle")
%br
- %input#order_cycle_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'orderCycles', blank: "{ id: 0, name: 'All' }", on: { selecting: "confirmRefresh" }, ng: { model: 'orderCycleFilter', change: 'refreshData()' } }
+ %input#order_cycle_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'orderCycles', blank: "{ id: 0, name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'orderCycleFilter', change: 'refreshData()' } }
.filter_clear{ :class => "two columns omega" }
%label{ :for => 'clear_all_filters' }
%br
- %input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear All", 'ng-click' => "resetSelectFilters()" }
+ %input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_all'), 'ng-click' => "resetSelectFilters()" }
%hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'unitsVariantSelected()' } }
@@ -59,7 +59,7 @@
%h6{ :class => "eight columns alpha", 'ng-hide' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsVariant.full_name }}
%div{ :class => "four columns omega" }
%h6{ :class => "four columns alpha", :style => 'text-align: right;' }
- %a{ :href => '#', 'ng-click' => 'selectedUnitsVariant = {};selectedUnitsProduct = {};sharedResource=false;' } Clear
+ %a{ :href => '#', 'ng-click' => 'selectedUnitsVariant = {};selectedUnitsProduct = {};sharedResource=false;' }= t('admin.clear')
%hr
.row
.one.column.alpha
diff --git a/app/views/spree/admin/products/bulk_edit/_filters.html.haml b/app/views/spree/admin/products/bulk_edit/_filters.html.haml
index 4ce0c7c053..43ff84be58 100644
--- a/app/views/spree/admin/products/bulk_edit/_filters.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_filters.html.haml
@@ -15,4 +15,4 @@
.filter_clear.three.columns.omega
%label{ :for => 'clear_all_filters' }
%br
- %input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => "Clear Filters", 'ng-click' => "resetSelectFilters()" }
+ %input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" }
diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml
index 50789a2f82..9a8f7ad86a 100644
--- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml
@@ -20,7 +20,7 @@
%tr{ ng: { controller: "ColumnsCtrl" } }
%th.left-actions
%a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red' }
- Expand All
+ = t(:expand_all)
%th.producer{ 'ng-show' => 'columns.producer.visible' }=t('admin.producer')
%th.sku{ 'ng-show' => 'columns.sku.visible' }=t('admin.sku')
%th.name{ 'ng-show' => 'columns.name.visible' }=t('admin.name')
diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml
index 01af8014d1..1c11d50cff 100644
--- a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml
@@ -26,7 +26,7 @@
%input.fullwidth{ :type => 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'product.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' }
%td.tax_category{ 'ng-if' => 'columns.tax_category.visible' }
%select.select2{ name: 'product_tax_category_id', 'ofn-track-product' => 'tax_category_id', ng: {model: 'product.tax_category_id', options: 'tax_category.id as tax_category.name for tax_category in tax_categories'} }
- %option{value: ''} None
+ %option{value: ''}= t(:none)
%td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }
%input{ 'ng-model' => 'product.inherits_properties', :name => 'inherits_properties', 'ofn-track-product' => 'inherits_properties', type: "checkbox" }
%td.available_on{ 'ng-show' => 'columns.available_on.visible' }
diff --git a/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml b/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml
index a5f18986c0..529f8034c9 100644
--- a/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml
@@ -1,3 +1,3 @@
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" }
%div.four.columns.alpha
- %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'}
+ %input.four.columns.alpha{ :type => 'button', :value => t(:save_changes), 'ng-click' => 'submitProducts()'}
diff --git a/app/views/spree/admin/reports/bulk_coop.html.haml b/app/views/spree/admin/reports/bulk_coop.html.haml
index 8d9a1234ce..922f32f3a9 100644
--- a/app/views/spree/admin/reports/bulk_coop.html.haml
+++ b/app/views/spree/admin/reports/bulk_coop.html.haml
@@ -4,7 +4,7 @@
.row
.four.columns.alpha
= label_tag nil, "Distributor: "
- = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => 'All'}, {:class => "select2 fullwidth"})
+ = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => t(:all)}, {:class => "select2 fullwidth"})
= label_tag nil, "Report Type: "
%br
= select_tag(:report_type, options_for_select([['Bulk Co-op - Totals by Supplier',:bulk_coop_supplier_report],['Bulk Co-op - Allocation',:bulk_coop_allocation],['Bulk Co-op - Packing Sheets',:bulk_coop_packing_sheets],['Bulk Co-op - Customer Payments',:bulk_coop_customer_payments]], @report_type))
diff --git a/app/views/spree/admin/reports/sales_tax.html.haml b/app/views/spree/admin/reports/sales_tax.html.haml
index c90603d001..9971b2c5cb 100644
--- a/app/views/spree/admin/reports/sales_tax.html.haml
+++ b/app/views/spree/admin/reports/sales_tax.html.haml
@@ -4,7 +4,7 @@
.row
.four.columns.alpha
= label_tag nil, t(:report_distributor)
- = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => 'All'}, {:class => "select2 fullwidth"})
+ = f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => t(:all)}, {:class => "select2 fullwidth"})
= label_tag nil, t(:report_customers_type)
%br
= select_tag(:report_type, options_for_select([[t(:report_tax_types),:tax_types],[t(:report_tax_rates),:tax_rates]], @report_type))
diff --git a/app/views/spree/orders/_bought.html.haml b/app/views/spree/orders/_bought.html.haml
new file mode 100644
index 0000000000..fb66cd1525
--- /dev/null
+++ b/app/views/spree/orders/_bought.html.haml
@@ -0,0 +1,35 @@
+%tbody{ ng: { controller: 'EditBoughtOrderController' } }
+ %tr
+ %td.toggle-bought{ colspan: 2, ng: { click: 'showBought=!showBought' } }
+ %h5.brick
+ %i{ ng: { class: "{ 'ofn-i_007-caret-right': !showBought, 'ofn-i_005-caret-down': showBought}"} }
+ = t(:orders_bought_items_notice, count: @order.finalised_line_items.sum(&:quantity))
+ %td.text-right{ colspan: 3 }
+ %a.edit-finalised.button.radius.expand.small{ href: changeable_orders_link_path, ng: { class: "{secondary: !showBought, primary: showBought}" } }
+ = t(:orders_bought_edit_button)
+ %i.ofn-i_007-caret-right
+
+
+ - @order.finalised_line_items.each do |line_item|
+ - variant = line_item.variant
+ %tr.bought.line-item{class: "line-item-#{line_item.id} variant-#{variant.id}", ng: { show: 'showBought'} }
+ %td.cart-item-description
+
+ %div.item-thumb-image
+ - if variant.images.length == 0
+ = link_to mini_image(variant.product), variant.product
+ - else
+ = link_to image_tag(variant.images.first.attachment.url(:mini)), variant.product
+
+ = render 'spree/shared/line_item_name', line_item: line_item
+ %span.already-confirmed= t(:orders_bought_already_confirmed)
+ %td.text-right.cart-item-price
+ = line_item.single_display_amount_with_adjustments.to_html
+ %td.text-center.cart-item-quantity
+ = line_item.quantity
+ %td.cart-item-total.text-right
+ = line_item.display_amount_with_adjustments.to_html unless line_item.quantity.nil?
+
+ %td.bought-item-delete.text-center
+ %a{ng: {click: "deleteLineItem(#{line_item.id})"}}
+ %i.ofn-i_026-trash
diff --git a/app/views/spree/orders/_form.html.haml b/app/views/spree/orders/_form.html.haml
index 68ba409aa0..9813ff8642 100644
--- a/app/views/spree/orders/_form.html.haml
+++ b/app/views/spree/orders/_form.html.haml
@@ -20,19 +20,10 @@
= order_form.fields_for :line_items do |item_form|
= render :partial => 'line_item', :locals => { :variant => item_form.object.variant, :line_item => item_form.object, :item_form => item_form }
+ = render 'bought' if show_bought_items? && @order.cart?
+
%tfoot#edit-cart
- %tr
- %td{colspan:"2"}
- %td
- = button_tag :class => 'secondary radius expand small', :id => 'update-button' do
- %i.ofn-i_023-refresh
- = t(:update)
- %td
- %td#empty-cart.text-center
- %span#clear_cart_link{"data-hook" => ""}
- = link_to t(:orders_form_empty_cart), empty_cart_path, method: :put, :class => 'not-bold small'
- -#= form_tag empty_cart_path, :method => :put do
- -#= submit_tag t(:empty_cart), :class => 'button alert expand small'
+ = render 'spree/orders/form/cart_actions_row' if @order.cart?
/ This is the fees row which we want to replace with the pop-over
-# - unless @order.adjustments.eligible.blank?
@@ -52,6 +43,14 @@
%span.order-total.distribution-total= display_checkout_admin_and_handling_adjustments_total_for(@order)
%td
+ - checkout_adjustments_for(@order, exclude: [:line_item, :admin_and_handling]).reject{ |a| a.amount == 0 }.reverse_each do |adjustment|
+ %tr.order-adjustment
+ %td.text-right{:colspan => "3"}
+ = adjustment.label
+ %td.text-right.total
+ %span= adjustment.display_amount.to_html
+ %td
+
%tr
%td.text-right{colspan:"3"}
%h5
diff --git a/app/views/spree/orders/_line_item.html.haml b/app/views/spree/orders/_line_item.html.haml
index 5976332a18..7e200253be 100644
--- a/app/views/spree/orders/_line_item.html.haml
+++ b/app/views/spree/orders/_line_item.html.haml
@@ -17,7 +17,7 @@
= render 'spree/shared/line_item_name', line_item: line_item
- - if @insufficient_stock_lines.include? line_item
+ - if @insufficient_stock_lines.andand.include? line_item
%span.out-of-stock
= variant.in_stock? ? t(:insufficient_stock, :on_hand => variant.on_hand) : t(:out_of_stock)
%br/
@@ -35,6 +35,5 @@
= line_item.display_amount_with_adjustments.to_html unless line_item.quantity.nil?
%td.cart-item-delete.text-center{"data-hook" => "cart_item_delete"}
- {{ quantity }}
%a.delete{href: "#", id: "delete_#{dom_id(line_item)}"}
%i.delete.ofn-i_026-trash
diff --git a/app/views/spree/orders/_summary.html.haml b/app/views/spree/orders/_summary.html.haml
new file mode 100644
index 0000000000..b3276faede
--- /dev/null
+++ b/app/views/spree/orders/_summary.html.haml
@@ -0,0 +1,65 @@
+%table#line-items{"data-hook" => "order_details"}
+ %col{valign: "middle"}/
+ %col{halign: "center", valign: "middle", width: "5%"}/
+ %col{halign: "center", valign: "middle", width: "5%"}/
+ %col{halign: "center", valign: "middle", width: "5%"}/
+ %thead{"data-hook" => ""}
+ %tr{"data-hook" => "order_details_line_items_headers"}
+ %th= t(:item)
+ %th.price= t(:price)
+ %th.text-center.qty= t(:qty)
+ %th.text-right.total
+ %span= t(:total)
+ %tbody{"data-hook" => ""}
+ - order.line_items.each do |item|
+ %tr.line_item{"data-hook" => "order_details_line_item_row", class: "variant-#{item.variant.id}" }
+ %td(data-hook = "order_item_description")
+
+ %div.item-thumb-image{"data-hook" => "order_item_image"}
+ - if item.variant.images.length == 0
+ = link_to mini_image(item.variant.product), item.variant.product
+ - else
+ = link_to image_tag(item.variant.images.first.attachment.url(:mini)), item.variant.product
+
+
+ = render 'spree/shared/line_item_name', line_item: item
+
+ %td.text-right.price{"data-hook" => "order_item_price"}
+ %span= item.single_display_amount_with_adjustments.to_html
+ %td.text-center{"data-hook" => "order_item_qty"}= item.quantity
+ %td.text-right.total{"data-hook" => "order_item_total"}
+ %span= item.display_amount_with_adjustments.to_html
+
+ %tfoot
+ #subtotal{"data-hook" => "order_details_subtotal"}
+ %tr#subtotal-row.total
+ %td.text-right{colspan: "3"}
+ %strong
+ = t :order_produce
+ %td.text-right.total
+ %span= display_checkout_subtotal(order)
+
+ #order-charges{"data-hook" => "order_details_adjustments"}
+ - checkout_adjustments_for(order, exclude: [:line_item]).reject{ |a| a.amount == 0 }.reverse_each do |adjustment|
+ %tr.total
+ %td.text-right{:colspan => "3"}
+ %strong
+ = adjustment.label
+ %td.text-right.total
+ %span= adjustment.display_amount.to_html
+
+ #order-total{"data-hook" => "order_details_total"}
+ %tr.total
+ %td.text-right{colspan: "3"}
+ %h5
+ = t :order_total_price
+ %td.text-right.total
+ %h5#order_total= order.display_total.to_html
+
+ - if order.total_tax > 0
+ #tax{"data-hook" => "order_details_tax"}
+ %tr#tax-row.total
+ %td.text-right{colspan: "3"}
+ = t :order_includes_tax
+ %td.text-right.total
+ %span= display_checkout_tax_total(order)
diff --git a/app/views/spree/orders/edit.html.haml b/app/views/spree/orders/edit.html.haml
index 3417eb5e6a..a5afad1178 100644
--- a/app/views/spree/orders/edit.html.haml
+++ b/app/views/spree/orders/edit.html.haml
@@ -31,17 +31,6 @@
.row
= render :partial => 'form', :locals => { :order_form => order_form }
-
- .links{'data-hook' => "cart_buttons"}
- .row
- .columns.large-8{"data-hook" => ""}
-
- %a.button.large.secondary{href: main_app.shop_path}
- %i.ofn-i_008-caret-left
- = t :orders_edit_continue
- .columns.large-4.text-right
- %a#checkout-link.button.large.primary{href: main_app.checkout_path}
- = t :orders_edit_checkout
- %i.ofn-i_007-caret-right
+ = render "spree/orders/form/cart_links"
= render partial: "shared/footer"
diff --git a/app/views/spree/orders/form/_cart_actions_row.html.haml b/app/views/spree/orders/form/_cart_actions_row.html.haml
new file mode 100644
index 0000000000..5790f7f9e1
--- /dev/null
+++ b/app/views/spree/orders/form/_cart_actions_row.html.haml
@@ -0,0 +1,10 @@
+%tr
+ %td{colspan:"2"}
+ %td
+ = button_tag :class => 'secondary radius expand small', :id => 'update-button' do
+ %i.ofn-i_023-refresh
+ = t(:update)
+ %td
+ %td#empty-cart.text-center
+ %span#clear_cart_link{"data-hook" => ""}
+ = link_to t(:orders_form_empty_cart), empty_cart_path, method: :put, :class => 'not-bold small'
diff --git a/app/views/spree/orders/form/_cart_links.html.haml b/app/views/spree/orders/form/_cart_links.html.haml
new file mode 100644
index 0000000000..cec97a8a4d
--- /dev/null
+++ b/app/views/spree/orders/form/_cart_links.html.haml
@@ -0,0 +1,9 @@
+.row.links{'data-hook' => "cart_buttons"}
+ .columns.large-8{"data-hook" => ""}
+ %a.button.large.secondary{href: main_app.shop_path}
+ %i.ofn-i_008-caret-left
+ = t :orders_edit_continue
+ .columns.large-4.text-right
+ %a#checkout-link.button.large.primary{href: main_app.checkout_path}
+ = t :orders_edit_checkout
+ %i.ofn-i_007-caret-right
diff --git a/app/views/spree/orders/form/_update_buttons.html.haml b/app/views/spree/orders/form/_update_buttons.html.haml
new file mode 100644
index 0000000000..7a89ab25b4
--- /dev/null
+++ b/app/views/spree/orders/form/_update_buttons.html.haml
@@ -0,0 +1,24 @@
+.row
+ .columns.small-12.medium-3
+ - if current_order.andand.distributor == @order.distributor
+ - if current_order.line_items.present?
+ = link_to spree.cart_path, :class => "button expand" do
+ %i.ofn-i_008-caret-left
+ = t(:order_back_to_cart)
+ - else
+ = link_to main_app.shop_path, :class => "button expand" do
+ %i.ofn-i_008-caret-left
+ = t(:order_back_to_store)
+ - else
+
+ - if order.changes_allowed?
+ .columns.show-for-medium-up.medium-3
+ .columns.small-12.medium-3
+ = link_to spree.cancel_order_path(@order), method: :put, :class => "button secondary expand", "confirm-link-click" => t('orders_confirm_cancel') do
+ %i.ofn-i_009-close
+ = t(:cancel_order)
+ .columns.small-12.medium-3
+ = button_tag :class => 'button primary radius expand', :id => 'update-button', "ng-disabled" => 'update_order_form.$pristine' do
+ %i.ofn-i_051-check-big
+ %span{ ng: { show: 'update_order_form.$dirty' } }= t(:save_changes)
+ %span{ ng: { hide: 'update_order_form.$dirty' } }= t(:order_saved)
diff --git a/app/views/spree/orders/show.html.haml b/app/views/spree/orders/show.html.haml
index e4cb3b1980..24d058db49 100644
--- a/app/views/spree/orders/show.html.haml
+++ b/app/views/spree/orders/show.html.haml
@@ -10,8 +10,15 @@
.row
.columns.large-12.text-center
%h2
- = t :orders_show_number
- = " #" + @order.number
+ = t(:orders_show_order_number, number: @order.number)
+ - if @order.canceled?
+ %span.brick
+ = t(:orders_show_cancelled)
+ %i.ofn-i_009-close
+ - elsif @order.complete?
+ %span.turquoise
+ = t(:orders_show_confirmed)
+ %i.ofn-i_051-check-big
#order{"data-hook" => ""}
- if params.has_key? :checkout_complete
@@ -19,12 +26,5 @@
= render 'spree/shared/order_details', order: @order
- .row
- .columns.large-12
- = link_to t(:back_to_store), main_app.shop_path, :class => "button"
- - unless params.has_key? :checkout_complete
- - if try_spree_current_user && respond_to?(:spree_account_path)
- = link_to t(:my_account), spree_account_path, :class => "button"
-
= render partial: "shared/footer"
diff --git a/app/views/spree/products/_source.html.haml b/app/views/spree/products/_source.html.haml
index dfcd091d67..7751ba36ef 100644
--- a/app/views/spree/products/_source.html.haml
+++ b/app/views/spree/products/_source.html.haml
@@ -1,5 +1,5 @@
%div{:data-hook => "product_source"}
- %h6.product-section-title SUPPLIER
+ %h6.product-section-title= t(:supplier)
%table#product-source.table-display{:width => "100%"}
%tbody
- if @product.supplier
@@ -7,7 +7,7 @@
%td= link_to @product.supplier.name, [main_app, @product.supplier]
- if false
%br/
- %h6.product-section-title DISTRIBUTORS
+ %h6.product-section-title= t(:distributors)
%table#product-source.table-display{:width => "100%"}
%tbody
- order = current_order(false)
@@ -18,11 +18,11 @@
%td
%b= link_to(distributor.name, [main_app, distributor])
%td
- %b Current
+ %b= t(:current)
- elsif order.nil? || validator.can_change_to_distributor?(distributor)
%tr.even
%td= link_to distributor.name, [main_app, distributor]
- %td Available
+ %td= t(:available)
- else
%tr.even
%td= link_to distributor.name, [main_app, distributor]
diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml
index d0a7e99ddf..63e0f4450b 100644
--- a/app/views/spree/shared/_order_details.html.haml
+++ b/app/views/spree/shared/_order_details.html.haml
@@ -86,68 +86,14 @@
%br
.row
.columns.large-12
- %table#line-items{"data-hook" => "order_details"}
- %col{valign: "middle"}/
- %col{halign: "center", valign: "middle", width: "5%"}/
- %col{halign: "center", valign: "middle", width: "5%"}/
- %col{halign: "center", valign: "middle", width: "5%"}/
- %thead{"data-hook" => ""}
- %tr{"data-hook" => "order_details_line_items_headers"}
- %th= t(:item)
- %th.price= t(:price)
- %th.text-center.qty= t(:qty)
- %th.text-right.total
- %span= t(:total)
- %tbody{"data-hook" => ""}
- - order.line_items.each do |item|
- %tr{"data-hook" => "order_details_line_item_row"}
- %td(data-hook = "order_item_description")
+ - if order.changes_allowed?
+ .alert-box.order-summary{ "ofn-inline-alert" => true, "ng-show" => "visible" }
+ = t(:orders_changeable_orders_alert_html, oc_close: l(order.order_cycle.orders_close_at, format: "%b %d, %Y %H:%M"))
+ %a.close{ "ng-click" => "close()" } ×
- %div.item-thumb-image{"data-hook" => "order_item_image"}
- - if item.variant.images.length == 0
- = link_to mini_image(item.variant.product), item.variant.product
- - else
- = link_to image_tag(item.variant.images.first.attachment.url(:mini)), item.variant.product
-
-
- = render 'spree/shared/line_item_name', line_item: item
-
- %td.text-right.price{"data-hook" => "order_item_price"}
- %span= item.single_display_amount_with_adjustments.to_html
- %td.text-center{"data-hook" => "order_item_qty"}= item.quantity
- %td.text-right.total{"data-hook" => "order_item_total"}
- %span= item.display_amount_with_adjustments.to_html
-
- %tfoot
- #subtotal{"data-hook" => "order_details_subtotal"}
- %tr#subtotal-row.total
- %td.text-right{colspan: "3"}
- %strong
- = t :order_produce
- %td.text-right.total
- %span= display_checkout_subtotal(order)
-
- #order-charges{"data-hook" => "order_details_adjustments"}
- - checkout_adjustments_for(order, exclude: [:line_item]).reject{ |a| a.amount == 0 }.reverse_each do |adjustment|
- %tr.total
- %td.text-right{:colspan => "3"}
- %strong
- = adjustment.label
- %td.text-right.total
- %span= adjustment.display_amount.to_html
-
- #order-total{"data-hook" => "order_details_total"}
- %tr.total
- %td.text-right{colspan: "3"}
- %h5
- = t :order_total_price
- %td.text-right.total
- %h5#order_total= order.display_total.to_html
-
- - if order.total_tax > 0
- #tax{"data-hook" => "order_details_tax"}
- %tr#tax-row.total
- %td.text-right{colspan: "3"}
- = t :order_includes_tax
- %td.text-right.total
- %span= display_checkout_tax_total(order)
+ = form_for order, html: {id: 'update-order', name: 'update_order_form' } do |order_form|
+ - if order.changes_allowed?
+ = render 'spree/orders/form', order_form: order_form
+ -else
+ = render 'spree/orders/summary', order: order
+ = render 'spree/orders/form/update_buttons', order: order
diff --git a/app/views/spree/user_mailer/confirmation_instructions.text.erb b/app/views/spree/user_mailer/confirmation_instructions.text.erb
new file mode 100644
index 0000000000..fabf8936ba
--- /dev/null
+++ b/app/views/spree/user_mailer/confirmation_instructions.text.erb
@@ -0,0 +1,4 @@
+<%= t('.welcome', email: @email) %>
+
+<%= t('.text') %>
+<%= @confirmation_url %>
diff --git a/app/views/spree/users/_open_orders.html.haml b/app/views/spree/users/_open_orders.html.haml
new file mode 100644
index 0000000000..5f78738e34
--- /dev/null
+++ b/app/views/spree/users/_open_orders.html.haml
@@ -0,0 +1,23 @@
+.row
+ .small-12.columns
+ %table
+ %tr
+ %th.order1= t('.order')
+ %th.order2= t('.shop')
+ %th.order3.show-for-large-up= t('.changes_allowed_until')
+ %th.order4.show-for-large-up= t('.items')
+ %th.order5.text-right= t('.total')
+ %th.order6.text-right.show-for-large-up= t('.edit')
+ %th.order7.text-right= t('.cancel')
+ %tbody.transaction-group{"ng-repeat" => "order in Orders.changeable_orders", "ng-class-odd"=>"'odd'", "ng-class-even"=>"'even'"}
+ %tr.order-row
+ %td.order1
+ %a{"ng-href" => "{{::order.path}}", "ng-bind" => "::order.number"}
+ %td.order2{"ng-bind" => "::order.shop_name"}
+ %td.order3.show-for-large-up{"ng-bind" => "order.changes_allowed_until"}
+ %td.order4.show-for-large-up{"ng-bind" => "::order.item_count"}
+ %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","ng-bind" => "::order.total | localizeCurrency"}
+ %td.order6.text-right.show-for-large-up.brick
+ %a{"ng-href" => "{{::order.path}}" }= t('.edit')
+ %td.order7.text-right
+ = link_to t('.cancel'), "", method: :put, "ng-href" => "{{::order.cancel_path}}", "confirm-link-click" => t('orders_confirm_cancel')
diff --git a/app/views/spree/users/_skinny.html.haml b/app/views/spree/users/_skinny.html.haml
index 4894824296..70ba39d335 100644
--- a/app/views/spree/users/_skinny.html.haml
+++ b/app/views/spree/users/_skinny.html.haml
@@ -1,12 +1,10 @@
.row.active_table_row.skinny-head.margin-top{"ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open()}"}
.columns.small-2
- %span.margin-top
- %img.account-logo{"logo-fallback" => true, "ng-src" => "{{distributor.logo}}"}
- .columns.small-10.medium-5
- %span.margin-top
- %strong{"ng-bind" => "::distributor.name"}
- .columns.small-8.small-offset-2.medium-3.text-right
- %span.margin-top.distributor-balance{"ng-bind" => "::distributor.balance | formatBalance", "ng-class" => "{'credit' : distributor.balance < 0, 'debit' : distributor.balance > 0, 'paid' : distributor.balance == 0}" }
- .columns.small-2.medium-2.text-right
- %span.margin-top
+ %img.margin-top.account-logo{"logo-fallback" => true, "ng-src" => "{{distributor.logo}}"}
+ .columns.small-5
+ %h3.margin-top{"ng-bind" => "::distributor.name"}
+ .columns.small-4.text-right
+ %h3.margin-top.distributor-balance{"ng-bind" => "::distributor.balance | formatBalance", "ng-class" => "{'credit' : distributor.balance < 0, 'debit' : distributor.balance > 0, 'paid' : distributor.balance == 0}" }
+ .columns.small-1.text-right
+ %h3.margin-top
%i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
diff --git a/app/views/spree/users/show.html.haml b/app/views/spree/users/show.html.haml
index 0b4800f79e..8272cc9eeb 100644
--- a/app/views/spree/users/show.html.haml
+++ b/app/views/spree/users/show.html.haml
@@ -3,22 +3,26 @@
.row.pad-top
.small-12.columns.pad-top
- %h2= accurate_title
- .account-summary{"data-hook" => "account_summary"}
- = @user.email
- (#{link_to t(:edit), spree.edit_account_path})
- %h3= t(:my_orders)
+ %h2
+ = accurate_title
+ %span.account-summary{"data-hook" => "account_summary"}
+ = @user.email
+ (#{link_to t(:edit), spree.edit_account_path})
+
.orders{"ng-controller" => "OrdersCtrl", "ng-cloak" => true}
- .row
- .small-12.columns
- .active_table
- %distributor.active_table_node.row.animate-repeat{"ng-if" => "Orders.orders_by_distributor.length > 0", "ng-repeat" => "(key, distributor) in Orders.orders_by_distributor",
- "ng-controller" => "DistributorNodeCtrl",
- "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !distributor.active}",
- id: "{{distributor.hash}}"}
- .small-12.columns
- = render partial: "spree/users/skinny"
- = render partial: "spree/users/fat"
- .message{"ng-if" => "Orders.orders_by_distributor.length == 0", "ng-bind" => "::'you_have_no_orders_yet' | t"}
+ .my-open-orders{ ng: { show: 'Orders.changeable_orders.length > 0' } }
+ %h3= t(:open_orders)
+ = render 'open_orders'
+
+ .active_table
+ %h3.my-orders= t(:transaction_history)
+ %distributor.active_table_node.row.animate-repeat{"ng-if" => "Orders.orders_by_distributor.length > 0", "ng-repeat" => "(key, distributor) in Orders.orders_by_distributor",
+ "ng-controller" => "DistributorNodeCtrl",
+ "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !distributor.active}",
+ id: "{{distributor.hash}}"}
+ .small-12.columns
+ = render partial: "spree/users/skinny"
+ = render partial: "spree/users/fat"
+ .message{"ng-if" => "Orders.orders_by_distributor.length == 0", "ng-bind" => "::'you_have_no_orders_yet' | t"}
= render partial: "shared/footer"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a724c19864..0863fcef0f 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -80,6 +80,21 @@ en:
show_all: Show all
show_all_with_more: "Show All (%{num} More)"
cancel: Cancel
+ edit: Edit
+ distributors: Distributors
+ order_cycles: Order Cycles
+ enterprise_limit: Enterprise Limit
+ bulk_order_management: Bulk Order Management
+ enterprises: Enterprises
+ enterprise_groups: Groups
+ reports: Reports
+ variant_overrides: Inventory
+ more: More
+ spree_products: Spree Products
+ all: All
+ current: Current
+ available: Available
+ dashboard: Dashboard
admin:
# Common properties / models
@@ -107,6 +122,8 @@ en:
end_date: "End Date"
unsaved_changes: "You have unsaved changes"
form_invalid: "Form contains missing or invalid fields"
+ clear_filters: Clear Filters
+ clear: Clear
columns: Columns
actions: Actions
@@ -190,6 +207,7 @@ en:
edit: 'Edit'
update_address: 'Update Address'
confirm_delete: 'Sure to delete?'
+ search_by_email: "Search by email/code..."
cache_settings:
show:
@@ -299,6 +317,8 @@ en:
abn_placeholder: eg. 99 123 456 789
acn: ACN
acn_placeholder: eg. 123 456 789
+ display_invoice_logo: Display logo in invoices
+ invoice_text: Add customized text at the end of invoices
contact:
name: Name
name_placeholder: eg. Gustav Plum
@@ -371,17 +391,25 @@ en:
shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront or if it's visible to everybody."
shopfront_requires_login_false: "Public"
shopfront_requires_login_true: "Visible to registered customers only"
+ recommend_require_login: "We recommend to require users to login when orders can be changed."
allow_guest_orders: "Guest orders"
allow_guest_orders_tip: "Allow checkout as guest or require a registered user."
allow_guest_orders_false: "Require login to order"
allow_guest_orders_true: "Allow guest checkout"
+ allow_order_changes: "Change orders"
+ allow_order_changes_tip: "Allow customers to change their order as long the order cycle is open."
+ allow_order_changes_false: "Placed orders cannot be changed"
+ allow_order_changes_true: "Customers can remove items from an order of an open order cycle"
+ shopfront_message: Shopfront Message
shopfront_message_placeholder: >
An optional explanation for customers detailing how your shopfront works,
to be displayed above the product list on your shop page.
+ shopfront_closed_message: Shopfront Closed Message
shopfront_closed_message_placeholder: >
A message which provides a more detailed explanation about why your shop is
closed and/or when customers can expect it to open again. This is displayed
on your shop only when you have no active order cycles (ie. shop is closed).
+ shopfront_category_ordering: Shopfront Category Ordering
open_date: Open Date
close_date: Close Date
social:
@@ -523,12 +551,25 @@ en:
invoice_style2?: Use the alternative invoice model that includes total tax breakdown per rate and tax rate info per item (not yet suitable for countries displaying prices excluding tax)
enable_receipt_printing?: Show options for printing receipts using thermal printers in order dropdown?
+# Frontend views
+#
+# These keys are referenced relatively like `t('.message')` in
+# app/views/checkout/_already_ordered.html.haml.
+#
+ checkout:
+ already_ordered:
+ cart: "cart"
+ message_html: "You have an order for this order cycle already. Check the %{cart} to see the items you ordered before. You can also cancel items as long as the order cycle is open."
home:
hubs:
show_closed_shops: "Show closed shops"
hide_closed_shops: "Hide closed shops"
show_on_map: "Show all on the map"
shared:
+ menu:
+ cart:
+ checkout: "Checkout now"
+ already_ordered_products: "Already ordered in this order cycle"
register_call:
selling_on_ofn: "Interested in getting on the Open Food Network?"
register: "Register here"
@@ -604,6 +645,7 @@ en:
terms_of_service: "Terms of service"
on_demand: On demand
none: None
+ not_allowed: Not allowed
label_shops: "Shops"
label_map: "Map"
@@ -626,7 +668,6 @@ en:
items: "items"
cart_headline: "Your shopping cart"
total: "Total"
- checkout: "Checkout now"
cart_updating: "Updating cart..."
cart_empty: "Cart empty"
cart_edit: "Edit your cart"
@@ -635,15 +676,6 @@ en:
card_securitycode: "Security Code"
card_expiry_date: Expiry Date
- ofn_cart_headline: "Current cart for:"
- ofn_cart_distributor: "Distributor:"
- ofn_cart_oc: "Order cycle:"
- ofn_cart_from: "From:"
- ofn_cart_to: "To:"
- ofn_cart_product: "Product:"
- ofn_cart_quantitiy: "Quantity:"
- ofn_cart_send: "Buy me"
-
ie_warning_headline: "Your browser is out of date :-("
ie_warning_text: "For the best Open Food Network experience, we strongly recommend upgrading your browser:"
ie_warning_chrome: Download Chrome
@@ -743,6 +775,7 @@ en:
order_billing_address: Billing address
order_delivery_on: Delivery on
order_delivery_address: Delivery address
+ order_delivery_time: Delivery time
order_special_instructions: "Your notes:"
order_pickup_time: Ready for collection
order_pickup_instructions: Collection Instructions
@@ -751,6 +784,8 @@ en:
order_includes_tax: (includes tax)
order_payment_paypal_successful: Your payment via PayPal has been processed successfully.
order_hub_info: Hub Info
+ order_back_to_store: Back To Store
+ order_back_to_cart: Back To Cart
bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required."
@@ -860,6 +895,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using
hubs_distance: Closest to
hubs_distance_filter: "Show me shops near %{location}"
+ shop_changeable_orders_alert_html:
+ one: Your order with %{shop} / %{order} is open for review. You can make changes until %{oc_close}.
+ other: You have %{count} orders with %{shop} currently open for review. You can make changes until %{oc_close}.
+ orders_changeable_orders_alert_html: This order has been confirmed, but you can make changes until %{oc_close}.
+
products_clear_all: Clear all
products_showing: "Showing:"
products_with: with
@@ -1005,7 +1045,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
orders_edit_checkout: Checkout
orders_form_empty_cart: "Empty cart"
orders_form_subtotal: Produce subtotal
- orders_form_admin: Admin and handling
+ orders_form_admin: Admin & Handling
orders_form_total: Total
orders_oc_expired_headline: Orders have closed for this order cycle
orders_oc_expired_text: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders."
@@ -1015,7 +1055,18 @@ See the %{link} to find out more about %{sitename}'s features and to start using
orders_oc_expired_phone: "Phone:"
orders_show_title: Order Confirmation
orders_show_time: Order ready on
- orders_show_number: Order confirmation
+ orders_show_order_number: "Order #%{number}"
+ orders_show_cancelled: Cancelled
+ orders_show_confirmed: Confirmed
+ orders_your_order_has_been_cancelled: "Your order has been cancelled"
+ orders_could_not_cancel: "Sorry, the order could not be cancelled"
+ orders_cannot_remove_the_final_item: "Cannot remove the final item from an order, please cancel the order instead."
+ orders_bought_items_notice:
+ one: An additional item is already confirmed for this order cycle
+ other: "%{count} additional items already confirmed for this order cycle"
+ orders_bought_edit_button: Edit confirmed items
+ orders_bought_already_confirmed: "* already confirmed"
+ orders_confirm_cancel: Are you sure you want to cancel this order?
products_cart_distributor_choice: "Distributor for your order:"
products_cart_distributor_change: "Your distributor for this order will be changed to %{name} if you add this product to your cart."
@@ -1122,6 +1173,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
enterprise_long_desc: "Long Description"
enterprise_long_desc_placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words."
enterprise_long_desc_length: "%{num} characters / up to 600 recommended"
+ enterprise_limit: Enterprise Limit
enterprise_abn: "ABN"
enterprise_abn_placeholder: "eg. 99 123 456 789"
enterprise_acn: "ACN"
@@ -1262,6 +1314,7 @@ Please follow the instructions there to make your enterprise visible on the Open
calculator_values: "Calculator values"
flat_percent_per_item: "Flat Percent (per item)"
new_order_cycles: "New Order Cycles"
+ new_order_cycle: "New Order Cycle"
select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle"
edit_order_cycle: "Edit Order Cycle"
roles: "Roles"
@@ -1282,6 +1335,7 @@ Please follow the instructions there to make your enterprise visible on the Open
price: "Price"
on_hand: "On hand"
save_changes: "Save Changes"
+ order_saved: "Order Saved"
spree_admin_overview_enterprises_header: "My Enterprises"
spree_admin_overview_enterprises_footer: "MANAGE MY ENTERPRISES"
spree_admin_enterprises_hubs_name: "Name"
@@ -1542,6 +1596,10 @@ Please follow the instructions there to make your enterprise visible on the Open
now_out_of_stock: is now out of stock.
only_n_remainging: "now only has %{num} remaining."
+ producers:
+ signup:
+ start_free_profile: "Start with a free profile, and expand when you're ready!"
+
spree:
admin:
products:
@@ -1558,7 +1616,11 @@ Please follow the instructions there to make your enterprise visible on the Open
date_picker:
format: ! '%Y-%m-%d'
js_format: 'yy-mm-dd'
+ inventory: Inventory
zipcode: Postcode
+ orders:
+ bought:
+ item: "Already ordered in this order cycle"
shipment_states:
backorder: backorder
partial: partial
@@ -1596,6 +1658,23 @@ Please follow the instructions there to make your enterprise visible on the Open
orders:
invoice:
tax_invoice: "TAX INVOICE: "
+ payment_states:
+ balance_due: balance due
+ completed: completed
+ checkout: checkout
+ credit_owed: credit owed
+ failed: failed
+ paid: paid
+ pending: pending
+ processing: processing
+ void: void
+ invalid: invalid
+ shipment_states:
+ backorder: backorder
+ partial: partial
+ pending: pending
+ ready: ready
+ shipped: shipped
user_mailer:
reset_password_instructions:
request_sent_text: |
@@ -1606,3 +1685,19 @@ Please follow the instructions there to make your enterprise visible on the Open
issue_text: |
If the above URL does not work try copying and pasting it into your browser.
If you continue to have problems please feel free to contact us.
+ weight: Weight (per kg)
+ zipcode: Postcode
+ users:
+ show:
+ open_orders: Open Orders
+ transaction_history: Transaction History
+ open_orders:
+ order: Order
+ shop: Shop
+ changes_allowed_until: Changes Allowed Until
+ items: Items
+ total: Total
+ edit: Edit
+ cancel: Cancel
+ closed: Closed
+ until: Until
diff --git a/config/routes.rb b/config/routes.rb
index 90700d89b0..17986e2081 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -42,6 +42,8 @@ Openfoodnetwork::Application.routes.draw do
end
end
+ resources :line_items, only: [:index, :destroy]
+
resources :groups, only: [:index, :show] do
collection do
get :signup
@@ -262,6 +264,7 @@ Spree::Core::Engine.routes.prepend do
resources :orders do
get :clear, :on => :collection
get :order_cycle_expired, :on => :collection
+ put :cancel, on: :member
end
end
diff --git a/db/migrate/20161012022142_add_allow_order_changes_to_enterprise.rb b/db/migrate/20161012022142_add_allow_order_changes_to_enterprise.rb
new file mode 100644
index 0000000000..887ce44058
--- /dev/null
+++ b/db/migrate/20161012022142_add_allow_order_changes_to_enterprise.rb
@@ -0,0 +1,5 @@
+class AddAllowOrderChangesToEnterprise < ActiveRecord::Migration
+ def change
+ add_column :enterprises, :allow_order_changes, :boolean, default: false, null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 65e413e92b..75c43b1d72 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -251,6 +251,7 @@ ActiveRecord::Schema.define(:version => 20161215230219) do
t.boolean "allow_guest_orders", :default => true, :null => false
t.text "invoice_text"
t.boolean "display_invoice_logo", :default => false
+ t.boolean "allow_order_changes", :default => false, :null => false
end
add_index "enterprises", ["address_id"], :name => "index_enterprises_on_address_id"
diff --git a/lib/open_food_network/scope_variant_to_hub.rb b/lib/open_food_network/scope_variant_to_hub.rb
index dadcfbd9a8..e70e7966e1 100644
--- a/lib/open_food_network/scope_variant_to_hub.rb
+++ b/lib/open_food_network/scope_variant_to_hub.rb
@@ -47,6 +47,14 @@ module OpenFoodNetwork
end
end
+ def increment!(attribute, by=1)
+ if attribute == :count_on_hand && @variant_override.andand.stock_overridden?
+ @variant_override.increment_stock! by
+ else
+ super
+ end
+ end
+
def sku
@variant_override.andand.sku || super
end
diff --git a/spec/controllers/line_items_controller_spec.rb b/spec/controllers/line_items_controller_spec.rb
new file mode 100644
index 0000000000..ba78537fd7
--- /dev/null
+++ b/spec/controllers/line_items_controller_spec.rb
@@ -0,0 +1,165 @@
+require 'spec_helper'
+
+describe LineItemsController do
+ let(:user) { create(:user) }
+ let(:distributor) { create(:distributor_enterprise) }
+ let(:order_cycle) { create(:simple_order_cycle) }
+
+ context "listing bought items" do
+ let!(:completed_order) do
+ order = create(:completed_order_with_totals, user: user, distributor: distributor, order_cycle: order_cycle)
+ while !order.completed? do break unless order.next! end
+ order
+ end
+
+ before do
+ controller.stub spree_current_user: user
+ controller.stub current_order_cycle: order_cycle
+ controller.stub current_distributor: distributor
+ end
+
+ it "lists items bought by the user from the same shop in the same order_cycle" do
+ get :index, { format: :json }
+ expect(response.status).to eq 200
+ json_response = JSON.parse(response.body)
+ expect(json_response.length).to eq completed_order.line_items(:reload).count
+ expect(json_response[0]['id']).to eq completed_order.line_items.first.id
+ end
+ end
+
+ describe "destroying a line item on a completed order" do
+ let(:item) do
+ order = create(:completed_order_with_totals)
+ item = create(:line_item, order: order)
+ while !order.completed? do break unless order.next! end
+ item
+ end
+
+ before { controller.stub spree_current_user: item.order.user }
+
+ context "without a line item id" do
+ it "fails and raises an error" do
+ expect { delete :destroy }.to raise_error
+ end
+ end
+
+ context "with a line item id" do
+ let(:params) { { format: :json, id: item } }
+
+ context "without a user" do
+ it "denies deletion" do
+ delete :destroy, params
+ expect(response.status).to eq 403
+ expect { item.reload }.to_not raise_error
+ end
+ end
+
+ context "where the item's order is associated with the current user" do
+ before { item.order.update_attributes(user_id: user.id) }
+
+ context "without an order cycle" do
+ it "denies deletion" do
+ delete :destroy, params
+ expect(response.status).to eq 403
+ expect { item.reload }.to_not raise_error
+ end
+ end
+
+ context "with an order cycle" do
+ before { item.order.update_attributes(order_cycle_id: order_cycle.id) }
+
+ context "without a distributor" do
+ it "denies deletion" do
+ delete :destroy, params
+ expect(response.status).to eq 403
+ expect { item.reload }.to_not raise_error
+ end
+ end
+
+ context "where the item's order has a distributor" do
+ before { item.order.update_attributes(distributor_id: distributor.id) }
+ context "where changes are not allowed" do
+ it "denies deletion" do
+ delete :destroy, params
+ expect(response.status).to eq 403
+ expect { item.reload }.to_not raise_error
+ end
+ end
+
+ context "where changes are allowed" do
+ before { distributor.update_attributes(allow_order_changes: true) }
+
+ it "deletes the line item" do
+ delete :destroy, params
+ expect(response.status).to eq 204
+ expect { item.reload }.to raise_error
+ end
+ end
+ end
+ end
+ end
+ end
+
+ context "where shipping and payment fees apply" do
+ let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true, allow_order_changes: true) }
+ let(:shipping_fee) { 3 }
+ let(:payment_fee) { 5 }
+ let(:order) { create(:completed_order_with_fees, distributor: distributor, shipping_fee: shipping_fee, payment_fee: payment_fee) }
+
+ before do
+ Spree::Config.shipment_inc_vat = true
+ Spree::Config.shipping_tax_rate = 0.25
+ end
+
+ it "updates the fees" do
+ # Sanity check fees
+ item_num = order.line_items.length
+ initial_fees = item_num * (shipping_fee + payment_fee)
+ expect(order.adjustment_total).to eq initial_fees
+ expect(order.shipment.adjustment.included_tax).to eq 1.2
+
+ # Delete the item
+ item = order.line_items.first
+ controller.stub spree_current_user: order.user
+ request = { format: :json, id: item }
+ delete :destroy, request
+ expect(response.status).to eq 204
+
+ # Check the fees again
+ order.reload
+ order.shipment.reload
+ expect(order.adjustment_total).to eq initial_fees - shipping_fee - payment_fee
+ expect(order.shipment.adjustment.amount).to eq shipping_fee
+ expect(order.payment.adjustment.amount).to eq payment_fee
+ expect(order.shipment.adjustment.included_tax).to eq 0.6
+ end
+ end
+
+ context "where enterprise fees apply" do
+ let(:user) { create(:user) }
+ let(:variant) { create(:variant) }
+ let(:distributor) { create(:distributor_enterprise, allow_order_changes: true) }
+ let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) }
+ let(:enterprise_fee) { create(:enterprise_fee, calculator: build(:calculator_per_item) ) }
+ let!(:exchange) { create(:exchange, incoming: true, sender: variant.product.supplier, receiver: order_cycle.coordinator, variants: [variant], enterprise_fees: [enterprise_fee]) }
+ let!(:order) do
+ order = create(:completed_order_with_totals, user: user, distributor: distributor, order_cycle: order_cycle)
+ order.reload.line_items.first.update_attributes(variant_id: variant.id)
+ while !order.completed? do break unless order.next! end
+ order.update_distribution_charge!
+ order
+ end
+ let(:params) { { format: :json, id: order.line_items.first } }
+
+ it "updates the fees" do
+ expect(order.reload.adjustment_total).to eq enterprise_fee.calculator.preferred_amount
+
+ controller.stub spree_current_user: user
+ delete :destroy, params
+ expect(response.status).to eq 204
+
+ expect(order.reload.adjustment_total).to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb
index 74e9efaaf9..4ec0c63eed 100644
--- a/spec/controllers/spree/orders_controller_spec.rb
+++ b/spec/controllers/spree/orders_controller_spec.rb
@@ -191,6 +191,216 @@ describe Spree::OrdersController do
end
end
+ describe "removing items from a completed order" do
+ context "with shipping and transaction fees" do
+ let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true, allow_order_changes: true) }
+ let(:order) { create(:completed_order_with_fees, distributor: distributor, shipping_fee: shipping_fee, payment_fee: payment_fee) }
+ let(:line_item1) { order.line_items.first }
+ let(:line_item2) { order.line_items.second }
+ let(:shipping_fee) { 3 }
+ let(:payment_fee) { 5 }
+ let(:item_num) { order.line_items.length }
+ let(:expected_fees) { item_num * (shipping_fee + payment_fee) }
+ let(:params) { { order: { line_items_attributes: {
+ "0" => {id: line_item1.id, quantity: 1},
+ "1" => {id: line_item2.id, quantity: 0}
+ } } } }
+
+ before do
+ Spree::Config.shipment_inc_vat = true
+ Spree::Config.shipping_tax_rate = 0.25
+
+ # Sanity check the fees
+ expect(order.adjustments.length).to eq 2
+ expect(item_num).to eq 2
+ expect(order.adjustment_total).to eq expected_fees
+ expect(order.shipment.adjustment.included_tax).to eq 1.2
+
+ allow(subject).to receive(:spree_current_user) { order.user }
+ allow(subject).to receive(:order_to_update) { order }
+ end
+
+ it "updates the fees" do
+ # Setting quantity of an item to zero
+ spree_post :update, params
+
+ # Check if fees got updated
+ order.reload
+ expect(order.line_items.count).to eq 1
+ expect(order.adjustment_total).to eq expected_fees - shipping_fee - payment_fee
+ expect(order.shipment.adjustment.included_tax).to eq 0.6
+ end
+ end
+
+ context "with enterprise fees" do
+ let(:user) { create(:user) }
+ let(:variant) { create(:variant) }
+ let(:distributor) { create(:distributor_enterprise, allow_order_changes: true) }
+ let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) }
+ let(:enterprise_fee) { create(:enterprise_fee, calculator: build(:calculator_per_item) ) }
+ let!(:exchange) { create(:exchange, incoming: true, sender: variant.product.supplier, receiver: order_cycle.coordinator, variants: [variant], enterprise_fees: [enterprise_fee]) }
+ let!(:order) do
+ order = create(:completed_order_with_totals, user: user, distributor: distributor, order_cycle: order_cycle)
+ order.reload.line_items.first.update_attributes(variant_id: variant.id)
+ while !order.completed? do break unless order.next! end
+ order.update_distribution_charge!
+ order
+ end
+ let(:params) { { order: { line_items_attributes: {
+ "0" => { id: order.line_items.first.id, quantity: 2 }
+ } } } }
+
+ before do
+ allow(subject).to receive(:spree_current_user) { order.user }
+ allow(subject).to receive(:order_to_update) { order }
+ end
+
+ it "updates the fees" do
+ expect(order.reload.adjustment_total).to eq enterprise_fee.calculator.preferred_amount
+
+ controller.stub spree_current_user: user
+ spree_post :update, params
+
+ expect(order.reload.adjustment_total).to eq enterprise_fee.calculator.preferred_amount * 2
+ end
+ end
+ end
+
+ describe "removing items from a completed order" do
+ let(:order) { create(:completed_order_with_totals) }
+ let!(:line_item) { order.reload.line_items.first }
+ let(:params) { { order: {} } }
+
+ before { allow(subject).to receive(:order_to_update) { order } }
+
+ context "when more than one item remains" do
+ before do
+ params[:order][:line_items_attributes] = { "0" => {quantity: "1", id: line_item.id} }
+ end
+
+ it "removes the item" do
+ spree_post :update, params
+ expect(flash[:error]).to be nil
+ expect(response).to redirect_to spree.order_path(order)
+ expect(order.reload.line_items.count).to eq 1
+ end
+ end
+
+ context "when only one item remains" do
+ before do
+ params[:order][:line_items_attributes] = { "0" => {quantity: "0", id: line_item.id} }
+ end
+
+ it "does not remove the item, flash suggests cancellation" do
+ spree_post :update, params
+ expect(flash[:error]).to eq I18n.t(:orders_cannot_remove_the_final_item)
+ expect(response).to redirect_to spree.order_path(order)
+ expect(order.reload.line_items.count).to eq 1
+ end
+ end
+ end
+
+ describe "#order_to_update" do
+ let!(:current_order) { double(:current_order) }
+ let(:params) { { } }
+
+ before do
+ allow(controller).to receive(:current_order) { current_order }
+ allow(controller).to receive(:params) { params }
+ end
+
+ context "when no order id is given in params" do
+ it "returns the current_order" do
+ expect(controller.send(:order_to_update)).to eq current_order
+ end
+ end
+
+ context "when an order_id is given in params" do
+ before do
+ params.merge!({id: order.number})
+ end
+
+ context "and the order is not complete" do
+ let!(:order) { create(:order) }
+
+ it "returns the current_order" do
+ expect(controller.send(:order_to_update)).to eq current_order
+ end
+ end
+
+ context "and the order is complete" do
+ let!(:order) { create(:completed_order_with_totals) }
+
+ context "and the user doesn't have permisson to 'update' the order" do
+ before { allow(controller).to receive(:can?).with(:update, order) { false } }
+
+ it "returns the current_order" do
+ expect(controller.send(:order_to_update)).to eq current_order
+ end
+ end
+
+ context "and the user has permission to 'update' the order" do
+ before { allow(controller).to receive(:can?).with(:update, order) { true } }
+
+ context "and the order is not editable" do
+
+ it "returns the current_order" do
+ expect(controller.send(:order_to_update)).to eq current_order
+ end
+ end
+
+ context "and the order is editable" do
+ let(:order_cycle) { create(:simple_order_cycle) }
+ let(:distributor) { create(:enterprise, allow_order_changes: true) }
+
+ before do
+ order.update_attributes(order_cycle_id: order_cycle.id, distributor_id: distributor.id)
+ end
+
+ it "returns the order" do
+ expect(controller.send(:order_to_update)).to eq order
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe "cancelling an order" do
+ let(:user) { create(:user) }
+ let(:order) { create(:order, user: user) }
+ let(:params) { { id: order.number } }
+
+ context "when the user does not have permission to cancel the order" do
+ it "responds with unauthorized" do
+ spree_put :cancel, params
+ expect(response).to render_template 'shared/unauthorized'
+ end
+ end
+
+ context "when the user has permission to cancel the order" do
+ before { allow(controller).to receive(:spree_current_user) { user } }
+
+ context "when the order is not yet complete" do
+ it "responds with forbidden" do
+ spree_put :cancel, params
+ expect(response.status).to redirect_to spree.order_path(order)
+ expect(flash[:error]).to eq I18n.t(:orders_could_not_cancel)
+ end
+ end
+
+ context "when the order is complete" do
+ let(:order) { create(:completed_order_with_totals, user: user) }
+
+ it "responds with success" do
+ spree_put :cancel, params
+ expect(response.status).to redirect_to spree.order_path(order)
+ expect(flash[:success]).to eq I18n.t(:orders_your_order_has_been_cancelled)
+ end
+ end
+ end
+ end
+
private
diff --git a/spec/factories.rb b/spec/factories.rb
index d4193c11aa..5b4df5cf38 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -1,6 +1,20 @@
require 'ffaker'
require 'spree/core/testing_support/factories'
+# http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md
+#
+# The spree_core gem defines factories in several files. For example:
+#
+# - lib/spree/core/testing_support/factories/calculator_factory.rb
+# * calculator
+# * no_amount_calculator
+#
+# - lib/spree/core/testing_support/factories/order_factory.rb
+# * order
+# * order_with_totals
+# * order_with_inventory_unit_shipped
+# * completed_order_with_totals
+#
FactoryGirl.define do
factory :classification, class: Spree::Classification do
end
@@ -171,6 +185,10 @@ FactoryGirl.define do
end
sequence(:calculator_amount)
+ factory :calculator_per_item, class: Spree::Calculator::PerItem do
+ preferred_amount { generate(:calculator_amount) }
+ end
+
factory :enterprise_fee, :class => EnterpriseFee do
ignore { amount nil }
@@ -178,7 +196,7 @@ FactoryGirl.define do
sequence(:fee_type) { |n| EnterpriseFee::FEE_TYPES[n % EnterpriseFee::FEE_TYPES.count] }
enterprise { Enterprise.first || FactoryGirl.create(:supplier_enterprise) }
- calculator { Spree::Calculator::PerItem.new(preferred_amount: amount || generate(:calculator_amount)) }
+ calculator { build(:calculator_per_item, preferred_amount: amount) }
after(:create) { |ef| ef.calculator.save! }
end
@@ -237,6 +255,27 @@ FactoryGirl.define do
end
end
+ factory :completed_order_with_fees, parent: :order_with_totals_and_distribution do
+ ignore do
+ shipping_fee 3
+ payment_fee 5
+ end
+
+ shipping_method do
+ shipping_calculator = build(:calculator_per_item, preferred_amount: shipping_fee)
+ create(:shipping_method, calculator: shipping_calculator, require_ship_address: false, distributors: [distributor])
+ end
+
+ after(:create) do |order, evaluator|
+ create(:line_item, order: order)
+ order.create_shipment!
+ payment_calculator = build(:calculator_per_item, preferred_amount: evaluator.payment_fee)
+ payment_method = create(:payment_method, calculator: payment_calculator)
+ create(:payment, order: order, amount: order.total, payment_method: payment_method, state: 'checkout')
+ while !order.completed? do break unless order.next! end
+ end
+ end
+
factory :zone_with_member, :parent => :zone do
default_tax true
diff --git a/spec/features/consumer/account_spec.rb b/spec/features/consumer/account_spec.rb
index 58d4e1acc6..d5c7236b72 100644
--- a/spec/features/consumer/account_spec.rb
+++ b/spec/features/consumer/account_spec.rb
@@ -7,57 +7,79 @@ feature %q{
}, js: true do
include UIComponentHelper
include AuthenticationWorkflow
- let!(:user) { create(:user)}
- let!(:user2) {create(:user)}
+
+ let(:user) { create(:user)}
let!(:distributor1) { create(:distributor_enterprise) }
let!(:distributor2) { create(:distributor_enterprise) }
let!(:distributor_credit) { create(:distributor_enterprise) }
let!(:distributor_without_orders) { create(:distributor_enterprise) }
let!(:accounts_distributor) {create :distributor_enterprise}
let!(:order_account_invoice) { create(:order, distributor: accounts_distributor, state: 'complete', user: user) }
- let!(:d1o1) { create(:completed_order_with_totals, distributor_id: distributor1.id, user_id: user.id, total: 10000)}
- let!(:d1o2) { create(:order_without_full_payment, distributor_id: distributor1.id, user_id: user.id, total: 5000)}
- let!(:d2o1) { create(:completed_order_with_totals, distributor_id: distributor2.id, user_id: user.id)}
- let!(:credit_order) { create(:order_with_credit_payment, distributor_id: distributor_credit.id, user_id: user.id)}
-# let!(:credit_payment) { create(:payment, amount: 12000.00, order_id: credit_order.id)}
-
- before do
- Spree::Config.accounts_distributor_id = accounts_distributor.id
- credit_order.update!
- login_as user
- visit "/account"
- end
-
- it "shows all hubs that have been ordered from with balance or credit" do
- # Single test to avoid re-rendering page
- expect(page).to have_content distributor1.name
- expect(page).to have_content distributor2.name
- expect(page).not_to have_content distributor_without_orders.name
- # Exclude the special Accounts & Billing distributor
- expect(page).not_to have_content accounts_distributor.name
- expect(page).to have_content distributor1.name + " " + "Balance due"
- expect(page).to have_content distributor_credit.name + " Credit"
- end
-
-
- it "reveals table of orders for distributors when clicked" do
- expand_active_table_node distributor1.name
- expect(page).to have_link "Order " + d1o1.number, href:"/orders/#{d1o1.number}"
-
- expand_active_table_node distributor2.name
- expect(page).not_to have_content "Order " + d1o1.number.to_s
- end
-
- context "for a user without orders" do
+ context "as a logged in user" do
before do
- login_as user2
- visit "/account"
+ Spree::Config.accounts_distributor_id = accounts_distributor.id
+ login_as user
end
- it "displays an appropriate message" do
- expect(page).to have_content {t :you_have_no_orders_yet}
+ context "with completed orders" do
+ let(:order_cycle) { create(:simple_order_cycle) }
+ let!(:d1o1) { create(:completed_order_with_totals, distributor: distributor1, user: user, total: 10000, order_cycle: order_cycle)}
+ let!(:d1o2) { create(:order_without_full_payment, distributor: distributor1, user: user, total: 5000, order_cycle: order_cycle)}
+ let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user: user)}
+ let!(:credit_order) { create(:order_with_credit_payment, distributor: distributor_credit, user: user)}
+
+ before do
+ credit_order.update!
+ end
+
+ it "shows all hubs that have been ordered from with balance or credit" do
+ # Single test to avoid re-rendering page
+ visit "/account"
+
+ # No distributors allow changes to orders
+ expect(page).to_not have_content I18n.t('spree.users.show.open_orders')
+
+ # It shows all hubs that have been ordered from with balance or credit
+ expect(page).to have_content distributor1.name
+ expect(page).to have_content distributor2.name
+ expect(page).not_to have_content distributor_without_orders.name
+
+ # Exclude the special Accounts & Billing distributor
+ expect(page).not_to have_content accounts_distributor.name
+ expect(page).to have_content distributor1.name + " " + "Balance due"
+ expect(page).to have_content distributor_credit.name + " Credit"
+
+ # It reveals table of orders for distributors when clicked
+ expand_active_table_node distributor1.name
+ expect(page).to have_link "Order " + d1o1.number, href:"/orders/#{d1o1.number}"
+
+ expand_active_table_node distributor2.name
+ expect(page).not_to have_content "Order " + d1o1.number.to_s
+ end
+
+ context "when there is at least one changeable order" do
+ before do
+ distributor1.update_attributes(allow_order_changes: true)
+ end
+
+ it "shows such orders in a section labelled 'Open Orders'" do
+ visit '/account'
+ expect(page).to have_content I18n.t('spree.users.show.open_orders')
+
+ expect(page).to have_link d1o1.number, href: spree.order_path(d1o1)
+ expect(page).to have_link d1o2.number, href: spree.order_path(d1o2)
+ expect(page).to have_link I18n.t('spree.users.open_orders.cancel'), href: spree.cancel_order_path(d1o1)
+ expect(page).to have_link I18n.t('spree.users.open_orders.cancel'), href: spree.cancel_order_path(d1o2)
+ end
+ end
+ end
+
+ context "without any completed orders" do
+ it "displays an appropriate message" do
+ visit "/account"
+ expect(page).to have_content {t :you_have_no_orders_yet}
+ end
end
end
-
end
diff --git a/spec/features/consumer/shopping/cart_spec.rb b/spec/features/consumer/shopping/cart_spec.rb
index cdf3e5a031..99c0f75450 100644
--- a/spec/features/consumer/shopping/cart_spec.rb
+++ b/spec/features/consumer/shopping/cart_spec.rb
@@ -84,5 +84,50 @@ feature "full-page cart", js: true do
page.should have_content "Insufficient stock available, only 2 remaining"
end
end
+
+ context "when ordered in the same order cycle" do
+ let(:address) { create(:address) }
+ let(:user) { create(:user, bill_address: address, ship_address: address) }
+ let!(:prev_order1) { create(:completed_order_with_totals, order_cycle: order_cycle, distributor: distributor, user: user) }
+ let!(:prev_order2) { create(:completed_order_with_totals, order_cycle: order_cycle, distributor: distributor, user: user) }
+
+ before do
+ order.user = user
+ order.save
+ order.distributor.allow_order_changes = true
+ order.distributor.save
+ add_product_to_cart order, product_tax
+ quick_login_as user
+ visit spree.cart_path
+ end
+
+ it "shows already ordered line items" do
+ item1 = prev_order1.line_items.first
+ item2 = prev_order2.line_items.first
+
+ expect(page).to_not have_content item1.variant.name
+ expect(page).to_not have_content item2.variant.name
+
+ expect(page).to have_link I18n.t(:orders_bought_edit_button), href: spree.account_path
+ find("td.toggle-bought").click
+
+ expect(page).to have_content item1.variant.name
+ expect(page).to have_content item2.variant.name
+ page.find(".line-item-#{item1.id} td.bought-item-delete a").click
+ expect(page).to have_no_content item1.variant.name
+ expect(page).to have_content item2.variant.name
+
+ # open the dropdown cart and check there as well
+ find('#cart').click
+ expect(page).to have_no_content item1.variant.name
+ expect(page).to have_content item2.variant.name
+
+ visit spree.cart_path
+
+ find("td.toggle-bought").click
+ expect(page).to have_no_content item1.variant.name
+ expect(page).to have_content item2.variant.name
+ end
+ end
end
end
diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb
index e10a6e0da3..220fdcb89b 100644
--- a/spec/features/consumer/shopping/checkout_spec.rb
+++ b/spec/features/consumer/shopping/checkout_spec.rb
@@ -113,6 +113,24 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
user.reload.bill_address.address1.should eq '123 Your Head'
user.reload.ship_address.address1.should eq '123 Your Head'
end
+
+ it "it doesn't tell about previous orders" do
+ expect(page).to_not have_content("You have an order for this order cycle already.")
+ end
+
+ context "with previous orders" do
+ let!(:prev_order) { create(:completed_order_with_totals, order_cycle: order_cycle, distributor: distributor, user: order.user) }
+
+ before do
+ order.distributor.allow_order_changes = true
+ order.distributor.save
+ end
+
+ it "informs about previous orders" do
+ visit checkout_path
+ expect(page).to have_content("You have an order for this order cycle already.")
+ end
+ end
end
context "on the checkout page" do
@@ -402,7 +420,7 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do
o.save!
end
- it "checks out successfully" do
+ it "checks out successfully", retry: 3 do
visit checkout_path
checkout_as_guest
choose sm2.name
diff --git a/spec/features/consumer/shopping/orders_spec.rb b/spec/features/consumer/shopping/orders_spec.rb
new file mode 100644
index 0000000000..379f1b5197
--- /dev/null
+++ b/spec/features/consumer/shopping/orders_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+feature "Order Management", js: true do
+ include AuthenticationWorkflow
+
+ describe "editing a completed order" do
+ let(:address) { create(:address) }
+ let(:user) { create(:user, bill_address: address, ship_address: address) }
+ let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) }
+ let(:order_cycle) { create(:order_cycle) }
+ let(:shipping_method) { distributor.shipping_methods.first }
+ let(:order) { create(:completed_order_with_totals, order_cycle: order_cycle, distributor: distributor, user: user, bill_address: address, ship_address: address) }
+ let!(:item1) { order.reload.line_items.first }
+ let!(:item2) { create(:line_item, order: order) }
+ let!(:item3) { create(:line_item, order: order) }
+
+ before do
+ shipping_method.calculator.update_attributes(preferred_amount: 5.0)
+ order.update_attributes(shipping_method_id: shipping_method.id)
+ order.reload.save
+ quick_login_as user
+ end
+
+ context "when the distributor doesn't allow changes to be made to orders" do
+ before do
+ order.distributor.update_attributes(allow_order_changes: false)
+ end
+
+ it "doesn't show form elements for editing the order" do
+ visit spree.order_path(order)
+ expect(find("tr.variant-#{item1.variant.id}")).to have_content item1.product.name
+ expect(find("tr.variant-#{item2.variant.id}")).to have_content item2.product.name
+ expect(find("tr.variant-#{item3.variant.id}")).to have_content item3.product.name
+ expect(page).to_not have_button I18n.t(:save_changes)
+ end
+ end
+
+ context "when the distributor allows changes to be made to orders" do
+ before do
+ order.distributor.update_attributes(allow_order_changes: true)
+ end
+
+ it "allows quantity to be changed, items to be removed and the order to be cancelled" do
+ visit spree.order_path(order)
+
+ expect(page).to have_button I18n.t(:order_saved), disabled: true
+ expect(page).to_not have_button I18n.t(:save_changes)
+
+ # Changing the quantity of an item
+ within "tr.variant-#{item1.variant.id}" do
+ expect(page).to have_content item1.product.name
+ expect(page).to have_field 'order_line_items_attributes_0_quantity'
+ fill_in 'order_line_items_attributes_0_quantity', with: 2
+ end
+
+ expect(page).to have_button I18n.t(:save_changes)
+
+ expect(find("tr.variant-#{item2.variant.id}")).to have_content item2.product.name
+ expect(find("tr.variant-#{item3.variant.id}")).to have_content item3.product.name
+ expect(find("tr.order-adjustment")).to have_content "Shipping"
+ expect(find("tr.order-adjustment")).to have_content "$5.00"
+
+ click_button I18n.t(:save_changes)
+
+ expect(find(".order-total.grand-total")).to have_content "$45.00"
+ expect(item1.reload.quantity).to eq 2
+
+ # Deleting an item
+ within "tr.variant-#{item2.variant.id}" do
+ click_link "delete_line_item_#{item2.id}"
+ end
+
+ expect(find(".order-total.grand-total")).to have_content "$35.00"
+ expect(Spree::LineItem.find_by_id(item2.id)).to be nil
+
+ # Cancelling the order
+ click_link(I18n.t(:cancel_order))
+ expect(page).to have_content I18n.t(:orders_show_cancelled)
+ expect(order.reload).to be_canceled
+ end
+ end
+ end
+end
diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb
index 4331c92fa3..2445bc6ad0 100644
--- a/spec/features/consumer/shopping/shopping_spec.rb
+++ b/spec/features/consumer/shopping/shopping_spec.rb
@@ -137,6 +137,32 @@ feature "As a consumer I want to shop with a distributor", js: true do
end
end
end
+
+ context "when logged in" do
+ let!(:prev_order) { create(:completed_order_with_totals, order_cycle: oc1, distributor: distributor, user: order.user) }
+
+ before do
+ distributor.allow_order_changes = true
+ distributor.save
+ quick_login_as order.user
+ visit shop_path
+ end
+
+ it "shows previous orders if order cycle was selected already" do
+ select "frogs", from: "order_cycle_id"
+ expect(page).to have_content "Next order closing in 2 days"
+ visit shop_path
+ find("#cart").click
+ expect(page).to have_text(I18n.t("shared.menu.cart.already_ordered_products"))
+ end
+
+ it "shows previous orders after selecting an order cycle" do
+ select "frogs", from: "order_cycle_id"
+ expect(page).to have_content "Next order closing in 2 days"
+ find("#cart").click
+ expect(page).to have_text(I18n.t("shared.menu.cart.already_ordered_products"))
+ end
+ end
end
end
diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb
index ca2c8260cf..8715e1ef5c 100644
--- a/spec/models/order_cycle_spec.rb
+++ b/spec/models/order_cycle_spec.rb
@@ -505,4 +505,22 @@ describe OrderCycle do
OrderCycle.earliest_closing_times[e2.id].should == time2
end
end
+
+ describe "finding all line items sold by to a user by a given shop" do
+ let(:shop) { create(:enterprise) }
+ let(:user) { create(:user) }
+ let(:oc) { create(:order_cycle) }
+ let!(:order1) { create(:completed_order_with_totals, distributor: shop, user: user, order_cycle: oc) }
+ let!(:order2) { create(:completed_order_with_totals, distributor: create(:enterprise), user: user, order_cycle: oc) }
+ let!(:order3) { create(:completed_order_with_totals, distributor: shop, user: create(:user), order_cycle: oc) }
+ let!(:order4) { create(:completed_order_with_totals, distributor: shop, user: user, order_cycle: create(:order_cycle)) }
+ let!(:order5) { create(:completed_order_with_totals, distributor: shop, user: user, order_cycle: oc) }
+
+ before { order5.cancel }
+
+ it "only returns items from non-cancelled orders in the OC, placed by the user at the shop" do
+ items = oc.items_bought_by_user(user, shop)
+ expect(items).to eq order1.reload.line_items
+ end
+ end
end
diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb
index 111108f7b4..d5ab83b59c 100644
--- a/spec/models/spree/line_item_spec.rb
+++ b/spec/models/spree/line_item_spec.rb
@@ -77,6 +77,56 @@ module Spree
end
end
+ describe "tracking stock when quantity is changed" do
+ context "when the order is already complete" do
+ let(:shop) { create(:distributor_enterprise)}
+ let(:order) { create(:completed_order_with_totals, distributor: shop) }
+ let!(:line_item) { order.reload.line_items.first }
+ let!(:variant) { line_item.variant }
+
+ context "when a variant override applies" do
+ let!(:vo) { create(:variant_override, hub: shop, variant: variant, count_on_hand: 3 ) }
+
+ it "draws stock from the variant override" do
+ expect(vo.reload.count_on_hand).to eq 3
+ expect{line_item.increment!(:quantity)}.to_not change{Spree::Variant.find(variant.id).on_hand}
+ expect(vo.reload.count_on_hand).to eq 2
+ end
+ end
+
+ context "when a variant override does not apply" do
+ it "draws stock from the variant" do
+ expect{line_item.increment!(:quantity)}.to change{Spree::Variant.find(variant.id).on_hand}.by(-1)
+ end
+ end
+ end
+ end
+
+ describe "tracking stock when a line item is destroyed" do
+ context "when the order is already complete" do
+ let(:shop) { create(:distributor_enterprise)}
+ let(:order) { create(:completed_order_with_totals, distributor: shop) }
+ let!(:line_item) { order.reload.line_items.first }
+ let!(:variant) { line_item.variant }
+
+ context "when a variant override applies" do
+ let!(:vo) { create(:variant_override, hub: shop, variant: variant, count_on_hand: 3 ) }
+
+ it "restores stock to the variant override" do
+ expect(vo.reload.count_on_hand).to eq 3
+ expect{line_item.destroy}.to_not change{Spree::Variant.find(variant.id).on_hand}
+ expect(vo.reload.count_on_hand).to eq 4
+ end
+ end
+
+ context "when a variant override does not apply" do
+ it "restores stock to the variant" do
+ expect{line_item.destroy}.to change{Spree::Variant.find(variant.id).on_hand}.by(1)
+ end
+ end
+ end
+ end
+
describe "calculating price with adjustments" do
it "does not return fractional cents" do
li = LineItem.new
diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb
index 8922380566..e7c46c27cb 100644
--- a/spec/models/spree/order_spec.rb
+++ b/spec/models/spree/order_spec.rb
@@ -650,4 +650,97 @@ describe Spree::Order do
end
end
end
+
+ describe "a completed order with shipping and transaction fees" do
+ let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true, allow_order_changes: true) }
+ let(:order) { create(:completed_order_with_fees, distributor: distributor, shipping_fee: shipping_fee, payment_fee: payment_fee) }
+ let(:shipping_fee) { 3 }
+ let(:payment_fee) { 5 }
+ let(:item_num) { order.line_items.length }
+ let(:expected_fees) { item_num * (shipping_fee + payment_fee) }
+
+ before do
+ Spree::Config.shipment_inc_vat = true
+ Spree::Config.shipping_tax_rate = 0.25
+
+ # Sanity check the fees
+ expect(order.adjustments.length).to eq 2
+ expect(item_num).to eq 2
+ expect(order.adjustment_total).to eq expected_fees
+ expect(order.shipment.adjustment.included_tax).to eq 1.2
+ end
+
+ context "removing line_items" do
+ it "updates shipping and transaction fees" do
+ # Setting quantity of an item to zero
+ order.update_attributes(line_items_attributes: [{id: order.line_items.first.id, quantity: 0}])
+
+ # Check if fees got updated
+ order.reload
+ expect(order.adjustment_total).to eq expected_fees - shipping_fee - payment_fee
+ expect(order.shipment.adjustment.included_tax).to eq 0.6
+ end
+ end
+
+ context "changing the shipping method to one without fees" do
+ let(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0)) }
+
+ it "updates shipping fees" do
+ # Change the shipping method
+ order.shipment.update_attributes(shipping_method_id: shipping_method.id)
+ order.save
+
+ # Check if fees got updated
+ order.reload
+ expect(order.adjustment_total).to eq expected_fees - (item_num * shipping_fee)
+ expect(order.shipment.adjustment.included_tax).to eq 0
+ end
+ end
+
+ context "changing the payment method to one without fees" do
+ let(:payment_method) { create(:payment_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0)) }
+
+ it "removes transaction fees" do
+ # Change the payment method
+ order.payment.update_attributes(payment_method_id: payment_method.id)
+ order.save
+
+ # Check if fees got updated
+ order.reload
+ expect(order.adjustment_total).to eq expected_fees - (item_num * payment_fee)
+ end
+ end
+ end
+
+ describe "retrieving previously ordered items" do
+ let(:distributor) { create(:distributor_enterprise) }
+ let(:order_cycle) { create(:simple_order_cycle) }
+ let!(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) }
+
+ it "returns no items if nothing has been ordered" do
+ expect(order.finalised_line_items).to eq []
+ end
+
+ context "when no order has been finalised in this order cycle" do
+ let(:product) { create(:product) }
+
+ it "returns no items even though the cart contains items" do
+ order.add_variant(product.master, 1, 3)
+ expect(order.finalised_line_items).to eq []
+ end
+ end
+
+ context "when an order has been finalised in this order cycle" do
+ let!(:prev_order) { create(:completed_order_with_totals, distributor: distributor, order_cycle: order_cycle, user: order.user) }
+ let!(:prev_order2) { create(:completed_order_with_totals, distributor: distributor, order_cycle: order_cycle, user: order.user) }
+ let(:product) { create(:product) }
+
+ it "returns previous items" do
+ prev_order.add_variant(product.master, 1, 3)
+ prev_order2.reload # to get the right response from line_items
+ expect(order.finalised_line_items.length).to eq 3
+ expect(order.finalised_line_items).to match_array(prev_order.line_items + prev_order2.line_items)
+ end
+ end
+ end
end
diff --git a/spec/models/variant_override_spec.rb b/spec/models/variant_override_spec.rb
index a24a921f43..91956a082c 100644
--- a/spec/models/variant_override_spec.rb
+++ b/spec/models/variant_override_spec.rb
@@ -97,6 +97,26 @@ describe VariantOverride do
end
end
+ describe "incrementing stock" do
+ let!(:vo) { create(:variant_override, variant: variant, hub: hub, count_on_hand: 8) }
+
+ context "when the vo overrides stock" do
+ it "increments stock" do
+ vo.increment_stock! 2
+ vo.reload.count_on_hand.should == 10
+ end
+ end
+
+ context "when the vo doesn't override stock" do
+ before { vo.update_attributes(count_on_hand: nil) }
+
+ it "silently logs an error" do
+ Bugsnag.should_receive(:notify)
+ vo.increment_stock! 2
+ end
+ end
+ end
+
describe "checking default stock value is present" do
it "returns true when a default stock level has been set" do
vo = create(:variant_override, variant: variant, hub: hub, count_on_hand: 12, default_stock: 20)