Merge pull request #6565 from Matt-Yorkley/adjustments-inclusive

[Adjustments] Improve inclusive/additional tax recording
This commit is contained in:
Maikel
2021-02-08 09:38:31 +11:00
committed by GitHub
9 changed files with 113 additions and 15 deletions

View File

@@ -15,9 +15,7 @@ module Spree
order = current_order || raise(ActiveRecord::RecordNotFound)
items = order.line_items.map(&method(:line_item))
tax_adjustments = order.adjustments.tax
# TODO: Remove in Spree 2.2
tax_adjustments = tax_adjustments.additional if tax_adjustments.respond_to?(:additional)
tax_adjustments = order.adjustments.tax.additional
shipping_adjustments = order.adjustments.shipping
order.adjustments.eligible.each do |adjustment|
@@ -175,12 +173,8 @@ module Spree
def payment_details(items)
item_sum = items.sum { |i| i[:Quantity] * i[:Amount][:value] }
# Would use tax_total here, but it can include "included" taxes as well.
# For instance, tax_total would include the 10% GST in Australian stores.
# A quick sum will get us around that little problem.
# TODO: Remove additional check in 2.2
tax_adjustments = current_order.adjustments.tax
tax_adjustments = tax_adjustments.additional if tax_adjustments.respond_to?(:additional)
tax_adjustments = current_order.adjustments.tax.additional
tax_adjustments_total = tax_adjustments.sum(:amount)
if item_sum.zero?

View File

@@ -64,12 +64,14 @@ module Spree
end
end
scope :tax, -> { where(originator_type: 'Spree::TaxRate', adjustable_type: 'Spree::Order') }
scope :tax, -> { where(originator_type: 'Spree::TaxRate') }
scope :price, -> { where(adjustable_type: 'Spree::LineItem') }
scope :optional, -> { where(mandatory: false) }
scope :charge, -> { where('amount >= 0') }
scope :credit, -> { where('amount < 0') }
scope :return_authorization, -> { where(source_type: "Spree::ReturnAuthorization") }
scope :inclusive, -> { where(included: true) }
scope :additional, -> { where(included: false) }
scope :enterprise_fee, -> { where(originator_type: 'EnterpriseFee') }
scope :admin, -> { where(source_type: nil, originator_type: nil) }

View File

@@ -47,6 +47,7 @@ module Spree
dependent: :destroy
has_many :line_item_adjustments, through: :line_items, source: :adjustments
has_many :all_adjustments, class_name: 'Spree::Adjustment', dependent: :destroy
has_many :shipments, dependent: :destroy do
def states

View File

@@ -61,7 +61,7 @@ module Spree
def adjust(order)
label = create_label
if included_in_price
if Zone.default_tax.contains? order.tax_zone
if default_zone_or_zone_match? order
order.line_items.each { |line_item| create_adjustment(label, line_item, line_item) }
else
amount = -1 * calculator.compute(order)
@@ -89,6 +89,10 @@ module Spree
end
end
def default_zone_or_zone_match?(order)
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.
@@ -114,6 +118,8 @@ module Spree
label = ""
label << (name.presence || tax_category.name) + " "
label << (show_rate_in_label? ? "#{amount * 100}%" : "")
label << " (#{I18n.t('models.tax_rate.included_in_price')})" if included_in_price?
label
end
def with_tax_included_in_price

View File

@@ -140,6 +140,8 @@ en:
models:
order_cycle:
cloned_order_cycle_name: "COPY OF %{order_cycle}"
tax_rate:
included_in_price: "Included in price"
validators:
date_time_string_validator:

View File

@@ -0,0 +1,22 @@
class AddIncludedToAdjustments < ActiveRecord::Migration
class Spree::TaxRate < ActiveRecord::Base; end
class Spree::Adjustment < ActiveRecord::Base
belongs_to :originator, polymorphic: true
end
def up
add_column :spree_adjustments, :included, :boolean, default: false
Spree::Adjustment.reset_column_information
inclusive_tax_rates = Spree::TaxRate.where(included_in_price: true)
# Set included boolean to true on all adjustments based on price-inclusive tax rates
Spree::Adjustment.where(originator_type: 'Spree::TaxRate', originator_id: inclusive_tax_rates).
update_all(included: true)
end
def down
remove_column :spree_adjustments, :included
end
end

View File

@@ -386,16 +386,17 @@ ActiveRecord::Schema.define(version: 20210203215049) do
t.string "label", limit: 255
t.string "source_type", limit: 255
t.integer "adjustable_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "mandatory"
t.integer "originator_id"
t.string "originator_type", limit: 255
t.boolean "eligible", default: true
t.string "adjustable_type", limit: 255
t.decimal "included_tax", precision: 10, scale: 2, default: 0.0, null: false
t.decimal "included_tax", precision: 10, scale: 2, default: 0.0, null: false
t.string "state", limit: 255
t.integer "order_id"
t.boolean "included", default: false
end
add_index "spree_adjustments", ["adjustable_id"], name: "index_adjustments_on_order_id", using: :btree

View File

@@ -43,7 +43,8 @@ module Spree
order: order_object_for(target),
label: label,
mandatory: mandatory,
state: state
state: state,
included: tax_included?(self, target)
)
end
@@ -78,6 +79,15 @@ module Spree
private
# Used for setting the #included boolean on tax adjustments. This will be removed in a
# later step, as the responsibility for creating all adjustments related to tax will be
# moved into the Spree::TaxRate class.
def tax_included?(originator, target)
originator.is_a?(Spree::TaxRate) &&
originator.included_in_price &&
originator.default_zone_or_zone_match?(order_object_for(target))
end
def order_object_for(target)
# Temporary method for adjustments transition.
if target.is_a? Spree::Order

View File

@@ -460,5 +460,65 @@ module Spree
context "extends LocalizedNumber" do
it_behaves_like "a model using the LocalizedNumber module", [:amount]
end
describe "inclusive and additional taxes" do
let!(:zone) { create(:zone_with_member) }
let!(:tax_category) { create(:tax_category, name: "Tax Test") }
let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true) }
let(:order) { create(:order, distributor: distributor) }
let(:included_in_price) { true }
let(:tax_rate) {
create(:tax_rate, included_in_price: included_in_price, zone: zone,
calculator: ::Calculator::FlatRate.new(preferred_amount: 0.1))
}
let(:product) { create(:product, tax_category: tax_category) }
let(:variant) { product.variants.first }
describe "tax adjustment creation" do
before do
tax_category.tax_rates << tax_rate
allow(order).to receive(:tax_zone) { zone }
order.line_items << create(:line_item, variant: variant, quantity: 5)
end
context "with included taxes" do
it "records the tax as included" do
expect(order.all_adjustments.tax.count).to eq 1
expect(order.all_adjustments.tax.first.included).to be true
end
end
context "with additional taxes" do
let(:included_in_price) { false }
it "records the tax as additional" do
expect(order.all_adjustments.tax.count).to eq 1
expect(order.all_adjustments.tax.first.included).to be false
end
end
end
describe "inclusive and additional scopes" do
let(:included) { true }
let(:adjustment) {
create(:adjustment, adjustable: order, source: order,
originator: tax_rate, included: included)
}
context "when tax is included in price" do
it "is returned by the #included scope" do
expect(Spree::Adjustment.inclusive).to eq [adjustment]
end
end
context "when tax is additional to the price" do
let(:included) { false }
it "is returned by the #additional scope" do
expect(Spree::Adjustment.additional).to eq [adjustment]
end
end
end
end
end
end