Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions core/app/models/spree/calculator/default_tax.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def compute_order(order)

line_items_total = matched_line_items.sum(&:discounted_amount)
if rate.included_in_price
round_to_two_places(line_items_total - ( line_items_total / (1 + rate.amount) ) )
order_tax_amount = round_to_two_places(line_items_total - ( line_items_total / (1 + rate.amount) ) )
refund_if_necessary(order_tax_amount, order.tax_zone)
else
round_to_two_places(line_items_total * rate.amount)
end
Expand Down Expand Up @@ -49,7 +50,22 @@ def round_to_two_places(amount)

def deduced_total_by_rate(item, rate)
unrounded_net_amount = item.discounted_amount / (1 + sum_of_included_tax_rates(item))
round_to_two_places(unrounded_net_amount * rate.amount)
refund_if_necessary(
round_to_two_places(unrounded_net_amount * rate.amount),
item.order.tax_zone
)
end

def refund_if_necessary(amount, order_tax_zone)
if default_zone_or_zone_match?(order_tax_zone)
amount
else
amount * -1
end
end

def default_zone_or_zone_match?(order_tax_zone)
Zone.default_tax.try!(:contains?, order_tax_zone) || rate.zone.contains?(order_tax_zone)
end
end
end
11 changes: 1 addition & 10 deletions core/app/models/spree/tax_rate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,11 @@ def adjust(order_tax_zone, item)

# This method is used by Adjustment#update to recalculate the cost.
def compute_amount(item)
if included_in_price && !default_zone_or_zone_match?(item.order.tax_zone)
# In this case, it's a refund.
calculator.compute(item) * - 1
else
calculator.compute(item)
end
calculator.compute(item)
end

private

def default_zone_or_zone_match?(order_tax_zone)
Zone.default_tax.try!(:contains?, order_tax_zone) || zone.contains?(order_tax_zone)
end

def adjustment_label(amount)
Spree.t translation_key(amount),
scope: "adjustment_labels.tax_rates",
Expand Down
187 changes: 140 additions & 47 deletions core/spec/models/spree/calculator/default_tax_spec.rb
Original file line number Diff line number Diff line change
@@ -1,51 +1,55 @@
require 'spec_helper'

describe Spree::Calculator::DefaultTax, type: :model do
let!(:address) { create(:address) }
let!(:zone) { create(:zone, name: "Country Zone", default_tax: true, countries: [address.country]) }
let!(:tax_category) { create(:tax_category) }
let(:address) { create(:address) }
let!(:zone) { create(:zone, name: "Country Zone", default_tax: true, countries: [tax_rate_country]) }
let(:tax_rate_country) { address.country }
let(:tax_category) { create(:tax_category) }
let!(:rate) { create(:tax_rate, tax_category: tax_category, amount: 0.05, included_in_price: included_in_price, zone: zone) }
let(:included_in_price) { false }
let!(:calculator) { Spree::Calculator::DefaultTax.new(calculable: rate ) }
let!(:order) { create(:order, ship_address: address) }
let!(:line_item) { create(:line_item, price: 10, quantity: 3, tax_category: tax_category) }
let!(:shipment) { create(:shipment, cost: 15) }
subject(:calculator) { Spree::Calculator::DefaultTax.new(calculable: rate ) }

context "#compute" do
context "when given an order" do
let!(:line_item_1) { line_item }
let!(:line_item_2) { create(:line_item, price: 10, quantity: 3, tax_category: tax_category) }
let(:order) do
create(
:order_with_line_items,
line_items_attributes: [
{ price: 10, quantity: 3, tax_category: tax_category }.merge(line_item_one_options),
{ price: 10, quantity: 3, tax_category: tax_category }.merge(line_item_two_options)
],
ship_address: address
)
end
let(:line_item_one_options) { {} }
let(:line_item_two_options) { {} }

before do
allow(order).to receive_messages line_items: [line_item_1, line_item_2]
context "when all items matches the rate's tax category" do
it "should be equal to the sum of the item totals * rate" do
expect(calculator.compute(order)).to eq(3)
end
end

context "when no line items match the tax category" do
before do
line_item_1.tax_category = nil
line_item_2.tax_category = nil
end
let(:other_tax_category) { create(:tax_category) }
let(:line_item_one_options) { { tax_category: other_tax_category } }
let(:line_item_two_options) { { tax_category: other_tax_category } }

it "should be 0" do
expect(calculator.compute(order)).to eq(0)
end
end

