Import ClientFlow production v4928.1.5.132.4
This commit is contained in:
@@ -4,11 +4,19 @@ Moved from app.admin_dashboard in v4.7.2. The handlers still reuse
|
||||
legacy helpers to keep this refactor behavior-preserving.
|
||||
"""
|
||||
from fastapi import APIRouter
|
||||
import re
|
||||
from fastapi.responses import PlainTextResponse
|
||||
import app.admin_dashboard as legacy
|
||||
from app.admin_dashboard import * # noqa: F401,F403
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
_EMAIL_RE = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
|
||||
|
||||
def _valid_optional_email(value: str) -> bool:
|
||||
value = str(value or "").strip()
|
||||
return not value or bool(_EMAIL_RE.match(value))
|
||||
|
||||
|
||||
@router.get("/customers", response_class=HTMLResponse)
|
||||
@router.get("/clientes", response_class=HTMLResponse)
|
||||
@@ -70,13 +78,39 @@ async def create_customer_action(request: Request):
|
||||
"email": str(form.get("email") or "").strip(),
|
||||
"phone": str(form.get("phone") or "").strip(),
|
||||
})
|
||||
except ValueError as exc:
|
||||
return PlainTextResponse(f"Dados inválidos ao criar cliente: {exc}", status_code=422)
|
||||
except Exception as exc:
|
||||
return PlainTextResponse(f"Erro ao criar cliente: {exc}", status_code=500)
|
||||
return RedirectResponse(f"/customers/{customer.get('id')}", status_code=303)
|
||||
|
||||
|
||||
@router.get("/customers/new", response_class=HTMLResponse)
|
||||
async def customer_new_page(name: Optional[str] = None, tax_id: Optional[str] = None, email: Optional[str] = None):
|
||||
body = f"""
|
||||
<a class="cf-row-link d-inline-flex mb-3" href="/customers">← Voltar a clientes</a>
|
||||
<section class="card cf-card">
|
||||
<div class="card-body p-4">
|
||||
<h1 class="h4 fw-bold mb-1">Novo cliente fiscal</h1>
|
||||
<div class="text-secondary mb-4">Cria uma ficha fiscal para associar a oportunidades, documentos Jasmin e processos de envio.</div>
|
||||
<form method="post" action="/customers/create" class="row g-3">
|
||||
<div class="col-12"><label class="form-label small fw-bold text-secondary">Nome fiscal</label><input class="form-control" name="name" value="{esc(name or '')}" placeholder="Nome fiscal" required></div>
|
||||
<div class="col-md-6"><label class="form-label small fw-bold text-secondary">NIF</label><input class="form-control" name="tax_id" value="{esc(tax_id or '')}" placeholder="NIF"></div>
|
||||
<div class="col-md-6"><label class="form-label small fw-bold text-secondary">Email</label><input class="form-control" name="email" value="{esc(email or '')}" placeholder="email@empresa.pt"></div>
|
||||
<div class="col-md-6"><label class="form-label small fw-bold text-secondary">Telefone</label><input class="form-control" name="phone" placeholder="Telefone"></div>
|
||||
<div class="col-md-6"><label class="form-label small fw-bold text-secondary">País</label><input class="form-control" name="country" value="PT"></div>
|
||||
<div class="col-12 d-flex gap-2"><button class="btn btn-primary" type="submit">Criar ficha</button><a class="btn btn-outline-secondary" href="/customers">Cancelar</a></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
"""
|
||||
return layout("Novo cliente", "Criar ficha fiscal", body, "customers")
|
||||
|
||||
|
||||
@router.get("/customers/{customer_id}", response_class=HTMLResponse)
|
||||
async def customer_detail_page(customer_id: str):
|
||||
if not is_uuid_text(customer_id):
|
||||
return PlainTextResponse("Identificador de cliente inválido.", status_code=422)
|
||||
try:
|
||||
from app.commercial_service import get_customer, list_commercial_documents, list_opportunities_for_customer, list_shipments
|
||||
customer = get_customer(customer_id)
|
||||
@@ -131,7 +165,7 @@ async def customer_detail_page(customer_id: str):
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-start gap-2 mb-3"><div><h2 class="cf-section-title mb-1">Nova oportunidade</h2><div class="small text-secondary">Cria um processo comercial manual já ligado a este cliente fiscal.</div></div></div>
|
||||
<form method="post" action="/customers/{esc(customer_id)}/opportunities/create" class="row g-2 align-items-end">
|
||||
<div class="col-md-3"><label class="form-label small fw-bold text-secondary">Origem</label><select class="form-select" name="origin"><option value="phone">Telefone</option><option value="whatsapp">WhatsApp</option><option value="email">Email</option><option value="presential">Presencial</option><option value="manual">Manual</option></select></div>
|
||||
<div class="col-md-3"><label class="form-label small fw-bold text-secondary">Pedido</label><select class="form-select" name="request_type"><option value="quote">Orçamento</option><option value="info">Informação</option><option value="proforma">Pró-forma</option><option value="invoice">Fatura</option><option value="order">Encomenda</option><option value="support">Assistência</option></select></div>
|
||||
<div class="col-md-3"><label class="form-label small fw-bold text-secondary">Pedido</label><select class="form-select" name="request_type"><option value="quote">Orçamento</option><option value="info">Informação</option><option value="invoice">Fatura</option><option value="order">Encomenda</option><option value="support">Assistência</option></select></div>
|
||||
<div class="col-md-6"><label class="form-label small fw-bold text-secondary">Produto/interesse</label><input class="form-control" name="product_interest" placeholder="Ex.: carregador monofásico, cabo, instalação..."></div>
|
||||
<div class="col-md-4"><label class="form-label small fw-bold text-secondary">Contacto</label><input class="form-control" name="contact_name" placeholder="Nome do contacto"></div>
|
||||
<div class="col-md-4"><label class="form-label small fw-bold text-secondary">Email contacto</label><input class="form-control" name="contact_email" placeholder="email@empresa.pt"></div>
|
||||
@@ -152,7 +186,17 @@ async def customer_detail_page(customer_id: str):
|
||||
|
||||
@router.post("/customers/{customer_id}/opportunities/create")
|
||||
async def create_customer_opportunity_action(customer_id: str, request: Request):
|
||||
if not is_uuid_text(customer_id):
|
||||
return PlainTextResponse("Identificador de cliente inválido.", status_code=422)
|
||||
form = await request.form()
|
||||
contact_email = str(form.get("contact_email") or "").strip()
|
||||
if not _valid_optional_email(contact_email):
|
||||
return PlainTextResponse("Email de contacto inválido.", status_code=422)
|
||||
meaningful = any(str(form.get(k) or "").strip() for k in ("contact_name", "contact_phone", "product_interest", "notes")) or bool(contact_email)
|
||||
if not meaningful:
|
||||
return PlainTextResponse("Dados insuficientes para criar oportunidade.", status_code=422)
|
||||
if contact_email and not any(str(form.get(k) or "").strip() for k in ("contact_name", "contact_phone", "product_interest", "notes")):
|
||||
return PlainTextResponse("Dados insuficientes para criar oportunidade: indique produto, notas ou outro contacto válido.", status_code=422)
|
||||
try:
|
||||
from app.opportunity_service import create_manual_opportunity_from_customer
|
||||
result = create_manual_opportunity_from_customer(
|
||||
@@ -167,6 +211,8 @@ async def create_customer_opportunity_action(customer_id: str, request: Request)
|
||||
create_task=bool(form.get("create_task")),
|
||||
created_by="operator",
|
||||
)
|
||||
except ValueError as exc:
|
||||
return PlainTextResponse(f"Dados inválidos ao criar oportunidade: {exc}", status_code=422)
|
||||
except Exception as exc:
|
||||
return PlainTextResponse(f"Erro ao criar oportunidade: {exc}", status_code=500)
|
||||
return RedirectResponse(result.get("next_url") or f"/customers/{customer_id}", status_code=303)
|
||||
@@ -174,6 +220,8 @@ async def create_customer_opportunity_action(customer_id: str, request: Request)
|
||||
|
||||
@router.post("/customers/{customer_id}/update")
|
||||
async def update_customer_action(customer_id: str, request: Request):
|
||||
if not is_uuid_text(customer_id):
|
||||
return PlainTextResponse("Identificador de cliente inválido.", status_code=422)
|
||||
form = await request.form()
|
||||
try:
|
||||
from app.commercial_service import update_customer
|
||||
@@ -189,6 +237,8 @@ async def update_customer_action(customer_id: str, request: Request):
|
||||
"jasmin_customer_party_key": str(form.get("jasmin_customer_party_key") or "").strip(),
|
||||
"jasmin_customer_id": str(form.get("jasmin_customer_id") or "").strip(),
|
||||
})
|
||||
except ValueError as exc:
|
||||
return PlainTextResponse(f"Dados inválidos ao guardar cliente: {exc}", status_code=422)
|
||||
except Exception as exc:
|
||||
# Keep database details out of the operator UI. Duplicate NIFs are a
|
||||
# business conflict, not a technical 500.
|
||||
|
||||
Reference in New Issue
Block a user