solidus_avatax 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,113 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://development.avalara.net/Tax/TaxSvc.asmx
|
6
|
+
body:
|
7
|
+
encoding: UTF-8
|
8
|
+
string: "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"
|
9
|
+
xmlns:ser=\"http://avatax.avalara.com/services\">\n <soapenv:Header>\n <wsse:Security
|
10
|
+
xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">\n
|
11
|
+
\ <wsse:UsernameToken wsu:Id=\"UsernameToken-7\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">\n
|
12
|
+
\ <wsse:Username>[Username]</wsse:Username>\n <wsse:Password
|
13
|
+
Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">[Password]</wsse:Password>\n
|
14
|
+
\ <wsu:Created>2012-03-02T23:41:44.511Z</wsu:Created>\n </wsse:UsernameToken>\n
|
15
|
+
\ </wsse:Security>\n <ser:Profile>\n <ser:Name></ser:Name>\n
|
16
|
+
\ <ser:Client>Spree::Avatax</ser:Client>\n <ser:Adapter>Avatax
|
17
|
+
SDK for Ruby2.0.2</ser:Adapter>\n <ser:Machine></ser:Machine>\n </ser:Profile>\n
|
18
|
+
\ </soapenv:Header>\n <soapenv:Body>\n <ser:GetTax>\n <ser:GetTaxRequest>\n
|
19
|
+
\ <ser:CompanyCode>[Company]</ser:CompanyCode>\n <ser:DocType>SalesInvoice</ser:DocType>\n
|
20
|
+
\ <ser:DocCode>R140311533</ser:DocCode>\n <ser:DocDate>2015-07-24</ser:DocDate>\n
|
21
|
+
\ <ser:SalespersonCode></ser:SalespersonCode>\n <ser:CustomerCode>kaylie@keebler.info</ser:CustomerCode>\n
|
22
|
+
\ <ser:CustomerUsageType></ser:CustomerUsageType>\n <ser:Discount>-0.0</ser:Discount>\n
|
23
|
+
\ <ser:PurchaseOrderNo></ser:PurchaseOrderNo>\n <ser:ExemptionNo></ser:ExemptionNo>\n
|
24
|
+
\ <ser:OriginCode></ser:OriginCode>\n <ser:DestinationCode></ser:DestinationCode>\n
|
25
|
+
\ <ser:Addresses>\n <ser:BaseAddress>\n <ser:AddressCode>1</ser:AddressCode>\n
|
26
|
+
\ <ser:Line1>YT-1300</ser:Line1>\n <ser:Line2></ser:Line2>\n
|
27
|
+
\ <ser:Line3></ser:Line3>\n <ser:City>Mos Eisley</ser:City>\n
|
28
|
+
\ <ser:Region></ser:Region>\n <ser:PostalCode>12010</ser:PostalCode>\n
|
29
|
+
\ <ser:Country></ser:Country>\n <ser:TaxRegionId>0</ser:TaxRegionId>\n
|
30
|
+
\ <ser:Latitude></ser:Latitude>\n <ser:Longitude></ser:Longitude>\n
|
31
|
+
\ </ser:BaseAddress> \n </ser:Addresses>\n <ser:Lines>\n
|
32
|
+
\ <ser:Line>\n <ser:No>Spree::LineItem-1</ser:No>\n <ser:OriginCode>1</ser:OriginCode>\n
|
33
|
+
\ <ser:DestinationCode>1</ser:DestinationCode>\n <ser:ItemCode>SKU-65</ser:ItemCode>\n
|
34
|
+
\ <ser:TaxCode>TaxCode - 659848</ser:TaxCode>\n <ser:Qty>1</ser:Qty>\n
|
35
|
+
\ <ser:Amount>19.99</ser:Amount>\n <ser:Discounted>true</ser:Discounted>\n
|
36
|
+
\ <ser:RevAcct></ser:RevAcct>\n <ser:Ref1></ser:Ref1>
|
37
|
+
\ \n <ser:Ref2></ser:Ref2>\n <ser:ExemptionNo></ser:ExemptionNo>\n
|
38
|
+
\ <ser:CustomerUsageType></ser:CustomerUsageType>\n <ser:Description>Vel
|
39
|
+
ut accusantium doloremque itaque id praesentium. Voluptatem et quia eum at.
|
40
|
+
Explicabo consequ...</ser:Description>\n <ser:TaxOverride>\n <ser:TaxOverrideType>None</ser:TaxOverrideType>\n
|
41
|
+
\ <ser:TaxAmount>0</ser:TaxAmount>\n <ser:TaxDate>1900-01-01</ser:TaxDate>\n
|
42
|
+
\ <ser:Reason></ser:Reason>\n </ser:TaxOverride>\n
|
43
|
+
\ <ser:TaxIncluded>false</ser:TaxIncluded>\n <ser:BusinessIdentificationNo></ser:BusinessIdentificationNo>\n
|
44
|
+
\ </ser:Line>\n <ser:Line>\n <ser:No>Spree::Shipment-1</ser:No>\n
|
45
|
+
\ <ser:OriginCode>1</ser:OriginCode>\n <ser:DestinationCode>1</ser:DestinationCode>\n
|
46
|
+
\ <ser:ItemCode></ser:ItemCode>\n <ser:TaxCode>FR020100</ser:TaxCode>\n
|
47
|
+
\ <ser:Qty>1</ser:Qty>\n <ser:Amount>0.0</ser:Amount>\n
|
48
|
+
\ <ser:Discounted>false</ser:Discounted>\n <ser:RevAcct></ser:RevAcct>\n
|
49
|
+
\ <ser:Ref1></ser:Ref1> \n <ser:Ref2></ser:Ref2>\n
|
50
|
+
\ <ser:ExemptionNo></ser:ExemptionNo>\n <ser:CustomerUsageType></ser:CustomerUsageType>\n
|
51
|
+
\ <ser:Description>Shipping Charge</ser:Description>\n <ser:TaxOverride>\n
|
52
|
+
\ <ser:TaxOverrideType>None</ser:TaxOverrideType>\n <ser:TaxAmount>0</ser:TaxAmount>\n
|
53
|
+
\ <ser:TaxDate>1900-01-01</ser:TaxDate>\n <ser:Reason></ser:Reason>\n
|
54
|
+
\ </ser:TaxOverride>\n <ser:TaxIncluded>false</ser:TaxIncluded>\n
|
55
|
+
\ <ser:BusinessIdentificationNo></ser:BusinessIdentificationNo>\n
|
56
|
+
\ </ser:Line> \n </ser:Lines>\n <ser:DetailLevel>Tax</ser:DetailLevel>\n
|
57
|
+
\ <ser:ReferenceCode></ser:ReferenceCode>\n <ser:HashCode>0</ser:HashCode>\n
|
58
|
+
\ <ser:LocationCode></ser:LocationCode>\n <ser:Commit>false</ser:Commit>\n
|
59
|
+
\ <ser:BatchCode></ser:BatchCode>\n <ser:TaxOverride>\n <ser:TaxOverrideType>None</ser:TaxOverrideType>\n
|
60
|
+
\ <ser:TaxAmount>0</ser:TaxAmount>\n <ser:TaxDate>1900-01-01</ser:TaxDate>\n
|
61
|
+
\ <ser:Reason></ser:Reason>\n </ser:TaxOverride>\n <ser:CurrencyCode></ser:CurrencyCode>\n
|
62
|
+
\ <ser:ServiceMode>Remote</ser:ServiceMode>\n <ser:PaymentDate>1900-01-01</ser:PaymentDate>\n
|
63
|
+
\ <ser:ExchangeRate>0</ser:ExchangeRate>\n <ser:ExchangeRateEffDate>1900-01-01</ser:ExchangeRateEffDate>\n
|
64
|
+
\ <ser:PosLaneCode></ser:PosLaneCode>\n <ser:BusinessIdentificationNo></ser:BusinessIdentificationNo>\n
|
65
|
+
\ </ser:GetTaxRequest>\n </ser:GetTax>\n </soapenv:Body>\n</soapenv:Envelope>"
|
66
|
+
headers:
|
67
|
+
Soapaction:
|
68
|
+
- '"http://avatax.avalara.com/services/GetTax"'
|
69
|
+
Content-Type:
|
70
|
+
- text/xml;charset=UTF-8
|
71
|
+
Content-Length:
|
72
|
+
- '5695'
|
73
|
+
Accept-Encoding:
|
74
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
75
|
+
Accept:
|
76
|
+
- "*/*"
|
77
|
+
User-Agent:
|
78
|
+
- Ruby
|
79
|
+
response:
|
80
|
+
status:
|
81
|
+
code: 200
|
82
|
+
message: OK
|
83
|
+
headers:
|
84
|
+
Content-Type:
|
85
|
+
- text/xml; charset=utf-8
|
86
|
+
Vary:
|
87
|
+
- Accept-Encoding
|
88
|
+
X-Powered-By:
|
89
|
+
- ASP.NET
|
90
|
+
Date:
|
91
|
+
- Fri, 24 Jul 2015 22:56:50 GMT
|
92
|
+
Content-Length:
|
93
|
+
- '1412'
|
94
|
+
Set-Cookie:
|
95
|
+
- TS017f1821=01256c426ec9d076789aa3b731ab9e0cb3fa7a55759b9cb7c29a1eb5a3cb7bab4c369a691f;
|
96
|
+
Path=/
|
97
|
+
- TS017f1821_28=0167927a8784f722bbb25bcbe6d81457a5d4e9cc570d9e99c7edf0f05fd1d5a5c882883aa2fe5ac877460393e98b81445699fb67f3;
|
98
|
+
Path=/
|
99
|
+
body:
|
100
|
+
encoding: UTF-8
|
101
|
+
string: <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body
|
102
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetTaxResponse
|
103
|
+
xmlns="http://avatax.avalara.com/services"><GetTaxResult><TransactionId>1211170742</TransactionId><ResultCode>Success</ResultCode><DocId>102167004</DocId><DocType>SalesInvoice</DocType><DocCode>R140311533</DocCode><DocDate>2015-07-24</DocDate><DocStatus>Saved</DocStatus><Reconciled>false</Reconciled><Timestamp>2015-07-24T22:56:50.527</Timestamp><TotalAmount>19.99</TotalAmount><TotalDiscount>0</TotalDiscount><TotalExemption>0</TotalExemption><TotalTaxable>19.99</TotalTaxable><TotalTax>1.6</TotalTax><TotalTaxCalculated>1.6</TotalTaxCalculated><HashCode>0</HashCode><TaxLines><TaxLine><No>Spree::LineItem-1</No><TaxCode>P0000000</TaxCode><Taxability>true</Taxability><BoundaryLevel>Zip5</BoundaryLevel><Exemption>0</Exemption><Discount>0</Discount><Taxable>19.99</Taxable><Rate>0.080000</Rate><Tax>1.6</Tax><TaxCalculated>1.6</TaxCalculated><TaxIncluded>false</TaxIncluded><TaxDetails><TaxDetail><Country>US</Country><Region>NY</Region><JurisType>State</JurisType><JurisCode>36</JurisCode><TaxType>Sales</TaxType><Base>19.99</Base><Taxable>19.99</Taxable><Rate>0.040000</Rate><Tax>0.8</Tax><TaxCalculated>0.8</TaxCalculated><NonTaxable>0</NonTaxable><Exemption>0</Exemption><JurisName>NEW
|
104
|
+
YORK</JurisName><TaxName>NY STATE TAX</TaxName><TaxAuthorityType>45</TaxAuthorityType><TaxGroup/><RateType>G</RateType><StateAssignedNo/></TaxDetail><TaxDetail><Country>US</Country><Region>NY</Region><JurisType>County</JurisType><JurisCode>057</JurisCode><TaxType>Sales</TaxType><Base>19.99</Base><Taxable>19.99</Taxable><Rate>0.040000</Rate><Tax>0.8</Tax><TaxCalculated>0.8</TaxCalculated><NonTaxable>0</NonTaxable><Exemption>0</Exemption><JurisName>MONTGOMERY</JurisName><TaxName>NY
|
105
|
+
COUNTY TAX</TaxName><TaxAuthorityType>45</TaxAuthorityType><TaxGroup/><RateType>G</RateType><StateAssignedNo>MO
|
106
|
+
2781</StateAssignedNo></TaxDetail></TaxDetails><ExemptCertId>0</ExemptCertId><TaxDate>2015-07-24</TaxDate><ReportingDate>2015-07-24</ReportingDate><AccountingMethod>Accrual</AccountingMethod></TaxLine><TaxLine><No>Spree::Shipment-1</No><TaxCode>FR020100</TaxCode><Taxability>true</Taxability><BoundaryLevel>Zip5</BoundaryLevel><Exemption>0</Exemption><Discount>0</Discount><Taxable>0</Taxable><Rate>0.080000</Rate><Tax>0</Tax><TaxCalculated>0</TaxCalculated><TaxIncluded>false</TaxIncluded><TaxDetails><TaxDetail><Country>US</Country><Region>NY</Region><JurisType>State</JurisType><JurisCode>36</JurisCode><TaxType>Sales</TaxType><Base>0</Base><Taxable>0</Taxable><Rate>0.040000</Rate><Tax>0</Tax><TaxCalculated>0</TaxCalculated><NonTaxable>0</NonTaxable><Exemption>0</Exemption><JurisName>NEW
|
107
|
+
YORK</JurisName><TaxName>NY STATE TAX</TaxName><TaxAuthorityType>45</TaxAuthorityType><TaxGroup/><RateType>G</RateType><StateAssignedNo/></TaxDetail><TaxDetail><Country>US</Country><Region>NY</Region><JurisType>County</JurisType><JurisCode>057</JurisCode><TaxType>Sales</TaxType><Base>0</Base><Taxable>0</Taxable><Rate>0.040000</Rate><Tax>0</Tax><TaxCalculated>0</TaxCalculated><NonTaxable>0</NonTaxable><Exemption>0</Exemption><JurisName>MONTGOMERY</JurisName><TaxName>NY
|
108
|
+
COUNTY TAX</TaxName><TaxAuthorityType>45</TaxAuthorityType><TaxGroup/><RateType>G</RateType><StateAssignedNo>MO
|
109
|
+
2781</StateAssignedNo></TaxDetail></TaxDetails><ExemptCertId>0</ExemptCertId><TaxDate>2015-07-24</TaxDate><ReportingDate>2015-07-24</ReportingDate><AccountingMethod>Accrual</AccountingMethod></TaxLine></TaxLines><TaxAddresses><TaxAddress><Address>YT-1300</Address><AddressCode>1</AddressCode><BoundaryLevel>2</BoundaryLevel><City>Mos
|
110
|
+
Eisley</City><Country>US</Country><PostalCode>12010</PostalCode><Region>NY</Region><TaxRegionId>2088427</TaxRegionId><JurisCode>3605700000</JurisCode><Latitude/><Longitude/><GeocodeType>ZIP5Centroid</GeocodeType><ValidateStatus>StreetNotInCity</ValidateStatus><DistanceToBoundary>0</DistanceToBoundary></TaxAddress></TaxAddresses><Locked>false</Locked><AdjustmentReason>0</AdjustmentReason><AdjustmentDescription/><Version>1</Version><TaxDate>2015-07-24</TaxDate><TaxSummary/><VolatileTaxRates>false</VolatileTaxRates></GetTaxResult></GetTaxResponse></s:Body></s:Envelope>
|
111
|
+
http_version:
|
112
|
+
recorded_at: Fri, 24 Jul 2015 22:57:02 GMT
|
113
|
+
recorded_with: VCR 2.9.3
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spree::Adjustment do
|
4
|
+
describe 'closed validation' do
|
5
|
+
context 'with a tax adjustment' do
|
6
|
+
let(:adjustment) { build(:adjustment, finalized: false, source: Spree::TaxRate.first) }
|
7
|
+
|
8
|
+
it 'should trigger' do
|
9
|
+
adjustment.valid?
|
10
|
+
expect(adjustment.errors[:finalized]).to include("Tax adjustments must always be finalized for Avatax")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'with a non-tax adjustment' do
|
15
|
+
let(:adjustment) { build(:adjustment, finalized: false, source: nil) }
|
16
|
+
|
17
|
+
it 'should not trigger' do
|
18
|
+
adjustment.valid?
|
19
|
+
expect(adjustment.errors[:finalized]).to be_empty
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spree::OrderContents do
|
4
|
+
let(:order) { create :order_with_line_items, line_items_count: 1 }
|
5
|
+
let(:order_contents) { Spree::OrderContents.new(order) }
|
6
|
+
|
7
|
+
describe 'add_with_avatax' do
|
8
|
+
let(:variant) { create :variant }
|
9
|
+
|
10
|
+
subject { order_contents.add_with_avatax(variant) }
|
11
|
+
|
12
|
+
it 'clears tax' do
|
13
|
+
expect(SpreeAvatax::SalesShared).to receive(:reset_tax_attributes).with(order)
|
14
|
+
subject
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'remove_with_avatax' do
|
19
|
+
subject { order_contents.remove_with_avatax(order.line_items.first.variant) }
|
20
|
+
|
21
|
+
it 'recomputes tax' do
|
22
|
+
expect(SpreeAvatax::SalesShared).to receive(:reset_tax_attributes).with(order)
|
23
|
+
subject
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'update_cart_with_avatax' do
|
28
|
+
subject { order_contents.update_cart_with_avatax({}) }
|
29
|
+
|
30
|
+
it 'recomputes tax' do
|
31
|
+
expect(SpreeAvatax::SalesShared).to receive(:reset_tax_attributes).with(order)
|
32
|
+
subject
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spree::Order do
|
4
|
+
subject { order }
|
5
|
+
|
6
|
+
let!(:order) { create(:order_with_line_items, line_items_count: 1) }
|
7
|
+
|
8
|
+
context 'after_save' do
|
9
|
+
context 'when something unimportant changes' do
|
10
|
+
context 'when in confirm state' do
|
11
|
+
before { order.update_columns(state: 'confirm') }
|
12
|
+
|
13
|
+
it 'does not change the order state' do
|
14
|
+
expect {
|
15
|
+
order.update_attributes!(special_instructions: 'unimportant change')
|
16
|
+
}.to_not change { order.state }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when the ship address changes' do
|
22
|
+
def change_address
|
23
|
+
order.update_attributes!(ship_address: create(:address))
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when in confirm state' do
|
27
|
+
before { order.update_columns(state: 'confirm') }
|
28
|
+
|
29
|
+
it 'sets the order state to payment' do
|
30
|
+
expect {
|
31
|
+
change_address
|
32
|
+
}.to change { order.state }.from('confirm').to('payment')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when not in confirm state' do
|
37
|
+
before { order.update_columns(state: 'address') }
|
38
|
+
|
39
|
+
it 'does not update avatax' do
|
40
|
+
expect {
|
41
|
+
change_address
|
42
|
+
}.to_not change { order.state }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when transitioning from address" do
|
49
|
+
before do
|
50
|
+
subject.update_attributes!(state: 'address')
|
51
|
+
end
|
52
|
+
|
53
|
+
it "clears tax" do
|
54
|
+
expect(SpreeAvatax::SalesShared).to receive(:reset_tax_attributes).with(order)
|
55
|
+
subject.next!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when transitioning to payment" do
|
60
|
+
before do
|
61
|
+
subject.update_attributes!(state: 'delivery')
|
62
|
+
end
|
63
|
+
|
64
|
+
it "generates the sales invoice" do
|
65
|
+
expect(SpreeAvatax::SalesInvoice).to receive(:generate).with(subject)
|
66
|
+
subject.next!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when transitioning to complete" do
|
71
|
+
before do
|
72
|
+
subject.update_attributes!(state: 'confirm')
|
73
|
+
subject.payments.create!(state: 'checkout')
|
74
|
+
end
|
75
|
+
|
76
|
+
it "commits the sales invoice" do
|
77
|
+
expect(SpreeAvatax::SalesInvoice).to receive(:commit).with(subject)
|
78
|
+
subject.complete!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#avatax_order_adjustment_total' do
|
83
|
+
context 'no adjustments exist' do
|
84
|
+
it 'returns 0' do
|
85
|
+
expect(subject.avatax_order_adjustment_total).to eq 0
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'eligible non-tax adjustments exist' do
|
90
|
+
let!(:non_eligible_promo_adjustment) do
|
91
|
+
subject.adjustments.create!(order: order, source_type: 'Spree::Promotion', eligible: false, amount: -1.0, label: 'bad promo')
|
92
|
+
end
|
93
|
+
let!(:eligible_promo_adjustment) do
|
94
|
+
subject.adjustments.create!(order: order, source_type: 'Spree::Promotion', eligible: true, amount: -10.0, label: 'good promo')
|
95
|
+
end
|
96
|
+
let!(:eligible_non_promo_adjustment) do
|
97
|
+
subject.adjustments.create!(order: order, source_type: nil, eligible: true, amount: -100.0, label: 'non-promo')
|
98
|
+
end
|
99
|
+
let!(:eligible_positive_adjustment) do
|
100
|
+
subject.adjustments.create!(order: order, source_type: nil, eligible: true, amount: 1000.0, label: 'positive non-promo')
|
101
|
+
end
|
102
|
+
let(:eligible_total) do
|
103
|
+
eligible_promo_adjustment.amount + eligible_non_promo_adjustment.amount + eligible_positive_adjustment.amount
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns the correct amount' do
|
107
|
+
expect(subject.avatax_order_adjustment_total).to eq -eligible_total
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spree::ShippingRate do
|
4
|
+
subject do
|
5
|
+
shipping_rate.calculate_tax_amount
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:shipping_rate) do
|
9
|
+
shipment.shipping_rates.create!({
|
10
|
+
tax_rate: Spree::TaxRate.first,
|
11
|
+
shipping_method: shipping_method,
|
12
|
+
cost: 10.00,
|
13
|
+
selected: true,
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:shipment) { create(:shipment) }
|
18
|
+
let(:shipping_method) { create(:shipping_method) }
|
19
|
+
|
20
|
+
it 'foo' do
|
21
|
+
expect(subject).to eq 0
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Spree::TaxRate do
|
4
|
+
let(:tax_rate) { Spree::TaxRate.first }
|
5
|
+
|
6
|
+
describe '.match' do
|
7
|
+
it { expect(Spree::TaxRate.match(:whatever)).to eq [tax_rate] }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.avatax_the_one_rate' do
|
11
|
+
it 'returns the tax rate' do
|
12
|
+
expect(Spree::TaxRate.avatax_the_one_rate).to eq tax_rate
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'tax rate count limitation' do
|
17
|
+
let(:tax_rate_2) { build(:tax_rate) }
|
18
|
+
|
19
|
+
it 'limits to a single rate' do
|
20
|
+
expect(tax_rate_2).to be_invalid
|
21
|
+
expect(tax_rate_2.errors.full_messages).to eq ['only one tax rate is allowed and this would make 2']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#adjust' do
|
26
|
+
it 'raises TaxRateInvalidOperation' do
|
27
|
+
expect {
|
28
|
+
tax_rate.adjust(nil, nil)
|
29
|
+
}.to raise_error(SpreeAvatax::TaxRateInvalidOperation)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#compute_amount' do
|
34
|
+
it 'raises TaxRateInvalidOperation' do
|
35
|
+
expect {
|
36
|
+
tax_rate.compute_amount(nil)
|
37
|
+
}.to raise_error(SpreeAvatax::TaxRateInvalidOperation)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SpreeAvatax::Calculator do
|
4
|
+
let(:calculator) { SpreeAvatax::Calculator.new }
|
5
|
+
|
6
|
+
describe '.description' do
|
7
|
+
it 'should not be nil' do
|
8
|
+
expect(SpreeAvatax::Calculator.description).to eq Spree.t(:avatax_description)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#compute' do
|
13
|
+
it 'should raise DoNotUseCompute' do
|
14
|
+
expect {
|
15
|
+
calculator.compute(nil)
|
16
|
+
}.to raise_error(SpreeAvatax::Calculator::DoNotUseCompute)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SpreeAvatax::ReturnInvoice do
|
4
|
+
|
5
|
+
describe '.generate' do
|
6
|
+
let(:reimbursement) { create(:reimbursement) }
|
7
|
+
let(:order) { reimbursement.order }
|
8
|
+
let(:return_item) { reimbursement.return_items.first }
|
9
|
+
|
10
|
+
let(:expected_gettax_params) do
|
11
|
+
{
|
12
|
+
doccode: reimbursement.number,
|
13
|
+
referencecode: reimbursement.order.number,
|
14
|
+
customercode: reimbursement.order.user_id,
|
15
|
+
companycode: SpreeAvatax::Config.company_code,
|
16
|
+
|
17
|
+
doctype: SpreeAvatax::ReturnInvoice::DOC_TYPE,
|
18
|
+
docdate: Date.today,
|
19
|
+
|
20
|
+
commit: false,
|
21
|
+
|
22
|
+
taxoverridetype: SpreeAvatax::ReturnInvoice::TAX_OVERRIDE_TYPE,
|
23
|
+
reason: SpreeAvatax::ReturnInvoice::TAX_OVERRIDE_REASON,
|
24
|
+
taxdate: expected_tax_override_date,
|
25
|
+
|
26
|
+
addresses: [
|
27
|
+
{
|
28
|
+
addresscode: SpreeAvatax::ReturnInvoice::DESTINATION_CODE,
|
29
|
+
line1: REXML::Text.normalize(reimbursement.order.ship_address.address1),
|
30
|
+
line2: REXML::Text.normalize(reimbursement.order.ship_address.address2),
|
31
|
+
city: REXML::Text.normalize(reimbursement.order.ship_address.city),
|
32
|
+
postalcode: REXML::Text.normalize(reimbursement.order.ship_address.zipcode),
|
33
|
+
},
|
34
|
+
],
|
35
|
+
lines: [
|
36
|
+
{
|
37
|
+
no: return_item.id,
|
38
|
+
itemcode: return_item.inventory_unit.line_item.variant.sku,
|
39
|
+
taxcode: return_item.inventory_unit.line_item.tax_category.tax_code,
|
40
|
+
qty: 1,
|
41
|
+
amount: -return_item.pre_tax_amount,
|
42
|
+
origincodeline: SpreeAvatax::ReturnInvoice::DESTINATION_CODE,
|
43
|
+
destinationcodeline: SpreeAvatax::ReturnInvoice::DESTINATION_CODE,
|
44
|
+
|
45
|
+
description: REXML::Text.normalize(expected_truncated_description),
|
46
|
+
},
|
47
|
+
],
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
let(:expected_tax_override_date) { Date.today }
|
52
|
+
let(:expected_truncated_description) { return_item.inventory_unit.line_item.variant.product.description[0...100] }
|
53
|
+
let(:gettax_response) { return_invoice_gettax_response(reimbursement.number, return_item.id) }
|
54
|
+
let(:gettax_response_return_item_tax_line) { Array.wrap(gettax_response[:tax_lines][:tax_line]).first }
|
55
|
+
let(:return_item_calculated_tax) do
|
56
|
+
BigDecimal.new(gettax_response_return_item_tax_line[:tax]).abs
|
57
|
+
end
|
58
|
+
|
59
|
+
before do
|
60
|
+
expect(SpreeAvatax::ReturnInvoice.send(:tax_svc))
|
61
|
+
.to receive(:gettax)
|
62
|
+
.with(expected_gettax_params)
|
63
|
+
.and_return(gettax_response)
|
64
|
+
end
|
65
|
+
|
66
|
+
subject do
|
67
|
+
SpreeAvatax::ReturnInvoice.generate(reimbursement)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'creates a return invoice' do
|
71
|
+
expect {
|
72
|
+
subject
|
73
|
+
}.to change { SpreeAvatax::ReturnInvoice.count }.by(1)
|
74
|
+
expect(reimbursement.return_invoice).to eq SpreeAvatax::ReturnInvoice.last
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'persists the results to the return items' do
|
78
|
+
expect {
|
79
|
+
subject
|
80
|
+
}.to change { return_item.reload.additional_tax_total }.from(0).to(return_item_calculated_tax)
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when the response for a return item is missing' do
|
84
|
+
before do
|
85
|
+
gettax_response_return_item_tax_line[:no] = (return_item.id + 1).to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'raises ReturnItemResponseMissing' do
|
89
|
+
expect {
|
90
|
+
subject
|
91
|
+
}.to raise_error(SpreeAvatax::ReturnInvoice::ReturnItemResponseMissing)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when an invoice already exists' do
|
96
|
+
let!(:previous_return_invoice) { create(:return_invoice, reimbursement: reimbursement) }
|
97
|
+
|
98
|
+
it 'deletes the previous invoice' do
|
99
|
+
subject
|
100
|
+
expect(SpreeAvatax::ReturnInvoice.find_by(id: previous_return_invoice.id)).to be_nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'when avatax_invoice_at is present' do
|
105
|
+
let(:expected_tax_override_date) { 2.days.ago.to_date }
|
106
|
+
|
107
|
+
before do
|
108
|
+
reimbursement.order.update_attributes!(avatax_invoice_at: expected_tax_override_date)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'succeeds' do
|
112
|
+
subject # method expectation will fail if date isn't right
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'when the description is too long' do
|
117
|
+
let(:description) { 'a'*1000 }
|
118
|
+
let(:expected_truncated_description) { 'a'*100 }
|
119
|
+
|
120
|
+
before do
|
121
|
+
return_item.inventory_unit.line_item.variant.product.update!(description: description)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'succeeds' do
|
125
|
+
subject # method expectation will fail if date isn't right
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'user input contains XML characters' do
|
130
|
+
let(:line1) { "<&line1>" }
|
131
|
+
let(:line2) { "<&line2>" }
|
132
|
+
let(:city) { "<&city>" }
|
133
|
+
let(:zipcode) { "<12345>" }
|
134
|
+
let(:description) { "A description <wi>&/th xml characters" }
|
135
|
+
|
136
|
+
before(:each) do
|
137
|
+
ship_address = order.ship_address
|
138
|
+
ship_address.update_columns(address1: line1, address2: line2, city: city, zipcode: zipcode)
|
139
|
+
return_item.inventory_unit.line_item.variant.product.update_columns(description: description)
|
140
|
+
end
|
141
|
+
|
142
|
+
let(:expected_gettax_params) do
|
143
|
+
super().tap do |params|
|
144
|
+
params[:addresses].first.merge!(line1: REXML::Text.normalize(line1), line2: REXML::Text.normalize(line2), city: REXML::Text.normalize(city), postalcode: REXML::Text.normalize(zipcode))
|
145
|
+
params[:lines][0][:description] = REXML::Text.normalize(description)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'succeeds' do
|
150
|
+
subject
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe '.finalize' do
|
156
|
+
let(:return_invoice) { create(:return_invoice) }
|
157
|
+
|
158
|
+
let(:expected_posttax_params) do
|
159
|
+
{
|
160
|
+
doccode: return_invoice.doc_code,
|
161
|
+
companycode: SpreeAvatax::Config.company_code,
|
162
|
+
|
163
|
+
doctype: SpreeAvatax::ReturnInvoice::DOC_TYPE,
|
164
|
+
docdate: return_invoice.doc_date,
|
165
|
+
|
166
|
+
commit: true,
|
167
|
+
|
168
|
+
totalamount: return_invoice.pre_tax_total,
|
169
|
+
totaltax: return_invoice.additional_tax_total,
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
before do
|
174
|
+
expect(SpreeAvatax::ReturnInvoice.send(:tax_svc))
|
175
|
+
.to receive(:posttax)
|
176
|
+
.with(expected_posttax_params)
|
177
|
+
.and_return(
|
178
|
+
return_invoice_posttax_response
|
179
|
+
)
|
180
|
+
end
|
181
|
+
|
182
|
+
subject do
|
183
|
+
SpreeAvatax::ReturnInvoice.finalize(return_invoice.reimbursement)
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'marks the return invoice as committed' do
|
187
|
+
expect {
|
188
|
+
subject
|
189
|
+
}.to change { return_invoice.reload.committed? }.from(false).to(true)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|