context "when one item matches the tax category" do
before do
line_item_1.tax_category = tax_category
line_item_2.tax_category = nil
end
let(:other_tax_category) { create(:tax_category) }
let(:line_item_two_options) { { tax_category: other_tax_category } }

it "should be equal to the item total * rate" do
expect(calculator.compute(order)).to eq(1.5)
end

context "correctly rounds to within two decimal places" do
before do
line_item_1.price = 10.333
line_item_1.quantity = 1
end
let(:line_item_one_options) { { price: 10.333, quantity: 1 } }

specify do
# Amount is 0.51665, which will be rounded to...
Expand All @@ -54,12 +58,6 @@
end
end

context "when more than one item matches the tax category" do
it "should be equal to the sum of the item totals * rate" do
expect(calculator.compute(order)).to eq(3)
end
end

context "when tax is included in price" do
let(:included_in_price) { true }

Expand All @@ -70,54 +68,149 @@
# 60 - 57.14 = $2.86
expect(calculator.compute(order).to_f).to eql 2.86
end

context "when the order's tax address is outside the default VAT zone" do
let(:order_zone) { create(:zone, countries: [address.country]) }
let(:default_vat_country) { create(:country, iso: "DE") }

before do
rate.zone.update(countries: [default_vat_country])
# The order has to be reloaded here because of tax zone caching.
order.reload
end

it 'creates a negative amount, indicating a VAT refund' do
expect(subject.compute(order)).to eq(-2.86)
end
end
end
end
end

shared_examples_for 'computing any item' do
let(:promo_total) { 0 }
let(:order) { build_stubbed(:order, ship_address: address) }

context "when tax is included in price" do
let(:included_in_price) { true }

context "when the variant matches the tax category" do
it "should be equal to the item's full price * rate" do
expect(calculator.compute(item)).to eql 1.43
end

context "when line item is discounted" do
before do
line_item.promo_total = -1
end
let(:promo_total) { -1 }

it "should be equal to the item's discounted total * rate" do
expect(calculator.compute(line_item)).to eql 1.38
expect(calculator.compute(item)).to eql 1.38
end
end

it "should be equal to the item's full price * rate" do
expect(calculator.compute(line_item)).to eql 1.43
context "when the order's tax address is outside the default VAT zone" do
let!(:order_zone) { create(:zone, countries: [address.country]) }
let(:default_vat_country) { create(:country, iso: "DE") }

before do
rate.zone.update(countries: [default_vat_country])
end

it 'creates a negative amount, indicating a VAT refund' do
expect(subject.compute(item)).to eq(-1.43)
end
end
end
end

context "when tax is not included in price" do
context "when the line item is discounted" do
before { line_item.promo_total = -1 }
let(:promo_total) { -1 }

it "should be equal to the item's pre-tax total * rate" do
expect(calculator.compute(line_item)).to eq(1.45)
expect(calculator.compute(item)).to eq(1.45)
end
end

context "when the variant matches the tax category" do
it "should be equal to the item pre-tax total * rate" do
expect(calculator.compute(line_item)).to eq(1.50)
expect(calculator.compute(item)).to eq(1.50)
end
end
end
end

context "when given a shipment" do
it "should be 5% of 15" do
expect(calculator.compute(shipment)).to eq(0.75)
end
describe 'when given a line item' do
let(:item) do
build_stubbed(
:line_item,
price: 10,
quantity: 3,
promo_total: promo_total,
order: order,
tax_category: tax_category
)
end

it "takes discounts into consideration" do
shipment.promo_total = -1
# 5% of 14
expect(calculator.compute(shipment)).to eq(0.7)
end
it_behaves_like 'computing any item'
end

describe 'when given a shipment' do
let(:shipping_method) do
build_stubbed(
:shipping_method,
tax_category: tax_category
)
end

let(:shipping_rate) do
build_stubbed(
:shipping_rate,
selected: true,
shipping_method: shipping_method
)
end

let(:item) do
build_stubbed(
:shipment,
cost: 30,
promo_total: promo_total,
order: order,
shipping_rates: [shipping_rate]
)
end

it_behaves_like 'computing any item'
end

describe 'when given a shipping rate' do
let(:shipping_method) do
build_stubbed(
:shipping_method,
tax_category: tax_category
)
end

let(:shipment) do
build_stubbed(
:shipment,
order: order
)
end

let(:item) do
# cost and discounted_amount for shipping rates are the same as they
# can not be discounted. for the sake of passing tests, the cost is
# adjusted here.
build_stubbed(
:shipping_rate,
cost: 30 + promo_total,
selected: true,
shipping_method: shipping_method,
shipment: shipment
)
end

it_behaves_like 'computing any item'
end
end