Class: Spree::TaxRate
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Spree::TaxRate
- Defined in:
- app/models/spree/tax_rate.rb
Class Method Summary collapse
-
.adjust(order, items) ⇒ Object
This method is best described by the documentation on #potentially_applicable?.
-
.default ⇒ Object
For Vat the default rate is the rate that is configured for the default category It is needed for every price calculation (as all customer facing prices include vat ) The function returns the actual amount, which may be 0 in case of wrong setup, but is never nil.
-
.match(order) ⇒ Object
Gets the array of TaxRates appropriate for the specified order.
-
.store_pre_tax_amount(item, rates) ⇒ Object
Pre-tax amounts must be stored so that we can calculate correct rate amounts in the future.
Instance Method Summary collapse
-
#adjust(order, item) ⇒ Object
Creates necessary tax adjustments for the order.
-
#compute_amount(item) ⇒ Object
This method is used by Adjustment#update to recalculate the cost.
- #default_zone_or_zone_match?(item) ⇒ Boolean
-
#potentially_applicable?(order) ⇒ Boolean
Tax rates can potentially be applicable to an order.
Methods included from Core::AdjustmentSource
Methods included from Core::CalculatedAdjustments
Class Method Details
.adjust(order, items) ⇒ Object
This method is best described by the documentation on #potentially_applicable?
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'app/models/spree/tax_rate.rb', line 75 def self.adjust(order, items) rates = self.match(order) tax_categories = rates.map(&:tax_category) relevant_items, non_relevant_items = items.partition { |item| tax_categories.include?(item.tax_category) } relevant_items.each do |item| item.adjustments.tax.delete_all relevant_rates = rates.select { |rate| rate.tax_category == item.tax_category } store_pre_tax_amount(item, relevant_rates) relevant_rates.each do |rate| rate.adjust(order, item) end end non_relevant_items.each do |item| if item.adjustments.tax.present? item.adjustments.tax.delete_all item.update_column(:pre_tax_amount, nil) Spree::ItemAdjustments.new(item).update end end end |
.default ⇒ Object
For Vat the default rate is the rate that is configured for the default category It is needed for every price calculation (as all customer facing prices include vat ) The function returns the actual amount, which may be 0 in case of wrong setup, but is never nil
99 100 101 102 103 104 105 106 107 |
# File 'app/models/spree/tax_rate.rb', line 99 def self.default category = TaxCategory.includes(:tax_rates).where(is_default: true).first return 0 unless category address ||= Address.new(country_id: Spree::Config[:default_country_id]) rate = category.tax_rates.detect { |rate| rate.zone.include? address }.try(:amount) rate || 0 end |
.match(order) ⇒ Object
Gets the array of TaxRates appropriate for the specified order
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'app/models/spree/tax_rate.rb', line 30 def self.match(order) order_zone = order.tax_zone return [] unless order_zone rates = includes(zone: { zone_members: :zoneable }).load.select do |rate| # Why "potentially"? # Go see the documentation for that method. rate.potentially_applicable?(order) end # Imagine with me this scenario: # You are living in Spain and you have a store which ships to France. # Spain is therefore your default tax rate. # When you ship to Spain, you want the Spanish rate to apply. # When you ship to France, you want the French rate to apply. # # Normally, Spree would notice that you have two potentially applicable # tax rates for one particular item. # When you ship to Spain, only the Spanish one will apply. # When you ship to France, you'll see a Spanish refund AND a French tax. # This little bit of code at the end stops the Spanish refund from appearing. # # For further discussion, see #4397 and #4327. rates.delete_if do |rate| rate.included_in_price? && (rates - [rate]).map(&:tax_category).include?(rate.tax_category) end end |
.store_pre_tax_amount(item, rates) ⇒ Object
Pre-tax amounts must be stored so that we can calculate correct rate amounts in the future. For example: github.com/spree/spree/issues/4318#issuecomment-34723428
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'app/models/spree/tax_rate.rb', line 61 def self.store_pre_tax_amount(item, rates) if rates.any? { |r| r.included_in_price } case item when Spree::LineItem item_amount = item.discounted_amount when Spree::Shipment item_amount = item.discounted_cost end pre_tax_amount = item_amount / (1 + rates.map(&:amount).sum) item.update_column(:pre_tax_amount, pre_tax_amount) end end |
Instance Method Details
#adjust(order, item) ⇒ Object
Creates necessary tax adjustments for the order.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'app/models/spree/tax_rate.rb', line 160 def adjust(order, item) amount = compute_amount(item) return if amount == 0 included = included_in_price && default_zone_or_zone_match?(item) if amount < 0 label = Spree.t(:refund) + ' ' + create_label end self.adjustments.create!({ :adjustable => item, :amount => amount, :order => order, :label => label || create_label, :included => included }) end |
#compute_amount(item) ⇒ Object
This method is used by Adjustment#update to recalculate the cost.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'app/models/spree/tax_rate.rb', line 180 def compute_amount(item) if included_in_price if default_zone_or_zone_match?(item) calculator.compute(item) else # In this case, it's a refund. calculator.compute(item) * - 1 end else calculator.compute(item) end end |
#default_zone_or_zone_match?(item) ⇒ Boolean
193 194 195 196 |
# File 'app/models/spree/tax_rate.rb', line 193 def default_zone_or_zone_match?(item) Zone.default_tax.contains?(item.order.tax_zone) || item.order.tax_zone == self.zone end |
#potentially_applicable?(order) ⇒ Boolean
Tax rates can potentially be applicable to an order. We do not know if they are/aren’t until we attempt to apply these rates to the items contained within the Order itself. For instance, if a rate passes the criteria outlined in this method, but then has a tax category that doesn’t match against any of the line items inside of the order, then that tax rate will not be applicable to anything. For instance:
Zones:
- Spain (default tax zone)
- France
Tax rates: (note: amounts below do not actually reflect real VAT rates)
21% inclusive - "Clothing" - Spain
18% inclusive - "Clothing" - France
10% inclusive - "Food" - Spain
8% inclusive - "Food" - France
5% inclusive - "Hotels" - Spain
2% inclusive - "Hotels" - France
Order has:
Line Item #1 - Tax Category: Clothing
Line Item #2 - Tax Category: Food
Tax rates that should be selected:
21% inclusive - "Clothing" - Spain
10% inclusive - "Food" - Spain
If the order’s address changes to one in France, then the tax will be recalculated:
18% inclusive - "Clothing" - France
8% inclusive - "Food" - France
Note here that the “Hotels” tax rates will not be used at all. This is because there are no items which have the tax category of “Hotels”.
Under no circumstances should negative adjustments be applied for the Spanish tax rates.
Those rates should never come into play at all and only the French rates should apply.
150 151 152 153 154 155 156 157 |
# File 'app/models/spree/tax_rate.rb', line 150 def potentially_applicable?(order) # If the rate's zone matches the order's tax zone, then it's applicable. self.zone == order.tax_zone || # If the rate's zone *contains* the order's tax zone, then it's applicable. self.zone.contains?(order.tax_zone) || # 1) The rate's zone is the default zone, then it's always applicable. (self.included_in_price? && self.zone.default_tax) end |