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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2c998021175378307065168f9f8986de374c1f25
4
+ data.tar.gz: b9bc111b7b45a443c12180eb64e4c44c1762ba2f
5
+ SHA512:
6
+ metadata.gz: 382aedc870d8f820285a38130dc916ec0cfbbfaf6ee3146de453f96b7909b512eb64d8be82fa78567675d696a5d0d71d553ac2b205705d0c791c3620edc0aafa
7
+ data.tar.gz: 3000285496df31516db1b39f4ef5a12571a134543db6cec4f9e09c33b3f5cc9763ac7a465961895c2b96ae64ce4b6a1edf6de704fa7b8d66b9b1e8c71a884d69
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ \#*
2
+ *~
3
+ .#*
4
+ .DS_Store
5
+ .idea
6
+ .project
7
+ .sass-cache
8
+ coverage
9
+ Gemfile.lock
10
+ tmp
11
+ nbproject
12
+ pkg
13
+ *.swp
14
+ spec/dummy
15
+ spec/avalara_config.yml
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.6
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "solidus", git: "git@github.com:solidusio/solidus.git", branch: "master"
4
+ gem "solidus_auth_devise", "~> 1.0"
5
+
6
+ group :development, :test do
7
+ gem "pry-rails"
8
+ gem 'pry-byebug'
9
+ gem 'vcr'
10
+ gem 'webmock'
11
+ gem 'timecop'
12
+ end
13
+
14
+ gemspec
15
+
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2015 Solidus
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name Solidus nor the names of its contributors may be used to
13
+ endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ Solidus Avatax
2
+ ===========
3
+
4
+ Avatax integration with Solidus.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ In your Gemfile:
10
+
11
+ ```ruby
12
+ gem "solidus_avatax"
13
+ ```
14
+
15
+ Then run from the command line:
16
+
17
+ ```shell
18
+ bundle install
19
+ rails g solidus_avatax:install
20
+ ```
21
+
22
+ Configuration
23
+ -------------
24
+
25
+ If you want to notify Avatax about short ships you should configure the
26
+ following:
27
+
28
+ ```ruby
29
+ Spree::OrderCancellations.short_ship_tax_notifier = ->(unit_cancels) do
30
+ SpreeAvatax::ShortShipReturnInvoice.generate(unit_cancels: unit_cancels)
31
+ end
32
+ ```
33
+
34
+ Known Issues
35
+ ------------
36
+
37
+ 1. "Additional tax" (e.g. US taxes) *is* supported but "included tax" (e.g.
38
+ VAT) is *not*. This feature is not on the roadmap but we'd be willing to
39
+ look at pull requests for it.
40
+ 2. Note for future development: There is currently a bug in Solidus where the
41
+ "open all adjustments" admin button doesn't work for line item adjustments.
42
+ See
43
+ [here](https://github.com/spree/spree/blob/v2.2.2/backend/app/controllers/spree/admin/orders_controller.rb#L103).
44
+ If that bug were ever fixed, we'd want to monkey patch the controller action
45
+ to prevent tax adjustments from ever being re-opened. We always want tax
46
+ adjustments to be "closed", which tells Solidus not to try to recalculate
47
+ them automatically.
48
+
49
+ Testing
50
+ -------
51
+
52
+ First bundle your dependencies, then run `rake`. `rake` will default to
53
+ building the dummy app if it does not exist, then it will run specs. The dummy
54
+ app can be regenerated by using `rake test_app`.
55
+
56
+ ```shell
57
+ bundle
58
+ bundle exec rake
59
+ ```
60
+
61
+ Live tests are provided to insure that the Avalara gem works as promised. The
62
+ credentials must be provided under `spec/avalara_config.yml` to run them
63
+ successfully. See the example YAML for guidance.
64
+
65
+ ```
66
+ username: 'USERNAME'
67
+ password: 'PASSWORD'
68
+ company_code: 'COMPANY'
69
+ ```
70
+
71
+ These tests will communicate against the test Avatax API.
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/extension_rake'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task :default do
10
+ if Dir["spec/dummy"].empty?
11
+ Rake::Task[:test_app].invoke
12
+ Dir.chdir("../../")
13
+ end
14
+ Rake::Task[:spec].invoke
15
+ end
16
+
17
+ desc 'Generates a dummy app for testing'
18
+ task :test_app do
19
+ ENV['LIB_NAME'] = 'solidus_avatax'
20
+ Rake::Task['extension:test_app'].invoke
21
+ end
@@ -0,0 +1 @@
1
+ // Only needed for Specs right now. Due to this stupid thing the test app generator does.
@@ -0,0 +1 @@
1
+ /* Only needed for Specs right now. Due to this stupid thing the test app generator does. */
@@ -0,0 +1,21 @@
1
+ Spree::Adjustment.class_eval do
2
+ # We always want tax adjustments to be "closed" because that tells Spree not to try to recalculate them automatically.
3
+ validates(
4
+ :finalized,
5
+ {
6
+ inclusion: {
7
+ in: [true],
8
+ message: "Tax adjustments must always be finalized for Avatax",
9
+ },
10
+ if: 'source_type == "Spree::TaxRate"',
11
+ }
12
+ )
13
+
14
+ if !defined?(Spree::Adjustment.non_tax) # Spree 2.4+ has this scope already
15
+ scope :non_tax, -> do
16
+ source_type = arel_table[:source_type]
17
+ where(source_type.not_eq('Spree::TaxRate').or source_type.eq(nil))
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,26 @@
1
+ Spree::OrderContents.class_eval do
2
+ def add_with_avatax(*args)
3
+ add_without_avatax(*args).tap do
4
+ SpreeAvatax::SalesShared.reset_tax_attributes(order)
5
+ end
6
+ end
7
+
8
+ def remove_with_avatax(*args)
9
+ remove_without_avatax(*args).tap do
10
+ SpreeAvatax::SalesShared.reset_tax_attributes(order)
11
+ end
12
+ end
13
+
14
+ def update_cart_with_avatax(params)
15
+ if update_cart_without_avatax(params)
16
+ SpreeAvatax::SalesShared.reset_tax_attributes(order)
17
+ true
18
+ else
19
+ false
20
+ end
21
+ end
22
+
23
+ alias_method_chain :update_cart, :avatax
24
+ alias_method_chain :add, :avatax
25
+ alias_method_chain :remove, :avatax
26
+ end
@@ -0,0 +1,43 @@
1
+ Spree::Order.class_eval do
2
+
3
+ has_one :avatax_sales_invoice, class_name: 'SpreeAvatax::SalesInvoice', inverse_of: :order
4
+
5
+ after_save :avatax_order_after_save
6
+
7
+ state_machine.after_transition from: :address do |order, transition|
8
+ SpreeAvatax::SalesShared.reset_tax_attributes(order)
9
+ end
10
+
11
+ state_machine.before_transition to: :payment do |order, transition|
12
+ SpreeAvatax::SalesInvoice.generate(order)
13
+ end
14
+
15
+ state_machine.after_transition to: :complete do |order, transition|
16
+ SpreeAvatax::SalesInvoice.commit(order)
17
+ end
18
+
19
+ state_machine.after_transition to: :canceled do |order, transition|
20
+ SpreeAvatax::SalesInvoice.cancel(order)
21
+ end
22
+
23
+ # The total of discounts and charges added at the order level.
24
+ # This intentionally excludes line item & shipment level discounts as those are sent to avatax
25
+ # by being wrapped into net amount of the line item/shipment itself.
26
+ def avatax_order_adjustment_total
27
+ # We invert the sign because avatax calls this the "discount" even though it can handle charges
28
+ # as well as discounts
29
+ -adjustments.non_tax.eligible.sum(:amount)
30
+ end
31
+
32
+ def avatax_order_after_save
33
+ # NOTE: DO NOT do anything that will trigger any saves inside of here. It will cause infinite
34
+ # recursion since it will cause another "after_save" to be called with the dirty attributes
35
+ # in the same state. Instead just move the order out of the "confirm" state so that it
36
+ # will have to go through tax calculations again.
37
+ if ship_address_id_changed? && confirm?
38
+ Rails.logger.info "[avatax] order address change detected for order #{number} while in confirm state. resetting order state to 'payment'."
39
+ update_columns(state: 'payment', updated_at: Time.now)
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,11 @@
1
+ Spree::PromotionHandler::Coupon.class_eval do
2
+ def apply_with_avatax
3
+ apply_without_avatax.tap do
4
+ if successful?
5
+ SpreeAvatax::SalesShared.reset_tax_attributes(order)
6
+ end
7
+ end
8
+ end
9
+
10
+ alias_method_chain :apply, :avatax
11
+ end
@@ -0,0 +1 @@
1
+ Spree::Reimbursement.has_one :return_invoice, class_name: "SpreeAvatax::ReturnInvoice"
@@ -0,0 +1,45 @@
1
+ class SpreeAvatax::TaxRateInvalidOperation < StandardError; end
2
+
3
+ Spree::TaxRate.class_eval do
4
+ validate :avatax_there_can_be_only_one, on: :create
5
+
6
+ class << self
7
+ def match(order)
8
+ [avatax_the_one_rate]
9
+ end
10
+
11
+ def adjust(order, items)
12
+ # do nothing. we'll take care of this ourselves at different points via the various hooks we have in place
13
+ end
14
+
15
+ def store_pre_tax_amount
16
+ # do nothing. this is only for "included" and we don't support included tax.
17
+ # also, we perform calculations at a different time.
18
+ # this should never be called anyway because only TaxRate.adjust calls it, but we override it just to be safe.
19
+ end
20
+
21
+ # require exactly one tax rate. if that's not true then alert ourselves and carry on as best we can
22
+ def avatax_the_one_rate
23
+ rates = all.to_a
24
+ rates.sort_by(&:id).first
25
+ end
26
+ end
27
+
28
+ def adjust(order, item)
29
+ # We've overridden the class-level TaxRate.adjust so nothing should be calling this code
30
+ raise SpreeAvatax::TaxRateInvalidOperation.new("Spree::TaxRate#adjust should never be called when Avatax is present")
31
+ end
32
+
33
+ def compute_amount(item)
34
+ # Avatax tax adjustments should always be finalized so Spree should never attempt to call this code
35
+ raise SpreeAvatax::TaxRateInvalidOperation.new("Spree::TaxRate#compute_amount should never be called when Avatax is present")
36
+ end
37
+
38
+ private
39
+
40
+ def avatax_there_can_be_only_one
41
+ if Spree::TaxRate.count > 0
42
+ errors.add(:base, "only one tax rate is allowed and this would make #{Spree::TaxRate.count+1}")
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ require_dependency 'spree/calculator'
2
+
3
+ #
4
+ # This is a no-op calculator that just returns the existing value.
5
+ # We hook our tax calculations in SpreeAvatax::TaxComputer at the order level instead of here at the line item level
6
+ #
7
+
8
+ class SpreeAvatax::Calculator < Spree::Calculator
9
+ class DoNotUseCompute < StandardError; end
10
+ class TooManyPossibleAdjustments < StandardError; end
11
+
12
+ def self.description
13
+ Spree.t(:avatax_description)
14
+ end
15
+
16
+ def compute(computable)
17
+ raise DoNotUseCompute.new("The avatax calculator should never use #compute")
18
+ end
19
+
20
+ def compute_shipping_rate(shipping_rate)
21
+ # always return zero here. we'll take care of calculating this ourselves at different points
22
+ # via hooks into the order
23
+ 0
24
+ end
25
+
26
+ end
@@ -0,0 +1,197 @@
1
+ class SpreeAvatax::ReturnInvoice < ActiveRecord::Base
2
+ DOC_TYPE = 'ReturnInvoice'
3
+
4
+ DESTINATION_CODE = "1"
5
+
6
+ TAX_OVERRIDE_TYPE = 'TaxDate'
7
+ TAX_OVERRIDE_REASON = 'Adjustment for return'
8
+
9
+ class AvataxApiError < StandardError; end
10
+ class AlreadyCommittedError < StandardError; end
11
+ class ReturnItemResponseMissing < StandardError; end
12
+
13
+ class_attribute :avatax_logger
14
+ self.avatax_logger = Logger.new(Rails.root.join('log/avatax.log'))
15
+
16
+ belongs_to :reimbursement, class_name: "Spree::Reimbursement"
17
+
18
+ validates :reimbursement, presence: true
19
+ validates :committed, inclusion: {in: [true, false], message: "must be true or false"}
20
+ # these are values we need for the post_tax call
21
+ validates(
22
+ :doc_id, :doc_code, :doc_date, :pre_tax_total, :additional_tax_total,
23
+ presence: true
24
+ )
25
+
26
+ class << self
27
+ # Calls the Avatax API to generate a return invoice and calculate taxes on the return items.
28
+ # On failure it will raise.
29
+ # On success it will update the tax amounts on each return item and create a ReturnInvoice record.
30
+ # At this point an uncommitted return invoice has been created on Avatax's side.
31
+ # After the reimbursement completes the ".finalize" method will get called and we'll commit the
32
+ # return invoice.
33
+ def generate(reimbursement)
34
+ success_result = get_tax(reimbursement)
35
+
36
+ if reimbursement.return_invoice
37
+ if reimbursement.return_invoice.committed?
38
+ raise AlreadyCommittedError.new("Return invoice #{reimbursement.return_invoice.id} is already committed.")
39
+ else
40
+ reimbursement.return_invoice.destroy
41
+ end
42
+ end
43
+
44
+ # Array.wrap required because the XML engine the Avatax gem uses turns child nodes into
45
+ # {...} instead of [{...}] when there is only one child.
46
+ tax_lines = Array.wrap(success_result[:tax_lines][:tax_line])
47
+
48
+ reimbursement.return_items.each do |return_item|
49
+ tax_line = tax_lines.detect { |l| l[:no] == return_item.id.to_s }
50
+
51
+ if tax_line.nil?
52
+ Rails.logger.error("missing return item #{return_item.id} in avatax response: #{success_result.inspect}")
53
+ raise ReturnItemResponseMissing.new("couldn't find return item #{return_item.id} in avatax response")
54
+ end
55
+
56
+ tax = BigDecimal.new(tax_line[:tax]).abs
57
+
58
+ return_item.update_attributes!({
59
+ additional_tax_total: tax
60
+ })
61
+ end
62
+
63
+ reimbursement.create_return_invoice!({
64
+ committed: false,
65
+ doc_id: success_result[:doc_id],
66
+ doc_code: success_result[:doc_code],
67
+ doc_date: success_result[:doc_date],
68
+ pre_tax_total: success_result[:total_amount],
69
+ additional_tax_total: success_result[:total_tax],
70
+ })
71
+ end
72
+
73
+ # Commit the return invoice on Avatax's side after the reimbursement completes.
74
+ # On failure it will raise.
75
+ # On success it markes the invoice as committed.
76
+ def finalize(reimbursement)
77
+ post_tax(reimbursement.return_invoice)
78
+
79
+ reimbursement.return_invoice.update!(committed: true)
80
+ end
81
+
82
+ private
83
+
84
+ def get_tax(reimbursement)
85
+ params = get_tax_params(reimbursement)
86
+
87
+ avatax_logger.info "AVATAX_REQUEST context=get_tax reimbursement_id=#{reimbursement.id}"
88
+ avatax_logger.debug params.to_json
89
+
90
+ result = tax_svc.gettax(params)
91
+ require_success!(result, reimbursement, 'get_tax')
92
+
93
+ result
94
+ end
95
+
96
+ def post_tax(return_invoice)
97
+ params = post_tax_params(return_invoice)
98
+
99
+ avatax_logger.info "AVATAX_REQUEST context=post_tax reimbursement_id=#{return_invoice.reimbursement.id} return_invoice_id=#{return_invoice.id}"
100
+ avatax_logger.debug params.to_json
101
+
102
+ result = tax_svc.posttax(params)
103
+ require_success!(result, return_invoice.reimbursement, 'post_tax')
104
+
105
+ result
106
+ end
107
+
108
+ def require_success!(result, reimbursement, context)
109
+ if result[:result_code] == 'Success'
110
+ avatax_logger.info "AVATAX_RESPONSE context=#{context} result=success reimbursement_id=#{reimbursement.id} doc_id=#{result[:doc_id]}"
111
+ avatax_logger.debug result.to_json
112
+ else
113
+ avatax_logger.error "AVATAX_RESPONSE context=#{context} result=error reimbursement_id=#{reimbursement.id} doc_id=#{result[:doc_id]}"
114
+ avatax_logger.error result.to_json
115
+
116
+ raise AvataxApiError.new("#{context} error: #{result[:messages]}")
117
+ end
118
+ end
119
+
120
+ # see https://github.com/avadev/AvaTax-Calc-SOAP-Ruby/blob/master/GetTaxTest.rb
121
+ def get_tax_params(reimbursement)
122
+ {
123
+ doccode: reimbursement.number,
124
+ referencecode: reimbursement.order.number,
125
+ customercode: reimbursement.order.user_id,
126
+ companycode: SpreeAvatax::Config.company_code,
127
+
128
+ doctype: DOC_TYPE,
129
+ docdate: Date.today,
130
+
131
+ commit: false, # we commit after the reimbursement succeeds
132
+
133
+ # These fields let Avatax know to use a different date for calculating tax
134
+ taxoverridetype: TAX_OVERRIDE_TYPE,
135
+ reason: TAX_OVERRIDE_REASON,
136
+ taxdate: reimbursement.order.avatax_invoice_at.try(:to_date) || reimbursement.order.completed_at.to_date,
137
+
138
+ addresses: [
139
+ {
140
+ addresscode: DESTINATION_CODE,
141
+ line1: REXML::Text.normalize(reimbursement.order.ship_address.address1),
142
+ line2: REXML::Text.normalize(reimbursement.order.ship_address.address2),
143
+ city: REXML::Text.normalize(reimbursement.order.ship_address.city),
144
+ postalcode: REXML::Text.normalize(reimbursement.order.ship_address.zipcode),
145
+ },
146
+ ],
147
+
148
+ lines: get_tax_line_params(reimbursement),
149
+ }
150
+ end
151
+
152
+ def get_tax_line_params(reimbursement)
153
+ return_items = reimbursement.return_items.includes(inventory_unit: {line_item: {variant: :product}})
154
+
155
+ return_items.map do |return_item|
156
+ {
157
+ # Required Parameters
158
+ no: return_item.id,
159
+ itemcode: return_item.inventory_unit.line_item.variant.sku,
160
+ taxcode: return_item.inventory_unit.line_item.tax_category.tax_code,
161
+ qty: 1,
162
+ amount: -return_item.pre_tax_amount,
163
+ origincodeline: DESTINATION_CODE, # We don't really send the correct value here
164
+ destinationcodeline: DESTINATION_CODE,
165
+
166
+ # Best Practice Parameters
167
+ description: REXML::Text.normalize(return_item.inventory_unit.line_item.variant.product.description.to_s[0...100]),
168
+ }
169
+ end
170
+ end
171
+
172
+ # see https://github.com/avadev/AvaTax-Calc-SOAP-Ruby/blob/master/PostTaxTest.rb
173
+ def post_tax_params(return_invoice)
174
+ {
175
+ doccode: return_invoice.doc_code,
176
+ companycode: SpreeAvatax::Config.company_code,
177
+
178
+ doctype: DOC_TYPE,
179
+ docdate: return_invoice.doc_date,
180
+
181
+ commit: true,
182
+
183
+ totalamount: return_invoice.pre_tax_total,
184
+ totaltax: return_invoice.additional_tax_total,
185
+ }
186
+ end
187
+
188
+ def tax_svc
189
+ @tax_svc ||= AvaTax::TaxService.new({
190
+ username: SpreeAvatax::Config.username,
191
+ password: SpreeAvatax::Config.password,
192
+ service_url: SpreeAvatax::Config.service_url,
193
+ clientname: 'Spree::Avatax',
194
+ })
195
+ end
196
+ end
197
+ end