Mesmo padrão já usado pro CODIGO (db_clientes.codigo_em_uso): nova
db_clientes.cnpj_em_uso(documento, ignorar_id=None) checada em
criar()/editar() antes de gravar. A comparação ignora pontuação dos dois
lados (não só normaliza o valor recebido) porque CLIENTE.CNPJ tem
formatação inconsistente em registros legados (alguns com máscara,
outros sem) — sem isso, um cadastro novo com o mesmo documento de um
legado ainda mascarado passaria batido.
app/templates/cliente_form.html: nova mensagem de erro
'erro_cnpj_duplicado', mesmo estilo visual do erro de código duplicado.
6 testes novos (unitário do db + integração do router pros dois
fluxos), suíte completa (196 testes) passando.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- app/routers/contatos.py: CLIENTE_CONTATO.TELEFONE passa a ser gravado
sempre com DDI 55 (normalizar_telefone_com_ddi, mesma normalização já
usada em CLIENTE.TELEFONE/CELULAR) — confirmado no node '10 - Validar
Telefones' do workflow n8n de produção (ER6Piy9yu93uOVuY) que o
telefone é usado exatamente como está gravado, sem nenhuma
normalização própria. Um contato cadastrado pelo painel sem o
funcionário digitar '55' na frente nunca recebia a chave automática
por WhatsApp — falha silenciosa, sem nenhum erro visível.
- app/db/clientes.py, app/db/contatos.py, app/db/funcionarios.py:
excluir_cliente/atualizar_contato/excluir_contato/redefinir_senha
passam a retornar bool (cur.rowcount > 0) em vez de None — um
ID inexistente não gera erro nenhum do MySQL (WHERE só não bate com
nada), então sem checar rowcount o router não tinha como saber que
não alterou/excluiu nada.
- app/routers/clientes.py, app/routers/contatos.py,
app/routers/funcionarios.py: editar/excluir/redefinir-senha/
alternar-ativo passam a checar essa existência e mostrar 'não
encontrado' em vez de registrar 'sucesso' no log e redirecionar pra
uma tela que também vai dizer que não encontrou nada.
- app/templates/clientes_lista.html: busca não estava com urlencode nos
links de paginação/ordenação — um termo de busca com '&' (comum em
razão social, ex. 'Fulano & Cia') cortava o resto da query string do
link, perdendo página/ordenação.
Suíte completa (192 testes) passando, com 13 testes novos cobrindo os
casos de 'não encontrado' e o DDI do telefone de contato.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codigo, Razao, Fantasia e CNPJ agora sao links no cabecalho da tabela:
clicar ordena crescente; clicar de novo na mesma coluna alterna pra
decrescente (e vice-versa). Trocar de coluna sempre comeca em
crescente. Seta (▲/▼) indica a coluna/direcao ativa.
db_clientes.listar_clientes() ganha o parametro direcao (whitelist
asc/desc, igual ja acontecia com ordenar_por). Links de
Anterior/Proxima da paginacao preservam a direcao escolhida.
97/97 testes passando.
Antes, digitar qualquer coisa na busca comparava com CODIGO, CNPJ,
RAZAO e FANTASIA ao mesmo tempo (OR), entao escolher 'Ordenar por:
Codigo' e buscar '242' trazia tambem clientes cujo CNPJ ou Razao
continham '242' em outro lugar. Agora a busca usa so a coluna
selecionada no dropdown (mesma whitelist ja usada pra ordenacao).
listar_clientes()/contar_clientes() e o template ganharam o ajuste;
o dropdown mudou de rotulo pra 'Buscar/ordenar por' pra deixar claro
que controla os dois.
93/93 testes passando.
- Tabela FUNCIONARIO_PAINEL (nova, ja criada no banco de dev) +
app/db/funcionarios.py: nome/login/senha (hash scrypt, stdlib, sem
dependencia nova) por funcionario, nunca excluido de verdade (so
ativo/inativo).
- app/senha.py: gerar_hash/verificar_senha com hashlib.scrypt + salt
aleatorio por senha.
- app/auth.py: login passa a checar duas fontes, nessa ordem -
ADMIN_USER/ADMIN_PASSWORD (bootstrap, continua exigindo as duas
preenchidas) e depois FUNCIONARIO_PAINEL (so contas ATIVO=1). O
nome retornado (usado no log) passa a ser o nome de verdade do
funcionario, nao mais sempre 'admin'.
- Tela /funcionarios: listar, criar (com confirmacao de senha e
checagem de login duplicado), editar nome/login, redefinir senha
(form separado) e ativar/desativar. Tudo logado em LOG_PAINEL.
- Testado ponta a ponta contra o app real: funcionario criado via
tela consegue logar de verdade, e a acao aparece no /log com o
nome de quem fez.
93/93 testes passando (23 novos).
- Tabela LOG_PAINEL (nova, ja criada no banco de dev) + app/db/log.py:
toda criacao/edicao/exclusao de cliente e contato fica registrada
(usuario, acao, sucesso/erro, detalhe). Tela /log mostra esse
historico.
- app/n8n_client.py: leitura opcional (N8N_API_URL/N8N_API_KEY) da
Data Table pagamentos_processados do n8n, mostrando os envios
automaticos de chave na mesma tela /log. Degrada de forma segura
(lista vazia) se nao configurado ou se a chamada falhar.
- Lista de clientes ganha ordenacao configuravel (codigo/razao/
fantasia/CNPJ) via whitelist, alem da busca que ja existia.
- CODIGO duplicado agora BLOQUEIA de verdade na criacao/edicao (sem
bypass) — antes so avisava com opcao de confirmar mesmo assim.
- Formulario de novo cliente sugere o proximo CODIGO sequencial
(editavel), ignorando a faixa reservada para dados de teste
(>= 900000) para nao ser contaminado por sobra de teste no banco.
- IMPLANTACAO/MENSALIDADE/JUROS_MORA/JUROS_MULTA/DATA_CONTRATO/
VENCIMENTO_CONTRATO deixam de ser obrigatorios — em branco, gravam
default (0,00 / hoje) em vez de bloquear o cadastro. Só CODIGO/CNPJ/
RAZAO/FANTASIA continuam obrigatorios.
- Limpeza: removidas 10 linhas de teste vazadas (CODIGO 999902) que
ficaram no banco de dev por sessoes de teste interrompidas.
69/69 testes passando (16 novos).
CSRF: novo app/csrf.py + middleware em app/main.py garantem um cookie
csrf_token por navegador; todo formulario que muda estado (criar/editar/
excluir cliente, adicionar/editar/excluir contato) embute esse valor
como campo oculto, e Depends(exigir_csrf) confere no POST que o valor
enviado bate com o cookie antes de deixar a rota executar. Verificado
ponta a ponta com o app real rodando (curl): token errado -> 403,
token correto -> 303.
Paginacao: db_clientes.listar_clientes ganha offset, nova
contar_clientes(); GET /clientes aceita ?pagina=N (100 clientes por
pagina), template mostra Anterior/Proxima e o total.
53/53 testes passando (8 novos).