workarea-avatax 4.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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  4. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.github/workflows/ci.yml +66 -0
  7. data/.gitignore +18 -0
  8. data/.rubocop.yml +8 -0
  9. data/CHANGELOG.md +123 -0
  10. data/CODE_OF_CONDUCT.md +3 -0
  11. data/CONTRIBUTING.md +3 -0
  12. data/Gemfile +10 -0
  13. data/LICENSE.md +3 -0
  14. data/README.md +57 -0
  15. data/Rakefile +50 -0
  16. data/app/models/workarea/avatax/usage_type.rb +17 -0
  17. data/app/models/workarea/pricing/calculators/avalara_tax_calculator.rb +105 -0
  18. data/app/models/workarea/user.decorator +7 -0
  19. data/app/seeds/workarea/avatax_seeds.rb +44 -0
  20. data/app/view_models/workarea/admin/user_view_model.decorator +21 -0
  21. data/app/view_models/workarea/storefront/cart_view_model.decorator +7 -0
  22. data/app/views/workarea/admin/users/_avatax_properties_fields.html.haml +5 -0
  23. data/app/workers/workarea/avatax/tax_invoice_worker.rb +29 -0
  24. data/bin/rails +17 -0
  25. data/config/initializers/appends.rb +4 -0
  26. data/config/initializers/avatax.rb +14 -0
  27. data/config/initializers/seeds.rb +1 -0
  28. data/config/locales/en.yml +6 -0
  29. data/lib/avatax/api.decorator +52 -0
  30. data/lib/workarea/avatax/bogus_gateway/bogus_create_transaction.rb +187 -0
  31. data/lib/workarea/avatax/bogus_gateway.rb +34 -0
  32. data/lib/workarea/avatax/engine.rb +36 -0
  33. data/lib/workarea/avatax/tax_request/addresses.rb +35 -0
  34. data/lib/workarea/avatax/tax_request/line_item.rb +47 -0
  35. data/lib/workarea/avatax/tax_request/order_line_item.rb +64 -0
  36. data/lib/workarea/avatax/tax_request/order_lines.rb +70 -0
  37. data/lib/workarea/avatax/tax_request/response.rb +49 -0
  38. data/lib/workarea/avatax/tax_request/shipping_line_item.rb +34 -0
  39. data/lib/workarea/avatax/tax_request.rb +77 -0
  40. data/lib/workarea/avatax/version.rb +5 -0
  41. data/lib/workarea/avatax.rb +71 -0
  42. data/test/dummy/Rakefile +6 -0
  43. data/test/dummy/bin/bundle +3 -0
  44. data/test/dummy/bin/rails +4 -0
  45. data/test/dummy/bin/rake +4 -0
  46. data/test/dummy/bin/setup +34 -0
  47. data/test/dummy/bin/update +29 -0
  48. data/test/dummy/config/application.rb +23 -0
  49. data/test/dummy/config/boot.rb +5 -0
  50. data/test/dummy/config/cable.yml +9 -0
  51. data/test/dummy/config/environment.rb +5 -0
  52. data/test/dummy/config/environments/development.rb +54 -0
  53. data/test/dummy/config/environments/production.rb +86 -0
  54. data/test/dummy/config/environments/test.rb +44 -0
  55. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  56. data/test/dummy/config/initializers/assets.rb +11 -0
  57. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  58. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  59. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  60. data/test/dummy/config/initializers/inflections.rb +16 -0
  61. data/test/dummy/config/initializers/mime_types.rb +4 -0
  62. data/test/dummy/config/initializers/new_framework_defaults.rb +23 -0
  63. data/test/dummy/config/initializers/workarea.rb +5 -0
  64. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/test/dummy/config/locales/en.yml +23 -0
  66. data/test/dummy/config/puma.rb +47 -0
  67. data/test/dummy/config/routes.rb +5 -0
  68. data/test/dummy/config/secrets.yml +22 -0
  69. data/test/dummy/config/spring.rb +6 -0
  70. data/test/dummy/config.ru +5 -0
  71. data/test/dummy/db/seeds.rb +2 -0
  72. data/test/dummy/lib/assets/.keep +0 -0
  73. data/test/dummy/log/.keep +0 -0
  74. data/test/dummy/public/404.html +67 -0
  75. data/test/dummy/public/422.html +67 -0
  76. data/test/dummy/public/500.html +66 -0
  77. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  78. data/test/dummy/public/apple-touch-icon.png +0 -0
  79. data/test/dummy/public/favicon.ico +0 -0
  80. data/test/factories/workarea/avatax_factories.rb +142 -0
  81. data/test/lib/workarea/avatax/tax_request/order_line_items_test.rb +114 -0
  82. data/test/lib/workarea/avatax/tax_request/response_test.rb +42 -0
  83. data/test/lib/workarea/avatax/tax_request_test.rb +51 -0
  84. data/test/lib/workarea/avatax_test.rb +28 -0
  85. data/test/models/workarea/avatax/usage_type_test.rb +13 -0
  86. data/test/models/workarea/pricing/calculators/avalara_tax_calculator_test.rb +113 -0
  87. data/test/support/avatax_test_settings.rb +9 -0
  88. data/test/system/workarea/storefront/avatax_system_test.rb +22 -0
  89. data/test/test_helper.rb +20 -0
  90. data/test/vcr_cassettes/succesful_avatax_create_transaction.yml +60 -0
  91. data/test/vcr_cassettes/succesful_avatax_create_transaction_multiple_shippings.yml +76 -0
  92. data/workarea-avatax.gemspec +20 -0
  93. metadata +176 -0
