solidus_avatax 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +15 -0
  6. data/LICENSE +26 -0
  7. data/README.md +71 -0
  8. data/Rakefile +21 -0
  9. data/app/assets/javascripts/spree/frontend/solidus_avatax.js +1 -0
  10. data/app/assets/stylesheets/spree/frontend/solidus_avatax.css +1 -0
  11. data/app/models/spree/adjustment_decorator.rb +21 -0
  12. data/app/models/spree/order_contents_decorator.rb +26 -0
  13. data/app/models/spree/order_decorator.rb +43 -0
  14. data/app/models/spree/promotion_handler/coupon_decorator.rb +11 -0
  15. data/app/models/spree/reimbursement_decorator.rb +1 -0
  16. data/app/models/spree/tax_rate_decorator.rb +45 -0
  17. data/app/models/spree_avatax/calculator.rb +26 -0
  18. data/app/models/spree_avatax/return_invoice.rb +197 -0
  19. data/app/models/spree_avatax/sales_invoice.rb +157 -0
  20. data/app/models/spree_avatax/sales_shared.rb +211 -0
  21. data/app/models/spree_avatax/shared.rb +53 -0
  22. data/app/models/spree_avatax/short_ship_return_invoice.rb +133 -0
  23. data/app/models/spree_avatax/short_ship_return_invoice_inventory_unit.rb +11 -0
  24. data/bin/rails +7 -0
  25. data/circle.yml +6 -0
  26. data/config/locales/en.yml +5 -0
  27. data/db/migrate/20140122165618_add_avatax_response_at_to_orders.rb +5 -0
  28. data/db/migrate/20140214153139_add_avatax_invoice_at_to_orders.rb +5 -0
  29. data/db/migrate/20140617222244_close_all_tax_adjustments.rb +5 -0
  30. data/db/migrate/20140701144237_update_avatax_calculator_type.rb +17 -0
  31. data/db/migrate/20140801132302_create_spree_avatax_return_invoices.rb +19 -0
  32. data/db/migrate/20140903135132_create_spree_avatax_sales_orders.rb +15 -0
  33. data/db/migrate/20140903135357_create_spree_avatax_sales_invoices.rb +19 -0
  34. data/db/migrate/20140904171341_generate_uncommitted_sales_invoices.rb +31 -0
  35. data/db/migrate/20140911214414_add_transaction_id_to_sales_orders_and_sales_invoices.rb +6 -0
  36. data/db/migrate/20140911215422_add_canceled_at_and_cancel_transaction_id_to_sales_invoices.rb +6 -0
  37. data/db/migrate/20150427154942_create_spree_avatax_short_ship_return_invoices.rb +44 -0
  38. data/db/migrate/20150518172627_fix_avatax_short_ship_index.rb +30 -0
  39. data/lib/generators/solidus_avatax/install/install_generator.rb +31 -0
  40. data/lib/generators/solidus_avatax/install/templates/config/initializers/avatax.rb +18 -0
  41. data/lib/solidus_avatax.rb +4 -0
  42. data/lib/spree_avatax/config.rb +16 -0
  43. data/lib/spree_avatax/engine.rb +29 -0
  44. data/lib/spree_avatax/factories.rb +25 -0
  45. data/lib/tasks/commit_backfill.rake +119 -0
  46. data/lib/tasks/sales_invoice_backfill.rake +43 -0
  47. data/solidus_avatax.gemspec +34 -0
  48. data/spec/features/store_credits_spec.rb +92 -0
  49. data/spec/features/tax_calculation_spec.rb +75 -0
  50. data/spec/fixtures/vcr_cassettes/sales_invoice_gettax_with_discounts.yml +136 -0
  51. data/spec/fixtures/vcr_cassettes/sales_invoice_gettax_without_discounts.yml +136 -0
  52. data/spec/fixtures/vcr_cassettes/taxes_with_store_credits.yml +113 -0
  53. data/spec/models/spree/adjustment_spec.rb +23 -0
  54. data/spec/models/spree/order_contents_spec.rb +35 -0
  55. data/spec/models/spree/order_spec.rb +111 -0
  56. data/spec/models/spree/shipping_rate_spec.rb +23 -0
  57. data/spec/models/spree/tax_rate_spec.rb +40 -0
  58. data/spec/models/spree_avatax/calculator.rb +20 -0
  59. data/spec/models/spree_avatax/return_invoice_spec.rb +193 -0
  60. data/spec/models/spree_avatax/sales_invoice_spec.rb +491 -0
  61. data/spec/models/spree_avatax/sales_shared_spec.rb +47 -0
  62. data/spec/models/spree_avatax/shared_spec.rb +89 -0
  63. data/spec/models/spree_avatax/short_ship_return_invoice_spec.rb +181 -0
  64. data/spec/spec_helper.rb +85 -0
  65. data/spec/support/return_invoice_soap_responses.rb +117 -0
  66. data/spec/support/sales_invoice_soap_responses.rb +259 -0
  67. data/spec/support/short_ship_return_invoice_soap_responses.rb +178 -0
  68. data/spec/support/zone_support.rb +6 -0
  69. metadata +320 -0
