solidus_avatax 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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