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.
- checksums.yaml +7 -0
- data/README.md +77 -0
- data/lib/br_validator/cep.rb +44 -0
- data/lib/br_validator/cnpj.rb +78 -0
- data/lib/br_validator/cpf.rb +63 -0
- data/lib/br_validator/email.rb +48 -0
- data/lib/br_validator/ie/ac.rb +48 -0
- data/lib/br_validator/ie/al.rb +46 -0
- data/lib/br_validator/ie/am.rb +52 -0
- data/lib/br_validator/ie/ap.rb +64 -0
- data/lib/br_validator/ie/ba.rb +76 -0
- data/lib/br_validator/ie/ce.rb +42 -0
- data/lib/br_validator/ie/df.rb +58 -0
- data/lib/br_validator/ie/es.rb +39 -0
- data/lib/br_validator/ie/go.rb +49 -0
- data/lib/br_validator/ie/ma.rb +41 -0
- data/lib/br_validator/ie/mg.rb +93 -0
- data/lib/br_validator/ie/mod11.rb +48 -0
- data/lib/br_validator/ie/ms.rb +45 -0
- data/lib/br_validator/ie/mt.rb +42 -0
- data/lib/br_validator/ie/pa.rb +44 -0
- data/lib/br_validator/ie/pb.rb +42 -0
- data/lib/br_validator/ie/pe.rb +52 -0
- data/lib/br_validator/ie/pi.rb +38 -0
- data/lib/br_validator/ie/pr.rb +53 -0
- data/lib/br_validator/ie/rj.rb +53 -0
- data/lib/br_validator/ie/rn.rb +58 -0
- data/lib/br_validator/ie/ro.rb +58 -0
- data/lib/br_validator/ie/rr.rb +47 -0
- data/lib/br_validator/ie/rs.rb +42 -0
- data/lib/br_validator/ie/sc.rb +42 -0
- data/lib/br_validator/ie/se.rb +42 -0
- data/lib/br_validator/ie/sp.rb +121 -0
- data/lib/br_validator/ie/to.rb +49 -0
- data/lib/br_validator/ie.rb +112 -0
- data/lib/br_validator/phone.rb +59 -0
- data/lib/br_validator/pix.rb +134 -0
- data/lib/br_validator/shared.rb +31 -0
- data/lib/br_validator/version.rb +5 -0
- data/lib/br_validator.rb +13 -0
- metadata +84 -0
|
@@ -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
|
+
# RJ: verified against the official SEFAZ-RJ "Roteiro de Crítica da
|
|
9
|
+
# Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_RJ.html) for the
|
|
10
|
+
# check-digit rule, and cross-checked against a worked example
|
|
11
|
+
# (99.999.99-3) for the weights, which the Sintegra page itself does
|
|
12
|
+
# not enumerate.
|
|
13
|
+
module Rj
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
ALLOWED_CHARS = /\A[\d.\-\s]+\z/.freeze
|
|
17
|
+
WEIGHTS = [2, 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 calculate_check_digit(v)
|
|
25
|
+
sum = Mod11.weighted_sum(v, WEIGHTS)
|
|
26
|
+
remainder = sum % 11
|
|
27
|
+
|
|
28
|
+
return 0 if remainder <= 1
|
|
29
|
+
|
|
30
|
+
11 - remainder
|
|
31
|
+
end
|
|
32
|
+
private_class_method :calculate_check_digit
|
|
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 == 8
|
|
40
|
+
|
|
41
|
+
calculate_check_digit(v) == v[7].to_i
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def format(value)
|
|
45
|
+
v = normalize(value)
|
|
46
|
+
|
|
47
|
+
return value unless v.length == 8
|
|
48
|
+
|
|
49
|
+
"#{v[0...2]}.#{v[2...5]}.#{v[5...7]}-#{v[7...8]}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -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
|
+
# RN: verified against the official SEFAZ-RN "Roteiro de Crítica da
|
|
9
|
+
# Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_RN.html),
|
|
10
|
+
# including both of its worked examples (20.040.040-1 and
|
|
11
|
+
# 20.0.040.040-0). Format: always starts with "20", followed by either
|
|
12
|
+
# 7 or 8 more digits, plus 1 check digit (9 or 10 digits total, both
|
|
13
|
+
# still valid today per the official page).
|
|
14
|
+
module Rn
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
ALLOWED_CHARS = /\A[\d.\-\s]+\z/.freeze
|
|
18
|
+
private_constant :ALLOWED_CHARS
|
|
19
|
+
|
|
20
|
+
def normalize(value)
|
|
21
|
+
Shared.remove_non_digits(value)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def weights_for(base_length)
|
|
25
|
+
(0...base_length).map { |i| base_length + 1 - i }
|
|
26
|
+
end
|
|
27
|
+
private_class_method :weights_for
|
|
28
|
+
|
|
29
|
+
def is_valid?(value)
|
|
30
|
+
return false unless value.match?(ALLOWED_CHARS)
|
|
31
|
+
|
|
32
|
+
v = normalize(value)
|
|
33
|
+
|
|
34
|
+
return false unless [9, 10].include?(v.length) && v.start_with?("20")
|
|
35
|
+
|
|
36
|
+
base = v[0...-1]
|
|
37
|
+
check_digit = Mod11.mod11_times_ten_check_digit(
|
|
38
|
+
Mod11.weighted_sum(base, weights_for(base.length))
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
check_digit == v[-1].to_i
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def format(value)
|
|
45
|
+
v = normalize(value)
|
|
46
|
+
|
|
47
|
+
case v.length
|
|
48
|
+
when 9
|
|
49
|
+
"#{v[0...2]}.#{v[2...5]}.#{v[5...8]}-#{v[8...9]}"
|
|
50
|
+
when 10
|
|
51
|
+
"#{v[0...2]}.#{v[2...3]}.#{v[3...6]}.#{v[6...9]}-#{v[9...10]}"
|
|
52
|
+
else
|
|
53
|
+
value
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -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
|
+
# RO: verified against the official SEFAZ-RO "Roteiro de Crítica da
|
|
9
|
+
# Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_RO.html),
|
|
10
|
+
# including its worked example (0000000062521-3). Since 01/08/2000 the
|
|
11
|
+
# format is 13 digits + 1 check digit (the old "município + empresa"
|
|
12
|
+
# layout is superseded, with old registrations re-expressed by
|
|
13
|
+
# zero-padding into the new 13-digit field).
|
|
14
|
+
#
|
|
15
|
+
# The weights cycle 2-9 applied right to left over the 13 digits, i.e.
|
|
16
|
+
# [6,5,4,3,2,9,8,7,6,5,4,3,2] read left to right. Unlike most other
|
|
17
|
+
# states, a zero remainder maps to check digit 1, not 0 (the source
|
|
18
|
+
# explicitly says "subtract 10" from the 11-or-10 result, rather than
|
|
19
|
+
# mapping straight to 0).
|
|
20
|
+
module Ro
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
ALLOWED_CHARS = /\A[\d.\-\s]+\z/.freeze
|
|
24
|
+
WEIGHTS = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2].freeze
|
|
25
|
+
private_constant :ALLOWED_CHARS, :WEIGHTS
|
|
26
|
+
|
|
27
|
+
def normalize(value)
|
|
28
|
+
Shared.remove_non_digits(value)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def calculate_check_digit(base)
|
|
32
|
+
remainder = Mod11.weighted_sum(base, WEIGHTS) % 11
|
|
33
|
+
diff = 11 - remainder
|
|
34
|
+
|
|
35
|
+
diff > 9 ? diff - 10 : diff
|
|
36
|
+
end
|
|
37
|
+
private_class_method :calculate_check_digit
|
|
38
|
+
|
|
39
|
+
def is_valid?(value)
|
|
40
|
+
return false unless value.match?(ALLOWED_CHARS)
|
|
41
|
+
|
|
42
|
+
v = normalize(value)
|
|
43
|
+
|
|
44
|
+
return false unless v.length == 14
|
|
45
|
+
|
|
46
|
+
calculate_check_digit(v[0...13]) == v[13].to_i
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def format(value)
|
|
50
|
+
v = normalize(value)
|
|
51
|
+
|
|
52
|
+
return value unless v.length == 14
|
|
53
|
+
|
|
54
|
+
"#{v[0...13]}-#{v[13...14]}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../shared"
|
|
4
|
+
require_relative "mod11"
|
|
5
|
+
|
|
6
|
+
module BrValidator
|
|
7
|
+
module Ie
|
|
8
|
+
# RR: verified against the official SEFAZ-RR "Roteiro de Crítica da
|
|
9
|
+
# Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_RR.html),
|
|
10
|
+
# including its worked example (24006153-6) and the ten additional
|
|
11
|
+
# valid numbers it lists. Format: "24" (fixed) + 6 sequence digits + 1
|
|
12
|
+
# check digit = 9 digits total. Unlike every other state, the check
|
|
13
|
+
# digit uses módulo 9, and the weights are the digit's own 1-based
|
|
14
|
+
# position (ascending, not descending).
|
|
15
|
+
module Rr
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
ALLOWED_CHARS = /\A[\d\-\s]+\z/.freeze
|
|
19
|
+
WEIGHTS = [1, 2, 3, 4, 5, 6, 7, 8].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?("24")
|
|
32
|
+
|
|
33
|
+
check_digit = Mod11.weighted_sum(v, WEIGHTS) % 9
|
|
34
|
+
|
|
35
|
+
check_digit == v[8].to_i
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def format(value)
|
|
39
|
+
v = normalize(value)
|
|
40
|
+
|
|
41
|
+
return value unless v.length == 9
|
|
42
|
+
|
|
43
|
+
"#{v[0...8]}-#{v[8...9]}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
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
|
+
# RS: verified against the official SEFAZ-RS "Roteiro de Crítica da
|
|
9
|
+
# Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_RS.html),
|
|
10
|
+
# including its worked example (224/3658792). Format: 3 digits
|
|
11
|
+
# (município) + 6 digits (empresa) + 1 check digit = 10 digits total.
|
|
12
|
+
module Rs
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
ALLOWED_CHARS = %r{\A[\d/\s]+\z}.freeze
|
|
16
|
+
WEIGHTS = [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 == 10
|
|
29
|
+
|
|
30
|
+
Mod11.mod11_check_digit(Mod11.weighted_sum(v, WEIGHTS)) == v[9].to_i
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def format(value)
|
|
34
|
+
v = normalize(value)
|
|
35
|
+
|
|
36
|
+
return value unless v.length == 10
|
|
37
|
+
|
|
38
|
+
"#{v[0...3]}/#{v[3...10]}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
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
|
+
# SC: verified against the official SEFAZ-SC "Roteiro de Crítica da
|
|
9
|
+
# Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_SC.html),
|
|
10
|
+
# including its worked example (251.040.852). Format: 8 digits + 1
|
|
11
|
+
# check digit.
|
|
12
|
+
module Sc
|
|
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...3]}.#{v[3...6]}.#{v[6...9]}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
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
|
+
# SE: verified against the official SEFAZ-SE "Roteiro de Crítica da
|
|
9
|
+
# Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_SE.html),
|
|
10
|
+
# including its worked example (27123456-3). Format: 8 digits + 1
|
|
11
|
+
# check digit.
|
|
12
|
+
module Se
|
|
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,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../shared"
|
|
4
|
+
require_relative "mod11"
|
|
5
|
+
|
|
6
|
+
module BrValidator
|
|
7
|
+
module Ie
|
|
8
|
+
# SP: verified against the official SEFAZ-SP "Roteiro de Crítica da
|
|
9
|
+
# Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_SP.html),
|
|
10
|
+
# including both of its worked examples: the standard
|
|
11
|
+
# industrial/commercial format (110.042.490.114) and the "Produtor
|
|
12
|
+
# Rural" format (P-01100424.3/002).
|
|
13
|
+
#
|
|
14
|
+
# Note: the source restates the Produtor Rural example at the very end
|
|
15
|
+
# as "P-011000424.3/002" (14 characters) -- this contradicts both the
|
|
16
|
+
# document's own "13 caracteres" rule and its worked calculation
|
|
17
|
+
# (which sums exactly 8 digits to 91, matching the 13-character form).
|
|
18
|
+
# Treated as a typo in the source; the 13-character form is what this
|
|
19
|
+
# module expects.
|
|
20
|
+
module Sp
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
STANDARD_ALLOWED_CHARS = /\A[\d.\s]+\z/.freeze
|
|
24
|
+
PRODUTOR_RURAL_ALLOWED = %r{\A[Pp\d.\-/\s]+\z}.freeze
|
|
25
|
+
FORMATTING_CHARS = %r{[.\-/\s]}.freeze
|
|
26
|
+
PRODUTOR_RURAL_PREFIX = /\A\s*[Pp]/.freeze
|
|
27
|
+
|
|
28
|
+
STANDARD_FIRST_DIGIT_WEIGHTS = [1, 3, 4, 5, 6, 7, 8, 10].freeze
|
|
29
|
+
STANDARD_SECOND_DIGIT_WEIGHTS = [3, 2, 10, 9, 8, 7, 6, 5, 4, 3, 2].freeze
|
|
30
|
+
PRODUTOR_RURAL_WEIGHTS = [1, 3, 4, 5, 6, 7, 8, 10].freeze
|
|
31
|
+
private_constant :STANDARD_ALLOWED_CHARS, :PRODUTOR_RURAL_ALLOWED, :FORMATTING_CHARS,
|
|
32
|
+
:PRODUTOR_RURAL_PREFIX, :STANDARD_FIRST_DIGIT_WEIGHTS,
|
|
33
|
+
:STANDARD_SECOND_DIGIT_WEIGHTS, :PRODUTOR_RURAL_WEIGHTS
|
|
34
|
+
|
|
35
|
+
def calculate_check_digit(digits, weights)
|
|
36
|
+
sum = Mod11.weighted_sum(digits, weights)
|
|
37
|
+
|
|
38
|
+
sum % 11 == 10 ? 0 : sum % 11
|
|
39
|
+
end
|
|
40
|
+
private_class_method :calculate_check_digit
|
|
41
|
+
|
|
42
|
+
def produtor_rural?(value)
|
|
43
|
+
value.match?(PRODUTOR_RURAL_PREFIX)
|
|
44
|
+
end
|
|
45
|
+
private_class_method :produtor_rural?
|
|
46
|
+
|
|
47
|
+
def normalize_standard(value)
|
|
48
|
+
Shared.remove_non_digits(value)
|
|
49
|
+
end
|
|
50
|
+
private_class_method :normalize_standard
|
|
51
|
+
|
|
52
|
+
def valid_standard?(value)
|
|
53
|
+
return false unless value.match?(STANDARD_ALLOWED_CHARS)
|
|
54
|
+
|
|
55
|
+
v = normalize_standard(value)
|
|
56
|
+
|
|
57
|
+
return false unless v.length == 12
|
|
58
|
+
|
|
59
|
+
first_digit = calculate_check_digit(v, STANDARD_FIRST_DIGIT_WEIGHTS)
|
|
60
|
+
return false unless first_digit == v[8].to_i
|
|
61
|
+
|
|
62
|
+
second_digit = calculate_check_digit(v, STANDARD_SECOND_DIGIT_WEIGHTS)
|
|
63
|
+
|
|
64
|
+
second_digit == v[11].to_i
|
|
65
|
+
end
|
|
66
|
+
private_class_method :valid_standard?
|
|
67
|
+
|
|
68
|
+
def format_standard(value)
|
|
69
|
+
v = normalize_standard(value)
|
|
70
|
+
|
|
71
|
+
return value unless v.length == 12
|
|
72
|
+
|
|
73
|
+
"#{v[0...3]}.#{v[3...6]}.#{v[6...9]}.#{v[9...12]}"
|
|
74
|
+
end
|
|
75
|
+
private_class_method :format_standard
|
|
76
|
+
|
|
77
|
+
# Format: P0MMMSSSSD000 (13 characters) -- "P" (fixed) + "0" (fixed)
|
|
78
|
+
# + 3 município digits + 4 sequence digits + 1 check digit + 3
|
|
79
|
+
# unused digits.
|
|
80
|
+
def normalize_produtor_rural(value)
|
|
81
|
+
value.gsub(FORMATTING_CHARS, "").upcase
|
|
82
|
+
end
|
|
83
|
+
private_class_method :normalize_produtor_rural
|
|
84
|
+
|
|
85
|
+
def valid_produtor_rural?(value)
|
|
86
|
+
return false unless value.match?(PRODUTOR_RURAL_ALLOWED)
|
|
87
|
+
|
|
88
|
+
v = normalize_produtor_rural(value)
|
|
89
|
+
|
|
90
|
+
return false unless v.length == 13 && v[0] == "P" && v[1] == "0"
|
|
91
|
+
|
|
92
|
+
base = v[1...9]
|
|
93
|
+
check_digit = calculate_check_digit(base, PRODUTOR_RURAL_WEIGHTS)
|
|
94
|
+
|
|
95
|
+
check_digit == v[9].to_i
|
|
96
|
+
end
|
|
97
|
+
private_class_method :valid_produtor_rural?
|
|
98
|
+
|
|
99
|
+
def format_produtor_rural(value)
|
|
100
|
+
v = normalize_produtor_rural(value)
|
|
101
|
+
|
|
102
|
+
return value unless v.length == 13
|
|
103
|
+
|
|
104
|
+
"P-#{v[1...9]}.#{v[9...10]}/#{v[10...13]}"
|
|
105
|
+
end
|
|
106
|
+
private_class_method :format_produtor_rural
|
|
107
|
+
|
|
108
|
+
def is_valid?(value)
|
|
109
|
+
produtor_rural?(value) ? valid_produtor_rural?(value) : valid_standard?(value)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def normalize(value)
|
|
113
|
+
produtor_rural?(value) ? normalize_produtor_rural(value) : normalize_standard(value)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def format(value)
|
|
117
|
+
produtor_rural?(value) ? format_produtor_rural(value) : format_standard(value)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
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
|
+
# TO: verified against the official SEFAZ-TO "Roteiro de Crítica da
|
|
9
|
+
# Inscrição Estadual" (sintegra.gov.br/Cad_Estados/cad_TO.html),
|
|
10
|
+
# including its worked example (29010227836). Format: 11 digits, where
|
|
11
|
+
# positions 3-4 hold a fixed "tipo" code (01 Produtor Rural, 02
|
|
12
|
+
# Indústria e Comércio, 03 Empresas Rudimentares, 99 Cadastro Antigo)
|
|
13
|
+
# that is excluded from the check-digit calculation, and position 11
|
|
14
|
+
# is the check digit.
|
|
15
|
+
module To
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
ALLOWED_CHARS = /\A[\d\s]+\z/.freeze
|
|
19
|
+
WEIGHTS = [9, 8, 7, 6, 5, 4, 3, 2].freeze
|
|
20
|
+
VALID_TYPE_CODES = %w[01 02 03 99].freeze
|
|
21
|
+
# 1-based positions used in the check-digit calculation (positions
|
|
22
|
+
# 3-4 are skipped).
|
|
23
|
+
DIGIT_POSITIONS = [1, 2, 5, 6, 7, 8, 9, 10].freeze
|
|
24
|
+
private_constant :ALLOWED_CHARS, :WEIGHTS, :VALID_TYPE_CODES, :DIGIT_POSITIONS
|
|
25
|
+
|
|
26
|
+
def normalize(value)
|
|
27
|
+
Shared.remove_non_digits(value)
|
|
28
|
+
end
|
|
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 == 11 && VALID_TYPE_CODES.include?(v[2...4])
|
|
36
|
+
|
|
37
|
+
sum = DIGIT_POSITIONS.each_with_index.sum do |pos, i|
|
|
38
|
+
v[pos - 1].to_i * WEIGHTS[i]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Mod11.mod11_check_digit(sum) == v[10].to_i
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def format(value)
|
|
45
|
+
normalize(value)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ie/ac"
|
|
4
|
+
require_relative "ie/al"
|
|
5
|
+
require_relative "ie/am"
|
|
6
|
+
require_relative "ie/ap"
|
|
7
|
+
require_relative "ie/ba"
|
|
8
|
+
require_relative "ie/ce"
|
|
9
|
+
require_relative "ie/df"
|
|
10
|
+
require_relative "ie/es"
|
|
11
|
+
require_relative "ie/go"
|
|
12
|
+
require_relative "ie/ma"
|
|
13
|
+
require_relative "ie/mg"
|
|
14
|
+
require_relative "ie/ms"
|
|
15
|
+
require_relative "ie/mt"
|
|
16
|
+
require_relative "ie/pa"
|
|
17
|
+
require_relative "ie/pb"
|
|
18
|
+
require_relative "ie/pe"
|
|
19
|
+
require_relative "ie/pi"
|
|
20
|
+
require_relative "ie/pr"
|
|
21
|
+
require_relative "ie/rj"
|
|
22
|
+
require_relative "ie/rn"
|
|
23
|
+
require_relative "ie/ro"
|
|
24
|
+
require_relative "ie/rr"
|
|
25
|
+
require_relative "ie/rs"
|
|
26
|
+
require_relative "ie/sc"
|
|
27
|
+
require_relative "ie/se"
|
|
28
|
+
require_relative "ie/sp"
|
|
29
|
+
require_relative "ie/to"
|
|
30
|
+
|
|
31
|
+
module BrValidator
|
|
32
|
+
# Validates, normalizes, and formats Brazilian Inscrição Estadual
|
|
33
|
+
# numbers.
|
|
34
|
+
#
|
|
35
|
+
# Inscrição Estadual has no single national rule: each state (SEFAZ)
|
|
36
|
+
# defines its own digit count and check-digit algorithm, so every UF is
|
|
37
|
+
# modeled as its own module under BrValidator::Ie and registered in the
|
|
38
|
+
# STATES map below. A UF absent from that map is simply not supported
|
|
39
|
+
# yet.
|
|
40
|
+
module Ie
|
|
41
|
+
module_function
|
|
42
|
+
|
|
43
|
+
STATES = {
|
|
44
|
+
"AC" => Ac,
|
|
45
|
+
"AL" => Al,
|
|
46
|
+
"AM" => Am,
|
|
47
|
+
"AP" => Ap,
|
|
48
|
+
"BA" => Ba,
|
|
49
|
+
"CE" => Ce,
|
|
50
|
+
"DF" => Df,
|
|
51
|
+
"ES" => Es,
|
|
52
|
+
"GO" => Go,
|
|
53
|
+
"MA" => Ma,
|
|
54
|
+
"MG" => Mg,
|
|
55
|
+
"MS" => Ms,
|
|
56
|
+
"MT" => Mt,
|
|
57
|
+
"PA" => Pa,
|
|
58
|
+
"PB" => Pb,
|
|
59
|
+
"PE" => Pe,
|
|
60
|
+
"PI" => Pi,
|
|
61
|
+
"PR" => Pr,
|
|
62
|
+
"RJ" => Rj,
|
|
63
|
+
"RN" => Rn,
|
|
64
|
+
"RO" => Ro,
|
|
65
|
+
"RR" => Rr,
|
|
66
|
+
"RS" => Rs,
|
|
67
|
+
"SC" => Sc,
|
|
68
|
+
"SE" => Se,
|
|
69
|
+
"SP" => Sp,
|
|
70
|
+
"TO" => To
|
|
71
|
+
}.freeze
|
|
72
|
+
private_constant :STATES
|
|
73
|
+
|
|
74
|
+
def resolve_state(uf)
|
|
75
|
+
STATES[uf.strip.upcase]
|
|
76
|
+
end
|
|
77
|
+
private_class_method :resolve_state
|
|
78
|
+
|
|
79
|
+
# Returns true if value is a valid Inscrição Estadual for uf.
|
|
80
|
+
def is_valid?(value, uf)
|
|
81
|
+
state = resolve_state(uf)
|
|
82
|
+
|
|
83
|
+
return false if state.nil?
|
|
84
|
+
|
|
85
|
+
state.is_valid?(value)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Strips formatting and returns the canonical representation of value
|
|
89
|
+
# for uf.
|
|
90
|
+
#
|
|
91
|
+
# If uf is not supported, value is returned unchanged.
|
|
92
|
+
def normalize(value, uf)
|
|
93
|
+
state = resolve_state(uf)
|
|
94
|
+
|
|
95
|
+
return value if state.nil?
|
|
96
|
+
|
|
97
|
+
state.normalize(value)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns value in uf's standard human-readable representation.
|
|
101
|
+
#
|
|
102
|
+
# If uf is not supported or the normalized value has an invalid
|
|
103
|
+
# length, value is returned unchanged.
|
|
104
|
+
def format(value, uf)
|
|
105
|
+
state = resolve_state(uf)
|
|
106
|
+
|
|
107
|
+
return value if state.nil?
|
|
108
|
+
|
|
109
|
+
state.format(value)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "shared"
|
|
4
|
+
|
|
5
|
+
module BrValidator
|
|
6
|
+
# Validates, normalizes, and formats Brazilian national phone numbers
|
|
7
|
+
# (mobile and landline, no +55 country code).
|
|
8
|
+
module Phone
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
ALLOWED_CHARS = /\A[\d\s()-]+\z/.freeze
|
|
12
|
+
private_constant :ALLOWED_CHARS
|
|
13
|
+
|
|
14
|
+
# Strips formatting and returns the canonical (digits-only)
|
|
15
|
+
# representation of value.
|
|
16
|
+
def normalize(value)
|
|
17
|
+
Shared.remove_non_digits(value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns true if value is a structurally valid Brazilian phone number.
|
|
21
|
+
#
|
|
22
|
+
# Enforces Anatel's numbering plan: the DDD must be one of the 67 codes
|
|
23
|
+
# actually assigned, mobile numbers (11 digits) must carry the "ninth
|
|
24
|
+
# digit" 9 (Resolução nº 553/2010), and landline numbers (10 digits)
|
|
25
|
+
# must start with 2-5.
|
|
26
|
+
def is_valid?(value)
|
|
27
|
+
return false unless value.match?(ALLOWED_CHARS)
|
|
28
|
+
|
|
29
|
+
phone = normalize(value)
|
|
30
|
+
|
|
31
|
+
return false unless [10, 11].include?(phone.length)
|
|
32
|
+
return false unless Shared::VALID_DDDS.include?(phone[0...2])
|
|
33
|
+
|
|
34
|
+
subscriber_first_digit = phone[2]
|
|
35
|
+
|
|
36
|
+
return subscriber_first_digit == "9" if phone.length == 11
|
|
37
|
+
|
|
38
|
+
"2345".include?(subscriber_first_digit)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns value as (XX) XXXXX-XXXX (mobile) or (XX) XXXX-XXXX
|
|
42
|
+
# (landline).
|
|
43
|
+
#
|
|
44
|
+
# If the normalized value has an invalid length, value is returned
|
|
45
|
+
# unchanged.
|
|
46
|
+
def format(value)
|
|
47
|
+
phone = normalize(value)
|
|
48
|
+
|
|
49
|
+
case phone.length
|
|
50
|
+
when 11
|
|
51
|
+
"(#{phone[0...2]}) #{phone[2...7]}-#{phone[7...11]}"
|
|
52
|
+
when 10
|
|
53
|
+
"(#{phone[0...2]}) #{phone[2...6]}-#{phone[6...10]}"
|
|
54
|
+
else
|
|
55
|
+
value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|