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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +15 -0
- data/LICENSE +26 -0
- data/README.md +71 -0
- data/Rakefile +21 -0
- data/app/assets/javascripts/spree/frontend/solidus_avatax.js +1 -0
- data/app/assets/stylesheets/spree/frontend/solidus_avatax.css +1 -0
- data/app/models/spree/adjustment_decorator.rb +21 -0
- data/app/models/spree/order_contents_decorator.rb +26 -0
- data/app/models/spree/order_decorator.rb +43 -0
- data/app/models/spree/promotion_handler/coupon_decorator.rb +11 -0
- data/app/models/spree/reimbursement_decorator.rb +1 -0
- data/app/models/spree/tax_rate_decorator.rb +45 -0
- data/app/models/spree_avatax/calculator.rb +26 -0
- data/app/models/spree_avatax/return_invoice.rb +197 -0
- data/app/models/spree_avatax/sales_invoice.rb +157 -0
- data/app/models/spree_avatax/sales_shared.rb +211 -0
- data/app/models/spree_avatax/shared.rb +53 -0
- data/app/models/spree_avatax/short_ship_return_invoice.rb +133 -0
- data/app/models/spree_avatax/short_ship_return_invoice_inventory_unit.rb +11 -0
- data/bin/rails +7 -0
- data/circle.yml +6 -0
- data/config/locales/en.yml +5 -0
- data/db/migrate/20140122165618_add_avatax_response_at_to_orders.rb +5 -0
- data/db/migrate/20140214153139_add_avatax_invoice_at_to_orders.rb +5 -0
- data/db/migrate/20140617222244_close_all_tax_adjustments.rb +5 -0
- data/db/migrate/20140701144237_update_avatax_calculator_type.rb +17 -0
- data/db/migrate/20140801132302_create_spree_avatax_return_invoices.rb +19 -0
- data/db/migrate/20140903135132_create_spree_avatax_sales_orders.rb +15 -0
- data/db/migrate/20140903135357_create_spree_avatax_sales_invoices.rb +19 -0
- data/db/migrate/20140904171341_generate_uncommitted_sales_invoices.rb +31 -0
- data/db/migrate/20140911214414_add_transaction_id_to_sales_orders_and_sales_invoices.rb +6 -0
- data/db/migrate/20140911215422_add_canceled_at_and_cancel_transaction_id_to_sales_invoices.rb +6 -0
- data/db/migrate/20150427154942_create_spree_avatax_short_ship_return_invoices.rb +44 -0
- data/db/migrate/20150518172627_fix_avatax_short_ship_index.rb +30 -0
- data/lib/generators/solidus_avatax/install/install_generator.rb +31 -0
- data/lib/generators/solidus_avatax/install/templates/config/initializers/avatax.rb +18 -0
- data/lib/solidus_avatax.rb +4 -0
- data/lib/spree_avatax/config.rb +16 -0
- data/lib/spree_avatax/engine.rb +29 -0
- data/lib/spree_avatax/factories.rb +25 -0
- data/lib/tasks/commit_backfill.rake +119 -0
- data/lib/tasks/sales_invoice_backfill.rake +43 -0
- data/solidus_avatax.gemspec +34 -0
- data/spec/features/store_credits_spec.rb +92 -0
- data/spec/features/tax_calculation_spec.rb +75 -0
- data/spec/fixtures/vcr_cassettes/sales_invoice_gettax_with_discounts.yml +136 -0
- data/spec/fixtures/vcr_cassettes/sales_invoice_gettax_without_discounts.yml +136 -0
- data/spec/fixtures/vcr_cassettes/taxes_with_store_credits.yml +113 -0
- data/spec/models/spree/adjustment_spec.rb +23 -0
- data/spec/models/spree/order_contents_spec.rb +35 -0
- data/spec/models/spree/order_spec.rb +111 -0
- data/spec/models/spree/shipping_rate_spec.rb +23 -0
- data/spec/models/spree/tax_rate_spec.rb +40 -0
- data/spec/models/spree_avatax/calculator.rb +20 -0
- data/spec/models/spree_avatax/return_invoice_spec.rb +193 -0
- data/spec/models/spree_avatax/sales_invoice_spec.rb +491 -0
- data/spec/models/spree_avatax/sales_shared_spec.rb +47 -0
- data/spec/models/spree_avatax/shared_spec.rb +89 -0
- data/spec/models/spree_avatax/short_ship_return_invoice_spec.rb +181 -0
- data/spec/spec_helper.rb +85 -0
- data/spec/support/return_invoice_soap_responses.rb +117 -0
- data/spec/support/sales_invoice_soap_responses.rb +259 -0
- data/spec/support/short_ship_return_invoice_soap_responses.rb +178 -0
- data/spec/support/zone_support.rb +6 -0
- 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
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 @@
|
|
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
|