Module: Models::TaxableResource
- Extended by:
- ActiveSupport::Concern
- Included in:
- CreditMemo, Invoice, Order, Quote
- Defined in:
- app/concerns/models/taxable_resource.rb
Belongs to collapse
Instance Method Summary collapse
-
#apply_tax_rate_to_line_items ⇒ Object
Propagates the current resource_tax_rate to all line items without re-fetching from the external tax service.
- #build_tax_params ⇒ Object
- #calculate_tax_for_all_lines ⇒ Object
- #copy_tax_rate(orig_rate) ⇒ Object
- #effective_date ⇒ Object
- #get_rates_for_line(line_item) ⇒ Object
- #get_tax_rate(auto_save: true) ⇒ Object
- #manual_rate_goods ⇒ Object
- #manual_rate_services ⇒ Object
- #manual_rate_shipping ⇒ Object
- #origin_address ⇒ Object
- #refresh_tax_rate ⇒ Object
- #resource_not_taxable? ⇒ Boolean
- #set_initial_tax_rate ⇒ Object
- #should_refresh_tax_rate? ⇒ Boolean
- #state_code ⇒ Object
- #state_code_sym ⇒ Object
- #taxes_grouped_by_rate ⇒ Object
- #taxes_grouped_by_type ⇒ Object
Instance Method Details
#apply_tax_rate_to_line_items ⇒ Object
Propagates the current resource_tax_rate to all line items without re-fetching
from the external tax service. Use this when the tax rate has already been
inherited (e.g. copied from the original invoice) and only the line items need
to be updated to reflect it.
179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'app/concerns/models/taxable_resource.rb', line 179 def apply_tax_rate_to_line_items # Reload to avoid acting on lines pending destroy (frozen hash errors). line_items.reload if resource_tax_rate.nil? line_items.each { |li| li.update_tax_rate(rate: 0, resource_tax_rate_id: nil, tax_type: nil) } else attrs = { resource_tax_rate_id: resource_tax_rate.id, tax_type: resource_tax_rate.tax_type } line_items.active_goods_lines.each { |li| li.update_tax_rate(**attrs.merge({ rate: manual_rate_goods || resource_tax_rate.rate_goods })) } line_items.active_services_lines.each { |li| li.update_tax_rate(**attrs.merge({ rate: manual_rate_services || resource_tax_rate.rate_services })) } line_items.active_shipping_lines.each { |li| li.update_tax_rate(**attrs.merge({ rate: manual_rate_shipping || resource_tax_rate.rate_shipping })) } line_items.active_tax_unclassed_lines.each { |li| li.update_tax_rate(rate: 0, resource_tax_rate_id: nil, tax_type: nil) } end true end |
#build_tax_params ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'app/concerns/models/taxable_resource.rb', line 117 def build_tax_params params = { effective_date:, from_street: origin_address.street1, from_city: origin_address.city, from_state: origin_address.state_code, from_zip: origin_address.zip, from_country: origin_address.country&.iso, to_street: shipping_address.street1, to_city: shipping_address.city, to_state: shipping_address.state_code, to_zip: shipping_address.zip, to_country: shipping_address.country&.iso } if respond_to?(:line_items) # Add line items break down, first we check if our association is already loaded, if not we will eager_load with item for speed line_items_loaded = line_items.loaded? ? line_items : line_items.eager_load(item: :product_tax_code) params[:line_items] = line_items_loaded.active_non_shipping_lines.map do |li| { id: li.id, quantity: li.quantity, product_tax_code: li.product_tax_code&.product_tax_code, tax_class: li.tax_class, unit_price: li.discounted_price, # Not sure if they want discount or unit price msrp here discount: li.discounts_total # This is total discount on the line apparently } end params[:freight_charged] = if respond_to?(:shipping_cost) shipping_cost else 0.0 end end params end |
#calculate_tax_for_all_lines ⇒ Object
258 259 260 261 262 263 |
# File 'app/concerns/models/taxable_resource.rb', line 258 def calculate_tax_for_all_lines line_items.each do |li| li.calculate_tax li.save end end |
#copy_tax_rate(orig_rate) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'app/concerns/models/taxable_resource.rb', line 67 def copy_tax_rate(orig_rate) return nil if orig_rate.nil? new_rate = resource_tax_rate || ResourceTaxRate.new new_rate.assign_attributes( source: orig_rate.source, tax_type: orig_rate.tax_type, tax_rate_id: orig_rate.tax_rate_id, rate_goods: orig_rate.rate_goods, rate_services: orig_rate.rate_services, rate_shipping: orig_rate.rate_shipping, retrieved_on: orig_rate.retrieved_on ) new_rate.save! self.resource_tax_rate = new_rate new_rate end |
#effective_date ⇒ Object
30 31 32 |
# File 'app/concerns/models/taxable_resource.rb', line 30 def effective_date try(:tax_date) || try(:shipped_date) || Date.current end |
#get_rates_for_line(line_item) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'app/concerns/models/taxable_resource.rb', line 212 def get_rates_for_line(line_item) return { rate: 0, resource_tax_rate_id: nil, tax_type: nil } if resource_tax_rate.nil? || line_item.tax_class == 'none' rate = case line_item.tax_class when 'svc' manual_rate_services || resource_tax_rate.rate_services when 'shp' manual_rate_shipping || resource_tax_rate.rate_shipping else manual_rate_goods || resource_tax_rate.rate_goods end rate ||= 0 # Roman added because errors reported when picking items. I.e. TO662607 { rate:, resource_tax_rate_id: resource_tax_rate.id, tax_type: resource_tax_rate.tax_type } end |
#get_tax_rate(auto_save: true) ⇒ Object
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'app/concerns/models/taxable_resource.rb', line 147 def get_tax_rate(auto_save: true) if resource_not_taxable? # delete any existing rate resource_tax_rate&.destroy self.resource_tax_rate = nil update_columns(resource_tax_rate_id: nil) if auto_save nil else res = Taxes::GetTaxRate.new.process(**build_tax_params) # create or update the linked resource_tax_rate new_rate = resource_tax_rate || ResourceTaxRate.new new_rate.assign_attributes( source: res.source, tax_type: res.tax_type, tax_rate_id: res.tax_rate_id, rate_goods: res.rate_goods, rate_services: res.rate_services, rate_shipping: res.rate_shipping, retrieved_on: res.retrieved_on ) new_rate.save! self.resource_tax_rate = new_rate update_columns(resource_tax_rate_id: new_rate.id) if auto_save new_rate end end |
#manual_rate_goods ⇒ Object
194 195 196 197 198 |
# File 'app/concerns/models/taxable_resource.rb', line 194 def manual_rate_goods return nil unless try(:manual_tax_rate_goods) manual_tax_rate_goods / 100 end |
#manual_rate_services ⇒ Object
200 201 202 203 204 |
# File 'app/concerns/models/taxable_resource.rb', line 200 def manual_rate_services return nil unless try(:manual_tax_rate_services) manual_tax_rate_services / 100 end |
#manual_rate_shipping ⇒ Object
206 207 208 209 210 |
# File 'app/concerns/models/taxable_resource.rb', line 206 def manual_rate_shipping return nil unless try(:manual_tax_rate_shipping) manual_tax_rate_shipping / 100 end |
#origin_address ⇒ Object
34 35 36 |
# File 'app/concerns/models/taxable_resource.rb', line 34 def origin_address store&.warehouse_address end |
#refresh_tax_rate ⇒ Object
170 171 172 173 |
# File 'app/concerns/models/taxable_resource.rb', line 170 def refresh_tax_rate get_tax_rate(auto_save: true) apply_tax_rate_to_line_items end |
#resource_not_taxable? ⇒ Boolean
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'app/concerns/models/taxable_resource.rb', line 81 def resource_not_taxable? # no shipping address return true if shipping_address.nil? # no country associated with shipping address return true if shipping_address.country.nil? return true if shipping_address.country.iso == 'US' && TAXABLE_STATES.select { |k, _v| k == shipping_address.state_code.upcase.to_sym }.keys.map(&:to_s).first.nil? # they have a tax exemption return true if billing_entity && billing_entity.tax_exemptions.effective_on(effective_date).where(state_code:).any? # it's marked as manually tax exempt (e.g Amazon Seller Central order where Amazon has collected their exemption certificate) return true if respond_to?(:tax_exempt) && tax_exempt? # it's a store transfer return true if is_a?(Order) && (order_type == Order::STORE_TRANSFER) # it's an international order between US and CA return true if origin_address.country.present? && shipping_address.country.present? && origin_address.country.iso.in?(%w[US CA]) && (origin_address.country.iso != shipping_address.country.iso) # else it is taxable false end |
#resource_tax_rate ⇒ ResourceTaxRate
9 |
# File 'app/concerns/models/taxable_resource.rb', line 9 belongs_to :resource_tax_rate, dependent: :destroy, optional: true |
#set_initial_tax_rate ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'app/concerns/models/taxable_resource.rb', line 38 def set_initial_tax_rate if is_a?(Invoice) && order.present? # copy tax rate from order copy_tax_rate(order.resource_tax_rate) elsif is_a?(Order) && (order_type == 'CO') && rma.present? if rma.has_replacement_items? && rma.original_invoice&.invoice_type != Invoice::CI # If there is a replacement order, use current tax rates so the credit # and replacement orders line up. Skipped for CI (Consignment) invoices # since the marketplace (e.g. Amazon Seller Central) sets the tax and # we inherit those exact amounts on the credit order's line items — the # header rate must come from the invoice so it matches those amounts. get_tax_rate(auto_save: false) elsif rma.original_invoice.present? # copy tax rate from original invoice copy_tax_rate(rma.original_invoice.resource_tax_rate) else get_tax_rate(auto_save: false) end elsif is_a?(CreditMemo) && credit_order.present? # copy tax rate from credit order copy_tax_rate(credit_order.resource_tax_rate) elsif is_a?(CreditMemo) && original_invoice.present? # copy tax rate from original invoice copy_tax_rate(original_invoice.resource_tax_rate) else get_tax_rate(auto_save: false) end end |
#should_refresh_tax_rate? ⇒ Boolean
15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'app/concerns/models/taxable_resource.rb', line 15 def should_refresh_tax_rate? # CI = Consignment Invoice (marketplace sales, e.g. Amazon Seller Central). # On these invoices the marketplace collects and remits tax, so the tax_total # on each line item is set directly by the marketplace feed — not calculated # from our tax rates. Credit orders created from CI-invoice RMAs inherit those # exact amounts. Refreshing would overwrite them with a rate-based calculation. return false if respond_to?(:ci_invoice_credit_order?) && ci_invoice_credit_order? saved_change_to_shipping_address_id? or (respond_to?(:tax_exempt) and saved_change_to_tax_exempt?) or (respond_to?(:manual_tax_rate_goods) and saved_change_to_manual_tax_rate_goods?) or (respond_to?(:manual_tax_rate_services) and saved_change_to_manual_tax_rate_services?) or (respond_to?(:manual_tax_rate_shipping) and saved_change_to_manual_tax_rate_shipping?) end |
#state_code ⇒ Object
105 106 107 108 109 |
# File 'app/concerns/models/taxable_resource.rb', line 105 def state_code return nil if shipping_address.nil? shipping_address.state_code end |
#state_code_sym ⇒ Object
111 112 113 114 115 |
# File 'app/concerns/models/taxable_resource.rb', line 111 def state_code_sym return nil if state_code.nil? state_code.to_sym end |
#taxes_grouped_by_rate ⇒ Object
250 251 252 253 254 255 256 |
# File 'app/concerns/models/taxable_resource.rb', line 250 def taxes_grouped_by_rate tax_grouped = {} line_items.group_by(&:tax_type).each do |tax_type, lines| tax_grouped[tax_type] = lines.sum(&:tax_total) unless tax_type.nil? end tax_grouped end |
#taxes_grouped_by_type ⇒ Object
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'app/concerns/models/taxable_resource.rb', line 228 def taxes_grouped_by_type tax_grouped = {} tt = nil grouped_by_type = line_items.all.group_by(&:tax_type) grouped_by_type.delete_if { |tax_type, _lines| tax_type.nil? } grouped_by_type.each do |tax_type, lines_by_type| lines_by_type.group_by(&:tax_rate).each do |rate, lines_by_rate| tt = tax_type tax_amount = lines_by_rate.sum(&:tax_total) rate_percentage = (rate * 100) next if tax_amount.zero? # don't show $0 amounts rate_string = ActionController::Base.helpers.number_with_precision(rate_percentage, precision: 3, strip_insignificant_zeros: true) name = "#{TaxRate.description_for(tt, destination_country: try(:shipping_address)&.country)} #{rate_string}%" tax_grouped[tax_type] = { tax_amount:, name:, rate: rate_percentage } end end # add any offset amount to the total, this is for legacy documents which had a rounding issue tax_grouped[tt][:tax_amount] += tax_offset if tt && tax_grouped[tt] && tax_offset tax_grouped end |