diff --git a/app/routers/__init__.py b/app/routers/__init__.py new file mode 100644 index 0000000..567b54d --- /dev/null +++ b/app/routers/__init__.py @@ -0,0 +1 @@ +# app/routers/__init__.py diff --git a/app/routers/clientes.py b/app/routers/clientes.py new file mode 100644 index 0000000..486e148 --- /dev/null +++ b/app/routers/clientes.py @@ -0,0 +1,137 @@ +# app/routers/clientes.py +""" +Rotas de gestão de CLIENTE: listar/buscar, criar, editar, ver detalhe +(contatos + status de Clientes_Chaves), excluir. +""" +from typing import Optional +from urllib.parse import urlencode + +from fastapi import APIRouter, Depends, Form, Request +from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.templating import Jinja2Templates + +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 + +router = APIRouter(prefix="/clientes") +templates = Jinja2Templates(directory="app/templates") + + +def _redirect_com_mensagem(url: str, mensagem: str, tipo: str = "success") -> RedirectResponse: + separador = "&" if "?" in url else "?" + return RedirectResponse(url + separador + urlencode({"mensagem": mensagem, "mensagem_tipo": tipo}), status_code=303) + + +def _ler_form_cliente(form: dict) -> dict: + return {campo: (form.get(campo) or "").strip() for campo in db_clientes.CAMPOS_CLIENTE} + + +@router.get("", response_class=HTMLResponse) +def listar( + request: Request, + busca: Optional[str] = None, + mensagem: Optional[str] = None, + mensagem_tipo: Optional[str] = None, + usuario: str = Depends(exigir_login), +): + clientes = db_clientes.listar_clientes(busca=busca or None) + return templates.TemplateResponse( + "clientes_lista.html", + {"request": request, "clientes": clientes, "busca": busca, "mensagem": mensagem, "mensagem_tipo": mensagem_tipo}, + ) + + +@router.get("/novo", response_class=HTMLResponse) +def form_novo(request: Request, usuario: str = Depends(exigir_login)): + return templates.TemplateResponse( + "cliente_form.html", + {"request": request, "cliente": None, "dados": {c: "" for c in db_clientes.CAMPOS_CLIENTE}, "campos": db_clientes.CAMPOS_CLIENTE}, + ) + + +@router.post("/novo") +async def criar(request: Request, usuario: str = Depends(exigir_login)): + form = dict((await request.form())) + dados = _ler_form_cliente(form) + confirmar_duplicado = form.get("confirmar_codigo_duplicado") == "1" + + if not confirmar_duplicado and db_clientes.codigo_em_uso(dados["CODIGO"]): + return templates.TemplateResponse( + "cliente_form.html", + {"request": request, "cliente": None, "dados": dados, "campos": db_clientes.CAMPOS_CLIENTE, "aviso_codigo_duplicado": True}, + ) + + _, formato_valido, tipo = normalizar_documento(dados["CNPJ"]) + dv_invalido = False + if formato_valido: + dv_invalido = not (validar_dv_cpf(dados["CNPJ"]) if tipo == "CPF" else validar_dv_cnpj(dados["CNPJ"])) + if dv_invalido and form.get("confirmar_dv_invalido") != "1": + return templates.TemplateResponse( + "cliente_form.html", + {"request": request, "cliente": None, "dados": dados, "campos": db_clientes.CAMPOS_CLIENTE, "aviso_dv_invalido": True}, + ) + + novo_id = db_clientes.criar_cliente(dados) + return _redirect_com_mensagem(f"/clientes/{novo_id}", "Cliente criado.") + + +@router.get("/{cliente_id}", response_class=HTMLResponse) +def detalhe( + request: Request, + cliente_id: int, + mensagem: Optional[str] = None, + mensagem_tipo: Optional[str] = None, + usuario: str = Depends(exigir_login), +): + cliente = db_clientes.obter_cliente(cliente_id) + if cliente is None: + return _redirect_com_mensagem("/clientes", "Cliente não encontrado.", "danger") + + contatos = db_contatos.listar_contatos_por_cliente(cliente_id) + status = db_chaves.status_chaves(cliente["CODIGO"]) + return templates.TemplateResponse( + "cliente_detalhe.html", + { + "request": request, "cliente": cliente, "contatos": contatos, "status_chaves": status, + "mensagem": mensagem, "mensagem_tipo": mensagem_tipo, + }, + ) + + +@router.get("/{cliente_id}/editar", response_class=HTMLResponse) +def form_editar(request: Request, cliente_id: int, usuario: str = Depends(exigir_login)): + cliente = db_clientes.obter_cliente(cliente_id) + if cliente is None: + return _redirect_com_mensagem("/clientes", "Cliente não encontrado.", "danger") + return templates.TemplateResponse( + "cliente_form.html", + {"request": request, "cliente": cliente, "dados": cliente, "campos": db_clientes.CAMPOS_CLIENTE}, + ) + + +@router.post("/{cliente_id}/editar") +async def editar(request: Request, cliente_id: int, usuario: str = Depends(exigir_login)): + form = dict((await request.form())) + dados = _ler_form_cliente(form) + confirmar_duplicado = form.get("confirmar_codigo_duplicado") == "1" + + if not confirmar_duplicado and db_clientes.codigo_em_uso(dados["CODIGO"], ignorar_id=cliente_id): + return templates.TemplateResponse( + "cliente_form.html", + {"request": request, "cliente": {"ID": cliente_id}, "dados": dados, "campos": db_clientes.CAMPOS_CLIENTE, "aviso_codigo_duplicado": True}, + ) + + db_clientes.atualizar_cliente(cliente_id, dados) + return _redirect_com_mensagem(f"/clientes/{cliente_id}", "Cliente atualizado.") + + +@router.post("/{cliente_id}/excluir") +def excluir(cliente_id: int, usuario: str = Depends(exigir_login)): + try: + db_clientes.excluir_cliente(cliente_id) + except db_clientes.ClienteComDependenciasError as exc: + return _redirect_com_mensagem(f"/clientes/{cliente_id}", str(exc), "danger") + return _redirect_com_mensagem("/clientes", "Cliente excluído.") diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..aee3555 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,35 @@ + + + +
+ +{{ cliente.RAZAO }} — {{ cliente.CNPJ }}
++ Editar +
+ + +| Nome | Telefone | |
|---|---|---|
| {{ ct.NOME }} | {{ ct.TELEFONE }} | {{ ct.EMAIL or "" }} |
| {{ mes }} | {% endfor %}
|---|
| {{ "✓" if valor else "—" }} | {% endfor %}
Nenhuma linha em Clientes_Chaves para o código {{ cliente.CODIGO }}.
+ {% endif %} +{% endblock %} diff --git a/app/templates/cliente_form.html b/app/templates/cliente_form.html new file mode 100644 index 0000000..015cb3a --- /dev/null +++ b/app/templates/cliente_form.html @@ -0,0 +1,26 @@ + +{% extends "base.html" %} +{% block titulo %}{{ "Editar" if cliente else "Novo" }} cliente{% endblock %} +{% block conteudo %} +| Código | Razão | Fantasia | CNPJ | |
|---|---|---|---|---|
| {{ c.CODIGO }} | +{{ c.RAZAO }} | +{{ c.FANTASIA }} | +{{ c.CNPJ }} | +Abrir | +