vat-gst-calculator 0.5.0 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b41f3eb40c2fc01049599af67185633c146994e9e8aa68ef2518ecee6d4cdc3f
4
- data.tar.gz: 7603c43981f2bcb359a77be92d714873dbfbb0b0e8d035ab401b94e6e5b995e6
3
+ metadata.gz: 4c0b1547695777a69f063993628affe4eaf28498180236ca0bd2e5902d5bd7d7
4
+ data.tar.gz: 5f7fd7051a155b3f5c0187ec2ebde52df83991a35491e0d8abf780d93ff94aed
5
5
  SHA512:
6
- metadata.gz: b61a549c2b33e5549bce687e018516f4f8b2218634c25ac81c799d732239cc3461f73a8b502b24749c3657abe206b09f4695284c2167836f300882c40aa1bb5b
7
- data.tar.gz: 7ef3a105c047ed5c26806df2bc2a5f7da234c7a36b079c19a13c40d19481ce7f2be781e67554e3c2050e60e993e419fe933d040325fa0951014a16e92573c6fc
6
+ metadata.gz: 444a34ccad4d894adca304083e25cc4baae48ae6a6abb7d1aabcd0c074a87d790120f0c6abd6ff2bfa242174c9939a00957c53a399151d68fe1acc0a551d7fbb
7
+ data.tar.gz: a7b0bcf732ca7a34da47c703e6b9f85fdae33461ad2602c73058f5770284b0e50204a3938b6674fd30a46317ab5481971a2d5d282665fccc70e713f1c9c8a28c
data/.gitignore CHANGED
@@ -1,3 +1,6 @@
1
1
  .byebug_history
2
2
  .rspec_status
3
3
  Gemfile.lock
