mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Merge pull request #7805 from Matt-Yorkley/adjustments-admin
[Adjustments] Admin adjustments
This commit is contained in:
@@ -17,6 +17,7 @@ class ApplicationController < ActionController::Base
|
||||
helper 'spree/orders'
|
||||
helper 'spree/payment_methods'
|
||||
helper 'shared'
|
||||
helper 'adjustments'
|
||||
helper 'enterprises'
|
||||
helper 'order_cycles'
|
||||
helper 'order'
|
||||
|
||||
@@ -5,18 +5,16 @@ module Spree
|
||||
class AdjustmentsController < ::Admin::ResourceController
|
||||
belongs_to 'spree/order', find_by: :number
|
||||
|
||||
prepend_before_action :set_included_tax, only: [:create, :update]
|
||||
before_action :set_order_id, only: [:create, :update]
|
||||
before_action :skip_changing_canceled_orders, only: [:create, :update]
|
||||
after_action :update_order, only: [:create, :update, :destroy]
|
||||
before_action :set_default_tax_rate, only: :edit
|
||||
before_action :enable_updates, only: :update
|
||||
after_action :apply_tax, only: [:create, :update]
|
||||
|
||||
private
|
||||
|
||||
def update_order
|
||||
@order.reload
|
||||
@order.update_order!
|
||||
@order.updater.update_totals_and_states
|
||||
end
|
||||
|
||||
def collection
|
||||
@@ -43,58 +41,13 @@ module Spree
|
||||
redirect_to admin_order_adjustments_path(@order) if @order.canceled?
|
||||
end
|
||||
|
||||
# Choose a default tax rate to show on the edit form. The adjustment stores its included
|
||||
# tax in dollars, but doesn't store the source of the tax (ie. TaxRate that generated it).
|
||||
# We guess which tax rate here, choosing:
|
||||
# 1. A tax rate that will compute to the same amount as the existing tax
|
||||
# 2. If that's not present, the first tax rate that's valid for the current order
|
||||
# When we have to go with 2, we show an error message to ask the admin to check that the
|
||||
# correct tax is being applied.
|
||||
def set_default_tax_rate
|
||||
return if @adjustment.included_tax <= 0
|
||||
|
||||
tax_rates = TaxRate.match(@order)
|
||||
tax_rate_with_matching_tax = find_tax_rate_with_matching_tax(tax_rates)
|
||||
tax_rate_valid_for_order = tax_rates.first.andand.id
|
||||
|
||||
@tax_rate_id = tax_rate_with_matching_tax || tax_rate_valid_for_order
|
||||
|
||||
return unless tax_rate_with_matching_tax.nil?
|
||||
|
||||
@adjustment.errors.add :tax_rate_id, I18n.t(:adjustments_tax_rate_error)
|
||||
end
|
||||
|
||||
def find_tax_rate_with_matching_tax(tax_rates)
|
||||
tax_rates_yielding_matching_tax = tax_rates.select do |tr|
|
||||
tr.compute_tax(@adjustment.amount) == @adjustment.included_tax
|
||||
end
|
||||
tax_rates_yielding_matching_tax.first.andand.id
|
||||
end
|
||||
|
||||
def set_included_tax
|
||||
included_tax = 0
|
||||
if params[:tax_rate_id].present?
|
||||
tax_rate = TaxRate.find params[:tax_rate_id]
|
||||
amount = params[:adjustment][:amount].to_f
|
||||
included_tax = tax_rate.compute_tax amount
|
||||
end
|
||||
params[:adjustment][:included_tax] = included_tax
|
||||
end
|
||||
|
||||
# Spree 2.0 keeps shipping fee adjustments open unless they are manually
|
||||
# closed. But open adjustments cannot be edited.
|
||||
# To preserve updates, like changing the amount of the shipping fee,
|
||||
# we close the adjustment first.
|
||||
#
|
||||
# The Spree admin interface allows to open and close adjustments manually
|
||||
# but we removed that functionality as it had no purpose for us.
|
||||
def enable_updates
|
||||
@adjustment.close
|
||||
def apply_tax
|
||||
Spree::TaxRate.adjust(@order, [@adjustment])
|
||||
end
|
||||
|
||||
def permitted_resource_params
|
||||
params.require(:adjustment).permit(
|
||||
:label, :amount, :included_tax
|
||||
:label, :amount, :tax_category_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,9 +14,7 @@ module Spree
|
||||
end
|
||||
|
||||
def edit
|
||||
country_id = Address.default.country.id
|
||||
@order.build_bill_address(country_id: country_id) if @order.bill_address.nil?
|
||||
@order.build_ship_address(country_id: country_id) if @order.ship_address.nil?
|
||||
build_addresses
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -25,9 +23,10 @@ module Spree
|
||||
@order.associate_user!(Spree.user_class.find_by(email: @order.email))
|
||||
end
|
||||
|
||||
refresh_shipment_rates
|
||||
recalculate_taxes
|
||||
OrderWorkflow.new(@order).advance_to_payment
|
||||
|
||||
@order.shipments.map(&:refresh_rates)
|
||||
flash[:success] = Spree.t('customer_details_updated')
|
||||
redirect_to spree.admin_order_customer_path(@order)
|
||||
else
|
||||
@@ -43,6 +42,25 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def build_addresses
|
||||
country_id = Address.default.country.id
|
||||
@order.build_bill_address(country_id: country_id) if @order.bill_address.nil?
|
||||
@order.build_ship_address(country_id: country_id) if @order.ship_address.nil?
|
||||
end
|
||||
|
||||
def refresh_shipment_rates
|
||||
@order.shipments.map(&:refresh_rates)
|
||||
end
|
||||
|
||||
def recalculate_taxes
|
||||
# If the order's address has been changed, the tax zone could be different,
|
||||
# which means a different set of tax rates might be applicable.
|
||||
@order.create_tax_charge!
|
||||
Spree::TaxRate.adjust(@order, @order.adjustments.admin)
|
||||
|
||||
@order.updater.update_totals_and_states
|
||||
end
|
||||
|
||||
def order_params
|
||||
params.require(:order).permit(
|
||||
:email,
|
||||
|
||||
19
app/helpers/adjustments_helper.rb
Normal file
19
app/helpers/adjustments_helper.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module AdjustmentsHelper
|
||||
def display_adjustment_taxes(adjustment)
|
||||
if adjustment.included_tax_total > 0
|
||||
amount = Spree::Money.new(adjustment.included_tax_total, currency: adjustment.currency)
|
||||
I18n.t(:tax_amount_included, amount: amount)
|
||||
elsif adjustment.additional_tax_total > 0
|
||||
Spree::Money.new(adjustment.additional_tax_total, currency: adjustment.currency)
|
||||
else
|
||||
Spree::Money.new(0.00, currency: adjustment.currency)
|
||||
end
|
||||
end
|
||||
|
||||
def display_adjustment_total_with_tax(adjustment)
|
||||
total = adjustment.amount + adjustment.additional_tax_total
|
||||
Spree::Money.new(total, currency: adjustment.currency)
|
||||
end
|
||||
end
|
||||
@@ -77,8 +77,6 @@ module Spree
|
||||
scope :enterprise_fee, -> { where(originator_type: 'EnterpriseFee') }
|
||||
scope :admin, -> { where(originator_type: nil) }
|
||||
|
||||
scope :with_tax, -> { where('spree_adjustments.included_tax <> 0') }
|
||||
scope :without_tax, -> { where('spree_adjustments.included_tax = 0') }
|
||||
scope :payment_fee, -> { where(AdjustmentScopes::PAYMENT_FEE_SCOPE) }
|
||||
scope :shipping, -> { where(AdjustmentScopes::SHIPPING_SCOPE) }
|
||||
scope :eligible, -> { where(AdjustmentScopes::ELIGIBLE_SCOPE) }
|
||||
@@ -129,20 +127,24 @@ module Spree
|
||||
state != "open"
|
||||
end
|
||||
|
||||
def set_absolute_included_tax!(tax)
|
||||
update! included_tax: tax.round(2)
|
||||
end
|
||||
|
||||
def display_included_tax
|
||||
Spree::Money.new(included_tax, currency: currency)
|
||||
end
|
||||
|
||||
def has_tax?
|
||||
included_tax.positive?
|
||||
tax_total.positive?
|
||||
end
|
||||
|
||||
def included_tax_total
|
||||
adjustments.tax.inclusive.sum(:amount)
|
||||
end
|
||||
|
||||
def additional_tax_total
|
||||
adjustments.tax.additional.sum(:amount)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tax_total
|
||||
adjustments.tax.sum(:amount)
|
||||
end
|
||||
|
||||
def update_adjustable_adjustment_total
|
||||
Spree::ItemAdjustments.new(adjustable).update if adjustable
|
||||
end
|
||||
|
||||
@@ -117,25 +117,6 @@ module Spree
|
||||
Zone.default_tax&.contains?(order.tax_zone) || order.tax_zone == zone
|
||||
end
|
||||
|
||||
# Manually apply a TaxRate to a particular amount. TaxRates normally compute against
|
||||
# LineItems or Orders, so we mock out a line item here to fit the interface
|
||||
# that our calculator (usually DefaultTax) expects.
|
||||
def compute_tax(amount)
|
||||
line_item = LineItem.new quantity: 1
|
||||
line_item.tax_category = tax_category
|
||||
line_item.define_singleton_method(:price) { amount }
|
||||
|
||||
# Tax on adjustments (represented by the included_tax field) is always inclusive of
|
||||
# tax. However, there's nothing to stop an admin from setting one up with a tax rate
|
||||
# that's marked as not inclusive of tax, and that would result in the DefaultTax
|
||||
# calculator generating a slightly incorrect value. Therefore, we treat the tax
|
||||
# rate as inclusive of tax for the calculations below, regardless of its original
|
||||
# setting.
|
||||
with_tax_included_in_price do
|
||||
calculator.compute line_item
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_label(adjustment_amount)
|
||||
@@ -146,19 +127,5 @@ module Spree
|
||||
label << " (#{I18n.t('models.tax_rate.included_in_price')})" if included_in_price?
|
||||
label
|
||||
end
|
||||
|
||||
def with_tax_included_in_price
|
||||
old_included_in_price = included_in_price
|
||||
|
||||
self.included_in_price = true
|
||||
calculator.calculable.included_in_price = true
|
||||
|
||||
result = yield
|
||||
ensure
|
||||
self.included_in_price = old_included_in_price
|
||||
calculator.calculable.included_in_price = old_included_in_price
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This class will be used to get Tax Adjustments related to an order,
|
||||
# and proceed basic calcultation over them.
|
||||
# Collects Tax Adjustments related to an order, and returns a hash with a total for each rate.
|
||||
|
||||
class OrderTaxAdjustmentsFetcher
|
||||
def initialize(order)
|
||||
@@ -9,47 +8,13 @@ class OrderTaxAdjustmentsFetcher
|
||||
end
|
||||
|
||||
def totals
|
||||
all.each_with_object({}) do |adjustment, hash|
|
||||
tax_rates_hash = tax_rates_hash(adjustment)
|
||||
hash.update(tax_rates_hash) { |_tax_rate, amount1, amount2| amount1 + amount2 }
|
||||
order.all_adjustments.tax.each_with_object({}) do |adjustment, hash|
|
||||
tax_rate = adjustment.originator
|
||||
hash[tax_rate] = hash[tax_rate].to_f + adjustment.amount
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :order
|
||||
|
||||
def all
|
||||
tax_adjustments = order.all_adjustments.tax
|
||||
admin_adjustments_with_tax = order.all_adjustments.admin.with_tax
|
||||
|
||||
tax_adjustments.or(admin_adjustments_with_tax)
|
||||
end
|
||||
|
||||
def tax_rates_hash(adjustment)
|
||||
tax_rates = TaxRateFinder.tax_rates_of(adjustment)
|
||||
|
||||
Hash[tax_rates.collect do |tax_rate|
|
||||
tax_amount = if tax_rates.one?
|
||||
adjustment_tax_amount(adjustment)
|
||||
else
|
||||
tax_rate.compute_tax(adjustment.amount)
|
||||
end
|
||||
[tax_rate, tax_amount]
|
||||
end]
|
||||
end
|
||||
|
||||
def adjustment_tax_amount(adjustment)
|
||||
if no_tax_adjustments?(adjustment)
|
||||
adjustment.included_tax
|
||||
else
|
||||
adjustment.amount
|
||||
end
|
||||
end
|
||||
|
||||
def no_tax_adjustments?(adjustment)
|
||||
# Admin Adjustments currently do not have tax adjustments.
|
||||
# The tax amount is stored in the included_tax attribute.
|
||||
adjustment.originator_type.nil?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,18 +6,12 @@
|
||||
class TaxRateFinder
|
||||
# @return [Array<Spree::TaxRate>]
|
||||
def self.tax_rates_of(adjustment)
|
||||
new.tax_rates(
|
||||
adjustment.originator,
|
||||
adjustment.adjustable,
|
||||
adjustment.amount,
|
||||
adjustment.included_tax
|
||||
)
|
||||
new.tax_rates(adjustment.originator, adjustment.adjustable)
|
||||
end
|
||||
|
||||
# @return [Array<Spree::TaxRate>]
|
||||
def tax_rates(originator, adjustable, amount, included_tax)
|
||||
find_associated_tax_rate(originator, adjustable) ||
|
||||
find_closest_tax_rates_from_included_tax(amount, included_tax)
|
||||
def tax_rates(originator, adjustable)
|
||||
find_associated_tax_rate(originator, adjustable) || []
|
||||
end
|
||||
|
||||
private
|
||||
@@ -48,37 +42,4 @@ class TaxRateFinder
|
||||
enterprise_fee.tax_category
|
||||
end
|
||||
end
|
||||
|
||||
# There are two cases in which a line item is not associated to a tax rate.
|
||||
#
|
||||
# 1. Shipping fees and adjustments created from the admin panel have taxes set
|
||||
# at creation in the included_tax field without relation to the
|
||||
# corresponding TaxRate.
|
||||
# 2. Removing line items from an order doesn't always remove the associated
|
||||
# enterprise fees. These orphaned fees don't have a line item any more to
|
||||
# find the item's tax rate.
|
||||
#
|
||||
# In these cases we try to find the used tax rate based on the included tax.
|
||||
# For example, if the included tax is 10% of the adjustment, we look for a tax
|
||||
# rate of 10%. Due to rounding errors, the included tax may be 9.9% of the
|
||||
# adjustment. That's why we call it an approximation of the tax rate and look
|
||||
# for the closest and hopefully find the 10% tax rate.
|
||||
#
|
||||
# This attempt can fail.
|
||||
#
|
||||
# - If an admin created an adjustment with a miscalculated included tax then
|
||||
# we don't know which tax rate the admin intended to use.
|
||||
# - An admin may also enter included tax that doesn't correspond to any tax
|
||||
# rate in the system. They may enter a fee of $1.2 with tax of $0.2, but
|
||||
# that doesn't mean that there is a 20% tax rate in the database.
|
||||
# - The used tax rate may also have been deleted. Maybe the tax law changed.
|
||||
#
|
||||
# In either of these cases, we will find a tax rate that doesn't correspond
|
||||
# to the included tax.
|
||||
def find_closest_tax_rates_from_included_tax(amount, included_tax)
|
||||
approximation = (included_tax / (amount - included_tax))
|
||||
return [] if approximation.infinite? || approximation.zero? || approximation.nan?
|
||||
|
||||
[Spree::TaxRate.order(Arel.sql("ABS(amount - #{approximation})")).first]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,20 +4,32 @@
|
||||
%th= "#{t('spree.date')}/#{t('spree.time')}"
|
||||
%th= t(:description)
|
||||
%th= t(:amount)
|
||||
%th= t(:included_tax)
|
||||
%th= t(:tax_category)
|
||||
%th= t(:tax)
|
||||
%th= t(:total_incl_tax)
|
||||
%th.actions
|
||||
%tbody
|
||||
- @collection.each do |adjustment|
|
||||
- @edit_url = edit_admin_order_adjustment_path(@order, adjustment)
|
||||
- @delete_url = admin_order_adjustment_path(@order, adjustment)
|
||||
- taxable = adjustment.adjustable_type == "Spree::Shipment" ? adjustment.adjustable : adjustment
|
||||
- tr_class = cycle('odd', 'even')
|
||||
- tr_id = spree_dom_id(adjustment)
|
||||
%tr{:class => tr_class, "data-hook" => "adjustment_row", :id => tr_id}
|
||||
%td.align-center.created_at= pretty_time(adjustment.created_at)
|
||||
%td.align-center.label= adjustment.label
|
||||
%td.align-center.amount= adjustment.display_amount.to_html
|
||||
%td.align-center.included-tax= adjustment.display_included_tax.to_html
|
||||
%td.align-center.created_at
|
||||
= pretty_time(adjustment.created_at)
|
||||
%td.align-center.label
|
||||
= adjustment.label
|
||||
%td.align-center.amount
|
||||
= adjustment.display_amount.to_html
|
||||
%td.align-center.tax-category
|
||||
= taxable.tax_category&.name || "-"
|
||||
%td.align-center.tax
|
||||
= display_adjustment_taxes(taxable)
|
||||
%td.align-center.total
|
||||
= display_adjustment_total_with_tax(taxable)
|
||||
- unless @order.canceled?
|
||||
%td.actions
|
||||
= link_to_edit adjustment, no_text: true
|
||||
= link_to_delete adjustment, no_text: true
|
||||
- if adjustment.originator_type.nil?
|
||||
= link_to_edit adjustment, no_text: true
|
||||
= link_to_delete adjustment, no_text: true
|
||||
|
||||
@@ -6,18 +6,14 @@
|
||||
= f.error_message_on :amount
|
||||
|
||||
- if @adjustment.admin?
|
||||
.four.columns
|
||||
= f.field_container :included_tax do
|
||||
= f.label :included_tax, t(:included_tax)
|
||||
= f.text_field :included_tax, disabled: true, class: 'fullwidth',
|
||||
value: number_with_precision(f.object.included_tax, precision: 2)
|
||||
= f.error_message_on :included_tax
|
||||
|
||||
.omega.four.columns
|
||||
= f.field_container :tax_rate_id do
|
||||
= f.label :tax_rate_id, t(:tax_rate)
|
||||
= select_tag :tax_rate_id, options_from_collection_for_select(Spree::TaxRate.all, :id, :name, @tax_rate_id), prompt: t(:remove_tax), class: 'select2 fullwidth'
|
||||
= f.error_message_on :tax_rate_id
|
||||
= f.field_container :tax_category do
|
||||
= f.label :tax_category, t(:tax_category)
|
||||
= select_tag "adjustment[tax_category_id]",
|
||||
options_from_collection_for_select(Spree::TaxCategory.all, :id, :name, @adjustment.tax_category_id),
|
||||
prompt: t(:none),
|
||||
class: 'select2 fullwidth'
|
||||
= f.error_message_on :tax_category
|
||||
|
||||
.row
|
||||
.alpha.omega.twelve.columns
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
.row
|
||||
.alpha.three.columns
|
||||
.alpha.four.columns
|
||||
= f.field_container :amount do
|
||||
= f.label :amount, raw(t(:amount) + content_tag(:span, " *", :class => "required"))
|
||||
= text_field :adjustment, :amount, :class => 'fullwidth'
|
||||
= f.error_message_on :amount
|
||||
|
||||
.omega.three.columns
|
||||
= f.field_container :tax_rate_id do
|
||||
= f.label :tax_rate_id, t(:tax_rate)
|
||||
= select_tag :tax_rate_id, options_from_collection_for_select(Spree::TaxRate.all, :id, :name), prompt: t(:none), class: 'select2 fullwidth'
|
||||
= f.error_message_on :tax_rate_id
|
||||
.omega.four.columns
|
||||
= f.field_container :tax_category do
|
||||
= f.label :tax_category, t(:tax_category)
|
||||
= select_tag "adjustment[tax_category_id]",
|
||||
options_from_collection_for_select(Spree::TaxCategory.all, :id, :name),
|
||||
prompt: t(:none),
|
||||
class: 'select2 fullwidth'
|
||||
= f.error_message_on :tax_category
|
||||
|
||||
.row
|
||||
.alpha.omega.twelve.columns
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
%td{:align => "right"}
|
||||
1
|
||||
%td{:align => "right"}
|
||||
= adjustment.included_tax > 0 ? adjustment.display_included_tax : ""
|
||||
= display_adjustment_taxes(adjustment)
|
||||
%td{:align => "right"}
|
||||
= adjustment.display_amount
|
||||
%tfoot
|
||||
|
||||
@@ -2164,6 +2164,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
fundraising_fee: "Fundraising fee"
|
||||
price_graph: "Price graph"
|
||||
included_tax: "Included tax"
|
||||
tax: "Tax"
|
||||
tax_amount_included: "%{amount} (included)"
|
||||
remove_tax: "Remove tax"
|
||||
balance: "Balance"
|
||||
transaction: "Transaction"
|
||||
|
||||
61
db/migrate/20210617203927_migrate_admin_tax_amounts.rb
Normal file
61
db/migrate/20210617203927_migrate_admin_tax_amounts.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
class MigrateAdminTaxAmounts < ActiveRecord::Migration[6.0]
|
||||
class Spree::Adjustment < ApplicationRecord
|
||||
belongs_to :originator, polymorphic: true
|
||||
belongs_to :adjustable, polymorphic: true
|
||||
belongs_to :order, class_name: "Spree::Order"
|
||||
belongs_to :tax_category, class_name: 'Spree::TaxCategory'
|
||||
has_many :adjustments, as: :adjustable, dependent: :destroy
|
||||
|
||||
scope :admin, -> { where(originator_type: nil) }
|
||||
end
|
||||
|
||||
def up
|
||||
migrate_admin_taxes!
|
||||
end
|
||||
|
||||
def migrate_admin_taxes!
|
||||
Spree::Adjustment.admin.where('included_tax <> 0').includes(:order).find_each do |adjustment|
|
||||
|
||||
tax_rate = find_tax_rate(adjustment)
|
||||
tax_category = tax_rate&.tax_category
|
||||
label = tax_adjustment_label(tax_rate)
|
||||
|
||||
adjustment.update_columns(tax_category_id: tax_category.id) if tax_category.present?
|
||||
|
||||
Spree::Adjustment.create!(
|
||||
label: label,
|
||||
amount: adjustment.included_tax,
|
||||
order_id: adjustment.order_id,
|
||||
adjustable: adjustment,
|
||||
originator_type: "Spree::TaxRate",
|
||||
originator_id: tax_rate&.id,
|
||||
state: "closed",
|
||||
included: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def find_tax_rate(adjustment)
|
||||
amount = adjustment.amount
|
||||
included_tax = adjustment.included_tax
|
||||
approximation = (included_tax / (amount - included_tax))
|
||||
|
||||
return if approximation.infinite? || approximation.zero? || approximation.nan?
|
||||
|
||||
applicable_rates(adjustment).min_by{ |rate| (rate.amount - approximation).abs }
|
||||
end
|
||||
|
||||
def applicable_rates(adjustment)
|
||||
return [] unless adjustment.order&.distributor_id.present?
|
||||
|
||||
Spree::TaxRate.match(adjustment.order)
|
||||
end
|
||||
|
||||
def tax_adjustment_label(tax_rate)
|
||||
if tax_rate.nil?
|
||||
I18n.t('included_tax')
|
||||
else
|
||||
"#{tax_rate.name} #{tax_rate.amount * 100}% (#{I18n.t('models.tax_rate.included_in_price')})"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2021_05_27_201938) do
|
||||
ActiveRecord::Schema.define(version: 2021_06_17_203927) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -67,8 +67,7 @@ module OrderManagement
|
||||
def update_adjustment_total
|
||||
order.adjustment_total = all_adjustments.additional.eligible.sum(:amount)
|
||||
order.additional_tax_total = all_adjustments.tax.additional.sum(:amount)
|
||||
order.included_tax_total = all_adjustments.tax.inclusive.sum(:amount) +
|
||||
adjustments.admin.sum(:included_tax)
|
||||
order.included_tax_total = all_adjustments.tax.inclusive.sum(:amount)
|
||||
end
|
||||
|
||||
def update_order_total
|
||||
|
||||
@@ -34,12 +34,11 @@ module OrderManagement
|
||||
:sum).and_return(20)
|
||||
allow(order).to receive_message_chain(:all_adjustments, :tax, :inclusive,
|
||||
:sum).and_return(15)
|
||||
allow(order).to receive_message_chain(:adjustments, :admin, :sum).and_return(2)
|
||||
|
||||
updater.update_adjustment_total
|
||||
expect(order.adjustment_total).to eq(-5)
|
||||
expect(order.additional_tax_total).to eq(20)
|
||||
expect(order.included_tax_total).to eq(17)
|
||||
expect(order.included_tax_total).to eq(15)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -219,11 +219,11 @@ module OpenFoodNetwork
|
||||
end
|
||||
|
||||
def total_untaxable_admin_adjustments(order)
|
||||
order.adjustments.admin.without_tax.sum(:amount)
|
||||
order.adjustments.admin.where(tax_category: nil).sum(:amount)
|
||||
end
|
||||
|
||||
def total_taxable_admin_adjustments(order)
|
||||
order.adjustments.admin.with_tax.sum(:amount)
|
||||
order.adjustments.admin.where.not(tax_category: nil).sum(:amount)
|
||||
end
|
||||
|
||||
def detail?
|
||||
|
||||
@@ -43,71 +43,146 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
describe "setting included tax" do
|
||||
describe "setting the adjustment's tax" do
|
||||
let(:order) { create(:order) }
|
||||
let(:tax_rate) { create(:tax_rate, amount: 0.1, calculator: ::Calculator::DefaultTax.new) }
|
||||
let(:zone) { create(:zone_with_member) }
|
||||
let(:tax_rate) { create(:tax_rate, amount: 0.1, zone: zone, included_in_price: true ) }
|
||||
|
||||
describe "creating an adjustment" do
|
||||
it "sets included tax to zero when no tax rate is specified" do
|
||||
spree_post :create, order_id: order.number,
|
||||
adjustment: { label: 'Testing included tax', amount: '110' }, tax_rate_id: ''
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
let(:tax_category_param) { '' }
|
||||
let(:params) {
|
||||
{
|
||||
order_id: order.number,
|
||||
adjustment: {
|
||||
label: 'Testing included tax', amount: '110', tax_category_id: tax_category_param
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a = Adjustment.last
|
||||
expect(a.label).to eq('Testing included tax')
|
||||
expect(a.amount).to eq(110)
|
||||
expect(a.included_tax).to eq(0)
|
||||
expect(a.order_id).to eq(order.id)
|
||||
context "when no tax category is specified" do
|
||||
it "doesn't apply tax" do
|
||||
spree_post :create, params
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
|
||||
expect(order.reload.total).to eq 110
|
||||
new_adjustment = Adjustment.admin.last
|
||||
|
||||
expect(new_adjustment.label).to eq('Testing included tax')
|
||||
expect(new_adjustment.amount).to eq(110)
|
||||
expect(new_adjustment.tax_category).to be_nil
|
||||
expect(new_adjustment.order_id).to eq(order.id)
|
||||
|
||||
expect(order.reload.total).to eq 110
|
||||
expect(order.included_tax_total).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
it "calculates included tax when a tax rate is provided" do
|
||||
spree_post :create, order_id: order.number,
|
||||
adjustment: { label: 'Testing included tax', amount: '110' }, tax_rate_id: tax_rate.id.to_s
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
context "when a tax category is provided" do
|
||||
let(:tax_category_param) { tax_rate.tax_category.id.to_s }
|
||||
|
||||
a = Adjustment.last
|
||||
expect(a.label).to eq('Testing included tax')
|
||||
expect(a.amount).to eq(110)
|
||||
expect(a.included_tax).to eq(10)
|
||||
expect(a.order_id).to eq(order.id)
|
||||
it "applies tax" do
|
||||
spree_post :create, params
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
|
||||
expect(order.reload.total).to eq 110
|
||||
new_adjustment = Adjustment.admin.last
|
||||
|
||||
expect(new_adjustment.label).to eq('Testing included tax')
|
||||
expect(new_adjustment.amount).to eq(110)
|
||||
expect(new_adjustment.tax_category).to eq tax_rate.tax_category
|
||||
expect(new_adjustment.order_id).to eq(order.id)
|
||||
|
||||
expect(order.reload.total).to eq 110
|
||||
expect(order.included_tax_total).to eq 10
|
||||
end
|
||||
end
|
||||
|
||||
context "when the tax category has multiple rates for the same tax zone" do
|
||||
let(:tax_category) { create(:tax_category) }
|
||||
let!(:tax_rate1) {
|
||||
create(:tax_rate, amount: 0.1, zone: zone, included_in_price: false,
|
||||
tax_category: tax_category )
|
||||
}
|
||||
let!(:tax_rate2) {
|
||||
create(:tax_rate, amount: 0.2, zone: zone, included_in_price: false,
|
||||
tax_category: tax_category )
|
||||
}
|
||||
let(:tax_category_param) { tax_category.id.to_s }
|
||||
let(:params) {
|
||||
{
|
||||
order_id: order.number,
|
||||
adjustment: {
|
||||
label: 'Testing multiple rates', amount: '100', tax_category_id: tax_category_param
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it "applies both rates" do
|
||||
spree_post :create, params
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
|
||||
new_adjustment = Adjustment.admin.last
|
||||
|
||||
expect(new_adjustment.amount).to eq(100)
|
||||
expect(new_adjustment.tax_category).to eq tax_category
|
||||
expect(new_adjustment.order_id).to eq(order.id)
|
||||
expect(new_adjustment.adjustments.tax.count).to eq 2
|
||||
|
||||
expect(order.reload.total).to eq 130
|
||||
expect(order.additional_tax_total).to eq 30
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "updating an adjustment" do
|
||||
let(:old_tax_category) { create(:tax_category) }
|
||||
let(:tax_category_param) { '' }
|
||||
let(:params) {
|
||||
{
|
||||
id: adjustment.id,
|
||||
order_id: order.number,
|
||||
adjustment: {
|
||||
label: 'Testing included tax', amount: '110', tax_category_id: tax_category_param
|
||||
}
|
||||
}
|
||||
}
|
||||
let(:adjustment) {
|
||||
create(:adjustment, adjustable: order, order: order, amount: 1100, included_tax: 100)
|
||||
create(:adjustment, adjustable: order, order: order,
|
||||
amount: 1100, tax_category: old_tax_category)
|
||||
}
|
||||
|
||||
it "sets included tax to zero when no tax rate is specified" do
|
||||
spree_put :update, order_id: order.number, id: adjustment.id,
|
||||
adjustment: { label: 'Testing included tax', amount: '110' }, tax_rate_id: ''
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
context "when no tax category is specified" do
|
||||
it "doesn't apply tax" do
|
||||
spree_put :update, params
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
|
||||
a = Adjustment.last
|
||||
expect(a.label).to eq('Testing included tax')
|
||||
expect(a.amount).to eq(110)
|
||||
expect(a.included_tax).to eq(0)
|
||||
expect(a.order_id).to eq(order.id)
|
||||
adjustment = Adjustment.admin.last
|
||||
|
||||
expect(order.reload.total).to eq 110
|
||||
expect(adjustment.label).to eq('Testing included tax')
|
||||
expect(adjustment.amount).to eq(110)
|
||||
expect(adjustment.tax_category).to be_nil
|
||||
expect(adjustment.order_id).to eq(order.id)
|
||||
|
||||
expect(order.reload.total).to eq 110
|
||||
expect(order.included_tax_total).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
it "calculates included tax when a tax rate is provided" do
|
||||
spree_put :update, order_id: order.number, id: adjustment.id,
|
||||
adjustment: { label: 'Testing included tax', amount: '110' }, tax_rate_id: tax_rate.id.to_s
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
context "when a tax category is provided" do
|
||||
let(:tax_category_param) { tax_rate.tax_category.id.to_s }
|
||||
|
||||
a = Adjustment.last
|
||||
expect(a.label).to eq('Testing included tax')
|
||||
expect(a.amount).to eq(110)
|
||||
expect(a.included_tax).to eq(10)
|
||||
expect(a.order_id).to eq(order.id)
|
||||
it "applies tax" do
|
||||
spree_put :update, params
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
|
||||
expect(order.reload.total).to eq 110
|
||||
adjustment = Adjustment.admin.last
|
||||
|
||||
expect(adjustment.label).to eq('Testing included tax')
|
||||
expect(adjustment.amount).to eq(110)
|
||||
expect(adjustment.tax_category).to eq tax_rate.tax_category
|
||||
expect(adjustment.order_id).to eq(order.id)
|
||||
|
||||
expect(order.reload.total).to eq 110
|
||||
expect(order.included_tax_total).to eq 10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -151,7 +226,7 @@ module Spree
|
||||
let(:order) { create(:completed_order_with_totals) }
|
||||
let(:tax_rate) { create(:tax_rate, amount: 0.1, calculator: ::Calculator::DefaultTax.new) }
|
||||
let(:adjustment) {
|
||||
create(:adjustment, adjustable: order, order: order, amount: 1100, included_tax: 100)
|
||||
create(:adjustment, adjustable: order, order: order, amount: 1100)
|
||||
}
|
||||
|
||||
before do
|
||||
@@ -161,7 +236,7 @@ module Spree
|
||||
it "doesn't create adjustments" do
|
||||
expect {
|
||||
spree_post :create, order_id: order.number,
|
||||
adjustment: { label: "Testing", amount: "110" }, tax_rate_id: ""
|
||||
adjustment: { label: "Testing", amount: "110" }
|
||||
}.to_not change { [Adjustment.count, order.reload.total] }
|
||||
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
@@ -170,7 +245,7 @@ module Spree
|
||||
it "doesn't change adjustments" do
|
||||
expect {
|
||||
spree_put :update, order_id: order.number, id: adjustment.id,
|
||||
adjustment: { label: "Testing", amount: "110" }, tax_rate_id: ""
|
||||
adjustment: { label: "Testing", amount: "110" }
|
||||
}.to_not change { [adjustment.reload.amount, order.reload.total] }
|
||||
|
||||
expect(response).to redirect_to spree.admin_order_adjustments_path(order)
|
||||
|
||||
@@ -17,9 +17,10 @@ feature '
|
||||
create(:order_with_totals_and_distribution, user: user, distributor: distributor,
|
||||
order_cycle: order_cycle, state: 'complete', payment_state: 'balance_due')
|
||||
}
|
||||
let!(:tax_category) { create(:tax_category, name: 'GST') }
|
||||
let!(:tax_rate) {
|
||||
create(:tax_rate, name: 'GST', calculator: build(:calculator, preferred_amount: 10),
|
||||
zone: create(:zone_with_member))
|
||||
zone: create(:zone_with_member), tax_category: tax_category)
|
||||
}
|
||||
|
||||
before do
|
||||
@@ -37,19 +38,19 @@ feature '
|
||||
click_link 'New Adjustment'
|
||||
fill_in 'adjustment_amount', with: 110
|
||||
fill_in 'adjustment_label', with: 'Late fee'
|
||||
select2_select 'GST', from: 'tax_rate_id'
|
||||
select2_select 'GST', from: 'adjustment_tax_category_id'
|
||||
click_button 'Continue'
|
||||
|
||||
# Then I should see the adjustment, with the correct tax
|
||||
expect(page).to have_selector 'td.label', text: 'Late fee'
|
||||
expect(page).to have_selector 'td.amount', text: '110'
|
||||
expect(page).to have_selector 'td.included-tax', text: '10'
|
||||
expect(page).to have_selector 'td.amount', text: '110.00'
|
||||
expect(page).to have_selector 'td.tax', text: '10.00'
|
||||
end
|
||||
|
||||
scenario "modifying taxed adjustments on an order" do
|
||||
# Given a taxed adjustment
|
||||
adjustment = create(:adjustment, label: "Extra Adjustment", adjustable: order,
|
||||
amount: 110, included_tax: 10, order: order)
|
||||
amount: 110, tax_category: tax_category, order: order)
|
||||
|
||||
# When I go to the adjustments page for the order
|
||||
login_as_admin_and_visit spree.admin_orders_path
|
||||
@@ -57,23 +58,21 @@ feature '
|
||||
click_link 'Adjustments'
|
||||
page.find('tr', text: 'Extra Adjustment').find('a.icon-edit').click
|
||||
|
||||
# Then I should see the uneditable included tax and our tax rate as the default
|
||||
expect(page).to have_field :adjustment_included_tax, with: '10.00', disabled: true
|
||||
expect(page).to have_select2 :tax_rate_id, selected: 'GST'
|
||||
expect(page).to have_select2 :adjustment_tax_category_id, selected: 'GST'
|
||||
|
||||
# When I edit the adjustment, removing the tax
|
||||
select2_select 'Remove tax', from: :tax_rate_id
|
||||
select2_select 'None', from: :adjustment_tax_category_id
|
||||
click_button 'Continue'
|
||||
|
||||
# Then the adjustment tax should be cleared
|
||||
expect(page).to have_selector 'td.amount', text: '110'
|
||||
expect(page).to have_selector 'td.included-tax', text: '0'
|
||||
expect(page).to have_selector 'td.amount', text: '110.00'
|
||||
expect(page).to have_selector 'td.tax', text: '0.00'
|
||||
end
|
||||
|
||||
scenario "modifying an untaxed adjustment on an order" do
|
||||
# Given an untaxed adjustment
|
||||
adjustment = create(:adjustment, label: "Extra Adjustment", adjustable: order,
|
||||
amount: 110, included_tax: 0, order: order)
|
||||
amount: 110, tax_category: nil, order: order)
|
||||
|
||||
# When I go to the adjustments page for the order
|
||||
login_as_admin_and_visit spree.admin_orders_path
|
||||
@@ -81,23 +80,21 @@ feature '
|
||||
click_link 'Adjustments'
|
||||
page.find('tr', text: 'Extra Adjustment').find('a.icon-edit').click
|
||||
|
||||
# Then I should see the uneditable included tax and 'Remove tax' as the default tax rate
|
||||
expect(page).to have_field :adjustment_included_tax, with: '0.00', disabled: true
|
||||
expect(page).to have_select2 :tax_rate_id, selected: []
|
||||
expect(page).to have_select2 :adjustment_tax_category_id, selected: []
|
||||
|
||||
# When I edit the adjustment, setting a tax rate
|
||||
select2_select 'GST', from: :tax_rate_id
|
||||
select2_select 'GST', from: :adjustment_tax_category_id
|
||||
click_button 'Continue'
|
||||
|
||||
# Then the adjustment tax should be recalculated
|
||||
expect(page).to have_selector 'td.amount', text: '110'
|
||||
expect(page).to have_selector 'td.included-tax', text: '10'
|
||||
expect(page).to have_selector 'td.amount', text: '110.00'
|
||||
expect(page).to have_selector 'td.tax', text: '10.00'
|
||||
end
|
||||
|
||||
scenario "viewing adjustments on a canceled order" do
|
||||
# Given a taxed adjustment
|
||||
adjustment = create(:adjustment, label: "Extra Adjustment", adjustable: order,
|
||||
amount: 110, included_tax: 10, order: order)
|
||||
amount: 110, tax_category: tax_category, order: order)
|
||||
order.cancel!
|
||||
|
||||
login_as_admin_and_visit spree.edit_admin_order_path(order)
|
||||
|
||||
@@ -409,15 +409,14 @@ feature '
|
||||
expect(page).to have_content test_tracking_number
|
||||
end
|
||||
|
||||
scenario "editing shipping fees" do
|
||||
scenario "viewing shipping fees" do
|
||||
shipping_fee = order.shipment_adjustments.first
|
||||
|
||||
click_link "Adjustments"
|
||||
shipping_adjustment_tr_selector = "tr#spree_adjustment_#{order.shipment_adjustments.first.id}"
|
||||
page.find("#{shipping_adjustment_tr_selector} td.actions a.icon-edit").click
|
||||
|
||||
fill_in "Amount", with: "5"
|
||||
click_button "Continue"
|
||||
|
||||
expect(page.find("#{shipping_adjustment_tr_selector} td.amount")).to have_content "5.00"
|
||||
expect(page).to have_selector "tr#spree_adjustment_#{shipping_fee.id}"
|
||||
expect(page).to have_selector 'td.amount', text: shipping_fee.amount.to_s
|
||||
expect(page).to have_selector 'td.tax', text: shipping_fee.included_tax_total.to_s
|
||||
end
|
||||
|
||||
context "when an included variant has been deleted" do
|
||||
|
||||
@@ -504,13 +504,13 @@ feature '
|
||||
create(:adjustment, order: order1, adjustable: adj_fee2, originator: tax_rate, amount: 3,
|
||||
state: "closed")
|
||||
}
|
||||
let!(:adj_manual1) {
|
||||
let!(:adj_admin1) {
|
||||
create(:adjustment, order: order1, adjustable: order1, originator: nil,
|
||||
label: "Manual adjustment", amount: 30)
|
||||
}
|
||||
let!(:adj_manual2) {
|
||||
let!(:adj_admin2) {
|
||||
create(:adjustment, order: order1, adjustable: order1, originator: nil,
|
||||
label: "Manual adjustment", amount: 40, included_tax: 3)
|
||||
label: "Manual adjustment", amount: 40, tax_category: tax_category)
|
||||
}
|
||||
|
||||
before do
|
||||
@@ -590,8 +590,8 @@ feature '
|
||||
xero_invoice_header,
|
||||
xero_invoice_li_row(line_item1),
|
||||
xero_invoice_li_row(line_item2),
|
||||
xero_invoice_adjustment_row(adj_manual1),
|
||||
xero_invoice_adjustment_row(adj_manual2),
|
||||
xero_invoice_adjustment_row(adj_admin1),
|
||||
xero_invoice_adjustment_row(adj_admin2),
|
||||
xero_invoice_summary_row('Total untaxable fees (no tax)', 10.0,
|
||||
'GST Free Income', opts),
|
||||
xero_invoice_summary_row('Total taxable fees (tax inclusive)', 20.0,
|
||||
|
||||
129
spec/migrations/migrate_admin_tax_amounts_spec.rb
Normal file
129
spec/migrations/migrate_admin_tax_amounts_spec.rb
Normal file
@@ -0,0 +1,129 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_relative '../../db/migrate/20210617203927_migrate_admin_tax_amounts'
|
||||
|
||||
describe MigrateAdminTaxAmounts do
|
||||
subject { MigrateAdminTaxAmounts.new }
|
||||
|
||||
let(:tax_category10) { create(:tax_category) }
|
||||
let(:tax_category50) { create(:tax_category) }
|
||||
let!(:tax_rate10) { create(:tax_rate, amount: 0.1, tax_category: tax_category10) }
|
||||
let!(:tax_rate50) { create(:tax_rate, amount: 0.5, tax_category: tax_category50) }
|
||||
let(:adjustment10) { create(:adjustment, amount: 100, included_tax: 10) }
|
||||
let(:adjustment50) { create(:adjustment, amount: 100, included_tax: 50) }
|
||||
|
||||
describe '#migrate_admin_taxes!' do
|
||||
context "when the adjustment has no tax" do
|
||||
let!(:adjustment_without_tax) { create(:adjustment, included_tax: 0) }
|
||||
|
||||
it "doesn't move the tax to an adjustment" do
|
||||
expect { subject.migrate_admin_taxes! }.to_not change {
|
||||
Spree::Adjustment.count
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context "when the adjustments have tax" do
|
||||
before do
|
||||
adjustment10; adjustment50
|
||||
allow(subject).to receive(:applicable_rates) { [tax_rate10, tax_rate50] }
|
||||
end
|
||||
|
||||
it "moves the tax to an adjustment" do
|
||||
expect(Spree::Adjustment).to receive(:create!).at_least(:once).and_call_original
|
||||
|
||||
subject.migrate_admin_taxes!
|
||||
expect(adjustment10.reload.tax_category).to eq tax_category10
|
||||
expect(adjustment50.reload.tax_category).to eq tax_category50
|
||||
|
||||
tax_adjustment10 = Spree::Adjustment.tax.where(adjustable_id: adjustment10).first
|
||||
|
||||
expect(tax_adjustment10.amount).to eq adjustment10.included_tax
|
||||
expect(tax_adjustment10.adjustable).to eq adjustment10
|
||||
expect(tax_adjustment10.originator).to eq tax_rate10
|
||||
expect(tax_adjustment10.state).to eq "closed"
|
||||
expect(tax_adjustment10.included).to eq true
|
||||
|
||||
tax_adjustment50 = Spree::Adjustment.tax.where(adjustable_id: adjustment50).first
|
||||
|
||||
expect(tax_adjustment50.amount).to eq adjustment50.included_tax
|
||||
expect(tax_adjustment50.adjustable).to eq adjustment50
|
||||
expect(tax_adjustment50.originator).to eq tax_rate50
|
||||
expect(tax_adjustment50.state).to eq "closed"
|
||||
expect(tax_adjustment50.included).to eq true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#find_tax_rate" do
|
||||
before do
|
||||
allow(subject).to receive(:applicable_rates) { [tax_rate10, tax_rate50] }
|
||||
end
|
||||
|
||||
it "matches rates correctly" do
|
||||
expect(subject.find_tax_rate(adjustment10)).to eq(tax_rate10)
|
||||
|
||||
expect(subject.find_tax_rate(adjustment50)).to eq(tax_rate50)
|
||||
end
|
||||
|
||||
context "without a perfect match" do
|
||||
let(:adjustment45) { create(:adjustment, amount: 100, included_tax: 45) }
|
||||
|
||||
it "finds the closest match" do
|
||||
expect(subject.find_tax_rate(adjustment45)).to eq(tax_rate50)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#applicabe_rates" do
|
||||
let(:distributor) { create(:enterprise) }
|
||||
let(:order) { create(:order, distributor: distributor) }
|
||||
let!(:adjustment) { create(:adjustment, order: order) }
|
||||
|
||||
context "when the order is nil" do
|
||||
let(:order) { nil }
|
||||
|
||||
it "returns an empty array" do
|
||||
expect(Spree::TaxRate).to_not receive(:match)
|
||||
|
||||
expect(subject.applicable_rates(adjustment)).to eq []
|
||||
end
|
||||
end
|
||||
|
||||
context "when the order has no distributor" do
|
||||
let(:distributor) { nil }
|
||||
|
||||
it "returns an empty array" do
|
||||
expect(Spree::TaxRate).to_not receive(:match)
|
||||
|
||||
expect(subject.applicable_rates(adjustment)).to eq []
|
||||
end
|
||||
end
|
||||
|
||||
context "when the order has a distributor" do
|
||||
it "calls TaxRate#match for an array of applicable taxes for the order" do
|
||||
expect(Spree::TaxRate).to receive(:match) { [tax_rate10] }
|
||||
|
||||
expect(subject.applicable_rates(adjustment)).to eq [tax_rate10]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#tax_adjustment_label' do
|
||||
let(:tax_rate) { create(:tax_rate, name: "Test Rate", amount: 0.20) }
|
||||
|
||||
context "when a tax rate is given" do
|
||||
it "makes a detailed label" do
|
||||
expect(subject.tax_adjustment_label(tax_rate)).
|
||||
to eq("Test Rate 20.0% (Included in price)")
|
||||
end
|
||||
end
|
||||
|
||||
context "when the tax rate is nil" do
|
||||
it "makes a basic label" do
|
||||
expect(subject.tax_adjustment_label(nil)).to eq("Included tax")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -148,33 +148,6 @@ module Spree
|
||||
expect(adjustment.metadata).to be
|
||||
end
|
||||
|
||||
describe "querying included tax" do
|
||||
let!(:adjustment_with_tax) { create(:adjustment, included_tax: 123) }
|
||||
let!(:adjustment_without_tax) { create(:adjustment, included_tax: 0) }
|
||||
|
||||
describe "finding adjustments with and without tax included" do
|
||||
it "finds adjustments with tax" do
|
||||
expect(Adjustment.with_tax).to include adjustment_with_tax
|
||||
expect(Adjustment.with_tax).not_to include adjustment_without_tax
|
||||
end
|
||||
|
||||
it "finds adjustments without tax" do
|
||||
expect(Adjustment.without_tax).to include adjustment_without_tax
|
||||
expect(Adjustment.without_tax).not_to include adjustment_with_tax
|
||||
end
|
||||
end
|
||||
|
||||
describe "checking if an adjustment includes tax" do
|
||||
it "returns true when it has > 0 tax" do
|
||||
expect(adjustment_with_tax).to have_tax
|
||||
end
|
||||
|
||||
it "returns false when it has 0 tax" do
|
||||
expect(adjustment_without_tax).not_to have_tax
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "recording included tax" do
|
||||
describe "TaxRate adjustments" do
|
||||
let!(:zone) { create(:zone_with_member) }
|
||||
|
||||
@@ -37,45 +37,6 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
describe "ensuring that tax rate is marked as tax included_in_price" do
|
||||
let(:tax_rate) {
|
||||
create(:tax_rate, included_in_price: false, calculator: ::Calculator::DefaultTax.new)
|
||||
}
|
||||
|
||||
it "sets included_in_price to true" do
|
||||
tax_rate.send(:with_tax_included_in_price) do
|
||||
expect(tax_rate.included_in_price).to be true
|
||||
end
|
||||
end
|
||||
|
||||
it "sets the included_in_price value accessible to the calculator to true" do
|
||||
tax_rate.send(:with_tax_included_in_price) do
|
||||
expect(tax_rate.calculator.calculable.included_in_price).to be true
|
||||
end
|
||||
end
|
||||
|
||||
it "passes through the return value of the block" do
|
||||
expect(tax_rate.send(:with_tax_included_in_price) do
|
||||
'asdf'
|
||||
end).to eq('asdf')
|
||||
end
|
||||
|
||||
it "restores both values to their original afterwards" do
|
||||
tax_rate.send(:with_tax_included_in_price) {}
|
||||
expect(tax_rate.included_in_price).to be false
|
||||
expect(tax_rate.calculator.calculable.included_in_price).to be false
|
||||
end
|
||||
|
||||
it "restores both values when an exception is raised" do
|
||||
expect do
|
||||
tax_rate.send(:with_tax_included_in_price) { raise StandardError, 'oops' }
|
||||
end.to raise_error 'oops'
|
||||
|
||||
expect(tax_rate.included_in_price).to be false
|
||||
expect(tax_rate.calculator.calculable.included_in_price).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context "original Spree::TaxRate specs" do
|
||||
context "match" do
|
||||
let(:order) { create(:order) }
|
||||
|
||||
@@ -52,8 +52,10 @@ describe OrderTaxAdjustmentsFetcher do
|
||||
calculator: Calculator::FlatRate.new(preferred_amount: 48.0))
|
||||
end
|
||||
let(:admin_adjustment) do
|
||||
create(:adjustment, order: order, amount: 50.0, included_tax: tax_rate25.compute_tax(50.0),
|
||||
label: "Admin Adjustment")
|
||||
create(:adjustment, order: order, amount: 50.0, tax_category: tax_category25,
|
||||
label: "Admin Adjustment").tap do |adjustment|
|
||||
Spree::TaxRate.adjust(order, [adjustment])
|
||||
end
|
||||
end
|
||||
|
||||
let(:order_cycle) do
|
||||
|
||||
@@ -5,71 +5,46 @@ require 'spec_helper'
|
||||
describe TaxRateFinder do
|
||||
describe "getting the corresponding tax rate" do
|
||||
let(:amount) { BigDecimal(120) }
|
||||
let(:included_tax) { BigDecimal(20) }
|
||||
let(:tax_rate) { create_rate(0.2) }
|
||||
let(:tax_rate) {
|
||||
create(:tax_rate, amount: 0.2, calculator: Calculator::DefaultTax.new, zone: zone)
|
||||
}
|
||||
let(:tax_category) { create(:tax_category, tax_rates: [tax_rate]) }
|
||||
let(:zone) { create(:zone_with_member) }
|
||||
let(:shipment) { create(:shipment) }
|
||||
let(:line_item) { create(:line_item) }
|
||||
let(:enterprise_fee) { create(:enterprise_fee, tax_category: tax_category) }
|
||||
let(:order) { create(:order_with_taxes, zone: zone) }
|
||||
|
||||
subject { TaxRateFinder.new }
|
||||
|
||||
it "finds the tax rate of a shipping fee" do
|
||||
rates = TaxRateFinder.new.tax_rates(
|
||||
tax_rate,
|
||||
shipment,
|
||||
amount,
|
||||
included_tax
|
||||
)
|
||||
rates = subject.tax_rates(tax_rate, shipment)
|
||||
expect(rates).to eq [tax_rate]
|
||||
end
|
||||
|
||||
it "finds a close match" do
|
||||
it "deals with soft-deleted tax rates" do
|
||||
tax_rate.destroy
|
||||
close_tax_rate = create_rate(tax_rate.amount + 0.05)
|
||||
# other tax rates, not as close to the real one
|
||||
create_rate(tax_rate.amount + 0.06)
|
||||
create_rate(tax_rate.amount - 0.06)
|
||||
|
||||
rates = TaxRateFinder.new.tax_rates(
|
||||
nil,
|
||||
shipment,
|
||||
amount,
|
||||
included_tax
|
||||
)
|
||||
|
||||
expect(rates).to eq [close_tax_rate]
|
||||
rates = subject.tax_rates(tax_rate, shipment)
|
||||
expect(rates).to eq [tax_rate]
|
||||
end
|
||||
|
||||
it "finds the tax rate of an enterprise fee" do
|
||||
rates = TaxRateFinder.new.tax_rates(
|
||||
enterprise_fee,
|
||||
order,
|
||||
amount,
|
||||
included_tax
|
||||
)
|
||||
rates = subject.tax_rates(enterprise_fee, order)
|
||||
expect(rates).to eq [tax_rate]
|
||||
end
|
||||
|
||||
# There is a bug that leaves orphan adjustments on an order after
|
||||
# associated line items have been removed.
|
||||
# https://github.com/openfoodfoundation/openfoodnetwork/issues/3127
|
||||
it "deals with a missing line item" do
|
||||
rates = TaxRateFinder.new.tax_rates(
|
||||
enterprise_fee,
|
||||
nil,
|
||||
amount,
|
||||
included_tax
|
||||
)
|
||||
it "deals with a soft-deleted line item" do
|
||||
line_item.destroy
|
||||
rates = subject.tax_rates(enterprise_fee, line_item)
|
||||
expect(rates).to eq [tax_rate]
|
||||
end
|
||||
|
||||
def create_rate(amount)
|
||||
create(
|
||||
:tax_rate,
|
||||
amount: amount,
|
||||
calculator: Calculator::DefaultTax.new,
|
||||
zone: zone
|
||||
)
|
||||
context "when the given adjustment has no associated tax" do
|
||||
let(:adjustment) { create(:adjustment) }
|
||||
|
||||
it "returns an empty array" do
|
||||
expect(subject.tax_rates(adjustment.originator, adjustment.adjustable)).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user