Auto-merge from CI [skip ci]

This commit is contained in:
Continuous Integration
2015-12-17 13:02:37 +11:00
42 changed files with 977 additions and 166 deletions

View File

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

View File

@@ -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

View File

@@ -0,0 +1 @@
angular.module("admin.businessModelConfiguration", ["admin.utils"])

View File

@@ -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()

View File

@@ -0,0 +1,8 @@
angular.module("admin.enterprises").directive "monthlyPricingDescription", (monthlyBillDescription) ->
restrict: 'E'
scope:
joiner: "@"
template: "<span ng-bind-html='billDescription'></span>"
link: (scope, element, attrs) ->
joiners = { comma: ", ", newline: "<br>" }
scope.billDescription = monthlyBillDescription.replace("{joiner}", joiners[scope.joiner])

View File

@@ -1,4 +1,4 @@
angular.module("admin.accounts_and_billing_settings").directive "watchValueAs", ->
angular.module("admin.utils").directive "watchValueAs", ->
restrict: 'A'
scope: {
value: "=watchValueAs"

View File

@@ -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" } }

View File

@@ -6,6 +6,10 @@
text-align: right;
}
.underline {
text-decoration: underline;
}
table .blank-action {
display: inline-block;
width: 29px;

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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")
}
})

View File

@@ -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")
}
})

View File

@@ -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")
}
})

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
&nbsp;
.ten.columns
&nbsp;
.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 &nbsp;
%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 &nbsp;
%td= adjustment.label
%td.text-right= adjustment.display_amount
%tr.total
%td.text-center &nbsp;
%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

View File

@@ -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
&nbsp;
.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' } }

View File

@@ -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 dont sell anything you dont 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 dont sell anything you dont 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 dont sell anything you dont pay anything, but you never pay more than $50 a month).
.row

View File

@@ -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 }

View File

@@ -14,7 +14,7 @@
%td{width: "10%" }
&nbsp;
%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}"

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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