4
+ .idea/*
5
+ *.swp
6
+ tags
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
 
@@ -1,7 +1,19 @@
1
+ require "time"
2
+
1
3
  module Easyship
2
4
  module SalesTax
3
5
  module Formula
4
6
  class Error < StandardError; end
7
+
8
+ NO_TAX = {
9
+ shipment_charge_total: 0,
10
+ insurance_fee: 0,
11
+ pickup_fee: 0,
12
+ residential_surcharge: 0
13
+ }.freeze
14
+
15
+ DEFAULT_EFFECTIVE_TIMESTAMP = Time.at(0).utc.freeze
16
+
5
17
  class << self
6
18
  def build_fee_struct(shipment_charge_total, insurance_fee, pickup_fee, residential_surcharge)
7
19
  {
@@ -12,23 +24,42 @@ module Easyship
12
24
  }
13
25
  end
14
26
 
15
- def no_taxes
16
- build_fee_struct(0, 0, 0, 0)
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) }
32
+ end
33
+
34
+ private
35
+
36
+ def effective_time(effective_timestamp)
37
+ case effective_timestamp
38
+ when String
39
+ Time.parse(effective_timestamp).utc
40
+ when Time
41
+ effective_timestamp
42
+ else
43
+ Time.now.utc
44
+ end
17
45
  end
18
46
  end
19
47
 
20
48
  DOMESTIC = {
21
- 'AU' => {
49
+ 'AU' => [{
50
+ effective_timestamp: DEFAULT_EFFECTIVE_TIMESTAMP,
22
51
  sales_tax_website_name: 'GST',
23
- sales_tax: { '_ALL': build_fee_struct(0.1, 0, 0.1, 0) },
24
- provincial_sales_taxes: { '_ALL': no_taxes }
25
- },
26
- 'GB' => {
52
+ sales_tax: {'_ALL': build_fee_struct(0.1, 0, 0.1, 0)},
53
+ provincial_sales_taxes: {'_ALL': NO_TAX}
54
+ }],
55
+ 'GB' => [{
56
+ effective_timestamp: DEFAULT_EFFECTIVE_TIMESTAMP,
27
57
  sales_tax_website_name: 'VAT',
28
- sales_tax: { '_ALL': build_fee_struct(0.2, 0, 0.2, 0.2) }, # TBC
29
- provincial_sales_taxes: { '_ALL': no_taxes }
30
- },
31
- 'CA' => {
58
+ sales_tax: {'_ALL': build_fee_struct(0.2, 0, 0.2, 0.2)}, # TBC
59
+ provincial_sales_taxes: {'_ALL': NO_TAX}
60
+ }],
61
+ 'CA' => [{
62
+ effective_timestamp: DEFAULT_EFFECTIVE_TIMESTAMP,
32
63
  sales_tax_website_name: 'Taxes',
33
64
  sales_tax: {
34
65
  'AB' => build_fee_struct(0.05, 0, 0.05, 0.05), # Alberta
@@ -46,41 +77,52 @@ module Easyship
46
77
  'YT' => build_fee_struct(0.05, 0, 0.05, 0.05), # Yukon
47
78
  },
48
79
  provincial_sales_taxes: {
49
- 'AB' => no_taxes, # Alberta
50
- 'BC' => no_taxes, # British Columbia
51
- 'MB' => no_taxes, # Manitoba
52
- 'NB' => no_taxes, # New Brunswick
53
- 'NL' => no_taxes, # Newfoundland and Labrador
54
- 'NT' => no_taxes, # Northwest Territories
55
- 'NS' => no_taxes, # Nova Scotia
56
- 'NU' => no_taxes, # Nunavut
57
- 'ON' => no_taxes, # Ontario
58
- 'PE' => no_taxes, # Prince Edward Island
80
+ 'AB' => NO_TAX, # Alberta
81
+ 'BC' => NO_TAX, # British Columbia
82
+ 'MB' => NO_TAX, # Manitoba
83
+ 'NB' => NO_TAX, # New Brunswick
84
+ 'NL' => NO_TAX, # Newfoundland and Labrador
85
+ 'NT' => NO_TAX, # Northwest Territories
86
+ 'NS' => NO_TAX, # Nova Scotia
87
+ 'NU' => NO_TAX, # Nunavut
88
+ 'ON' => NO_TAX, # Ontario
89
+ 'PE' => NO_TAX, # Prince Edward Island
59
90
  'QC' => build_fee_struct(0.09975, 0, 0.09975, 0.09975), # Quebec
60
- 'SK' => no_taxes, # Saskatchewan
61
- 'YT' => no_taxes, # Yukon
91
+ 'SK' => NO_TAX, # Saskatchewan
92
+ 'YT' => NO_TAX, # Yukon
62
93
  }
63
- },
64
- 'SG' => {
65
- sales_tax_website_name: 'GST',
66
- sales_tax: { '_ALL': build_fee_struct(0.07, 0.07, 0.07, 0.07) },
67
- provincial_sales_taxes: { '_ALL': no_taxes }
68
- },
69
- 'HK' => {
94
+ }],
95
+ 'SG' => [
96
+ {
97
+ effective_timestamp: DEFAULT_EFFECTIVE_TIMESTAMP,
98
+ sales_tax_website_name: 'GST',
99
+ sales_tax: {'_ALL': build_fee_struct(0.07, 0.07, 0.07, 0.07)},
100
+ provincial_sales_taxes: {'_ALL': NO_TAX}
101
+ },
102
+ {
103
+ effective_timestamp: Time.utc(2023, 1, 1, 8),
104
+ sales_tax_website_name: 'GST',
105
+ sales_tax: {'_ALL': build_fee_struct(0.08, 0.08, 0.08, 0.08)},
106
+ provincial_sales_taxes: {'_ALL': NO_TAX}
107
+ }
108
+ ],
109
+ 'HK' => [{
110
+ effective_timestamp: DEFAULT_EFFECTIVE_TIMESTAMP,
70
111
  sales_tax_website_name: nil,
71
- sales_tax: { '_ALL': no_taxes },
72
- provincial_sales_taxes: { '_ALL': no_taxes }
73
- },
112
+ sales_tax: {'_ALL': NO_TAX},
113
+ provincial_sales_taxes: {'_ALL': NO_TAX}
114
+ }],
74
115
  # 'NL' => {
75
116
  # sales_tax_website_name: 'VAT',
76
117
  # sales_tax: { '_ALL': build_fee_struct(0.21, 0, 0.21, 0.21) },
77
118
  # provincial_sales_taxes: { '_ALL': no_taxes }
78
119
  # },
79
- 'US' => {
120
+ 'US' => [{
121
+ effective_timestamp: DEFAULT_EFFECTIVE_TIMESTAMP,
80
122
  sales_tax_website_name: nil,
81
- sales_tax: { '_ALL': no_taxes },
82
- provincial_sales_taxes: { '_ALL': no_taxes }
83
- },
123
+ sales_tax: {'_ALL': NO_TAX},
124
+ provincial_sales_taxes: {'_ALL': NO_TAX}
125
+ }],
84
126
  # 'DE' => {
85
127
  # sales_tax_website_name: 'VAT',
86
128
  # sales_tax: { '_ALL': build_fee_struct(0.19, 0, 0, 0) },
@@ -126,16 +168,18 @@ module Easyship
126
168
  # sales_tax: { '_ALL': build_fee_struct(0.2, 0, 0, 0) },
127
169
  # provincial_sales_taxes: { '_ALL': no_taxes }
128
170
  # },
129
- 'AE' => {
171
+ 'AE' => [{
172
+ effective_timestamp: DEFAULT_EFFECTIVE_TIMESTAMP,
130
173
  sales_tax_website_name: 'VAT',
131
- sales_tax: { '_ALL': build_fee_struct(0.05, 0, 0, 0) },
132
- provincial_sales_taxes: { '_ALL': no_taxes }
133
- },
134
- # 'IE' => {
135
- # sales_tax_website_name: 'VAT',
136
- # sales_tax: { '_ALL': build_fee_struct(0.23, 0, 0, 0) },
137
- # provincial_sales_taxes: { '_ALL': no_taxes }
138
- # },
174
+ sales_tax: {'_ALL': build_fee_struct(0.05, 0, 0, 0)},
175
+ provincial_sales_taxes: {'_ALL': NO_TAX}
176
+ }],
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
+ }],
139
183
  # 'DK' => {
140
184
  # sales_tax_website_name: 'VAT',
141
185
  # sales_tax: { '_ALL': build_fee_struct(0.25, 0, 0, 0) },
@@ -176,11 +220,12 @@ module Easyship
176
220
  # sales_tax: { '_ALL': build_fee_struct(0.1, 0, 0, 0) },
177
221
  # provincial_sales_taxes: { '_ALL': no_taxes }
178
222
  # },
179
- 'NZ' => {
223
+ 'NZ' => [{
224
+ effective_timestamp: DEFAULT_EFFECTIVE_TIMESTAMP,
180
225
  sales_tax_website_name: 'VAT',
181
- sales_tax: { '_ALL': build_fee_struct(0.15, 0.15, 0.15, 0.15) },
182
- provincial_sales_taxes: { '_ALL': no_taxes }
183
- },
226
+ sales_tax: {'_ALL': build_fee_struct(0.15, 0.15, 0.15, 0.15)},
227
+ provincial_sales_taxes: {'_ALL': NO_TAX}
228
+ }],
184
229
  # 'IN' => {
185
230
  # sales_tax_website_name: 'VAT',
186
231
  # sales_tax: { '_ALL': build_fee_struct(0.15, 0, 0, 0) },
@@ -196,7 +241,9 @@ module Easyship
196
241
  # sales_tax: { '_ALL': build_fee_struct(0.16, 0, 0, 0) },
197
242
  # provincial_sales_taxes: { '_ALL': no_taxes }
198
243
  # }
199
- }.freeze
244
+ }.transform_values { |value| value.sort_by { |rates| -rates[:effective_timestamp].to_i } }.freeze
245
+
246
+ DEFAULT_EFFECTIVE_COUNTRY_ALPHA2S = DOMESTIC.keys.freeze
200
247
  end
201
248
  end
202
249
  end
@@ -1,7 +1,7 @@
1
1
  module Easyship
2
2
  module SalesTax
3
3
  module Calculator
4
- VERSION = "0.5.0"
4
+ VERSION = "0.7.0"
5
5
  end
6
6
  end
7
7
  end
@@ -6,33 +6,45 @@ module Easyship
6
6
  module Calculator
7
7
  class Error < StandardError; end
8
8
 
9
- def self.calculate(origin_country_alpha2: nil, destination_country_alpha2: nil, origin_state: nil, destination_state: nil, fees: nil)
10
- return fallback_value if origin_country_alpha2.nil? || origin_country_alpha2.empty? || destination_country_alpha2.nil? || destination_country_alpha2.empty?
11
- return fallback_value unless fees.is_a?(Fee)
9
+ FALLBACK_VALUE = {sales_tax: 0, provincial_sales_tax: 0}.freeze
10
+
11
+ def self.calculate(origin_country_alpha2: nil, destination_country_alpha2: nil, origin_state: nil, destination_state: nil, fees: nil, effective_timestamp: nil, effective_country_alpha2s: [])
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)
12
14
 
13
15
  if domestic?(origin_country_alpha2, destination_country_alpha2) || within_eu?(origin_country_alpha2, destination_country_alpha2)
14
- domestic_value(origin_country_alpha2: origin_country_alpha2, origin_state: origin_state, destination_state: destination_state, fees: fees)
16
+ domestic_value(
17
+ origin_country_alpha2: origin_country_alpha2,
18
+ origin_state: origin_state,
19
+ destination_state: destination_state,
20
+ fees: fees,
21
+ effective_timestamp: effective_timestamp,
22
+ effective_country_alpha2s: effective_country_alpha2s
23
+ )
15
24
  else
16
- fallback_value
25
+ FALLBACK_VALUE
17
26
  end
18
27
  rescue
19
- fallback_value
28
+ FALLBACK_VALUE
29
+ end
30
+
31
+ def self.sales_tax_website_name(country_alpha2, effective_timestamp = nil)
32
+ rates = Formula.effective_data(country_alpha2, effective_timestamp)
33
+ (rates || {}).dig(:sales_tax_website_name)
20
34
  end
21
35
 
22
- def self.domestic_value(origin_country_alpha2: nil, origin_state: nil, destination_state: nil, fees: nil)
23
- domestic_rates = Formula::DOMESTIC[origin_country_alpha2.to_s]
24
- return fallback_value if domestic_rates.nil? || domestic_rates.empty?
36
+ private
37
+
38
+ def self.domestic_value(origin_country_alpha2: nil, origin_state: nil, destination_state: nil, fees: nil, effective_timestamp: nil, effective_country_alpha2s: [])
39
+ rates = Formula.effective_data(origin_country_alpha2, effective_timestamp, effective_country_alpha2s)
40
+ return FALLBACK_VALUE if rates.nil?
25
41
 
26
42
  {
27
- sales_tax: tax_calculate(domestic_rates[:sales_tax], nil, destination_state, fees, false),
28
- provincial_sales_tax: tax_calculate(domestic_rates[:provincial_sales_taxes], origin_state, destination_state, fees, true)
43
+ sales_tax: tax_calculate(rates[:sales_tax], nil, destination_state, fees, false),
44
+ provincial_sales_tax: tax_calculate(rates[:provincial_sales_taxes], origin_state, destination_state, fees, true)
29
45
  }
30
46
  end
31
47
 
32
- def self.fallback_value
33
- { sales_tax: 0, provincial_sales_tax: 0 }
34
- end
35
-
36
48
  def self.tax_calculate(formula, origin_state, destination_state, fees, is_provincial_sales_taxes)
37
49
  if (tax_destination_formula = formula[destination_state.to_s.upcase])
38
50
  # ONLY CA has provincial_sales_taxes(QST/PST) and it only apply when ship within same state
@@ -46,11 +58,6 @@ module Easyship
46
58
  end
47
59
  end
48
60
 
49
- def self.sales_tax_website_name(country_alpha2)
50
- return if Formula::DOMESTIC[country_alpha2.to_s].nil? || Formula::DOMESTIC[country_alpha2.to_s].empty?
51
- Formula::DOMESTIC[country_alpha2][:sales_tax_website_name]
52
- end
53
-
54
61
  def self.in_eu?(country_alpha2)
55
62
  %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
63
  end
@@ -23,8 +23,6 @@ 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.add_development_dependency "bundler", "~> 1.17"
27
- spec.add_development_dependency "rake", "~> 10.0"
28
26
  spec.add_development_dependency "rspec", "~> 3.0"
29
27
  spec.add_development_dependency "byebug"
30
28
  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.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aloha Chen
@@ -9,36 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-11-11 00:00:00.000000000 Z
12
+ date: 2023-01-31 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: bundler
16
- requirement: !ruby/object:Gem::Requirement
17
- requirements:
18
- - - "~>"
19
- - !ruby/object:Gem::Version
20
- version: '1.17'
21
- type: :development
22
- prerelease: false
23
- version_requirements: !ruby/object:Gem::Requirement
24
- requirements:
25
- - - "~>"
26
- - !ruby/object:Gem::Version
27
- version: '1.17'
28
- - !ruby/object:Gem::Dependency
29
- name: rake
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: '10.0'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '10.0'
42
14
  - !ruby/object:Gem::Dependency
43
15
  name: rspec
44
16
  requirement: !ruby/object:Gem::Requirement