br-utils 0.1.1 → 0.2.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +446 -22
  3. data/lib/brazilian-utils/area-code-utils.rb +60 -0
  4. data/lib/brazilian-utils/bank-account-utils.rb +82 -0
  5. data/lib/brazilian-utils/bank-utils.rb +61 -0
  6. data/lib/brazilian-utils/boleto-utils.rb +139 -0
  7. data/lib/brazilian-utils/caepf-utils.rb +91 -0
  8. data/lib/brazilian-utils/cbo-utils.rb +74 -0
  9. data/lib/brazilian-utils/cei-utils.rb +79 -0
  10. data/lib/brazilian-utils/cep-utils.rb +25 -1
  11. data/lib/brazilian-utils/certidao-utils.rb +171 -0
  12. data/lib/brazilian-utils/cfop-utils.rb +74 -0
  13. data/lib/brazilian-utils/cnae-utils.rb +104 -0
  14. data/lib/brazilian-utils/cnh-utils.rb +53 -0
  15. data/lib/brazilian-utils/cno-utils.rb +76 -0
  16. data/lib/brazilian-utils/cnpj-utils.rb +125 -12
  17. data/lib/brazilian-utils/cns-utils.rb +110 -0
  18. data/lib/brazilian-utils/cpf-utils.rb +13 -0
  19. data/lib/brazilian-utils/credit-card-utils.rb +47 -0
  20. data/lib/brazilian-utils/csosn-utils.rb +55 -0
  21. data/lib/brazilian-utils/cst-utils.rb +103 -0
  22. data/lib/brazilian-utils/currency-utils.rb +347 -228
  23. data/lib/brazilian-utils/data/area_codes.json +676 -0
  24. data/lib/brazilian-utils/data/banks.json +357 -0
  25. data/lib/brazilian-utils/data/cbo.json +1 -0
  26. data/lib/brazilian-utils/data/cfop.json +1 -0
  27. data/lib/brazilian-utils/data/cnae.json +1 -0
  28. data/lib/brazilian-utils/data/csosn.json +42 -0
  29. data/lib/brazilian-utils/data/cst.json +294 -0
  30. data/lib/brazilian-utils/data/legal_nature.json +900 -0
  31. data/lib/brazilian-utils/data/municipalities.json +1 -0
  32. data/lib/brazilian-utils/data/ncm.json +1 -0
  33. data/lib/brazilian-utils/data/states.json +218 -0
  34. data/lib/brazilian-utils/date-utils.rb +504 -256
  35. data/lib/brazilian-utils/iban-utils.rb +120 -0
  36. data/lib/brazilian-utils/ie-utils.rb +84 -0
  37. data/lib/brazilian-utils/legal-nature-utils.rb +238 -235
  38. data/lib/brazilian-utils/legal-process-utils.rb +31 -3
  39. data/lib/brazilian-utils/license-plate-utils.rb +21 -5
  40. data/lib/brazilian-utils/municipality-utils.rb +58 -0
  41. data/lib/brazilian-utils/ncm-utils.rb +91 -0
  42. data/lib/brazilian-utils/nfe-key-utils.rb +158 -0
  43. data/lib/brazilian-utils/number-utils.rb +123 -0
  44. data/lib/brazilian-utils/passport-utils.rb +65 -0
  45. data/lib/brazilian-utils/phone-utils.rb +491 -280
  46. data/lib/brazilian-utils/pis-utils.rb +16 -1
  47. data/lib/brazilian-utils/pix-key-utils.rb +70 -0
  48. data/lib/brazilian-utils/pix-payload-utils.rb +253 -0
  49. data/lib/brazilian-utils/registro-profissional-utils.rb +112 -0
  50. data/lib/brazilian-utils/renavam-utils.rb +14 -0
  51. data/lib/brazilian-utils/state-utils.rb +105 -0
  52. data/lib/brazilian-utils/text-utils.rb +105 -0
  53. data/lib/brazilian-utils/vin-utils.rb +46 -0
  54. data/lib/brazilian-utils/voter-id-utils.rb +36 -0
  55. metadata +39 -1
