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