@@ -0,0 +1,29 @@
1
+ module Workarea
2
+ module Avatax
3
+ class TaxInvoiceWorker
4
+ include Sidekiq::Worker
5
+ include Sidekiq::CallbacksWorker
6
+
7
+ sidekiq_options(
8
+ enqueue_on: {
9
+ Workarea::Order => :place,
10
+ ignore_if: -> { Avatax.config.order_handling == :none }
11
+ }
12
+ )
13
+
14
+ def perform(order_id)
15
+ order = Workarea::Order.find(order_id)
16
+ shippings = Workarea::Shipping.where(order_id: order.id).to_a
17
+
18
+ response = Avatax::TaxRequest.new(
19
+ order: order,
20
+ shippings: shippings,
21
+ type: "SalesInvoice",
22
+ commit: Avatax.commit?
23
+ ).response
24
+
25
+ raise "Failed to invoice tax for order: #{order.id}" unless response.success?
26
+ end
27
+ end
28
+ end
29
+ end
data/bin/rails ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path("../..", __FILE__)
5
+ ENGINE_PATH = File.expand_path("../../lib/workarea/avatax/engine", __FILE__)
6
+
7
+ # Set up gems listed in the Gemfile.
8
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
9
+ require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
10
+
11
+ require "action_controller/railtie"
12
+ require "action_view/railtie"
13
+ require "action_mailer/railtie"
14
+ require "rails/test_unit/railtie"
15
+ require "sprockets/railtie"
16
+
17
+ require "rails/engine/commands"
@@ -0,0 +1,4 @@
1
+ Workarea.append_partials(
2
+ "admin.user_properties",
3
+ "workarea/admin/users/avatax_properties_fields"
4
+ )
@@ -0,0 +1,14 @@
1
+ Workarea.config.pricing_calculators.swap(
2
+ "Workarea::Pricing::Calculators::TaxCalculator",
3
+ "Workarea::Pricing::Calculators::AvalaraTaxCalculator"
4
+ )
5
+
6
+ if Rails.application.secrets.avatax.present?
7
+ avatax_sercrets = Rails.application.secrets.avatax.deep_symbolize_keys
8
+ AvaTax.configure do |config|
9
+ config.username = avatax_sercrets[:username]
10
+ config.password = avatax_sercrets[:password]
11
+ end
12
+ end
13
+
14
+ Workarea::Avatax.auto_configure_gateway
@@ -0,0 +1 @@
1
+ Workarea.config.seeds.append("Workarea::AvataxSeeds")
@@ -0,0 +1,6 @@
1
+ en:
2
+ workarea:
3
+ admin:
4
+ users:
5
+ edit:
6
+ avatax_entity_use_code: AvaTax Entity/Use Code
@@ -0,0 +1,52 @@
1
+ module AvaTax
2
+ decorate API, with: :workarea_avatax do
3
+ def request(method, path, model, options = {})
4
+ timeout = options.delete(:timeout)
5
+
6
+ connection.send(method) do |request|
7
+ request.options.timeout = timeout if timeout
8
+
9
+ case method
10
+ when :get, :delete
11
+ request.url("#{URI.encode(path)}?#{URI.encode_www_form(options)}")
12
+ when :post, :put
13
+ request.path = ("#{URI.encode(path)}?#{URI.encode_www_form(options)}")
14
+ request.headers["Content-Type"] = "application/json"
15
+ request.body = model.to_json unless model.empty?
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ # NOTE: Remove this decoration when
23
+ # https://github.com/avadev/AvaTax-REST-V2-Ruby-SDK/issues/16 is
24
+ # resolved and merged in.
25
+ def connection
26
+ client_id = "#{app_name};#{app_version};RubySdk;#{AvaTax::VERSION.dup};#{machine_name}"
27
+ options = {
28
+ :headers =>
29
+ {
30
+ 'Accept' => "application/json; charset=utf-8",
31
+ 'User-Agent' => user_agent,
32
+ 'X-Avalara-Client' => client_id
33
+ },
34
+ :url => endpoint,
35
+ :proxy => proxy,
36
+ }.merge(connection_options)
37
+
38
+ Faraday.new(options) do |faraday|
39
+ faraday.response :oj, content_type: /\bjson$/
40
+ faraday.basic_auth(username, password)
41
+
42
+ if logger
43
+ faraday.response :logger do |logger|
44
+ logger.filter(/(Authorization\:\ \"Basic\ )(\w+)\=/, '\1[REMOVED]')
45
+ end
46
+ end
47
+
48
+ faraday.adapter Faraday.default_adapter
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,187 @@
1
+ module Workarea
2
+ module Avatax
3
+ class BogusGateway::BogusCreateTransaction < BogusGateway::BogusResponse
4
+ attr_reader :request_body
5
+
6
+ def initialize(request_body)
7
+ @request_body = request_body
8
+ end
9
+
10
+ def response
11
+ env = Faraday::Env.new(
12
+ :post,
13
+ response_body,
14
+ URI("https://sandbox-rest.avatax.com/api/v2/transactions/create"),
15
+ Faraday::RequestOptions.new,
16
+ request_headers,
17
+ Faraday::SSLOptions.new(true),
18
+ nil,
19
+ nil,
20
+ nil,
21
+ response_headers,
22
+ 201,
23
+ "Created"
24
+ )
25
+ Faraday::Response.new(env)
26
+ end
27
+
28
+ private
29
+
30
+ def response_body
31
+ {
32
+ "id" => 0,
33
+ "code" => "0B41518F49",
34
+ "companyId" => 0,
35
+ "status" => "Temporary",
36
+ "type" => "SalesOrder",
37
+ "totalTax" => lines.sum { |line| line["tax"] },
38
+ "lines" => lines
39
+ }
40
+ end
41
+
42
+ def lines
43
+ @lines ||= request_body[:lines].map.with_index(1) do |line, number|
44
+ if Rails.env.test?
45
+ test_like_line(line, number)
46
+ else
47
+ api_like_line(line, number)
48
+ end
49
+ end.compact
50
+ end
51
+
52
+ def api_like_line(line, number)
53
+ {
54
+ "id" => 0,
55
+ "transactionId" => 0,
56
+ "lineNumber" => number.to_s,
57
+ "description" => "",
58
+ "discountAmount" => 0.0,
59
+ "exemptAmount" => 0.0,
60
+ "exemptCertId" => 0,
61
+ "isItemTaxable" => true,
62
+ "itemCode" => "",
63
+ "lineAmount" => line[:amount],
64
+ "quantity" => 0.0,
65
+ "reportingDate" => "2017-06-08",
66
+ "tax" => (line[:amount].to_m * 0.08).to_s,
67
+ "taxableAmount" => line[:amount],
68
+ "taxCalculated" => (line[:amount].to_m * 0.08).to_s,
69
+ "taxCode" => "P0000000",
70
+ "taxDate" => "2017-06-08",
71
+ "taxIncluded" => false,
72
+ "details" => [
73
+ {
74
+ "id" => 0,
75
+ "transactionLineId" => 0,
76
+ "transactionId" => 0,
77
+ "country" => "US",
78
+ "region" => "PA",
79
+ "exemptAmount" => 0.0,
80
+ "jurisCode" => "42",
81
+ "jurisName" => "PENNSYLVANIA",
82
+ "stateAssignedNo" => "",
83
+ "jurisType" => "STA",
84
+ "nonTaxableAmount" => 0.0,
85
+ "rate" => 0.06,
86
+ "tax" => (line[:amount].to_m * 0.06).to_s,
87
+ "taxableAmount" => line[:amount],
88
+ "taxType" => "Sales",
89
+ "taxName" => "PA STATE TAX",
90
+ "taxAuthorityTypeId" => 45,
91
+ "taxCalculated" => (line[:amount].to_m * 0.06).to_s,
92
+ "rateType" => "General",
93
+ "rateTypeCode" => "G"
94
+ },
95
+ {
96
+ "id" => 0,
97
+ "transactionLineId" => 0,
98
+ "transactionId" => 0,
99
+ "country" => "US",
100
+ "region" => "PA",
101
+ "exemptAmount" => 0.0,
102
+ "jurisCode" => "101",
103
+ "jurisName" => "PHILADELPHIA",
104
+ "stateAssignedNo" => "51",
105
+ "jurisType" => "CTY",
106
+ "nonTaxableAmount" => 0.0,
107
+ "rate" => 0.02,
108
+ "tax" => (line[:amount].to_m * 0.02).to_s,
109
+ "taxableAmount" => line[:amount],
110
+ "taxType" => "Sales",
111
+ "taxName" => "PA COUNTY TAX",
112
+ "taxAuthorityTypeId" => 45,
113
+ "taxCalculated" => (line[:amount].to_m * 0.02).to_s,
114
+ "rateType" => "General",
115
+ "rateTypeCode" => "G"
116
+ }
117
+ ]
118
+ }
119
+ end
120
+
121
+ def test_like_line(line, number)
122
+ request_address = request_body[:addresses][:shipTo] ||
123
+ request_body[:addresses][:singleLocation]
124
+
125
+ address =
126
+ if request_address.blank?
127
+ Workarea::Address.new
128
+ else
129
+ # need to underscore keys and duck a workarea::address
130
+ Hashie::Mash.new(Hash[request_address.map { |k, v| [k.to_s.underscore, v] }])
131
+ end
132
+
133
+ tax_rate = Workarea::Tax.find_rate(
134
+ line[:taxCode],
135
+ line[:amount].to_m,
136
+ address
137
+ )
138
+
139
+ return unless tax_rate.present?
140
+ {
141
+ "id" => 0,
142
+ "transactionId" => 0,
143
+ "lineNumber" => number.to_s,
144
+ "description" => "",
145
+ "discountAmount" => 0.0,
146
+ "exemptAmount" => 0.0,
147
+ "exemptCertId" => 0,
148
+ "isItemTaxable" => true,
149
+ "itemCode" => "",
150
+ "lineAmount" => line[:amount],
151
+ "quantity" => 0.0,
152
+ "reportingDate" => "2017-06-08",
153
+ "tax" => (line[:amount].to_m * tax_rate.percentage).to_s,
154
+ "taxableAmount" => line[:amount],
155
+ "taxCalculated" => (line[:amount].to_m * tax_rate.percentage).to_s,
156
+ "taxCode" => "P0000000",
157
+ "taxDate" => "2017-06-08",
158
+ "taxIncluded" => false,
159
+ "details" => [
160
+ {
161
+ "id" => 0,
162
+ "transactionLineId" => 0,
163
+ "transactionId" => 0,
164
+ "country" => "US",
165
+ "region" => "PA",
166
+ "exemptAmount" => 0.0,
167
+ "jurisCode" => "42",
168
+ "jurisName" => "PENNSYLVANIA",
169
+ "stateAssignedNo" => "",
170
+ "jurisType" => "STA",
171
+ "nonTaxableAmount" => 0.0,
172
+ "rate" => tax_rate.percentage,
173
+ "tax" => (line[:amount].to_m * tax_rate.percentage).to_s,
174
+ "taxableAmount" => line[:amount],
175
+ "taxType" => "Sales",
176
+ "taxName" => "PA STATE TAX",
177
+ "taxAuthorityTypeId" => 45,
178
+ "taxCalculated" => (line[:amount].to_m * tax_rate.percentage).to_s,
179
+ "rateType" => "General",
180
+ "rateTypeCode" => "G"
181
+ }
182
+ ]
183
+ }
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,34 @@
1
+ module Workarea
2
+ module Avatax
3
+ class BogusGateway
4
+ class BogusResponse
5
+ private
6
+
7
+ def request_headers
8
+ {
9
+ "Accept" => "application/json; charset=utf-8",
10
+ "User-Agent" => "AvaTax Ruby Gem 17.5.0",
11
+ "Content-Type" => "application/json",
12
+ "Authorization" => "Basic ZXBpZ2VvbkB3ZWJsaW5jLmNvbTo2NDhCMEE5ODUx"
13
+ }
14
+ end
15
+
16
+ def response_headers
17
+ {
18
+ "transfer-encoding" => "chunked",
19
+ "content-type" => "application/json; charset=utf-8",
20
+ "server" => "Kestrel",
21
+ "serverduration" => "00:00:00.0937524",
22
+ "databasecalls" => "4",
23
+ "databaseduration" => "00:00:00.0156261",
24
+ "date" => "Thu, 08 Jun 2017 19:04:38 GMT"
25
+ }
26
+ end
27
+ end
28
+
29
+ def create_transaction(body, options = {})
30
+ BogusCreateTransaction.new(body).response
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ module Workarea
2
+ module Avatax
3
+ class Engine < ::Rails::Engine
4
+ include Workarea::Plugin
5
+
6
+ isolate_namespace Workarea::Avatax
7
+
8
+ #Workarea::Avatax.configure do |config|
9
+ # config.valid_service_urls = [
10
+ # "https://development.avalara.net", # development
11
+ # "https://avatax.avalara.net" # production
12
+ # ]
13
+ #end
14
+
15
+ initializer "workarea.avatax.templates" do
16
+ #Plugin.append_partials(
17
+ # "admin.store_menu",
18
+ # "workarea/admin/menus/avatax_settings"
19
+ #)
20
+
21
+ #Plugin.append_partials(
22
+ # "admin.user_permissions",
23
+ # "workarea/admin/users/avatax_settings"
24
+ #)
25
+ #Plugin.append_partials(
26
+ # "admin.user_properties",
27
+ # "workarea/admin/users/user_properties_fields"
28
+ #)
29
+ end
30
+
31
+ #initializer "workarea.avatax.listeners" do
32
+ # Workarea::Publisher.add_listener(Workarea::Avatax::InvoiceListener)
33
+ #end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ module Workarea
2
+ module Avatax
3
+ class TaxRequest::Addresses
4
+ attr_reader :tax_request
5
+
6
+ def initialize(tax_request:)
7
+ @tax_request = tax_request
8
+ end
9
+
10
+ def hash
11
+ return { singleLocation: ship_from } unless ship_to.present?
12
+
13
+ { shipFrom: ship_from, shipTo: ship_to }
14
+ end
15
+
16
+ private
17
+
18
+ def ship_from
19
+ Avatax.config.distribution_center
20
+ end
21
+
22
+ def ship_to
23
+ return unless address = tax_request.shippings.first.try(:address)
24
+ {
25
+ line1: address.street,
26
+ line2: address.street_2,
27
+ city: address.city,
28
+ region: address.region,
29
+ country: address.country.alpha2,
30
+ postalCode: address.postal_code
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ module Workarea
2
+ module Avatax
3
+ class TaxRequest::LineItem
4
+ attr_accessor :line_number
5
+
6
+ def initialize(**)
7
+ @line_number = 0
8
+ end
9
+
10
+ def hash
11
+ {
12
+ quantity: quantity,
13
+ amount: amount.to_s,
14
+ itemCode: item_code,
15
+ taxCode: tax_code,
16
+ description: description,
17
+ number: line_number,
18
+ addresses: addresses
19
+ }.compact
20
+ end
21
+
22
+ private
23
+
24
+ def quantity
25
+ raise NotImplementedError, "#{self.class.name} must implement #quantity"
26
+ end
27
+
28
+ def amount
29
+ raise NotImplementedError, "#{self.class.name} must implement #amount"
30
+ end
31
+
32
+ def item_code
33
+ raise NotImplementedError, "#{self.class.name} must implement #item_code"
34
+ end
35
+
36
+ def tax_code
37
+ raise NotImplementedError, "#{self.class.name} must implement #tax_code"
38
+ end
39
+
40
+ def description
41
+ raise NotImplementedError, "#{self.class.name} must implement #description_code"
42
+ end
43
+
44
+ def addresses; end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,64 @@
1
+ module Workarea
2
+ module Avatax
3
+ class TaxRequest::OrderLineItem < TaxRequest::LineItem
4
+ attr_reader :order_item, :adjustment, :shipping
5
+
6
+ def initialize(order_item:, adjustment:, adjustment_set:, quantity: nil, shipping: nil)
7
+ @order_item = order_item
8
+ @adjustment = adjustment
9
+ @adjustment_set = adjustment_set
10
+ @quantity = quantity
11
+ @shipping = shipping
12
+ end
13
+
14
+ private
15
+
16
+ def addresses
17
+ return unless address = shipping&.address
18
+
19
+ {
20
+ shipTo: {
21
+ line1: address.street,
22
+ line2: address.street_2,
23
+ city: address.city,
24
+ region: address.region,
25
+ country: address.country.alpha2,
26
+ postalCode: address.postal_code
27
+ }
28
+ }
29
+ end
30
+
31
+ def quantity
32
+ @quantity || adjustment.quantity
33
+ end
34
+
35
+ def amount
36
+ total = @adjustment_set.taxable_share_for adjustment
37
+ if shipping.present? && shipping.partial?
38
+ total *= quantity / order_item.quantity.to_f
39
+ end
40
+ total
41
+ end
42
+
43
+ def item_code
44
+ order_item.sku
45
+ end
46
+
47
+ def tax_code
48
+ adjustment.data["tax_code"]
49
+ end
50
+
51
+ def product
52
+ @product ||= Mongoid::Factory.from_db(
53
+ Catalog::Product,
54
+ order_item.product_attributes
55
+ )
56
+ end
57
+
58
+ def description
59
+ return if order_item.product_attributes.empty?
60
+ product.name
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,70 @@
1
+ module Workarea
2
+ module Avatax
3
+ class TaxRequest::OrderLines
4
+ attr_reader :order, :shippings
5
+
6
+ def initialize(order, shippings)
7
+ @order = order
8
+ @shippings = shippings
9
+ end
10
+
11
+ def order_line_items
12
+ @order_line_items ||=
13
+ if shippings.any?(&:partial?)
14
+ split_shipping_lines
15
+ else
16
+ single_shipping_lines
17
+ end.map.with_index(1) { |line, index| line.tap { |li| li.line_number = index } }
18
+ end
19
+
20
+ def shipping_line_items
21
+ @shipping_line_item ||= shippings.map { |shipping| TaxRequest::ShippingLineItem.new(shipping: shipping) }
22
+ end
23
+
24
+ def lines
25
+ (order_line_items + shipping_line_items).map.with_index(1) do |line_item, index|
26
+ line_item.tap { |li| li.line_number = index }
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def order_price_adjustments
33
+ order.price_adjustments
34
+ end
35
+
36
+ def grouped_order_price_adjustments
37
+ order_price_adjustments.grouped_by_parent
38
+ end
39
+
40
+ def single_shipping_lines
41
+ grouped_order_price_adjustments.flat_map do |item, set|
42
+ set.map do |adjustment|
43
+ next unless adjustment.data["tax_code"].present?
44
+
45
+ TaxRequest::OrderLineItem.new(order_item: item, adjustment: adjustment, adjustment_set: set)
46
+ end
47
+ end.compact
48
+ end
49
+
50
+ def split_shipping_lines
51
+ shippings.flat_map do |shipping|
52
+ shipping.quantities.flat_map do |order_item_id, quantity|
53
+ item = order.items.detect { |oi| oi.id.to_s == order_item_id }
54
+ adjustment_set = grouped_order_price_adjustments[item]
55
+
56
+ adjustment_set.map do |adjustment|
57
+ TaxRequest::OrderLineItem.new(
58
+ order_item: item,
59
+ adjustment: adjustment,
60
+ adjustment_set: adjustment_set,
61
+ quantity: quantity,
62
+ shipping: shipping
63
+ )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,49 @@
1
+ module Workarea
2
+ module Avatax
3
+ class TaxRequest::Response
4
+ attr_reader :response, :request_order_line_items, :request_shipping_line_items
5
+ delegate :success?, to: :response
6
+
7
+ def initialize(response:, request_order_line_items:, request_shipping_line_items:)
8
+ @response = response
9
+ @request_order_line_items = request_order_line_items
10
+ @request_shipping_line_items = request_shipping_line_items
11
+ end
12
+
13
+ def body
14
+ @body ||= Hashie::Mash.new response.body
15
+ end
16
+
17
+ def tax_line_for_adjustment(price_adjustment, shipping: nil)
18
+ return unless success?
19
+
20
+ line_number =
21
+ if shipping&.partial?
22
+ request_order_line_items
23
+ .detect { |line_item| line_item.adjustment == price_adjustment && line_item.shipping == shipping }
24
+ .try(:line_number)
25
+ else
26
+ request_order_line_items
27
+ .detect { |line_item| line_item.adjustment == price_adjustment }
28
+ .try(:line_number)
29
+ end
30
+
31
+ return unless line_number
32
+
33
+ body.lines.detect { |line| line.lineNumber.to_i == line_number }
34
+ end
35
+
36
+ def tax_line_for_shipping(shipping)
37
+ return unless success?
38
+
39
+ line_number = request_shipping_line_items
40
+ .detect { |line_item| line_item.shipping.id == shipping.id }
41
+ .try(:line_number)
42
+
43
+ return unless line_number
44
+
45
+ body.lines.detect { |line| line.lineNumber.to_i == line_number }
46
+ end
47
+ end
48
+ end
49
+ end