|
1 | 1 | # cnpj.py - functions for handling CNPJ numbers |
2 | 2 | # coding: utf-8 |
3 | 3 | # |
4 | | -# Copyright (C) 2015 Arthur de Jong |
| 4 | +# Copyright (C) 2015-2026 Arthur de Jong |
5 | 5 | # |
6 | 6 | # This library is free software; you can redistribute it and/or |
7 | 7 | # modify it under the terms of the GNU Lesser General Public |
|
20 | 20 |
|
21 | 21 | """CNPJ (Cadastro Nacional da Pessoa Jurídica, Brazilian company identifier). |
22 | 22 |
|
23 | | -Numbers from the national register of legal entities have 14 digits. The |
24 | | -first 8 digits identify the company, the following 4 digits identify a |
| 23 | +Numbers from the national register of legal entities have 14 alphanumeric digits. |
| 24 | +The first 8 digits identify the company, the following 4 digits identify a |
25 | 25 | business unit and the last 2 digits are check digits. |
26 | 26 |
|
27 | 27 | >>> validate('16.727.230/0001-97') |
28 | 28 | '16727230000197' |
| 29 | +>>> validate('12. ABC.345 /01DE–35') # new format from July 2026 onwards |
| 30 | +'12ABC34501DE35' |
29 | 31 | >>> validate('16.727.230.0001-98') |
30 | 32 | Traceback (most recent call last): |
31 | 33 | ... |
|
40 | 42 |
|
41 | 43 | from __future__ import annotations |
42 | 44 |
|
| 45 | +import re |
| 46 | + |
43 | 47 | from stdnum.exceptions import * |
44 | | -from stdnum.util import clean, isdigits |
| 48 | +from stdnum.util import clean |
| 49 | + |
| 50 | + |
| 51 | +# Minimal regex of valid characters |
| 52 | +_cnpj_re = re.compile(r'^[\dA-Z]+$') |
45 | 53 |
|
46 | 54 |
|
47 | 55 | def compact(number: str) -> str: |
48 | 56 | """Convert the number to the minimal representation. This strips the |
49 | 57 | number of any valid separators and removes surrounding whitespace.""" |
50 | | - return clean(number, ' -./').strip() |
| 58 | + return clean(number, ' -./').strip().upper() |
51 | 59 |
|
52 | 60 |
|
53 | 61 | def calc_check_digits(number: str) -> str: |
54 | 62 | """Calculate the check digits for the number.""" |
55 | | - d1 = (11 - sum(((3 - i) % 8 + 2) * int(n) |
56 | | - for i, n in enumerate(number[:12]))) % 11 % 10 |
57 | | - d2 = (11 - sum(((4 - i) % 8 + 2) * int(n) |
58 | | - for i, n in enumerate(number[:12])) - |
59 | | - 2 * d1) % 11 % 10 |
60 | | - return '%d%d' % (d1, d2) |
| 63 | + number = compact(number) |
| 64 | + values = [ord(n) - 48 for n in number[:12]] |
| 65 | + weights = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] |
| 66 | + d1 = (11 - sum(w * v for w, v in zip(weights, values))) % 11 % 10 |
| 67 | + values.append(d1) |
| 68 | + weights = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] |
| 69 | + d2 = (11 - sum(w * v for w, v in zip(weights, values))) % 11 % 10 |
| 70 | + return f'{d1}{d2}' |
61 | 71 |
|
62 | 72 |
|
63 | 73 | def validate(number: str) -> str: |
64 | 74 | """Check if the number is a valid CNPJ. This checks the length and |
65 | 75 | whether the check digits are correct.""" |
66 | 76 | number = compact(number) |
67 | | - if not isdigits(number) or int(number) <= 0: |
| 77 | + if not _cnpj_re.match(number) or number.startswith('000000000000'): |
68 | 78 | raise InvalidFormat() |
69 | 79 | if len(number) != 14: |
70 | 80 | raise InvalidLength() |
|
0 commit comments