europe 0.0.27 → 0.0.28

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: d0918fb1141de821b11aff7fb2d11da6ef7c75b2870e3b90a1373cfd26dc5fbe
4
- data.tar.gz: f7b250f54690746d5339f6209625238461b7dd7edcc4d58adad5e095660186be
3
+ metadata.gz: 988b8443805f27f2cb40846b2a2cef3cee76ac464f5c134f833c0ba63acdfce7
4
+ data.tar.gz: '087c687254747b8d653dad7f33de58f52474acfe5211b8a17a1e3be8d3d12fcb'
5
5
  SHA512:
6
- metadata.gz: f57c86ea54e7539887b3f8674035bca535379c5fcc1b532e4a995f0d67c3435f34e5195487db2ecabbbe6c3a0d1846ae70b232ce18ab9d2436dadd2bb7ef6864
7
- data.tar.gz: d05a4f28813638f846d0e507c60e7f4f731d843914448c85b68ad3b4011f61784bb8dd15a501fd5bfdbd2c21c79ba7aa2692f47bece6701444dbf09bb77276a3
6
+ metadata.gz: 70a9e075c5b0c73d8472eb32729c6641a8a365a728be765f5b567516fface3113a2d6fd7751408af1c4aab67e6455a2b138bb92e99fbec41436ce4ae7fd5eb06
7
+ data.tar.gz: b03c256f4cb9e82129fd549dd57d38681df03877df510b6f224c25f9ddfa2125a44cb1279868d58f487d45971100461cf74722527437552d2db941e0921fc839
data/.rubocop.yml CHANGED
@@ -19,3 +19,15 @@ Style/CaseLikeIf:
19
19
 
20
20
  Naming/PredicateMethod:
21
21
  Enabled: false
