brazil-validator 0.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +77 -0
  3. data/lib/br_validator/cep.rb +44 -0
  4. data/lib/br_validator/cnpj.rb +78 -0
  5. data/lib/br_validator/cpf.rb +63 -0
  6. data/lib/br_validator/email.rb +48 -0
  7. data/lib/br_validator/ie/ac.rb +48 -0
  8. data/lib/br_validator/ie/al.rb +46 -0
  9. data/lib/br_validator/ie/am.rb +52 -0
  10. data/lib/br_validator/ie/ap.rb +64 -0
  11. data/lib/br_validator/ie/ba.rb +76 -0
  12. data/lib/br_validator/ie/ce.rb +42 -0
  13. data/lib/br_validator/ie/df.rb +58 -0
  14. data/lib/br_validator/ie/es.rb +39 -0
  15. data/lib/br_validator/ie/go.rb +49 -0
  16. data/lib/br_validator/ie/ma.rb +41 -0
  17. data/lib/br_validator/ie/mg.rb +93 -0
  18. data/lib/br_validator/ie/mod11.rb +48 -0
  19. data/lib/br_validator/ie/ms.rb +45 -0
  20. data/lib/br_validator/ie/mt.rb +42 -0
  21. data/lib/br_validator/ie/pa.rb +44 -0
  22. data/lib/br_validator/ie/pb.rb +42 -0
  23. data/lib/br_validator/ie/pe.rb +52 -0
  24. data/lib/br_validator/ie/pi.rb +38 -0
  25. data/lib/br_validator/ie/pr.rb +53 -0
  26. data/lib/br_validator/ie/rj.rb +53 -0
  27. data/lib/br_validator/ie/rn.rb +58 -0
  28. data/lib/br_validator/ie/ro.rb +58 -0
  29. data/lib/br_validator/ie/rr.rb +47 -0
  30. data/lib/br_validator/ie/rs.rb +42 -0
  31. data/lib/br_validator/ie/sc.rb +42 -0
  32. data/lib/br_validator/ie/se.rb +42 -0
  33. data/lib/br_validator/ie/sp.rb +121 -0
  34. data/lib/br_validator/ie/to.rb +49 -0
  35. data/lib/br_validator/ie.rb +112 -0
  36. data/lib/br_validator/phone.rb +59 -0
  37. data/lib/br_validator/pix.rb +134 -0
  38. data/lib/br_validator/shared.rb +31 -0
  39. data/lib/br_validator/version.rb +5 -0
  40. data/lib/br_validator.rb +13 -0
  41. metadata +84 -0
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # DF: SEFAZ-DF's own Sintegra "Roteiro de Crítica" page
9
+ # (sintegra.gov.br/Cad_Estados/cad_DF.html) is empty, so this was
10
+ # instead cross-verified against two independent secondary sources
11
+ # that agree with each other (cadcobol.com.br's fully worked example,
12
+ # arithmetic re-checked by hand, and mestredocalculo.com.br
13
+ # independently citing the same valid example "07.300.001.001-09").
14
+ # The algorithm is structurally identical to AC's officially-confirmed
15
+ # one (same weight sequences), differing only in the fixed "07" prefix
16
+ # -- strong evidence both derive from the same original SEFAZ
17
+ # documentation.
18
+ #
19
+ # Format: "07" (fixed) + 6 sequence digits + 3 "ordem do
20
+ # estabelecimento" digits (001 = matriz) + 2 check digits = 13 digits
21
+ # total.
22
+ module Df
23
+ module_function
24
+
25
+ ALLOWED_CHARS = %r{\A[\d./\-\s]+\z}.freeze
26
+ FIRST_DIGIT_WEIGHTS = [4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2].freeze
27
+ SECOND_DIGIT_WEIGHTS = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2].freeze
28
+ private_constant :ALLOWED_CHARS, :FIRST_DIGIT_WEIGHTS, :SECOND_DIGIT_WEIGHTS
29
+
30
+ def normalize(value)
31
+ Shared.remove_non_digits(value)
32
+ end
33
+
34
+ def is_valid?(value)
35
+ return false unless value.match?(ALLOWED_CHARS)
36
+
37
+ v = normalize(value)
38
+
39
+ return false unless v.length == 13 && v.start_with?("07")
40
+
41
+ first_digit = Mod11.mod11_check_digit(Mod11.weighted_sum(v, FIRST_DIGIT_WEIGHTS))
42
+ return false unless first_digit == v[11].to_i
43
+
44
+ second_digit = Mod11.mod11_check_digit(Mod11.weighted_sum(v, SECOND_DIGIT_WEIGHTS))
45
+
46
+ second_digit == v[12].to_i
47
+ end
48
+
49
+ def format(value)
50
+ v = normalize(value)
51
+
52
+ return value unless v.length == 13
53
+
54
+ "#{v[0...2]}.#{v[2...5]}.#{v[5...8]}.#{v[8...11]}-#{v[11...13]}"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # ES: verified against the official SEFAZ-ES "Roteiro de Crítica da
9
+ # Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_ES.html),
10
+ # including its worked example (all-9s base -> sum 396). Format: 8
11
+ # digits + 1 check digit. No official punctuation mask is published,
12
+ # so format returns plain digits.
13
+ module Es
14
+ module_function
15
+
16
+ ALLOWED_CHARS = /\A[\d\s]+\z/.freeze
17
+ WEIGHTS = [9, 8, 7, 6, 5, 4, 3, 2].freeze
18
+ private_constant :ALLOWED_CHARS, :WEIGHTS
19
+
20
+ def normalize(value)
21
+ Shared.remove_non_digits(value)
22
+ end
23
+
24
+ def is_valid?(value)
25
+ return false unless value.match?(ALLOWED_CHARS)
26
+
27
+ v = normalize(value)
28
+
29
+ return false unless v.length == 9
30
+
31
+ Mod11.mod11_check_digit(Mod11.weighted_sum(v, WEIGHTS)) == v[8].to_i
32
+ end
33
+
34
+ def format(value)
35
+ normalize(value)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # GO: verified against the official SEFAZ-GO "Roteiro de Crítica da
9
+ # Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_GO.html),
10
+ # including its worked example (10.987.654-7). Format: AB.CDE.FGH-I,
11
+ # where AB must be 10, 11, or 20-29.
12
+ module Go
13
+ module_function
14
+
15
+ ALLOWED_CHARS = /\A[\d.\-\s]+\z/.freeze
16
+ WEIGHTS = [9, 8, 7, 6, 5, 4, 3, 2].freeze
17
+ private_constant :ALLOWED_CHARS, :WEIGHTS
18
+
19
+ def normalize(value)
20
+ Shared.remove_non_digits(value)
21
+ end
22
+
23
+ def valid_prefix?(v)
24
+ prefix = v[0...2].to_i
25
+
26
+ prefix == 10 || prefix == 11 || (20..29).cover?(prefix)
27
+ end
28
+ private_class_method :valid_prefix?
29
+
30
+ def is_valid?(value)
31
+ return false unless value.match?(ALLOWED_CHARS)
32
+
33
+ v = normalize(value)
34
+
35
+ return false unless v.length == 9 && valid_prefix?(v)
36
+
37
+ Mod11.mod11_check_digit(Mod11.weighted_sum(v, WEIGHTS)) == v[8].to_i
38
+ end
39
+
40
+ def format(value)
41
+ v = normalize(value)
42
+
43
+ return value unless v.length == 9
44
+
45
+ "#{v[0...2]}.#{v[2...5]}.#{v[5...8]}-#{v[8...9]}"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # MA: verified against the official SEFAZ-MA "Roteiro de Crítica da
9
+ # Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_MA.html),
10
+ # including its worked example (120000385).
11
+ #
12
+ # Format: "12" (fixed) + 6 sequence digits + 1 check digit = 9 digits
13
+ # total. No official punctuation mask is published, so format returns
14
+ # plain digits.
15
+ module Ma
16
+ module_function
17
+
18
+ ALLOWED_CHARS = /\A[\d\s]+\z/.freeze
19
+ WEIGHTS = [9, 8, 7, 6, 5, 4, 3, 2].freeze
20
+ private_constant :ALLOWED_CHARS, :WEIGHTS
21
+
22
+ def normalize(value)
23
+ Shared.remove_non_digits(value)
24
+ end
25
+
26
+ def is_valid?(value)
27
+ return false unless value.match?(ALLOWED_CHARS)
28
+
29
+ v = normalize(value)
30
+
31
+ return false unless v.length == 9 && v.start_with?("12")
32
+
33
+ Mod11.mod11_check_digit(Mod11.weighted_sum(v, WEIGHTS)) == v[8].to_i
34
+ end
35
+
36
+ def format(value)
37
+ normalize(value)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # MG: verified against the official SEFAZ-MG "Roteiro de Crítica da
9
+ # Inscrição Estadual" (mirrored at
10
+ # sintegra.gov.br/Cad_Estados/cad_MG.html), including its fully worked
11
+ # example (062.307.904/0081).
12
+ #
13
+ # Format: A1A2A3 B1B2B3B4B5B6 C1C2 D1D2 (13 digits), where A =
14
+ # município code, B = registration number, C = establishment order, D
15
+ # = check digits.
16
+ module Mg
17
+ module_function
18
+
19
+ ALLOWED_CHARS = %r{\A[\d./\s]+\z}.freeze
20
+ FIRST_DIGIT_WEIGHTS = [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2].freeze
21
+ SECOND_DIGIT_WEIGHTS = [3, 2, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2].freeze
22
+ private_constant :ALLOWED_CHARS, :FIRST_DIGIT_WEIGHTS, :SECOND_DIGIT_WEIGHTS
23
+
24
+ def normalize(value)
25
+ Shared.remove_non_digits(value)
26
+ end
27
+
28
+ def sum_of_digits(value)
29
+ value.digits.sum
30
+ end
31
+ private_class_method :sum_of_digits
32
+
33
+ # D1 equalizes the field widths by inserting a "0" right after the
34
+ # município code, then sums the *digits* of each weighted product
35
+ # (not the products themselves) before completing to the next
36
+ # multiple of ten.
37
+ def calculate_first_digit(base)
38
+ with_inserted_zero = "#{base[0...3]}0#{base[3..]}"
39
+
40
+ digit_sum = FIRST_DIGIT_WEIGHTS.each_with_index.sum do |weight, i|
41
+ sum_of_digits(with_inserted_zero[i].to_i * weight)
42
+ end
43
+
44
+ remainder = digit_sum % 10
45
+
46
+ return 0 if remainder.zero?
47
+
48
+ 10 - remainder
49
+ end
50
+ private_class_method :calculate_first_digit
51
+
52
+ # D2 uses the original (un-padded) base plus D1, and sums the
53
+ # weighted products directly, following the general módulo 11
54
+ # remainder rule.
55
+ def calculate_second_digit(base, first_digit)
56
+ with_first_digit = "#{base}#{first_digit}"
57
+
58
+ sum = Mod11.weighted_sum(with_first_digit, SECOND_DIGIT_WEIGHTS)
59
+ remainder = sum % 11
60
+
61
+ return 0 if remainder <= 1
62
+
63
+ 11 - remainder
64
+ end
65
+ private_class_method :calculate_second_digit
66
+
67
+ def is_valid?(value)
68
+ return false unless value.match?(ALLOWED_CHARS)
69
+
70
+ v = normalize(value)
71
+
72
+ return false unless v.length == 13
73
+
74
+ base = v[0...11]
75
+ first_digit = calculate_first_digit(base)
76
+
77
+ return false unless first_digit == v[11].to_i
78
+
79
+ second_digit = calculate_second_digit(base, first_digit)
80
+
81
+ second_digit == v[12].to_i
82
+ end
83
+
84
+ def format(value)
85
+ v = normalize(value)
86
+
87
+ return value unless v.length == 13
88
+
89
+ "#{v[0...3]}.#{v[3...6]}.#{v[6...9]}/#{v[9...13]}"
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrValidator
4
+ module Ie
5
+ # Shared by most Inscrição Estadual algorithms (SEFAZ "Roteiro de
6
+ # Crítica" documents): weight each digit, sum the products, then
7
+ # reduce modulo 11.
8
+ #
9
+ # Not part of the public API of brazil-validator.
10
+ module Mod11
11
+ module_function
12
+
13
+ def weighted_sum(digits, weights)
14
+ weights.each_with_index.sum { |weight, i| digits[i].to_i * weight }
15
+ end
16
+
17
+ # The most common check-digit rule across states: remainder 0 or 1
18
+ # maps to digit 0, otherwise the digit is 11 minus the remainder.
19
+ def mod11_check_digit(sum)
20
+ remainder = sum % 11
21
+
22
+ return 0 if remainder <= 1
23
+
24
+ 11 - remainder
25
+ end
26
+
27
+ # Used by Bahia's módulo-10 branch: remainder 0 maps to digit 0,
28
+ # otherwise the digit is 10 minus the remainder.
29
+ def mod10_check_digit(sum)
30
+ remainder = sum % 10
31
+
32
+ return 0 if remainder.zero?
33
+
34
+ 10 - remainder
35
+ end
36
+
37
+ # Used by Alagoas and Rio Grande do Norte: the sum is multiplied by
38
+ # 10 before reducing modulo 11, and the remainder *is* the digit
39
+ # directly (a remainder of 10 wraps to 0). Mirrors CPF's check-digit
40
+ # formula.
41
+ def mod11_times_ten_check_digit(sum)
42
+ remainder = (sum * 10) % 11
43
+
44
+ remainder == 10 ? 0 : remainder
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # MS: verified against the official SEFAZ-MS "Roteiro de Crítica da
9
+ # Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_MS.html) for the
10
+ # algorithm (which the page does not accompany with a worked numeric
11
+ # example), plus an independently hand-computed regression vector
12
+ # (281234566). Format: 8 digits (always starting with "28" or "50") +
13
+ # 1 check digit.
14
+ module Ms
15
+ module_function
16
+
17
+ ALLOWED_CHARS = /\A[\d\s]+\z/.freeze
18
+ WEIGHTS = [9, 8, 7, 6, 5, 4, 3, 2].freeze
19
+ private_constant :ALLOWED_CHARS, :WEIGHTS
20
+
21
+ def normalize(value)
22
+ Shared.remove_non_digits(value)
23
+ end
24
+
25
+ def valid_prefix?(v)
26
+ %w[28 50].include?(v[0...2])
27
+ end
28
+ private_class_method :valid_prefix?
29
+
30
+ def is_valid?(value)
31
+ return false unless value.match?(ALLOWED_CHARS)
32
+
33
+ v = normalize(value)
34
+
35
+ return false unless v.length == 9 && valid_prefix?(v)
36
+
37
+ Mod11.mod11_check_digit(Mod11.weighted_sum(v, WEIGHTS)) == v[8].to_i
38
+ end
39
+
40
+ def format(value)
41
+ normalize(value)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # MT: verified against the official SEFAZ-MT "Roteiro de Crítica da
9
+ # Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_MT.html),
10
+ # including its worked example (0013000001-9). Format: 10 digits + 1
11
+ # check digit.
12
+ module Mt
13
+ module_function
14
+
15
+ ALLOWED_CHARS = /\A[\d\-\s]+\z/.freeze
16
+ WEIGHTS = [3, 2, 9, 8, 7, 6, 5, 4, 3, 2].freeze
17
+ private_constant :ALLOWED_CHARS, :WEIGHTS
18
+
19
+ def normalize(value)
20
+ Shared.remove_non_digits(value)
21
+ end
22
+
23
+ def is_valid?(value)
24
+ return false unless value.match?(ALLOWED_CHARS)
25
+
26
+ v = normalize(value)
27
+
28
+ return false unless v.length == 11
29
+
30
+ Mod11.mod11_check_digit(Mod11.weighted_sum(v, WEIGHTS)) == v[10].to_i
31
+ end
32
+
33
+ def format(value)
34
+ v = normalize(value)
35
+
36
+ return value unless v.length == 11
37
+
38
+ "#{v[0...10]}-#{v[10...11]}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # PA: verified against the official SEFAZ-PA "Roteiro de Crítica da
9
+ # Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_PA.html),
10
+ # including its two worked examples (15999999-5, 75000002-3). Format:
11
+ # 8 digits (always starting with 15, 75, 76, 77, 78 or 79) + 1 check
12
+ # digit.
13
+ module Pa
14
+ module_function
15
+
16
+ ALLOWED_CHARS = /\A[\d\-\s]+\z/.freeze
17
+ WEIGHTS = [9, 8, 7, 6, 5, 4, 3, 2].freeze
18
+ VALID_PREFIXES = %w[15 75 76 77 78 79].freeze
19
+ private_constant :ALLOWED_CHARS, :WEIGHTS, :VALID_PREFIXES
20
+
21
+ def normalize(value)
22
+ Shared.remove_non_digits(value)
23
+ end
24
+
25
+ def is_valid?(value)
26
+ return false unless value.match?(ALLOWED_CHARS)
27
+
28
+ v = normalize(value)
29
+
30
+ return false unless v.length == 9 && VALID_PREFIXES.include?(v[0...2])
31
+
32
+ Mod11.mod11_check_digit(Mod11.weighted_sum(v, WEIGHTS)) == v[8].to_i
33
+ end
34
+
35
+ def format(value)
36
+ v = normalize(value)
37
+
38
+ return value unless v.length == 9
39
+
40
+ "#{v[0...8]}-#{v[8...9]}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # PB: verified against the official SEFAZ-PB "Roteiro de Crítica da
9
+ # Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_PB.html),
10
+ # including its worked example (06000001-5). Format: 8 digits + 1
11
+ # check digit.
12
+ module Pb
13
+ module_function
14
+
15
+ ALLOWED_CHARS = /\A[\d\-\s]+\z/.freeze
16
+ WEIGHTS = [9, 8, 7, 6, 5, 4, 3, 2].freeze
17
+ private_constant :ALLOWED_CHARS, :WEIGHTS
18
+
19
+ def normalize(value)
20
+ Shared.remove_non_digits(value)
21
+ end
22
+
23
+ def is_valid?(value)
24
+ return false unless value.match?(ALLOWED_CHARS)
25
+
26
+ v = normalize(value)
27
+
28
+ return false unless v.length == 9
29
+
30
+ Mod11.mod11_check_digit(Mod11.weighted_sum(v, WEIGHTS)) == v[8].to_i
31
+ end
32
+
33
+ def format(value)
34
+ v = normalize(value)
35
+
36
+ return value unless v.length == 9
37
+
38
+ "#{v[0...8]}-#{v[8...9]}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # PE: verified against the official SEFAZ-PE "Roteiro de Crítica da
9
+ # Inscrição Estadual" for the e-Fisco system
10
+ # (sintegra.gov.br/Cad_Estados/cad_PE.html), including its fully
11
+ # worked example (0321418-40). Format: 7 digits + 2 check digits.
12
+ module Pe
13
+ module_function
14
+
15
+ ALLOWED_CHARS = /\A[\d\-\s]+\z/.freeze
16
+ FIRST_DIGIT_WEIGHTS = [8, 7, 6, 5, 4, 3, 2].freeze
17
+ SECOND_DIGIT_WEIGHTS = [9, 8, 7, 6, 5, 4, 3, 2].freeze
18
+ private_constant :ALLOWED_CHARS, :FIRST_DIGIT_WEIGHTS, :SECOND_DIGIT_WEIGHTS
19
+
20
+ def normalize(value)
21
+ Shared.remove_non_digits(value)
22
+ end
23
+
24
+ def is_valid?(value)
25
+ return false unless value.match?(ALLOWED_CHARS)
26
+
27
+ v = normalize(value)
28
+
29
+ return false unless v.length == 9
30
+
31
+ base = v[0...7]
32
+ first_digit = Mod11.mod11_check_digit(Mod11.weighted_sum(base, FIRST_DIGIT_WEIGHTS))
33
+
34
+ return false unless first_digit == v[7].to_i
35
+
36
+ second_digit = Mod11.mod11_check_digit(
37
+ Mod11.weighted_sum(base + first_digit.to_s, SECOND_DIGIT_WEIGHTS)
38
+ )
39
+
40
+ second_digit == v[8].to_i
41
+ end
42
+
43
+ def format(value)
44
+ v = normalize(value)
45
+
46
+ return value unless v.length == 9
47
+
48
+ "#{v[0...7]}-#{v[7...9]}"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # PI: verified against the official SEFAZ-PI "Roteiro de Crítica da
9
+ # Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_PI.html),
10
+ # including its worked example (012345679). Format: 8 digits + 1 check
11
+ # digit.
12
+ module Pi
13
+ module_function
14
+
15
+ ALLOWED_CHARS = /\A[\d\s]+\z/.freeze
16
+ WEIGHTS = [9, 8, 7, 6, 5, 4, 3, 2].freeze
17
+ private_constant :ALLOWED_CHARS, :WEIGHTS
18
+
19
+ def normalize(value)
20
+ Shared.remove_non_digits(value)
21
+ end
22
+
23
+ def is_valid?(value)
24
+ return false unless value.match?(ALLOWED_CHARS)
25
+
26
+ v = normalize(value)
27
+
28
+ return false unless v.length == 9
29
+
30
+ Mod11.mod11_check_digit(Mod11.weighted_sum(v, WEIGHTS)) == v[8].to_i
31
+ end
32
+
33
+ def format(value)
34
+ normalize(value)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../shared"
4
+ require_relative "mod11"
5
+
6
+ module BrValidator
7
+ module Ie
8
+ # PR: verified against the official SEFAZ-PR "Roteiro de Crítica da
9
+ # Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_PR.html),
10
+ # including its fully worked example (123.45678-50), and cross-checked
11
+ # against the reference Visual Basic routine published on the same
12
+ # page. Format: 8 digits + 2 check digits.
13
+ module Pr
14
+ module_function
15
+
16
+ ALLOWED_CHARS = /\A[\d.\-\s]+\z/.freeze
17
+ FIRST_DIGIT_WEIGHTS = [3, 2, 7, 6, 5, 4, 3, 2].freeze
18
+ SECOND_DIGIT_WEIGHTS = [4, 3, 2, 7, 6, 5, 4, 3, 2].freeze
19
+ private_constant :ALLOWED_CHARS, :FIRST_DIGIT_WEIGHTS, :SECOND_DIGIT_WEIGHTS
20
+
21
+ def normalize(value)
22
+ Shared.remove_non_digits(value)
23
+ end
24
+
25
+ def is_valid?(value)
26
+ return false unless value.match?(ALLOWED_CHARS)
27
+
28
+ v = normalize(value)
29
+
30
+ return false unless v.length == 10
31
+
32
+ base = v[0...8]
33
+ first_digit = Mod11.mod11_check_digit(Mod11.weighted_sum(base, FIRST_DIGIT_WEIGHTS))
34
+
35
+ return false unless first_digit == v[8].to_i
36
+
37
+ second_digit = Mod11.mod11_check_digit(
38
+ Mod11.weighted_sum(base + first_digit.to_s, SECOND_DIGIT_WEIGHTS)
39
+ )
40
+
41
+ second_digit == v[9].to_i
42
+ end
43
+
44
+ def format(value)
45
+ v = normalize(value)
46
+
47
+ return value unless v.length == 10
48
+
49
+ "#{v[0...3]}.#{v[3...8]}-#{v[8...10]}"
50
+ end
51
+ end
52
+ end
53
+ end