Class: TaxRate
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- TaxRate
- Includes:
- Models::Auditable
- Defined in:
- app/models/tax_rate.rb
Overview
== Schema Information
Table name: tax_rates
Database name: primary
id :integer not null, primary key
country_iso :string not null
description :string(255)
effective_date :date not null
expiration_date :date
goods :decimal(6, 4)
services :decimal(6, 4)
shipping :decimal(6, 4)
short_name :string(255)
state_code :string
tax_type :string(3) not null
use_food :decimal(6, 4)
use_merch :decimal(6, 4)
sales_tax_credit_account_id :integer
sales_tax_payable_account_id :integer
use_tax_account_id :integer
Constant Summary
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#effective_date ⇒ Object
readonly
Validates goods, services, shipping, effective date, tax type.
-
#goods ⇒ Object
readonly
Validates goods, services, shipping.
-
#services ⇒ Object
readonly
Validates goods, services, shipping.
-
#shipping ⇒ Object
readonly
Validates goods, services, shipping.
-
#state_code ⇒ Object
readonly
validates_presence_of :sales_tax_payable_account_id Per Venus request on July 2023.
-
#tax_type ⇒ Object
readonly
Validates goods, services, shipping, effective date, tax type.
-
#use_food ⇒ Object
readonly
Validates use merch, use food.
-
#use_merch ⇒ Object
readonly
Validates use merch, use food.
-
#use_tax_account_id ⇒ Object
readonly
Validates use tax account id.
Belongs to collapse
-
#country ⇒ Country?
The country this record belongs to.
-
#sales_tax_credit_account ⇒ LedgerDetailAccount?
The sales tax credit account this record belongs to.
-
#sales_tax_payable_account ⇒ LedgerDetailAccount?
The sales tax payable account this record belongs to.
-
#state ⇒ State?
The state this record belongs to.
-
#use_tax_account ⇒ LedgerDetailAccount?
The use tax account this record belongs to.
Methods included from Models::Auditable
Has many collapse
-
#resource_tax_rates ⇒ ActiveRecord::Relation<ResourceTaxRate>
The associated resource tax rates.
-
#voucher_items ⇒ ActiveRecord::Relation<VoucherItem>
The associated voucher items.
Class Method Summary collapse
-
.description_for(tax_type, destination_country: nil) ⇒ String
Human description for
tax_type. -
.effective_now ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are effective now.
-
.effective_on ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are effective on.
-
.ransackable_scopes(_auth_object = nil) ⇒ Array<Symbol>
Ransack-allowlisted scopes.
-
.rates_for_voucher_entry ⇒ Array<Hash>
Flatten every effective-today TaxRate into one row per
(rate_type, jurisdiction)for the voucher / AP-bill entry form's tax dropdown. -
.sales_tax ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are sales tax.
-
.sales_tax_credit ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are sales tax credit.
-
.tax_types_for_select ⇒ Array<String>
Distinct
tax_typecodes fromTAXABLE_STATESplus"vat", for the tax-type filter dropdown. -
.taxjar_summary_rates(country_code: nil) ⇒ Object
Returns an array of tax rate for a specific country, this is designed for single tax rate regardless of origin/destination, such as in canada { :country_code => "CA", :country => "Canada", :region_code => "NB", :region => "New Brunswick", :minimum_rate => { :label => "GST", :rate => 0.05 }, :average_rate => { :label => "HST", :rate => 0.15 } }.
-
.use_internal_rates ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are use internal rates.
-
.use_tax ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are use tax.
Instance Method Summary collapse
-
#active? ⇒ Boolean
Whether the rate is currently effective (today between
effective_dateandexpiration_date). -
#expiration_date_after_effective_date ⇒ void
protected
Validation: an
expiration_date, if present, can't precedeeffective_date. -
#has_overlapping_tax_rates ⇒ void
protected
Validation: prevent two rates from covering the same
(country, state, tax_type)over an overlapping date window. -
#tax_explanation ⇒ String
"U"for use-tax rates,"V"for VAT / sales-tax rates — the abbreviation shown next to the rate in the voucher form. -
#tax_type_available_in_state ⇒ void
protected
Validation: the
il(Illinois) tax type is only valid whenstate_code == 'IL'.
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#effective_date ⇒ Object (readonly)
Validates goods, services, shipping, effective date, tax type.
Validations:
78 |
# File 'app/models/tax_rate.rb', line 78 validates :goods, :services, :shipping, :effective_date, :tax_type, presence: true |
#goods ⇒ Object (readonly)
Validates goods, services, shipping.
Validations:
- Numericality ({ greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0 })
- Presence
74 |
# File 'app/models/tax_rate.rb', line 74 validates :goods, :services, :shipping, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0 } |
#services ⇒ Object (readonly)
Validates goods, services, shipping.
Validations:
- Numericality ({ greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0 })
- Presence
74 |
# File 'app/models/tax_rate.rb', line 74 validates :goods, :services, :shipping, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0 } |
#shipping ⇒ Object (readonly)
Validates goods, services, shipping.
Validations:
- Numericality ({ greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0 })
- Presence
74 |
# File 'app/models/tax_rate.rb', line 74 validates :goods, :services, :shipping, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0 } |
#state_code ⇒ Object (readonly)
validates_presence_of :sales_tax_payable_account_id Per Venus request on July 2023
Validations:
- Presence ({ if: proc { |tr| tr.country_iso.in?(%(US CA)) } })
82 |
# File 'app/models/tax_rate.rb', line 82 validates :state_code, presence: { if: proc { |tr| tr.country_iso.in?(%(US CA)) } } |
#tax_type ⇒ Object (readonly)
Validates goods, services, shipping, effective date, tax type.
Validations:
78 |
# File 'app/models/tax_rate.rb', line 78 validates :goods, :services, :shipping, :effective_date, :tax_type, presence: true |
#use_food ⇒ Object (readonly)
Validates use merch, use food.
Validations:
- Numericality ({ greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0, allow_nil: true })
76 |
# File 'app/models/tax_rate.rb', line 76 validates :use_merch, :use_food, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0, allow_nil: true } |
#use_merch ⇒ Object (readonly)
Validates use merch, use food.
Validations:
- Numericality ({ greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0, allow_nil: true })
76 |
# File 'app/models/tax_rate.rb', line 76 validates :use_merch, :use_food, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0, allow_nil: true } |
#use_tax_account_id ⇒ Object (readonly)
Validates use tax account id.
Validations:
- Presence ({ if: proc { |tr| tr.use_merch.present? or tr.use_food.present? } })
80 |
# File 'app/models/tax_rate.rb', line 80 validates :use_tax_account_id, presence: { if: proc { |tr| tr.use_merch.present? or tr.use_food.present? } } |
Class Method Details
.description_for(tax_type, destination_country: nil) ⇒ String
Human description for tax_type. Falls back to "VAT <country>" for EU rates and "Sales Tax" when no row in
TAXABLE_STATES matches.
156 157 158 159 160 161 162 163 |
# File 'app/models/tax_rate.rb', line 156 def self.description_for(tax_type, destination_country: nil) d = TAXABLE_STATES.find { |_k, v| v[:tax_type] == tax_type }.try(:[], 1).try(:[], :description) if d.nil? && tax_type == 'vat' d = +"VAT" d << " #{destination_country.name}" if destination_country end d || 'Sales Tax' end |
.effective_now ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are effective now. Active Record Scope
67 |
# File 'app/models/tax_rate.rb', line 67 scope :effective_now, -> { effective_on(Date.current) } |
.effective_on ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are effective on. Active Record Scope
66 |
# File 'app/models/tax_rate.rb', line 66 scope :effective_on, ->(date) { where("effective_date <= :date and (expiration_date IS NULL OR expiration_date >= :date)", date: date) } |
.ransackable_scopes(_auth_object = nil) ⇒ Array<Symbol>
Returns Ransack-allowlisted scopes.
138 139 140 |
# File 'app/models/tax_rate.rb', line 138 def self.ransackable_scopes(_auth_object = nil) %i[effective_on] end |
.rates_for_voucher_entry ⇒ Array<Hash>
Flatten every effective-today TaxRate into one row per
(rate_type, jurisdiction) for the voucher / AP-bill entry
form's tax dropdown. tax_type is "V" for value-added /
sales tax and "U" for use-tax rows.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'app/models/tax_rate.rb', line 100 def self.rates_for_voucher_entry rates = [] TaxRate.effective_on(Date.current).each do |tr| %w[goods services shipping].each do |rate_type| next unless tr.send(rate_type).present? && (tr.send(rate_type) != 0) rates << if tr.state_code.present? { "label" => "#{tr.tax_type.upcase} #{rate_type} #{tr.state_code}", "value" => "#{tr.tax_type.upcase}_#{rate_type}_#{tr.state_code}", "rate" => tr.send(rate_type) * 100, "id" => tr.id, "tax_type" => "V" } else { "label" => "#{tr.tax_type.upcase} #{rate_type} #{tr.country_iso}", "value" => "#{tr.tax_type.upcase}_#{rate_type}_#{tr.country_iso}", "rate" => tr.send(rate_type) * 100, "id" => tr.id, "tax_type" => "V" } end end %w[use_food use_merch].each do |rate_type| next unless tr.send(rate_type).present? && (tr.send(rate_type) != 0) rates << if tr.state_code.present? { "label" => "#{tr.tax_type.upcase} #{rate_type} #{tr.state_code}", "value" => "#{tr.tax_type.upcase}_#{rate_type}_#{tr.state_code}", "rate" => tr.send(rate_type) * 100, "id" => tr.id, "tax_type" => "U" } else { "label" => "#{tr.tax_type.upcase} #{rate_type} #{tr.country_iso}", "value" => "#{tr.tax_type.upcase}_#{rate_type}_#{tr.country_iso}", "rate" => tr.send(rate_type) * 100, "id" => tr.id, "tax_type" => "U" } end end end rates end |
.sales_tax ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are sales tax. Active Record Scope
70 |
# File 'app/models/tax_rate.rb', line 70 scope :sales_tax, -> { where.not(sales_tax_payable_account_id: nil) } |
.sales_tax_credit ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are sales tax credit. Active Record Scope
68 |
# File 'app/models/tax_rate.rb', line 68 scope :sales_tax_credit, -> { where.not(sales_tax_credit_account_id: nil) } |
.tax_types_for_select ⇒ Array<String>
Distinct tax_type codes from TAXABLE_STATES plus "vat",
for the tax-type filter dropdown.
145 146 147 |
# File 'app/models/tax_rate.rb', line 145 def self.tax_types_for_select TAXABLE_STATES.map { |_k, v| v[:tax_type].to_s }.uniq + ['vat'] end |
.taxjar_summary_rates(country_code: nil) ⇒ Object
Returns an array of tax rate for a specific country, this is designed for single tax rate
regardless of origin/destination, such as in canada
{
:country_code => "CA",
:country => "Canada",
:region_code => "NB",
:region => "New Brunswick",
:minimum_rate => {
:label => "GST",
:rate => 0.05
},
:average_rate => {
:label => "HST",
:rate => 0.15
}
}
182 183 184 185 186 |
# File 'app/models/tax_rate.rb', line 182 def self.taxjar_summary_rates(country_code: nil) rates = Api::Taxjar.client.summary_rates rates = rates.select { |r| r.country_code == country_code } if country_code rates end |
.use_internal_rates ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are use internal rates. Active Record Scope
71 |
# File 'app/models/tax_rate.rb', line 71 scope :use_internal_rates, ->(states) { where(state_code: states) } |
.use_tax ⇒ ActiveRecord::Relation<TaxRate>
A relation of TaxRates that are use tax. Active Record Scope
69 |
# File 'app/models/tax_rate.rb', line 69 scope :use_tax, -> { where.not(use_tax_account_id: nil) } |
Instance Method Details
#active? ⇒ Boolean
Whether the rate is currently effective (today between
effective_date and expiration_date).
90 91 92 |
# File 'app/models/tax_rate.rb', line 90 def active? effective_date <= Date.current and expiration_date > Date.current end |
#country ⇒ Country?
Returns the country this record belongs to.
59 |
# File 'app/models/tax_rate.rb', line 59 belongs_to :country, foreign_key: 'country_iso', primary_key: 'iso', optional: true |
#expiration_date_after_effective_date ⇒ void (protected)
This method returns an undefined value.
Validation: an expiration_date, if present, can't precede
effective_date.
200 201 202 |
# File 'app/models/tax_rate.rb', line 200 def expiration_date_after_effective_date errors.add(:expiration_date, "cannot be before effective date") if expiration_date && effective_date && (expiration_date < effective_date) end |
#has_overlapping_tax_rates ⇒ void (protected)
This method returns an undefined value.
Validation: prevent two rates from covering the same
(country, state, tax_type) over an overlapping date window.
NULL expiration_date is treated as "open-ended".
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'app/models/tax_rate.rb', line 208 def has_overlapping_tax_rates return unless effective_date && tax_type # Overlap requires the same country + state + tax_type. Without the # country_iso filter, different-country rates that share an empty # state_code (e.g. EU VAT) were wrongly flagged as conflicts. scope = TaxRate.where(country_iso: country_iso, state_code: state_code, tax_type: tax_type) scope = scope.where.not(id: id) if persisted? # Two date ranges overlap when each starts on or before the other ends. # A NULL expiration_date means "open-ended" / no upper bound, so both the # stored rate and the record being validated may be unbounded on the right. # overlap ⇔ r.effective_date <= new.expiration AND new.effective_date <= r.expiration_date scope = scope.where(tax_rates: { effective_date: ..expiration_date }) if expiration_date scope = scope.where('tax_rates.expiration_date IS NULL OR tax_rates.expiration_date >= ?', effective_date) conflicting_ids = scope.ids return if conflicting_ids.empty? errors.add(:base, "Existing tax rates with id: #{conflicting_ids.join(',')} in place for these values and with the same date coverage. Cannot save an overlapping tax rate.") end |
#resource_tax_rates ⇒ ActiveRecord::Relation<ResourceTaxRate>
Returns the associated resource tax rates.
64 |
# File 'app/models/tax_rate.rb', line 64 has_many :resource_tax_rates |
#sales_tax_credit_account ⇒ LedgerDetailAccount?
Returns the sales tax credit account this record belongs to.
55 |
# File 'app/models/tax_rate.rb', line 55 belongs_to :sales_tax_credit_account, class_name: "LedgerDetailAccount", optional: true |
#sales_tax_payable_account ⇒ LedgerDetailAccount?
Returns the sales tax payable account this record belongs to.
57 |
# File 'app/models/tax_rate.rb', line 57 belongs_to :sales_tax_payable_account, class_name: "LedgerDetailAccount", optional: true |
#state ⇒ State?
Returns the state this record belongs to.
51 |
# File 'app/models/tax_rate.rb', line 51 belongs_to :state, foreign_key: 'state_code', primary_key: 'code', optional: true |
#tax_explanation ⇒ String
"U" for use-tax rates, "V" for VAT / sales-tax rates —
the abbreviation shown next to the rate in the voucher form.
128 129 130 131 132 133 134 |
# File 'app/models/tax_rate.rb', line 128 def tax_explanation if use_tax_account_id.nil? "V" else "U" end end |
#tax_type_available_in_state ⇒ void (protected)
This method returns an undefined value.
Validation: the il (Illinois) tax type is only valid when
state_code == 'IL'.
193 194 195 |
# File 'app/models/tax_rate.rb', line 193 def tax_type_available_in_state errors.add(:tax_type, "Tax Type is not available in this state") if (state_code != 'IL') && (tax_type == 'il') end |
#use_tax_account ⇒ LedgerDetailAccount?
Returns the use tax account this record belongs to.
53 |
# File 'app/models/tax_rate.rb', line 53 belongs_to :use_tax_account, class_name: "LedgerDetailAccount", optional: true |
#voucher_items ⇒ ActiveRecord::Relation<VoucherItem>
Returns the associated voucher items.
62 |
# File 'app/models/tax_rate.rb', line 62 has_many :voucher_items |