# ClientFlow v3 — Clientes, documentos e envios Esta versão separa responsabilidades para evitar que a página da oportunidade fique demasiado pesada. ## Princípio de UI - **Clientes**: dados fiscais, NIF, morada, contactos e ligação Jasmin. - **Oportunidades**: estado da potencial compra, produtos, documentos associados e envios. - **Documentos**: orçamentos e faturas em `commercial_documents`. - **Envios**: registos Packlink em `shipments`. ## Páginas principais ### `/customers` Lista clientes locais normalizados, com pesquisa por nome, NIF, email, telefone ou `jasmin_customer_party_key`. Permite criar uma ficha mínima de cliente com nome e NIF. ### `/customers/{id}` Ficha completa de cliente: - nome fiscal; - NIF; - email; - telefone; - morada fiscal; - código postal; - cidade; - país; - Jasmin `customerPartyKey`; - Jasmin ID; - oportunidades associadas; - documentos Jasmin; - envios Packlink. ### `/opportunities/{id}` A oportunidade mantém apenas um resumo compacto do cliente e um seletor para associar uma ficha local. Botões operacionais: - **Criar orçamento**: cria sempre um novo orçamento Jasmin. - **Converter em fatura**: converte o orçamento ativo/mais recente em fatura. ## Modelo de dados Novas tabelas/colunas usadas nesta versão: - `customers` - `commercial_documents` - `commercial_document_lines` - `shipments` - `opportunities.local_customer_id` O campo antigo `opportunities.customer_id` é mantido para compatibilidade com dados legados/Chatwoot. A ligação nova e normalizada usa `opportunities.local_customer_id`. ## Fluxo Jasmin validado 1. Cliente associado à oportunidade. 2. `find_or_create_customer_for_opportunity()` usa primeiro o cliente local. 3. NIF é enviado para Jasmin sem prefixo `PT`. 4. Se o cliente não existir no Jasmin, cria cliente com `partyKey = CF{NIF}`. 5. Cria orçamento `ORC / ORC2026`. 6. Para faturar, chama `POST /billing/invoices/fromQuotation/{quotationId}` com body `{}`. ## Regras importantes - Não enviar `electronicMail` nem `telephone` vazios para Jasmin. - OData Jasmin deve usar `$top <= 100`. - Packlink usa API key no header `Authorization`, sem `Bearer`. - Para cotação Packlink em Portugal, normalizar CP para 4 dígitos. ## Estratégia de refactor Esta v3 ainda não reescreve o `admin_dashboard.py` totalmente. A prioridade foi criar a camada de domínio e a navegação operacional correta. O próximo passo recomendado é extrair progressivamente: - `app/admin_ui/pages/customers.py` - `app/admin_ui/pages/opportunities.py` - `app/admin_ui/pages/integrations.py` - `app/admin_ui/components.py`