@@ -0,0 +1,120 @@
1
+ module BrazilianUtils
2
+ # Utilities for the Brazilian IBAN: `BR` + 2 ISO 7064 MOD 97-10 check
3
+ # digits + an 8-digit bank ISPB + a 5-digit branch + a 10-digit account +
4
+ # a 1-letter account type + a 1-character owner indicator (29 characters
5
+ # total).
6
+ module IBANUtils
7
+ LENGTH = 29
8
+
9
+ # @private
10
+ def self.apply_mask(chars, group_size)
11
+ chars.chars.each_slice(group_size).map(&:join).join(' ')
12
+ end
13
+
14
+ private_class_method :apply_mask
15
+
16
+ # Removes IBAN formatting, keeps letters and digits upper-cased, capped
17
+ # to 29 characters.
18
+ #
19
+ # @param value [String, Integer]
20
+ # @return [String]
21
+ def self.parse(value)
22
+ return '' unless value.is_a?(String) || value.is_a?(Integer)
23
+
24
+ value.to_s.gsub(/[^a-zA-Z0-9]/, '').upcase[0, LENGTH]
25
+ end
26
+
27
+ # Formats an IBAN in the ISO 13616 print grouping: blocks of 4
28
+ # characters separated by spaces, upper-cased. Does not validate (use
29
+ # {is_valid}).
30
+ #
31
+ # @param value [String]
32
+ # @return [String]
33
+ def self.format(value)
34
+ cleaned = parse(value)
35
+ return '' if cleaned.empty?
36
+
37
+ apply_mask(cleaned, 4)
38
+ end
39
+
40
+ # Normalizes an IBAN candidate, honoring the printed 4-character
41
+ # grouping (a single whitespace, `.`, `-` or `/` between groups; a
42
+ # separator inside a group is rejected).
43
+ #
44
+ # @return [String, nil]
45
+ #
46
+ # @private
47
+ def self.normalize(value)
48
+ return nil unless value.is_a?(String)
49
+
50
+ raw = value.strip
51
+ return nil if raw.empty?
52
+
53
+ alnum_count = 0
54
+ raw.each_char do |ch|
55
+ if ch.match?(/[A-Za-z0-9]/)
56
+ alnum_count += 1
57
+ elsif ch.match?(%r{[\s.\-/]})
58
+ return nil unless alnum_count.positive? && (alnum_count % 4).zero?
59
+ else
60
+ return nil
61
+ end
62
+ end
63
+
64
+ compact = raw.gsub(%r{[\s.\-/]}, '').upcase
65
+ compact.match?(/\A[A-Z0-9]{#{LENGTH}}\z/) ? compact : nil
66
+ end
67
+
68
+ private_class_method :normalize
69
+
70
+ # @private
71
+ def self.checksum_valid?(compact)
72
+ rearranged = compact[4..-1] + compact[0, 4]
73
+ numeric = rearranged.chars.map { |c| c.match?(/[A-Z]/) ? (c.ord - 55).to_s : c }.join
74
+ numeric.to_i % 97 == 1
75
+ end
76
+
77
+ private_class_method :checksum_valid?
78
+
79
+ # Validates a Brazilian IBAN; any other country is invalid.
80
+ #
81
+ # @param value [String]
82
+ # @return [Boolean]
83
+ def self.is_valid(value)
84
+ compact = normalize(value)
85
+ return false unless compact
86
+ return false unless compact.start_with?('BR')
87
+ return false unless compact[2, 2].match?(/\A\d{2}\z/)
88
+ return false unless compact[4, 8].match?(/\A\d{8}\z/)
89
+ return false unless compact[12, 5].match?(/\A\d{5}\z/)
90
+ return false unless compact[17, 10].match?(/\A\d{10}\z/)
91
+ return false unless compact[27, 1].match?(/\A[A-Z]\z/)
92
+ return false unless compact[28, 1].match?(/\A[1-9A-Z]\z/)
93
+
94
+ checksum_valid?(compact)
95
+ end
96
+
97
+ class << self
98
+ alias valid? is_valid
99
+ end
100
+
101
+ # Parses a Brazilian IBAN into its fields.
102
+ #
103
+ # @param value [String]
104
+ # @return [Hash, nil] `nil` whenever {is_valid} would return false.
105
+ def self.get_info(value)
106
+ return nil unless is_valid(value)
107
+
108
+ compact = normalize(value)
109
+ {
110
+ countryCode: compact[0, 2],
111
+ checkDigits: compact[2, 2],
112
+ bankIspb: compact[4, 8],
113
+ branch: compact[12, 5],
114
+ account: compact[17, 10],
115
+ accountType: compact[27, 1],
116
+ owner: compact[28, 1]
117
+ }
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,84 @@
1
+ module BrazilianUtils
2
+ # Utilities for the Inscrição Estadual (IE), the state-level tax
3
+ # registration number.
4
+ #
5
+ # @note This is deliberately a *structural-only* check: each of the 27
6
+ # Brazilian states (and the Distrito Federal) defines its own SINTEGRA
7
+ # check-digit algorithm for its IE, and the contract for this function
8
+ # does not supply a single test case exercising any of them. Without a
9
+ # verifiable reference, computing (and possibly getting wrong) 27
10
+ # different check-digit algorithms would be worse than only validating
11
+ # the one thing that is unambiguous per the contract: the expected
12
+ # digit count for each state. This mirrors the reference (Go)
13
+ # implementation, which takes the same structural-only approach for the
14
+ # same reason.
15
+ module IEUtils
16
+ # State (UF) -> accepted digit-count(s) after stripping mask
17
+ # characters ({space, '.', '-', '/'}).
18
+ DIGIT_COUNTS = {
19
+ 'AC' => [13],
20
+ 'AL' => [9],
21
+ 'AM' => [9],
22
+ 'AP' => [9],
23
+ 'BA' => [8, 9],
24
+ 'CE' => [9],
25
+ 'DF' => [13],
26
+ 'ES' => [9],
27
+ 'GO' => [9],
28
+ 'MA' => [9],
29
+ 'MG' => [13],
30
+ 'MS' => [9],
31
+ 'MT' => [11],
32
+ 'PA' => [9],
33
+ 'PB' => [9],
34
+ 'PE' => [9, 14],
35
+ 'PI' => [9],
36
+ 'PR' => [10],
37
+ 'RJ' => [8],
38
+ 'RN' => [9, 10],
39
+ 'RO' => [9, 14],
40
+ 'RR' => [9],
41
+ 'RS' => [10],
42
+ 'SC' => [9],
43
+ 'SE' => [9],
44
+ 'SP' => [12],
45
+ 'TO' => [9, 11]
46
+ }.freeze
47
+
48
+ # Checks the *structure* of a state Inscrição Estadual: whether its
49
+ # cleaned length matches one of the digit counts accepted by the given
50
+ # UF. This never validates a per-state SINTEGRA check digit.
51
+ #
52
+ # @param value [String, Integer] The IE, bare or separated by any mix
53
+ # of space, `.`, `-` or `/`. For São Paulo, a value starting with
54
+ # `P`/`p` (a produtor rural registration) is accepted when its
55
+ # cleaned length is exactly 13.
56
+ # @param state [String] The two-letter UF code (case-insensitive).
57
+ # @return [Boolean] false for an unknown UF or empty/invalid input.
58
+ def self.is_valid(value, state)
59
+ return false unless value.is_a?(String) || value.is_a?(Integer)
60
+
61
+ uf = state.to_s.strip.upcase
62
+ counts = DIGIT_COUNTS[uf]
63
+ return false unless counts
64
+
65
+ raw = value.to_s.strip
66
+ return false if raw.empty?
67
+
68
+ cleaned = raw.gsub(%r{[\s.\-/]}, '')
69
+ return false if cleaned.empty?
70
+
71
+ if uf == 'SP' && cleaned.start_with?('P', 'p')
72
+ return cleaned.length == 13
73
+ end
74
+
75
+ return false unless cleaned.match?(/\A\d+\z/)
76
+
77
+ counts.include?(cleaned.length)
78
+ end
79
+
80
+ class << self
81
+ alias valid? is_valid
82
+ end
83
+ end
84
+ end