solidus_avatax_certified 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env_example +4 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/GEM_VERSION +1 -0
- data/Gemfile +18 -0
- data/LICENSE +26 -0
- data/README.md +27 -0
- data/Rakefile +21 -0
- data/app/assets/javascripts/spree/backend/address_validator.js +84 -0
- data/app/assets/javascripts/spree/backend/solidus_avatax_certified.js +5 -0
- data/app/assets/javascripts/spree/backend/use_code_picker.js +51 -0
- data/app/assets/javascripts/spree/frontend/address_validator.js +87 -0
- data/app/assets/javascripts/spree/frontend/flash.js.coffee +26 -0
- data/app/assets/javascripts/spree/frontend/solidus_avatax_certified.js +4 -0
- data/app/assets/stylesheets/spree/backend/solidus_avatax_certified.css +4 -0
- data/app/assets/stylesheets/spree/frontend/solidus_avatax_certified.css +4 -0
- data/app/controllers/spree/admin/avalara_entity_use_codes_controller.rb +15 -0
- data/app/controllers/spree/admin/avatax_settings_controller.rb +82 -0
- data/app/controllers/spree/admin/users_controller_decorator.rb +11 -0
- data/app/controllers/spree/checkout_controller_decorator.rb +22 -0
- data/app/models/solidus_avatax_certified/address.rb +95 -0
- data/app/models/solidus_avatax_certified/avatax_log.rb +69 -0
- data/app/models/solidus_avatax_certified/line.rb +146 -0
- data/app/models/solidus_avatax_certified/preference_updater.rb +74 -0
- data/app/models/spree/address_decorator.rb +13 -0
- data/app/models/spree/adjustment_decorator.rb +10 -0
- data/app/models/spree/avalara_entity_use_code.rb +5 -0
- data/app/models/spree/avalara_preference.rb +72 -0
- data/app/models/spree/avalara_transaction.rb +181 -0
- data/app/models/spree/calculator/avalara_transaction.rb +86 -0
- data/app/models/spree/line_item_decorator.rb +25 -0
- data/app/models/spree/order_decorator.rb +85 -0
- data/app/models/spree/payment_decorator.rb +23 -0
- data/app/models/spree/refund_decorator.rb +30 -0
- data/app/models/spree/shipment_decorator.rb +28 -0
- data/app/models/spree/shipping_rate_decorator.rb +25 -0
- data/app/models/spree/user_decorator.rb +3 -0
- data/app/models/tax_svc.rb +143 -0
- data/app/overrides/spree/admin/shared/_configuration_menu.rb +10 -0
- data/app/overrides/spree/admin/users/sidebar.rb +8 -0
- data/app/overrides/spree/admin/users/validation_button.rb +6 -0
- data/app/views/spree/admin/avalara_entity_use_codes/_form.html.erb +27 -0
- data/app/views/spree/admin/avalara_entity_use_codes/edit.html.erb +16 -0
- data/app/views/spree/admin/avalara_entity_use_codes/index.html.erb +33 -0
- data/app/views/spree/admin/avalara_entity_use_codes/new.html.erb +17 -0
- data/app/views/spree/admin/avalara_entity_use_codes/show.html.erb +24 -0
- data/app/views/spree/admin/avatax_settings/edit.html.erb +183 -0
- data/app/views/spree/admin/avatax_settings/ping_my_service.js.erb +8 -0
- data/app/views/spree/admin/avatax_settings/show.html.erb +124 -0
- data/app/views/spree/admin/avatax_settings/validate_address.js.erb +8 -0
- data/app/views/spree/admin/users/avalara_information.html.erb +53 -0
- data/app/views/spree/checkout/_address.html.erb +35 -0
- data/app/views/spree/checkout/_address_validator_button.html.erb +3 -0
- data/bin/rails +7 -0
- data/circle.yml +17 -0
- data/config/initializers/avatax.rb +5 -0
- data/config/initializers/spree.rb +3 -0
- data/config/locales/en.yml +35 -0
- data/config/routes.rb +18 -0
- data/db/migrate/20160624154205_create_spree_avalara_transactions.rb +9 -0
- data/db/migrate/20160624154423_create_spree_avalara_entity_use_codes.rb +9 -0
- data/db/migrate/20160624155217_add_columns_to_spree_users.rb +6 -0
- data/db/migrate/20160628200504_create_spree_avalara_preferences.rb +10 -0
- data/db/migrate/20160805193512_add_vat_id_to_spree_users.rb +5 -0
- data/db/seeds.rb +1 -0
- data/lib/generators/solidus_avatax_certified/install/install_generator.rb +45 -0
- data/lib/solidus_avatax_certified/engine.rb +22 -0
- data/lib/solidus_avatax_certified/preference_seeder.rb +69 -0
- data/lib/solidus_avatax_certified/seeder.rb +117 -0
- data/lib/solidus_avatax_certified.rb +2 -0
- data/lib/tasks/load_seeds.rake +15 -0
- data/setup.md +12 -0
- data/solidus_avatax_certified.gemspec +42 -0
- data/spec/controllers/spree/admin/avalara_entity_use_codes_controller_spec.rb +53 -0
- data/spec/controllers/spree/admin/avatax_settings_controller_spec.rb +67 -0
- data/spec/factories/avalara_entity_use_code_factory.rb +6 -0
- data/spec/factories/avalara_factories.rb +76 -0
- data/spec/factories/avalara_order_factory.rb +62 -0
- data/spec/factories/avalara_preference_factory.rb +7 -0
- data/spec/factories/avalara_shipment_factory.rb +27 -0
- data/spec/factories/avalara_shipping_method_factory.rb +23 -0
- data/spec/factories/tax_rate_factory.rb +19 -0
- data/spec/features/certification_spec.rb +77 -0
- data/spec/features/checkout_spec.rb +156 -0
- data/spec/features/vat_spec.rb +153 -0
- data/spec/models/solidus_avatax_certified/address_spec.rb +80 -0
- data/spec/models/solidus_avatax_certified/avatax_log_spec.rb +97 -0
- data/spec/models/solidus_avatax_certified/line_spec.rb +100 -0
- data/spec/models/spree/address_decorator_spec.rb +44 -0
- data/spec/models/spree/adjustment_decorator_spec.rb +25 -0
- data/spec/models/spree/avalara_entity_use_code_spec.rb +5 -0
- data/spec/models/spree/avalara_preference_spec.rb +173 -0
- data/spec/models/spree/avalara_transaction_spec.rb +203 -0
- data/spec/models/spree/calculator/avalara_transaction_spec.rb +129 -0
- data/spec/models/spree/line_item_decorator_spec.rb +33 -0
- data/spec/models/spree/order_decorator_spec.rb +164 -0
- data/spec/models/spree/payment_decorator_spec.rb +117 -0
- data/spec/models/spree/refund_decorator_spec.rb +81 -0
- data/spec/models/spree/shipment_decorator_spec.rb +36 -0
- data/spec/models/spree/shipping_rate_decorator_spec.rb +17 -0
- data/spec/models/spree/user_decorator_spec.rb +5 -0
- data/spec/spec_helper.rb +81 -0
- data/spec/support/avatax_config_preferences.rb +8 -0
- data/spec/support/vcr.rb +9 -0
- data/spec/vcr/Certification/Exempt_sales_should_be_reflected_in_the_test_data_through_use_of_ExemptionNo_or_CustomerUsageType_/does_not_add_additional_tax.yml +276 -0
- data/spec/vcr/Certification/Transactions_have_been_committed_/commits_an_order.yml +336 -0
- data/spec/vcr/Certification/Transactions_have_been_voided/cancelled_/should_be_successful/returns_ResultCode_with_value_Success.yml +522 -0
- data/spec/vcr/Certification/return_orders/_commit_avatax_final/should_commit_avatax_final.yml +404 -0
- data/spec/vcr/Checkout/complete_order/has_tax_and_shipping_tax_adjustments_on_page.yml +72 -0
- data/spec/vcr/Checkout/complete_order/order_line_items_and_shipments_have_an_additional_tax_total_sum_of_0_40.yml +72 -0
- data/spec/vcr/Checkout/payment/tax_included/has_tax_and_shipping_tax_adjustments_on_page.yml +72 -0
- data/spec/vcr/Checkout/payment/tax_included/order_line_items_and_shipments_have_an_included_tax_total_sum_of_0_38.yml +72 -0
- data/spec/vcr/Checkout/payment/tax_not_included/on_payment_page/has_tax_and_shipping_tax_adjustments_on_page.yml +72 -0
- data/spec/vcr/Checkout/payment/tax_not_included/on_payment_page/order_line_items_and_shipments_and_shipments_have_an_additional_tax_total_sum_of_0_40.yml +72 -0
- data/spec/vcr/Checkout/payment/with_promotion/tax_not_included/has_adjusted_tax_amount_after_promotion_applied.yml +73 -0
- data/spec/vcr/SolidusAvataxCertified_Address/_validate/fails_when_information_is_incorrect.yml +54 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_computing_a_line_item/when_tax_is_included_in_price/should_be_equal_to_the_item_pre-tax_total_rate.yml +137 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_computing_a_line_item/when_tax_is_included_in_price/should_be_equal_to_the_previous_included_tax_total_is_order_is_at_address.yml +138 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_computing_a_line_item/when_tax_is_included_in_price/should_be_equal_to_the_previous_included_tax_total_is_order_is_at_cart.yml +137 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_computing_a_line_item/when_tax_is_not_included_in_price/should_be_equal_to_the_item_pre-tax_total_rate.yml +136 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_computing_a_line_item/when_tax_is_not_included_in_price/should_be_equal_to_the_previous_additional_tax_total_if_order_is_at_address.yml +137 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_computing_a_line_item/when_tax_is_not_included_in_price/should_be_equal_to_the_previous_additional_tax_total_if_order_is_at_cart.yml +136 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_computing_a_line_item/when_tax_is_not_included_in_price/should_be_equal_to_the_previous_tax_total_if_preference_tax_calculation_is_false.yml +136 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_computing_a_line_item/when_tax_is_not_included_in_price/when_the_line_item_is_discounted/should_be_equal_to_the_items_pre-tax_total_rate.yml +276 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_computing_a_line_item/when_tax_is_not_included_in_price/when_the_order_is_discounted/should_be_equal_to_the_items_pre-tax_total_rate.yml +206 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_given_a_shipment/computing_normal_shipment/should_be_equal_4_0.yml +141 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_given_a_shipment/when_tax_is_included_in_price/should_be_equal_to_3_85.yml +143 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_given_a_shipment/with_promotion/should_equal_3_96.yml +210 -0
- data/spec/vcr/Spree_Calculator_AvalaraTransaction/_compute/when_given_an_order/when_computing_an_order/should_raise_error.yml +136 -0
- data/spec/vcr/Spree_Order/_cancel_avalara/should_receive_cancel_order_when_cancel_avalara_is_called.yml +136 -0
- data/spec/vcr/Spree_Order/_cancel_avalara/state_machine_event_cancel/should_recieve_cancel_avalara_when_event_cancel_is_called.yml +259 -0
- data/spec/vcr/Spree_Order/_validate_ship_address/should_return_false_if_validation_failed.yml +54 -0
- data/spec/vcr/Spree_Order/_validate_ship_address/should_return_the_response_if_refuse_checkout_on_address_validation_is_disabled.yml +59 -0
- data/spec/vcr/Spree_Order/_validate_ship_address/should_return_the_response_if_validation_is_success.yml +59 -0
- data/spec/vcr/Spree_Payment/_avalara_finalize/should_update_the_amount_to_be_the_order_total.yml +136 -0
- data/spec/vcr/Spree_Payment/_cancel_avalara/committed_order/should_be_successful/ResultCode_returns_Success.yml +205 -0
- data/spec/vcr/Spree_Payment/_cancel_avalara/uncommitted_order/should_fail/ResultCode_returns_Error.yml +191 -0
- data/spec/vcr/Spree_Payment/_purchase_/receive_avalara_finalize.yml +136 -0
- data/spec/vcr/Spree_Payment/_void_transaction_/receive_cancel_avalara.yml +136 -0
- data/spec/vcr/Spree_Refund/full_refund/returns_correct_tax_calculations.yml +270 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_in_another_EU_country/Seller_does_not_have_Nexus_Jurisdition_Registered/TotalTax_is_equal_to_order_included_tax_total.yml +137 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_in_another_EU_country/Seller_does_not_have_Nexus_Jurisdition_Registered/origin_country_tax_is_returned.yml +138 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_in_another_EU_country/Seller_does_not_have_Nexus_Jurisdition_Registered/with_BusinessIdentificationNo/origin_country_zero_rate_is_returned.yml +143 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_in_another_EU_country/Seller_has_Nexus_Jurisdition_Registered/TotalTax_is_equal_to_order_included_tax_total.yml +139 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_in_another_EU_country/Seller_has_Nexus_Jurisdition_Registered/destination_country_tax_is_returned.yml +139 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_in_another_EU_country/Seller_has_Nexus_Jurisdition_Registered/with_BusinessIdentificationNo/origin_country_zero_rate_is_returned.yml +144 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_in_same_EU_country/TotalTax_is_equal_to_order_included_tax_total.yml +137 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_in_same_EU_country/tax_detail_country_equals_to_IT.yml +138 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_is_outside_EU/Seller_does_not_have_Nexus_Jurisdition_registered/TotalTax_is_equal_to_order_included_tax_total.yml +137 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_is_outside_EU/Seller_does_not_have_Nexus_Jurisdition_registered/tax_detail_country_equals_to_IT.yml +137 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_is_outside_EU/Seller_does_not_have_Nexus_Jurisdition_registered/total_tax_is_0_since_utah_is_not_in_jurisdiction.yml +137 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_is_outside_EU/Seller_does_not_have_Nexus_Jurisdition_registered/with_BusinessIdentificationNo/origin_country_zero_rate_is_returned.yml +138 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_is_outside_EU/Seller_has_Nexus_Jurisdiction_Registered/TotalTax_is_equal_to_order_included_tax_total.yml +195 -0
- data/spec/vcr/VAT/Seller_in_EU_country_Buyer_is_outside_EU/Seller_has_Nexus_Jurisdiction_Registered/tax_detail_region_equals_to_AL.yml +195 -0
- data/spec/vcr/address_validation_success.yml +59 -0
- data/spec/vcr/capture_with_customer_usage_type.yml +72 -0
- data/spec/vcr/order_cancel.yml +258 -0
- data/spec/vcr/order_capture.yml +72 -0
- data/spec/vcr/order_capture_finalize.yml +141 -0
- data/spec/vcr/order_capture_with_promo.yml +73 -0
- data/spec/vcr/order_return_capture.yml +68 -0
- data/spec/vcr/tax_included_order.yml +73 -0
- metadata +581 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
module SolidusAvataxCertified
|
2
|
+
class AvataxLog
|
3
|
+
def initialize(path_name, file_name, log_info = nil, schedule = nil)
|
4
|
+
if !Spree::AvalaraPreference.log_to_stdout.is_true?
|
5
|
+
schedule = 'weekly' unless schedule != nil
|
6
|
+
@logger ||= Logger.new("#{Rails.root}/log/#{path_name}.log", schedule)
|
7
|
+
progname(file_name.split('/').last.chomp('.rb'))
|
8
|
+
info(log_info) unless log_info.nil?
|
9
|
+
else
|
10
|
+
log_info = "-#{file_name} #{log_info}"
|
11
|
+
@logger ||= Logger.new(STDOUT)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def logger
|
16
|
+
@logger
|
17
|
+
end
|
18
|
+
|
19
|
+
def enabled?
|
20
|
+
Spree::AvalaraPreference.log.is_true? || Spree::AvalaraPreference.log_to_stdout.is_true?
|
21
|
+
end
|
22
|
+
|
23
|
+
def progname(progname = nil)
|
24
|
+
if enabled?
|
25
|
+
progname.nil? ? logger.progname : logger.progname = progname
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def info(log_info = nil)
|
30
|
+
if enabled?
|
31
|
+
unless log_info.nil?
|
32
|
+
logger.info "[AVATAX] #{log_info}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def info_and_debug(log_info, response)
|
38
|
+
if enabled?
|
39
|
+
logger.info "[AVATAX] #{log_info}"
|
40
|
+
if response.is_a?(Hash)
|
41
|
+
logger.debug "[AVATAX] #{JSON.generate(response)}"
|
42
|
+
else
|
43
|
+
logger.debug "[AVATAX] #{response}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def debug(obj, text = nil)
|
50
|
+
if enabled?
|
51
|
+
logger.debug "[AVATAX] #{obj.inspect}"
|
52
|
+
if text.nil?
|
53
|
+
obj
|
54
|
+
else
|
55
|
+
logger.debug "[AVATAX] text"
|
56
|
+
text
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def error(log_info = nil)
|
62
|
+
if enabled?
|
63
|
+
unless log_info.nil?
|
64
|
+
logger.error "[AVATAX] #{log_info}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module SolidusAvataxCertified
|
2
|
+
class Line
|
3
|
+
attr_reader :order, :lines
|
4
|
+
|
5
|
+
def initialize(order, invoice_type, refund = nil)
|
6
|
+
@order = order
|
7
|
+
@invoice_type = invoice_type
|
8
|
+
@lines = []
|
9
|
+
@refund = refund
|
10
|
+
@refunds = []
|
11
|
+
build_lines
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_lines
|
15
|
+
if %w(ReturnInvoice ReturnOrder).include?(@invoice_type)
|
16
|
+
refund_lines
|
17
|
+
else
|
18
|
+
item_lines_array
|
19
|
+
shipment_lines_array
|
20
|
+
end
|
21
|
+
|
22
|
+
logger.debug @lines
|
23
|
+
end
|
24
|
+
|
25
|
+
def item_line(line_item)
|
26
|
+
{
|
27
|
+
LineNo: "#{line_item.id}-LI",
|
28
|
+
Description: line_item.name[0..255],
|
29
|
+
TaxCode: line_item.tax_category.try(:tax_code) || '',
|
30
|
+
ItemCode: line_item.variant.sku,
|
31
|
+
Qty: line_item.quantity,
|
32
|
+
Amount: line_item.amount.to_f,
|
33
|
+
OriginCode: get_stock_location(line_item),
|
34
|
+
DestinationCode: 'Dest',
|
35
|
+
CustomerUsageType: order.customer_usage_type,
|
36
|
+
Discounted: discounted?(line_item),
|
37
|
+
TaxIncluded: tax_included_in_price?(line_item)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def item_lines_array
|
42
|
+
order.line_items.each do |line_item|
|
43
|
+
lines << item_line(line_item)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def shipment_lines_array
|
48
|
+
order.shipments.each do |shipment|
|
49
|
+
next unless shipment.tax_category
|
50
|
+
lines << shipment_line(shipment)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def shipment_line(shipment)
|
55
|
+
{
|
56
|
+
LineNo: "#{shipment.id}-FR",
|
57
|
+
ItemCode: shipment.shipping_method.name,
|
58
|
+
Qty: 1,
|
59
|
+
Amount: shipment.discounted_amount.to_f,
|
60
|
+
OriginCode: "#{shipment.stock_location_id}",
|
61
|
+
DestinationCode: 'Dest',
|
62
|
+
CustomerUsageType: order.customer_usage_type,
|
63
|
+
Description: 'Shipping Charge',
|
64
|
+
TaxCode: shipment.shipping_method_tax_code,
|
65
|
+
Discounted: false,
|
66
|
+
TaxIncluded: tax_included_in_price?(shipment)
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def refund_lines
|
71
|
+
return lines << refund_line if @refund.reimbursement.nil?
|
72
|
+
|
73
|
+
return_items = @refund.reimbursement.customer_return.return_items
|
74
|
+
inventory_units = Spree::InventoryUnit.where(id: return_items.pluck(:inventory_unit_id))
|
75
|
+
|
76
|
+
inventory_units.group_by(&:line_item_id).each_value do |inv_unit|
|
77
|
+
|
78
|
+
inv_unit_ids = inv_unit.map { |iu| iu.id }
|
79
|
+
return_items = Spree::ReturnItem.where(inventory_unit_id: inv_unit_ids)
|
80
|
+
quantity = inv_unit.uniq.count
|
81
|
+
|
82
|
+
amount = if return_items.first.respond_to?(:amount)
|
83
|
+
return_items.sum(:amount)
|
84
|
+
else
|
85
|
+
return_items.sum(:pre_tax_amount)
|
86
|
+
end
|
87
|
+
|
88
|
+
lines << return_item_line(inv_unit.first.line_item, quantity, amount)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def refund_line
|
93
|
+
{
|
94
|
+
LineNo: "#{@refund.id}-RA",
|
95
|
+
ItemCode: @refund.transaction_id || 'Refund',
|
96
|
+
Qty: 1,
|
97
|
+
Amount: -@refund.amount.to_f,
|
98
|
+
OriginCode: 'Orig',
|
99
|
+
DestinationCode: 'Dest',
|
100
|
+
CustomerUsageType: order.customer_usage_type,
|
101
|
+
Description: 'Refund',
|
102
|
+
TaxIncluded: true
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def return_item_line(line_item, quantity, amount)
|
107
|
+
{
|
108
|
+
LineNo: "#{line_item.id}-LI",
|
109
|
+
Description: line_item.name[0..255],
|
110
|
+
TaxCode: line_item.tax_category.try(:tax_code) || '',
|
111
|
+
ItemCode: line_item.variant.sku,
|
112
|
+
Qty: quantity,
|
113
|
+
Amount: -amount.to_f,
|
114
|
+
OriginCode: get_stock_location(line_item),
|
115
|
+
DestinationCode: 'Dest',
|
116
|
+
CustomerUsageType: order.customer_usage_type
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_stock_location(li)
|
121
|
+
inventory_units = li.inventory_units
|
122
|
+
|
123
|
+
return 'Orig' if inventory_units.blank?
|
124
|
+
|
125
|
+
stock_loc_id = inventory_units.first.try(:shipment).try(:stock_location_id)
|
126
|
+
|
127
|
+
stock_loc_id.nil? ? 'Orig' : "#{stock_loc_id}"
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def discounted?(line_item)
|
133
|
+
line_item.adjustments.promotion.eligible.any? || order.adjustments.promotion.eligible.any?
|
134
|
+
end
|
135
|
+
|
136
|
+
def logger
|
137
|
+
@logger ||= SolidusAvataxCertified::AvataxLog.new('avalara_order_lines', 'SolidusAvataxCertified::Line', "Building Lines for Order#: #{order.number}")
|
138
|
+
end
|
139
|
+
|
140
|
+
def tax_included_in_price?(item)
|
141
|
+
# Need better error handling
|
142
|
+
# if no tax rates, raise error tax rates needs to be set up
|
143
|
+
order.tax_zone.tax_rates.where(tax_category: item.tax_category).try(:first).included_in_price
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module SolidusAvataxCertified
|
2
|
+
class PreferenceUpdater
|
3
|
+
|
4
|
+
def initialize(params)
|
5
|
+
@avatax_origin = params[:address]
|
6
|
+
@avatax_preferences = params[:settings]
|
7
|
+
end
|
8
|
+
|
9
|
+
def update
|
10
|
+
update_validation_enabled_countries
|
11
|
+
update_origin_address
|
12
|
+
update_stored_preferences
|
13
|
+
update_boolean_preferences
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def update_stored_preferences
|
19
|
+
Spree::AvalaraPreference.storable_envs.each do |preference|
|
20
|
+
if !ENV["AVATAX_#{preference.name.upcase}"].blank?
|
21
|
+
update_value(preference, ENV["AVATAX_#{preference.name.upcase}"])
|
22
|
+
else
|
23
|
+
update_value(preference, @avatax_preferences[preference.name.downcase])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_boolean_preferences
|
29
|
+
Spree::AvalaraPreference.booleans.each do |boolean|
|
30
|
+
if !@avatax_preferences[boolean.name].nil?
|
31
|
+
update_value(boolean, 'true')
|
32
|
+
else
|
33
|
+
update_value(boolean, 'false')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_origin_address
|
39
|
+
set_region
|
40
|
+
set_country
|
41
|
+
update_value(Spree::AvalaraPreference.origin_address, @avatax_origin.to_json)
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_validation_enabled_countries
|
45
|
+
if @avatax_preferences['address_validation_enabled_countries'].present?
|
46
|
+
update_value(Spree::AvalaraPreference.validation_enabled_countries, @avatax_preferences['address_validation_enabled_countries'].join(','))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_value(preference, param)
|
51
|
+
if value_changed?(preference, param)
|
52
|
+
preference.update_attributes(value: param)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def value_changed?(preference, param)
|
57
|
+
preference.value != param
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_region
|
61
|
+
region = @avatax_origin['Region']
|
62
|
+
unless region.blank?
|
63
|
+
@avatax_origin['Region'] = Spree::State.find(region).try(:abbr)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_country
|
68
|
+
country = @avatax_origin['Country']
|
69
|
+
unless country.blank?
|
70
|
+
@avatax_origin['Country'] = Spree::Country.find(country).try(:iso)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Spree::Address.class_eval do
|
2
|
+
def validation_enabled?
|
3
|
+
Spree::AvalaraPreference.address_validation.is_true? && country_validation_enabled?
|
4
|
+
end
|
5
|
+
|
6
|
+
def country_validation_enabled?
|
7
|
+
Spree::Address.validation_enabled_countries.include?(country.try(:name))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.validation_enabled_countries
|
11
|
+
Spree::AvalaraPreference.validation_enabled_countries_array
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Spree
|
2
|
+
class AvalaraPreference < Spree::Base
|
3
|
+
validates_uniqueness_of :name
|
4
|
+
validates_presence_of :name, :object_type
|
5
|
+
|
6
|
+
scope :booleans, -> { where(object_type: 'boolean') }
|
7
|
+
scope :strings, -> { where(object_type: 'string') }
|
8
|
+
scope :arrays, -> { where(object_type: 'array') }
|
9
|
+
scope :jsons, -> { where(object_type: 'json') }
|
10
|
+
scope :storable_envs, -> { where(name: ['company_code', 'license_key', 'account', 'endpoint']) }
|
11
|
+
|
12
|
+
def self.company_code
|
13
|
+
find_by(name: 'company_code')
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.license_key
|
17
|
+
find_by(name: 'license_key')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.account
|
21
|
+
find_by(name: 'account')
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.endpoint
|
25
|
+
find_by(name: 'endpoint')
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.origin_address
|
29
|
+
find_by(name: 'origin_address')
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.log
|
33
|
+
find_by(name: 'log')
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.log_to_stdout
|
37
|
+
find_by(name: 'log_to_stdout')
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.address_validation
|
41
|
+
find_by(name: 'address_validation')
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.refuse_checkout_address_validation_error
|
45
|
+
find_by(name: 'refuse_checkout_address_validation_error')
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.tax_calculation
|
49
|
+
find_by(name: 'tax_calculation')
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.document_commit
|
53
|
+
find_by(name: 'document_commit')
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.validation_enabled_countries
|
57
|
+
find_by(name: 'address_validation_enabled_countries')
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.validation_enabled_countries_array
|
61
|
+
find_by(name: 'address_validation_enabled_countries').value.split(',')
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.customer_can_validate
|
65
|
+
find_by(name: 'customer_can_validate')
|
66
|
+
end
|
67
|
+
|
68
|
+
def is_true?
|
69
|
+
[true, 1, "1", "t", "T", "true", "TRUE", "on", "ON"].include?(value)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'logging'
|
2
|
+
require_dependency 'spree/order'
|
3
|
+
|
4
|
+
module Spree
|
5
|
+
class AvalaraTransaction < Spree::Base
|
6
|
+
belongs_to :order
|
7
|
+
validates :order, presence: true
|
8
|
+
validates :order_id, uniqueness: true
|
9
|
+
# has_many :adjustments, as: :source
|
10
|
+
|
11
|
+
def lookup_avatax
|
12
|
+
post_order_to_avalara(false, 'SalesOrder')
|
13
|
+
end
|
14
|
+
|
15
|
+
def commit_avatax(invoice_dt = nil, refund = nil)
|
16
|
+
if tax_calculation_enabled?
|
17
|
+
if %w(ReturnInvoice ReturnOrder).include?(invoice_dt)
|
18
|
+
post_return_to_avalara(false, invoice_dt, refund)
|
19
|
+
else
|
20
|
+
post_order_to_avalara(false, invoice_dt)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
{ TotalTax: '0.00' }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def commit_avatax_final(invoice_dt = nil, refund = nil)
|
28
|
+
if document_committing_enabled?
|
29
|
+
if tax_calculation_enabled?
|
30
|
+
if %w(ReturnInvoice ReturnOrder).include?(invoice_dt)
|
31
|
+
post_return_to_avalara(true, invoice_dt, refund)
|
32
|
+
else
|
33
|
+
post_order_to_avalara(true, invoice_dt)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
{ TotalTax: '0.00' }
|
37
|
+
end
|
38
|
+
else
|
39
|
+
logger.debug 'avalara document committing disabled'
|
40
|
+
'avalara document committing disabled'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def cancel_order
|
45
|
+
cancel_order_to_avalara('SalesInvoice') if tax_calculation_enabled?
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def cancel_order_to_avalara(doc_type = 'SalesInvoice')
|
51
|
+
logger.info('cancel order to avalara')
|
52
|
+
|
53
|
+
cancel_tax_request = {
|
54
|
+
CompanyCode: Spree::AvalaraPreference.company_code.value,
|
55
|
+
DocType: doc_type,
|
56
|
+
DocCode: order.number,
|
57
|
+
CancelCode: 'DocVoided'
|
58
|
+
}
|
59
|
+
|
60
|
+
mytax = TaxSvc.new
|
61
|
+
cancel_tax_result = mytax.cancel_tax(cancel_tax_request)
|
62
|
+
|
63
|
+
logger.debug cancel_tax_result
|
64
|
+
|
65
|
+
if cancel_tax_result == 'Error in Cancel Tax'
|
66
|
+
return 'Error in Cancel Tax'
|
67
|
+
else
|
68
|
+
return cancel_tax_result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def post_order_to_avalara(commit = false, invoice_detail = nil)
|
73
|
+
logger.info('post order to avalara')
|
74
|
+
avatax_address = SolidusAvataxCertified::Address.new(order)
|
75
|
+
avatax_line = SolidusAvataxCertified::Line.new(order, invoice_detail)
|
76
|
+
|
77
|
+
doc_date = order.completed? ? order.completed_at.strftime('%F') : Date.today.strftime('%F')
|
78
|
+
|
79
|
+
gettaxes = {
|
80
|
+
DocCode: order.number,
|
81
|
+
DocDate: doc_date,
|
82
|
+
Discount: order.all_adjustments.promotion.eligible.sum(:amount).abs.to_s,
|
83
|
+
Commit: commit,
|
84
|
+
DocType: invoice_detail ? invoice_detail : 'SalesOrder',
|
85
|
+
Addresses: avatax_address.addresses,
|
86
|
+
Lines: avatax_line.lines
|
87
|
+
}.merge(base_tax_hash)
|
88
|
+
|
89
|
+
if !business_id_no.blank?
|
90
|
+
gettaxes[:BusinessIdentificationNo] = business_id_no
|
91
|
+
end
|
92
|
+
|
93
|
+
logger.debug gettaxes
|
94
|
+
|
95
|
+
mytax = TaxSvc.new
|
96
|
+
|
97
|
+
tax_result = mytax.get_tax(gettaxes)
|
98
|
+
|
99
|
+
logger.info_and_debug('tax result', tax_result)
|
100
|
+
|
101
|
+
return { TotalTax: '0.00' } if tax_result == 'error in Tax'
|
102
|
+
return tax_result if tax_result['ResultCode'] == 'Success'
|
103
|
+
end
|
104
|
+
|
105
|
+
def post_return_to_avalara(commit = false, invoice_detail = nil, refund = nil)
|
106
|
+
logger.info('starting post return order to avalara')
|
107
|
+
|
108
|
+
avatax_address = SolidusAvataxCertified::Address.new(order)
|
109
|
+
avatax_line = SolidusAvataxCertified::Line.new(order, invoice_detail, refund)
|
110
|
+
|
111
|
+
taxoverride = {
|
112
|
+
TaxOverrideType: 'TaxDate',
|
113
|
+
Reason: refund.try(:reason).try(:name).limit(255) || 'Return',
|
114
|
+
TaxDate: order.completed_at.strftime('%F')
|
115
|
+
}
|
116
|
+
|
117
|
+
gettaxes = {
|
118
|
+
DocCode: order.number.to_s + '.' + refund.id.to_s,
|
119
|
+
DocDate: Date.today.strftime('%F'),
|
120
|
+
Commit: commit,
|
121
|
+
DocType: invoice_detail ? invoice_detail : 'ReturnOrder',
|
122
|
+
Addresses: avatax_address.addresses,
|
123
|
+
Lines: avatax_line.lines
|
124
|
+
}.merge(base_tax_hash)
|
125
|
+
|
126
|
+
if !business_id_no.blank?
|
127
|
+
gettaxes[:BusinessIdentificationNo] = business_id_no
|
128
|
+
end
|
129
|
+
|
130
|
+
gettaxes[:TaxOverride] = taxoverride
|
131
|
+
|
132
|
+
logger.debug gettaxes
|
133
|
+
|
134
|
+
mytax = TaxSvc.new
|
135
|
+
|
136
|
+
tax_result = mytax.get_tax(gettaxes)
|
137
|
+
|
138
|
+
logger.info_and_debug('tax result', tax_result)
|
139
|
+
|
140
|
+
return { TotalTax: '0.00' } if tax_result == 'error in Tax'
|
141
|
+
return tax_result if tax_result['ResultCode'] == 'Success'
|
142
|
+
end
|
143
|
+
|
144
|
+
def base_tax_hash
|
145
|
+
{
|
146
|
+
CustomerCode: customer_code,
|
147
|
+
CompanyCode: Spree::AvalaraPreference.company_code.value,
|
148
|
+
CustomerUsageType: order.customer_usage_type,
|
149
|
+
ExemptionNo: order.user.try(:exemption_number),
|
150
|
+
Client: avatax_client_version,
|
151
|
+
ReferenceCode: order.number,
|
152
|
+
DetailLevel: 'Tax',
|
153
|
+
CurrencyCode: order.currency
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
def customer_code
|
158
|
+
order.user ? order.user.id : order.email
|
159
|
+
end
|
160
|
+
|
161
|
+
def business_id_no
|
162
|
+
order.user.try(:vat_id)
|
163
|
+
end
|
164
|
+
|
165
|
+
def avatax_client_version
|
166
|
+
AVATAX_CLIENT_VERSION || 'a0o33000004FH8l'
|
167
|
+
end
|
168
|
+
|
169
|
+
def document_committing_enabled?
|
170
|
+
Spree::AvalaraPreference.document_commit.is_true?
|
171
|
+
end
|
172
|
+
|
173
|
+
def tax_calculation_enabled?
|
174
|
+
Spree::AvalaraPreference.tax_calculation.is_true?
|
175
|
+
end
|
176
|
+
|
177
|
+
def logger
|
178
|
+
@logger ||= SolidusAvataxCertified::AvataxLog.new('post_order_to_avalara', __FILE__)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Spree
|
2
|
+
class Calculator::AvalaraTransaction < Calculator::DefaultTax
|
3
|
+
def self.description
|
4
|
+
Spree.t(:avalara_transaction_calculator)
|
5
|
+
end
|
6
|
+
|
7
|
+
def compute_order(order)
|
8
|
+
raise 'Spree::AvalaraTransaction is designed to calculate taxes at the shipment and line-item levels.'
|
9
|
+
end
|
10
|
+
|
11
|
+
def compute_shipment_or_line_item(item)
|
12
|
+
order = item.order
|
13
|
+
item_address = order.ship_address || order.billing_address
|
14
|
+
prev_tax_amount = prev_tax_amount(item)
|
15
|
+
|
16
|
+
return prev_tax_amount unless Spree::AvalaraPreference.tax_calculation.is_true?
|
17
|
+
return prev_tax_amount if %w(address cart).include?(order.state)
|
18
|
+
return prev_tax_amount if item_address.nil?
|
19
|
+
return prev_tax_amount unless calculable.zone.include?(item_address)
|
20
|
+
|
21
|
+
avalara_response = get_avalara_response(order)
|
22
|
+
tax_for_item(item, avalara_response)
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :compute_shipment, :compute_shipment_or_line_item
|
26
|
+
alias_method :compute_line_item, :compute_shipment_or_line_item
|
27
|
+
|
28
|
+
def compute_shipping_rate(shipping_rate)
|
29
|
+
return 0
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def prev_tax_amount(item)
|
35
|
+
if rate.included_in_price
|
36
|
+
item.included_tax_total
|
37
|
+
else
|
38
|
+
item.additional_tax_total
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_avalara_response(order)
|
43
|
+
Rails.cache.fetch(cache_key(order), time_to_idle: 5.minutes) do
|
44
|
+
order.avalara_capture
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def long_cache_key(order)
|
50
|
+
key = order.avatax_cache_key
|
51
|
+
key << (order.ship_address.try(:cache_key) || order.bill_address.try(:cache_key)).to_s
|
52
|
+
order.line_items.each do |line_item|
|
53
|
+
key << line_item.avatax_cache_key
|
54
|
+
end
|
55
|
+
order.shipments.each do |shipment|
|
56
|
+
key << shipment.avatax_cache_key
|
57
|
+
end
|
58
|
+
order.all_adjustments.not_tax do |adj|
|
59
|
+
key << adj.avatax_cache_key
|
60
|
+
end
|
61
|
+
key
|
62
|
+
end
|
63
|
+
|
64
|
+
# long keys blow up in dev with the default ActiveSupport::Cache::FileStore
|
65
|
+
# This transparently shrinks 'em
|
66
|
+
def cache_key(order)
|
67
|
+
long_key = long_cache_key(order)
|
68
|
+
short_key = Digest::SHA1.hexdigest(long_key)
|
69
|
+
"avtx_#{short_key}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def tax_for_item(item, avalara_response)
|
73
|
+
prev_tax_amount = prev_tax_amount(item)
|
74
|
+
|
75
|
+
return prev_tax_amount if avalara_response.nil?
|
76
|
+
return 0 if avalara_response[:TotalTax] == '0.00'
|
77
|
+
|
78
|
+
avalara_response['TaxLines'].each do |line|
|
79
|
+
if line['LineNo'] == "#{item.id}-#{item.avatax_line_code}"
|
80
|
+
return line['TaxCalculated'].to_f
|
81
|
+
end
|
82
|
+
end
|
83
|
+
0
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Spree::LineItem.class_eval do
|
2
|
+
def to_hash
|
3
|
+
{
|
4
|
+
'Index' => id,
|
5
|
+
'Name' => name,
|
6
|
+
'ItemID' => sku,
|
7
|
+
'Price' => price.to_s,
|
8
|
+
'Qty' => quantity,
|
9
|
+
'TaxCategory' => tax_category
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def avatax_cache_key
|
14
|
+
key = ['Spree::LineItem']
|
15
|
+
key << self.id
|
16
|
+
key << self.quantity
|
17
|
+
key << self.price
|
18
|
+
key << self.promo_total
|
19
|
+
key.join('-')
|
20
|
+
end
|
21
|
+
|
22
|
+
def avatax_line_code
|
23
|
+
'LI'
|
24
|
+
end
|
25
|
+
end
|