Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
<%= render component("ui/forms/field").text_field(f, :name) %>
<%= render component("ui/forms/field").text_field(f, :tax_code) %>
<%= render component("ui/forms/field").text_field(f, :description) %>
<% if Spree::Backend::Config.show_reverse_charge_fields %>
<%= render component("ui/forms/field").select(
f,
:tax_reverse_charge_mode,
Spree::TaxCategory.tax_reverse_charge_modes.keys.map { |key| [I18n.t("spree.tax_reverse_charge_modes.#{key}"), key] }
) %>
<% end %>
<label class="flex gap-2 items-center">
<%= render component("ui/forms/checkbox").new(
name: "#{f.object_name}[is_default]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
end

def columns
[
columns = [
{
header: :name,
data: ->(tax_category) do
Expand Down Expand Up @@ -82,5 +82,18 @@
},
},
]

if Spree::Backend::Config.show_reverse_charge_fields
columns << {

Check warning on line 87 in admin/app/components/solidus_admin/tax_categories/index/component.rb

View check run for this annotation

Codecov / codecov/patch

admin/app/components/solidus_admin/tax_categories/index/component.rb#L87

Added line #L87 was not covered by tests
header: :tax_reverse_charge_mode,
data: ->(tax_category) do
link_to_if tax_category.tax_reverse_charge_mode, I18n.t("spree.tax_reverse_charge_modes.#{tax_category.tax_reverse_charge_mode}"), edit_path(tax_category),

Check warning on line 90 in admin/app/components/solidus_admin/tax_categories/index/component.rb

View check run for this annotation

Codecov / codecov/patch

admin/app/components/solidus_admin/tax_categories/index/component.rb#L90

Added line #L90 was not covered by tests
data: { turbo_frame: :resource_modal },
class: 'body-link'
end
}
end

columns
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
<%= render component("ui/forms/field").text_field(f, :name) %>
<%= render component("ui/forms/field").text_field(f, :tax_code) %>
<%= render component("ui/forms/field").text_field(f, :description) %>
<% if Spree::Backend::Config.show_reverse_charge_fields %>
<%= render component("ui/forms/field").select(
f,
:tax_reverse_charge_mode,
Spree::TaxCategory.tax_reverse_charge_modes.keys.map { |key| [I18n.t("spree.tax_reverse_charge_modes.#{key}"), key] }
) %>
<% end %>
<label class="flex gap-2 items-center">
<%= render component("ui/forms/checkbox").new(
name: "#{f.object_name}[is_default]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class TaxCategoriesController < SolidusAdmin::ResourcesController
def resource_class = Spree::TaxCategory

def permitted_resource_params
params.require(:tax_category).permit(:name, :description, :is_default, :tax_code)
params.require(:tax_category).permit(:name, :description, :is_default, :tax_code, :tax_reverse_charge_mode)
end
end
end
12 changes: 12 additions & 0 deletions backend/app/views/spree/admin/tax_categories/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,16 @@
<%= f.text_area :description, class: 'fullwidth' %>
<% end %>
</div>

<% if Spree::Backend::Config.show_reverse_charge_fields %>
<div class="col-4">
<%= f.field_container :tax_reverse_charge_mode do %>
<%= f.label :tax_reverse_charge_mode %>
<%= f.select :tax_reverse_charge_mode,
Spree::TaxCategory.tax_reverse_charge_modes.keys.map { |key| [I18n.t("spree.tax_reverse_charge_modes.#{key}"), key] },
{ include_blank: false },
{ class: 'custom-select fullwidth' } %>
<% end %>
</div>
<% end %>
</div>
13 changes: 11 additions & 2 deletions backend/app/views/spree/admin/tax_categories/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@
<colgroup>
<col style="width: 20%">
<col style="width: 10%">
<% if Spree::Backend::Config.show_reverse_charge_fields %>
<col style="width: 10%">
<% end %>
<col style="width: 40%">
<col style="width: 15%">
<col style="width: 15%">
<col style="width: 10%">
<col style="width: 10%">
</colgroup>
<thead>
<tr data-hook="tax_header">
<th><%= Spree::TaxCategory.human_attribute_name(:name) %></th>
<th><%= Spree::TaxCategory.human_attribute_name(:tax_code) %></th>
<% if Spree::Backend::Config.show_reverse_charge_fields %>
<th><%= Spree::TaxCategory.human_attribute_name(:tax_reverse_charge_mode) %></th>
<% end %>
<th><%= Spree::TaxCategory.human_attribute_name(:description) %></th>
<th><%= Spree::TaxCategory.human_attribute_name(:is_default) %></th>
<th class="actions"></th>
Expand All @@ -41,6 +47,9 @@
<tr id="<%= spree_dom_id tax_category %>" data-hook="tax_row">
<td><%= tax_category.name %></td>
<td><%= tax_category.tax_code %></td>
<% if Spree::Backend::Config.show_reverse_charge_fields %>
<td><%= I18n.t("spree.tax_reverse_charge_modes.#{tax_category.tax_reverse_charge_mode}") %></td>
<% end %>
<td><%= tax_category.description %></td>
<td><%= tax_category.is_default? ? t('spree.say_yes') : t('spree.say_no') %></td>
<td class="actions">
Expand Down
24 changes: 24 additions & 0 deletions core/app/models/spree/tax/tax_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,33 @@ def rates_for_item(item)
tax_category_id = item.try(:variant_tax_category_id) || item.tax_category_id

@rates_for_item.select do |rate|
tax_category = rate.tax_categories.find { |tax_cat| tax_cat.id == tax_category_id }
next unless tax_category && tax_applicable?(tax_category, item.order.tax_address)

rate.active? && rate.tax_categories.map(&:id).include?(tax_category_id)
end
end

# Determine if tax is applicable based on tax category and address
#
# @param [Spree::TaxCategory] tax_category
# the tax category to check tax_reverse_charge_mode
# @param [Spree::Address] address
# the address to check reverse_charge_status
# @return [Boolean] true if tax is applicable, false otherwise
def tax_applicable?(tax_category, address)
case tax_category.tax_reverse_charge_mode
# Strict mode: Tax applies only if the address is NOT enabled (reverse charge)
when 'strict'
!address.reverse_charge_status_enabled?
# Loose mode: Tax applies only if the address is explicitly disabled
when 'loose'
address.reverse_charge_status_disabled?
# Tax always applies
when 'disabled'
true
end
end
end
end
end
6 changes: 6 additions & 0 deletions core/app/models/spree/tax_category.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ class TaxCategory < Spree::Base
self.tax_rate_tax_categories = []
end

enum :tax_reverse_charge_mode, {
disabled: 0,
loose: 1,
strict: 2
}, prefix: true

validates :name, presence: true
validates_uniqueness_of :name, case_sensitive: true, unless: :deleted_at

Expand Down
5 changes: 5 additions & 0 deletions core/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ en:
is_default: Default
name: Name
tax_code: Tax Code
tax_reverse_charge_mode: Tax Reverse Charge Mode
spree/tax_rate:
amount: Rate
expires_at: Expiration Date
Expand Down Expand Up @@ -2273,6 +2274,10 @@ en:
tax_rate_amount_explanation: When using the "Default Tax" calculator, the amount is treated as a decimal amount to aid in calculations. (i.e. If the tax rate is 5% then enter 0.05) If using the "Flat Fee" calculator, the amount is used for the static fee.
tax_rate_level: Tax Rate Level
tax_rates: Tax Rates
tax_reverse_charge_modes:
disabled: Disabled
loose: Loose
strict: Strict
taxon: Taxon
taxon_attachment_removal_error: There was an error removing the attachment
taxon_edit: Edit Taxon
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

class AddTaxReverseChargeModeToSpreeTaxCategories < ActiveRecord::Migration[7.0]
def change
add_column :spree_tax_categories, :tax_reverse_charge_mode, :integer, default: 0, null: false,
comment: "Enum values: 0 = disabled, 1 = loose, 2 = strict"
end
end
101 changes: 101 additions & 0 deletions core/spec/models/spree/tax/tax_helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,106 @@ def valid_rates(item)
end
end
end

context "when tax_reverse_charge_mode is strict and address is enabled" do
let(:tax_category) { create(:tax_category, tax_reverse_charge_mode: 'strict') }

before do
allow(tax_address).to receive(:reverse_charge_status_enabled?).and_return(true)
end

it "returns an empty array" do
expect(subject).to be_empty
end
end

context "when tax_reverse_charge_mode is strict and address is not enabled" do
let(:tax_category) { create(:tax_category, tax_reverse_charge_mode: 'strict') }

before do
allow(tax_address).to receive(:reverse_charge_status_enabled?).and_return(false)
end

it "returns the applicable tax rate" do
expect(subject).to contain_exactly(tax_rate)
end
end

context "when tax_reverse_charge_mode is loose and address is disabled" do
let(:tax_category) { create(:tax_category, tax_reverse_charge_mode: 'loose') }

before do
allow(tax_address).to receive(:reverse_charge_status_disabled?).and_return(true)
end

it "returns the applicable tax rate" do
expect(subject).to contain_exactly(tax_rate)
end
end

context "when tax_reverse_charge_mode is loose and address is not disabled" do
let(:tax_category) { create(:tax_category, tax_reverse_charge_mode: 'loose') }

before do
allow(tax_address).to receive(:reverse_charge_status_disabled?).and_return(false)
end

it "returns an empty array" do
expect(subject).to be_empty
end
end

context "when tax_reverse_charge_mode is disabled" do
let(:tax_category) { create(:tax_category, tax_reverse_charge_mode: 'disabled') }

it "returns the applicable tax rate" do
expect(subject).to contain_exactly(tax_rate)
end
end
end

describe '#tax_applicable?' do
let(:tax_category) { create(:tax_category, tax_reverse_charge_mode: tax_reverse_charge_mode) }
let(:address) { create(:address) }

subject { DummyClass.new.send(:tax_applicable?, tax_category, address) }

context 'when tax_reverse_charge_mode is strict' do
let(:tax_reverse_charge_mode) { 'strict' }

context 'when address is enabled' do
before { allow(address).to receive(:reverse_charge_status_enabled?).and_return(true) }

it { is_expected.to be_falsey }
end

context 'when address is not enabled' do
before { allow(address).to receive(:reverse_charge_status_enabled?).and_return(false) }

it { is_expected.to be_truthy }
end
end

context 'when tax_reverse_charge_mode is loose' do
let(:tax_reverse_charge_mode) { 'loose' }

context 'when address is disabled' do
before { allow(address).to receive(:reverse_charge_status_disabled?).and_return(true) }

it { is_expected.to be_truthy }
end

context 'when address is not disabled' do
before { allow(address).to receive(:reverse_charge_status_disabled?).and_return(false) }

it { is_expected.to be_falsey }
end
end

context 'when tax_reverse_charge_mode is disabled' do
let(:tax_reverse_charge_mode) { 'disabled' }

it { is_expected.to be_truthy }
end
end
end
30 changes: 30 additions & 0 deletions core/spec/models/spree/tax_category_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,34 @@
end
end
end

describe 'enum tax_reverse_charge_mode' do
it 'defines the expected enum values' do
expect(Spree::TaxCategory.tax_reverse_charge_modes).to eq({
'disabled' => 0,
'loose' => 1,
'strict' => 2
})
end

it 'allows valid values' do
tax_category = build(:tax_category)
# Updates the tax_reverse_charge_mode to "strict"
expect(tax_category).to be_valid
tax_category.tax_reverse_charge_mode_strict!

# Updates the tax_reverse_charge_mode to "loose"
expect(tax_category).to be_valid
tax_category.tax_reverse_charge_mode_loose!
expect(tax_category).to be_valid

# Updates the tax_reverse_charge_mode to "disabled"
tax_category.tax_reverse_charge_mode_disabled!
expect(tax_category).to be_valid
end

it 'raises an error for invalid values' do
expect { Spree::TaxCategory.new(tax_reverse_charge_mode: :invalid_status) }.to raise_error(ArgumentError)
end
end
end
Loading