mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-20 00:37:26 +00:00
This method returns the same thing as the Spree::Order#line_items_adjustments scope, but in a slightly less useful format (an array instead of a relation). The method's name is also totally inaccurate, as currently the only adjustments that appear on line items are tax adjustments for inclusive tax rates, which by definition have no effect on the price whatsoever...
465 lines
18 KiB
Ruby
465 lines
18 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
module Spree
|
|
describe Adjustment do
|
|
let(:order) { build(:order) }
|
|
let(:adjustment) { Spree::Adjustment.create(label: "Adjustment", amount: 5) }
|
|
|
|
describe "scopes" do
|
|
let!(:arbitrary_adjustment) { create(:adjustment, source: nil, label: "Arbitrary") }
|
|
let!(:return_authorization_adjustment) { create(:adjustment, source: create(:return_authorization)) }
|
|
|
|
it "returns return_authorization adjustments" do
|
|
expect(Spree::Adjustment.return_authorization.to_a).to eq [return_authorization_adjustment]
|
|
end
|
|
end
|
|
|
|
context "#update!" do
|
|
context "when originator present" do
|
|
let(:originator) { double("originator", update_adjustment: nil) }
|
|
before do
|
|
allow(originator).to receive_messages update_amount: true
|
|
allow(adjustment).to receive_messages originator: originator, label: 'adjustment', amount: 0
|
|
end
|
|
|
|
it "should do nothing when closed" do
|
|
adjustment.close
|
|
expect(originator).not_to receive(:update_adjustment)
|
|
adjustment.update!
|
|
end
|
|
|
|
it "should do nothing when finalized" do
|
|
adjustment.finalize
|
|
expect(originator).not_to receive(:update_adjustment)
|
|
adjustment.update!
|
|
end
|
|
|
|
it "should set the eligibility" do
|
|
expect(adjustment).to receive(:set_eligibility)
|
|
adjustment.update!
|
|
end
|
|
|
|
it "should ask the originator to update_adjustment" do
|
|
expect(originator).to receive(:update_adjustment)
|
|
adjustment.update!
|
|
end
|
|
end
|
|
|
|
it "should do nothing when originator is nil" do
|
|
allow(adjustment).to receive_messages originator: nil
|
|
expect(adjustment).not_to receive(:amount=)
|
|
adjustment.update!
|
|
end
|
|
end
|
|
|
|
context "#eligible? after #set_eligibility" do
|
|
context "when amount is 0" do
|
|
before { adjustment.amount = 0 }
|
|
it "should be eligible if mandatory?" do
|
|
adjustment.mandatory = true
|
|
adjustment.set_eligibility
|
|
expect(adjustment).to be_eligible
|
|
end
|
|
|
|
it "should not be eligible unless mandatory?" do
|
|
adjustment.mandatory = false
|
|
adjustment.set_eligibility
|
|
expect(adjustment).to_not be_eligible
|
|
end
|
|
end
|
|
|
|
context "when amount is greater than 0" do
|
|
before { adjustment.amount = 25.00 }
|
|
|
|
it "should be eligible if mandatory?" do
|
|
adjustment.mandatory = true
|
|
adjustment.set_eligibility
|
|
expect(adjustment).to be_eligible
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#save" do
|
|
it "should call order#update!" do
|
|
adjustment = Spree::Adjustment.new(
|
|
adjustable: order,
|
|
amount: 10,
|
|
label: "Foo"
|
|
)
|
|
expect(order).to receive(:update!)
|
|
adjustment.save
|
|
end
|
|
end
|
|
|
|
context "adjustment state" do
|
|
let(:adjustment) { create(:adjustment, state: 'open') }
|
|
|
|
context "#immutable?" do
|
|
it "is true when adjustment state isn't open" do
|
|
adjustment.state = "closed"
|
|
expect(adjustment).to be_immutable
|
|
adjustment.state = "finalized"
|
|
expect(adjustment).to be_immutable
|
|
end
|
|
|
|
it "is false when adjustment state is open" do
|
|
adjustment.state = "open"
|
|
expect(adjustment).to_not be_immutable
|
|
end
|
|
end
|
|
|
|
context "#finalized?" do
|
|
it "is true when adjustment state is finalized" do
|
|
adjustment.state = "finalized"
|
|
expect(adjustment).to be_finalized
|
|
end
|
|
|
|
it "is false when adjustment state isn't finalized" do
|
|
adjustment.state = "closed"
|
|
expect(adjustment).to_not be_finalized
|
|
adjustment.state = "open"
|
|
expect(adjustment).to_not be_finalized
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#display_amount" do
|
|
before { adjustment.amount = 10.55 }
|
|
|
|
context "with display_currency set to true" do
|
|
before { Spree::Config[:display_currency] = true }
|
|
|
|
it "shows the currency" do
|
|
expect(adjustment.display_amount.to_s).to eq "$10.55 #{Spree::Config[:currency]}"
|
|
end
|
|
end
|
|
|
|
context "with display_currency set to false" do
|
|
before { Spree::Config[:display_currency] = false }
|
|
|
|
it "does not include the currency" do
|
|
expect(adjustment.display_amount.to_s).to eq "$10.55"
|
|
end
|
|
end
|
|
|
|
context "with currency set to JPY" do
|
|
context "when adjustable is set to an order" do
|
|
before do
|
|
allow(order).to receive(:currency) { 'JPY' }
|
|
adjustment.adjustable = order
|
|
end
|
|
|
|
it "displays in JPY" do
|
|
expect(adjustment.display_amount.to_s).to eq "¥11"
|
|
end
|
|
end
|
|
|
|
context "when adjustable is nil" do
|
|
it "displays in the default currency" do
|
|
expect(adjustment.display_amount.to_s).to eq "$10.55"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context '#currency' do
|
|
it 'returns the globally configured currency' do
|
|
expect(adjustment.currency).to eq Spree::Config[:currency]
|
|
end
|
|
end
|
|
|
|
it "has metadata" do
|
|
adjustment = create(:adjustment, metadata: create(:adjustment_metadata))
|
|
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) }
|
|
let!(:order) { create(:order, bill_address: create(:address)) }
|
|
let!(:line_item) { create(:line_item, order: order) }
|
|
let(:tax_rate) { create(:tax_rate, included_in_price: true, calculator: ::Calculator::FlatRate.new(preferred_amount: 0.1)) }
|
|
let(:adjustment) { line_item.adjustments(:reload).first }
|
|
|
|
before do
|
|
order.reload
|
|
tax_rate.adjust(order)
|
|
end
|
|
|
|
it "has 100% tax included" do
|
|
expect(adjustment.amount).to be > 0
|
|
expect(adjustment.included_tax).to eq(adjustment.amount)
|
|
end
|
|
|
|
it "does not crash when order data has been updated previously" do
|
|
order.line_item_adjustments.first.destroy
|
|
tax_rate.adjust(order)
|
|
end
|
|
end
|
|
|
|
describe "Shipment adjustments" do
|
|
let(:hub) { create(:distributor_enterprise, charges_sales_tax: true) }
|
|
let(:order) { create(:order, distributor: hub) }
|
|
let(:line_item) { create(:line_item, order: order) }
|
|
|
|
let(:shipping_method) { create(:shipping_method_with, :flat_rate) }
|
|
let(:shipment) { create(:shipment_with, :shipping_method, shipping_method: shipping_method, order: order) }
|
|
|
|
describe "the shipping charge" do
|
|
it "is the adjustment amount" do
|
|
order.shipments = [shipment]
|
|
expect(order.adjustments.first.amount).to eq(50)
|
|
end
|
|
end
|
|
|
|
describe "when tax on shipping is disabled" do
|
|
before do
|
|
allow(Config).to receive(:shipment_inc_vat).and_return(false)
|
|
end
|
|
|
|
it "records 0% tax on shipment adjustments" do
|
|
allow(Config).to receive(:shipping_tax_rate).and_return(0)
|
|
order.shipments = [shipment]
|
|
|
|
expect(order.adjustments.first.included_tax).to eq(0)
|
|
end
|
|
|
|
it "records 0% tax on shipments when a rate is set but shipment_inc_vat is false" do
|
|
allow(Config).to receive(:shipping_tax_rate).and_return(0.25)
|
|
order.shipments = [shipment]
|
|
|
|
expect(order.adjustments.first.included_tax).to eq(0)
|
|
end
|
|
end
|
|
|
|
describe "when tax on shipping is enabled" do
|
|
before do
|
|
allow(Config).to receive(:shipment_inc_vat).and_return(true)
|
|
end
|
|
|
|
it "takes the shipment adjustment tax included from the system setting" do
|
|
allow(Config).to receive(:shipping_tax_rate).and_return(0.25)
|
|
order.shipments = [shipment]
|
|
|
|
# Finding the tax included in an amount that's already inclusive of tax:
|
|
# total - ( total / (1 + rate) )
|
|
# 50 - ( 50 / (1 + 0.25) )
|
|
# = 10
|
|
expect(order.adjustments.first.included_tax).to eq(10.00)
|
|
end
|
|
|
|
it "records 0% tax on shipments when shipping_tax_rate is not set" do
|
|
allow(Config).to receive(:shipping_tax_rate).and_return(0)
|
|
order.shipments = [shipment]
|
|
|
|
expect(order.adjustments.first.included_tax).to eq(0)
|
|
end
|
|
|
|
it "records 0% tax on shipments when the distributor does not charge sales tax" do
|
|
order.distributor.update! charges_sales_tax: false
|
|
order.shipments = [shipment]
|
|
|
|
expect(order.adjustments.first.included_tax).to eq(0)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "EnterpriseFee adjustments" do
|
|
let(:zone) { create(:zone_with_member) }
|
|
let(:fee_tax_rate) { create(:tax_rate, included_in_price: true, calculator: ::Calculator::DefaultTax.new, zone: zone, amount: 0.1) }
|
|
let(:fee_tax_category) { create(:tax_category, tax_rates: [fee_tax_rate]) }
|
|
|
|
let(:coordinator) { create(:distributor_enterprise, charges_sales_tax: true) }
|
|
let(:variant) { create(:variant, product: create(:product, tax_category: nil)) }
|
|
let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: [enterprise_fee], distributors: [coordinator], variants: [variant]) }
|
|
let(:line_item) { create(:line_item, variant: variant) }
|
|
let(:order) { create(:order, line_items: [line_item], order_cycle: order_cycle, distributor: coordinator) }
|
|
let(:adjustment) { order.adjustments(:reload).enterprise_fee.first }
|
|
|
|
context "when enterprise fees have a fixed tax_category" do
|
|
before do
|
|
order.reload.update_distribution_charge!
|
|
end
|
|
|
|
context "when enterprise fees are taxed per-order" do
|
|
let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, tax_category: fee_tax_category, calculator: ::Calculator::FlatRate.new(preferred_amount: 50.0)) }
|
|
|
|
describe "when the tax rate includes the tax in the price" do
|
|
it "records the tax on the enterprise fee adjustments" do
|
|
# The fee is $50, tax is 10%, and the fee is inclusive of tax
|
|
# Therefore, the included tax should be 0.1/1.1 * 50 = $4.55
|
|
|
|
expect(adjustment.included_tax).to eq(4.55)
|
|
end
|
|
end
|
|
|
|
describe "when the tax rate does not include the tax in the price" do
|
|
before do
|
|
fee_tax_rate.update_attribute :included_in_price, false
|
|
order.reload.create_tax_charge! # Updating line_item or order has the same effect
|
|
order.update_distribution_charge!
|
|
end
|
|
|
|
it "records the tax on TaxRate adjustment on the order" do
|
|
expect(adjustment.included_tax).to eq(0)
|
|
expect(order.adjustments.tax.first.amount).to eq(5.0)
|
|
end
|
|
end
|
|
|
|
describe "when enterprise fees have no tax" do
|
|
before do
|
|
enterprise_fee.tax_category = nil
|
|
enterprise_fee.save!
|
|
order.update_distribution_charge!
|
|
end
|
|
|
|
it "records no tax as charged" do
|
|
expect(adjustment.included_tax).to eq(0)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when enterprise fees are taxed per-item" do
|
|
let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, tax_category: fee_tax_category, calculator: ::Calculator::PerItem.new(preferred_amount: 50.0)) }
|
|
|
|
describe "when the tax rate includes the tax in the price" do
|
|
it "records the tax on the enterprise fee adjustments" do
|
|
expect(adjustment.included_tax).to eq(4.55)
|
|
end
|
|
end
|
|
|
|
describe "when the tax rate does not include the tax in the price" do
|
|
before do
|
|
fee_tax_rate.update_attribute :included_in_price, false
|
|
order.reload.create_tax_charge! # Updating line_item or order has the same effect
|
|
order.update_distribution_charge!
|
|
end
|
|
|
|
it "records the tax on TaxRate adjustment on the order" do
|
|
expect(adjustment.included_tax).to eq(0)
|
|
expect(order.adjustments.tax.first.amount).to eq(5.0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when enterprise fees inherit their tax_category from the product they are applied to" do
|
|
let(:product_tax_rate) {
|
|
create(:tax_rate, included_in_price: true, calculator: ::Calculator::DefaultTax.new, zone: zone, amount: 0.2)
|
|
}
|
|
let(:product_tax_category) { create(:tax_category, tax_rates: [product_tax_rate]) }
|
|
|
|
before do
|
|
variant.product.update_attribute(:tax_category_id, product_tax_category.id)
|
|
|
|
order.create_tax_charge! # Updating line_item or order has the same effect
|
|
order.update_distribution_charge!
|
|
end
|
|
|
|
context "when enterprise fees are taxed per-order" do
|
|
let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, inherits_tax_category: true, calculator: ::Calculator::FlatRate.new(preferred_amount: 50.0)) }
|
|
|
|
describe "when the tax rate includes the tax in the price" do
|
|
it "records no tax on the enterprise fee adjustments" do
|
|
# EnterpriseFee tax category is nil and inheritance only applies to per item fees
|
|
# so tax on the enterprise_fee adjustment will be 0
|
|
# Tax on line item is: 0.2/1.2 x $10 = $1.67
|
|
expect(adjustment.included_tax).to eq(0.0)
|
|
expect(line_item.adjustments.first.included_tax).to eq(1.67)
|
|
end
|
|
end
|
|
|
|
describe "when the tax rate does not include the tax in the price" do
|
|
before do
|
|
product_tax_rate.update_attribute :included_in_price, false
|
|
order.reload.create_tax_charge! # Updating line_item or order has the same effect
|
|
order.reload.update_distribution_charge!
|
|
end
|
|
|
|
it "records the no tax on TaxRate adjustment on the order" do
|
|
# EnterpriseFee tax category is nil and inheritance only applies to per item fees
|
|
# so total tax on the order is only that which applies to the line_item itself
|
|
# ie. $10 x 0.2 = $2.0
|
|
expect(adjustment.included_tax).to eq(0)
|
|
expect(order.adjustments.tax.first.amount).to eq(2.0)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when enterprise fees are taxed per-item" do
|
|
let(:enterprise_fee) { create(:enterprise_fee, enterprise: coordinator, inherits_tax_category: true, calculator: ::Calculator::PerItem.new(preferred_amount: 50.0)) }
|
|
|
|
describe "when the tax rate includes the tax in the price" do
|
|
it "records the tax on the enterprise fee adjustments" do
|
|
# Applying product tax rate of 0.2 to enterprise fee of $50
|
|
# gives tax on fee of 0.2/1.2 x $50 = $8.33
|
|
# Tax on line item is: 0.2/1.2 x $10 = $1.67
|
|
expect(adjustment.included_tax).to eq(8.33)
|
|
expect(line_item.adjustments.first.included_tax).to eq(1.67)
|
|
end
|
|
end
|
|
|
|
describe "when the tax rate does not include the tax in the price" do
|
|
before do
|
|
product_tax_rate.update_attribute :included_in_price, false
|
|
order.reload.create_tax_charge! # Updating line_item or order has the same effect
|
|
order.update_distribution_charge!
|
|
end
|
|
|
|
it "records the tax on TaxRate adjustment on the order" do
|
|
# EnterpriseFee inherits tax_category from product so total tax on
|
|
# the order is that which applies to the line item itself, plus the
|
|
# same rate applied to the fee of $50. ie. ($10 + $50) x 0.2 = $12.0
|
|
expect(adjustment.included_tax).to eq(0)
|
|
expect(order.adjustments.tax.first.amount).to eq(12.0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "setting the included tax by tax rate" do
|
|
let(:adjustment) { Adjustment.new label: 'foo', amount: 50 }
|
|
|
|
it "sets it, rounding to two decimal places" do
|
|
adjustment.set_included_tax! 0.25
|
|
expect(adjustment.included_tax).to eq(10.00)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "extends LocalizedNumber" do
|
|
it_behaves_like "a model using the LocalizedNumber module", [:amount]
|
|
end
|
|
end
|
|
end
|