diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8c5acb4696..757cecd578 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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' diff --git a/app/controllers/spree/admin/adjustments_controller.rb b/app/controllers/spree/admin/adjustments_controller.rb index d86edaaac1..55ab5efefe 100644 --- a/app/controllers/spree/admin/adjustments_controller.rb +++ b/app/controllers/spree/admin/adjustments_controller.rb @@ -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 diff --git a/app/controllers/spree/admin/orders/customer_details_controller.rb b/app/controllers/spree/admin/orders/customer_details_controller.rb index 3644fbaf65..271b6b1b16 100644 --- a/app/controllers/spree/admin/orders/customer_details_controller.rb +++ b/app/controllers/spree/admin/orders/customer_details_controller.rb @@ -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, diff --git a/app/helpers/adjustments_helper.rb b/app/helpers/adjustments_helper.rb new file mode 100644 index 0000000000..7b7206ca2c --- /dev/null +++ b/app/helpers/adjustments_helper.rb @@ -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 diff --git a/app/models/spree/adjustment.rb b/app/models/spree/adjustment.rb index 20449e1213..8312229736 100644 --- a/app/models/spree/adjustment.rb +++ b/app/models/spree/adjustment.rb @@ -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 diff --git a/app/models/spree/tax_rate.rb b/app/models/spree/tax_rate.rb index 0bc308f971..0b2fc4f812 100644 --- a/app/models/spree/tax_rate.rb +++ b/app/models/spree/tax_rate.rb @@ -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 diff --git a/app/services/order_tax_adjustments_fetcher.rb b/app/services/order_tax_adjustments_fetcher.rb index e39522c2ab..83ea65dfc1 100644 --- a/app/services/order_tax_adjustments_fetcher.rb +++ b/app/services/order_tax_adjustments_fetcher.rb @@ -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 diff --git a/app/services/tax_rate_finder.rb b/app/services/tax_rate_finder.rb index 59d24dd052..1f90304efe 100644 --- a/app/services/tax_rate_finder.rb +++ b/app/services/tax_rate_finder.rb @@ -6,18 +6,12 @@ class TaxRateFinder # @return [Array] 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] - 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 diff --git a/app/views/spree/admin/adjustments/_adjustments_table.html.haml b/app/views/spree/admin/adjustments/_adjustments_table.html.haml index cbc7df005b..b24f650de7 100644 --- a/app/views/spree/admin/adjustments/_adjustments_table.html.haml +++ b/app/views/spree/admin/adjustments/_adjustments_table.html.haml @@ -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 diff --git a/app/views/spree/admin/adjustments/_edit_form.html.haml b/app/views/spree/admin/adjustments/_edit_form.html.haml index 58d2db990d..acfceb6df6 100644 --- a/app/views/spree/admin/adjustments/_edit_form.html.haml +++ b/app/views/spree/admin/adjustments/_edit_form.html.haml @@ -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 diff --git a/app/views/spree/admin/adjustments/_new_form.html.haml b/app/views/spree/admin/adjustments/_new_form.html.haml index a3e831aa8d..e049e0884a 100644 --- a/app/views/spree/admin/adjustments/_new_form.html.haml +++ b/app/views/spree/admin/adjustments/_new_form.html.haml @@ -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 diff --git a/app/views/spree/admin/orders/_invoice_table.html.haml b/app/views/spree/admin/orders/_invoice_table.html.haml index c96099edb6..878724659a 100644 --- a/app/views/spree/admin/orders/_invoice_table.html.haml +++ b/app/views/spree/admin/orders/_invoice_table.html.haml @@ -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 diff --git a/config/locales/en.yml b/config/locales/en.yml index d20f7e7c7e..0222570709 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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" diff --git a/db/migrate/20210617203927_migrate_admin_tax_amounts.rb b/db/migrate/20210617203927_migrate_admin_tax_amounts.rb new file mode 100644 index 0000000000..b6fb3da24f --- /dev/null +++ b/db/migrate/20210617203927_migrate_admin_tax_amounts.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 5d673a4ac0..dc58145fb3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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" diff --git a/engines/order_management/app/services/order_management/order/updater.rb b/engines/order_management/app/services/order_management/order/updater.rb index 684ca7b9ac..e17a9e3ad6 100644 --- a/engines/order_management/app/services/order_management/order/updater.rb +++ b/engines/order_management/app/services/order_management/order/updater.rb @@ -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 diff --git a/engines/order_management/spec/services/order_management/order/updater_spec.rb b/engines/order_management/spec/services/order_management/order/updater_spec.rb index 2a5c1f111d..142c042c81 100644 --- a/engines/order_management/spec/services/order_management/order/updater_spec.rb +++ b/engines/order_management/spec/services/order_management/order/updater_spec.rb @@ -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 diff --git a/lib/open_food_network/xero_invoices_report.rb b/lib/open_food_network/xero_invoices_report.rb index 2207ee2dc3..1a6e2b187c 100644 --- a/lib/open_food_network/xero_invoices_report.rb +++ b/lib/open_food_network/xero_invoices_report.rb @@ -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? diff --git a/spec/controllers/spree/admin/adjustments_controller_spec.rb b/spec/controllers/spree/admin/adjustments_controller_spec.rb index b8e1a2bb13..7c832fd835 100644 --- a/spec/controllers/spree/admin/adjustments_controller_spec.rb +++ b/spec/controllers/spree/admin/adjustments_controller_spec.rb @@ -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) diff --git a/spec/features/admin/adjustments_spec.rb b/spec/features/admin/adjustments_spec.rb index 887a4bd59e..2d5ca5ded7 100644 --- a/spec/features/admin/adjustments_spec.rb +++ b/spec/features/admin/adjustments_spec.rb @@ -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) diff --git a/spec/features/admin/order_spec.rb b/spec/features/admin/order_spec.rb index 8a9404f870..6a97135dea 100644 --- a/spec/features/admin/order_spec.rb +++ b/spec/features/admin/order_spec.rb @@ -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 diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb index a11c88e451..723754cc56 100644 --- a/spec/features/admin/reports_spec.rb +++ b/spec/features/admin/reports_spec.rb @@ -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, diff --git a/spec/migrations/migrate_admin_tax_amounts_spec.rb b/spec/migrations/migrate_admin_tax_amounts_spec.rb new file mode 100644 index 0000000000..af5df9c110 --- /dev/null +++ b/spec/migrations/migrate_admin_tax_amounts_spec.rb @@ -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 diff --git a/spec/models/spree/adjustment_spec.rb b/spec/models/spree/adjustment_spec.rb index 4ee57d8b77..7ae26c83c6 100644 --- a/spec/models/spree/adjustment_spec.rb +++ b/spec/models/spree/adjustment_spec.rb @@ -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) } diff --git a/spec/models/spree/tax_rate_spec.rb b/spec/models/spree/tax_rate_spec.rb index bf50676f29..34877fade4 100644 --- a/spec/models/spree/tax_rate_spec.rb +++ b/spec/models/spree/tax_rate_spec.rb @@ -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) } diff --git a/spec/services/order_tax_adjustments_fetcher_spec.rb b/spec/services/order_tax_adjustments_fetcher_spec.rb index 0f0d1055b5..b8e9bb7470 100644 --- a/spec/services/order_tax_adjustments_fetcher_spec.rb +++ b/spec/services/order_tax_adjustments_fetcher_spec.rb @@ -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 diff --git a/spec/services/tax_rate_finder_spec.rb b/spec/services/tax_rate_finder_spec.rb index b7bb07db73..8fcfdb738c 100644 --- a/spec/services/tax_rate_finder_spec.rb +++ b/spec/services/tax_rate_finder_spec.rb @@ -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