22
+
23
+ Metrics/ModuleLength:
24
+ Max: 150
25
+ CountAsOne: ['hash', 'array']
26
+
27
+ Metrics/ClassLength:
28
+ Exclude:
29
+ - 'test/**/*'
30
+
31
+ Metrics/MethodLength:
32
+ Exclude:
33
+ - 'test/**/*'
data/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
  This project adheres to [Semantic Versioning](http://semver.org/).
4
+ ## 0.0.28
5
+ - Added support for batch VAT checks
6
+ - Updated fallback rates for Estonia and Romania
7
+ - [Full Changelog](https://github.com/gem-shards/europe.rb/compare/v0.0.27...v0.0.28)
4
8
  ## 0.0.27
5
9
  - Updated VIES VAT check service to their REST API instead of their SOAP API
6
10
  - Improved error handling for the VAT check service
data/README.md CHANGED
@@ -7,6 +7,7 @@ This gem provides EU governmental data, extracted from various EU / EC websites.
7
7
  - [Installation](#installation)
8
8
  - [Usage](#usage)
9
9
  - [Validating VAT numbers](#validating-vat-numbers)
10
+ - [Batch VAT validation](#batch-vat-validation)
10
11
  - [Validate VAT number format](#validate-vat-number-format)
11
12
  - [Validate Postal code format](#validate-postal-code-format)
12
13
  - [Retrieving VAT rates for each EC/EU member](#retrieving-vat-rates-for-each-eceu-member)
@@ -55,6 +56,34 @@ Response
55
56
  :address => nil }
56
57
  ```
57
58
 
59
+ ### Batch VAT validation
60
+ Validate 3-100 VAT numbers in one request using the VIES batch API. The process is asynchronous: submit numbers, poll for status, then download the report.
61
+
62
+ **Step 1: Submit batch**
63
+ ```ruby
64
+ result = Europe::Vat::Batch.validate(['NL009291477B01', 'BE0123456789', 'DE123456789'])
65
+ # => { token: "a1b2c3d4-..." }
66
+ ```
67
+ Optionally pass your own VAT number as requester:
68
+ ```ruby
69
+ result = Europe::Vat::Batch.validate(vat_numbers, requester: 'NL009291477B01')
70
+ ```
71
+
72
+ **Step 2: Check status**
73
+ ```ruby
74
+ Europe::Vat::Batch.status(result[:token])
75
+ # => { token: "a1b2c3d4-...", filename: "batch.csv", status: :completed,
76
+ # percentage: 100, created_at: "2026-06-08...", completed_at: "2026-06-08..." }
77
+ ```
78
+
79
+ **Step 3: Download report**
80
+ ```ruby
81
+ report = Europe::Vat::Batch.result(result[:token])
82
+ # => { data: "...csv content...", content_type: "text/csv", filename: "report.csv" }
83
+ ```
84
+
85
+ All methods return error symbols on failure (`:timeout`, `:service_unavailable`, `:too_few_rows`, `:too_many_rows`, etc.).
86
+
58
87
  ### Validate VAT number format
59
88
  Call
60
89
  ```ruby
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'securerandom'
7
+
8
+ module Europe
9
+ module Vat
10
+ # Batch VAT validation via VIES REST API
11
+ module Batch
12
+ BATCH_API_URL = 'https://ec.europa.eu/taxation_customs/vies/rest-api/vat-validation'
13
+ BATCH_REPORT_URL = 'https://ec.europa.eu/taxation_customs/vies/rest-api/vat-validation-report'
14
+
15
+ BATCH_ERRORS = {
16
+ 'VOW-ERR-6000' => :invalid_file_extension,
17
+ 'VOW-ERR-6001' => :too_many_rows,
18
+ 'VOW-ERR-6002' => :too_few_rows,
19
+ 'VOW-ERR-6005' => :filter_violation,
20
+ 'VOW-ERR-6006' => :token_not_found,
21
+ 'VOW-ERR-6007' => :invalid_file_structure,
22
+ 'VOW-ERR-6008' => :invalid_file_structure,
23
+ 'VOW-ERR-6011' => :file_too_large
24
+ }.freeze
25
+
26
+ BATCH_MIN_ROWS = 3
27
+ BATCH_MAX_ROWS = 100
28
+
29
+ HEADERS = {
30
+ 'Content-Type' => 'application/json',
31
+ 'Accept' => 'application/json'
32
+ }.freeze
33
+
34
+ class << self
35
+ def validate(vat_numbers, requester: nil)
36
+ return :too_few_rows if vat_numbers.size < BATCH_MIN_ROWS
37
+ return :too_many_rows if vat_numbers.size > BATCH_MAX_ROWS
38
+
39
+ with_error_handling do
40
+ body = parse_json_response(upload(generate_csv(vat_numbers, requester)))
41
+ return body if body.is_a?(Symbol)
42
+
43
+ { token: body['token'] }
44
+ end
45
+ end
46
+
47
+ def status(token)
48
+ with_error_handling do
49
+ body = parse_json_response(http_get("#{BATCH_API_URL}/#{token}", HEADERS))
50
+ return body if body.is_a?(Symbol)
51
+
52
+ {
53
+ token: body['token'], filename: body['filename'],
54
+ status: body['status']&.downcase&.to_sym, percentage: body['percentage'],
55
+ created_at: body['creationDateTime'], completed_at: body['completionTime']
56
+ }
57
+ end
58
+ end
59
+
60
+ def result(token)
61
+ with_error_handling do
62
+ response = http_get("#{BATCH_REPORT_URL}/#{token}")
63
+ return handle_error(response) unless response.is_a?(Net::HTTPSuccess)
64
+
65
+ error = check_json_error(response)
66
+ return error if error
67
+
68
+ { data: response.body, content_type: response['Content-Type'], filename: extract_filename(response) }
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def with_error_handling
75
+ yield
76
+ rescue Net::OpenTimeout, Net::ReadTimeout
77
+ :timeout
78
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, SocketError, OpenSSL::SSL::SSLError
79
+ :service_unavailable
80
+ rescue JSON::ParserError
81
+ :service_error
82
+ end
83
+
84
+ def http_get(url, headers = {})
85
+ uri = URI.parse(url)
86
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
87
+ http.request(Net::HTTP::Get.new(uri.request_uri, headers))
88
+ end
89
+ end
90
+
91
+ def parse_json_response(response)
92
+ return handle_error(response) unless response.is_a?(Net::HTTPSuccess)
93
+
94
+ body = JSON.parse(response.body)
95
+ return handle_error_body(body) if body['actionSucceed'] == false
96
+
97
+ body
98
+ end
99
+
100
+ def check_json_error(response)
101
+ return unless response['Content-Type']&.include?('application/json')
102
+
103
+ body = JSON.parse(response.body)
104
+ handle_error_body(body) if body['actionSucceed'] == false
105
+ end
106
+
107
+ def extract_filename(response)
108
+ disposition = response['Content-Disposition']
109
+ return unless disposition
110
+
111
+ disposition.match(/filename="?(.+?)"?(?:;|$)/)&.captures&.first
112
+ end
113
+
114
+ def handle_error(response)
115
+ body = JSON.parse(response.body)
116
+ handle_error_body(body)
117
+ rescue JSON::ParserError
118
+ :service_error
119
+ end
120
+
121
+ def handle_error_body(body)
122
+ error_code = body.dig('errorWrappers', 0, 'error')
123
+ BATCH_ERRORS[error_code] || Vat::VIES_ERRORS[error_code] || :service_error
124
+ end
125
+
126
+ def generate_csv(vat_numbers, requester)
127
+ requester_cc = requester ? requester[0..1] : ''
128
+ requester_vat = requester ? requester[2..-1] : ''
129
+ rows = ['"MS Code","VAT Number","Requester MS Code","Requester VAT Number"']
130
+ vat_numbers.each do |vat|
131
+ rows << "\"#{vat[0..1]}\",\"#{vat[2..-1]}\",\"#{requester_cc}\",\"#{requester_vat}\""
132
+ end
133
+ "#{rows.join("\n")}\n"
134
+ end
135
+
136
+ def upload(csv_content)
137
+ uri = URI.parse(BATCH_API_URL)
138
+ boundary = "EuropeGem#{SecureRandom.hex(16)}"
139
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
140
+ request = Net::HTTP::Post.new(uri.request_uri)
141
+ request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
142
+ request['Accept'] = 'application/json'
143
+ request.body = build_multipart_body(csv_content, boundary)
144
+ http.request(request)
145
+ end
146
+ end
147
+
148
+ def build_multipart_body(csv_content, boundary)
149
+ "--#{boundary}\r\n" \
150
+ "Content-Disposition: form-data; name=\"fileToUpload\"; filename=\"batch.csv\"\r\n" \
151
+ "Content-Type: text/csv\r\n\r\n" \
152
+ "#{csv_content}" \
153
+ "\r\n--#{boundary}--\r\n"
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -7,10 +7,10 @@ module Europe
7
7
  # Rates
8
8
  module Rates
9
9
  FALLBACK_RATES = {
10
- AT: 20.0, BE: 21.0, BG: 20.0, CY: 19.0, CZ: 21.0, DE: 19.0, DK: 25.0, EE: 22.0,
10
+ AT: 20.0, BE: 21.0, BG: 20.0, CY: 19.0, CZ: 21.0, DE: 19.0, DK: 25.0, EE: 24.0,
11
11
  EL: 24.0, ES: 21.0, FI: 25.5, FR: 20.0, HR: 25.0, HU: 27.0, IE: 23.0,
12
12
  IT: 22.0, LT: 21.0, LU: 17.0, LV: 21.0, MT: 18.0, NL: 21.0, PL: 23.0, PT: 23.0,
13
- RO: 19.0, SE: 25.0, SI: 22.0, SK: 23.0
13
+ RO: 21.0, SE: 25.0, SI: 22.0, SK: 23.0
14
14
  }.freeze
15
15
  RATES_URL = 'https://raw.githubusercontent.com/benbucksch/eu-vat-rates' \
16
16
  '/refs/heads/master/rates.json'
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'europe/vat/rates'
4
4
  require 'europe/vat/format'
5
+ require 'europe/vat/batch'
5
6
  require 'uri'
6
7
  require 'net/http'
7
8
  require 'json'
@@ -39,6 +40,34 @@ module Europe
39
40
  :service_unavailable
40
41
  end
41
42
 
43
+ def self.validate_with_country_code(country_code, number)
44
+ return :invalid_input if number.size < 4
45
+
46
+ # Remove country code from number if it's present
47
+ number = number[2..-1] if number[0..1].upcase.to_s == country_code.to_s
48
+
49
+ response = send_request(country_code, number)
50
+ return handle_error_response(response) unless response.is_a?(Net::HTTPSuccess)
51
+
52
+ setup_response(response)
53
+ rescue Net::OpenTimeout, Net::ReadTimeout
54
+ :timeout
55
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, SocketError, OpenSSL::SSL::SSLError
56
+ :service_unavailable
57
+ end
58
+
59
+ def self.batch_validate(vat_numbers, requester: nil)
60
+ Batch.validate(vat_numbers, requester: requester)
61
+ end
62
+
63
+ def self.batch_status(token)
64
+ Batch.status(token)
65
+ end
66
+
67
+ def self.batch_result(token)
68
+ Batch.result(token)
69
+ end
70
+
42
71
  def self.handle_error_response(response)
43
72
  body = JSON.parse(response.body)
44
73
  error_code = body.dig('errorWrappers', 0, 'error')
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Europe version
4
4
  module Europe
5
- VERSION = '0.0.27'
5
+ VERSION = '0.0.28'
6
6
  end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ module Europe
6
+ module Vat
7
+ BATCH_SUCCESS_BODY = '{"token":"test-token-uuid"}'
8
+ BATCH_STRUCTURE_ERROR_BODY = '{"actionSucceed":false,"errorWrappers":' \
9
+ '[{"error":"VOW-ERR-6008","message":' \
10
+ '"Your file does not match the expected structure"}]}'
11
+ BATCH_TOKEN_NOT_FOUND_BODY = '{"actionSucceed":false,"errorWrappers":' \
12
+ '[{"error":"VOW-ERR-6006","message":' \
13
+ '"The token is not valid"}]}'
14
+ BATCH_STATUS_PROCESSING_BODY = '{"token":"test-token-uuid","filename":"batch.csv",' \
15
+ '"creationDateTime":"2026-06-08T09:47:13.720Z",' \
16
+ '"status":"PROCESSING","processingStartTime":' \
17
+ '"2026-06-08T09:47:13.852Z","percentage":66.67}'
18
+ BATCH_STATUS_COMPLETED_BODY = '{"token":"test-token-uuid","filename":"batch.csv",' \
19
+ '"creationDateTime":"2026-06-08T09:47:13.720Z",' \
20
+ '"status":"COMPLETED","processingStartTime":' \
21
+ '"2026-06-08T09:47:13.852Z","completionTime":' \
22
+ '"2026-06-08T09:47:49.937Z","percentage":100.0}'
23
+
24
+ class BatchValidationTest < Minitest::Test
25
+ VAT_NUMBERS = %w[NL009291477B01 DE115235681 FR40303265045].freeze
26
+
27
+ def setup
28
+ WebMock.enable!
29
+ end
30
+
31
+ def teardown
32
+ WebMock.disable!
33
+ end
34
+
35
+ def test_batch_validate_returns_token
36
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL)
37
+ .to_return(status: 200, body: BATCH_SUCCESS_BODY,
38
+ headers: { 'Content-Type' => 'application/json' })
39
+
40
+ result = Europe::Vat::Batch.validate(VAT_NUMBERS)
41
+ assert_equal({ token: 'test-token-uuid' }, result)
42
+ end
43
+
44
+ def test_batch_validate_too_few_rows
45
+ assert_equal :too_few_rows, Europe::Vat::Batch.validate(%w[NL009291477B01 DE115235681])
46
+ end
47
+
48
+ def test_batch_validate_too_many_rows
49
+ numbers = (1..101).map { |i| "NL#{i.to_s.rjust(12, '0')}" }
50
+ assert_equal :too_many_rows, Europe::Vat::Batch.validate(numbers)
51
+ end
52
+
53
+ def test_batch_validate_with_requester
54
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL)
55
+ .with { |req| req.body.include?('NL') && req.body.include?('009291477B01') }
56
+ .to_return(status: 200, body: BATCH_SUCCESS_BODY,
57
+ headers: { 'Content-Type' => 'application/json' })
58
+
59
+ result = Europe::Vat::Batch.validate(VAT_NUMBERS, requester: 'NL009291477B01')
60
+ assert_equal({ token: 'test-token-uuid' }, result)
61
+ end
62
+
63
+ def test_batch_validate_structure_error
64
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL)
65
+ .to_return(status: 400, body: BATCH_STRUCTURE_ERROR_BODY,
66
+ headers: { 'Content-Type' => 'application/json' })
67
+
68
+ assert_equal :invalid_file_structure, Europe::Vat::Batch.validate(VAT_NUMBERS)
69
+ end
70
+
71
+ def test_batch_validate_timeout
72
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL).to_timeout
73
+
74
+ assert_equal :timeout, Europe::Vat::Batch.validate(VAT_NUMBERS)
75
+ end
76
+
77
+ def test_batch_validate_connection_refused
78
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL).to_raise(Errno::ECONNREFUSED)
79
+
80
+ assert_equal :service_unavailable, Europe::Vat::Batch.validate(VAT_NUMBERS)
81
+ end
82
+
83
+ def test_batch_status_processing
84
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/test-token-uuid")
85
+ .to_return(status: 200, body: BATCH_STATUS_PROCESSING_BODY,
86
+ headers: { 'Content-Type' => 'application/json' })
87
+
88
+ result = Europe::Vat::Batch.status('test-token-uuid')
89
+ assert_equal :processing, result[:status]
90
+ assert_equal 66.67, result[:percentage]
91
+ end
92
+
93
+ def test_batch_status_completed
94
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/test-token-uuid")
95
+ .to_return(status: 200, body: BATCH_STATUS_COMPLETED_BODY,
96
+ headers: { 'Content-Type' => 'application/json' })
97
+
98
+ result = Europe::Vat::Batch.status('test-token-uuid')
99
+ assert_equal :completed, result[:status]
100
+ assert_equal 100.0, result[:percentage]
101
+ assert_equal 'test-token-uuid', result[:token]
102
+ refute_nil result[:completed_at]
103
+ end
104
+
105
+ def test_batch_status_invalid_token
106
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/bad-token")
107
+ .to_return(status: 400, body: BATCH_TOKEN_NOT_FOUND_BODY,
108
+ headers: { 'Content-Type' => 'application/json' })
109
+
110
+ assert_equal :token_not_found, Europe::Vat::Batch.status('bad-token')
111
+ end
112
+
113
+ def test_batch_status_timeout
114
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/test-token-uuid").to_timeout
115
+
116
+ assert_equal :timeout, Europe::Vat::Batch.status('test-token-uuid')
117
+ end
118
+
119
+ def test_batch_result_returns_data
120
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_REPORT_URL}/test-token-uuid")
121
+ .to_return(
122
+ status: 200,
123
+ body: 'fake-xlsx-binary-data',
124
+ headers: {
125
+ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
126
+ 'Content-Disposition' => 'attachment; filename="report.xlsx"'
127
+ }
128
+ )
129
+
130
+ result = Europe::Vat::Batch.result('test-token-uuid')
131
+ assert_equal 'fake-xlsx-binary-data', result[:data]
132
+ assert_equal 'report.xlsx', result[:filename]
133
+ assert result[:content_type].include?('spreadsheetml')
134
+ end
135
+
136
+ def test_batch_result_invalid_token
137
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_REPORT_URL}/bad-token")
138
+ .to_return(status: 400, body: BATCH_TOKEN_NOT_FOUND_BODY,
139
+ headers: { 'Content-Type' => 'application/json' })
140
+
141
+ assert_equal :token_not_found, Europe::Vat::Batch.result('bad-token')
142
+ end
143
+
144
+ def test_batch_result_timeout
145
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_REPORT_URL}/test-token-uuid").to_timeout
146
+
147
+ assert_equal :timeout, Europe::Vat::Batch.result('test-token-uuid')
148
+ end
149
+
150
+ def test_generate_batch_csv_format
151
+ csv = Europe::Vat::Batch.send(:generate_csv, VAT_NUMBERS, nil)
152
+ lines = csv.strip.split("\n")
153
+
154
+ assert_equal 4, lines.size
155
+ assert_equal '"MS Code","VAT Number","Requester MS Code","Requester VAT Number"', lines[0]
156
+ assert_equal '"NL","009291477B01","",""', lines[1]
157
+ assert_equal '"DE","115235681","",""', lines[2]
158
+ assert_equal '"FR","40303265045","",""', lines[3]
159
+ end
160
+
161
+ def test_generate_batch_csv_with_requester
162
+ csv = Europe::Vat::Batch.send(:generate_csv, VAT_NUMBERS, 'NL009291477B01')
163
+ lines = csv.strip.split("\n")
164
+
165
+ assert_equal '"NL","009291477B01","NL","009291477B01"', lines[1]
166
+ end
167
+
168
+ # Delegation tests — ensure Europe::Vat.batch_* still works
169
+ def test_delegation_batch_validate
170
+ stub_request(:post, Europe::Vat::Batch::BATCH_API_URL)
171
+ .to_return(status: 200, body: BATCH_SUCCESS_BODY,
172
+ headers: { 'Content-Type' => 'application/json' })
173
+
174
+ result = Europe::Vat.batch_validate(VAT_NUMBERS)
175
+ assert_equal({ token: 'test-token-uuid' }, result)
176
+ end
177
+
178
+ def test_delegation_batch_status
179
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_API_URL}/test-token-uuid")
180
+ .to_return(status: 200, body: BATCH_STATUS_COMPLETED_BODY,
181
+ headers: { 'Content-Type' => 'application/json' })
182
+
183
+ result = Europe::Vat.batch_status('test-token-uuid')
184
+ assert_equal :completed, result[:status]
185
+ end
186
+
187
+ def test_delegation_batch_result
188
+ stub_request(:get, "#{Europe::Vat::Batch::BATCH_REPORT_URL}/test-token-uuid")
189
+ .to_return(
190
+ status: 200,
191
+ body: 'fake-xlsx-binary-data',
192
+ headers: {
193
+ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
194
+ 'Content-Disposition' => 'attachment; filename="report.xlsx"'
195
+ }
196
+ )
197
+
198
+ result = Europe::Vat.batch_result('test-token-uuid')
199
+ assert_equal 'fake-xlsx-binary-data', result[:data]
200
+ end
201
+ end
202
+ end
203
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: europe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.27
4
+ version: 0.0.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gem shards
@@ -165,6 +165,7 @@ files:
165
165
  - lib/europe/postal/postal.rb
166
166
  - lib/europe/rake_task.rb
167
167
  - lib/europe/tasks/eurostat.rake
168
+ - lib/europe/vat/batch.rb
168
169
  - lib/europe/vat/format.rb
169
170
  - lib/europe/vat/rates.rb
170
171
  - lib/europe/vat/vat.rb
@@ -174,6 +175,7 @@ files:
174
175
  - test/europe/currency/exchange_rates_test.rb
175
176
  - test/europe/eurostat/eurostat_test.rb
176
177
  - test/europe/postal/format_test.rb
178
+ - test/europe/vat/batch_validation_test.rb
177
179
  - test/europe/vat/format_test.rb
178
180
  - test/europe/vat/rates_test.rb
179
181
  - test/europe/vat/validation_test.rb
@@ -209,6 +211,7 @@ test_files:
209
211
  - test/europe/currency/exchange_rates_test.rb
210
212
  - test/europe/eurostat/eurostat_test.rb
211
213
  - test/europe/postal/format_test.rb
214
+ - test/europe/vat/batch_validation_test.rb
212
215
  - test/europe/vat/format_test.rb
213
216
  - test/europe/vat/rates_test.rb
214
217
  - test/europe/vat/validation_test.rb