fix: normalizar CNPJ/telefone de CLIENTE antes de gravar (sem mascara)

CLIENTE.CNPJ/TELEFONE/CELULAR agora sao gravados sempre sem pontuacao
(so digitos, e letras no caso do CNPJ alfanumerico), igual ao que
CLIENTE_CONTATO.TELEFONE ja fazia. Resolve a inconsistencia de formato
apontada na revisao final do plano (achado 7): duas tabelas do mesmo
app guardando telefone/documento em formatos diferentes.

So afeta escritas novas feitas pelo painel (criar/editar cliente) -
registros legados existentes so ficam sem mascara quando forem
reeditados e salvos aqui; nao faz backfill em massa dos dados atuais.
This commit is contained in:
Gustavo
2026-08-13 10:54:37 -03:00
parent 9b04877d59
commit 96cbd8a294
2 changed files with 82 additions and 1 deletions
+17 -1
View File
@@ -15,7 +15,7 @@ from ..auth import exigir_login
from ..db import chaves as db_chaves
from ..db import clientes as db_clientes
from ..db import contatos as db_contatos
from ..normalize import normalizar_documento, validar_dv_cpf, validar_dv_cnpj
from ..normalize import normalizar_documento, normalizar_telefone, validar_dv_cpf, validar_dv_cnpj
router = APIRouter(prefix="/clientes")
templates = Jinja2Templates(directory="app/templates")
@@ -30,6 +30,20 @@ def _ler_form_cliente(form: dict) -> dict:
return {campo: (form.get(campo) or "").strip() for campo in db_clientes.CAMPOS_CLIENTE}
def _normalizar_documento_e_telefones(dados: dict) -> None:
"""
Grava CNPJ/CPF e telefones sempre sem máscara (só dígitos/letras),
igual ao que CLIENTE_CONTATO.TELEFONE já faz — evita que CLIENTE fique
com dois formatos diferentes convivendo (achado da revisão final:
docs/superpowers/plans/2026-08-13-painel-clientes-web.md). Só afeta
escritas novas feitas por este painel; registros legados só passam a
ficar sem máscara quando forem reeditados e salvos aqui.
"""
dados["CNPJ"] = normalizar_documento(dados["CNPJ"])[0]
dados["TELEFONE"] = normalizar_telefone(dados["TELEFONE"])
dados["CELULAR"] = normalizar_telefone(dados["CELULAR"])
@router.get("", response_class=HTMLResponse)
def listar(
request: Request,
@@ -59,6 +73,7 @@ def form_novo(request: Request, usuario: str = Depends(exigir_login)):
async def criar(request: Request, usuario: str = Depends(exigir_login)):
form = dict((await request.form()))
dados = _ler_form_cliente(form)
_normalizar_documento_e_telefones(dados)
confirmar_duplicado = form.get("confirmar_codigo_duplicado") == "1"
confirmar_dv = form.get("confirmar_dv_invalido") == "1"
@@ -141,6 +156,7 @@ def form_editar(request: Request, cliente_id: int, usuario: str = Depends(exigir
async def editar(request: Request, cliente_id: int, usuario: str = Depends(exigir_login)):
form = dict((await request.form()))
dados = _ler_form_cliente(form)
_normalizar_documento_e_telefones(dados)
confirmar_duplicado = form.get("confirmar_codigo_duplicado") == "1"
if not confirmar_duplicado and db_clientes.codigo_em_uso(dados["CODIGO"], ignorar_id=cliente_id):
+65
View File
@@ -83,6 +83,71 @@ def test_criar_cliente_codigo_duplicado_exige_confirmacao(monkeypatch):
assert criado.get("ok") is True
def test_criar_cliente_normaliza_cnpj_e_telefones(monkeypatch):
monkeypatch.setattr(router_clientes.db_clientes, "codigo_em_uso", lambda codigo, ignorar_id=None: False)
capturado = {}
monkeypatch.setattr(
router_clientes.db_clientes, "criar_cliente", lambda dados: capturado.setdefault("dados", dados) and 42
)
dados_form = {campo: "x" for campo in router_clientes.db_clientes.CAMPOS_CLIENTE}
dados_form.update(
{
"CODIGO": "000001",
"CNPJ": "11.222.333/0001-81",
"TELEFONE": "(75) 3333-4444",
"CELULAR": "(75) 99909-1780",
"IMPLANTACAO": "0",
"MENSALIDADE": "100",
"JUROS_MORA": "0",
"JUROS_MULTA": "0",
"DATA_CONTRATO": "2026-01-01",
"VENCIMENTO_CONTRATO": "2027-01-01",
"DIA_VENCIMENTO": "10",
"confirmar_dv_invalido": "1",
}
)
resposta = client.post("/clientes/novo", data=dados_form)
assert resposta.status_code == 303
assert capturado["dados"]["CNPJ"] == "11222333000181"
assert capturado["dados"]["TELEFONE"] == "7533334444"
assert capturado["dados"]["CELULAR"] == "75999091780"
def test_editar_cliente_normaliza_cnpj_e_telefones(monkeypatch):
monkeypatch.setattr(router_clientes.db_clientes, "codigo_em_uso", lambda codigo, ignorar_id=None: False)
capturado = {}
monkeypatch.setattr(
router_clientes.db_clientes,
"atualizar_cliente",
lambda cliente_id, dados: capturado.setdefault("dados", dados),
)
dados_form = {campo: "x" for campo in router_clientes.db_clientes.CAMPOS_CLIENTE}
dados_form.update(
{
"CODIGO": "000001",
"CNPJ": "111.444.777-35",
"TELEFONE": "(75) 3333-4444",
"CELULAR": "(75) 99909-1780",
"IMPLANTACAO": "0",
"MENSALIDADE": "100",
"JUROS_MORA": "0",
"JUROS_MULTA": "0",
"DATA_CONTRATO": "2026-01-01",
"VENCIMENTO_CONTRATO": "2027-01-01",
"DIA_VENCIMENTO": "10",
}
)
resposta = client.post("/clientes/1/editar", data=dados_form)
assert resposta.status_code == 303
assert capturado["dados"]["CNPJ"] == "11144477735"
assert capturado["dados"]["TELEFONE"] == "7533334444"
assert capturado["dados"]["CELULAR"] == "75999091780"
def test_excluir_cliente_com_dependencia_mostra_erro(monkeypatch):
monkeypatch.setattr(
router_clientes.db_clientes,