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