@@ -0,0 +1,157 @@
1
+ # A SalesInvoice is persisted by Avatax but it's not recognized as complete until it's "committed".
2
+ class SpreeAvatax::SalesInvoice < ActiveRecord::Base
3
+ DOC_TYPE = 'SalesInvoice'
4
+ CANCEL_CODE = 'DocVoided'
5
+
6
+ class CommitInvoiceNotFound < StandardError; end
7
+ class AlreadyCommittedError < StandardError; end
8
+
9
+ belongs_to :order, class_name: "Spree::Order", inverse_of: :avatax_sales_invoice
10
+
11
+ validates :order, presence: true
12
+ validates :doc_id, presence: true
13
+ validates :doc_code, presence: true
14
+ validates :doc_date, presence: true
15
+
16
+ class << self
17
+ # Calls the Avatax API to generate a sales invoice and calculate taxes on the line items.
18
+ # On failure it will raise.
19
+ # On success it updates taxes on the order and its line items and create a SalesInvoice record.
20
+ # At this point the record is saved but uncommitted on Avatax's end.
21
+ # After the order completes the ".commit" method will get called and we'll commit the
22
+ # sales invoice, which marks it as complete on Avatax's end.
23
+ def generate(order)
24
+ bench_start = Time.now
25
+
26
+ return if order.completed? || !SpreeAvatax::Shared.taxable_order?(order)
27
+
28
+ taxable_records = order.line_items + order.shipments
29
+ taxable_records.each do |taxable_record|
30
+ taxable_record.update_column(:pre_tax_amount, taxable_record.discounted_amount.round(2))
31
+ end
32
+
33
+ result = SpreeAvatax::SalesShared.get_tax(order, DOC_TYPE)
34
+ # run this immediately to ensure that everything matches up before modifying the database
35
+ tax_line_data = SpreeAvatax::SalesShared.build_tax_line_data(order, result)
36
+
37
+ if sales_invoice = order.avatax_sales_invoice
38
+ if sales_invoice.committed_at.nil?
39
+ sales_invoice.destroy
40
+ else
41
+ raise AlreadyCommittedError.new("Sales invoice #{sales_invoice.id} is already committed.")
42
+ end
43
+ end
44
+
45
+ sales_invoice = order.create_avatax_sales_invoice!({
46
+ transaction_id: result[:transaction_id],
47
+ doc_id: result[:doc_id],
48
+ doc_code: result[:doc_code],
49
+ doc_date: result[:doc_date],
50
+ pre_tax_total: result[:total_amount],
51
+ additional_tax_total: result[:total_tax],
52
+ })
53
+
54
+ SpreeAvatax::SalesShared.update_taxes(order, tax_line_data)
55
+
56
+ sales_invoice
57
+ rescue Exception => e
58
+ if SpreeAvatax::Config.sales_invoice_generate_error_handler
59
+ SpreeAvatax::Config.sales_invoice_generate_error_handler.call(order, e)
60
+ else
61
+ raise
62
+ end
63
+ ensure
64
+ duration = (Time.now - bench_start).round
65
+ Rails.logger.info "avatax_sales_invoice_generate_duration=#{(duration*1000).round}"
66
+ end
67
+
68
+ def commit(order)
69
+ return if !SpreeAvatax::Shared.taxable_order?(order)
70
+
71
+ raise CommitInvoiceNotFound.new("No invoice for order #{order.number}") if order.avatax_sales_invoice.nil?
72
+
73
+ post_tax(order.avatax_sales_invoice)
74
+
75
+ order.avatax_sales_invoice.update!(committed_at: Time.now)
76
+
77
+ order.avatax_sales_invoice
78
+ rescue Exception => e
79
+ if SpreeAvatax::Config.sales_invoice_commit_error_handler
80
+ SpreeAvatax::Config.sales_invoice_commit_error_handler.call(order, e)
81
+ else
82
+ raise
83
+ end
84
+ end
85
+
86
+ def cancel(order)
87
+ return if order.avatax_sales_invoice.nil?
88
+
89
+ result = cancel_tax(order.avatax_sales_invoice)
90
+
91
+ order.avatax_sales_invoice.update!({
92
+ canceled_at: Time.now,
93
+ cancel_transaction_id: result[:transaction_id],
94
+ })
95
+ rescue Exception => e
96
+ if SpreeAvatax::Config.sales_invoice_cancel_error_handler
97
+ SpreeAvatax::Config.sales_invoice_cancel_error_handler.call(order, e)
98
+ else
99
+ raise
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def post_tax(sales_invoice)
106
+ params = posttax_params(sales_invoice)
107
+
108
+ logger.info "[avatax] posttax sales_invoice=#{sales_invoice.id} order=#{sales_invoice.order_id}"
109
+ logger.debug { "[avatax] params: #{params.to_json}" }
110
+
111
+ response = SpreeAvatax::Shared.tax_svc.posttax(params)
112
+ SpreeAvatax::Shared.require_success!(response)
113
+
114
+ response
115
+ end
116
+
117
+ def cancel_tax(sales_invoice)
118
+ params = canceltax_params(sales_invoice)
119
+
120
+ logger.info "[avatax] canceltax sales_invoice=#{sales_invoice.id}"
121
+ logger.debug { "[avatax] params: #{params.to_json}" }
122
+
123
+ response = SpreeAvatax::Shared.tax_svc.canceltax(params)
124
+
125
+ SpreeAvatax::Shared.require_success!(response)
126
+
127
+ response
128
+ end
129
+
130
+ # see https://github.com/avadev/AvaTax-Calc-SOAP-Ruby/blob/master/PostTaxTest.rb
131
+ def posttax_params(sales_invoice)
132
+ {
133
+ doccode: sales_invoice.doc_code,
134
+ companycode: SpreeAvatax::Config.company_code,
135
+
136
+ doctype: DOC_TYPE,
137
+ docdate: sales_invoice.doc_date,
138
+
139
+ commit: true,
140
+
141
+ totalamount: sales_invoice.pre_tax_total,
142
+ totaltax: sales_invoice.additional_tax_total,
143
+ }
144
+ end
145
+
146
+ # see https://github.com/avadev/AvaTax-Calc-SOAP-Ruby/blob/master/CancelTaxTest.rb
147
+ def canceltax_params(sales_invoice)
148
+ {
149
+ # Required Parameters
150
+ doccode: sales_invoice.doc_code,
151
+ doctype: DOC_TYPE,
152
+ cancelcode: CANCEL_CODE,
153
+ companycode: SpreeAvatax::Config.company_code,
154
+ }
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,211 @@
1
+ module SpreeAvatax::SalesShared
2
+
3
+ DESTINATION_CODE = "1"
4
+
5
+ SHIPPING_TAX_CODE = 'FR020100' # docs: http://goo.gl/KuIuxc
6
+
7
+ SHIPPING_DESCRIPTION = 'Shipping Charge'
8
+
9
+ class InvalidApiResponse < StandardError; end
10
+
11
+ class << self
12
+ # Queries Avatax for taxes on a specific order using the specified doc_type.
13
+ # SalesOrder doc types are not persisted on Avatax.
14
+ # SalesInvoice doc types do persist an uncommitted record on Avatax.
15
+ def get_tax(order, doc_type)
16
+ params = gettax_params(order, doc_type)
17
+
18
+ logger.info "[avatax] gettax order=#{order.id} doc_type=#{doc_type}"
19
+ logger.debug { "[avatax] params: #{params.to_json}" }
20
+
21
+ response = SpreeAvatax::Shared.tax_svc.gettax(params)
22
+ SpreeAvatax::Shared.require_success!(response)
23
+
24
+ response
25
+ end
26
+
27
+ def update_taxes(order, tax_line_data)
28
+ reset_tax_attributes(order)
29
+
30
+ tax_line_data.each do |data|
31
+ record, tax_line = data[:record], data[:tax_line]
32
+
33
+ record.update_column(:pre_tax_amount, record.discounted_amount)
34
+
35
+ tax = BigDecimal.new(tax_line[:tax]).abs
36
+
37
+ record.adjustments.tax.create!({
38
+ adjustable: record,
39
+ amount: tax,
40
+ order: order,
41
+ label: Spree.t(:avatax_label),
42
+ included: false, # would be true for VAT
43
+ source: Spree::TaxRate.avatax_the_one_rate,
44
+ finalized: true, # this tells spree not to automatically recalculate avatax tax adjustments
45
+ })
46
+
47
+ Spree::ItemAdjustments.new(record).update
48
+ record.save!
49
+ end
50
+
51
+ Spree::OrderUpdater.new(order).update
52
+ order.save!
53
+ end
54
+
55
+ # returns an array like:
56
+ # [
57
+ # {tax_line: {...}, record: #<Spree::LineItem id=111>},
58
+ # {tax_line: {...}, record: #<Spree::LineItem id=222>},
59
+ # {tax_line: {...}, record: #<Spree::Shipment id=111>},
60
+ # ]
61
+ def build_tax_line_data(order, avatax_result)
62
+ # Array.wrap is required because the XML engine the Avatax gem uses turns child nodes into
63
+ # {...} instead of [{...}] when there is only one child.
64
+ tax_lines = Array.wrap(avatax_result[:tax_lines][:tax_line])
65
+
66
+ # builds a hash like: {"L-111": {record: #<Spree::LineItem ...>}, ...}
67
+ data = (order.line_items + order.shipments).map { |r| [avatax_id(r), {record: r}] }.to_h
68
+
69
+ # adds :tax_line to each entry in the data
70
+ tax_lines.each do |tax_line|
71
+ avatax_id = tax_line[:no]
72
+ if data[avatax_id]
73
+ data[avatax_id][:tax_line] = tax_line
74
+ else
75
+ raise InvalidApiResponse.new("Couldn't find #{avatax_id.inspect}")
76
+ end
77
+ end
78
+
79
+ missing = data.select { |avatax_id, data| data[:tax_line].nil? }
80
+ if missing.any?
81
+ raise InvalidApiResponse.new("missing tax data for #{missing.keys}")
82
+ end
83
+
84
+ data.values
85
+ end
86
+
87
+ # sometimes we have to store different types of things in a single array (like line items and
88
+ # shipments). this allows us to provide a unique identifier to each record.
89
+ def avatax_id(record)
90
+ "#{record.class.name}-#{record.id}"
91
+ end
92
+
93
+
94
+ # Clears previously-set tax attributes from an order, if any, unless the
95
+ # order has already been completed.
96
+ #
97
+ # @param order [Spree::Order] the order
98
+ def reset_tax_attributes(order)
99
+ return if order.completed?
100
+
101
+ destroyed_adjustments = order.all_adjustments.tax.destroy_all
102
+ return if destroyed_adjustments.empty?
103
+
104
+ order.line_items.each do |line_item|
105
+ line_item.update_attributes!({
106
+ additional_tax_total: 0,
107
+ adjustment_total: 0,
108
+ pre_tax_amount: 0,
109
+ included_tax_total: 0,
110
+ })
111
+
112
+ Spree::ItemAdjustments.new(line_item).update
113
+ line_item.save!
114
+ end
115
+
116
+ order.update_attributes!({
117
+ additional_tax_total: 0,
118
+ adjustment_total: 0,
119
+ included_tax_total: 0,
120
+ })
121
+
122
+ order.update!
123
+ order.save!
124
+ end
125
+
126
+ private
127
+
128
+ def logger
129
+ SpreeAvatax::Shared.logger
130
+ end
131
+
132
+ # see https://github.com/avadev/AvaTax-Calc-SOAP-Ruby/blob/master/GetTaxTest.rb
133
+ def gettax_params(order, doc_type)
134
+ {
135
+ doccode: order.number,
136
+ customercode: REXML::Text.normalize(order.email),
137
+ companycode: SpreeAvatax::Config.company_code,
138
+
139
+ doctype: doc_type,
140
+ docdate: Date.today,
141
+
142
+ commit: false, # we commit separately after the order completes
143
+
144
+ # NOTE: we only want order-level adjustments here. not line item or shipping adjustments.
145
+ # avatax distributes order-level discounts across all "lineitem" entries that have
146
+ # "discounted:true"
147
+ # Also, the "discount" can be negative and Avatax handles that OK. A negative number
148
+ # would mean that *charges* were added to the order via an order-level adjustment.
149
+ discount: order.avatax_order_adjustment_total.round(2).to_f,
150
+
151
+ addresses: [
152
+ {
153
+ addresscode: DESTINATION_CODE,
154
+ line1: REXML::Text.normalize(order.ship_address.address1),
155
+ line2: REXML::Text.normalize(order.ship_address.address2),
156
+ city: REXML::Text.normalize(order.ship_address.city),
157
+ postalcode: REXML::Text.normalize(order.ship_address.zipcode),
158
+ },
159
+ ],
160
+
161
+ lines: gettax_lines_params(order),
162
+ }
163
+ end
164
+
165
+ def gettax_lines_params(order)
166
+ line_items = order.line_items.includes(variant: :product)
167
+
168
+ line_item_lines = line_items.map do |line_item|
169
+ {
170
+ # Required Parameters
171
+ no: avatax_id(line_item),
172
+ qty: line_item.quantity,
173
+ amount: line_item.discounted_amount.round(2).to_f,
174
+ origincodeline: DESTINATION_CODE, # We don't really send the correct value here
175
+ destinationcodeline: DESTINATION_CODE,
176
+
177
+ # Best Practice Parameters
178
+ description: REXML::Text.normalize(line_item.variant.product.description.to_s.truncate(100)),
179
+
180
+ # Optional Parameters
181
+ itemcode: line_item.variant.sku,
182
+ taxcode: line_item.tax_category.tax_code,
183
+ # "discounted" tells avatax to include this item when it distributes order-level discounts
184
+ # across avatax "lines"
185
+ discounted: true,
186
+ }
187
+ end
188
+
189
+ shipment_lines = order.shipments.map do |shipment|
190
+ {
191
+ # Required Parameters
192
+ no: avatax_id(shipment),
193
+ qty: 1,
194
+ amount: shipment.discounted_amount.round(2).to_f,
195
+ origincodeline: DESTINATION_CODE, # We don't really send the correct value here
196
+ destinationcodeline: DESTINATION_CODE,
197
+
198
+ # Best Practice Parameters
199
+ description: SHIPPING_DESCRIPTION,
200
+
201
+ # Optional Parameters
202
+ taxcode: SHIPPING_TAX_CODE,
203
+ # order-level discounts do not apply to shipments
204
+ discounted: false,
205
+ }
206
+ end
207
+
208
+ line_item_lines + shipment_lines
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,53 @@
1
+ module SpreeAvatax::Shared
2
+ class FailedApiResponse < StandardError
3
+ attr_reader :response, :messages
4
+
5
+ def initialize(response)
6
+ @response = response
7
+ # avatax seems to have two different error message formats:
8
+ # https://gist.github.com/jordan-brough/a22163e4551c692365b8
9
+ # https://gist.github.com/jordan-brough/c778a3417850dfa2307c
10
+ # We should pester Avatax about this sometime.
11
+ if @response[:messages].is_a?(Array)
12
+ @messages = response[:messages]
13
+ else
14
+ @messages = Array.wrap(response[:messages][:message])
15
+ end
16
+
17
+ super(messages.map { |msg| msg[:summary] })
18
+ end
19
+ end
20
+
21
+ class << self
22
+
23
+ def logger
24
+ Rails.logger
25
+ end
26
+
27
+ def taxable_order?(order)
28
+ order.line_items.present? && order.ship_address.present?
29
+ end
30
+
31
+ def tax_svc
32
+ @tax_svc ||= AvaTax::TaxService.new({
33
+ username: SpreeAvatax::Config.username,
34
+ password: SpreeAvatax::Config.password,
35
+ service_url: SpreeAvatax::Config.service_url,
36
+ clientname: 'Spree::Avatax',
37
+ })
38
+ end
39
+
40
+ def require_success!(response)
41
+ if response[:result_code] == 'Success'
42
+ logger.info "[avatax] response - result=success doc_id=#{response[:doc_id]} doc_code=#{response[:doc_code]} transaction_id=#{response[:transaction_id]}"
43
+ logger.debug { "[avatax] response: #{response.to_json}" }
44
+ else
45
+ logger.error "[avatax] response - result=error doc_id=#{response[:doc_id]} doc_code=#{response[:doc_code]} transaction_id=#{response[:transaction_id]}"
46
+ logger.error "[avatax] response: #{response.to_json}"
47
+ raise FailedApiResponse.new(response)
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,133 @@
1
+ class SpreeAvatax::ShortShipReturnInvoice < ActiveRecord::Base
2
+ DOC_TYPE = 'ReturnInvoice'
3
+
4
+ DESTINATION_CODE = "1"
5
+
6
+ TAX_OVERRIDE_TYPE = 'TaxAmount'
7
+ TAX_OVERRIDE_REASON = 'Short ship'
8
+
9
+ has_many :short_ship_return_invoice_inventory_units, class_name: 'SpreeAvatax::ShortShipReturnInvoiceInventoryUnit', inverse_of: :short_ship_return_invoice
10
+ has_many :inventory_units, through: :short_ship_return_invoice_inventory_units, class_name: 'Spree::InventoryUnit'
11
+
12
+ validates :doc_id, presence: true
13
+ validates :doc_code, presence: true
14
+ validates :doc_date, presence: true
15
+
16
+ class << self
17
+ # Calls the Avatax API to generate a return invoice for an item that has
18
+ # been short shipped. It tells Avatax how much tax was refunded rather than
19
+ # asking it how much should be refunded.
20
+ # It is generated in the "committed" state since there is no need for a two-
21
+ # step commit here.
22
+ #
23
+ # On failure it will raise.
24
+ def generate(unit_cancels:)
25
+ inventory_units = unit_cancels.map(&:inventory_unit)
26
+
27
+ order_ids = inventory_units.map(&:order_id).uniq
28
+ if order_ids.size > 1
29
+ raise "unit cancels #{unit_cancels.map(&:id)} had more than one order: #{order_ids}"
30
+ end
31
+
32
+ success_result = get_tax(unit_cancels: unit_cancels)
33
+
34
+ create!(
35
+ inventory_units: inventory_units,
36
+ committed: true,
37
+ doc_id: success_result[:doc_id],
38
+ doc_code: success_result[:doc_code],
39
+ doc_date: success_result[:doc_date],
40
+ )
41
+ end
42
+
43
+ private
44
+
45
+ def get_tax(unit_cancels:)
46
+ params = gettax_params(unit_cancels: unit_cancels)
47
+
48
+ logger.info("[avatax] gettax unit_cancel_ids=#{unit_cancels.map(&:id)} doc_type=#{DOC_TYPE}")
49
+ logger.debug("[avatax] params: " + params.to_json)
50
+
51
+ response = SpreeAvatax::Shared.tax_svc.gettax(params)
52
+ SpreeAvatax::Shared.require_success!(response)
53
+
54
+ response
55
+ end
56
+
57
+ # see https://github.com/avadev/AvaTax-Calc-SOAP-Ruby/blob/master/GetTaxTest.rb
58
+ def gettax_params(unit_cancels:)
59
+ # we verified previously that there is only one order
60
+ order = unit_cancels.first.inventory_unit.order
61
+
62
+ lines = gettax_line_params(
63
+ unit_cancels: unit_cancels,
64
+ taxed_at: order.avatax_sales_invoice.try!(:doc_date) || order.completed_at
65
+ )
66
+
67
+ {
68
+ doccode: "#{order.number}-short-#{Time.now.to_f}",
69
+ referencecode: order.number,
70
+ customercode: order.user_id,
71
+ companycode: SpreeAvatax::Config.company_code,
72
+
73
+ doctype: DOC_TYPE,
74
+ docdate: Date.today,
75
+
76
+ commit: true,
77
+
78
+ addresses: [
79
+ {
80
+ addresscode: DESTINATION_CODE,
81
+ line1: REXML::Text.normalize(order.ship_address.address1),
82
+ line2: REXML::Text.normalize(order.ship_address.address2),
83
+ city: REXML::Text.normalize(order.ship_address.city),
84
+ postalcode: REXML::Text.normalize(order.ship_address.zipcode),
85
+ },
86
+ ],
87
+
88
+ lines: lines,
89
+ }
90
+ end
91
+
92
+ def gettax_line_params(unit_cancels:, taxed_at:)
93
+ unit_cancels.sort_by(&:id).map do |unit_cancel|
94
+ inventory_unit = unit_cancel.inventory_unit
95
+
96
+ adjustment = unit_cancel.adjustment
97
+
98
+ if adjustment
99
+ total = -adjustment.amount # adjustment is stored as a negative amount
100
+ tax = inventory_unit.additional_tax_total
101
+ before_tax = total - tax
102
+ else
103
+ # TODO: Consider removing this case. Are there expected scenarios
104
+ # where there will be no adjustment present?
105
+ total = 0
106
+ tax = 0
107
+ before_tax = 0
108
+ end
109
+
110
+ {
111
+ # Required Parameters
112
+ no: inventory_unit.id,
113
+ itemcode: inventory_unit.line_item.variant.sku,
114
+ taxcode: inventory_unit.line_item.tax_category.tax_code,
115
+ qty: 1,
116
+ amount: -before_tax,
117
+ origincodeline: DESTINATION_CODE, # We don't really send the correct value here
118
+ destinationcodeline: DESTINATION_CODE,
119
+
120
+ # We tell Avatax what the amounts were rather than asking Avatax what
121
+ # the amounts should have been.
122
+ taxoverridetypeline: TAX_OVERRIDE_TYPE,
123
+ reasonline: TAX_OVERRIDE_REASON,
124
+ taxamountline: -tax,
125
+ taxdateline: taxed_at.to_date,
126
+
127
+ # Best Practice Parameters
128
+ description: REXML::Text.normalize(inventory_unit.line_item.variant.product.description.to_s[0...100]),
129
+ }
130
+ end
131
+ end
132
+ end
133
+ end