Ao sair do campo CNPJ no formulario de cliente, busca automaticamente
na BrasilAPI (publica, gratuita, sem chave - dados abertos da Receita)
e preenche Razao/Fantasia/Endereco/Cidade/Bairro/UF/CEP/Telefone/
E-mail, so nos campos que estiverem vazios (nunca sobrescreve o que
ja foi digitado). So funciona para CNPJ - CPF e dado pessoal, sem
consulta publica, e a rota recusa (404) se detectar CPF.
- app/cnpj_lookup.py: cliente HTTP pra BrasilAPI, degrada em silencio
(retorna None) se nao encontrar/rede cair/formato nao reconhecido
(ex.: CNPJ alfanumerico novo, que a BrasilAPI pode nao cobrir ainda).
- Nova rota GET /clientes/consulta-cnpj/{documento}, chamada via
fetch() no formulario (JS vanilla, sem framework).
- Testado ponta a ponta contra a API real.
106/106 testes passando (10 novos).
58 lines
2.2 KiB
Python
58 lines
2.2 KiB
Python
"""
|
|
Consulta de CNPJ na BrasilAPI (https://brasilapi.com.br/api/cnpj/v1) para
|
|
preencher automaticamente o formulário de cliente. Fonte pública/gratuita,
|
|
sem autenticação, mantida pela comunidade a partir de dados abertos da
|
|
Receita Federal — não é a API oficial da Receita (que não tem consulta
|
|
pública direta por CNPJ).
|
|
|
|
Só faz sentido para CNPJ (empresa) — CPF é dado pessoal e não tem consulta
|
|
pública, então o chamador nunca deve passar um CPF aqui (ver
|
|
app/routers/clientes.py, que checa o tipo via normalizar_documento antes).
|
|
|
|
Degrada de forma segura: qualquer falha (CNPJ não encontrado, rede fora do
|
|
ar, formato que a BrasilAPI ainda não reconhece — o novo CNPJ alfanumérico,
|
|
vigente desde 31/07/2026, pode não estar coberto por todo provedor ainda)
|
|
retorna None. O formulário simplesmente não preenche nada sozinho nesse caso.
|
|
"""
|
|
import httpx
|
|
|
|
_BASE_URL = "https://brasilapi.com.br/api/cnpj/v1"
|
|
|
|
|
|
def consultar_cnpj(cnpj_normalizado: str) -> dict | None:
|
|
"""
|
|
cnpj_normalizado: 14 caracteres (12 alfanuméricos + 2 dígitos), já sem
|
|
máscara — ver app/normalize.py. Retorna um dict só com os campos que
|
|
existem em CLIENTE, prontos para preencher o formulário, ou None se não
|
|
encontrou/a consulta falhou.
|
|
"""
|
|
if len(cnpj_normalizado) != 14:
|
|
return None
|
|
|
|
try:
|
|
resposta = httpx.get(f"{_BASE_URL}/{cnpj_normalizado}", timeout=8.0)
|
|
if resposta.status_code != 200:
|
|
return None
|
|
dados = resposta.json()
|
|
except (httpx.HTTPError, ValueError):
|
|
return None
|
|
|
|
if not isinstance(dados, dict):
|
|
return None
|
|
|
|
logradouro = dados.get("logradouro") or ""
|
|
numero = dados.get("numero") or ""
|
|
endereco = f"{logradouro}, {numero}" if numero else logradouro
|
|
|
|
return {
|
|
"RAZAO": dados.get("razao_social") or "",
|
|
"FANTASIA": dados.get("nome_fantasia") or dados.get("razao_social") or "",
|
|
"ENDERECO": endereco[:45], # CLIENTE.ENDERECO é varchar(45)
|
|
"CIDADE": dados.get("municipio") or "",
|
|
"BAIRRO": dados.get("bairro") or "",
|
|
"UF": dados.get("uf") or "",
|
|
"CEP": (dados.get("cep") or "")[:10],
|
|
"TELEFONE": dados.get("ddd_telefone_1") or "",
|
|
"EMAIL": dados.get("email") or "",
|
|
}
|