diff --git a/Gemfile b/Gemfile index 2b8f6bdddc..8964676754 100644 --- a/Gemfile +++ b/Gemfile @@ -115,7 +115,6 @@ end group :test do gem 'webmock' - # See spec/spec_helper.rb for instructions #gem 'perftools.rb' end diff --git a/app/assets/javascripts/admin/products/controllers/edit_units_controller.js.coffee b/app/assets/javascripts/admin/products/controllers/edit_units_controller.js.coffee new file mode 100644 index 0000000000..b5ef2847b4 --- /dev/null +++ b/app/assets/javascripts/admin/products/controllers/edit_units_controller.js.coffee @@ -0,0 +1,24 @@ +angular.module("admin.products").controller "editUnitsCtrl", ($scope, VariantUnitManager) -> + + $scope.product = + variant_unit: angular.element('#variant_unit').val() + variant_unit_scale: angular.element('#variant_unit_scale').val() + + $scope.variant_unit_options = VariantUnitManager.variantUnitOptions() + + if $scope.product.variant_unit == 'items' + $scope.variant_unit_with_scale = 'items' + else + $scope.variant_unit_with_scale = $scope.product.variant_unit + '_' + $scope.product.variant_unit_scale + + $scope.setFields = -> + if $scope.variant_unit_with_scale == 'items' + variant_unit = 'items' + variant_unit_scale = null + else + options = $scope.variant_unit_with_scale.split('_') + variant_unit = options[0] + variant_unit_scale = options[1] + + $scope.product.variant_unit = variant_unit + $scope.product.variant_unit_scale = variant_unit_scale diff --git a/app/assets/javascripts/admin/products/controllers/variant_units_controller.js.coffee b/app/assets/javascripts/admin/products/controllers/variant_units_controller.js.coffee new file mode 100644 index 0000000000..2cceef9f1d --- /dev/null +++ b/app/assets/javascripts/admin/products/controllers/variant_units_controller.js.coffee @@ -0,0 +1,14 @@ +angular.module("admin.products").controller "variantUnitsCtrl", ($scope, VariantUnitManager, $timeout) -> + + $scope.unitName = (scale, type) -> + VariantUnitManager.getUnitName(scale, type) + + $scope.scale = angular.element('#product_variant_unit_scale').val() + + $scope.updateValue = -> + unit_value_human = angular.element('#unit_value_human').val() + $scope.unit_value = unit_value_human * $scope.scale + + variant_unit_value = angular.element('#variant_unit_value').val() + $scope.unit_value_human = variant_unit_value / $scope.scale + $timeout -> $scope.updateValue() 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..d900fd0a27 100644 --- a/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee @@ -10,7 +10,7 @@ Darkswarm.controller "OrderCycleCtrl", ($scope, $timeout, OrderCycle) -> $("#order_cycle_id").trigger("openTrigger") -Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart) -> +Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Products, Variants, Cart, ChangeableOrdersAlert) -> # Track previous order cycle id for use with revertOrderCycle() $scope.previous_order_cycle_id = OrderCycle.order_cycle.order_cycle_id $scope.$watch 'order_cycle.order_cycle_id', (newValue, oldValue)-> @@ -30,3 +30,5 @@ Darkswarm.controller "OrderCycleChangeCtrl", ($scope, $timeout, OrderCycle, Prod Variants.clear() Cart.clear() Products.update() + Cart.reloadFinalisedLineItems() + ChangeableOrdersAlert.reload() 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/changeable_order_alert.js.coffee b/app/assets/javascripts/darkswarm/directives/changeable_order_alert.js.coffee new file mode 100644 index 0000000000..7fec779ce1 --- /dev/null +++ b/app/assets/javascripts/darkswarm/directives/changeable_order_alert.js.coffee @@ -0,0 +1,5 @@ +Darkswarm.directive "changeableOrdersAlert", (ChangeableOrdersAlert) -> + restrict: "C" + scope: true + link: (scope, element, attrs) -> + scope.alert = ChangeableOrdersAlert 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..b797390e73 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/bought").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/changeable_orders_alert.js.coffee b/app/assets/javascripts/darkswarm/services/changeable_orders_alert.js.coffee new file mode 100644 index 0000000000..e4b597c57b --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/changeable_orders_alert.js.coffee @@ -0,0 +1,14 @@ +Darkswarm.factory 'ChangeableOrdersAlert', ($http) -> + new class ChangeableOrdersAlert + html: '' + visible: true + + constructor: -> + @reload() + + reload: -> + $http.get('/shop/changeable_orders_alert').then (response) => + @html = response.data.trim() + + close: => + @visible = false 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/animations.scss b/app/assets/stylesheets/darkswarm/animations.scss index f9c7c1967f..7601da02dc 100644 --- a/app/assets/stylesheets/darkswarm/animations.scss +++ b/app/assets/stylesheets/darkswarm/animations.scss @@ -33,7 +33,9 @@ @-webkit-keyframes slideOutUp { 0% { + opacity: 1; -webkit-transform: translateY(0); + -ms-transform: translateY(0); transform: translateY(0); } @@ -46,10 +48,18 @@ @keyframes slideOutUp { 0% { + opacity: 1; -webkit-transform: translateY(0); -ms-transform: translateY(0); transform: translateY(0); } + + 100% { + opacity: 0; + -webkit-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } } @-webkit-keyframes fadeIn { 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/shop.css.scss b/app/assets/stylesheets/darkswarm/shop.css.scss index 33ccbb31e7..eb738fd6ba 100644 --- a/app/assets/stylesheets/darkswarm/shop.css.scss +++ b/app/assets/stylesheets/darkswarm/shop.css.scss @@ -110,6 +110,10 @@ } } + .alert-box.changeable-orders-alert { + margin-bottom: 0px; + } + .alert-box.shopfront-message { border: 2px solid $clr-turquoise; border-radius: 5px; 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..45f0d497f2 --- /dev/null +++ b/app/controllers/line_items_controller.rb @@ -0,0 +1,48 @@ +class LineItemsController < BaseController + respond_to :json + + before_filter :load_line_item, only: :destroy + + def bought + respond_with bought_items, each_serializer: Api::LineItemSerializer + end + + def destroy + authorize! :destroy, @line_item + destroy_with_lock @line_item + respond_with(@line_item) + end + + private + + def load_line_item + @line_item = Spree::LineItem.find_by_id(params[:id]) + not_found unless @line_item + end + + # List all items the user already ordered in the current order cycle + def bought_items + return [] unless current_order_cycle && spree_current_user && current_distributor + current_order_cycle.items_bought_by_user(spree_current_user, current_distributor) + end + + def unauthorized + status = spree_current_user ? 403 : 401 + render nothing: true, status: status and return + end + + def not_found + render nothing: true, status: 404 and return + end + + def destroy_with_lock(item) + order = item.order + order.with_lock do + item.destroy + order.update_shipping_fees! + order.update_payment_fees! + order.update_distribution_charge! + order.create_tax_charge! + end + end +end diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 091a501ca5..6ad6f56e56 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -37,6 +37,10 @@ class ShopController < BaseController end end + def changeable_orders_alert + render layout: false + end + private def filtered_json(products_json) diff --git a/app/controllers/spree/admin/orders_controller_decorator.rb b/app/controllers/spree/admin/orders_controller_decorator.rb index 129d4c3308..b9798c8ca1 100644 --- a/app/controllers/spree/admin/orders_controller_decorator.rb +++ b/app/controllers/spree/admin/orders_controller_decorator.rb @@ -12,6 +12,9 @@ Spree::Admin::OrdersController.class_eval do before_filter :load_distribution_choices, only: [:new, :edit, :update] + # Ensure that the distributor is set for an order when + before_filter :ensure_distribution, only: :new + # After updating an order, the fees should be updated as well # Currently, adding or deleting line items does not trigger updating the # fees! This is a quick fix for that. @@ -131,4 +134,16 @@ Spree::Admin::OrdersController.class_eval do ocs.closed + ocs.undated end + + def ensure_distribution + unless @order + @order = Spree::Order.new + @order.generate_order_number + @order.save! + end + unless @order.distribution_set? + render 'set_distribution', locals: { order: @order } + end + end + end 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..22791661fb 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,30 @@ Spree::OrdersController.class_eval do def wrap_json_infinity(n) n == Float::INFINITY ? 2147483647 : n end + + def order_to_update + return @order_to_update if defined? @order_to_update + return @order_to_update = current_order unless params[:id] + @order_to_update = changeable_order_from_number + end + + # If a specific order is requested, return it if it is COMPLETE and + # changes are allowed and the user has access. Return nil if not. + def changeable_order_from_number + order = Spree::Order.complete.find_by_number(params[:id]) + return nil unless order.andand.changes_allowed? && can?(:update, order) + order + end + + def check_at_least_one_line_item + return unless order_to_update.andand.complete? + + items = params[:order][:line_items_attributes] + .andand.select{ |k,attrs| attrs["quantity"].to_i > 0 } + + if items.empty? + flash[:error] = I18n.t(:orders_cannot_remove_the_final_item) + redirect_to order_path(order_to_update) + end + end end diff --git a/app/helpers/checkout_helper.rb b/app/helpers/checkout_helper.rb index eb4cb1fb65..7397ce812e 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 @@ -55,7 +55,6 @@ module CheckoutHelper def display_adjustment_tax_rates(adjustment) tax_rates = adjustment.tax_rates - return "" if adjustment.amount == adjustment.included_tax tax_rates.map { |tr| number_to_percentage(tr.amount * 100, :precision => 1) }.join(", ") end 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..99540f6ed4 100644 --- a/app/helpers/spree/orders_helper.rb +++ b/app/helpers/spree/orders_helper.rb @@ -16,5 +16,31 @@ module Spree def cart_count current_order.andand.line_items.andand.count || 0 end + + def changeable_orders + # Only returns open order for the current user + shop + oc combo + return @changeable_orders unless @changeable_orders.nil? + return @changeable_orders = [] unless spree_current_user && current_distributor && current_order_cycle + return @changeable_orders = [] unless current_distributor.allow_order_changes? + @changeable_orders = Spree::Order.complete.where( + state: 'complete', + user_id: spree_current_user.id, + distributor_id: current_distributor.id, + order_cycle_id: current_order_cycle.id) + end + + def changeable_orders_link_path + changeable_orders.one? ? spree.order_path(changeable_orders.first) : spree.account_path + end + + def shop_changeable_orders_alert_html + return "" unless changeable_orders.any? + t(:shop_changeable_orders_alert_html, + count: changeable_orders.count, + path: changeable_orders_link_path, + order: changeable_orders.first.number, + shop: current_distributor.name, + oc_close: l(current_order_cycle.orders_close_at, format: "%A, %b %d, %Y @ %H:%M")) + end end end 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..6a21b9309e 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) + scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor) + items = Spree::LineItem.joins(:order).merge(orders) + 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/adjustment_decorator.rb b/app/models/spree/adjustment_decorator.rb index 4ad456ba12..035398d332 100644 --- a/app/models/spree/adjustment_decorator.rb +++ b/app/models/spree/adjustment_decorator.rb @@ -34,6 +34,7 @@ module Spree included_tax > 0 end + # @return [Array] def tax_rates case originator when Spree::TaxRate @@ -47,14 +48,17 @@ module Spree return originator.tax_category ? originator.tax_category.tax_rates.match(source) : [] end else - [find_closest_tax_rate_from_included_tax] + find_closest_tax_rates_from_included_tax end end - def find_closest_tax_rate_from_included_tax + # shipping fees and adjustments created from the admin panel have + # taxes set at creation in the included_tax field without relation + # to the corresponding TaxRate, so we look for the closest one + def find_closest_tax_rates_from_included_tax approximation = (included_tax / (amount - included_tax)) - return nil if approximation.infinite? or approximation.zero? - Spree::TaxRate.order("ABS(amount - #{approximation})").first + return [] if approximation.infinite? or approximation.zero? + [Spree::TaxRate.order("ABS(amount - #{approximation})").first] 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..273848ad9c 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 @@ -158,6 +177,10 @@ Spree::Order.class_eval do save! end + def distribution_set? + distributor && order_cycle + end + def update_distribution_charge! with_lock do EnterpriseFee.clear_all_adjustments_on_order self @@ -195,6 +218,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 +234,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 +283,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 +302,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 +316,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 +343,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..226c614998 100644 --- a/app/models/variant_override.rb +++ b/app/models/variant_override.rb @@ -19,7 +19,7 @@ class VariantOverride < ActiveRecord::Base def self.indexed(hub) Hash[ - for_hubs(hub).map { |vo| [vo.variant, vo] } + for_hubs(hub).preload(:variant).map { |vo| [vo.variant, vo] } ] end @@ -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/orders/_form/add_distribution_fields.html.haml.deface b/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface index a6c51d517c..af07be1d23 100644 --- a/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface +++ b/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface @@ -1,38 +1,3 @@ / insert_before "[data-hook='admin_order_form_buttons']" -%fieldset.no-border-bottom - %legend{align: 'center'} Distribution - - - if @order.complete? - .alpha.six.columns - %p - %b Distributor: - = f.object.distributor.andand.name || "None" - = f.hidden_field :distributor_id - .omega.six.columns - %p - %b Order cycle: - = f.object.order_cycle.andand.name || "None" - = f.hidden_field :order_cycle_id - - - else - .alpha.six.columns - .field - %label{for: "order_distributor_id"} Distributor - %input.ofn-select2.fullwidth{id: "order_distributor_id", - type: 'number', - name: "order[distributor_id]", - "ng-model" => 'distributor_id', - data: "shops" } - - .omega.six.columns - .field - %label{ for: "order_order_cycle_id"} Order Cycle - %input.ofn-select2.fullwidth{id: "order_order_cycle_id", - type: 'number', - name: "order[order_cycle_id]", - "ng-model" => 'order_cycle_id', - "ng-disabled" => "!distributor_id", - data: "orderCycles", - text: "name_and_status", - filter: "validOrderCycle" } += render partial: 'spree/admin/orders/_form/distribution_fields' diff --git a/app/overrides/spree/admin/orders/_line_item/replace_variant_price.html.haml.deface b/app/overrides/spree/admin/orders/_line_item/replace_variant_price.html.haml.deface new file mode 100644 index 0000000000..605f645996 --- /dev/null +++ b/app/overrides/spree/admin/orders/_line_item/replace_variant_price.html.haml.deface @@ -0,0 +1,3 @@ +/ replace 'code[erb-loud]:contains(\'f.object.variant.display_amount\')' + += f.object.single_money diff --git a/app/overrides/spree/admin/orders/edit/suppress_errors.html.haml.deface b/app/overrides/spree/admin/orders/edit/suppress_errors.html.haml.deface new file mode 100644 index 0000000000..31a8c961e8 --- /dev/null +++ b/app/overrides/spree/admin/orders/edit/suppress_errors.html.haml.deface @@ -0,0 +1,6 @@ +/ replace "code[erb-loud]:contains(\'error_messages\')" + +-# Suppress errors when manually creating a new order - needs to proceed to edit page +-# without having line items (which otherwise gives a validation error) +- unless params["suppress_error_msg"] + = render partial: "spree/shared/error_messages", :locals => { :target => @order } diff --git a/app/overrides/spree/admin/products/_form/add_units_form.html.haml.deface b/app/overrides/spree/admin/products/_form/add_units_form.html.haml.deface index 9a5b6a87d6..c7e4e2feb3 100644 --- a/app/overrides/spree/admin/products/_form/add_units_form.html.haml.deface +++ b/app/overrides/spree/admin/products/_form/add_units_form.html.haml.deface @@ -1,16 +1,17 @@ / insert_top "[data-hook='admin_product_form_right']" -= f.field_container :variant_unit do - = f.label :variant_unit, 'Variant unit' - = f.select :variant_unit, product_variant_unit_options, {:include_blank => true}, {:class => "select2 fullwidth"} - = f.error_message_on :variant_unit +.variant_units_form{ 'ng-app' => 'admin.products', 'ng-controller' => 'editUnitsCtrl' } -= f.field_container :variant_unit_scale do - = f.label :variant_unit_scale, 'Variant unit scale' - = f.text_field :variant_unit_scale - = f.error_message_on :variant_unit_scale + = f.field_container :units do + = f.label :variant_unit_with_scale, :units + %select.select2.fullwidth{ id: 'product_variant_unit_with_scale', 'ng-model' => 'variant_unit_with_scale', 'ng-change' => 'setFields()', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' } + %option{'value' => ''} -= f.field_container :variant_unit_name do - = f.label :variant_unit_name, 'Variant unit name' - = f.text_field :variant_unit_name - = f.error_message_on :variant_unit_name + = f.text_field :variant_unit, {'id' => 'variant_unit', 'ng-value' => 'product.variant_unit', 'hidden' => true} + = f.text_field :variant_unit_scale, {'id' => 'variant_unit_scale', 'ng-value' => 'product.variant_unit_scale', 'hidden' => true} + + .variant_unit_name{'ng-show' => 'product.variant_unit == "items"'} + = f.field_container :variant_unit_name do + = f.label :variant_unit_name, 'Variant unit name' + = f.text_field :variant_unit_name, {placeholder: t('admin.products.unit_name_placeholder')} + = f.error_message_on :variant_unit_name diff --git a/app/overrides/spree/admin/products/new/replace_form.html.haml.deface b/app/overrides/spree/admin/products/new/replace_form.html.haml.deface index 01894a8965..e9dc6f13c5 100644 --- a/app/overrides/spree/admin/products/new/replace_form.html.haml.deface +++ b/app/overrides/spree/admin/products/new/replace_form.html.haml.deface @@ -34,7 +34,7 @@ .three.columns.omega{ 'ng-show' => "product.variant_unit_with_scale == 'items'" } = f.field_container :unit_name do = f.label :product_variant_unit_name, t(:unit_name) - %input.fullwidth{ id: 'product_variant_unit_name','ng-model' => 'product.variant_unit_name', :name => 'product[variant_unit_name]', :placeholder => 'eg. bunches', :type => 'text' } + %input.fullwidth{ id: 'product_variant_unit_name','ng-model' => 'product.variant_unit_name', :name => 'product[variant_unit_name]', :placeholder => t('admin.products.unit_name_placeholder'), :type => 'text' } .twelve.columns.alpha .six.columns.alpha = render 'spree/admin/products/primary_taxon_form', f: f diff --git a/app/overrides/spree/admin/shared/_order_details/replace_item_price.html.haml.deface b/app/overrides/spree/admin/shared/_order_details/replace_item_price.html.haml.deface new file mode 100644 index 0000000000..6da1bc542d --- /dev/null +++ b/app/overrides/spree/admin/shared/_order_details/replace_item_price.html.haml.deface @@ -0,0 +1,3 @@ +/ replace_contents 'tr[data-hook="order_details_line_item_row"] td.price' + += item.single_display_amount 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/overrides/spree/admin/variants/_form/add_unit_value_and_description.html.haml.deface b/app/overrides/spree/admin/variants/_form/add_unit_value_and_description.html.haml.deface index 8696134177..649b210799 100644 --- a/app/overrides/spree/admin/variants/_form/add_unit_value_and_description.html.haml.deface +++ b/app/overrides/spree/admin/variants/_form/add_unit_value_and_description.html.haml.deface @@ -1,10 +1,13 @@ / insert_top "[data-hook='admin_variant_form_fields']" - if product_has_variant_unit_option_type?(@product) - .field{"data-hook" => "unit_value"} - = f.label :unit_value, "Unit Value" - = f.text_field :unit_value, class: "fullwidth" + - if @product.variant_unit != 'items' + .field{"data-hook" => "unit_value", 'ng-controller' => 'variantUnitsCtrl'} + = f.label :unit_value, "#{t('admin.'+@product.variant_unit)} ({{unitName(#{@product.variant_unit_scale}, '#{@product.variant_unit}')}})" + = hidden_field_tag 'product_variant_unit_scale', @product.variant_unit_scale + = text_field_tag :unit_value_human, nil, {class: "fullwidth", 'ng-model' => 'unit_value_human', 'ng-change' => 'updateValue()'} + = f.text_field :unit_value, {hidden: true, 'ng-value' => 'unit_value'} - .field{"data-hook" => "unit_description"} - = f.label :unit_description, "Unit Description" - = f.text_field :unit_description, class: "fullwidth" + .field{"data-hook" => "unit_description"} + = f.label :unit_description, "Unit Description" + = f.text_field :unit_description, class: "fullwidth", placeholder: t('admin.products.unit_name_placeholder') diff --git a/app/overrides/spree/admin/variants/_form/replace_weight.html.haml.deface b/app/overrides/spree/admin/variants/_form/replace_weight.html.haml.deface new file mode 100644 index 0000000000..0618c3bab5 --- /dev/null +++ b/app/overrides/spree/admin/variants/_form/replace_weight.html.haml.deface @@ -0,0 +1,14 @@ +/ replace "[data-hook='admin_variant_form_additional_fields']" + +.right.six.columns.omega.label-block{"data-hook" => "admin_variant_form_additional_fields"} + - if @product.variant_unit != 'weight' + .field{"data-hook" => 'weight'} + = f.label 'weight', t('weight')+' (kg)' + - value = number_with_precision(@variant.weight, :precision => 2) + = f.text_field 'weight', :value => value, :class => 'fullwidth' + + - [:height, :width, :depth].each do |field| + .field{"data-hook" => field} + = f.label field, t(field) + - value = number_with_precision(@variant.send(field), :precision => 2) + = f.text_field field, :value => value, :class => 'fullwidth' 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..0d060798d3 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 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..10677b1bfe 100644 --- a/app/views/enterprises/shop.html.haml +++ b/app/views/enterprises/shop.html.haml @@ -8,6 +8,10 @@ = inject_shop_enterprises %shop.darkswarm + .alert-box.changeable-orders-alert.info.animate-show{ ng: { show: "alert.visible && alert.html", cloak: true } } + %span{ ng: { bind: { html: "alert.html" } } } + %a.close{ ng: { click: "alert.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/shop/changeable_orders_alert.html.haml b/app/views/shop/changeable_orders_alert.html.haml new file mode 100644 index 0000000000..2fc8f1b551 --- /dev/null +++ b/app/views/shop/changeable_orders_alert.html.haml @@ -0,0 +1 @@ += shop_changeable_orders_alert_html diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index 09f9aa15d5..628ae5a161 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -6,7 +6,7 @@ .row.summary .small-10.medium-10.large-11.columns.summary-header %h3 - %a{"ng-click" => "triggerProductModal()"} + %a{"ng-click" => "triggerProductModal()", href: 'javascript:void(0)'} %span{"ng-bind" => "::product.name"} %i.ofn-i_057-expand %small 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/_form/_distribution_fields.html.haml b/app/views/spree/admin/orders/_form/_distribution_fields.html.haml new file mode 100644 index 0000000000..01cb19ed83 --- /dev/null +++ b/app/views/spree/admin/orders/_form/_distribution_fields.html.haml @@ -0,0 +1,35 @@ +%fieldset.no-border-bottom + %legend{align: 'center'} Distribution + + - if @order.complete? + .alpha.six.columns + %p + %b Distributor: + = @order.distributor.andand.name || "None" + %input{type: "hidden", id: "order_distributor_id", value: @order.distributor.andand.id} + .omega.six.columns + %p + %b Order cycle: + = @order.order_cycle.andand.name || "None" + %input{type: "hidden", id: "order_order_cycle_id", value: @order.order_cycle.andand.id} + - else + .alpha.six.columns + .field + %label{for: "order_distributor_id"} Distributor + %input.ofn-select2.fullwidth{id: "order_distributor_id", + type: 'number', + name: "order[distributor_id]", + "ng-model" => 'distributor_id', + data: "shops" } + + .omega.six.columns + .field + %label{ for: "order_order_cycle_id"} Order Cycle + %input.ofn-select2.fullwidth{id: "order_order_cycle_id", + type: 'number', + name: "order[order_cycle_id]", + "ng-model" => 'order_cycle_id', + "ng-disabled" => "!distributor_id", + data: "orderCycles", + text: "name_and_status", + filter: "validOrderCycle" } 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/orders/set_distribution.html.haml b/app/views/spree/admin/orders/set_distribution.html.haml new file mode 100644 index 0000000000..b4874751d4 --- /dev/null +++ b/app/views/spree/admin/orders/set_distribution.html.haml @@ -0,0 +1,24 @@ +- content_for :page_title do + = t(:new) + +- content_for :page_actions do + %li= button_link_to t(:back_to_orders_list), spree.admin_orders_path, :icon => 'icon-arrow-left' + += admin_inject_shops 'admin.orders' += admin_inject_order_cycles + += render 'spree/admin/shared/order_tabs', :current => 'Order Details' + += csrf_meta_tags + +%div{"data-hook" => "admin_order_new_header"} + = render 'spree/shared/error_messages', :target => @order + +%div{"ng-app" => "admin.orders", "ng-controller" => "ordersCtrl"} + = form_for @order, url: admin_order_url(@order), method: :put do |f| + = render 'spree/admin/orders/_form/distribution_fields' + -# This param passed to stop validation error in next page due to no line items in order yet: + = hidden_field_tag 'suppress_error_msg', "true" + = button_tag :class => 'secondary radius expand small', :id => 'update-button' do + %i.icon-arrow-right + = t(:next) diff --git a/app/views/spree/admin/orders/ticket.html.haml b/app/views/spree/admin/orders/ticket.html.haml index 53f224cff1..22e9799754 100644 --- a/app/views/spree/admin/orders/ticket.html.haml +++ b/app/views/spree/admin/orders/ticket.html.haml @@ -13,24 +13,24 @@ '\x1B' + '\x74' + '\x10', '\x1B' + '\x61' + '\x31', // center align '\x1B' + '\x21' + '\x30', // em mode on - '#{@order.distributor.name}' + '\x0A', + '#{j(@order.distributor.name)}' + '\x0A', '\x1B' + '\x21' + '\x0A' + '\x1B' + '\x45' + '\x0A', // em mode off '\x0A', - '#{@order.distributor.address.address_part1}' + '\x0A', // text and line break - '#{@order.distributor.address.address_part2}' + '\x0A', - '#{@order.distributor.email}' + '\x0A', + '#{j(@order.distributor.address.address_part1)}' + '\x0A', // text and line break + '#{j(@order.distributor.address.address_part2)}' + '\x0A', + '#{j(@order.distributor.email)}' + '\x0A', '\x0A', // line break '\x1B' + '\x61' + '\x32', // right align - '#{l Time.zone.now.to_date}' + '\x0A', - '#{@order.number}' + '\x0A', + '#{j(l(Time.zone.now.to_date))}' + '\x0A', + '#{j(@order.number)}' + '\x0A', '\x1B' + '\x61' + '\x30', // left align '\x0A', '\x1B' + '\x4D' + '\x31', // small text - "#{'%6s %-23s%12s%12s' % - [t(:ticket_column_qty), - t(:ticket_column_item), - t(:ticket_column_unit_price), - t(:ticket_column_total_price)]}", + "#{'%6s %-26s%10s%10s' % + [j(t(:ticket_column_qty)), + j(t(:ticket_column_item)), + j(t(:ticket_column_unit_price)), + j(t(:ticket_column_total_price))]}", '\x0A', '\x1B' + '\x4D' + '\x30', // normal text '__________________________________________' + '\x0A', @@ -38,36 +38,34 @@ .sort_by{ |line_item| line_item.product.name } .map { |line_item| '%5d %-19.19s%8.8s%8.8s' % [line_item.quantity, - line_item.product.name, - line_item.single_display_amount_with_adjustments.money.format(symbol: false), - line_item.display_amount_with_adjustments.money.format(symbol: false)] } + j(line_item.product.name), + j(line_item.single_display_amount_with_adjustments.format(symbol: false, with_currency: false)), + j(line_item.display_amount_with_adjustments.format(symbol: false, with_currency: false))] } .join('" + \'\x0A\' + "')}", '\x0A', "#{checkout_adjustments_for(@order, exclude: [:line_item]) .reject{ |a| a.amount == 0 } .reverse.map { |adjustment| '%5s %-27.27s%8.8s' % ["", - raw(adjustment.label), - display_adjustment_amount(adjustment).money.format(symbol: false)] } - .join('" + \'\x0A\' + "')}", - '\x0A', + j(raw(adjustment.label)), + j(display_adjustment_amount(adjustment).format(symbol: false, with_currency: false))] + + '" + \'\x0A\' + "'}.join }", '__________________________________________' + '\x0A', '\x0A', '\x1B' + '\x45' + '\x0D', // bold on "#{'%31s%10s' % - [t(:total_incl_tax), - @order.display_total]}", + [j(t(:total_incl_tax)), + j(@order.display_total.format(with_currency: false))]}", '\x1B' + '\x45' + '\x0A', // bold off '\x0A', "#{display_checkout_taxes_hash(@order).map { |tax_rate, tax_value| '%31s%10s' % - [t(:tax_total, rate: tax_rate), - tax_value] } - .join('" + \'\x0A\' + "')}", - '\x0A', + [j(t(:tax_total, rate: tax_rate)), + j(tax_value.format(with_currency: false))] + + '" + \'\x0A\' + "'}.join }", "#{'%31s%10s' % - [t(:total_excl_tax), - display_checkout_total_less_tax(@order)]}", + [j(t(:total_excl_tax)), + j(display_checkout_total_less_tax(@order).format(with_currency: false))]}", '\x0A', '\x0A' + '\x0A' + '\x0A' + '\x0A' + '\x0A' + '\x0A' + '\x0A', '\x1B' + '\x69', // cut paper 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..73173f9c2e 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 @@ -99,6 +114,9 @@ en: sku: SKU tags: Tags variant: Variant + weight: Weight + volume: Volume + items: Items # General form elements quick_search: Quick Search @@ -107,6 +125,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 +210,7 @@ en: edit: 'Edit' update_address: 'Update Address' confirm_delete: 'Sure to delete?' + search_by_email: "Search by email/code..." cache_settings: show: @@ -218,6 +239,7 @@ en: new_button: New Enterprise Group products: + unit_name_placeholder: 'eg. bunches' bulk_edit: unit: Unit display_as: Display As @@ -299,6 +321,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 +395,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 / cancelled" + allow_order_changes_true: "Customers can change / cancel orders while order cycle is open" + 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 +555,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 +649,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 +672,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 +680,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 +779,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 +788,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 +899,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 +1049,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 +1059,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 +1177,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 +1318,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 +1339,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" @@ -1324,6 +1382,8 @@ Please follow the instructions there to make your enterprise visible on the Open edit_profile_details: "Edit profile details" edit_profile_details_etc: "Change your profile description, images, etc." order_cycle: "Order Cycle" + order_cycles: "Order Cycles" + enterprises: "Enterprises" remove_tax: "Remove tax" enterprise_terms_of_service: "Enterprise Terms of Service" enterprises_require_tos: "Enterprises must accept Terms of Service" @@ -1542,6 +1602,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 +1622,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 +1664,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 +1691,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/locales/fr.yml b/config/locales/fr.yml index d48e3039cd..969eecb4da 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -522,8 +522,8 @@ fr: date_of_transaction: "Date de la transaction :" ticket_column_qty: "Qté" ticket_column_item: "Produit" - ticket_column_unit_price: "Prix unitaire" - ticket_column_total_price: "Prix total" + ticket_column_unit_price: "Px unit" + ticket_column_total_price: "Px total" logo: "Logo (640x130)" logo_mobile: "Logo smartphone (75x26)" logo_mobile_svg: "Logo smartphone (SVG)" diff --git a/config/routes.rb b/config/routes.rb index 90700d89b0..ca5a8d5ade 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -28,6 +28,7 @@ Openfoodnetwork::Application.routes.draw do get :products post :order_cycle get :order_cycle + get :changeable_orders_alert end resources :producers, only: [:index] do @@ -42,6 +43,10 @@ Openfoodnetwork::Application.routes.draw do end end + resources :line_items, only: [:destroy] do + get :bought, on: :collection + end + resources :groups, only: [:index, :show] do collection do get :signup @@ -262,6 +267,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/lib/spree/money_decorator.rb b/lib/spree/money_decorator.rb index 755041df0e..0194925d4e 100644 --- a/lib/spree/money_decorator.rb +++ b/lib/spree/money_decorator.rb @@ -20,4 +20,7 @@ Spree::Money.class_eval do output end + def format(options={}) + @money.format(@options.merge!(options)) + end 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..1db2d96dea --- /dev/null +++ b/spec/controllers/line_items_controller_spec.rb @@ -0,0 +1,162 @@ +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 :bought, { 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 + delete :destroy + expect(response.status).to eq 404 + end + end + + context "with a line item id" do + let(:params) { { format: :json, id: item } } + + context "where the item's order is not associated with the user" do + it "denies deletion" do + delete :destroy, params + expect(response.status).to eq 403 + 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 + 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 + 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 + 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 ActiveRecord::RecordNotFound + 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..e7b5a7e12a 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 nil" do + expect(controller.send(:order_to_update)).to eq nil + 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 nil" do + expect(controller.send(:order_to_update)).to eq nil + 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 nil" do + expect(controller.send(:order_to_update)).to eq nil + 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..73e62744ba 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 @@ -57,6 +71,14 @@ FactoryGirl.define do end end + factory :order_cycle_with_overrides, parent: :order_cycle do + after (:create) do |oc| + oc.variants.each do |variant| + create(:variant_override, variant: variant, hub: oc.distributors.first, price: variant.price + 100) + end + end + end + factory :simple_order_cycle, :class => OrderCycle do sequence(:name) { |n| "Order Cycle #{n}" } @@ -171,6 +193,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 +204,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 +263,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/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index d9bfdfd345..2f826b51c4 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -58,124 +58,263 @@ feature %q{ end end - scenario "creating an order cycle", js: true do - page.driver.resize(1280, 2000) + context "with specific time" do + let(:order_cycle_opening_time) { Time.zone.local(2040, 11, 06, 06, 00, 00) } + let(:order_cycle_closing_time) { Time.zone.local(2040, 11, 13, 17, 00, 00) } - # Given coordinating, supplying and distributing enterprises with some products with variants - coordinator = create(:distributor_enterprise, name: 'My coordinator') - supplier = create(:supplier_enterprise, name: 'My supplier') - product = create(:product, supplier: supplier) - v1 = create(:variant, product: product) - v2 = create(:variant, product: product) - distributor = create(:distributor_enterprise, name: 'My distributor', with_payment_and_shipping: true) + scenario "creating an order cycle", js: true do + page.driver.resize(1280, 2000) - # Relationships required for interface to work - create(:enterprise_relationship, parent: supplier, child: coordinator, permissions_list: [:add_to_order_cycle]) - create(:enterprise_relationship, parent: distributor, child: coordinator, permissions_list: [:add_to_order_cycle]) - create(:enterprise_relationship, parent: supplier, child: distributor, permissions_list: [:add_to_order_cycle]) + # Given coordinating, supplying and distributing enterprises with some products with variants + coordinator = create(:distributor_enterprise, name: 'My coordinator') + supplier = create(:supplier_enterprise, name: 'My supplier') + product = create(:product, supplier: supplier) + v1 = create(:variant, product: product) + v2 = create(:variant, product: product) + distributor = create(:distributor_enterprise, name: 'My distributor', with_payment_and_shipping: true) - # And some enterprise fees - supplier_fee = create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee') - coordinator_fee = create(:enterprise_fee, enterprise: coordinator, name: 'Coord fee') - distributor_fee = create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee') + # Relationships required for interface to work + create(:enterprise_relationship, parent: supplier, child: coordinator, permissions_list: [:add_to_order_cycle]) + create(:enterprise_relationship, parent: distributor, child: coordinator, permissions_list: [:add_to_order_cycle]) + create(:enterprise_relationship, parent: supplier, child: distributor, permissions_list: [:add_to_order_cycle]) - # When I go to the new order cycle page - login_to_admin_section - click_link 'Order Cycles' - click_link 'New Order Cycle' + # And some enterprise fees + supplier_fee = create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee') + coordinator_fee = create(:enterprise_fee, enterprise: coordinator, name: 'Coord fee') + distributor_fee = create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee') - # Select a coordinator since there are two available - select2_select 'My coordinator', from: 'coordinator_id' - click_button "Continue >" + # When I go to the new order cycle page + login_to_admin_section + click_link 'Order Cycles' + click_link 'New Order Cycle' - # And I fill in the basic fields - fill_in 'order_cycle_name', with: 'Plums & Avos' - fill_in 'order_cycle_orders_open_at', with: '2040-11-06 06:00:00' - fill_in 'order_cycle_orders_close_at', with: '2040-11-13 17:00:00' + # Select a coordinator since there are two available + select2_select 'My coordinator', from: 'coordinator_id' + click_button "Continue >" - # And I add a coordinator fee - click_button 'Add coordinator fee' - select 'Coord fee', from: 'order_cycle_coordinator_fee_0_id' + # And I fill in the basic fields + fill_in 'order_cycle_name', with: 'Plums & Avos' + fill_in 'order_cycle_orders_open_at', with: order_cycle_opening_time + fill_in 'order_cycle_orders_close_at', with: order_cycle_closing_time - # I should not be able to add a blank supplier - page.should have_select 'new_supplier_id', selected: '' - page.should have_button 'Add supplier', disabled: true + # And I add a coordinator fee + click_button 'Add coordinator fee' + select 'Coord fee', from: 'order_cycle_coordinator_fee_0_id' - # And I add a supplier and some products - select 'My supplier', from: 'new_supplier_id' - click_button 'Add supplier' - fill_in 'order_cycle_incoming_exchange_0_receival_instructions', with: 'receival instructions' - page.find('table.exchanges tr.supplier td.products').click - check "order_cycle_incoming_exchange_0_variants_#{v1.id}" - check "order_cycle_incoming_exchange_0_variants_#{v2.id}" + # I should not be able to add a blank supplier + page.should have_select 'new_supplier_id', selected: '' + page.should have_button 'Add supplier', disabled: true - # I should not be able to re-add the supplier - page.should_not have_select 'new_supplier_id', with_options: ['My supplier'] - page.should have_button 'Add supplier', disabled: true - page.all("td.supplier_name").map(&:text).should == ['My supplier'] + # And I add a supplier and some products + select 'My supplier', from: 'new_supplier_id' + click_button 'Add supplier' + fill_in 'order_cycle_incoming_exchange_0_receival_instructions', with: 'receival instructions' + page.find('table.exchanges tr.supplier td.products').click + check "order_cycle_incoming_exchange_0_variants_#{v1.id}" + check "order_cycle_incoming_exchange_0_variants_#{v2.id}" - # And I add a supplier fee - within("tr.supplier-#{supplier.id}") { click_button 'Add fee' } - select 'My supplier', from: 'order_cycle_incoming_exchange_0_enterprise_fees_0_enterprise_id' - select 'Supplier fee', from: 'order_cycle_incoming_exchange_0_enterprise_fees_0_enterprise_fee_id' + # I should not be able to re-add the supplier + page.should_not have_select 'new_supplier_id', with_options: ['My supplier'] + page.should have_button 'Add supplier', disabled: true + page.all("td.supplier_name").map(&:text).should == ['My supplier'] - # And I add a distributor with the same products - select 'My distributor', from: 'new_distributor_id' - click_button 'Add distributor' + # And I add a supplier fee + within("tr.supplier-#{supplier.id}") { click_button 'Add fee' } + select 'My supplier', from: 'order_cycle_incoming_exchange_0_enterprise_fees_0_enterprise_id' + select 'Supplier fee', from: 'order_cycle_incoming_exchange_0_enterprise_fees_0_enterprise_fee_id' - fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time' - fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions' + # And I add a distributor with the same products + select 'My distributor', from: 'new_distributor_id' + click_button 'Add distributor' - page.find('table.exchanges tr.distributor td.products').click - check "order_cycle_outgoing_exchange_0_variants_#{v1.id}" - check "order_cycle_outgoing_exchange_0_variants_#{v2.id}" + fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time' + fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions' - page.find('table.exchanges tr.distributor td.tags').click - within ".exchange-tags" do - find(:css, "tags-input .tags input").set "wholesale\n" + page.find('table.exchanges tr.distributor td.products').click + check "order_cycle_outgoing_exchange_0_variants_#{v1.id}" + check "order_cycle_outgoing_exchange_0_variants_#{v2.id}" + + page.find('table.exchanges tr.distributor td.tags').click + within ".exchange-tags" do + find(:css, "tags-input .tags input").set "wholesale\n" + end + + # And I add a distributor fee + within("tr.distributor-#{distributor.id}") { click_button 'Add fee' } + select 'My distributor', from: 'order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_id' + select 'Distributor fee', from: 'order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_fee_id' + + # And I click Create + click_button 'Create' + + # Then my order cycle should have been created + page.should have_content 'Your order cycle has been created.' + + page.should have_selector 'a', text: 'Plums & Avos' + page.should have_selector "input[value='#{order_cycle_opening_time}']" + page.should have_selector "input[value='#{order_cycle_closing_time}']" + page.should have_content 'My coordinator' + + page.should have_selector 'td.suppliers', text: 'My supplier' + page.should have_selector 'td.distributors', text: 'My distributor' + + # And it should have some fees + oc = OrderCycle.last + oc.exchanges.incoming.first.enterprise_fees.should == [supplier_fee] + oc.coordinator_fees.should == [coordinator_fee] + oc.exchanges.outgoing.first.enterprise_fees.should == [distributor_fee] + + # And it should have some variants selected + oc.exchanges.first.variants.count.should == 2 + oc.exchanges.last.variants.count.should == 2 + + # And my receival and pickup time and instructions should have been saved + exchange = oc.exchanges.incoming.first + exchange.receival_instructions.should == 'receival instructions' + + exchange = oc.exchanges.outgoing.first + exchange.pickup_time.should == 'pickup time' + exchange.pickup_instructions.should == 'pickup instructions' + exchange.tag_list.should == ['wholesale'] end - # And I add a distributor fee - within("tr.distributor-#{distributor.id}") { click_button 'Add fee' } - select 'My distributor', from: 'order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_id' - select 'Distributor fee', from: 'order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_fee_id' + scenario "updating an order cycle", js: true do + # Given an order cycle with all the settings + oc = create(:order_cycle) + initial_variants = oc.variants.sort_by &:id - # And I click Create - click_button 'Create' + # And a coordinating, supplying and distributing enterprise with some products with variants + coordinator = oc.coordinator + supplier = create(:supplier_enterprise, name: 'My supplier') + distributor = create(:distributor_enterprise, name: 'My distributor', with_payment_and_shipping: true) + product = create(:product, supplier: supplier) + v1 = create(:variant, product: product) + v2 = create(:variant, product: product) - # Then my order cycle should have been created - page.should have_content 'Your order cycle has been created.' + # Relationships required for interface to work + create(:enterprise_relationship, parent: supplier, child: coordinator, permissions_list: [:add_to_order_cycle]) + create(:enterprise_relationship, parent: distributor, child: coordinator, permissions_list: [:add_to_order_cycle]) + create(:enterprise_relationship, parent: supplier, child: distributor, permissions_list: [:add_to_order_cycle]) - page.should have_selector 'a', text: 'Plums & Avos' + # And some enterprise fees + supplier_fee1 = create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee 1') + supplier_fee2 = create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee 2') + coordinator_fee1 = create(:enterprise_fee, enterprise: coordinator, name: 'Coord fee 1') + coordinator_fee2 = create(:enterprise_fee, enterprise: coordinator, name: 'Coord fee 2') + distributor_fee1 = create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee 1') + distributor_fee2 = create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee 2') - page.should have_selector "input[value='2040-11-06 06:00:00 +1100']" - page.should have_selector "input[value='2040-11-13 17:00:00 +1100']" - page.should have_content 'My coordinator' + # When I go to its edit page + login_to_admin_section + click_link 'Order Cycles' + click_link oc.name + wait_until { page.find('#order_cycle_name').value.present? } - page.should have_selector 'td.suppliers', text: 'My supplier' - page.should have_selector 'td.distributors', text: 'My distributor' + # And I update it + fill_in 'order_cycle_name', with: 'Plums & Avos' + fill_in 'order_cycle_orders_open_at', with: order_cycle_opening_time + fill_in 'order_cycle_orders_close_at', with: order_cycle_closing_time - # And it should have some fees - oc = OrderCycle.last - oc.exchanges.incoming.first.enterprise_fees.should == [supplier_fee] - oc.coordinator_fees.should == [coordinator_fee] - oc.exchanges.outgoing.first.enterprise_fees.should == [distributor_fee] + # CAN'T CHANGE COORDINATOR ANYMORE + # select 'My coordinator', from: 'order_cycle_coordinator_id' - # And it should have some variants selected - oc.exchanges.first.variants.count.should == 2 - oc.exchanges.last.variants.count.should == 2 + # And I configure some coordinator fees + click_button 'Add coordinator fee' + select 'Coord fee 1', from: 'order_cycle_coordinator_fee_0_id' + click_button 'Add coordinator fee' + click_button 'Add coordinator fee' + click_link 'order_cycle_coordinator_fee_2_remove' + select 'Coord fee 2', from: 'order_cycle_coordinator_fee_1_id' - # And my receival and pickup time and instructions should have been saved - exchange = oc.exchanges.incoming.first - exchange.receival_instructions.should == 'receival instructions' + # And I add a supplier and some products + select 'My supplier', from: 'new_supplier_id' + click_button 'Add supplier' + page.all("table.exchanges tr.supplier td.products").each { |e| e.click } - exchange = oc.exchanges.outgoing.first - exchange.pickup_time.should == 'pickup time' - exchange.pickup_instructions.should == 'pickup instructions' - exchange.tag_list.should == ['wholesale'] + page.should have_selector "#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true + page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).click # uncheck (with visible:true filter) + check "order_cycle_incoming_exchange_2_variants_#{v1.id}" + check "order_cycle_incoming_exchange_2_variants_#{v2.id}" + + # And I configure some supplier fees + within("tr.supplier-#{supplier.id}") { click_button 'Add fee' } + select 'My supplier', from: 'order_cycle_incoming_exchange_2_enterprise_fees_0_enterprise_id' + select 'Supplier fee 1', from: 'order_cycle_incoming_exchange_2_enterprise_fees_0_enterprise_fee_id' + within("tr.supplier-#{supplier.id}") { click_button 'Add fee' } + within("tr.supplier-#{supplier.id}") { click_button 'Add fee' } + click_link 'order_cycle_incoming_exchange_2_enterprise_fees_0_remove' + select 'My supplier', from: 'order_cycle_incoming_exchange_2_enterprise_fees_0_enterprise_id' + select 'Supplier fee 2', from: 'order_cycle_incoming_exchange_2_enterprise_fees_0_enterprise_fee_id' + + # And I add a distributor and some products + select 'My distributor', from: 'new_distributor_id' + click_button 'Add distributor' + + fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'New time 0' + fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'New instructions 0' + fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'New time 1' + fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'New instructions 1' + fill_in 'order_cycle_outgoing_exchange_2_pickup_time', with: 'New time 2' + fill_in 'order_cycle_outgoing_exchange_2_pickup_instructions', with: 'New instructions 2' + + page.find("table.exchanges tr.distributor-#{distributor.id} td.tags").click + within ".exchange-tags" do + find(:css, "tags-input .tags input").set "wholesale\n" + end + + page.all("table.exchanges tr.distributor td.products").each { |e| e.click } + + uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}" + check "order_cycle_outgoing_exchange_2_variants_#{v2.id}" + + # And I configure some distributor fees + within("tr.distributor-#{distributor.id}") { click_button 'Add fee' } + select 'My distributor', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_id' + select 'Distributor fee 1', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_fee_id' + within("tr.distributor-#{distributor.id}") { click_button 'Add fee' } + within("tr.distributor-#{distributor.id}") { click_button 'Add fee' } + click_link 'order_cycle_outgoing_exchange_2_enterprise_fees_0_remove' + select 'My distributor', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_id' + select 'Distributor fee 2', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_fee_id' + + # And I click Update + expect(page).to have_selector "#save-bar" + click_button 'Update and Close' + + # Then my order cycle should have been updated + page.should have_content 'Your order cycle has been updated.' + + page.should have_selector 'a', text: 'Plums & Avos' + + page.should have_selector "input[value='#{order_cycle_opening_time}']" + page.should have_selector "input[value='#{order_cycle_closing_time}']" + page.should have_content coordinator.name + + page.should have_selector 'td.suppliers', text: 'My supplier' + page.should have_selector 'td.distributors', text: 'My distributor' + + # And my coordinator fees should have been configured + OrderCycle.last.coordinator_fee_ids.should match_array [coordinator_fee1.id, coordinator_fee2.id] + + # And my supplier fees should have been configured + OrderCycle.last.exchanges.incoming.last.enterprise_fee_ids.should == [supplier_fee2.id] + + # And my distributor fees should have been configured + OrderCycle.last.exchanges.outgoing.last.enterprise_fee_ids.should == [distributor_fee2.id] + + # And my tags should have been save + OrderCycle.last.exchanges.outgoing.last.tag_list.should == ['wholesale'] + + # And it should have some variants selected + selected_initial_variants = initial_variants.take initial_variants.size - 1 + OrderCycle.last.variants.map(&:id).should match_array (selected_initial_variants.map(&:id) + [v1.id, v2.id]) + + # And the collection details should have been updated + OrderCycle.last.exchanges.where(pickup_time: 'New time 0', pickup_instructions: 'New instructions 0').should be_present + OrderCycle.last.exchanges.where(pickup_time: 'New time 1', pickup_instructions: 'New instructions 1').should be_present + end end - scenario "editing an order cycle" do # Given an order cycle with all the settings oc = create(:order_cycle) @@ -273,144 +412,6 @@ feature %q{ page.should_not have_selector 'table.exchanges tr.supplier' end - - scenario "updating an order cycle", js: true do - # Given an order cycle with all the settings - oc = create(:order_cycle) - initial_variants = oc.variants.sort_by &:id - - # And a coordinating, supplying and distributing enterprise with some products with variants - coordinator = oc.coordinator - supplier = create(:supplier_enterprise, name: 'My supplier') - distributor = create(:distributor_enterprise, name: 'My distributor', with_payment_and_shipping: true) - product = create(:product, supplier: supplier) - v1 = create(:variant, product: product) - v2 = create(:variant, product: product) - - # Relationships required for interface to work - create(:enterprise_relationship, parent: supplier, child: coordinator, permissions_list: [:add_to_order_cycle]) - create(:enterprise_relationship, parent: distributor, child: coordinator, permissions_list: [:add_to_order_cycle]) - create(:enterprise_relationship, parent: supplier, child: distributor, permissions_list: [:add_to_order_cycle]) - - # And some enterprise fees - supplier_fee1 = create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee 1') - supplier_fee2 = create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee 2') - coordinator_fee1 = create(:enterprise_fee, enterprise: coordinator, name: 'Coord fee 1') - coordinator_fee2 = create(:enterprise_fee, enterprise: coordinator, name: 'Coord fee 2') - distributor_fee1 = create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee 1') - distributor_fee2 = create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee 2') - - # When I go to its edit page - login_to_admin_section - click_link 'Order Cycles' - click_link oc.name - wait_until { page.find('#order_cycle_name').value.present? } - - # And I update it - fill_in 'order_cycle_name', with: 'Plums & Avos' - fill_in 'order_cycle_orders_open_at', with: '2040-11-06 06:00:00' - fill_in 'order_cycle_orders_close_at', with: '2040-11-13 17:00:00' - - # CAN'T CHANGE COORDINATOR ANYMORE - # select 'My coordinator', from: 'order_cycle_coordinator_id' - - # And I configure some coordinator fees - click_button 'Add coordinator fee' - select 'Coord fee 1', from: 'order_cycle_coordinator_fee_0_id' - click_button 'Add coordinator fee' - click_button 'Add coordinator fee' - click_link 'order_cycle_coordinator_fee_2_remove' - select 'Coord fee 2', from: 'order_cycle_coordinator_fee_1_id' - - # And I add a supplier and some products - select 'My supplier', from: 'new_supplier_id' - click_button 'Add supplier' - page.all("table.exchanges tr.supplier td.products").each { |e| e.click } - - page.should have_selector "#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true - page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).click # uncheck (with visible:true filter) - check "order_cycle_incoming_exchange_2_variants_#{v1.id}" - check "order_cycle_incoming_exchange_2_variants_#{v2.id}" - - # And I configure some supplier fees - within("tr.supplier-#{supplier.id}") { click_button 'Add fee' } - select 'My supplier', from: 'order_cycle_incoming_exchange_2_enterprise_fees_0_enterprise_id' - select 'Supplier fee 1', from: 'order_cycle_incoming_exchange_2_enterprise_fees_0_enterprise_fee_id' - within("tr.supplier-#{supplier.id}") { click_button 'Add fee' } - within("tr.supplier-#{supplier.id}") { click_button 'Add fee' } - click_link 'order_cycle_incoming_exchange_2_enterprise_fees_0_remove' - select 'My supplier', from: 'order_cycle_incoming_exchange_2_enterprise_fees_0_enterprise_id' - select 'Supplier fee 2', from: 'order_cycle_incoming_exchange_2_enterprise_fees_0_enterprise_fee_id' - - # And I add a distributor and some products - select 'My distributor', from: 'new_distributor_id' - click_button 'Add distributor' - - fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'New time 0' - fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'New instructions 0' - fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'New time 1' - fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'New instructions 1' - fill_in 'order_cycle_outgoing_exchange_2_pickup_time', with: 'New time 2' - fill_in 'order_cycle_outgoing_exchange_2_pickup_instructions', with: 'New instructions 2' - - page.find("table.exchanges tr.distributor-#{distributor.id} td.tags").click - within ".exchange-tags" do - find(:css, "tags-input .tags input").set "wholesale\n" - end - - page.all("table.exchanges tr.distributor td.products").each { |e| e.click } - - uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}" - check "order_cycle_outgoing_exchange_2_variants_#{v2.id}" - - # And I configure some distributor fees - within("tr.distributor-#{distributor.id}") { click_button 'Add fee' } - select 'My distributor', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_id' - select 'Distributor fee 1', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_fee_id' - within("tr.distributor-#{distributor.id}") { click_button 'Add fee' } - within("tr.distributor-#{distributor.id}") { click_button 'Add fee' } - click_link 'order_cycle_outgoing_exchange_2_enterprise_fees_0_remove' - select 'My distributor', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_id' - select 'Distributor fee 2', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_fee_id' - - # And I click Update - expect(page).to have_selector "#save-bar" - click_button 'Update and Close' - - # Then my order cycle should have been updated - page.should have_content 'Your order cycle has been updated.' - - page.should have_selector 'a', text: 'Plums & Avos' - - page.should have_selector "input[value='2040-11-06 06:00:00 +1100']" - page.should have_selector "input[value='2040-11-13 17:00:00 +1100']" - page.should have_content coordinator.name - - page.should have_selector 'td.suppliers', text: 'My supplier' - page.should have_selector 'td.distributors', text: 'My distributor' - - # And my coordinator fees should have been configured - OrderCycle.last.coordinator_fee_ids.should match_array [coordinator_fee1.id, coordinator_fee2.id] - - # And my supplier fees should have been configured - OrderCycle.last.exchanges.incoming.last.enterprise_fee_ids.should == [supplier_fee2.id] - - # And my distributor fees should have been configured - OrderCycle.last.exchanges.outgoing.last.enterprise_fee_ids.should == [distributor_fee2.id] - - # And my tags should have been save - OrderCycle.last.exchanges.outgoing.last.tag_list.should == ['wholesale'] - - # And it should have some variants selected - selected_initial_variants = initial_variants.take initial_variants.size - 1 - OrderCycle.last.variants.map(&:id).should match_array (selected_initial_variants.map(&:id) + [v1.id, v2.id]) - - # And the collection details should have been updated - OrderCycle.last.exchanges.where(pickup_time: 'New time 0', pickup_instructions: 'New instructions 0').should be_present - OrderCycle.last.exchanges.where(pickup_time: 'New time 1', pickup_instructions: 'New instructions 1').should be_present - end - - scenario "updating many order cycle opening/closing times at once", js: true do # Given three order cycles oc1 = create(:simple_order_cycle) @@ -907,8 +908,8 @@ feature %q{ # Then my order cycle should have been created page.should have_content 'Your order cycle has been created.' page.should have_selector 'a', text: 'Plums & Avos' - page.should have_selector "input[value='2040-10-17 06:00:00 +1100']" - page.should have_selector "input[value='2040-10-24 17:00:00 +1100']" + page.should have_selector "input[value='#{Time.zone.local(2040, 10, 17, 06, 00, 00)}']" + page.should have_selector "input[value='#{Time.zone.local(2040, 10, 24, 17, 00, 00)}']" # And it should have some variants selected oc = OrderCycle.last @@ -995,8 +996,8 @@ feature %q{ # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' page.should have_selector 'a', text: 'Plums & Avos' - page.should have_selector "input[value='2040-10-17 06:00:00 +1100']" - page.should have_selector "input[value='2040-10-24 17:00:00 +1100']" + page.should have_selector "input[value='#{Time.zone.local(2040, 10, 17, 06, 00, 00)}']" + page.should have_selector "input[value='#{Time.zone.local(2040, 10, 24, 17, 00, 00)}']" # And it should have a variant selected oc = OrderCycle.last diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index cf1598f3fe..5b0a215dd8 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -22,6 +22,14 @@ feature %q{ create :check_payment, order: @order, amount: @order.total end + def new_order_with_distribution(distributor, order_cycle) + visit 'admin/orders/new' + page.should have_selector('#s2id_order_distributor_id') + select2_select distributor.name, from: 'order_distributor_id' + select2_select order_cycle.name, from: 'order_order_cycle_id' + click_button 'Next' + end + scenario "creating an order with distributor and order cycle" do distributor_disabled = create(:distributor_enterprise) create(:simple_order_cycle, name: 'Two') @@ -43,7 +51,10 @@ feature %q{ select2_select @distributor.name, from: 'order_distributor_id' page.should have_select2 'order_order_cycle_id', options: ['One (open)'] select2_select @order_cycle.name, from: 'order_order_cycle_id' + click_button 'Next' + # it suppresses validation errors when setting distribution + page.should_not have_selector '#errorExplanation' page.should have_content 'ADD PRODUCT' targetted_select2_search @product.name, from: '#add_variant_id', dropdown_css: '.select2-drop' click_link 'Add' @@ -75,6 +86,8 @@ feature %q{ scenario "displays error when incorrect distribution for products is chosen" do d = create(:distributor_enterprise) oc = create(:simple_order_cycle, distributors: [d]) + puts d.name + puts @distributor.name @order.state = 'cart'; @order.completed_at = nil; @order.save @@ -89,7 +102,6 @@ feature %q{ select2_select oc.name, from: 'order_order_cycle_id' click_button 'Update And Recalculate Fees' - page.should have_content "Distributor or order cycle cannot supply the products in your cart" end @@ -127,12 +139,11 @@ feature %q{ # When I create a new order quick_login_as @user - visit spree.admin_path - - visit '/admin/orders' - click_link 'New Order' - select2_select @distributor.name, from: 'order_distributor_id' - select2_select @order_cycle.name, from: 'order_order_cycle_id' + new_order_with_distribution(@distributor, @order_cycle) + # visit '/admin/orders' + # click_link 'New Order' + # select2_select @distributor.name, from: 'order_distributor_id' + # select2_select @order_cycle.name, from: 'order_order_cycle_id' targetted_select2_search @product.name, from: '#add_variant_id', dropdown_css: '.select2-drop' click_link 'Add' page.has_selector? "table.index tbody[data-hook='admin_order_form_line_items'] tr" # Wait for JS @@ -142,7 +153,7 @@ feature %q{ # And I select that customer's email address and save the order targetted_select2_search @customer.email, from: '#customer_search_override', dropdown_css: '.select2-drop' click_button 'Continue' - within('h1.page-title') { page.should have_content "Shipments" } + page.should have_selector "h1.page-title", text: "Shipments" # Then their addresses should be associated with the order order = Spree::Order.last @@ -212,11 +223,7 @@ feature %q{ end scenario "creating an order with distributor and order cycle" do - visit '/admin/orders' - click_link 'New Order' - - select2_select distributor1.name, from: 'order_distributor_id' - select2_select order_cycle1.name, from: 'order_order_cycle_id' + new_order_with_distribution(distributor1, order_cycle1) expect(page).to have_content 'ADD PRODUCT' targetted_select2_search product.name, from: '#add_variant_id', dropdown_css: '.select2-drop' @@ -241,6 +248,7 @@ feature %q{ end + # Working around intermittent click failing # Possible causes of failure: # - the link moves diff --git a/spec/features/admin/variant_overrides_spec.rb b/spec/features/admin/variant_overrides_spec.rb index d21c0998e5..44fa258eb3 100644 --- a/spec/features/admin/variant_overrides_spec.rb +++ b/spec/features/admin/variant_overrides_spec.rb @@ -315,6 +315,30 @@ feature %q{ end end end + + end + + describe "when manually placing an order" do + let!(:order_cycle) { create(:order_cycle_with_overrides, name: "Overidden") } + + before do + dist = order_cycle.distributors.first + login_to_admin_section + visit 'admin/orders/new' + select2_select dist.name, from: 'order_distributor_id' + page.should have_select2 'order_order_cycle_id', with_options: ['Overidden (open)'] + select2_select order_cycle.name, from: 'order_order_cycle_id' + click_button 'Next' + end + + # Reproducing a bug, issue #1446 + it "shows the overridden price" do + product = order_cycle.products.first + targetted_select2_search product.name, from: '#add_variant_id', dropdown_css: '.select2-drop' + click_link 'Add' + page.has_selector? "table.index tbody[data-hook='admin_order_form_line_items'] tr" # Wait for JS + page.should have_content product.variants.first.variant_overrides.first.price + end end describe "when inventory_items do not exist for variants" do diff --git a/spec/features/admin/variants_spec.rb b/spec/features/admin/variants_spec.rb index 7303e81766..67123f222b 100644 --- a/spec/features/admin/variants_spec.rb +++ b/spec/features/admin/variants_spec.rb @@ -16,7 +16,7 @@ feature %q{ visit spree.admin_product_variants_path p click_link 'New Variant' - fill_in 'variant_unit_value', with: '1' + fill_in 'unit_value_human', with: '1' fill_in 'variant_unit_description', with: 'foo' click_button 'Create' @@ -43,11 +43,11 @@ feature %q{ expect(page).to_not have_selector "div[data-hook='presentation'] input" # And I should see unit value and description fields for the unit-related option value - page.should have_field "variant_unit_value", with: "1" + page.should have_field "unit_value_human", with: "1" page.should have_field "variant_unit_description", with: "foo" # When I update the fields and save the variant - fill_in "variant_unit_value", with: "123" + fill_in "unit_value_human", with: "123" fill_in "variant_unit_description", with: "bar" click_button 'Update' page.should have_content %Q(Variant "#{p.name}" has been successfully updated!) 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/registration_spec.rb b/spec/features/consumer/registration_spec.rb index c4e7af36ee..4a7b815737 100644 --- a/spec/features/consumer/registration_spec.rb +++ b/spec/features/consumer/registration_spec.rb @@ -172,17 +172,4 @@ feature "Registration", js: true do end expect(page).to have_content content end - - def perform_and_ensure(action, *args, assertion) - # Buttons/Links/Checkboxes appear to be unresponsive for a while - # so keep clicking them until assertion is satified - using_wait_time 0.5 do - 10.times do - send(action, *args) - return if assertion.call - end - # Only make it here if we have tried 10 times - expect(assertion.call).to be true - end - end end diff --git a/spec/features/consumer/shopping/cart_spec.rb b/spec/features/consumer/shopping/cart_spec.rb index b604769502..99c0f75450 100644 --- a/spec/features/consumer/shopping/cart_spec.rb +++ b/spec/features/consumer/shopping/cart_spec.rb @@ -32,10 +32,10 @@ feature "full-page cart", js: true do it "rounds fee calculations correctly" do # $0.86 + 20% = $1.032 # Fractional cents should be immediately rounded down and not carried through - expect(page).to have_selector '.cart-item-price', text: '$1.03' - expect(page).to have_selector '.cart-item-total', text: '$8.24' - expect(page).to have_selector '.order-total.item-total', text: '$8.24' - expect(page).to have_selector '.order-total.grand-total', text: '$8.24' + expect(page).to have_selector '.cart-item-price', text: with_currency(1.03) + expect(page).to have_selector '.cart-item-total', text: with_currency(8.24) + expect(page).to have_selector '.order-total.item-total', text: with_currency(8.24) + expect(page).to have_selector '.order-total.grand-total', text: with_currency(8.24) end end @@ -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 d9c9e8bf8f..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 @@ -135,9 +153,9 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do toggle_shipping choose sm2.name - page.should have_selector 'orderdetails .cart-total', text: "$11.23" - page.should have_selector 'orderdetails .shipping', text: "$4.56" - page.should have_selector 'orderdetails .total', text: "$15.79" + page.should have_selector 'orderdetails .cart-total', text: with_currency(11.23) + page.should have_selector 'orderdetails .shipping', text: with_currency(4.56) + page.should have_selector 'orderdetails .total', text: with_currency(15.79) # Tax should not be displayed in checkout, as the customer's choice of shipping method # affects the tax and we haven't written code to live-update the tax amount when they @@ -269,7 +287,7 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do # + shipping tax ($ 4.56 @ 25% = $0.91) # = $1.93 page.should have_content "(includes tax)" - page.should have_content "$1.93" + page.should have_content with_currency(1.93) end context "with basic details filled" do @@ -331,14 +349,14 @@ feature "As a consumer I want to check out my cart", js: true, retry: 3 do context "when we are charged a payment method fee (transaction fee)" do it "creates a payment including the transaction fee" do # Selecting the transaction fee, it is displayed - expect(page).to have_selector ".transaction-fee td", text: "$0.00" - expect(page).to have_selector ".total", text: "$11.23" + expect(page).to have_selector ".transaction-fee td", text: with_currency(0.00) + expect(page).to have_selector ".total", text: with_currency(11.23) toggle_payment - choose "#{pm2.name} ($5.67)" + choose "#{pm2.name} (#{with_currency(5.67)})" - expect(page).to have_selector ".transaction-fee td", text: "$5.67" - expect(page).to have_selector ".total", text: "$16.90" + expect(page).to have_selector ".transaction-fee td", text: with_currency(5.67) + expect(page).to have_selector ".total", text: with_currency(16.90) place_order expect(page).to have_content "Your order has been processed successfully" @@ -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/products_spec.rb b/spec/features/consumer/shopping/products_spec.rb index d8b0c2b792..c53027b12d 100644 --- a/spec/features/consumer/shopping/products_spec.rb +++ b/spec/features/consumer/shopping/products_spec.rb @@ -32,7 +32,7 @@ feature "As a consumer I want to view products", js: true do visit shop_path select "monday", :from => "order_cycle_id" - open_product_modal product + perform_and_ensure(:click_link, product.name, lambda{ page.has_selector?('.reveal-modal')}) modal_should_be_open_for product within(".reveal-modal") do @@ -47,7 +47,7 @@ feature "As a consumer I want to view products", js: true do visit shop_path select "monday", :from => "order_cycle_id" - open_product_modal product + perform_and_ensure(:click_link, product.name, lambda{ page.has_selector?('.reveal-modal')}) modal_should_be_open_for product within(".reveal-modal") do @@ -57,4 +57,4 @@ feature "As a consumer I want to view products", js: true do end end end -end \ No newline at end of file +end diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index e11f929f6a..2445bc6ad0 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -95,23 +95,23 @@ feature "As a consumer I want to shop with a distributor", js: true do # -- Selecting an order cycle visit shop_path select "turtles", from: "order_cycle_id" - page.should have_content "$1020.99" + page.should have_content with_currency(1020.99) # -- Cart shows correct price fill_in "variants[#{variant.id}]", with: 1 show_cart - within("li.cart") { page.should have_content "$1020.99" } + within("li.cart") { page.should have_content with_currency(1020.99) } # -- Changing order cycle select "frogs", from: "order_cycle_id" - page.should have_content "$19.99" + page.should have_content with_currency(19.99) # -- Cart should be cleared # ng-animate means that the old product row is likely to be present, so we explicitly # fill in the quantity in the incoming row page.should_not have_selector "tr.product-cart" within('product.ng-enter') { fill_in "variants[#{variant.id}]", with: 1 } - within("li.cart") { page.should have_content "$19.99" } + within("li.cart") { page.should have_content with_currency(19.99) } end describe "declining to clear the cart" do @@ -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 @@ -164,15 +190,15 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path # Page should not have product.price (with or without fee) - page.should_not have_price "$10.00" - page.should_not have_price "$33.00" + page.should_not have_price with_currency(10.00) + page.should_not have_price with_currency(33.00) # Page should have variant prices (with fee) - page.should have_price "$43.00" - page.should have_price "$53.00" + page.should have_price with_currency(43.00) + page.should have_price with_currency(53.00) # Product price should be listed as the lesser of these - page.should have_price "$43.00" + page.should have_price with_currency(43.00) end it "filters search results properly" do diff --git a/spec/features/consumer/shopping/variant_overrides_spec.rb b/spec/features/consumer/shopping/variant_overrides_spec.rb index f4ee8e6275..49d1b0b2e4 100644 --- a/spec/features/consumer/shopping/variant_overrides_spec.rb +++ b/spec/features/consumer/shopping/variant_overrides_spec.rb @@ -40,8 +40,8 @@ feature "shopping with variant overrides defined", js: true, retry: 3 do describe "viewing products" do it "shows the overridden price" do - page.should_not have_price "$12.22" # $11.11 + 10% fee - page.should have_price "$61.11" + page.should_not have_price with_currency(12.22) # $11.11 + 10% fee + page.should have_price with_currency(61.11) end it "looks up stock from the override" do @@ -59,9 +59,9 @@ feature "shopping with variant overrides defined", js: true, retry: 3 do it "calculates fees correctly" do page.find("#variant-#{v1.id} .graph-button").click page.find(".price_breakdown a").click - page.should have_selector 'li.cost div', text: '$55.55' - page.should have_selector 'li.packing-fee div', text: '$5.56' - page.should have_selector 'li.total div', text: '= $61.11' + page.should have_selector 'li.cost div', text: with_currency(55.55) + page.should have_selector 'li.packing-fee div', text: with_currency(5.56) + page.should have_selector 'li.total div', text: "= #{with_currency(61.11)}" end it "shows the correct prices when products are in the cart" do @@ -69,7 +69,7 @@ feature "shopping with variant overrides defined", js: true, retry: 3 do show_cart wait_until_enabled 'li.cart a.button' visit shop_path - page.should_not have_price '$12.22' + page.should_not have_price with_currency(12.22) end # The two specs below reveal an unrelated issue with fee calculation. See: @@ -79,29 +79,29 @@ feature "shopping with variant overrides defined", js: true, retry: 3 do fill_in "variants[#{v1.id}]", with: "2" show_cart page.should have_selector "#cart-variant-#{v1.id} .quantity", text: '2' - page.should have_selector "#cart-variant-#{v1.id} .price", text: '$61.11' - page.should have_selector "#cart-variant-#{v1.id} .total-price", text: '$122.22' + page.should have_selector "#cart-variant-#{v1.id} .price", text: with_currency(61.11) + page.should have_selector "#cart-variant-#{v1.id} .total-price", text: with_currency(122.22) end it "shows the correct prices in the shopping cart" do fill_in "variants[#{v1.id}]", with: "2" add_to_cart - page.should have_selector "tr.line-item.variant-#{v1.id} .cart-item-price", text: '$61.11' + page.should have_selector "tr.line-item.variant-#{v1.id} .cart-item-price", text: with_currency(61.11) page.should have_field "order[line_items_attributes][0][quantity]", with: '2' - page.should have_selector "tr.line-item.variant-#{v1.id} .cart-item-total", text: '$122.22' + page.should have_selector "tr.line-item.variant-#{v1.id} .cart-item-total", text: with_currency(122.22) - page.should have_selector "#edit-cart .item-total", text: '$122.22' - page.should have_selector "#edit-cart .grand-total", text: '$122.22' + page.should have_selector "#edit-cart .item-total", text: with_currency(122.22) + page.should have_selector "#edit-cart .grand-total", text: with_currency(122.22) end it "shows the correct prices in the checkout" do fill_in "variants[#{v1.id}]", with: "2" click_checkout - page.should have_selector 'form.edit_order .cart-total', text: '$122.22' - page.should have_selector 'form.edit_order .shipping', text: '$0.00' - page.should have_selector 'form.edit_order .total', text: '$122.22' + page.should have_selector 'form.edit_order .cart-total', text: with_currency(122.22) + page.should have_selector 'form.edit_order .shipping', text: with_currency(0.00) + page.should have_selector 'form.edit_order .total', text: with_currency(122.22) end end diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb index 082bf57d79..fa9aa909fc 100644 --- a/spec/features/consumer/shops_spec.rb +++ b/spec/features/consumer/shops_spec.rb @@ -125,7 +125,7 @@ feature 'Shops', js: true do describe "closed shops" do it "shows taxons for any order cycle" do visit shops_path - click_link 'Show Closed Shops' + click_link_and_ensure('Show Closed Shops', -> { page.has_selector? '.active_table_node'}) expand_active_table_node shop.name expect(page).to have_selector '.fat-taxons', text: 'Closed' end diff --git a/spec/helpers/admin/business_model_configuration_helper_spec.rb b/spec/helpers/admin/business_model_configuration_helper_spec.rb index 632aec302b..329029b6a6 100644 --- a/spec/helpers/admin/business_model_configuration_helper_spec.rb +++ b/spec/helpers/admin/business_model_configuration_helper_spec.rb @@ -20,12 +20,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "$10 + 5.0% OF SALES, CAPPED AT $20 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} + 5.0% OF SALES, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "$10 + 5.0% OF SALES PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} + 5.0% OF SALES PER MONTH, PLUS GST" } end end @@ -34,12 +34,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "$10 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "$10 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} PER MONTH, PLUS GST" } end end end @@ -52,7 +52,7 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES, CAPPED AT $20 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do @@ -77,7 +77,7 @@ describe Admin::BusinessModelConfigurationHelper do end end - context "when minimum billable turnover is $100" do + context "when minimum billable turnover is 100" do before { Spree::Config.set(:minimum_billable_turnover, 100) } context "when a fixed cost is included" do @@ -88,12 +88,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "$10 + 5.0% OF SALES ONCE TURNOVER EXCEEDS $100, CAPPED AT $20 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} + 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)}, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "$10 + 5.0% OF SALES ONCE TURNOVER EXCEEDS $100 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} + 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)} PER MONTH, PLUS GST" } end end @@ -102,12 +102,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "$10 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "$10 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} PER MONTH, PLUS GST" } end end end @@ -120,12 +120,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES ONCE TURNOVER EXCEEDS $100, CAPPED AT $20 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)}, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES ONCE TURNOVER EXCEEDS $100 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)} PER MONTH, PLUS GST" } end end @@ -160,12 +160,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "$10 + 5.0% OF SALES, CAPPED AT $20 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} + 5.0% OF SALES, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "$10 + 5.0% OF SALES PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} + 5.0% OF SALES PER MONTH" } end end @@ -174,12 +174,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "$10 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "$10 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} PER MONTH" } end end end @@ -192,7 +192,7 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES, CAPPED AT $20 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do @@ -217,7 +217,7 @@ describe Admin::BusinessModelConfigurationHelper do end end - context "when minimum billable turnover is $100" do + context "when minimum billable turnover is 100" do before { Spree::Config.set(:minimum_billable_turnover, 100) } context "when a fixed cost is included" do @@ -228,12 +228,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "$10 + 5.0% OF SALES ONCE TURNOVER EXCEEDS $100, CAPPED AT $20 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} + 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)}, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "$10 + 5.0% OF SALES ONCE TURNOVER EXCEEDS $100 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} + 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)} PER MONTH" } end end @@ -242,12 +242,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "$10 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "$10 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "#{with_currency(10, no_cents: true)} PER MONTH" } end end end @@ -260,12 +260,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES ONCE TURNOVER EXCEEDS $100, CAPPED AT $20 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)}, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES ONCE TURNOVER EXCEEDS $100 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)} PER MONTH" } end end @@ -304,12 +304,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 + 5.0% OF SALES, CAPPED AT $20 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} + 5.0% OF SALES, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 + 5.0% OF SALES PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} + 5.0% OF SALES PER MONTH, PLUS GST" } end end @@ -318,12 +318,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} PER MONTH, PLUS GST" } end end end @@ -336,7 +336,7 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES, CAPPED AT $20 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do @@ -361,7 +361,7 @@ describe Admin::BusinessModelConfigurationHelper do end end - context "when minimum billable turnover is $100" do + context "when minimum billable turnover is 100" do before { Spree::Config.set(:minimum_billable_turnover, 100) } context "when a fixed cost is included" do @@ -372,12 +372,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 + 5.0% OF SALES ONCE TURNOVER EXCEEDS $100, CAPPED AT $20 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} + 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)}, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 + 5.0% OF SALES ONCE TURNOVER EXCEEDS $100 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} + 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)} PER MONTH, PLUS GST" } end end @@ -386,12 +386,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} PER MONTH, PLUS GST" } end end end @@ -404,12 +404,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES ONCE TURNOVER EXCEEDS $100, CAPPED AT $20 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)}, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH, PLUS GST" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES ONCE TURNOVER EXCEEDS $100 PER MONTH, PLUS GST" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)} PER MONTH, PLUS GST" } end end @@ -444,12 +444,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 + 5.0% OF SALES, CAPPED AT $20 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} + 5.0% OF SALES, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 + 5.0% OF SALES PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} + 5.0% OF SALES PER MONTH" } end end @@ -458,12 +458,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} PER MONTH" } end end end @@ -476,7 +476,7 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES, CAPPED AT $20 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do @@ -501,7 +501,7 @@ describe Admin::BusinessModelConfigurationHelper do end end - context "when minimum billable turnover is $100" do + context "when minimum billable turnover is 100" do before { Spree::Config.set(:minimum_billable_turnover, 100) } context "when a fixed cost is included" do @@ -512,12 +512,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 + 5.0% OF SALES ONCE TURNOVER EXCEEDS $100, CAPPED AT $20 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} + 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)}, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 + 5.0% OF SALES ONCE TURNOVER EXCEEDS $100 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} + 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)} PER MONTH" } end end @@ -526,12 +526,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN $10 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN #{with_currency(10, no_cents: true)} PER MONTH" } end end end @@ -544,12 +544,12 @@ describe Admin::BusinessModelConfigurationHelper do context "when the bill is capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 20) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES ONCE TURNOVER EXCEEDS $100, CAPPED AT $20 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)}, CAPPED AT #{with_currency(20, no_cents: true)} PER MONTH" } end context "when the bill is not capped" do before { Spree::Config.set(:account_invoices_monthly_cap, 0) } - it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES ONCE TURNOVER EXCEEDS $100 PER MONTH" } + it { expect(helper.monthly_bill_description).to eq "FREE TRIAL THEN 5.0% OF SALES ONCE TURNOVER EXCEEDS #{with_currency(100, no_cents: true)} PER MONTH" } end end diff --git a/spec/helpers/products_helper_spec.rb b/spec/helpers/products_helper_spec.rb index e1c61ced5e..3415757722 100644 --- a/spec/helpers/products_helper_spec.rb +++ b/spec/helpers/products_helper_spec.rb @@ -4,13 +4,13 @@ module Spree describe ProductsHelper do it "displays variant price differences as absolute, not relative values" do variant = make_variant_stub(10.00, 10.00) - helper.variant_price_diff(variant).should == "($10.00)" + helper.variant_price_diff(variant).should == "(#{with_currency(10.00)})" variant = make_variant_stub(10.00, 15.55) - helper.variant_price_diff(variant).should == "($15.55)" + helper.variant_price_diff(variant).should == "(#{with_currency(15.55)})" variant = make_variant_stub(10.00, 5.55) - helper.variant_price_diff(variant).should == "($5.55)" + helper.variant_price_diff(variant).should == "(#{with_currency(5.55)})" end private diff --git a/spec/helpers/spree/orders_helper_spec.rb b/spec/helpers/spree/orders_helper_spec.rb new file mode 100644 index 0000000000..9c1787f16e --- /dev/null +++ b/spec/helpers/spree/orders_helper_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe Spree::OrdersHelper, type: :helper do + + describe "#changeable_orders" do + let(:complete_orders) { double(:complete_orders, where: "some_orders") } + + before do + allow(Spree::Order).to receive(:complete) { complete_orders } + allow(helper).to receive(:spree_current_user) { spree_current_user } + allow(helper).to receive(:current_distributor) { current_distributor } + allow(helper).to receive(:current_order_cycle) { current_order_cycle } + end + + context "when a current_user is defined" do + let(:spree_current_user) { double(:spree_current_user, id: 1) } + + context "when a current_distributor is defined" do + let(:current_distributor) { double(:current_distributor, id: 1) } + + context "when a current_order_cycle is defined" do + let(:current_order_cycle) { double(:current_order_cycle, id: 1) } + + context "when the current_distributor allows order changes" do + before { allow(current_distributor).to receive(:allow_order_changes?) { true} } + it { expect(helper.changeable_orders).to eq "some_orders" } + end + + context "when the current_distributor does not allow order changes" do + before { allow(current_distributor).to receive(:allow_order_changes?) { false } } + it { expect(helper.changeable_orders).to eq [] } + end + end + + context "when a current_order_cycle is not defined" do + let(:current_order_cycle) { nil } + it { expect(helper.changeable_orders).to eq [] } + end + end + + context "when a current_distributor is not defined" do + let(:current_distributor) { nil } + it { expect(helper.changeable_orders).to eq [] } + end + end + + context "when spree_current_user is not defined" do + let(:spree_current_user) { nil } + it { expect(helper.changeable_orders).to eq [] } + 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/adjustment_spec.rb b/spec/models/spree/adjustment_spec.rb index 64113c6290..2292381009 100644 --- a/spec/models/spree/adjustment_spec.rb +++ b/spec/models/spree/adjustment_spec.rb @@ -288,12 +288,12 @@ module Spree let!(:tax_rate) { create(:tax_rate, calculator: Spree::Calculator::DefaultTax.new, amount: 0.25) } let!(:other_tax_rate) { create(:tax_rate, calculator: Spree::Calculator::DefaultTax.new, amount: 0.3) } - it "returns nil if there is no included tax" do - adjustment_without_tax.find_closest_tax_rate_from_included_tax.should == nil + it "returns [] if there is no included tax" do + adjustment_without_tax.find_closest_tax_rates_from_included_tax.should == [] end it "returns the most accurate tax rate" do - adjustment_with_tax.find_closest_tax_rate_from_included_tax.should == tax_rate + adjustment_with_tax.find_closest_tax_rates_from_included_tax.should == [tax_rate] 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) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f1150cc485..00e0b152ba 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -97,6 +97,7 @@ RSpec.configure do |config| config.include Rails.application.routes.url_helpers config.include Spree::UrlHelpers config.include Spree::CheckoutHelpers + config.include Spree::MoneyHelper config.include Spree::Core::TestingSupport::ControllerRequests, :type => :controller config.include Devise::TestHelpers, :type => :controller config.extend Spree::Api::TestingSupport::Setup, :type => :controller diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index bca12c46ab..e3460b935d 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -170,6 +170,19 @@ module WebHelper page.evaluate_script "jQuery('#{selector}').select2('close');" end + def perform_and_ensure(action, *args, assertion) + # Buttons/Links/Checkboxes can be unresponsive for a while + # so keep clicking them until assertion is satified + using_wait_time 0.5 do + 10.times do + send(action, *args) + return if assertion.call + end + # Only make it here if we have tried 10 times + expect(assertion.call).to be true + end + end + private def wait_for_ajax wait_until { page.evaluate_script("$.active") == 0 } diff --git a/spec/support/spree/money_helper.rb b/spec/support/spree/money_helper.rb new file mode 100644 index 0000000000..f0ca8f73d9 --- /dev/null +++ b/spec/support/spree/money_helper.rb @@ -0,0 +1,7 @@ +module Spree + module MoneyHelper + def with_currency(amount, options = {}) + Spree::Money.new(amount, {delimiter: ''}.merge(options)).to_s # Delimiter is to match js localizeCurrency + end + end +end