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.
@@ -1,7 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TinValid
4
- class IrelandTin < Data.define(:tin)
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