tin_valid 0.1.1 → 1.1.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 +4 -4
- data/.rspec +0 -1
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +27 -1
- data/README.md +81 -2
- data/lib/tin_valid/austria_tin.rb +1 -0
- data/lib/tin_valid/belgium_tin.rb +11 -15
- data/lib/tin_valid/bulgaria_tin.rb +10 -16
- data/lib/tin_valid/croatia_tin.rb +7 -1
- data/lib/tin_valid/cyprus_tin.rb +5 -2
- data/lib/tin_valid/czechia_tin.rb +24 -23
- data/lib/tin_valid/denmark_tin.rb +10 -8
- data/lib/tin_valid/estonia_tin.rb +7 -8
- data/lib/tin_valid/finland_tin.rb +71 -0
- data/lib/tin_valid/france_tin.rb +28 -0
- data/lib/tin_valid/germany_tin.rb +16 -5
- data/lib/tin_valid/greece_tin.rb +10 -2
- data/lib/tin_valid/helpers.rb +14 -0
- data/lib/tin_valid/hungary_tin.rb +7 -3
- data/lib/tin_valid/ireland_tin.rb +7 -1
- data/lib/tin_valid/italy_tin.rb +161 -0
- data/lib/tin_valid/latvia_tin.rb +63 -0
- data/lib/tin_valid/lithuania_tin.rb +75 -0
- data/lib/tin_valid/luxembourg_tin.rb +106 -0
- data/lib/tin_valid/malta_tin.rb +51 -0
- data/lib/tin_valid/netherlands_tin.rb +36 -0
- data/lib/tin_valid/poland_tin.rb +79 -0
- data/lib/tin_valid/portugal_tin.rb +46 -0
- data/lib/tin_valid/romania_tin.rb +83 -0
- data/lib/tin_valid/slovakia_tin.rb +67 -0
- data/lib/tin_valid/slovenia_tin.rb +38 -0
- data/lib/tin_valid/spain_tin.rb +51 -0
- data/lib/tin_valid/sweden_tin.rb +7 -9
- data/lib/tin_valid/united_kingdom_tin.rb +32 -0
- data/lib/tin_valid/version.rb +1 -1
- data/lib/tin_valid.rb +16 -0
- metadata +18 -2
@@ -1,7 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module TinValid
|
4
|
-
class IrelandTin
|
4
|
+
class IrelandTin
|
5
|
+
def initialize(tin:)
|
6
|
+
@tin = tin
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :tin
|
10
|
+
|
5
11
|
def valid?
|
6
12
|
/\A[0-9]{7}[A-W][A-IW]?\z/.match?(tin) &&
|
7
13
|
tin.chars[7] == check
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinValid
|
4
|
+
# rubocop:disable Metrics/ClassLength
|
5
|
+
class ItalyTin
|
6
|
+
include TinValid::Helpers
|
7
|
+
|
8
|
+
def initialize(tin:, birth_date: nil)
|
9
|
+
@tin = tin
|
10
|
+
@birth_date = birth_date
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :tin, :birth_date
|
14
|
+
|
15
|
+
def valid?
|
16
|
+
match = MATCHER.match(tin)
|
17
|
+
return false unless match
|
18
|
+
return false unless valid_date?(match)
|
19
|
+
|
20
|
+
tin[-1] == check
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def valid_date?(match)
|
26
|
+
# C7,C8: Two last digits of a year.
|
27
|
+
year = replacement_to_i(match[:year])
|
28
|
+
|
29
|
+
# C9: A letter representing a month;
|
30
|
+
month = MONTH_LETTERS.fetch(match[:month])
|
31
|
+
|
32
|
+
# C10,C11: Day of month (in the range 1...31 for men)
|
33
|
+
# or day of month + 40 (in the range 41...71 for women).
|
34
|
+
day = replacement_to_i(match[:day])
|
35
|
+
day -= 40 if day > 40
|
36
|
+
|
37
|
+
if birth_date
|
38
|
+
birth_date == date("#{birth_century}#{year}", month, day)
|
39
|
+
else
|
40
|
+
date("19#{year}", month, day) || date("20#{year}", month, day)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def check
|
45
|
+
# 1. Each of the first fifteen characters,depending on its relevant
|
46
|
+
# position (even or odd), is converted into a numeric value,according to
|
47
|
+
# correspondence shown in the tables below:
|
48
|
+
sum = tin[..14].chars.each.with_index(1).sum do |char, index|
|
49
|
+
index.even? ? even_number(char) : odd_number(char)
|
50
|
+
end
|
51
|
+
|
52
|
+
# 2. The numerical values thus determined are added together and their sum
|
53
|
+
# is divided by 26. The check character (C16) is obtained by converting
|
54
|
+
# the remainder of the division in the corresponding alphabetic character
|
55
|
+
# according to the table below:
|
56
|
+
((sum % 26) + 65).chr
|
57
|
+
end
|
58
|
+
|
59
|
+
def birth_century = birth_date.strftime("%Y")[..1]
|
60
|
+
|
61
|
+
def replacement_to_i(string)
|
62
|
+
string.chars.map { NUMERICAL_REPLACEMENTS.fetch(_1, _1) }.join.to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
MATCHER = %r{
|
66
|
+
\A
|
67
|
+
[A-Z]{6}
|
68
|
+
(?<year>[0-9LMNPQRSTUV]{2})
|
69
|
+
(?<month>[ABCDEHLMPRST])
|
70
|
+
(?<day>[0-7LMNPQRST][0-9LMNPQRSTUV])
|
71
|
+
[A-Z]
|
72
|
+
[0-9LMNPQRSTUV]{3}
|
73
|
+
[A-Z]
|
74
|
+
\z
|
75
|
+
}x
|
76
|
+
private_constant :MATCHER
|
77
|
+
|
78
|
+
# C9: A letter representing a month; the letter can only take the values:
|
79
|
+
MONTH_LETTERS = {
|
80
|
+
"A" => 1,
|
81
|
+
"B" => 2,
|
82
|
+
"C" => 3,
|
83
|
+
"D" => 4,
|
84
|
+
"E" => 5,
|
85
|
+
"H" => 6,
|
86
|
+
"L" => 7,
|
87
|
+
"M" => 8,
|
88
|
+
"P" => 9,
|
89
|
+
"R" => 10,
|
90
|
+
"S" => 11,
|
91
|
+
"T" => 12
|
92
|
+
}.freeze
|
93
|
+
private_constant :MONTH_LETTERS
|
94
|
+
|
95
|
+
NUMERICAL_REPLACEMENTS = {
|
96
|
+
"L" => 0,
|
97
|
+
"M" => 1,
|
98
|
+
"N" => 2,
|
99
|
+
"P" => 3,
|
100
|
+
"Q" => 4,
|
101
|
+
"R" => 5,
|
102
|
+
"S" => 6,
|
103
|
+
"T" => 7,
|
104
|
+
"U" => 8,
|
105
|
+
"V" => 9
|
106
|
+
}.freeze
|
107
|
+
private_constant :NUMERICAL_REPLACEMENTS
|
108
|
+
|
109
|
+
def even_number(character)
|
110
|
+
case character
|
111
|
+
in "0".."9" then character.to_i
|
112
|
+
in "A".."Z" then character.ord - 65
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def odd_number(character)
|
117
|
+
ODD_NUMBER_TABLE.fetch(character)
|
118
|
+
end
|
119
|
+
|
120
|
+
ODD_NUMBER_TABLE = {
|
121
|
+
"A" => 1,
|
122
|
+
"0" => 1,
|
123
|
+
"B" => 0,
|
124
|
+
"1" => 0,
|
125
|
+
"C" => 5,
|
126
|
+
"2" => 5,
|
127
|
+
"D" => 7,
|
128
|
+
"3" => 7,
|
129
|
+
"E" => 9,
|
130
|
+
"4" => 9,
|
131
|
+
"F" => 13,
|
132
|
+
"5" => 13,
|
133
|
+
"G" => 15,
|
134
|
+
"6" => 15,
|
135
|
+
"H" => 17,
|
136
|
+
"7" => 17,
|
137
|
+
"I" => 19,
|
138
|
+
"8" => 19,
|
139
|
+
"J" => 21,
|
140
|
+
"9" => 21,
|
141
|
+
"K" => 2,
|
142
|
+
"L" => 4,
|
143
|
+
"M" => 18,
|
144
|
+
"N" => 20,
|
145
|
+
"O" => 11,
|
146
|
+
"P" => 3,
|
147
|
+
"Q" => 6,
|
148
|
+
"R" => 8,
|
149
|
+
"S" => 12,
|
150
|
+
"T" => 14,
|
151
|
+
"U" => 16,
|
152
|
+
"V" => 10,
|
153
|
+
"W" => 22,
|
154
|
+
"X" => 25,
|
155
|
+
"Y" => 24,
|
156
|
+
"Z" => 23
|
157
|
+
}.freeze
|
158
|
+
private_constant :ODD_NUMBER_TABLE
|
159
|
+
end
|
160
|
+
# rubocop:enable Metrics/ClassLength
|
161
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinValid
|
4
|
+
class LatviaTin
|
5
|
+
def initialize(tin:, birth_date: nil)
|
6
|
+
@tin = tin
|
7
|
+
@birth_date = birth_date
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :tin, :birth_date
|
11
|
+
|
12
|
+
def valid? = valid_v1? || valid_v2?
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
MATCHER_V1 = %r{
|
17
|
+
\A
|
18
|
+
(?<day>[0-3][0-9])
|
19
|
+
(?<month>[0-1][0-9])
|
20
|
+
(?<year>[0-9]{2})
|
21
|
+
(?<century>[0-2])
|
22
|
+
-?
|
23
|
+
[0-9]{4}
|
24
|
+
\z
|
25
|
+
}x
|
26
|
+
private_constant :MATCHER_V1
|
27
|
+
|
28
|
+
MATCHER_V2 = %r{
|
29
|
+
\A
|
30
|
+
32
|
31
|
+
[0-9]{4}
|
32
|
+
-?
|
33
|
+
[0-9]{5}
|
34
|
+
\z
|
35
|
+
}x
|
36
|
+
private_constant :MATCHER_V2
|
37
|
+
|
38
|
+
def valid_v1?
|
39
|
+
match = MATCHER_V1.match(tin)
|
40
|
+
return false unless match
|
41
|
+
return false if tin == "00000000000"
|
42
|
+
|
43
|
+
if birth_date
|
44
|
+
tin[..5] == birth_date.strftime("%d%m%y") &&
|
45
|
+
tin[6] == birth_century_digit
|
46
|
+
else
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def valid_v2?
|
52
|
+
MATCHER_V2.match?(tin)
|
53
|
+
end
|
54
|
+
|
55
|
+
def birth_century_digit
|
56
|
+
case birth_date.year
|
57
|
+
when 1800..1899 then "0"
|
58
|
+
when 1900..1999 then "1"
|
59
|
+
when 2000.. then "2"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinValid
|
4
|
+
class LithuaniaTin
|
5
|
+
def initialize(tin:, birth_date: nil)
|
6
|
+
@tin = tin
|
7
|
+
@birth_date = birth_date
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :tin, :birth_date
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
matcher = MATCHER.match(tin)
|
14
|
+
return false unless matcher
|
15
|
+
|
16
|
+
if birth_date && (matcher[:birth_date] != birth_date.strftime("%y%m%d"))
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
|
20
|
+
tin[-1].to_i == check
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
MATCHER = %r{
|
26
|
+
[1-6]
|
27
|
+
(?<birth_date>
|
28
|
+
[0-9]{2}
|
29
|
+
[0-1][0-9]
|
30
|
+
[0-3][0-9]
|
31
|
+
)
|
32
|
+
[0-9]{4}
|
33
|
+
}x
|
34
|
+
private_constant :MATCHER
|
35
|
+
|
36
|
+
# rubocop:disable Metrics/AbcSize
|
37
|
+
# rubocop:disable Metrics/MethodLength
|
38
|
+
def check
|
39
|
+
# 1. Multiply the values of each position by the corresponding weight:
|
40
|
+
weights =
|
41
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 1]
|
42
|
+
.each_with_index
|
43
|
+
.map { |weight, index| weight * tin[index].to_i }
|
44
|
+
|
45
|
+
# 2. Add up the results of the above multiplications;
|
46
|
+
sum = weights.sum
|
47
|
+
|
48
|
+
# 3. Get modulo 11 of the result of the previous addition;
|
49
|
+
remainder = sum % 11
|
50
|
+
|
51
|
+
# 4. C11 = remainder if remainder is not 10;
|
52
|
+
return remainder if remainder != 10
|
53
|
+
|
54
|
+
# 5. If remainder is 10, calculate a new check digit with over
|
55
|
+
# corresponding weight:
|
56
|
+
weights =
|
57
|
+
[3, 4, 5, 6, 7, 8, 9, 1, 2, 3]
|
58
|
+
.each_with_index
|
59
|
+
.map { |weight, index| weight * tin[index].to_i }
|
60
|
+
|
61
|
+
# 6. Add up the results of the above multiplications;
|
62
|
+
sum = weights.sum
|
63
|
+
|
64
|
+
# 7. Get modulo 11 of the result of the previous addition;
|
65
|
+
remainder = sum % 11
|
66
|
+
|
67
|
+
# 8. C11 = remainder if remainder is not 10; if remainder is 10, C11 = 0.
|
68
|
+
return remainder if remainder != 10
|
69
|
+
|
70
|
+
0
|
71
|
+
end
|
72
|
+
# rubocop:enable Metrics/AbcSize
|
73
|
+
# rubocop:enable Metrics/MethodLength
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinValid
|
4
|
+
class LuxembourgTin
|
5
|
+
include TinValid::Helpers
|
6
|
+
|
7
|
+
def initialize(tin:, birth_date: nil)
|
8
|
+
@tin = tin
|
9
|
+
@birth_date = birth_date
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :tin, :birth_date
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
match = MATCHER.match(tin)
|
16
|
+
return false unless match
|
17
|
+
|
18
|
+
tin_date = date(match[:year], match[:month], match[:day])
|
19
|
+
return false unless tin_date
|
20
|
+
|
21
|
+
return false if birth_date && birth_date != tin_date
|
22
|
+
|
23
|
+
check1? && check2?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
MATCHER = %r{
|
29
|
+
\A
|
30
|
+
(?<year>[0-9]{4})
|
31
|
+
(?<month>[0-1][0-9])
|
32
|
+
(?<day>[0-3][0-9])
|
33
|
+
[0-9]{5}
|
34
|
+
\z
|
35
|
+
}x
|
36
|
+
private_constant :MATCHER
|
37
|
+
|
38
|
+
def check1?
|
39
|
+
# 1. Multiply the values of each position by the corresponding weight:
|
40
|
+
weights =
|
41
|
+
[2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
|
42
|
+
.each_with_index
|
43
|
+
.map { |weight, index| weight * tin[index].to_i }
|
44
|
+
|
45
|
+
# 2. If the product of a doubling operation is > 9, sum the digits of the
|
46
|
+
# product;
|
47
|
+
weights = weights.map { _1 > 9 ? _1.to_s.chars.sum(&:to_i) : _1 }
|
48
|
+
|
49
|
+
# 3. Add up the results of the above multiplications;
|
50
|
+
sum = weights.sum
|
51
|
+
|
52
|
+
# 4. Get modulo 10 of the result of the previous addition;
|
53
|
+
remainder = sum % 10
|
54
|
+
|
55
|
+
# 5. If remainder = 0, C12 is valid. Otherwise the TIN is not valid.
|
56
|
+
remainder == 0
|
57
|
+
end
|
58
|
+
|
59
|
+
def check2?
|
60
|
+
# 1. Create an array n containing the individual C1 to C11 and C13 of the
|
61
|
+
# TIN (where ni = the value of the corresponding C), taken from right to
|
62
|
+
# left:
|
63
|
+
array = [*tin[..10].chars, tin[12]].map(&:to_i).reverse
|
64
|
+
|
65
|
+
# 2. Initialize the checksum c to 0;
|
66
|
+
# 3. For each index i of the array n, starting at 0, replace c by
|
67
|
+
# d(c,p(i mod 8, ni)), according to the following tables:
|
68
|
+
checksum = array.each_with_index.inject(0) do |c, (ni, index)|
|
69
|
+
table_d(c, table_p(index % 8, ni))
|
70
|
+
end
|
71
|
+
|
72
|
+
# 4. Check digit c if c = 0, C13 is valid. Otherwise, the TIN is not
|
73
|
+
# valid.
|
74
|
+
checksum == 0
|
75
|
+
end
|
76
|
+
|
77
|
+
def table_d(number_i, number_j) = TABLE_D.dig(number_i, number_j)
|
78
|
+
def table_p(number_m, number_n) = TABLE_P.dig(number_m, number_n)
|
79
|
+
|
80
|
+
TABLE_D = [
|
81
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
82
|
+
[1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
|
83
|
+
[2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
|
84
|
+
[3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
|
85
|
+
[4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
|
86
|
+
[5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
|
87
|
+
[6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
|
88
|
+
[7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
|
89
|
+
[8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
|
90
|
+
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
|
91
|
+
].freeze
|
92
|
+
private_constant :TABLE_D
|
93
|
+
|
94
|
+
TABLE_P = [
|
95
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
96
|
+
[1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
|
97
|
+
[5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
|
98
|
+
[8, 9, 1, 6, 0, 5, 3, 5, 2, 7],
|
99
|
+
[9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
|
100
|
+
[4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
|
101
|
+
[2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
|
102
|
+
[7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
|
103
|
+
].freeze
|
104
|
+
private_constant :TABLE_P
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinValid
|
4
|
+
class MaltaTin
|
5
|
+
def initialize(tin:)
|
6
|
+
@tin = tin
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :tin
|
10
|
+
|
11
|
+
def valid? = valid_format_1? || valid_format_2?
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
MATCHER_V1 = %r{
|
16
|
+
\A
|
17
|
+
(?<number>
|
18
|
+
(?<part1>[0-9]{0,5})
|
19
|
+
[0-9]{1,2}
|
20
|
+
)
|
21
|
+
(?<letter>[MGAPLHBZ])
|
22
|
+
\z
|
23
|
+
}x
|
24
|
+
private_constant :MATCHER_V1
|
25
|
+
|
26
|
+
MATCHER_V2 = %r{
|
27
|
+
\A
|
28
|
+
(11|22|33|44|55|66|77|88)
|
29
|
+
[0-9]{7}
|
30
|
+
\z
|
31
|
+
}x
|
32
|
+
private_constant :MATCHER_V2
|
33
|
+
|
34
|
+
def valid_format_1?
|
35
|
+
match = MATCHER_V1.match(tin)
|
36
|
+
return false unless match
|
37
|
+
|
38
|
+
case match[:letter]
|
39
|
+
when "A", "P"
|
40
|
+
true
|
41
|
+
when "M", "G", "L", "H", "B", "Z"
|
42
|
+
(0..32_000).cover?(match[:part1].to_i) &&
|
43
|
+
match[:number].to_i != 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid_format_2?
|
48
|
+
MATCHER_V2.match?(tin)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinValid
|
4
|
+
class NetherlandsTin
|
5
|
+
def initialize(tin:)
|
6
|
+
@tin = tin
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :tin
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
return false unless /\A[0-9]{9}\z/.match?(tin)
|
13
|
+
return false if tin == "000000000"
|
14
|
+
|
15
|
+
tin[-1].to_i == check
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def check
|
21
|
+
# 1. Multiply the values of each position by the corresponding weight:
|
22
|
+
multipliers =
|
23
|
+
9
|
24
|
+
.downto(2)
|
25
|
+
.each_with_index
|
26
|
+
.map { |multiplier, position| multiplier * tin[position].to_i }
|
27
|
+
|
28
|
+
# 2. Add up the results of the above multiplications;
|
29
|
+
sum = multipliers.sum
|
30
|
+
|
31
|
+
# 3. Get modulo 11 of the result of the previous addition;
|
32
|
+
# 4. Check digit = remainder (if remainder = 10, the TIN is not valid).
|
33
|
+
sum % 11
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinValid
|
4
|
+
class PolandTin
|
5
|
+
include TinValid::Helpers
|
6
|
+
|
7
|
+
def initialize(tin:, birth_date: nil)
|
8
|
+
@tin = tin
|
9
|
+
@birth_date = birth_date
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :tin, :birth_date
|
13
|
+
|
14
|
+
def valid? = valid_v1? || valid_v2?
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
MATCHER_V1 = /\A[0-9]{10}\z/
|
19
|
+
private_constant :MATCHER_V1
|
20
|
+
|
21
|
+
MATCHER_V2 = %r{
|
22
|
+
\A
|
23
|
+
(?<year>[0-9]{2})
|
24
|
+
(?<month>[0-9]{2})
|
25
|
+
(?<day>[0-3][0-9])
|
26
|
+
[0-9]{5}
|
27
|
+
\z
|
28
|
+
}x
|
29
|
+
private_constant :MATCHER_V2
|
30
|
+
|
31
|
+
def valid_v1?
|
32
|
+
return false unless MATCHER_V1.match?(tin)
|
33
|
+
return false if tin == "0000000000"
|
34
|
+
|
35
|
+
tin[-1].to_i == check
|
36
|
+
end
|
37
|
+
|
38
|
+
def valid_v2?
|
39
|
+
match = MATCHER_V2.match(tin)
|
40
|
+
return false unless match
|
41
|
+
return false if tin == "00000000000"
|
42
|
+
return true unless birth_date
|
43
|
+
|
44
|
+
tin_date = date(
|
45
|
+
"#{birth_century}#{match[:year]}",
|
46
|
+
match[:month].to_i - month_increase,
|
47
|
+
match[:day],
|
48
|
+
)
|
49
|
+
tin_date == birth_date
|
50
|
+
end
|
51
|
+
|
52
|
+
def check
|
53
|
+
# 1. Multiply the values of each position by the corresponding weight:
|
54
|
+
weights =
|
55
|
+
[6, 5, 7, 2, 3, 4, 5, 6, 7]
|
56
|
+
.each_with_index
|
57
|
+
.map { |weight, index| weight * tin[index].to_i }
|
58
|
+
|
59
|
+
# 2. Add up the results of the above multiplications;
|
60
|
+
sum = weights.sum
|
61
|
+
|
62
|
+
# 3. Get modulo 11 of the result of the previous addition;
|
63
|
+
# 4. Check digit = remainder (if remainder = 10, the TIN is not valid).
|
64
|
+
sum % 11
|
65
|
+
end
|
66
|
+
|
67
|
+
def birth_century = birth_date.year.to_s[..1].to_i
|
68
|
+
|
69
|
+
def month_increase
|
70
|
+
case birth_century
|
71
|
+
when 18 then 80
|
72
|
+
when 20 then 20
|
73
|
+
when 21 then 40
|
74
|
+
when 22 then 60
|
75
|
+
else 0
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TinValid
|
4
|
+
class PortugalTin
|
5
|
+
def initialize(tin:)
|
6
|
+
@tin = tin
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :tin
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
return false unless /\A[0-9]{9}\z/.match?(tin)
|
13
|
+
return false if tin == "000000000"
|
14
|
+
return false if tin == "123456789"
|
15
|
+
|
16
|
+
tin[-1].to_i == check
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def check
|
22
|
+
# 1. Multiply the values of each position by the corresponding weight:
|
23
|
+
multipliers =
|
24
|
+
9
|
25
|
+
.downto(2)
|
26
|
+
.with_index
|
27
|
+
.map { |multiplier, index| multiplier * tin[index].to_i }
|
28
|
+
|
29
|
+
# 2. Add up the results of the above multiplications;
|
30
|
+
sum = multipliers.sum
|
31
|
+
|
32
|
+
# 3. Get modulo 11 of the result of the previous addition;
|
33
|
+
remainder = sum % 11
|
34
|
+
|
35
|
+
# 4. Check digit = 11 - remainder:
|
36
|
+
digit = 11 - remainder
|
37
|
+
|
38
|
+
# If check digit < = 9 then check digit is OK (11 – remainder);
|
39
|
+
return digit if digit <= 9
|
40
|
+
|
41
|
+
# If check digit = 10 then check digit is 0 (zero);
|
42
|
+
# If check digit = 11 then check digit is 0 (zero).
|
43
|
+
0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|