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
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