vat-gst-calculator 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f39992ff401d87c4d256cbbe7da9ef39e03e134b9e21ca5f4ba258d9ad613497
4
- data.tar.gz: 118ee1b247f9536cdaf16aad6246ba8d61d48ee817a02ba7510c1245ccf4d52d
3
+ metadata.gz: 47259f182d190ca603c7694139e25c002852bc1c60952dbc8967ae170031dea5
4
+ data.tar.gz: 76f58ac22a22144627b1144ef6b9469179b68034ed104e0f369c1d726e53c8ac
5
5
  SHA512:
6
- metadata.gz: 8ba20e1380d4264138c446a675d5e1a6b5147e39713dcf7d441b706d09295214a0661f75e47411136f459dbf38843a38443d400d98cfc29e3d748e39ef426199
7
- data.tar.gz: 490338962e3e8146c6a851aaa4c027a491cc6bb9f276206a00d8687af5b80ab2e7891565d3308086cbd099e5cd71913441124e5590e851519622f211824f531b
6
+ metadata.gz: 89e1e20dabc983e4bcd34a3ace0982ebab08a56f6242ee2037b8cdf5a9608814064603fe7e3bffb9e3c49f6240ef85ba614eac96d2ea85435191f769d48f719a
7
+ data.tar.gz: 39b36cf998538c48282da9ec6462f29770b701dba666bc90501deaac39326a2ab12c5de231875b8a2f75b02895bac7b18477f2ec413c5ae8185d6e5844d97661
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ Gemfile.lock
4
4
  .idea/*
5
5
  *.swp
6
6
  tags
7
+ .tool-versions
data/README.md CHANGED
@@ -26,7 +26,9 @@ Easyship::SalesTax::Calculator.calculate(
26
26
  destination_country_alpha2: destination_country_alpha2,
27
27
  origin_state: origin_state,
28
28
  destination_state: destination_state,
29
- fees: Easyship::SalesTax::Fee.new(10, 20, 30, 40) # (shipment_charge_total, insurance_fee, pickup_fee, residential_surcharge)
29
+ fees: Easyship::SalesTax::Fee.new(10, 20, 30, 40), # (shipment_charge_total, insurance_fee, pickup_fee, residential_surcharge)
30
+ effective_timestamp: "2023-01-01", # String and Time instances are supported. Default to current time (utc)
31
+ effective_country_alpha2s: ["HK", "US"] # when empty or not specified, all avalilable country data is used
30
32
  )
31
33
  ```
32
34
 
@@ -11,6 +11,7 @@ module Easyship
11
11
  pickup_fee: 0,
12
12
  residential_surcharge: 0
13
13
  }.freeze
14
+
14
15
  DEFAULT_EFFECTIVE_TIMESTAMP = Time.at(0).utc.freeze
15
16
 
16
17
  class << self
@@ -23,10 +24,11 @@ module Easyship
23
24
  }
24
25
  end
25
26
 
26
- def effective_data(country_alpha2, effective_timestamp = nil)
27
- data = DOMESTIC.fetch(country_alpha2, [])
28
-
29
- data.find { |rates| rates[:effective_timestamp] <= effective_time(effective_timestamp) }
27
+ def effective_data(country_alpha2, effective_timestamp = nil, effective_country_alpha2s = [])
28
+ DOMESTIC
29
+ .slice(*(effective_country_alpha2s.empty? ? DEFAULT_EFFECTIVE_COUNTRY_ALPHA2S : effective_country_alpha2s))
30
+ .fetch(country_alpha2, [])
31
+ .find { |rates| rates[:effective_timestamp] <= effective_time(effective_timestamp) }
30
32
  end
31
33
 
32
34
  private
@@ -172,11 +174,12 @@ module Easyship
172
174
  sales_tax: {'_ALL': build_fee_struct(0.05, 0, 0, 0)},
173
175
  provincial_sales_taxes: {'_ALL': NO_TAX}
174
176
  }],
175
- # 'IE' => {
176
- # sales_tax_website_name: 'VAT',
177
- # sales_tax: { '_ALL': build_fee_struct(0.23, 0, 0, 0) },
178
- # provincial_sales_taxes: { '_ALL': no_taxes }
179
- # },
177
+ 'IE' => [{
178
+ effective_timestamp: DEFAULT_EFFECTIVE_TIMESTAMP,
179
+ sales_tax_website_name: 'VAT',
180
+ sales_tax: { '_ALL': build_fee_struct(0.23, 0, 0.23, 0.23) },
181
+ provincial_sales_taxes: { '_ALL': NO_TAX }
182
+ }],
180
183
  # 'DK' => {
181
184
  # sales_tax_website_name: 'VAT',
182
185
  # sales_tax: { '_ALL': build_fee_struct(0.25, 0, 0, 0) },
@@ -239,6 +242,8 @@ module Easyship
239
242
  # provincial_sales_taxes: { '_ALL': no_taxes }
240
243
  # }
241
244
  }.transform_values { |value| value.sort_by { |rates| -rates[:effective_timestamp].to_i } }.freeze
245
+
246
+ DEFAULT_EFFECTIVE_COUNTRY_ALPHA2S = DOMESTIC.keys.freeze
242
247
  end
243
248
  end
244
249
  end
@@ -1,7 +1,7 @@
1
1
  module Easyship
2
2
  module SalesTax
3
3
  module Calculator
4
- VERSION = "0.6.0"
4
+ VERSION = "0.8.0"
5
5
  end
6
6
  end
7
7
  end
@@ -7,60 +7,122 @@ module Easyship
7
7
  class Error < StandardError; end
8
8
 
9
9
  FALLBACK_VALUE = {sales_tax: 0, provincial_sales_tax: 0}.freeze
10
+ FALLBACK_VALUE_WITH_DETAILS =
11
+ {
12
+ sales_tax: 0,
13
+ provincial_sales_tax: 0,
14
+ sales_tax_detail: Easyship::SalesTax::Formula::NO_TAX,
15
+ provincial_sales_tax_detail: Easyship::SalesTax::Formula::NO_TAX
16
+ }.freeze
17
+ EU_COUNTRY_ALPHA2S = %w[NL LU EE LT HU BE PT SK HR CZ IT FI PL MT DE SI RO BG AT SE CY DK FR IE ES GR LV].freeze
10
18
 
11
- def self.calculate(origin_country_alpha2: nil, destination_country_alpha2: nil, origin_state: nil, destination_state: nil, fees: nil, effective_timestamp: nil)
12
- return FALLBACK_VALUE if origin_country_alpha2.nil? || origin_country_alpha2.empty? || destination_country_alpha2.nil? || destination_country_alpha2.empty?
13
- return FALLBACK_VALUE unless fees.is_a?(Fee)
19
+ class << self
20
+ def calculate(
21
+ origin_country_alpha2: nil,
22
+ destination_country_alpha2: nil,
23
+ origin_state: nil,
24
+ destination_state: nil,
25
+ fees: nil,
26
+ effective_timestamp: nil,
27
+ effective_country_alpha2s: [],
28
+ with_detail: false
29
+ )
30
+ return fallback_value(with_detail: with_detail) if origin_country_alpha2&.strip&.empty? || destination_country_alpha2&.strip&.empty?
31
+ return fallback_value(with_detail: with_detail) unless fees.is_a?(Fee)
32
+ return fallback_value(with_detail: with_detail) unless tax_eligible?(origin_country_alpha2, destination_country_alpha2)
14
33
 
15
- if domestic?(origin_country_alpha2, destination_country_alpha2) || within_eu?(origin_country_alpha2, destination_country_alpha2)
16
- domestic_value(origin_country_alpha2: origin_country_alpha2, origin_state: origin_state, destination_state: destination_state, fees: fees, effective_timestamp: effective_timestamp)
17
- else
18
- FALLBACK_VALUE
34
+ domestic_value(
35
+ origin_country_alpha2: origin_country_alpha2,
36
+ origin_state: origin_state,
37
+ destination_state: destination_state,
38
+ fees: fees,
39
+ effective_timestamp: effective_timestamp,
40
+ effective_country_alpha2s: effective_country_alpha2s,
41
+ with_detail: with_detail
42
+ )
43
+ rescue
44
+ fallback_value(with_detail: with_detail)
19
45
  end
20
- rescue
21
- FALLBACK_VALUE
22
- end
23
46
 
24
- def self.sales_tax_website_name(country_alpha2, effective_timestamp = nil)
25
- rates = Formula.effective_data(country_alpha2, effective_timestamp)
26
- (rates || {}).dig(:sales_tax_website_name)
27
- end
47
+ def calculate_with_detail(**kwargs)
48
+ calculate(with_detail: true, **kwargs)
49
+ end
28
50
 
29
- private
51
+ def sales_tax_website_name(country_alpha2, effective_timestamp = nil)
52
+ rates = Formula.effective_data(country_alpha2, effective_timestamp)
53
+ (rates || {}).dig(:sales_tax_website_name)
54
+ end
30
55
 
31
- def self.domestic_value(origin_country_alpha2: nil, origin_state: nil, destination_state: nil, fees: nil, effective_timestamp: nil)
32
- rates = Formula.effective_data(origin_country_alpha2, effective_timestamp)
33
- return FALLBACK_VALUE if rates.nil?
56
+ private
34
57
 
35
- {
36
- sales_tax: tax_calculate(rates[:sales_tax], nil, destination_state, fees, false),
37
- provincial_sales_tax: tax_calculate(rates[:provincial_sales_taxes], origin_state, destination_state, fees, true)
38
- }
39
- end
58
+ def domestic_value(
59
+ origin_country_alpha2: nil,
60
+ origin_state: nil,
61
+ destination_state: nil,
62
+ fees: nil,
63
+ effective_timestamp: nil,
64
+ effective_country_alpha2s: [],
65
+ with_detail: false
66
+ )
67
+ rates = Formula.effective_data(origin_country_alpha2, effective_timestamp, effective_country_alpha2s)
68
+ return fallback_value(with_detail: with_detail) if rates.nil?
69
+
70
+ {}.tap do |h|
71
+ h[:sales_tax] = tax_calculate(rates[:sales_tax], nil, destination_state, fees, false)
72
+ h[:provincial_sales_tax] = tax_calculate(rates[:provincial_sales_taxes], origin_state, destination_state, fees, true)
40
73
 
41
- def self.tax_calculate(formula, origin_state, destination_state, fees, is_provincial_sales_taxes)
42
- if (tax_destination_formula = formula[destination_state.to_s.upcase])
43
- # ONLY CA has provincial_sales_taxes(QST/PST) and it only apply when ship within same state
44
- # https://www.fedex.com/en-ca/news/canadian-sales-taxes.html
45
- return 0 if origin_state != destination_state && is_provincial_sales_taxes
46
- fees.to_h.inject(0) { |sum, (k, v)| sum + (tax_destination_formula[k] * v.to_f) }.round(2)
47
- elsif formula[:_ALL]
48
- fees.to_h.inject(0) { |sum, (k, v)| sum + (formula[:_ALL][k] * v.to_f) }.round(2)
49
- else
50
- 0
74
+ if with_detail
75
+ h[:sales_tax_detail] = tax_detail(rates[:sales_tax], nil, destination_state, fees, false)
76
+ h[:provincial_sales_tax_detail] = tax_detail(rates[:provincial_sales_taxes], origin_state, destination_state, fees, true)
77
+ end
78
+ end
51
79
  end
52
- end
53
80
 
54
- def self.in_eu?(country_alpha2)
55
- %w(NL LU EE LT HU BE PT SK HR CZ IT FI PL MT DE SI RO BG AT SE CY DK FR IE ES GR LV).include?(country_alpha2)
56
- end
81
+ def tax_calculate(formula, origin_state, destination_state, fees, is_provincial_sales_taxes)
82
+ if (tax_destination_formula = formula[destination_state.to_s.upcase])
83
+ # ONLY CA has provincial_sales_taxes(QST/PST) and it only apply when ship within same state
84
+ # https://www.fedex.com/en-ca/news/canadian-sales-taxes.html
85
+ return 0 if origin_state != destination_state && is_provincial_sales_taxes
86
+ fees.to_h.inject(0) { |sum, (k, v)| sum + (tax_destination_formula[k] * v.to_f) }.round(2)
87
+ elsif formula[:_ALL]
88
+ fees.to_h.inject(0) { |sum, (k, v)| sum + (formula[:_ALL][k] * v.to_f) }.round(2)
89
+ else
90
+ 0
91
+ end
92
+ end
57
93
 
58
- def self.domestic?(origin_country_alpha2 = nil, destination_country_alpha2 = nil)
59
- !origin_country_alpha2.nil? && !origin_country_alpha2.empty? && origin_country_alpha2 == destination_country_alpha2
60
- end
94
+ def tax_detail(formula, origin_state, destination_state, fees, is_provincial_sales_taxes)
95
+ if (tax_destination_formula = formula[destination_state.to_s.upcase])
96
+ # ONLY CA has provincial_sales_taxes(QST/PST) and it only apply when ship within same state
97
+ # https://www.fedex.com/en-ca/news/canadian-sales-taxes.html
98
+ return Easyship::SalesTax::Formula::NO_TAX if origin_state != destination_state && is_provincial_sales_taxes
99
+ fees.to_h.each_with_object({}) { |(k, v), hash| hash[k] = (tax_destination_formula[k] * v.to_f).round(2) }
100
+ elsif formula[:_ALL]
101
+ fees.to_h.each_with_object({}) { |(k, v), hash| hash[k] = (formula[:_ALL][k] * v.to_f).round(2) }
102
+ else
103
+ Easyship::SalesTax::Formula::NO_TAX
104
+ end
105
+ end
106
+
107
+ def in_eu?(country_alpha2)
108
+ EU_COUNTRY_ALPHA2S.include?(country_alpha2)
109
+ end
110
+
111
+ def domestic?(origin_country_alpha2 = nil, destination_country_alpha2 = nil)
112
+ !origin_country_alpha2&.strip&.empty? && origin_country_alpha2 == destination_country_alpha2
113
+ end
61
114
 
62
- def self.within_eu?(origin_country_alpha2 = nil, destination_country_alpha2 = nil)
63
- in_eu?(origin_country_alpha2) && in_eu?(destination_country_alpha2)
115
+ def within_eu?(origin_country_alpha2 = nil, destination_country_alpha2 = nil)
116
+ in_eu?(origin_country_alpha2) && in_eu?(destination_country_alpha2)
117
+ end
118
+
119
+ def tax_eligible?(...)
120
+ domestic?(...) || within_eu?(...)
121
+ end
122
+
123
+ def fallback_value(with_detail: false)
124
+ with_detail ? FALLBACK_VALUE_WITH_DETAILS : FALLBACK_VALUE
125
+ end
64
126
  end
65
127
  end
66
128
  end
@@ -23,6 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
+ spec.required_ruby_version = ">= 3.0.6"
27
+
26
28
  spec.add_development_dependency "rspec", "~> 3.0"
27
29
  spec.add_development_dependency "byebug"
28
30
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vat-gst-calculator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aloha Chen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-12-14 00:00:00.000000000 Z
12
+ date: 2023-07-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -72,14 +72,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: 3.0.6
76
76
  required_rubygems_version: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - ">="
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
81
  requirements: []
82
- rubygems_version: 3.0.9
82
+ rubygems_version: 3.2.33
83
83
  signing_key:
84
84
  specification_version: 4
85
85
  summary: Calculate Sales Tax