cnpj-dv 0.0.1 → 1.0.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/CHANGELOG.md +17 -0
- data/README.md +223 -0
- data/README.pt.md +186 -0
- data/src/cnpj-dv/cnpj_check_digits.rb +242 -0
- data/src/cnpj-dv/exceptions.rb +141 -0
- data/src/cnpj-dv/version.rb +2 -2
- data/src/cnpj-dv.rb +24 -4
- metadata +33 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 589e0576ee9e9cc2ddfeaf94c5f745792880885d7ea8dfcfb0953695b32a8981
|
|
4
|
+
data.tar.gz: 6662f13dfc238ea843cb79b14d1aaaf526c311e31f63dfc47937846a82ea3a2f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3dbe7e29ebf3299714c285c90fbf170c6fb8a41e028e9fe64216d411233505cb7bb0230369e82d1a71dd14829c1b0031a046fefd21b96f0589af82af34bba09
|
|
7
|
+
data.tar.gz: 3a9f8a328af8c39185dc607db7529da03596235aa194ac57ad2fcdf623b5aad7f4a54124f6926f727a79fd89e9872212b21027a8e707e48d7d7961f902c1b86f
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# cnpj-dv
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### 🚀 Stable Version Released!
|
|
6
|
+
|
|
7
|
+
Utility class to calculate check digits on CNPJ (Brazilian Business Tax ID). Main features:
|
|
8
|
+
|
|
9
|
+
- **Flexible input**: Accepts a `String` or `Array` of strings (formatted or raw).
|
|
10
|
+
- **Format agnostic**: Automatically strips non-alphanumeric characters and uppercases letters before processing.
|
|
11
|
+
- **Alphanumeric CNPJ**: Supports letters `A–Z` in the base; check digits remain numeric via modulo-11.
|
|
12
|
+
- **Lazy evaluation**: `CnpjDV::CnpjCheckDigits` computes `first`, `second`, `both`, and `cnpj` only on first access, then caches.
|
|
13
|
+
- **Eligibility gates**: Rejects wrong types, invalid length, all-zero base/branch IDs, and repeated numeric digits.
|
|
14
|
+
- **Error handling**: `CnpjCheckDigitsInputTypeError` (`TypeError`) plus length/invalid `StandardError` subclasses with structured attributes.
|
|
15
|
+
- **Minimal dependencies**: Requires `lacus-utils` for reusable utilities.
|
|
16
|
+
|
|
17
|
+
For detailed usage and API reference, see the [README](./README.md).
|
data/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
cnpj-dv for Ruby
|
|
2
|
+
|
|
3
|
+
[Gem Version](https://rubygems.org/gems/cnpj-dv)
|
|
4
|
+
[Downloads Count](https://rubygems.org/gems/cnpj-dv)
|
|
5
|
+
[Ruby Version](https://www.ruby-lang.org/)
|
|
6
|
+
[Test Status](https://github.com/LacusSolutions/br-utils-ruby/actions)
|
|
7
|
+
[Last Update Date](https://github.com/LacusSolutions/br-utils-ruby)
|
|
8
|
+
[Project License](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
> 🚀 **Full support for the [new alphanumeric CNPJ format](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).**
|
|
11
|
+
|
|
12
|
+
> 🌎 [Acessar documentação em português](https://github.com/LacusSolutions/br-utils-ruby/blob/main/packages/cnpj-dv/README.pt.md)
|
|
13
|
+
|
|
14
|
+
A Ruby utility to calculate check digits on CNPJ (Brazilian Business Tax ID).
|
|
15
|
+
|
|
16
|
+
## Ruby Support
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
| Ruby 3.2 | Ruby 3.3 | Ruby 3.4 |
|
|
20
|
+
| --------- | --------- | --------- |
|
|
21
|
+
| Passing ✔ | Passing ✔ | Passing ✔ |
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- ✅ **Alphanumeric CNPJ**: Full support for the new alphanumeric CNPJ format (introduced in 2026)
|
|
29
|
+
- ✅ **Flexible input**: Accepts `String` or `Array` of strings
|
|
30
|
+
- ✅ **Format agnostic**: Strips non-alphanumeric characters from string input and uppercases letters
|
|
31
|
+
- ✅ **Auto-expansion**: Multi-character strings in arrays are joined and parsed like a single string
|
|
32
|
+
- ✅ **Input validation**: Rejects ineligible CNPJs (all-zero base ID `00000000`, all-zero branch `0000`, or 12 numeric-only repeated digits)
|
|
33
|
+
- ✅ **Lazy evaluation**: Check digits are calculated only when accessed (via methods)
|
|
34
|
+
- ✅ **Caching**: Calculated values are cached for subsequent access
|
|
35
|
+
- ✅ **Minimal dependencies**: Only `[lacus-utils](https://rubygems.org/gems/lacus-utils)`
|
|
36
|
+
- ✅ **Error handling**: Specific types for type, length, and invalid CNPJ scenarios (`TypeError` vs `StandardError` semantics)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Install the gem directly:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
gem install cnpj-dv
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or add it to your `Gemfile` and run `bundle install`:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
gem 'cnpj-dv'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## Require
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
require 'cnpj-dv'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Quick Start
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
require 'cnpj-dv'
|
|
68
|
+
|
|
69
|
+
check_digits = CnpjDV::CnpjCheckDigits.new('914157320007')
|
|
70
|
+
|
|
71
|
+
check_digits.first # => '9'
|
|
72
|
+
check_digits.second # => '3'
|
|
73
|
+
check_digits.both # => '93'
|
|
74
|
+
check_digits.cnpj # => '91415732000793'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
With alphanumeric CNPJ (new format):
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
require 'cnpj-dv'
|
|
81
|
+
|
|
82
|
+
check_digits = CnpjDV::CnpjCheckDigits.new('MGKGMJ9X0001')
|
|
83
|
+
|
|
84
|
+
check_digits.first # => '6'
|
|
85
|
+
check_digits.second # => '8'
|
|
86
|
+
check_digits.both # => '68'
|
|
87
|
+
check_digits.cnpj # => 'MGKGMJ9X000168'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## Usage
|
|
93
|
+
|
|
94
|
+
The main resource of this package is the class `CnpjDV::CnpjCheckDigits`. Through an instance, you access CNPJ check-digit information:
|
|
95
|
+
|
|
96
|
+
- `initialize`: `CnpjDV::CnpjCheckDigits.new(cnpj_input)` — `cnpj_input` must be a `String` or an `Array` of strings. After sanitization, the value must have 12–14 alphanumeric characters (formatting stripped from strings; letters uppercased). Only the **first 12** characters are used as the base; if you pass 13 or 14 characters (e.g. a full CNPJ including prior check digits), characters 13–14 are **ignored** and the digits are recalculated. There are **no options**, keyword arguments, or configuration objects — the constructor takes only the CNPJ input.
|
|
97
|
+
- `first`: First check digit (13th character of the full CNPJ). Lazy, cached.
|
|
98
|
+
- `second`: Second check digit (14th character of the full CNPJ). Lazy, cached.
|
|
99
|
+
- `both`: Both check digits concatenated as a string.
|
|
100
|
+
- `cnpj`: The complete CNPJ as a string of 14 characters (12 base characters + 2 check digits).
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
### Input formats
|
|
105
|
+
|
|
106
|
+
The `CnpjCheckDigits` class accepts multiple input formats:
|
|
107
|
+
|
|
108
|
+
**String input:** raw digits and/or letters, or formatted CNPJ (e.g. `91.415.732/0007-93`, `MG.KGM.J9X/0001-68`). Non-alphanumeric characters are removed; lowercase letters are uppercased.
|
|
109
|
+
|
|
110
|
+
**Array of strings:** each element must be a string; values are concatenated and then parsed like a single string (e.g. `['9','1','4',…]`, `['9141','5732','0007']`, `['MG','KGM','J9X','0001']`). Non-string elements are not allowed.
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
require 'cnpj-dv'
|
|
114
|
+
|
|
115
|
+
# String — plain, formatted, or with existing check digits (only first 12 chars used)
|
|
116
|
+
CnpjDV::CnpjCheckDigits.new('914157320007')
|
|
117
|
+
CnpjDV::CnpjCheckDigits.new('91.415.732/0007')
|
|
118
|
+
CnpjDV::CnpjCheckDigits.new('91415732000793')
|
|
119
|
+
|
|
120
|
+
# Array of strings — single- or multi-character elements
|
|
121
|
+
CnpjDV::CnpjCheckDigits.new(%w[9 1 4 1 5 7 3 2 0 0 0 7])
|
|
122
|
+
CnpjDV::CnpjCheckDigits.new(%w[9141 5732 0007])
|
|
123
|
+
CnpjDV::CnpjCheckDigits.new(%w[MG KGM J9X 0001])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
### Errors & exceptions handling
|
|
129
|
+
|
|
130
|
+
This package uses **TypeError vs StandardError** semantics: *type errors* indicate incorrect API use (e.g. wrong type); *exceptions* indicate invalid or ineligible data (e.g. invalid length or business rules). You can rescue specific classes or use the base classes.
|
|
131
|
+
|
|
132
|
+
- `CnpjDV::CnpjCheckDigitsTypeError` — base class for type errors; extends Ruby’s `TypeError`
|
|
133
|
+
- `CnpjDV::CnpjCheckDigitsInputTypeError` — input is not `String` or `Array` of strings (or array contains a non-string element)
|
|
134
|
+
- `CnpjDV::CnpjCheckDigitsException` — base class for data/flow exceptions; extends `StandardError`
|
|
135
|
+
- `CnpjDV::CnpjCheckDigitsInputLengthException` — sanitized length is not 12–14
|
|
136
|
+
- `CnpjDV::CnpjCheckDigitsInputInvalidException` — base ID `00000000`, branch ID `0000`, or 12 identical numeric digits (repeated-digit pattern)
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
require 'cnpj-dv'
|
|
140
|
+
|
|
141
|
+
# Input type (e.g. integer not allowed)
|
|
142
|
+
begin
|
|
143
|
+
CnpjDV::CnpjCheckDigits.new(12_345_678_000_100)
|
|
144
|
+
rescue CnpjDV::CnpjCheckDigitsInputTypeError => e
|
|
145
|
+
puts e.message
|
|
146
|
+
# => CNPJ input must be of type string or string[]. Got integer number.
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Length (must be 12–14 alphanumeric characters after sanitization)
|
|
150
|
+
begin
|
|
151
|
+
CnpjDV::CnpjCheckDigits.new('12345678901')
|
|
152
|
+
rescue CnpjDV::CnpjCheckDigitsInputLengthException => e
|
|
153
|
+
puts e.message
|
|
154
|
+
# => CNPJ input "12345678901" does not contain 12 to 14 characters. Got 11.
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Invalid (e.g. all-zero base or branch, or repeated numeric digits)
|
|
158
|
+
begin
|
|
159
|
+
CnpjDV::CnpjCheckDigits.new('000000000001')
|
|
160
|
+
rescue CnpjDV::CnpjCheckDigitsInputInvalidException => e
|
|
161
|
+
puts e.message
|
|
162
|
+
# => CNPJ input "000000000001" is invalid. Base ID "00000000" is not eligible.
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Any data exception from the package
|
|
166
|
+
begin
|
|
167
|
+
CnpjDV::CnpjCheckDigits.new('000000000001')
|
|
168
|
+
rescue CnpjDV::CnpjCheckDigitsException => e
|
|
169
|
+
puts e.message
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Notable attributes on raised errors:
|
|
174
|
+
|
|
175
|
+
- `CnpjCheckDigitsInputTypeError`: `actual_input`, `actual_type`, `expected_type`
|
|
176
|
+
- `CnpjCheckDigitsInputLengthException`: `actual_input`, `evaluated_input`, `min_expected_length`, `max_expected_length`
|
|
177
|
+
- `CnpjCheckDigitsInputInvalidException`: `actual_input`, `reason`
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
### Other available resources
|
|
182
|
+
|
|
183
|
+
After `require 'cnpj-dv'`:
|
|
184
|
+
|
|
185
|
+
- `CnpjDV::CNPJ_MIN_LENGTH`: `12`
|
|
186
|
+
- `CnpjDV::CNPJ_MAX_LENGTH`: `14`
|
|
187
|
+
- **Exceptions**: see above
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
## Calculation algorithm
|
|
192
|
+
|
|
193
|
+
The package computes check digits with the official Brazilian modulo-11 rules extended to alphanumeric characters:
|
|
194
|
+
|
|
195
|
+
1. **Character value:** each character contributes `ord(character) − 48` (so `0`–`9` stay 0–9; letters use their ASCII offset from `0`).
|
|
196
|
+
2. **Weights:** from **right to left**, multiply by weights that cycle **2, 3, 4, 5, 6, 7, 8, 9**, then repeat from 2.
|
|
197
|
+
3. **First check digit (13th position):** apply steps 1–2 to the first **12** base characters; let `r = sum % 11`. The digit is `0` if `r < 2`, otherwise `11 − r`.
|
|
198
|
+
4. **Second check digit (14th position):** apply steps 1–2 to the first 12 characters **plus** the first check digit; same formula for `r`.
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
## Contribution & Support
|
|
203
|
+
|
|
204
|
+
We welcome contributions! Please see our [Contributing Guidelines](https://github.com/LacusSolutions/br-utils-ruby/blob/main/CONTRIBUTING.md) for details. If you find this project helpful, please consider:
|
|
205
|
+
|
|
206
|
+
- ⭐ Starring the repository
|
|
207
|
+
- 🤝 Contributing to the codebase
|
|
208
|
+
- 💡 [Suggesting new features](https://github.com/LacusSolutions/br-utils-ruby/issues)
|
|
209
|
+
- 🐛 [Reporting bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
This project is licensed under the MIT License — see the [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE) file for details.
|
|
216
|
+
|
|
217
|
+
## Changelog
|
|
218
|
+
|
|
219
|
+
See [CHANGELOG](./CHANGELOG.md) for a list of changes and version history.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
Made with ❤️ by [Lacus Solutions](https://github.com/LacusSolutions)
|
data/README.pt.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
> 🚀 **Suporte total ao [novo formato alfanumérico de CNPJ](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).**
|
|
4
|
+
|
|
5
|
+
> 🌎 [Access documentation in English](https://github.com/LacusSolutions/br-utils-ruby/blob/main/packages/cnpj-dv/README.md)
|
|
6
|
+
|
|
7
|
+
Utilitário em Ruby para calcular os dígitos verificadores de CNPJ (Cadastro Nacional da Pessoa Jurídica).
|
|
8
|
+
|
|
9
|
+
## Recursos
|
|
10
|
+
|
|
11
|
+
- ✅ **CNPJ alfanumérico**: Suporte completo ao novo formato alfanumérico de CNPJ (a partir de 2026)
|
|
12
|
+
- ✅ **Entrada flexível**: Aceita `String` ou `Array` de strings
|
|
13
|
+
- ✅ **Agnóstico ao formato**: Remove caracteres não alfanuméricos da entrada em string e converte letras para maiúsculas
|
|
14
|
+
- ✅ **Junção em array**: Strings com vários caracteres em arrays são concatenadas e interpretadas como uma única sequência
|
|
15
|
+
- ✅ **Validação de entrada**: Rejeita CNPJs inelegíveis (base toda zero `00000000`, filial `0000`, ou 12 dígitos numéricos repetidos)
|
|
16
|
+
- ✅ **Avaliação lazy**: Dígitos verificadores são calculados apenas quando acessados (via métodos)
|
|
17
|
+
- ✅ **Cache**: Valores calculados são armazenados em cache para acessos subsequentes
|
|
18
|
+
- ✅ **Dependências mínimas**: Apenas [`lacus-utils`](https://rubygems.org/gems/lacus-utils)
|
|
19
|
+
- ✅ **Tratamento de erros**: Tipos específicos para tipo, tamanho e CNPJ inválido (semântica `TypeError` vs `StandardError`)
|
|
20
|
+
|
|
21
|
+
## Instalação
|
|
22
|
+
|
|
23
|
+
Instale a gem diretamente:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gem install cnpj-dv
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Ou adicione ao seu `Gemfile` e execute `bundle install`:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
gem 'cnpj-dv'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Require
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
require 'cnpj-dv'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Início rápido
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
require 'cnpj-dv'
|
|
45
|
+
|
|
46
|
+
check_digits = CnpjDV::CnpjCheckDigits.new('914157320007')
|
|
47
|
+
|
|
48
|
+
check_digits.first # => '9'
|
|
49
|
+
check_digits.second # => '3'
|
|
50
|
+
check_digits.both # => '93'
|
|
51
|
+
check_digits.cnpj # => '91415732000793'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Com CNPJ alfanumérico (novo formato):
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
require 'cnpj-dv'
|
|
58
|
+
|
|
59
|
+
check_digits = CnpjDV::CnpjCheckDigits.new('MGKGMJ9X0001')
|
|
60
|
+
|
|
61
|
+
check_digits.first # => '6'
|
|
62
|
+
check_digits.second # => '8'
|
|
63
|
+
check_digits.both # => '68'
|
|
64
|
+
check_digits.cnpj # => 'MGKGMJ9X000168'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Utilização
|
|
68
|
+
|
|
69
|
+
O principal recurso deste pacote é a classe `CnpjDV::CnpjCheckDigits`. Por meio da instância, você acessa as informações dos dígitos verificadores do CNPJ:
|
|
70
|
+
|
|
71
|
+
- **`initialize`**: `CnpjDV::CnpjCheckDigits.new(cnpj_input)` — `cnpj_input` deve ser `String` ou `Array` de strings. Após a sanitização, o valor deve ter 12–14 caracteres alfanuméricos (formatação removida em strings; letras em maiúsculas). Apenas os **primeiros 12** caracteres entram como base; com 13 ou 14 caracteres (ex.: CNPJ completo com DV anteriores), os caracteres 13 e 14 são **ignorados** e os dígitos são recalculados. Não há **opções**, argumentos nomeados nem objeto de configuração — o construtor recebe apenas a entrada de CNPJ.
|
|
72
|
+
- **`first`**: Primeiro dígito verificador (13º caractere do CNPJ completo). Lazy, em cache.
|
|
73
|
+
- **`second`**: Segundo dígito verificador (14º caractere do CNPJ completo). Lazy, em cache.
|
|
74
|
+
- **`both`**: Ambos os dígitos verificadores concatenados em uma string.
|
|
75
|
+
- **`cnpj`**: O CNPJ completo como string de 14 caracteres (12 da base + 2 dígitos verificadores).
|
|
76
|
+
|
|
77
|
+
### Formatos de entrada
|
|
78
|
+
|
|
79
|
+
A classe `CnpjCheckDigits` aceita múltiplos formatos de entrada:
|
|
80
|
+
|
|
81
|
+
**String:** dígitos e/ou letras crus, ou CNPJ formatado (ex.: `91.415.732/0007-93`, `MG.KGM.J9X/0001-68`). Caracteres não alfanuméricos são removidos; letras minúsculas viram maiúsculas.
|
|
82
|
+
|
|
83
|
+
**Array de strings:** cada elemento deve ser string; os valores são concatenados e interpretados como uma única string (ex.: `['9','1','4',…]`, `['9141','5732','0007']`, `['MG','KGM','J9X','0001']`). Elementos que não são strings não são permitidos.
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
require 'cnpj-dv'
|
|
87
|
+
|
|
88
|
+
# String — crua, formatada ou com DV existentes (apenas os 12 primeiros caracteres são usados)
|
|
89
|
+
CnpjDV::CnpjCheckDigits.new('914157320007')
|
|
90
|
+
CnpjDV::CnpjCheckDigits.new('91.415.732/0007')
|
|
91
|
+
CnpjDV::CnpjCheckDigits.new('91415732000793')
|
|
92
|
+
|
|
93
|
+
# Array de strings — elementos de um ou vários caracteres
|
|
94
|
+
CnpjDV::CnpjCheckDigits.new(%w[9 1 4 1 5 7 3 2 0 0 0 7])
|
|
95
|
+
CnpjDV::CnpjCheckDigits.new(%w[9141 5732 0007])
|
|
96
|
+
CnpjDV::CnpjCheckDigits.new(%w[MG KGM J9X 0001])
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Erros e exceções
|
|
100
|
+
|
|
101
|
+
Este pacote usa a distinção **TypeError vs StandardError**: *erros de tipo* indicam uso incorreto da API (ex.: tipo errado); *exceções* indicam dados inválidos ou inelegíveis (ex.: tamanho ou regras de negócio). Você pode resgatar classes específicas ou as classes base.
|
|
102
|
+
|
|
103
|
+
- **`CnpjDV::CnpjCheckDigitsTypeError`** — classe base para erros de tipo; estende o `TypeError` do Ruby
|
|
104
|
+
- **`CnpjDV::CnpjCheckDigitsInputTypeError`** — entrada não é `String` nem `Array` de strings (ou o array contém elemento que não é string)
|
|
105
|
+
- **`CnpjDV::CnpjCheckDigitsException`** — classe base para exceções de dados/fluxo; estende `StandardError`
|
|
106
|
+
- **`CnpjDV::CnpjCheckDigitsInputLengthException`** — tamanho após sanitização não é 12–14
|
|
107
|
+
- **`CnpjDV::CnpjCheckDigitsInputInvalidException`** — base `00000000`, filial `0000`, ou 12 dígitos numéricos idênticos (padrão de repetição)
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
require 'cnpj-dv'
|
|
111
|
+
|
|
112
|
+
# Tipo de entrada (ex.: inteiro não permitido)
|
|
113
|
+
begin
|
|
114
|
+
CnpjDV::CnpjCheckDigits.new(12_345_678_000_100)
|
|
115
|
+
rescue CnpjDV::CnpjCheckDigitsInputTypeError => e
|
|
116
|
+
puts e.message
|
|
117
|
+
# => CNPJ input must be of type string or string[]. Got integer number.
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Tamanho (deve ser 12–14 caracteres alfanuméricos após sanitização)
|
|
121
|
+
begin
|
|
122
|
+
CnpjDV::CnpjCheckDigits.new('12345678901')
|
|
123
|
+
rescue CnpjDV::CnpjCheckDigitsInputLengthException => e
|
|
124
|
+
puts e.message
|
|
125
|
+
# => CNPJ input "12345678901" does not contain 12 to 14 characters. Got 11.
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Inválido (ex.: base ou filial zeradas, ou dígitos numéricos repetidos)
|
|
129
|
+
begin
|
|
130
|
+
CnpjDV::CnpjCheckDigits.new('000000000001')
|
|
131
|
+
rescue CnpjDV::CnpjCheckDigitsInputInvalidException => e
|
|
132
|
+
puts e.message
|
|
133
|
+
# => CNPJ input "000000000001" is invalid. Base ID "00000000" is not eligible.
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Qualquer exceção de dados do pacote
|
|
137
|
+
begin
|
|
138
|
+
CnpjDV::CnpjCheckDigits.new('000000000001')
|
|
139
|
+
rescue CnpjDV::CnpjCheckDigitsException => e
|
|
140
|
+
puts e.message
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Atributos relevantes nos erros:
|
|
145
|
+
|
|
146
|
+
- `CnpjCheckDigitsInputTypeError`: `actual_input`, `actual_type`, `expected_type`
|
|
147
|
+
- `CnpjCheckDigitsInputLengthException`: `actual_input`, `evaluated_input`, `min_expected_length`, `max_expected_length`
|
|
148
|
+
- `CnpjCheckDigitsInputInvalidException`: `actual_input`, `reason`
|
|
149
|
+
|
|
150
|
+
### Outros recursos disponíveis
|
|
151
|
+
|
|
152
|
+
Após `require 'cnpj-dv'`:
|
|
153
|
+
|
|
154
|
+
- **`CnpjDV::CNPJ_MIN_LENGTH`**: `12`
|
|
155
|
+
- **`CnpjDV::CNPJ_MAX_LENGTH`**: `14`
|
|
156
|
+
- **Exceções**: veja acima
|
|
157
|
+
|
|
158
|
+
## Algoritmo de cálculo
|
|
159
|
+
|
|
160
|
+
O pacote calcula os dígitos verificadores com as regras oficiais brasileiras de módulo 11 estendidas a caracteres alfanuméricos:
|
|
161
|
+
|
|
162
|
+
1. **Valor do caractere:** cada caractere contribui com `ord(caractere) − 48` (assim `0`–`9` permanecem 0–9; letras usam o deslocamento ASCII em relação a `0`).
|
|
163
|
+
2. **Pesos:** da **direita para a esquerda**, multiplicar pelos pesos que ciclam **2, 3, 4, 5, 6, 7, 8, 9** e voltam a 2.
|
|
164
|
+
3. **Primeiro dígito verificador (13ª posição):** aplicar os itens 1–2 aos **primeiros 12** caracteres da base; seja `r = soma % 11`. O dígito é `0` se `r < 2`, senão `11 − r`.
|
|
165
|
+
4. **Segundo dígito verificador (14ª posição):** aplicar os itens 1–2 aos 12 primeiros caracteres **mais** o primeiro dígito verificador; mesma fórmula para `r`.
|
|
166
|
+
|
|
167
|
+
## Contribuição e suporte
|
|
168
|
+
|
|
169
|
+
Contribuições são bem-vindas! Consulte as [Diretrizes de contribuição](https://github.com/LacusSolutions/br-utils-ruby/blob/main/CONTRIBUTING.md). Se o projeto for útil para você, considere:
|
|
170
|
+
|
|
171
|
+
- ⭐ Dar uma estrela no repositório
|
|
172
|
+
- 🤝 Contribuir com código
|
|
173
|
+
- 💡 [Sugerir novas funcionalidades](https://github.com/LacusSolutions/br-utils-ruby/issues)
|
|
174
|
+
- 🐛 [Reportar bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
|
|
175
|
+
|
|
176
|
+
## Licença
|
|
177
|
+
|
|
178
|
+
Este projeto está sob a licença MIT — veja o arquivo [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE).
|
|
179
|
+
|
|
180
|
+
## Changelog
|
|
181
|
+
|
|
182
|
+
Veja o [CHANGELOG](./CHANGELOG.md) para alterações e histórico de versões.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
Feito com ❤️ por [Lacus Solutions](https://github.com/LacusSolutions)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'exceptions'
|
|
4
|
+
|
|
5
|
+
module CnpjDV
|
|
6
|
+
# Minimum number of characters required for the CNPJ check digits calculation.
|
|
7
|
+
CNPJ_MIN_LENGTH = 12
|
|
8
|
+
|
|
9
|
+
# Maximum number of characters accepted as input for the CNPJ check digits
|
|
10
|
+
# calculation.
|
|
11
|
+
CNPJ_MAX_LENGTH = 14
|
|
12
|
+
|
|
13
|
+
CNPJ_BASE_ID_LENGTH = 8
|
|
14
|
+
CNPJ_INVALID_BASE_ID = '0' * CNPJ_BASE_ID_LENGTH
|
|
15
|
+
CNPJ_BRANCH_ID_LENGTH = 4
|
|
16
|
+
CNPJ_INVALID_BRANCH_ID = '0' * CNPJ_BRANCH_ID_LENGTH
|
|
17
|
+
|
|
18
|
+
DELTA_FACTOR = '0'.ord
|
|
19
|
+
WEIGHTS = [2, 3, 4, 5, 6, 7, 8, 9].freeze
|
|
20
|
+
DIGIT_CHARS = '0123456789'
|
|
21
|
+
|
|
22
|
+
private_constant :CNPJ_BASE_ID_LENGTH, :CNPJ_INVALID_BASE_ID,
|
|
23
|
+
:CNPJ_BRANCH_ID_LENGTH, :CNPJ_INVALID_BRANCH_ID,
|
|
24
|
+
:DELTA_FACTOR, :WEIGHTS, :DIGIT_CHARS
|
|
25
|
+
|
|
26
|
+
# Calculates and exposes CNPJ check digits from a valid base input.
|
|
27
|
+
#
|
|
28
|
+
# Validates length, base ID, branch ID and rejects repeated numeric digits.
|
|
29
|
+
#
|
|
30
|
+
# @example Numeric base
|
|
31
|
+
# check_digits = CnpjDV::CnpjCheckDigits.new('914157320007')
|
|
32
|
+
# check_digits.first # => '9'
|
|
33
|
+
# check_digits.second # => '3'
|
|
34
|
+
# check_digits.both # => '93'
|
|
35
|
+
# check_digits.cnpj # => '91415732000793'
|
|
36
|
+
#
|
|
37
|
+
# @example Alphanumeric base
|
|
38
|
+
# check_digits = CnpjDV::CnpjCheckDigits.new('MGKGMJ9X0001')
|
|
39
|
+
# check_digits.first # => '6'
|
|
40
|
+
# check_digits.second # => '8'
|
|
41
|
+
# check_digits.both # => '68'
|
|
42
|
+
# check_digits.cnpj # => 'MGKGMJ9X000168'
|
|
43
|
+
class CnpjCheckDigits
|
|
44
|
+
# Creates a calculator for the given CNPJ base (12 to 14 characters).
|
|
45
|
+
#
|
|
46
|
+
# @param cnpj_input [String, Array<String>] alphanumeric CNPJ with or without
|
|
47
|
+
# formatting, or an array of strings
|
|
48
|
+
# @raise [CnpjCheckDigitsInputTypeError] when input is not a +String+ or
|
|
49
|
+
# +Array<String>+
|
|
50
|
+
# @raise [CnpjCheckDigitsInputLengthException] when character count is not
|
|
51
|
+
# between 12 and 14
|
|
52
|
+
# @raise [CnpjCheckDigitsInputInvalidException] when base ID is all zero
|
|
53
|
+
# (+00.000.000+), branch ID is all zero (+0000+) or all digits are numeric
|
|
54
|
+
# the same (repeated digits, e.g. +77.777.777/7777-...+)
|
|
55
|
+
def initialize(cnpj_input)
|
|
56
|
+
parsed_input = parse_input(cnpj_input)
|
|
57
|
+
|
|
58
|
+
validate_length(parsed_input, cnpj_input)
|
|
59
|
+
validate_base_id(parsed_input, cnpj_input)
|
|
60
|
+
validate_branch_id(parsed_input, cnpj_input)
|
|
61
|
+
validate_non_repeated_digits(parsed_input, cnpj_input)
|
|
62
|
+
|
|
63
|
+
@cnpj_chars = parsed_input[0, CNPJ_MIN_LENGTH]
|
|
64
|
+
@cached_first_digit = nil
|
|
65
|
+
@cached_second_digit = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# First check digit (13th character of the full CNPJ).
|
|
69
|
+
#
|
|
70
|
+
# @return [String] a single numeric character (+"0"+–+"9"+)
|
|
71
|
+
def first
|
|
72
|
+
@cached_first_digit = _calculate(@cnpj_chars) if @cached_first_digit.nil?
|
|
73
|
+
|
|
74
|
+
DIGIT_CHARS[@cached_first_digit]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Second check digit (14th character of the full CNPJ).
|
|
78
|
+
#
|
|
79
|
+
# @return [String] a single numeric character (+"0"+–+"9"+)
|
|
80
|
+
def second
|
|
81
|
+
@cached_second_digit = _calculate([*@cnpj_chars, first]) if @cached_second_digit.nil?
|
|
82
|
+
|
|
83
|
+
DIGIT_CHARS[@cached_second_digit]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Both check digits concatenated (13th and 14th characters).
|
|
87
|
+
#
|
|
88
|
+
# @return [String] two-character numeric string
|
|
89
|
+
def both
|
|
90
|
+
first + second
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Full 14-character CNPJ (base 12 characters concatenated with the 2 check
|
|
94
|
+
# digits).
|
|
95
|
+
#
|
|
96
|
+
# @return [String] 14-character CNPJ (base may contain letters + numeric DVs)
|
|
97
|
+
def cnpj
|
|
98
|
+
@cnpj_chars.join + both
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Protected (not private) so test spy subclasses can override and call +super+.
|
|
102
|
+
# Leading underscore matches the cross-language helper name (`_calculate`).
|
|
103
|
+
protected
|
|
104
|
+
|
|
105
|
+
# Computes a single check digit using the standard CNPJ modulo-11 algorithm.
|
|
106
|
+
#
|
|
107
|
+
# @param cnpj_sequence [Array<String>] characters used in the weighted sum
|
|
108
|
+
# @return [Integer] check digit in the range 0–9
|
|
109
|
+
def _calculate(cnpj_sequence)
|
|
110
|
+
length = cnpj_sequence.length
|
|
111
|
+
sum_result = 0
|
|
112
|
+
|
|
113
|
+
(length - 1).downto(0) do |index|
|
|
114
|
+
char_value = cnpj_sequence[index].ord - DELTA_FACTOR
|
|
115
|
+
sum_result += char_value * WEIGHTS[(length - 1 - index) % 8]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
remainder = sum_result % 11
|
|
119
|
+
|
|
120
|
+
remainder < 2 ? 0 : 11 - remainder
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
# Parses a string or an array of strings into alphanumeric characters.
|
|
126
|
+
#
|
|
127
|
+
# @param cnpj_input [Object] candidate CNPJ input
|
|
128
|
+
# @return [Array<String>] uppercase alphanumeric characters
|
|
129
|
+
# @raise [CnpjCheckDigitsInputTypeError] when input is not a +String+ or
|
|
130
|
+
# +Array<String>+
|
|
131
|
+
def parse_input(cnpj_input)
|
|
132
|
+
return parse_string_input(cnpj_input) if cnpj_input.is_a?(String)
|
|
133
|
+
return parse_array_input(cnpj_input) if cnpj_input.is_a?(Array)
|
|
134
|
+
|
|
135
|
+
raise CnpjCheckDigitsInputTypeError.new(cnpj_input, 'string or string[]')
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Strips non-alphanumeric characters and uppercases the remainder.
|
|
139
|
+
#
|
|
140
|
+
# @param cnpj_string [String] raw or formatted CNPJ string
|
|
141
|
+
# @return [Array<String>] uppercase alphanumeric characters
|
|
142
|
+
def parse_string_input(cnpj_string)
|
|
143
|
+
result = []
|
|
144
|
+
|
|
145
|
+
cnpj_string.each_char do |char|
|
|
146
|
+
code = char.ord
|
|
147
|
+
if code.between?(48, 57) || code.between?(65, 90)
|
|
148
|
+
result << char
|
|
149
|
+
elsif code.between?(97, 122)
|
|
150
|
+
result << (code - 32).chr
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
result
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Concatenates an array of strings and normalizes the result.
|
|
158
|
+
#
|
|
159
|
+
# @param cnpj_array [Array] candidate array of string chunks
|
|
160
|
+
# @return [Array<String>] uppercase alphanumeric characters
|
|
161
|
+
# @raise [CnpjCheckDigitsInputTypeError] when input is not a +String+ or
|
|
162
|
+
# +Array<String>+
|
|
163
|
+
def parse_array_input(cnpj_array)
|
|
164
|
+
return [] if cnpj_array.empty?
|
|
165
|
+
|
|
166
|
+
raise CnpjCheckDigitsInputTypeError.new(cnpj_array, 'string or string[]') unless cnpj_array.all?(String)
|
|
167
|
+
|
|
168
|
+
parse_string_input(cnpj_array.join)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Ensures character count is between {CNPJ_MIN_LENGTH} and
|
|
172
|
+
# {CNPJ_MAX_LENGTH}.
|
|
173
|
+
#
|
|
174
|
+
# @param cnpj_chars [Array<String>] normalized characters
|
|
175
|
+
# @param original_input [String, Array<String>] original caller input
|
|
176
|
+
# @raise [CnpjCheckDigitsInputLengthException] when character count is not
|
|
177
|
+
# between 12 and 14
|
|
178
|
+
def validate_length(cnpj_chars, original_input)
|
|
179
|
+
chars_count = cnpj_chars.length
|
|
180
|
+
|
|
181
|
+
return if chars_count.between?(CNPJ_MIN_LENGTH, CNPJ_MAX_LENGTH)
|
|
182
|
+
|
|
183
|
+
raise CnpjCheckDigitsInputLengthException.new(
|
|
184
|
+
original_input,
|
|
185
|
+
cnpj_chars.join,
|
|
186
|
+
CNPJ_MIN_LENGTH,
|
|
187
|
+
CNPJ_MAX_LENGTH
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Rejects base ID (first 8 digits) when it is all zeros.
|
|
192
|
+
#
|
|
193
|
+
# @param cnpj_chars [Array<String>] normalized characters
|
|
194
|
+
# @param original_input [String, Array<String>] original caller input
|
|
195
|
+
# @raise [CnpjCheckDigitsInputInvalidException] when base ID is all zeros
|
|
196
|
+
# (+00.000.000+)
|
|
197
|
+
def validate_base_id(cnpj_chars, original_input)
|
|
198
|
+
return unless cnpj_chars[0, CNPJ_BASE_ID_LENGTH].all? { |char| char == '0' }
|
|
199
|
+
|
|
200
|
+
raise CnpjCheckDigitsInputInvalidException.new(
|
|
201
|
+
original_input,
|
|
202
|
+
"Base ID \"#{CNPJ_INVALID_BASE_ID}\" is not eligible."
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Rejects branch ID (digits 9–12) when it is all zeros.
|
|
207
|
+
#
|
|
208
|
+
# @param cnpj_chars [Array<String>] normalized characters
|
|
209
|
+
# @param original_input [String, Array<String>] original caller input
|
|
210
|
+
# @raise [CnpjCheckDigitsInputInvalidException] when branch ID is all zeros
|
|
211
|
+
# (+0000+)
|
|
212
|
+
def validate_branch_id(cnpj_chars, original_input)
|
|
213
|
+
branch_start = CNPJ_BASE_ID_LENGTH
|
|
214
|
+
branch_end = branch_start + CNPJ_BRANCH_ID_LENGTH
|
|
215
|
+
branch_id = cnpj_chars[branch_start...branch_end]
|
|
216
|
+
|
|
217
|
+
return unless branch_id.all? { |char| char == '0' }
|
|
218
|
+
|
|
219
|
+
raise CnpjCheckDigitsInputInvalidException.new(
|
|
220
|
+
original_input,
|
|
221
|
+
"Branch ID \"#{CNPJ_INVALID_BRANCH_ID}\" is not eligible."
|
|
222
|
+
)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Rejects inputs where all first 12 characters are the same numeric digit.
|
|
226
|
+
#
|
|
227
|
+
# @param cnpj_chars [Array<String>] normalized characters
|
|
228
|
+
# @param original_input [String, Array<String>] original caller input
|
|
229
|
+
# @raise [CnpjCheckDigitsInputInvalidException] when all digits are numeric
|
|
230
|
+
# the same (repeated digits, e.g. +77.777.777/7777-...+)
|
|
231
|
+
def validate_non_repeated_digits(cnpj_chars, original_input)
|
|
232
|
+
first_char = cnpj_chars[0]
|
|
233
|
+
return unless first_char.match?(/\A\d\z/)
|
|
234
|
+
return unless cnpj_chars[1, CNPJ_MIN_LENGTH - 1].all? { |char| char == first_char }
|
|
235
|
+
|
|
236
|
+
raise CnpjCheckDigitsInputInvalidException.new(
|
|
237
|
+
original_input,
|
|
238
|
+
'Repeated digits are not considered valid.'
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'lacus-utils'
|
|
5
|
+
|
|
6
|
+
module CnpjDV
|
|
7
|
+
# Formats the original input for inclusion in an exception message.
|
|
8
|
+
module FormatActualInput
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# @param actual_input [Object] the original input value
|
|
12
|
+
# @return [String] a quoted string, or compact JSON for arrays
|
|
13
|
+
def call(actual_input)
|
|
14
|
+
return "\"#{actual_input}\"" if actual_input.is_a?(String)
|
|
15
|
+
|
|
16
|
+
JSON.generate(actual_input)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
private_constant :FormatActualInput
|
|
20
|
+
|
|
21
|
+
# Base error for all `cnpj-dv` type-related errors.
|
|
22
|
+
#
|
|
23
|
+
# This class extends the native {TypeError} and serves as the base for all
|
|
24
|
+
# type validation errors in {CnpjCheckDigits}.
|
|
25
|
+
class CnpjCheckDigitsTypeError < TypeError
|
|
26
|
+
# @return [Object] the offending input value
|
|
27
|
+
attr_reader :actual_input
|
|
28
|
+
|
|
29
|
+
# @return [String] human-readable type of {#actual_input}
|
|
30
|
+
attr_reader :actual_type
|
|
31
|
+
|
|
32
|
+
# @return [String] description of the expected type
|
|
33
|
+
attr_reader :expected_type
|
|
34
|
+
|
|
35
|
+
# @param actual_input [Object] the offending input value
|
|
36
|
+
# @param actual_type [String] human-readable type of +actual_input+
|
|
37
|
+
# @param expected_type [String] description of the expected type
|
|
38
|
+
# @param message [String] error message
|
|
39
|
+
def initialize(actual_input, actual_type, expected_type, message)
|
|
40
|
+
super(message)
|
|
41
|
+
@actual_input = actual_input
|
|
42
|
+
@actual_type = actual_type
|
|
43
|
+
@expected_type = expected_type
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Error raised when the input provided to {CnpjCheckDigits} is not of the
|
|
48
|
+
# expected type (+String+ or +Array<String>+). The error message includes both
|
|
49
|
+
# the actual type of the input and the expected type.
|
|
50
|
+
class CnpjCheckDigitsInputTypeError < CnpjCheckDigitsTypeError
|
|
51
|
+
# @param actual_input [Object] the offending input value (the whole array when
|
|
52
|
+
# a non-string element is found)
|
|
53
|
+
# @param expected_type [String] description of the expected type (e.g.
|
|
54
|
+
# +"string or string[]"+)
|
|
55
|
+
def initialize(actual_input, expected_type)
|
|
56
|
+
actual_type = LacusUtils.describe_type(actual_input)
|
|
57
|
+
|
|
58
|
+
super(
|
|
59
|
+
actual_input,
|
|
60
|
+
actual_type,
|
|
61
|
+
expected_type,
|
|
62
|
+
"CNPJ input must be of type #{expected_type}. Got #{actual_type}."
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Base exception for all `cnpj-dv` rules-related errors.
|
|
68
|
+
#
|
|
69
|
+
# This class extends the native {StandardError} and serves as the base for all
|
|
70
|
+
# non-type-related errors in {CnpjCheckDigits}. It is suitable for validation
|
|
71
|
+
# errors, range errors, and other business logic exceptions that are not
|
|
72
|
+
# strictly type-related.
|
|
73
|
+
class CnpjCheckDigitsException < StandardError
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Error raised when the input (after optional processing) does not have the
|
|
77
|
+
# required length to calculate the check digits. A valid CNPJ input must
|
|
78
|
+
# contain between 12 and 14 alphanumeric characters. The error message
|
|
79
|
+
# distinguishes between the original input and the evaluated one (which strips
|
|
80
|
+
# punctuation characters).
|
|
81
|
+
class CnpjCheckDigitsInputLengthException < CnpjCheckDigitsException
|
|
82
|
+
# @return [String, Array<String>] the original input
|
|
83
|
+
attr_reader :actual_input
|
|
84
|
+
|
|
85
|
+
# @return [String] the normalized alphanumeric string
|
|
86
|
+
attr_reader :evaluated_input
|
|
87
|
+
|
|
88
|
+
# @return [Integer] minimum expected length (12)
|
|
89
|
+
attr_reader :min_expected_length
|
|
90
|
+
|
|
91
|
+
# @return [Integer] maximum expected length (14)
|
|
92
|
+
attr_reader :max_expected_length
|
|
93
|
+
|
|
94
|
+
# @param actual_input [String, Array<String>] the original input
|
|
95
|
+
# @param evaluated_input [String] the normalized alphanumeric string
|
|
96
|
+
# @param min_expected_length [Integer] minimum expected length
|
|
97
|
+
# @param max_expected_length [Integer] maximum expected length
|
|
98
|
+
def initialize(actual_input, evaluated_input, min_expected_length, max_expected_length)
|
|
99
|
+
super(build_message(actual_input, evaluated_input, min_expected_length, max_expected_length))
|
|
100
|
+
@actual_input = actual_input
|
|
101
|
+
@evaluated_input = evaluated_input
|
|
102
|
+
@min_expected_length = min_expected_length
|
|
103
|
+
@max_expected_length = max_expected_length
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def build_message(actual_input, evaluated_input, min_len, max_len)
|
|
109
|
+
fmt_actual = FormatActualInput.call(actual_input)
|
|
110
|
+
fmt_evaluated =
|
|
111
|
+
if actual_input == evaluated_input
|
|
112
|
+
evaluated_input.length.to_s
|
|
113
|
+
else
|
|
114
|
+
"#{evaluated_input.length} in \"#{evaluated_input}\""
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
"CNPJ input #{fmt_actual} does not contain #{min_len} to #{max_len} characters. " \
|
|
118
|
+
"Got #{fmt_evaluated}."
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Exception raised when the CNPJ input contains invalid character sequences,
|
|
123
|
+
# like all digits are repeated. This is a business logic exception and it is
|
|
124
|
+
# highly recommended that users of the library catch it and handle it
|
|
125
|
+
# appropriately.
|
|
126
|
+
class CnpjCheckDigitsInputInvalidException < CnpjCheckDigitsException
|
|
127
|
+
# @return [String, Array<String>] the original input
|
|
128
|
+
attr_reader :actual_input
|
|
129
|
+
|
|
130
|
+
# @return [String] human-readable reason why the input is invalid
|
|
131
|
+
attr_reader :reason
|
|
132
|
+
|
|
133
|
+
# @param actual_input [String, Array<String>] the original input
|
|
134
|
+
# @param reason [String] human-readable reason why the input is invalid
|
|
135
|
+
def initialize(actual_input, reason)
|
|
136
|
+
super("CNPJ input #{FormatActualInput.call(actual_input)} is invalid. #{reason}")
|
|
137
|
+
@actual_input = actual_input
|
|
138
|
+
@reason = reason
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
data/src/cnpj-dv/version.rb
CHANGED
data/src/cnpj-dv.rb
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'cnpj-dv/version'
|
|
4
|
+
require_relative 'cnpj-dv/exceptions'
|
|
5
|
+
require_relative 'cnpj-dv/cnpj_check_digits'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
# Check-digit calculation for Brazilian CNPJ (numeric and alphanumeric formats).
|
|
8
|
+
#
|
|
9
|
+
# The package distinguishes between **errors** and **exceptions**:
|
|
10
|
+
#
|
|
11
|
+
# - {CnpjDV::CnpjCheckDigitsTypeError} (extends the native {TypeError})
|
|
12
|
+
# signals incorrect API usage (the input is of the wrong *type*).
|
|
13
|
+
# - {CnpjDV::CnpjCheckDigitsException} (extends the native {StandardError})
|
|
14
|
+
# signals invalid or ineligible data (right type, bad value).
|
|
15
|
+
#
|
|
16
|
+
# Public API:
|
|
17
|
+
#
|
|
18
|
+
# - {CnpjDV::CnpjCheckDigits}
|
|
19
|
+
# - {CnpjDV::CNPJ_MIN_LENGTH}, {CnpjDV::CNPJ_MAX_LENGTH}
|
|
20
|
+
# - Exception hierarchy under {CnpjDV::CnpjCheckDigitsTypeError} /
|
|
21
|
+
# {CnpjDV::CnpjCheckDigitsException}
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# require 'cnpj-dv'
|
|
25
|
+
#
|
|
26
|
+
# check_digits = CnpjDV::CnpjCheckDigits.new('914157320007')
|
|
27
|
+
# check_digits.cnpj # => '91415732000793'
|
|
28
|
+
module CnpjDV
|
|
9
29
|
end
|
metadata
CHANGED
|
@@ -1,28 +1,55 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cnpj-dv
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Julio L. Muller
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
12
|
-
dependencies:
|
|
13
|
-
|
|
11
|
+
date: 2026-07-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: lacus-utils
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 1.1.0
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 2.0.0
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 1.1.0
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 2.0.0
|
|
33
|
+
description: Utility to calculate check digits on CNPJ (Brazilian Business Tax ID)
|
|
14
34
|
email:
|
|
35
|
+
- juliolmuller@outlook.com
|
|
15
36
|
executables: []
|
|
16
37
|
extensions: []
|
|
17
38
|
extra_rdoc_files: []
|
|
18
39
|
files:
|
|
40
|
+
- CHANGELOG.md
|
|
19
41
|
- LICENSE
|
|
42
|
+
- README.md
|
|
43
|
+
- README.pt.md
|
|
20
44
|
- src/cnpj-dv.rb
|
|
45
|
+
- src/cnpj-dv/cnpj_check_digits.rb
|
|
46
|
+
- src/cnpj-dv/exceptions.rb
|
|
21
47
|
- src/cnpj-dv/version.rb
|
|
22
48
|
homepage: https://github.com/LacusSolutions/br-utils-ruby
|
|
23
49
|
licenses:
|
|
24
50
|
- MIT
|
|
25
51
|
metadata:
|
|
52
|
+
source_code_uri: https://github.com/LacusSolutions/br-utils-ruby
|
|
26
53
|
rubygems_mfa_required: 'true'
|
|
27
54
|
post_install_message:
|
|
28
55
|
rdoc_options: []
|
|
@@ -32,7 +59,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
32
59
|
requirements:
|
|
33
60
|
- - ">="
|
|
34
61
|
- !ruby/object:Gem::Version
|
|
35
|
-
version: '3.
|
|
62
|
+
version: '3.1'
|
|
36
63
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
37
64
|
requirements:
|
|
38
65
|
- - ">="
|
|
@@ -42,5 +69,5 @@ requirements: []
|
|
|
42
69
|
rubygems_version: 3.4.19
|
|
43
70
|
signing_key:
|
|
44
71
|
specification_version: 4
|
|
45
|
-
summary: Check-digit calculation for CNPJ (Brazilian
|
|
72
|
+
summary: Check-digit calculation for CNPJ (Brazilian Business Tax ID)
|
|
46
73
|
test_files: []
|