diff --git a/app/assets/javascripts/admin/accounts_and_billing_settings/accounts_and_billing_settings.js.coffee b/app/assets/javascripts/admin/accounts_and_billing_settings/accounts_and_billing_settings.js.coffee index d4f544e300..06ee4fa4ef 100644 --- a/app/assets/javascripts/admin/accounts_and_billing_settings/accounts_and_billing_settings.js.coffee +++ b/app/assets/javascripts/admin/accounts_and_billing_settings/accounts_and_billing_settings.js.coffee @@ -1 +1 @@ -angular.module("admin.accounts_and_billing_settings", []) +angular.module("admin.accounts_and_billing_settings", ["admin.utils"]) diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 30c8e1a7b9..c0fa530626 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -23,6 +23,7 @@ //= require_tree ../templates/admin //= require ./admin //= require ./accounts_and_billing_settings/accounts_and_billing_settings +//= require ./business_model_configuration/business_model_configuration //= require ./customers/customers //= require ./dropdown/dropdown //= require ./enterprises/enterprises diff --git a/app/assets/javascripts/admin/business_model_configuration/business_model_configuration.js.coffee b/app/assets/javascripts/admin/business_model_configuration/business_model_configuration.js.coffee new file mode 100644 index 0000000000..cecb7c397e --- /dev/null +++ b/app/assets/javascripts/admin/business_model_configuration/business_model_configuration.js.coffee @@ -0,0 +1 @@ +angular.module("admin.businessModelConfiguration", ["admin.utils"]) diff --git a/app/assets/javascripts/admin/business_model_configuration/controllers/business_model_configuration_controller.js.coffee b/app/assets/javascripts/admin/business_model_configuration/controllers/business_model_configuration_controller.js.coffee new file mode 100644 index 0000000000..ca757c673d --- /dev/null +++ b/app/assets/javascripts/admin/business_model_configuration/controllers/business_model_configuration_controller.js.coffee @@ -0,0 +1,21 @@ +angular.module("admin.businessModelConfiguration").controller "BusinessModelConfigCtrl", ($scope, $filter) -> + $scope.turnover = 1000 + + $scope.bill = -> + return $filter('currency')(0) unless $scope.fixed || $scope.rate + Number($scope.fixed) + Number($scope.turnover) * Number($scope.rate) + + $scope.cappedBill = -> + return $scope.bill() if !$scope.cap? || Number($scope.cap) == 0 + Math.min($scope.bill(), Number($scope.cap)) + + $scope.capReached = -> + return "No" if !$scope.cap? || Number($scope.cap) == 0 + if $scope.bill() >= Number($scope.cap) then "Yes" else "No" + + $scope.includedTax = -> + return 0 if !$scope.taxRate? || Number($scope.taxRate) == 0 + ($scope.cappedBill() * Number($scope.taxRate)) + + $scope.total = -> + $scope.cappedBill() + $scope.includedTax() diff --git a/app/assets/javascripts/admin/enterprises/directives/monthly_pricing_description.js.coffee b/app/assets/javascripts/admin/enterprises/directives/monthly_pricing_description.js.coffee new file mode 100644 index 0000000000..6331fa2ca5 --- /dev/null +++ b/app/assets/javascripts/admin/enterprises/directives/monthly_pricing_description.js.coffee @@ -0,0 +1,8 @@ +angular.module("admin.enterprises").directive "monthlyPricingDescription", (monthlyBillDescription) -> + restrict: 'E' + scope: + joiner: "@" + template: "" + link: (scope, element, attrs) -> + joiners = { comma: ", ", newline: "
" } + scope.billDescription = monthlyBillDescription.replace("{joiner}", joiners[scope.joiner]) diff --git a/app/assets/javascripts/admin/accounts_and_billing_settings/directives/watchValueAs.js.coffee b/app/assets/javascripts/admin/utils/directives/watchValueAs.js.coffee similarity index 72% rename from app/assets/javascripts/admin/accounts_and_billing_settings/directives/watchValueAs.js.coffee rename to app/assets/javascripts/admin/utils/directives/watchValueAs.js.coffee index a14288db55..701858cd56 100644 --- a/app/assets/javascripts/admin/accounts_and_billing_settings/directives/watchValueAs.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/watchValueAs.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.accounts_and_billing_settings").directive "watchValueAs", -> +angular.module("admin.utils").directive "watchValueAs", -> restrict: 'A' scope: { value: "=watchValueAs" diff --git a/app/assets/javascripts/templates/admin/panels/enterprise_package.html.haml b/app/assets/javascripts/templates/admin/panels/enterprise_package.html.haml index c42d8910e1..7f143d1071 100644 --- a/app/assets/javascripts/templates/admin/panels/enterprise_package.html.haml +++ b/app/assets/javascripts/templates/admin/panels/enterprise_package.html.haml @@ -15,7 +15,8 @@ %h3 Hub Shop %p - %strong COST: 2% OF SALES, CAPPED AT $50 PER MONTH + %strong + %monthly-pricing-description{ joiner: "comma" } %p Your enterprise is the backbone of your local food system. You aggregate produce from other enterprises and can sell it through your shop on the Open Food Network. @@ -53,7 +54,8 @@ %h3 Producer Shop %p - %strong COST: 2% OF SALES, CAPPED AT $50 PER MONTH + %strong + %monthly-pricing-description{ joiner: "comma" } %p Sell your products directly to customers through your very own Open Food Network shopfront. @@ -63,7 +65,8 @@ %h3 Producer Hub %p - %strong COST: 2% OF SALES, CAPPED AT $50 PER MONTH + %strong + %monthly-pricing-description{ joiner: "comma" } %p Your enterprise is the backbone of your local food system. You can sell your own produce as well as produce aggregated from other enterprises through your shopfront on the Open Food Network. @@ -94,9 +97,7 @@ %h3 Hub Shop %p Sell produce from others .bottom - \2% OF SALES - %br - CAPPED AT $50 PER MONTH + %monthly-pricing-description{ joiner: "newline" } %div{ ng: { switch: { when: "true" } } } %a.button.selector.producer-profile{ ng: { click: "enterprise.owned && (enterprise.sells='none')", class: "{selected: enterprise.sells=='none', disabled: !enterprise.owned}" } } @@ -109,17 +110,14 @@ %h3 Producer Shop %p Sell your own produce .bottom - \2% OF SALES - %br - CAPPED AT $50 PER MONTH + %monthly-pricing-description{ joiner: "newline" } + %a.button.selector.producer-hub{ ng: { click: "enterprise.owned && (enterprise.sells='any')", class: "{selected: enterprise.sells=='any', disabled: !enterprise.owned}" } } .top %h3 Producer Hub %p Sell produce from self and others .bottom - \2% OF SALES - %br - CAPPED AT $50 PER MONTH + %monthly-pricing-description{ joiner: "newline" } %a.button.update.fullwidth{ ng: { show: "enterprise.owned", class: "{disabled: saved() && !saving, saving: saving}", click: "save()" } } %span{ ng: {hide: "saved() || saving" } } diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index c43b4098e7..d5a53e11c4 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -6,6 +6,10 @@ text-align: right; } +.underline { + text-decoration: underline; +} + table .blank-action { display: inline-block; width: 29px; diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss index d91bb28934..4676d93b1a 100644 --- a/app/assets/stylesheets/admin/orders.css.scss +++ b/app/assets/stylesheets/admin/orders.css.scss @@ -56,3 +56,21 @@ div#group_buy_calculation { text-align: center; } } + +.input-symbol { + position: relative; + &.before { + + span { + position: absolute; + transform: translate(0,-50%); + top:50%; + pointer-events:none; + margin-left: 1em; + } + + input { + text-indent:1em; + } + } +} diff --git a/app/controllers/admin/account_controller.rb b/app/controllers/admin/account_controller.rb index a752b2cfdd..00531cd426 100644 --- a/app/controllers/admin/account_controller.rb +++ b/app/controllers/admin/account_controller.rb @@ -2,8 +2,5 @@ class Admin::AccountController < Spree::Admin::BaseController def show @invoices = spree_current_user.account_invoices - # @enterprises = Enterprise.where(id: BillablePeriod.where(owner_id: spree_current_user).map(&:enterprise_id)) - # .group_by('enterprise.id').joins(:billable_periods) - # .select('SUM(billable_periods.turnover) AS turnover').order('turnover DESC') end end diff --git a/app/controllers/admin/business_model_configuration_controller.rb b/app/controllers/admin/business_model_configuration_controller.rb new file mode 100644 index 0000000000..312a2e3208 --- /dev/null +++ b/app/controllers/admin/business_model_configuration_controller.rb @@ -0,0 +1,31 @@ +require 'open_food_network/business_model_configuration_validator' + +class Admin::BusinessModelConfigurationController < Spree::Admin::BaseController + before_filter :load_settings, only: [:edit, :update] + before_filter :require_valid_settings, only: [:update] + + def update + Spree::Config.set(params[:settings]) + flash[:success] = t(:successfully_updated, :resource => t(:business_model_configuration)) + redirect_to_edit + end + + private + + def redirect_to_edit + redirect_to main_app.edit_admin_business_model_configuration_path + end + + def load_settings + @settings = OpenFoodNetwork::BusinessModelConfigurationValidator.new(params[:settings] || { + account_invoices_monthly_fixed: Spree::Config[:account_invoices_monthly_fixed], + account_invoices_monthly_rate: Spree::Config[:account_invoices_monthly_rate], + account_invoices_monthly_cap: Spree::Config[:account_invoices_monthly_cap], + account_invoices_tax_rate: Spree::Config[:account_invoices_tax_rate] + }) + end + + def require_valid_settings + render :edit unless @settings.valid? + end +end diff --git a/app/helpers/admin/account_helper.rb b/app/helpers/admin/account_helper.rb new file mode 100644 index 0000000000..0292522b74 --- /dev/null +++ b/app/helpers/admin/account_helper.rb @@ -0,0 +1,14 @@ +module Admin + module AccountHelper + def invoice_description_for(invoice) + month = t(:abbr_month_names, :scope => :date)[invoice.month] + year = invoice.year + star = invoice.order.nil? || invoice.order.completed? ? "" : "*" + "#{month} #{year}#{star}" + end + + def invoice_total_for(invoice) + invoice.order.andand.display_total || Spree::Money.new(0, { :currency => Spree::Config[:currency] }) + end + end +end diff --git a/app/helpers/admin/business_model_configuration_helper.rb b/app/helpers/admin/business_model_configuration_helper.rb new file mode 100644 index 0000000000..dcfebb1969 --- /dev/null +++ b/app/helpers/admin/business_model_configuration_helper.rb @@ -0,0 +1,48 @@ +module Admin + module BusinessModelConfigurationHelper + def monthly_bill_description + plus = monthly_bill_includes_fixed? && monthly_bill_includes_rate? ? " + " : "" + + if fixed_description.empty? && rate_description.empty? + t(:free).upcase + elsif monthly_bill_includes_cap? && monthly_bill_includes_rate? # only care about cap if there is a rate too + "#{fixed_description}#{plus}#{rate_description}{joiner}#{cap_description} #{t(:per_month).upcase}#{tax_description.upcase}" + else + "#{fixed_description}#{plus}#{rate_description} #{t(:per_month).upcase}#{tax_description.upcase}" + end + end + + private + + def fixed_description + fixed_amount = Spree::Money.new(Spree::Config[:account_invoices_monthly_fixed], {currency: Spree::Config[:currency]} ).rounded + monthly_bill_includes_fixed? ? "#{fixed_amount}" : "" + end + + def rate_description + percentage = (Spree::Config[:account_invoices_monthly_rate]*100).round(2) + monthly_bill_includes_rate? ? t(:percentage_of_sales, percentage: "#{percentage}%").upcase : "" + end + + def cap_description + cap_amount = Spree::Money.new(Spree::Config[:account_invoices_monthly_cap], { currency: Spree::Config[:currency] }).rounded + monthly_bill_includes_cap? ? "#{t(:capped_at_cap, cap: cap_amount).upcase}" : "" + end + + def tax_description + Spree::Config[:account_invoices_tax_rate] > 0 ? ", #{t(:plus_tax).upcase}" : "" + end + + def monthly_bill_includes_fixed? + Spree::Config[:account_invoices_monthly_fixed] > 0 + end + + def monthly_bill_includes_rate? + Spree::Config[:account_invoices_monthly_rate] > 0 + end + + def monthly_bill_includes_cap? + Spree::Config[:account_invoices_monthly_cap] > 0 + end + end +end diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 490e3dc00b..6036447d9b 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -1,5 +1,7 @@ module Admin module InjectionHelper + include BusinessModelConfigurationHelper + def admin_inject_enterprise admin_inject_json_ams "admin.enterprises", "enterprise", @enterprise, Api::Admin::EnterpriseSerializer end @@ -78,6 +80,10 @@ module Admin admin_inject_json_ams_array "admin.orders", "orderCycles", @order_cycles, Api::Admin::BasicOrderCycleSerializer, current_user: spree_current_user end + def admin_inject_monthly_bill_description + render partial: "admin/json/injection_ams", locals: {ngModule: "admin.enterprises", name: "monthlyBillDescription", json: monthly_bill_description.to_json} + end + def admin_inject_spree_api_key render partial: "admin/json/injection_ams", locals: {ngModule: 'ofn.admin', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"} end diff --git a/app/jobs/finalize_account_invoices.rb b/app/jobs/finalize_account_invoices.rb index 4baa841db6..25b614bebc 100644 --- a/app/jobs/finalize_account_invoices.rb +++ b/app/jobs/finalize_account_invoices.rb @@ -51,7 +51,7 @@ class FinalizeAccountInvoices job: "FinalizeAccountInvoices", error: "end_date is in the future", data: { - end_date: end_date.localtime.strftime("%F %T"), + end_date: end_date.in_time_zone.strftime("%F %T"), now: Time.zone.now.strftime("%F %T") } }) diff --git a/app/jobs/update_account_invoices.rb b/app/jobs/update_account_invoices.rb index c0597a9ba3..16112ef870 100644 --- a/app/jobs/update_account_invoices.rb +++ b/app/jobs/update_account_invoices.rb @@ -32,10 +32,14 @@ class UpdateAccountInvoices invoice_order: account_invoice.order.as_json }) else - billable_periods = account_invoice.billable_periods.order(:enterprise_id, :begins_at).reject{ |bp| bp.turnover == 0 } + billable_periods = account_invoice.billable_periods.order(:enterprise_id, :begins_at).reject{ |bp| bp.bill == 0 } if billable_periods.any? - address = billable_periods.first.enterprise.address + oldest_enterprise = billable_periods.first.enterprise + address = oldest_enterprise.address.dup + first, space, last = (oldest_enterprise.contact || "").partition(' ') + address.update_attributes(phone: oldest_enterprise.phone) if oldest_enterprise.phone.present? + address.update_attributes(firstname: first, lastname: last) if first.present? && last.present? account_invoice.order.update_attributes(bill_address: address, ship_address: address) end @@ -81,7 +85,7 @@ class UpdateAccountInvoices job: "UpdateAccountInvoices", error: "end_date is in the future", data: { - end_date: end_date.localtime.strftime("%F %T"), + end_date: end_date.in_time_zone.strftime("%F %T"), now: Time.zone.now.strftime("%F %T") } }) diff --git a/app/jobs/update_billable_periods.rb b/app/jobs/update_billable_periods.rb index 598d5f8e7b..80f19d961a 100644 --- a/app/jobs/update_billable_periods.rb +++ b/app/jobs/update_billable_periods.rb @@ -91,10 +91,10 @@ class UpdateBillablePeriods def clean_up_untouched_billable_periods_for(enterprise, job_start_time) # Snag and then delete any BillablePeriods which overlap - obsolete_billable_periods = enterprise.billable_periods.where('ends_at > (?) AND begins_at < (?) AND updated_at < (?)', start_date, end_date, job_start_time) + obsolete_billable_periods = enterprise.billable_periods.where('ends_at > (?) AND begins_at < (?) AND billable_periods.updated_at < (?)', start_date, end_date, job_start_time) if obsolete_billable_periods.any? - current_billable_periods = enterprise.billable_periods.where('ends_at >= (?) AND begins_at <= (?) AND updated_at > (?)', start_date, end_date, job_start_time) + current_billable_periods = enterprise.billable_periods.where('ends_at >= (?) AND begins_at <= (?) AND billable_periods.updated_at > (?)', start_date, end_date, job_start_time) Delayed::Worker.logger.info "#{enterprise.name} #{start_date.strftime("%F %T")} #{job_start_time.strftime("%F %T")}" Delayed::Worker.logger.info "#{obsolete_billable_periods.first.updated_at.strftime("%F %T")}" @@ -105,7 +105,9 @@ class UpdateBillablePeriods }) end - obsolete_billable_periods.each(&:delete) + obsolete_billable_periods.includes({ account_invoice: :order}). + where('spree_orders.state <> \'complete\' OR account_invoices.order_id IS NULL'). + each(&:delete) end private @@ -116,7 +118,7 @@ class UpdateBillablePeriods job: "UpdateBillablePeriods", error: "end_date is in the future", data: { - end_date: end_date.localtime.strftime("%F %T"), + end_date: end_date.in_time_zone.strftime("%F %T"), now: Time.zone.now.strftime("%F %T") } }) diff --git a/app/models/billable_period.rb b/app/models/billable_period.rb index 79f4b3eaba..d604b5a59c 100644 --- a/app/models/billable_period.rb +++ b/app/models/billable_period.rb @@ -1,3 +1,5 @@ +require 'open_food_network/bill_calculator' + class BillablePeriod < ActiveRecord::Base belongs_to :enterprise belongs_to :owner, class_name: 'Spree::User' @@ -15,14 +17,9 @@ class BillablePeriod < ActiveRecord::Base end def bill - # Will make this more sophisicated in the future in that it will use global config variables to calculate return 0 if trial? - if ['own', 'any'].include? sells - bill = (turnover * 0.02).round(2) - bill > 50 ? 50 : bill - else - 0 - end + return 0 unless ['own', 'any'].include?(sells) + OpenFoodNetwork::BillCalculator.new(turnover: turnover).bill end def label @@ -34,8 +31,8 @@ class BillablePeriod < ActiveRecord::Base end def adjustment_label - begins = begins_at.localtime.strftime("%d/%m/%y") - ends = ends_at.localtime.strftime("%d/%m/%y") + begins = begins_at.in_time_zone.strftime("%d/%m/%y") + ends = ends_at.in_time_zone.strftime("%d/%m/%y") "#{label} [#{begins} - #{ends}]" end @@ -47,13 +44,14 @@ class BillablePeriod < ActiveRecord::Base def ensure_correct_adjustment_for(invoice) if adjustment # adjustment.originator = enterprise.package + adjustment.adjustable = invoice adjustment.update_attributes( label: adjustment_label, amount: bill ) else self.adjustment = invoice.adjustments.new( adjustment_attrs, :without_protection => true ) end - if Spree::Config.account_bill_inc_tax - adjustment.set_included_tax! Spree::Config.account_bill_tax_rate + if Spree::Config.account_invoices_tax_rate > 0 + adjustment.set_included_tax! Spree::Config.account_invoices_tax_rate else adjustment.set_included_tax! 0 end diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 82054ad792..25e0ced2cc 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -92,6 +92,11 @@ class AbilityDecorator can [:admin, :known_users], :search can [:admin, :show], :account + + # For printing own account invoice orders + can [:print], Spree::Order do |order| + order.user == user + end end def add_product_management_abilities(user) diff --git a/app/models/spree/app_configuration_decorator.rb b/app/models/spree/app_configuration_decorator.rb index 2e1c838e95..fc7a8171cc 100644 --- a/app/models/spree/app_configuration_decorator.rb +++ b/app/models/spree/app_configuration_decorator.rb @@ -7,8 +7,6 @@ Spree::AppConfiguration.class_eval do # Tax Preferences preference :products_require_tax_category, :boolean, default: false preference :shipping_tax_rate, :decimal, default: 0 - preference :account_bill_inc_tax, :boolean, default: false - preference :account_bill_tax_rate, :decimal, default: 0 # Accounts & Billing Preferences preference :accounts_distributor_id, :integer, default: nil @@ -16,4 +14,10 @@ Spree::AppConfiguration.class_eval do preference :default_accounts_shipping_method_id, :integer, default: nil preference :auto_update_invoices, :boolean, default: false preference :auto_finalize_invoices, :boolean, default: false + + # Business Model Configuration + preference :account_invoices_monthly_fixed, :decimal, default: 0 + preference :account_invoices_monthly_rate, :decimal, default: 0 + preference :account_invoices_monthly_cap, :decimal, default: 0 + preference :account_invoices_tax_rate, :decimal, default: 0 end diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb index ee4384b460..9db9ae1269 100644 --- a/app/models/spree/line_item_decorator.rb +++ b/app/models/spree/line_item_decorator.rb @@ -7,7 +7,7 @@ Spree::LineItem.class_eval do attr_accessible :max_quantity, :final_weight_volume, :price attr_accessible :final_weight_volume, :price, :as => :api - before_save :calculate_final_weight_volume, unless: :final_weight_volume_changed? + before_save :calculate_final_weight_volume, if: :quantity_changed?, unless: :final_weight_volume_changed? after_save :update_units delegate :unit_description, to: :variant @@ -88,12 +88,10 @@ Spree::LineItem.class_eval do private def calculate_final_weight_volume - if quantity_changed? - if final_weight_volume.present? - self.final_weight_volume = final_weight_volume * quantity / quantity_was - elsif variant.andand.unit_value - self.final_weight_volume = ((variant.andand.unit_value) * quantity) - end + if final_weight_volume.present? && quantity_was > 0 + self.final_weight_volume = final_weight_volume * quantity / quantity_was + elsif variant.andand.unit_value.present? + self.final_weight_volume = variant.andand.unit_value * quantity end end end diff --git a/app/overrides/spree/admin/shared/_configuration_menu/add_business_model_configuration.html.haml.deface b/app/overrides/spree/admin/shared/_configuration_menu/add_business_model_configuration.html.haml.deface new file mode 100644 index 0000000000..7d5c311a7b --- /dev/null +++ b/app/overrides/spree/admin/shared/_configuration_menu/add_business_model_configuration.html.haml.deface @@ -0,0 +1,4 @@ +// insert_bottom "[data-hook='admin_configurations_sidebar_menu']" + +%li + = link_to 'Business Model', main_app.edit_admin_business_model_configuration_path diff --git a/app/views/admin/account/show.html.haml b/app/views/admin/account/show.html.haml index 809d52eb2f..808402ed16 100644 --- a/app/views/admin/account/show.html.haml +++ b/app/views/admin/account/show.html.haml @@ -7,34 +7,47 @@ %h4= t(:no_invoices_to_display) - @invoices.order('year DESC, month DESC').each do |invoice| - - order = invoice.order .row.invoice_title - .eight.columns.alpha - %h4= "#{t(:abbr_month_names, :scope => :date)[invoice.month]} #{invoice.year}#{invoice.order.completed? ? "" : "*"}" - .eight.columns.omega.text-right - %h4.balance= invoice.order.display_total + .two.columns.alpha + %h4= invoice_description_for(invoice) + .two.columns.text-right + %h5 + - if invoice.order.andand.complete? + %a{ href: print_admin_order_url(invoice.order), :target => "_blank"} + %i.icon-print + = t(:print) + - else +   + .ten.columns +   + .two.columns.omega.text-right + %h4.balance= invoice_total_for(invoice) %table.invoice_summary - %col{ width: '20%' } - %col{ width: '60%' } - %col{ width: '20%' } + %col{ width: '25%' } + %col{ width: '62.5%' } + %col{ width: '12.5%' } %thead %th Date %th= t(:description) %th= t(:charge) - - invoice.billable_periods.select{ |bp| bp.bill > 0}.each do |billable_period| - %tr - %td.text-center= "#{billable_period.begins_at.strftime("%d/%m/%Y")}" - %td= billable_period.label - %td.text-right= billable_period.display_bill - - order.adjustments.where('source_type <> (?)', "BillablePeriod").each do |adjustment| - %tr - %td.text-center   - %td= adjustment.label - %td.text-right= adjustment.display_amount + - if order = invoice.order + - invoice.billable_periods.select{ |bp| bp.adjustment.andand.amount.andand > 0}.each do |billable_period| + %tr + %td.text-center= "#{billable_period.begins_at.strftime("%d/%m/%Y")}" + %td= billable_period.label + -# Using amount from the actual adjustment on the order here so that we avoid recalculating the bill + -# at a future date with different settings to those used at the time the invoice was finalized + %td.text-right= billable_period.adjustment.display_amount + - order.adjustments.where('source_type <> (?)', "BillablePeriod").reject{ |a| a.amount == 0 }.each do |adjustment| + %tr + %td.text-center   + %td= adjustment.label + %td.text-right= adjustment.display_amount %tr.total %td.text-center   %td= t(:total).upcase - %td.text-right= order.display_total + %td.text-right= invoice_total_for(invoice) + -# - if @enterprises.empty? -# %h4 No enterprises to display @@ -51,8 +64,8 @@ -# %th Bill -# - enterprise.billable_periods.each do |billable_period| -# %tr --# %td= billable_period.begins_at.localtime.strftime("%F %T") --# %td= billable_period.ends_at.localtime.strftime("%F %T") +-# %td= billable_period.begins_at.in_time_zone.strftime("%F %T") +-# %td= billable_period.ends_at.in_time_zone.strftime("%F %T") -# %td= billable_period.sells -# %td= billable_period.trial? -# %td= billable_period.display_turnover diff --git a/app/views/admin/business_model_configuration/edit.html.haml b/app/views/admin/business_model_configuration/edit.html.haml new file mode 100644 index 0000000000..09a5949456 --- /dev/null +++ b/app/views/admin/business_model_configuration/edit.html.haml @@ -0,0 +1,84 @@ += render :partial => 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + %h1.page-title= t(:business_model_configuration) + %a.with-tip{ 'data-powertip' => "Configure the rate at which shops will be charged each month for use of the Open Food Network." } What's this? + += render 'spree/shared/error_messages', target: @settings + +.row{ ng: { app: 'admin.businessModelConfiguration', controller: "BusinessModelConfigCtrl" } } + .five.columns.omega + %fieldset.no-border-bottom + %legend=t(:bill_calculation_settings) + %p + Adjust the amount that enterprises will be billed each month for use of the OFN. + %br + = form_for @settings, as: :settings, url: main_app.admin_business_model_configuration_path, :method => :put do |f| + .row + .three.columns.alpha + = f.label :account_invoices_monthly_fixed, t(:fixed_monthly_charge) + %span.with-tip.icon-question-sign{'data-powertip' => "A fixed monthly charge for ALL enterprises who are set up as a shop, regardless of how much produce they sell."} + .two.columns.omega + .input-symbol.before + %span= Spree::Money.currency_symbol + = f.number_field :account_invoices_monthly_fixed, min: 0.0, class: "fullwidth", 'watch-value-as' => 'fixed' + .row + .three.columns.alpha + = f.label :account_invoices_monthly_rate, t(:percentage_of_turnover) + %span.with-tip.icon-question-sign{'data-powertip' => "When greater than zero, this rate (0.0 - 1.0) will be applied to the total turnover of each shop and added to any fixed charges (to the left) to calculate the monthly bill."} + .two.columns.omega + = f.number_field :account_invoices_monthly_rate, min: 0.0, max: 1.0, step: 0.01, class: "fullwidth", 'watch-value-as' => 'rate' + .row + .three.columns.alpha + = f.label :account_invoices_monthly_cap, t(:monthly_cap_excl_tax) + %span.with-tip.icon-question-sign{'data-powertip' => "When greater than zero, this value will be used as a cap on the amount that shops will be charged each month."} + .two.columns.omega + .input-symbol.before + %span= Spree::Money.currency_symbol + = f.number_field :account_invoices_monthly_cap, min: 0.0, class: "fullwidth", 'watch-value-as' => 'cap' + .row + .three.columns.alpha + = f.label :account_invoices_tax_rate, t(:tax_rate) + %span.with-tip.icon-question-sign{'data-powertip' => "Tax rate that applies to the the monthly bill that enterprises are charged for using the system."} + .two.columns.omega + = f.number_field :account_invoices_tax_rate, min: 0.0, max: 1.0, step: 0.01, class: "fullwidth", 'watch-value-as' => 'taxRate' + + .row + .five.columns.alpha.omega.form-buttons{"data-hook" => "buttons"} + = button t(:update), 'icon-refresh', value: "update" + + .two.columns +   + + .five.columns.alpha + %fieldset.no-border-bottom + %legend=t(:example_bill_calculator) + %p + Alter the example turnover to visualise the effect of the settings to the left. + %br + .row + .three.columns.alpha + = label_tag :turnover, t(:example_monthly_turnover) + %span.with-tip.icon-question-sign{'data-powertip' => "An example monthly turnover for an enterprise which will be used to generate calculate an example monthly bill below."} + .two.columns.omega + .input-symbol.before + %span= Spree::Money.currency_symbol + %input.fullwidth{ id: 'turnover', type: "number", ng: { model: 'turnover' } } + .row + .three.columns.alpha + = label_tag :cap_reached, t(:cap_reached?) + %span.with-tip.icon-question-sign{'data-powertip' => "Whether the cap (specified to the left) has been reached, given the settings and the turnover provided."} + .two.columns.omega + %input.fullwidth{ id: 'cap_reached', type: "text", readonly: true, ng: { value: 'capReached()' } } + .row + .three.columns.alpha + = label_tag :included_tax, t(:included_tax) + %span.with-tip.icon-question-sign{'data-powertip' => "The total tax included in the example monthly bill, given the settings and the turnover provided."} + .two.columns.omega + %input.fullwidth{ id: 'included_tax', type: "text", readonly: true, ng: { value: 'includedTax() | currency' } } + .row + .three.columns.alpha + = label_tag :total_incl_tax, t(:total_monthly_bill_incl_tax) + %span.with-tip.icon-question-sign{'data-powertip' => "The example total monthly bill with tax included, given the settings and the turnover provided."} + .two.columns.omega + %input.fullwidth{ id: 'total_incl_tax', type: "text", readonly: true, ng: { value: 'total() | currency' } } diff --git a/app/views/admin/enterprises/_change_type_form.html.haml b/app/views/admin/enterprises/_change_type_form.html.haml index 740857e381..6474af1344 100644 --- a/app/views/admin/enterprises/_change_type_form.html.haml +++ b/app/views/admin/enterprises/_change_type_form.html.haml @@ -1,4 +1,5 @@ = admin_inject_enterprise += admin_inject_monthly_bill_description = form_for @enterprise, url: main_app.register_admin_enterprise_path(@enterprise), html: { name: "change_type", id: "change_type", novalidate: true, "ng-app" => "admin.enterprises", "ng-controller"=> 'changeTypeFormCtrl' } do |change_type_form| @@ -16,9 +17,6 @@ .bottom ALWAYS FREE %p.description Add your products to Open Food Network, allowing hubs to stock your products in their stores. - %br - %br - Having a profile, and making connections within your local food system through the Open Food Network will always be free. .producer_shop.option.one-third.column %a.full-width.button.selector{ ng: { click: "sells='own'", class: "{selected: sells=='own'}" } } @@ -26,17 +24,13 @@ %h3 Producer Shop %p Sell your own produce .bottom - \%2 OF SALES - %br - CAPPED AT $50 PER MONTH + %monthly-pricing-description{ joiner: "newline" } + %p.description Sell your products directly to customers through your very own Open Food Network shopfront. %br %br A Producer Shop is for your produce only, if you want to sell produce grown/produced off site, select 'Producer Hub'. - %br - %br - You will be billed for 2% of your actual transactions, capped at $50 a month (so if you don’t sell anything you don’t pay anything, but you never pay more than $50 a month). .full_hub.option.one-third.column.omega %a.full-width.button.selector{ ng: { click: "sells='any'", class: "{selected: sells=='any'}" } } @@ -44,15 +38,10 @@ %h3 Producer Hub %p Sell produce from self and others .bottom - \%2 OF SALES - %br - CAPPED AT $50 PER MONTH + %monthly-pricing-description{ joiner: "newline" } + %p.description Your enterprise is the backbone of your local food system. You can sell your own produce as well as produce aggregated from other enterprises through your shopfront on the Open Food Network. - %br - %br - You will be billed for 2% of your actual transactions, capped at $50 a month (so if you don’t sell anything you don’t pay anything, but you never pay more than $50 a month). - -# %p.description -# Test out having your own shopfront with full access to all Shopfront features for 30 days. @@ -69,9 +58,6 @@ .bottom ALWAYS FREE %p.description People can find and contact you on the Open Food Network. Your enterprise will be visible on the map, and will be searchable in listings. - %br - %br - Having a profile, and making connections within your local food system through the Open Food Network will always be free. .full_hub.option.one-third.column %a.full-width.button.selector{ ng: { click: "sells='any'", class: "{selected: sells=='any'}" } } @@ -79,14 +65,10 @@ %h3 Hub Shop %p Sell produce from others .bottom - \%2 OF SALES - %br - CAPPED AT $50 PER MONTH + %monthly-pricing-description{ joiner: "newline" } + %p.description Your enterprise is the backbone of your local food system. You aggregate produce from other enterprises and can sell it through your shop on the Open Food Network. - %br - %br - You will be billed for 2% of your actual transactions, capped at $50 a month (so if you don’t sell anything you don’t pay anything, but you never pay more than $50 a month). .row diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index 826d09336c..62bc881728 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -8,6 +8,7 @@ %li#new_product_link = button_link_to "New Enterprise", main_app.new_admin_enterprise_path, :icon => 'icon-plus', :id => 'admin_new_enterprise_link' += admin_inject_monthly_bill_description = render 'admin/shared/enterprises_sub_menu' = render :partial => 'spree/shared/error_messages', :locals => { :target => @enterprise_set } diff --git a/app/views/spree/admin/orders/invoice.html.haml b/app/views/spree/admin/orders/invoice.html.haml index 75377b800e..0913ee27db 100644 --- a/app/views/spree/admin/orders/invoice.html.haml +++ b/app/views/spree/admin/orders/invoice.html.haml @@ -14,7 +14,7 @@ %td{width: "10%" }   %td{ :align => "right" } - %h4= @order.order_cycle.name + %h4= @order.order_cycle.andand.name %tr{ valign: "top" } %td{ :align => "left" } %strong= "From: #{@order.distributor.name}" diff --git a/app/views/spree/admin/tax_settings/edit.html.haml b/app/views/spree/admin/tax_settings/edit.html.haml index 102ec0609e..edd5a3b401 100644 --- a/app/views/spree/admin/tax_settings/edit.html.haml +++ b/app/views/spree/admin/tax_settings/edit.html.haml @@ -19,14 +19,5 @@ = number_field_tag "preferences[shipping_tax_rate]", Spree::Config[:shipping_tax_rate].to_f, in: 0.0..1.0, step: 0.01 = label_tag nil, t(:shipping_tax_rate) - .field.align-center{"data-hook" => "billing_tax"} - = hidden_field_tag 'preferences[account_bill_inc_tax]', '0' - = check_box_tag 'preferences[account_bill_inc_tax]', '1', Spree::Config[:account_bill_inc_tax] - = label_tag nil, t(:account_bill_inc_tax) - - .field.align-center{ "data-hook" => "account_bill_tax_rate" } - = number_field_tag "preferences[account_bill_tax_rate]", Spree::Config[:account_bill_tax_rate].to_f, in: 0.0..1.0, step: 0.01 - = label_tag nil, t(:account_bill_tax_rate) - .form-buttons{"data-hook" => "buttons"} = button t(:update), 'icon-refresh' diff --git a/config/locales/en.yml b/config/locales/en.yml index 373440ebfe..c9df13af92 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -30,6 +30,15 @@ en: confirm_send_invoice: "An invoice for this order will be sent to the customer. Are you sure you want to continue?" confirm_resend_order_confirmation: "Are you sure you want to resend the order confirmation email?" must_have_valid_business_number: "%{enterprise_name} must have a valid ABN before invoices can be sent." + invoice: "Invoice" + percentage_of_sales: "%{percentage} of sales" + percentage_of_turnover: "Percentage of turnover" + monthly_cap_excl_tax: "monthly cap (excl. GST)" + capped_at_cap: "capped at %{cap}" + per_month: "per month" + free: "free" + plus_tax: "plus GST" + total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. Tax)" sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" @@ -55,7 +64,6 @@ en: footer_links_md: "Links" footer_about_url: "About URL" footer_tos_url: "Terms of Service URL" - invoice: "Invoice" name: Name first_name: First Name diff --git a/config/routes.rb b/config/routes.rb index eb8c228b91..c8771560e9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -116,6 +116,8 @@ Openfoodnetwork::Application.routes.draw do end end + resource :business_model_configuration, only: [:edit, :update], controller: 'business_model_configuration' + resource :account, only: [:show], controller: 'account' end diff --git a/lib/open_food_network/bill_calculator.rb b/lib/open_food_network/bill_calculator.rb new file mode 100644 index 0000000000..84457d6530 --- /dev/null +++ b/lib/open_food_network/bill_calculator.rb @@ -0,0 +1,19 @@ +module OpenFoodNetwork + class BillCalculator + attr_accessor :turnover, :fixed, :rate, :cap, :tax_rate + + def initialize(opts={}) + @turnover = opts[:turnover] || 0 + @fixed = opts[:fixed] || Spree::Config[:account_invoices_monthly_fixed] + @rate = opts[:rate] || Spree::Config[:account_invoices_monthly_rate] + @cap = opts[:cap] || Spree::Config[:account_invoices_monthly_cap] + @tax_rate = opts[:tax_rate] || Spree::Config[:account_invoices_tax_rate] + end + + def bill + bill = fixed + (turnover * rate) + bill = cap > 0 ? [bill, cap].min : bill + bill * (1 + tax_rate) + end + end +end diff --git a/lib/open_food_network/business_model_configuration_validator.rb b/lib/open_food_network/business_model_configuration_validator.rb new file mode 100644 index 0000000000..d83d94ffc1 --- /dev/null +++ b/lib/open_food_network/business_model_configuration_validator.rb @@ -0,0 +1,20 @@ +# This class is a lightweight model used to validate preferences for business model configuration +# when they are submitted to the BusinessModelConfigurationController + +module OpenFoodNetwork + class BusinessModelConfigurationValidator + include ActiveModel::Validations + + attr_accessor :account_invoices_monthly_fixed, :account_invoices_monthly_rate, :account_invoices_monthly_cap, :account_invoices_tax_rate + + validates :account_invoices_monthly_fixed, presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :account_invoices_monthly_rate, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 } + validates :account_invoices_monthly_cap, presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :account_invoices_tax_rate, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 } + + def initialize(attr, button=nil) + attr.each { |k,v| instance_variable_set("@#{k}", v) } + @button = button + end + end +end diff --git a/lib/spree/money_decorator.rb b/lib/spree/money_decorator.rb index fed92b8210..3479bbd9a2 100644 --- a/lib/spree/money_decorator.rb +++ b/lib/spree/money_decorator.rb @@ -4,4 +4,9 @@ Spree::Money.class_eval do def self.currency_symbol Money.new(0, Spree::Config[:currency]).symbol end + + def rounded + @options[:no_cents] = true if @money.amount % 1 == 0 + to_s + end end diff --git a/spec/controllers/admin/business_model_configuration_controller_spec.rb b/spec/controllers/admin/business_model_configuration_controller_spec.rb new file mode 100644 index 0000000000..b0ae86d4ac --- /dev/null +++ b/spec/controllers/admin/business_model_configuration_controller_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe Admin::BusinessModelConfigurationController, type: :controller do + let(:user) { create(:user) } + let(:admin) { create(:admin_user) } + + before do + Spree::Config.set({ + account_invoices_monthly_fixed: 5, + account_invoices_monthly_rate: 0.02, + account_invoices_monthly_cap: 50, + account_invoices_tax_rate: 0.1 + }) + end + + describe "edit" do + context "as an enterprise user" do + before { allow(controller).to receive(:spree_current_user) { user } } + + it "does not allow access" do + spree_get :edit + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "as super admin" do + before { allow(controller).to receive(:spree_current_user) { admin } } + + it "allows access" do + spree_get :edit + expect(response).to_not redirect_to spree.unauthorized_path + end + end + end + + describe "update" do + context "as an enterprise user" do + before { allow(controller).to receive(:spree_current_user) { user } } + + it "does not allow access" do + spree_get :update + expect(response).to redirect_to spree.unauthorized_path + end + end + + context "as super admin" do + before {allow(controller).to receive(:spree_current_user) { admin } } + let(:params) { { settings: { } } } + + context "when settings are invalid" do + before do + params[:settings][:account_invoices_monthly_fixed] = '' + params[:settings][:account_invoices_monthly_rate] = '2' + params[:settings][:account_invoices_monthly_cap] = '-1' + params[:settings][:account_invoices_tax_rate] = '4' + spree_get :update, params + end + + it "does not allow them to be set" do + expect(response).to render_template :edit + expect(assigns(:settings).errors.count).to be 5 + expect(Spree::Config.account_invoices_monthly_fixed).to eq 5 + expect(Spree::Config.account_invoices_monthly_rate).to eq 0.02 + expect(Spree::Config.account_invoices_monthly_cap).to eq 50 + expect(Spree::Config.account_invoices_tax_rate).to eq 0.1 + end + end + + context "when required settings are valid" do + before do + params[:settings][:account_invoices_monthly_fixed] = '10' + params[:settings][:account_invoices_monthly_rate] = '0.05' + params[:settings][:account_invoices_monthly_cap] = '30' + params[:settings][:account_invoices_tax_rate] = '0.15' + end + + it "sets global config to the specified values" do + spree_get :update, params + expect(assigns(:settings).errors.count).to be 0 + expect(Spree::Config.account_invoices_monthly_fixed).to eq 10 + expect(Spree::Config.account_invoices_monthly_rate).to eq 0.05 + expect(Spree::Config.account_invoices_monthly_cap).to eq 30 + expect(Spree::Config.account_invoices_tax_rate).to eq 0.15 + end + end + end + end +end diff --git a/spec/features/admin/business_model_configuration_spec.rb b/spec/features/admin/business_model_configuration_spec.rb new file mode 100644 index 0000000000..05d5367437 --- /dev/null +++ b/spec/features/admin/business_model_configuration_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +feature 'Business Model Configuration' do + include AuthenticationWorkflow + include WebHelper + + describe "updating" do + let!(:admin) { create(:admin_user) } + + before do + Spree::Config.set({ + account_invoices_monthly_fixed: 5, + account_invoices_monthly_rate: 0.02, + account_invoices_monthly_cap: 50, + account_invoices_tax_rate: 0.1 + }) + end + + before do + quick_login_as_admin + end + + context "as an admin user", js: true do + it "loads the page" do + visit spree.admin_path + click_link "Configuration" + click_link "Business Model" + + expect(page).to have_field "settings_account_invoices_monthly_fixed", with: 5.0 + expect(page).to have_field "settings_account_invoices_monthly_rate", with: 0.02 + expect(page).to have_field "settings_account_invoices_monthly_cap", with: 50.0 + expect(page).to have_field "settings_account_invoices_tax_rate", with: 0.1 + end + + it "attributes can be changed", js: true do + visit edit_admin_business_model_configuration_path + + fill_in "settings_account_invoices_monthly_fixed", with: 10 + fill_in "settings_account_invoices_monthly_rate", with: 0.05 + fill_in "settings_account_invoices_monthly_cap", with: 30 + fill_in "settings_account_invoices_tax_rate", with: 0.15 + + click_button "Update" + + expect(Spree::Config.account_invoices_monthly_fixed).to eq 10 + expect(Spree::Config.account_invoices_monthly_rate).to eq 0.05 + expect(Spree::Config.account_invoices_monthly_cap).to eq 30 + expect(Spree::Config.account_invoices_tax_rate).to eq 0.15 + end + end + end +end diff --git a/spec/features/admin/tax_settings_spec.rb b/spec/features/admin/tax_settings_spec.rb index 9894ee8a72..4b2dd4486c 100644 --- a/spec/features/admin/tax_settings_spec.rb +++ b/spec/features/admin/tax_settings_spec.rb @@ -11,9 +11,7 @@ feature 'Account and Billing Settings' do Spree::Config.set({ products_require_tax_category: false, shipment_inc_vat: false, - shipping_tax_rate: 0, - account_bill_inc_tax: false, - account_bill_tax_rate: 0 + shipping_tax_rate: 0 }) end @@ -30,8 +28,6 @@ feature 'Account and Billing Settings' do expect(page).to have_unchecked_field 'preferences_products_require_tax_category' expect(page).to have_unchecked_field 'preferences_shipment_inc_vat' expect(page).to have_field 'preferences_shipping_tax_rate' - expect(page).to have_unchecked_field 'preferences_account_bill_inc_tax' - expect(page).to have_field 'preferences_account_bill_tax_rate' end it "attributes can be changed" do @@ -40,16 +36,12 @@ feature 'Account and Billing Settings' do check 'preferences_products_require_tax_category' check 'preferences_shipment_inc_vat' fill_in 'preferences_shipping_tax_rate', with: '0.12' - check 'preferences_account_bill_inc_tax' - fill_in 'preferences_account_bill_tax_rate', with: '0.05' click_button "Update" expect(Spree::Config.products_require_tax_category).to be true expect(Spree::Config.shipment_inc_vat).to be true expect(Spree::Config.shipping_tax_rate).to eq 0.12 - expect(Spree::Config.account_bill_inc_tax).to be true - expect(Spree::Config.account_bill_tax_rate).to eq 0.05 end end end diff --git a/spec/helpers/admin/business_model_configuration_helper_spec.rb b/spec/helpers/admin/business_model_configuration_helper_spec.rb new file mode 100644 index 0000000000..a10771cba7 --- /dev/null +++ b/spec/helpers/admin/business_model_configuration_helper_spec.rb @@ -0,0 +1,139 @@ +require 'spec_helper' + +describe Admin::BusinessModelConfigurationHelper do + describe "describing monthly bills for enterprises" do + context "when tax is applied to the service change" do + before { Spree::Config.set(:account_invoices_tax_rate, 0.1) } + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.05) } + + 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{joiner}CAPPED AT $20 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" } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + 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" } + 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" } + end + end + end + + context "when a fixed cost is not included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.05) } + + 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{joiner}CAPPED AT $20 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 PER MONTH, PLUS GST" } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + 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" } + 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" } + end + end + end + end + + context "when tax is applied to the service change" do + before { Spree::Config.set(:account_invoices_tax_rate, 0.0) } + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.05) } + + 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{joiner}CAPPED AT $20 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" } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + 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" } + 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" } + end + end + end + + context "when a fixed cost is not included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.05) } + + 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{joiner}CAPPED AT $20 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 PER MONTH" } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + 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" } + 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" } + end + end + end + end + end +end diff --git a/spec/jobs/finalize_account_invoices_spec.rb b/spec/jobs/finalize_account_invoices_spec.rb index e169df93e5..d4b250d42f 100644 --- a/spec/jobs/finalize_account_invoices_spec.rb +++ b/spec/jobs/finalize_account_invoices_spec.rb @@ -184,6 +184,11 @@ describe FinalizeAccountInvoices do Spree::Config.set({ accounts_distributor_id: accounts_distributor.id }) Spree::Config.set({ default_accounts_payment_method_id: pm.id }) Spree::Config.set({ default_accounts_shipping_method_id: sm.id }) + + # Make sure that bills are > 0 + Spree::Config.set(:account_invoices_monthly_fixed, 5) + Spree::Config.set(:account_invoices_monthly_rate, 0.02) + Spree::Config.set(:account_invoices_monthly_cap, 50) end context "finalizing an invoice" do @@ -200,9 +205,9 @@ describe FinalizeAccountInvoices do invoice.reload expect(invoice.completed_at).to_not be_nil - expect(invoice.total).to eq billable_period1.bill + expect(invoice.total).to eq billable_period1.bill.round(2) expect(invoice.payments.count).to eq 1 - expect(invoice.payments.first.amount).to eq billable_period1.bill + expect(invoice.payments.first.amount).to eq billable_period1.bill.round(2) expect(invoice.state).to eq 'complete' end end diff --git a/spec/jobs/update_account_invoices_spec.rb b/spec/jobs/update_account_invoices_spec.rb index a97811fc86..87d601b60a 100644 --- a/spec/jobs/update_account_invoices_spec.rb +++ b/spec/jobs/update_account_invoices_spec.rb @@ -7,17 +7,28 @@ end describe UpdateAccountInvoices do let(:year) { Time.zone.now.year } + before do + # Make sure that bills are > 0 + Spree::Config.set(:account_invoices_monthly_fixed, 5) + Spree::Config.set(:account_invoices_monthly_rate, 0.02) + Spree::Config.set(:account_invoices_monthly_cap, 50) + end + describe "units specs" do let!(:start_of_july) { Time.zone.local(year, 7) } let!(:updater) { UpdateAccountInvoices.new } let!(:user) { create(:user) } - let!(:old_billable_period) { create(:billable_period, owner: user, begins_at: start_of_july - 1.month, ends_at: start_of_july) } - let!(:billable_period1) { create(:billable_period, owner: user, begins_at: start_of_july, ends_at: start_of_july + 12.days) } - let!(:billable_period2) { create(:billable_period, owner: user, begins_at: start_of_july + 12.days, ends_at: start_of_july + 20.days) } - let(:june_account_invoice) { old_billable_period.account_invoice } - let(:july_account_invoice) { billable_period1.account_invoice } + let!(:june_billable_period1) { create(:billable_period, owner: user, begins_at: start_of_july - 1.month, ends_at: start_of_july - 20.days) } + let!(:june_billable_period2) { create(:billable_period, owner: user, begins_at: start_of_july - 20.days, ends_at: start_of_july - 10.days, turnover: 45, sells: "none" ) } + let!(:june_billable_period3) { create(:billable_period, owner: user, begins_at: start_of_july - 10.days, ends_at: start_of_july - 1.days, turnover: 0, sells: "any" ) } + let!(:july_billable_period1) { create(:billable_period, owner: user, begins_at: start_of_july, ends_at: start_of_july + 12.days) } + let!(:july_billable_period2) { create(:billable_period, owner: user, begins_at: start_of_july + 12.days, ends_at: start_of_july + 20.days) } + let!(:july_billable_period3) { create(:billable_period, owner: user, begins_at: start_of_july + 20.days, ends_at: start_of_july + 25.days, turnover: 45, sells: 'none') } + let!(:july_billable_period4) { create(:billable_period, owner: user, begins_at: start_of_july + 25.days, ends_at: start_of_july + 28.days, turnover: 0, sells: 'any') } + let(:june_account_invoice) { june_billable_period1.account_invoice } + let(:july_account_invoice) { july_billable_period1.account_invoice } describe "perform" do let(:accounts_distributor) { double(:accounts_distributor) } @@ -145,21 +156,27 @@ describe UpdateAccountInvoices do context "where the order is not complete" do before do allow(invoice_order).to receive(:complete?) { false } + june_billable_period1.enterprise.update_attributes(contact: "Firstname Lastname Something Else", phone: '12345') updater.update(june_account_invoice) end - it "creates adjustments for each billing item" do + it "creates adjustments for each billing item where bill is not 0" do adjustments = invoice_order.adjustments - expect(adjustments.map(&:source_id)).to eq [old_billable_period.id] - expect(adjustments.map(&:amount)).to eq [old_billable_period.bill] - expect(adjustments.map(&:label)).to eq [old_billable_period.adjustment_label] + expect(adjustments.map(&:source_id)).to eq [june_billable_period1.id, june_billable_period3.id] + expect(adjustments.map(&:amount)).to eq [june_billable_period1.bill.round(2), june_billable_period3.bill.round(2)] + expect(adjustments.map(&:label)).to eq [june_billable_period1.adjustment_label, june_billable_period3.adjustment_label] end it "assigns a addresses to the order" do expect(invoice_order.billing_address).to be_a Spree::Address expect(invoice_order.shipping_address).to be_a Spree::Address - expect(invoice_order.billing_address).to eq old_billable_period.enterprise.address - expect(invoice_order.shipping_address).to eq old_billable_period.enterprise.address + expect(invoice_order.shipping_address).to eq invoice_order.billing_address + [:address1, :address2, :city, :zipcode, :state_id, :country_id].each do |attr| + expect(invoice_order.billing_address[attr]).to eq june_billable_period1.enterprise.address[attr] + end + expect(invoice_order.billing_address.firstname).to eq "Firstname" + expect(invoice_order.billing_address.lastname).to eq "Lastname Something Else" + expect(invoice_order.billing_address.phone).to eq "12345" end it "saves the order" do @@ -180,11 +197,11 @@ describe UpdateAccountInvoices do updater.update(july_account_invoice) end - it "creates adjustments for each billing item" do + it "creates adjustments for each billing item where bill is not 0" do adjustments = july_account_invoice.order.adjustments - expect(adjustments.map(&:source_id)).to eq [billable_period1.id, billable_period2.id] - expect(adjustments.map(&:amount)).to eq [billable_period1.bill, billable_period2.bill] - expect(adjustments.map(&:label)).to eq [billable_period1.adjustment_label, billable_period2.adjustment_label] + expect(adjustments.map(&:source_id)).to eq [july_billable_period1.id, july_billable_period2.id,july_billable_period4.id] + expect(adjustments.map(&:amount)).to eq [july_billable_period1.bill.round(2), july_billable_period2.bill.round(2), july_billable_period4.bill.round(2)] + expect(adjustments.map(&:label)).to eq [july_billable_period1.adjustment_label, july_billable_period2.adjustment_label, july_billable_period4.adjustment_label] end it "saves the order" do @@ -328,14 +345,15 @@ describe UpdateAccountInvoices do let!(:accounts_distributor) { create(:distributor_enterprise) } let!(:user) { create(:user) } - let!(:billable_period1) { create(:billable_period, sells: 'any', owner: user, begins_at: start_of_july - 1.month, ends_at: start_of_july) } - let!(:billable_period2) { create(:billable_period, owner: user, begins_at: start_of_july, ends_at: start_of_july + 10.days) } - let!(:billable_period3) { create(:billable_period, owner: user, begins_at: start_of_july + 12.days, ends_at: start_of_july + 20.days) } - let!(:july_account_invoice) { billable_period2.account_invoice } + let!(:july_billable_period1) { create(:billable_period, sells: 'any', owner: user, begins_at: start_of_july - 1.month, ends_at: start_of_july) } + let!(:july_billable_period2) { create(:billable_period, owner: user, begins_at: start_of_july, ends_at: start_of_july + 10.days) } + let!(:july_billable_period3) { create(:billable_period, owner: user, begins_at: start_of_july + 12.days, ends_at: start_of_july + 20.days) } + let!(:july_account_invoice) { july_billable_period2.account_invoice } let!(:august_account_invoice) { create(:account_invoice, user: user, year: july_account_invoice.year, month: 8)} before do Spree::Config.set({ accounts_distributor_id: accounts_distributor.id }) + july_billable_period2.enterprise.update_attributes(contact: 'Anna Karenina', phone: '3433523') end context "when no invoice_order currently exists" do @@ -348,14 +366,19 @@ describe UpdateAccountInvoices do expect(user.orders.first).to eq invoice_order expect(invoice_order.completed_at).to be_nil billable_adjustments = invoice_order.adjustments.where('source_type = (?)', 'BillablePeriod') - expect(billable_adjustments.map(&:amount)).to eq [billable_period2.bill, billable_period3.bill] - expect(invoice_order.total).to eq billable_period2.bill + billable_period3.bill + expect(billable_adjustments.map(&:amount)).to eq [july_billable_period2.bill.round(2), july_billable_period3.bill.round(2)] + expect(invoice_order.total).to eq july_billable_period2.bill.round(2) + july_billable_period3.bill.round(2) expect(invoice_order.payments.count).to eq 0 expect(invoice_order.state).to eq 'cart' expect(invoice_order.bill_address).to be_a Spree::Address expect(invoice_order.ship_address).to be_a Spree::Address - expect(invoice_order.bill_address).to eq billable_period2.enterprise.address - expect(invoice_order.ship_address).to eq billable_period2.enterprise.address + expect(invoice_order.shipping_address).to eq invoice_order.billing_address + [:address1, :address2, :city, :zipcode, :state_id, :country_id].each do |attr| + expect(invoice_order.billing_address[attr]).to eq july_billable_period2.enterprise.address[attr] + end + expect(invoice_order.billing_address.firstname).to eq "Anna" + expect(invoice_order.billing_address.lastname).to eq "Karenina" + expect(invoice_order.billing_address.phone).to eq "3433523" end end @@ -387,14 +410,19 @@ describe UpdateAccountInvoices do expect(invoice_order.completed_at).to be_nil billable_adjustments = invoice_order.adjustments.where('source_type = (?)', 'BillablePeriod') expect(billable_adjustments).to_not include billable_adjustment - expect(billable_adjustments.map(&:amount)).to eq [billable_period2.bill, billable_period3.bill] - expect(invoice_order.total).to eq billable_period2.bill + billable_period3.bill + expect(billable_adjustments.map(&:amount)).to eq [july_billable_period2.bill.round(2), july_billable_period3.bill.round(2)] + expect(invoice_order.total).to eq july_billable_period2.bill.round(2) + july_billable_period3.bill.round(2) expect(invoice_order.payments.count).to eq 0 expect(invoice_order.state).to eq 'cart' expect(invoice_order.bill_address).to be_a Spree::Address expect(invoice_order.ship_address).to be_a Spree::Address - expect(invoice_order.bill_address).to eq billable_period2.enterprise.address - expect(invoice_order.ship_address).to eq billable_period2.enterprise.address + expect(invoice_order.shipping_address).to eq invoice_order.billing_address + [:address1, :address2, :city, :zipcode, :state_id, :country_id].each do |attr| + expect(invoice_order.billing_address[attr]).to eq july_billable_period2.enterprise.address[attr] + end + expect(invoice_order.billing_address.firstname).to eq "Anna" + expect(invoice_order.billing_address.lastname).to eq "Karenina" + expect(invoice_order.billing_address.phone).to eq "3433523" end end diff --git a/spec/jobs/update_billable_periods_spec.rb b/spec/jobs/update_billable_periods_spec.rb index bfdbfa8434..d868550e7e 100644 --- a/spec/jobs/update_billable_periods_spec.rb +++ b/spec/jobs/update_billable_periods_spec.rb @@ -524,6 +524,8 @@ describe UpdateBillablePeriods do let!(:bp6) { create(:billable_period, enterprise: enterprise, updated_at: job_start_time - 5.seconds, begins_at: start_of_july - 10.days, ends_at: start_of_july - 5.days ) } # Updated before start but ends at start_date (ie. not after start_date, so should be ignored) EDGE CASE let!(:bp7) { create(:billable_period, enterprise: enterprise, updated_at: job_start_time - 5.seconds, begins_at: start_of_july - 5.days, ends_at: start_of_july ) } + # Updated before start, but order is already complete, so should not be deleted + let!(:bp8) { create(:billable_period, enterprise: enterprise, updated_at: job_start_time - 5.seconds, begins_at: start_of_july, ends_at: start_of_july + 10.days, account_invoice: create(:account_invoice, order: create(:order, state: 'complete', completed_at: 5.minutes.ago))) } before do allow(Bugsnag).to receive(:notify) @@ -540,6 +542,7 @@ describe UpdateBillablePeriods do expect(bp5.reload.deleted_at).to be_nil expect(bp6.reload.deleted_at).to be_nil expect(bp7.reload.deleted_at).to be_nil + expect(bp8.reload.deleted_at).to be_nil end it "notifies bugsnag" do diff --git a/spec/models/billable_period_spec.rb b/spec/models/billable_period_spec.rb index 1ad974d804..3037ebc6bc 100644 --- a/spec/models/billable_period_spec.rb +++ b/spec/models/billable_period_spec.rb @@ -1,27 +1,214 @@ require 'spec_helper' -describe Customer, type: :model do +describe BillablePeriod, type: :model do + + require 'spec_helper' + describe 'ensure_correct_adjustment' do let!(:start_of_july) { Time.zone.now.beginning_of_year + 6.months } let!(:user) { create(:user) } let!(:invoice) { create(:order, user: user) } - let!(:billable_period) { create(:billable_period, owner: user, begins_at: start_of_july, ends_at: start_of_july + 12.days) } + let!(:subject) { create(:billable_period, owner: user, begins_at: start_of_july, ends_at: start_of_july + 12.days) } before do - allow(billable_period).to receive(:bill) { 99 } - allow(billable_period).to receive(:adjustment_label) { "Label for adjustment" } - Spree::Config.set({ account_bill_inc_tax: true }) - Spree::Config.set({ account_bill_tax_rate: 0.1 }) + allow(subject).to receive(:bill) { 99 } + allow(subject).to receive(:adjustment_label) { "Label for adjustment" } + Spree::Config.set({ account_invoices_tax_rate: 0.1 }) end context "when no adjustment currently exists" do it "creates an adjustment on the given order" do expect(invoice.total_tax).to eq 0.0 - expect(billable_period.adjustment).to be nil - billable_period.ensure_correct_adjustment_for(invoice) - expect(billable_period.adjustment).to be_a Spree::Adjustment + expect(subject.adjustment).to be nil + subject.ensure_correct_adjustment_for(invoice) + expect(subject.adjustment).to be_a Spree::Adjustment expect(invoice.total_tax).to eq 9.0 end end end + + + describe "calculating monthly bills for enterprises" do + let!(:subject) { create(:billable_period, turnover: 100) } + + context "when no tax is charged" do + before { Spree::Config.set(:account_invoices_tax_rate, 0) } + + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 65) } + it { expect(subject.bill).to eq 60 } + end + + context "at a level lower than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 55) } + it { expect(subject.bill).to eq 55 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 60 } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 15) } + it { expect(subject.bill).to eq 10 } + end + + context "at a level lower than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 5) } + it { expect(subject.bill).to eq 5 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 10 } + end + end + end + + context "when a fixed cost is not included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 55) } + it { expect(subject.bill).to eq 50 } + end + + context "at a level lower than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 45) } + it { expect(subject.bill).to eq 45 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 50 } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 20) } + it { expect(subject.bill).to eq 0 } + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 0 } + end + end + end + end + + context "when tax is charged" do + before { Spree::Config.set(:account_invoices_tax_rate, 0.1) } + + context "when a fixed cost is included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 10) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 61) } + it { expect(subject.bill).to eq 66 } + end + + context "at a level lower than the fixed charge plus the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 59) } + it { + expect(subject.bill.to_f).to eq 64.9 + } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 66 } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + context "at a level higher than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 11) } + it { expect(subject.bill).to eq 11 } + end + + context "at a level lower than the fixed charge" do + before { Spree::Config.set(:account_invoices_monthly_cap, 9) } + it { expect(subject.bill.to_f).to eq 9.9 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 11 } + end + end + end + + context "when a fixed cost is not included" do + before { Spree::Config.set(:account_invoices_monthly_fixed, 0) } + + context "when a percentage of turnover is included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0.5) } + + context "when the bill is capped" do + context "at a level higher than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 51) } + it { expect(subject.bill).to eq 55 } + end + + context "at a level lower than the product of the rate and turnover" do + before { Spree::Config.set(:account_invoices_monthly_cap, 49) } + it { expect(subject.bill.to_f).to eq 53.9 } + end + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 55 } + end + end + + context "when a percentage of turnover is not included" do + before { Spree::Config.set(:account_invoices_monthly_rate, 0) } + + context "when the bill is capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 20) } + it { expect(subject.bill).to eq 0 } + end + + context "when the bill is not capped" do + before { Spree::Config.set(:account_invoices_monthly_cap, 0) } + it { expect(subject.bill).to eq 0 } + end + end + end + end + end end diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index ecbd3ae610..6bb18fafa3 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -165,27 +165,57 @@ module Spree end context "and quantity is changed" do - context "and a final_weight_volume has been set" do - before do - expect(expect(li.final_weight_volume).to eq 3000) - attrs.merge!( quantity: 4 ) - li.update_attributes(attrs) + context "from > 0" do + context "and a final_weight_volume has been set" do + before do + expect(li.final_weight_volume).to eq 3000 + attrs.merge!( quantity: 4 ) + li.update_attributes(attrs) + end + + it "scales the final_weight_volume based on the change in quantity" do + expect(li.final_weight_volume).to eq 4000 + end end - it "calculates a final_weight_volume from the variants unit_value" do - expect(li.final_weight_volume).to eq 4000 + context "and a final_weight_volume has not been set" do + before do + li.update_attributes(final_weight_volume: nil) + attrs.merge!( quantity: 1 ) + li.update_attributes(attrs) + end + + it "calculates a final_weight_volume from the variants unit_value" do + expect(li.final_weight_volume).to eq 1000 + end end end - context "and a final_weight_volume has not been set" do - before do - li.update_attributes(final_weight_volume: nil) - attrs.merge!( quantity: 1 ) - li.update_attributes(attrs) + context "from 0" do + before { li.update_attributes(quantity: 0) } + + context "and a final_weight_volume has been set" do + before do + expect(li.final_weight_volume).to eq 0 + attrs.merge!( quantity: 4 ) + li.update_attributes(attrs) + end + + it "recalculates a final_weight_volume from the variants unit_value" do + expect(li.final_weight_volume).to eq 4000 + end end - it "calculates a final_weight_volume from the variants unit_value" do - expect(li.final_weight_volume).to eq 1000 + context "and a final_weight_volume has not been set" do + before do + li.update_attributes(final_weight_volume: nil) + attrs.merge!( quantity: 1 ) + li.update_attributes(attrs) + end + + it "calculates a final_weight_volume from the variants unit_value" do + expect(li.final_weight_volume).to eq 1000 + end end end end @@ -196,7 +226,7 @@ module Spree describe "generating the full name" do let(:li) { LineItem.new } - context "when display_name is blank" do + context "when display_name is blank" do before do li.stub(:unit_to_display) { 'unit_to_display' } li.stub(:display_name) { '' } @@ -223,7 +253,7 @@ module Spree li.stub(:unit_to_display) { '10kg' } li.stub(:display_name) { '10kg Box' } end - + it "returns display_name" do li.full_name.should == '10kg Box' end @@ -234,7 +264,7 @@ module Spree li.stub(:unit_to_display) { '1 Loaf' } li.stub(:display_name) { 'Spelt Sourdough' } end - + it "returns unit_to_display" do li.full_name.should == 'Spelt Sourdough (1 Loaf)' end