Release v4928.1.4.2 stable

This commit is contained in:
2026-06-09 22:55:58 +01:00
commit 6445044ac6
280 changed files with 41775 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
# ClientFlow v3.1 — correções Jasmin/Packlink
Esta versão é incremental sobre a v3 e corrige os pontos encontrados nos testes remotos.
## Alterações principais
### Produtos
Agora existe separação entre:
- `sku`: código interno/Odoo;
- `jasmin_sales_item`: artigo de venda no Jasmin.
Exemplo:
```text
sku = ODOO-1
jasmin_sales_item = CARREGADOR_MONO_7KW
```
As tabelas recebem colunas novas de forma aditiva:
```sql
ALTER TABLE products ADD COLUMN IF NOT EXISTS jasmin_sales_item TEXT;
ALTER TABLE opportunity_items ADD COLUMN IF NOT EXISTS jasmin_sales_item TEXT;
ALTER TABLE order_items ADD COLUMN IF NOT EXISTS jasmin_sales_item TEXT;
```
Quando uma linha é adicionada à oportunidade a partir do catálogo, `opportunity_items.jasmin_sales_item` é copiado do produto.
A criação de orçamento Jasmin usa esta prioridade:
```text
metadata.jasmin_sales_item
opportunity_items.jasmin_sales_item
JASMIN_DEFAULT_SALES_ITEM
sku
```
Assim o SKU Odoo deixa de ser usado como artigo Jasmin, exceto como fallback.
### Documentos Jasmin
Depois de criar orçamento/fatura, o serviço faz um `GET` ao Jasmin para tentar preencher:
- `document_type`
- `serie`
- `series_number`
- `document_number`
- `total_amount`
- `currency`
Isto melhora a tabela `commercial_documents` e a visualização na oportunidade.
### UI da oportunidade
- O formulário **Adicionar produto** saiu da área técnica e passou para o cartão **Produtos**.
- Os botões **Criar orçamento** e **Converter em fatura** mostram mensagem de feedback informando que o pedido foi enviado para a outbox.
- A tabela de produtos mostra SKU/Odoo e artigo Jasmin separadamente.
### Timers systemd
Foram adicionados exemplos em `deploy/systemd/`:
- `clientflow-outbox-jasmin.service`
- `clientflow-outbox-jasmin.timer`
- `clientflow-outbox-packlink.service`
- `clientflow-outbox-packlink.timer`
Instalação Jasmin:
```bash
sudo cp deploy/systemd/clientflow-outbox-jasmin.* /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now clientflow-outbox-jasmin.timer
systemctl list-timers | grep clientflow
```
O timer Jasmin processa outbox a cada 1 minuto.
## Verificações após deploy
```bash
cd /mnt/ssd/home/plx/clientflow_backend
source .venv/bin/activate
python -m py_compile app/*.py scripts/*.py
python -m pytest -q
sudo systemctl restart clientflow-api
sudo systemctl status clientflow-api --no-pager
```
## Migração prática de produtos existentes
Depois do deploy, ir a `/products/{id}` e preencher **Artigo Jasmin** para cada produto usado em documentos.
Para corrigir via SQL/Python um produto específico:
```bash
python - <<'PY'
from sqlalchemy import text
from app.db import engine
with engine.begin() as conn:
conn.execute(text("""
UPDATE products
SET jasmin_sales_item = 'CARREGADOR_MONO_7KW', updated_at = now()
WHERE sku = 'ODOO-1'
"""))
PY
```

View File

@@ -0,0 +1,78 @@
# ClientFlow v3.2 — HTMX, validações e operação sem terminal
Esta versão é incremental em cima da v3.1. O objetivo é tornar o fluxo Jasmin/Packlink mais visível e operável pela UI, sem reescrever o dashboard inteiro.
## Principais alterações
### HTMX progressivo na oportunidade
A página de oportunidade passa a usar HTMX em blocos específicos:
- cartão **Documentos Jasmin**;
- cartão **Produtos**;
- ações de outbox associadas à oportunidade.
O comportamento esperado é:
```text
[Criar orçamento]
→ cria item pending na outbox
→ atualiza apenas o cartão Documentos Jasmin
→ mostra estado pending/sent/failed
→ polling atualiza o cartão a cada 10 segundos
```
Isto reduz a sensação de que “não aconteceu nada” depois de clicar num botão.
### Validação antes de criar orçamento
Antes de criar outbox `jasmin.create_quotation`, o sistema valida:
- oportunidade com cliente local associado;
- cliente com nome fiscal;
- cliente com NIF;
- morada/código postal/cidade quando ainda não existe `jasmin_customer_party_key`;
- pelo menos uma linha de produto ativa;
- todas as linhas com `jasmin_sales_item` preenchido;
- preços unitários maiores que zero.
Se faltar algo, a UI mostra o erro diretamente no cartão Jasmin e não cria outbox inválida.
### Produtos por HTMX
No cartão **Produtos** da oportunidade:
- adicionar produto atualiza apenas o cartão;
- remover produto atualiza apenas o cartão;
- total é recalculado no bloco;
- produtos sem `jasmin_sales_item` aparecem como bloqueio visual para orçamento.
### Outbox visível na oportunidade
O cartão **Documentos Jasmin** mostra as ações recentes Jasmin relacionadas com a oportunidade:
- `create_quotation`;
- `convert_quotation_to_invoice`;
- estado `pending`, `sent`, `failed`;
- último erro;
- botão **Reprocessar** para itens `failed`/`pending`.
### Filtro de produtos sem Artigo Jasmin
Em `/products`, o filtro de estado ganhou a opção:
```text
Ativos sem Artigo Jasmin
```
Serve para encontrar rapidamente produtos que ainda bloqueiam emissão de orçamento Jasmin.
## Continuação recomendada
Para v3.3:
1. Obter PDF de orçamento/fatura.
2. Mostrar link/número real do documento quando o GET Jasmin devolver `documentType`, `serie` e `seriesNumber`.
3. Adicionar ações HTMX em `/integrations` para testar Jasmin/Packlink.
4. Implementar painel de Packlink semelhante ao painel Jasmin.
5. Refatorar `admin_dashboard.py` para `app/admin_ui/pages/` por etapas.

View File

@@ -0,0 +1,120 @@
# ClientFlow v3.3 — HTMX manual e operação Jasmin mais estável
Esta versão é incremental sobre a v3.2 e foca-se em remover o comportamento visual estranho causado pelo auto-refresh do cartão **Documentos Jasmin**.
## Alterações principais
### 1. Documentos Jasmin sem auto-refresh
Foi removido o polling HTMX automático do cartão de Documentos Jasmin.
Antes:
```html
hx-trigger="every 10s"
```
Agora:
- o cartão não se reconstrói sozinho;
- existe botão **Atualizar estado**;
- os botões **Criar orçamento** e **Converter em fatura** continuam a atualizar o cartão uma vez após o clique;
- o operador controla quando quer atualizar o estado.
Fluxo esperado:
```text
[Criar orçamento]
→ cria item na outbox
→ mostra feedback imediato
→ timer/worker processa
→ operador clica [Atualizar estado]
→ vê orçamento criado ou erro
```
### 2. Feedback visível após ações Jasmin
Os botões continuam a devolver mensagens visíveis no cartão:
- Pedido de orçamento enviado para a outbox Jasmin.
- Pedido de fatura enviado para a outbox Jasmin.
- Erros de validação antes de criar a outbox.
### 3. Outbox Jasmin visível na oportunidade
O cartão de Documentos Jasmin continua a mostrar as ações da outbox associadas à oportunidade:
- action type;
- estado;
- erro resumido;
- botão **Reprocessar** para itens pendentes/falhados.
### 4. Validação antes de criar orçamento
Mantém-se a validação preventiva:
- cliente associado;
- NIF válido;
- nome fiscal;
- morada/código postal/cidade quando o cliente ainda não existe no Jasmin;
- pelo menos uma linha de produto;
- todas as linhas com `jasmin_sales_item`;
- preço unitário maior que zero.
### 5. SKU/Odoo separado de artigo Jasmin
Mantém-se a regra introduzida na v3.1/v3.2:
```text
products.sku = código interno/Odoo
products.jasmin_sales_item = artigo Jasmin
opportunity_items.sku = código interno/Odoo
opportunity_items.jasmin_sales_item = artigo Jasmin usado no orçamento
```
A criação de orçamento usa `jasmin_sales_item`, não o SKU Odoo.
## Deploy
Aplicar como nas versões anteriores:
```bash
rsync -avz --delete \
--exclude ".env" \
--exclude ".venv/" \
--exclude "venv/" \
--exclude "__pycache__/" \
--exclude ".git/" \
--exclude "*.db" \
--exclude "*.log" \
--exclude "uploads/" \
./ plx@alarmsys:/mnt/ssd/home/plx/clientflow_backend/
```
Depois no remoto:
```bash
cd /mnt/ssd/home/plx/clientflow_backend
source .venv/bin/activate
python -m py_compile app/*.py scripts/*.py
sudo systemctl restart clientflow-api
```
## Nota operacional
O botão **Atualizar estado** não processa a outbox. Ele apenas recarrega o cartão.
Para processar automaticamente, manter ativo o timer systemd da outbox Jasmin:
```bash
systemctl list-timers | grep clientflow-outbox-jasmin
```
Ou processar manualmente:
```bash
JASMIN_OUTBOX_ENABLED=true \
OUTBOX_TARGET_SYSTEM=jasmin \
OUTBOX_DRY_RUN=false \
python scripts/process_outbox.py
```

View File

@@ -0,0 +1,60 @@
# ClientFlow v3.4 — Documentos, PDFs e Outbox Operacional
Esta versão é incremental sobre a v3.3 e foca-se em reduzir a necessidade de usar o terminal para operar integrações.
## Alterações principais
### Documentos Jasmin
- Adicionados botões no cartão **Documentos Jasmin** da oportunidade:
- **Atualizar nº**: volta a consultar o Jasmin e atualiza número/série/valor do documento local.
- **PDF**: tenta obter o PDF do orçamento ou da fatura diretamente via API Jasmin.
- Mantém atualização manual; não há auto-refresh contínuo do cartão.
### Outbox operacional
- Página `/outbox` remodelada para uso operacional.
- Filtros por estado: todas, pendentes, enviadas, falhadas e ignoradas.
- Filtros por sistema: Jasmin, Packlink, Chatwoot, Mautic e Odoo.
- Ações na UI:
- Reprocessar
- Ignorar
- Ver detalhe/payload/erro
- Novo estado `ignored` suportado na outbox.
### Produtos / Jasmin
- Página `/products/validate-jasmin` para validar se os artigos configurados em `products.jasmin_sales_item` existem no Jasmin.
- Botão **Validar artigos Jasmin** no catálogo de produtos.
- Mantém a separação:
- `products.sku` = SKU interno/Odoo
- `products.jasmin_sales_item` = artigo Jasmin usado em orçamentos/faturas
## Validações mantidas
Antes de criar orçamento Jasmin, continua a validar:
- Cliente associado à oportunidade
- NIF do cliente
- Nome fiscal
- Morada/código postal/cidade quando for necessário criar cliente no Jasmin
- Pelo menos um produto ativo na oportunidade
- Todos os produtos com `jasmin_sales_item`
- Preços unitários positivos
## Notas operacionais
- A obtenção de PDF depende dos endpoints de impressão do Jasmin estarem disponíveis no tenant.
- Se o PDF falhar, a página devolve erro textual sem afetar o documento já criado.
- A outbox Jasmin deve ser processada por timer systemd ou manualmente com `scripts/process_outbox.py`.
## Validação técnica
Executado na geração desta versão:
```bash
python -m py_compile app/*.py scripts/*.py
python -m pytest -q
```
Resultado: `4 passed`.

View File

@@ -0,0 +1,82 @@
# 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`

View File

@@ -0,0 +1,72 @@
# ClientFlow v4.0 — estabilização operacional + refactor modular incremental
Esta versão junta a estabilização prevista para v3.5 com o arranque do refactor v4.
## Incluído
### Operação segura
- Migrações SQL versionadas em `migrations/`.
- Script `scripts/apply_migrations.py`.
- Script `scripts/check_clientflow_health.py`.
- Script `scripts/install_systemd_timers.sh`.
- Índices adicionais para outbox, clientes, documentos, envios e produtos.
- Colunas de segurança na outbox: `locked_at`, `lock_owner`, `ignored_at`.
- API interna read-only:
- `/api/internal/health`
- `/api/internal/outbox/summary`
- `/api/internal/documents/summary`
### Refactor modular incremental
- Novo pacote `app/admin_ui/`.
- `app/main.py` passa a importar o router admin através de `app.admin_ui.router`.
- O router ainda usa `app.admin_dashboard` internamente para manter compatibilidade.
- Helpers HTML comuns em `app/admin_ui/components.py`.
- Estrutura de destino criada para modularizar:
- `app/customers/`
- `app/opportunities/`
- `app/commercial/`
- `app/logistics/`
- `app/integrations/`
- `app/outbox/`
## Deploy recomendado
```bash
cd /mnt/ssd/home/plx/clientflow_backend
source .venv/bin/activate
python scripts/apply_migrations.py --dry-run
python scripts/apply_migrations.py
python -m py_compile app/*.py scripts/*.py
python scripts/check_clientflow_health.py
sudo systemctl restart clientflow-api
```
## Timers systemd
Jasmin:
```bash
sudo ./scripts/install_systemd_timers.sh /mnt/ssd/home/plx/clientflow_backend
```
Packlink, só quando o fluxo de envios estiver validado:
```bash
ENABLE_PACKLINK_TIMER=true sudo -E ./scripts/install_systemd_timers.sh /mnt/ssd/home/plx/clientflow_backend
```
## Próximo passo após v4.0
Migrar gradualmente rotas de `app/admin_dashboard.py` para:
```text
app/admin_ui/pages/customers.py
app/admin_ui/pages/opportunities.py
app/admin_ui/pages/products.py
app/admin_ui/pages/outbox.py
app/admin_ui/pages/integrations.py
```
Sem mudar URLs públicas.

View File

@@ -0,0 +1,34 @@
# ClientFlow v4.1 — Correções de deploy e scripts operacionais
Esta versão é incremental sobre a v4.0 e corrige o problema encontrado no remoto:
```text
ModuleNotFoundError: No module named 'app'
```
## Alterações
- `scripts/apply_migrations.py` passa a resolver automaticamente a raiz do projeto.
- `scripts/check_clientflow_health.py` passa a resolver automaticamente a raiz do projeto.
- `scripts/process_outbox.py` também passa a mudar para a raiz do projeto antes de carregar `.env`.
- `scripts/test_jasmin_connection.py` e `scripts/test_packlink_connection.py` passam a carregar `.env` a partir da raiz do projeto mesmo quando executados de outra pasta.
## Comandos depois do deploy
```bash
cd /mnt/ssd/home/plx/clientflow_backend
source .venv/bin/activate
python scripts/apply_migrations.py --dry-run
python scripts/apply_migrations.py
python scripts/check_clientflow_health.py
sudo systemctl restart clientflow-api
sudo systemctl status clientflow-api --no-pager
```
Já não deve ser necessário executar com `PYTHONPATH=.`.
## Nota
O `.env` continua excluído do `rsync` e deve permanecer apenas no servidor remoto.

View File

@@ -0,0 +1,59 @@
# ClientFlow v4.2 — Operação Comercial e Logística
Esta versão melhora a maturidade operacional do ClientFlow sem alterar a arquitetura principal.
## Alterações principais
- Nova página `/operations` / `/operacoes` para operação diária.
- Nova página `/system/health` para saúde operacional.
- Timeline da oportunidade passa a juntar eventos, produtos, documentos, outbox e envios.
- API interna adiciona:
- `/api/internal/operations/summary`
- `/api/internal/system/health`
- Navegação passa a separar:
- Operações
- Outbox
- Integrações
- A página de operações destaca:
- oportunidades abertas
- outbox pendente/falhada
- faturas emitidas hoje
- orçamentos abertos
- envios pendentes
- clientes incompletos
- produtos sem Artigo Jasmin
## Objetivo
Reduzir a dependência do terminal para operação diária. A equipa deve conseguir abrir `/operations` e saber rapidamente o que precisa de ação.
## Rotas novas
```text
/operations
/operacoes
/system/health
/api/internal/operations/summary
/api/internal/system/health
```
## Notas de deploy
Depois do rsync:
```bash
cd /mnt/ssd/home/plx/clientflow_backend
source .venv/bin/activate
python scripts/apply_migrations.py --dry-run
python scripts/apply_migrations.py
python scripts/check_clientflow_health.py
sudo systemctl restart clientflow-api
```
## Próximas melhorias sugeridas
- Packlink com cotação antes de criar envio.
- Guardar PDFs localmente.
- Backups/restore via scripts próprios.
- Duplicados de clientes por NIF.
- Sincronização de preços Jasmin vs Odoo.

View File

@@ -0,0 +1,42 @@
# ClientFlow v4.2 — revisão operacional e UI
Esta revisão mantém a arquitetura da v4.2, mas corrige pontos de estabilidade e torna a UI mais operacional.
## Correções aplicadas
- `OUTBOX_DRY_RUN=true` deixou de marcar itens como `sent`; agora passa o item para `dry_run`.
- Integrações com `*_OUTBOX_ENABLED=false` deixam de ficar apenas em `skipped` silencioso; agora ficam `blocked` com erro claro.
- A idempotência de `jasmin.create_quotation` passou a ser determinística por oportunidade: `jasmin:quotation:{opportunity_id}`.
- `integration_outbox` ganhou estados visíveis e reprocessáveis: `pending`, `processing`, `sent`, `failed`, `blocked`, `dry_run`, `ignored`, `cancelled`.
- O schema base passa a garantir colunas de controlo da outbox (`locked_at`, `lock_owner`, `ignored_at`) mesmo em instalações novas.
- Webhooks Chatwoot exigem `CLIENTFLOW_WEBHOOK_SECRET` em `ENV=prod`, `production` ou `staging`.
- Admin UI e API interna podem ser protegidas com `CLIENTFLOW_ADMIN_TOKEN`.
## Melhorias UI
- Página da oportunidade ganhou painel **Integrações da oportunidade** com contadores:
- pendentes;
- falhadas;
- bloqueadas;
- dry-run.
- A oportunidade passa a mostrar ações Jasmin/Packlink/Chatwoot/Mautic ligadas à oportunidade, com último erro e reprocessamento.
- `/outbox` ganhou filtros para os novos estados operacionais.
- Badges visuais foram atualizados para `processing`, `blocked`, `dry_run` e `cancelled`.
## Validação
Executado:
```bash
python -m compileall app scripts tests
pytest -q
```
Resultado: `12 passed`.
## Ainda recomendado para próxima ronda
- Substituir o token simples por login/sessão + CSRF nos POSTs.
- Refatorar `admin_dashboard.py`, que continua demasiado grande.
- Confirmar endpoints reais de PDF Jasmin no tenant de produção.
- Criar testes funcionais com Postgres de teste e mocks para Jasmin/Packlink.

View File

@@ -0,0 +1,89 @@
# ClientFlow v4.5.1 — UI Alignment
Esta atualização alinha a UI real instalada com o mockup aprovado para a organização v4.5 Operational Core.
## Objetivo
Manter a lógica e os endpoints da v4.5, mas tornar a interface mais próxima do mockup:
- sidebar clara e moderna;
- topbar com pesquisa global;
- cartões KPI mais legíveis;
- tabelas com linhas em formato de cartões;
- melhor responsividade em mobile;
- distinção visual mais clara entre Dashboard, Centro de trabalho, Comunicações, Oportunidades, Outbox e Integrações.
## Escopo
Esta versão é essencialmente visual.
Não altera:
- migrações da BD;
- lógica Jasmin;
- lógica Packlink;
- outbox worker;
- webhooks;
- serviços de email;
- endpoints principais.
## Páginas afetadas
Como o layout base é partilhado, a melhoria visual afeta a UI admin em geral:
- `/`
- `/operations`
- `/communications`
- `/opportunities`
- `/outbox`
- `/integrations`
- `/products`
- `/customers`
## Atualização em campo
Não é necessário aplicar migração nova.
Procedimento recomendado:
```bash
cd /home/ricar/Transferências/clientflow_backend_v451_ui_alignment
rsync -avz --delete --dry-run \
--exclude ".env" \
--exclude ".venv/" \
--exclude "venv/" \
--exclude "__pycache__/" \
--exclude ".git/" \
--exclude "*.db" \
--exclude "*.log" \
--exclude "uploads/" \
./ plx@alarmsys:/mnt/ssd/home/plx/clientflow_backend/
```
Se o dry-run estiver correto:
```bash
rsync -avz --delete \
--exclude ".env" \
--exclude ".venv/" \
--exclude "venv/" \
--exclude "__pycache__/" \
--exclude ".git/" \
--exclude "*.db" \
--exclude "*.log" \
--exclude "uploads/" \
./ plx@alarmsys:/mnt/ssd/home/plx/clientflow_backend/
```
No servidor:
```bash
cd /mnt/ssd/home/plx/clientflow_backend
source .venv/bin/activate 2>/dev/null || true
PYTHONPATH=. python -m compileall app scripts tests
pytest -q
sudo systemctl restart clientflow-api
sudo systemctl status clientflow-api --no-pager
journalctl -u clientflow-api -n 80 --no-pager
```

View File

@@ -0,0 +1,19 @@
# ClientFlow v4.5.2 — UI Icons Fix
Patch visual pequeno sobre a v4.5.1.
## O que corrige
- Os cartões KPI da v4.5.1 mostravam caixas azul-claro vazias onde deveriam existir ícones.
- A v4.5.2 adiciona ícones/fallback visuais nos KPI cards através de CSS seguro.
- Adiciona Bootstrap Icons por CDN para uso progressivo nos templates.
- Corrige um pequeno bloco duplicado no rodapé da sidebar.
## O que não altera
- Base de dados
- Migrações
- Jasmin
- Packlink
- Workers/outbox
- Webhooks

View File

@@ -0,0 +1,49 @@
# ClientFlow v4.5.3 — Semantic Icons + UI Polish
Esta microversão corrige o acabamento visual da v4.5.2.
## Objetivo
Substituir os ícones genéricos dos cards KPI por ícones semânticos, mantendo a lógica operacional e as integrações intactas.
## Alterações
- Atualização do badge visual para `v4.5.3 UI`.
- Sidebar passa a usar Bootstrap Icons semânticos.
- Cards KPI passam a renderizar um elemento `.cf-kpi-icon` com ícone Bootstrap.
- Ícones automáticos por tema:
- oportunidades/pipeline: funil;
- documentos/orçamentos: documento;
- finanças/pagamentos: euro;
- tasks: checklist;
- comunicações/email: envelope;
- erros/bloqueios: alerta;
- envios/logística: camião;
- clientes: cartão de cliente;
- produtos/Jasmin: caixa/artigo;
- integrações/sistema: puzzle.
- Tons visuais por tipo: verde, laranja, vermelho e roxo quando aplicável.
- Mantém Bootstrap 5 + Bootstrap Icons via CDN.
## Não altera
- Base de dados.
- Migrações.
- Jasmin.
- Packlink.
- Outbox worker.
- Webhooks.
- Lógica de documentos comerciais.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```
Resultado esperado:
```text
19 passed
```

View File

@@ -0,0 +1,110 @@
# ClientFlow v4.5 — Operational Core
Esta versão junta a proposta v4.3 + v4.4 numa atualização única e incremental para o sistema em campo.
## Objetivo
Transformar a UI num sistema operacional diário:
```text
Email / Chatwoot / pedido recebido
→ Comunicação classificada
→ Centro de trabalho
→ Cliente / oportunidade
→ Task humana ou Outbox automática
→ Documento / envio
→ Timeline
```
## O que muda
### Dashboard
O `/` passa a ser uma visão geral: métricas, alertas e ligação direta ao Centro de trabalho. Deixa de ser a página principal para execução item a item.
### Centro de trabalho
O `/operations` passa a juntar itens acionáveis:
- tasks humanas pendentes;
- comunicações por tratar;
- outbox pending/failed/blocked;
- clientes/produtos/documentos que bloqueiam o fluxo.
### Comunicações
Nova área:
```text
/communications
/comunicacoes
```
Guarda emails/mensagens classificados com ligação opcional a cliente, oportunidade e task.
Estados suportados:
```text
new
classified
needs_review
linked
task_created
done
ignored
```
### Oportunidade
A página da oportunidade passa a incluir navegação por secções:
```text
Resumo
Produtos
Documentos
Tasks
Comunicações
Outbox
Timeline
Técnico
```
### Tasks contextualizadas
A tabela `tasks` recebe colunas para contexto operacional:
```text
communication_id
document_id
shipment_id
outbox_id
priority
assigned_to
```
### Timeline unificada
Nova tabela `timeline_events`, integrada na timeline da oportunidade.
## Instalação
1. Fazer backup da base de dados.
2. Aplicar código v4.5.
3. Reiniciar serviço.
4. Confirmar que o startup executou `init_db()`.
5. Opcional: aplicar manualmente `migrations/006_v45_operational_core.sql`.
6. Validar no browser:
```text
/
/operations
/communications
/opportunities/{id}
```
## Notas
- Não muda para React/Vue.
- Mantém FastAPI + HTML/HTMX.
- Não remove rotas antigas.
- A automação crítica continua por outbox e deve continuar a exigir validação humana quando aplicável.

View File

@@ -0,0 +1,83 @@
# Guia de atualização — ClientFlow v4.5 Operational Core
## Antes de atualizar
1. Fazer backup da base de dados PostgreSQL.
2. Guardar o `.env` atual.
3. Confirmar que os timers/serviços atuais estão estáveis:
```bash
systemctl status clientflow-api.service
systemctl list-timers | grep clientflow
```
## Instalação recomendada
```bash
# exemplo; ajustar diretórios ao servidor real
cd /opt/clientflow
systemctl stop clientflow-api.service
cp -a clientflow_backend clientflow_backend_backup_$(date +%Y%m%d_%H%M)
unzip clientflow_backend_v45_operational_core.zip -d /opt/clientflow/clientflow_backend_v45
cp /opt/clientflow/clientflow_backend/.env /opt/clientflow/clientflow_backend_v45/.env
cd /opt/clientflow/clientflow_backend_v45
python -m compileall app scripts tests
pytest -q
systemctl start clientflow-api.service
```
Se o serviço usa um caminho fixo, trocar o symlink/current release conforme o teu deploy atual.
## Migração de base de dados
A v4.5 é aditiva. O `init_db()` cria/atualiza:
```text
communications
timeline_events
tasks.communication_id
tasks.document_id
tasks.shipment_id
tasks.outbox_id
tasks.priority
tasks.assigned_to
```
Também existe migração SQL explícita:
```bash
psql "$DATABASE_URL" -f migrations/006_v45_operational_core.sql
```
## Validação pós-instalação
Abrir:
```text
/
/operations
/communications
/opportunities
/outbox
/integrations
```
Verificar:
```text
Dashboard mostra apenas visão geral.
Centro de trabalho mostra fila operacional.
Comunicações abre sem erro.
Oportunidade mostra tabs: Resumo, Produtos, Documentos, Tasks, Comunicações, Outbox, Timeline, Técnico.
Outbox continua a processar sem marcar dry_run como sent.
```
## Rollback
Como a migração é aditiva, o rollback de código pode ser feito voltando para a pasta/ZIP anterior. As tabelas e colunas novas podem ficar na BD sem afetar a versão anterior.
## Limitações conhecidas
- A v4.5 cria a estrutura de Comunicações, mas o conector real de email/classificação deve inserir dados nessa tabela.
- A confirmação de pagamentos e ações críticas continuam humanas.
- Permissões e CSRF completos continuam recomendados para uma ronda posterior se o sistema estiver exposto fora da rede controlada.

View File

@@ -0,0 +1,22 @@
# ClientFlow v4.6.1 — Customer mismatch warning tuning
Esta correção reduz falsos positivos no aviso “contacto e cliente fiscal não coincidem”.
O caso típico era uma oportunidade com contacto curto/truncado, por exemplo:
- Contacto/origem: `Riotec elec`
- Cliente fiscal: `Riotec - Electricidade, Aquecimento e Controle, Lda`
A heurística anterior comparava strings completas e gerava alerta. A nova heurística compara tokens significativos e aceita abreviações/prefixos como o mesmo cliente provável.
## Desativar completamente o aviso
Se o aviso continuar a criar ruído operacional, adiciona no `.env`:
```env
CLIENTFLOW_CUSTOMER_MISMATCH_WARNING_ENABLED=false
```
Depois reinicia o serviço.
A validação real antes de criar documentos continua a depender dos dados fiscais do cliente associado e das validações Jasmin/Packlink.

View File

@@ -0,0 +1,32 @@
# ClientFlow v4.6.2 — Customer warning off by default + opportunity layout tuning
Esta versão desativa por defeito o aviso de divergência entre contacto/origem e cliente fiscal associado.
## Motivo
Na operação real é comum a conversa chegar por um contacto abreviado ou pessoal, mas o documento fiscal pertencer a uma empresa. Exemplos válidos:
- `Riotec elec` → `Riotec - Electricidade, Aquecimento e Controle, Lda`
- `Bruno Oliveira` → `Nortuflex`
Nestes casos o aviso gerava ruído e podia atrasar a operação.
## Comportamento novo
Por defeito:
```env
CLIENTFLOW_CUSTOMER_MISMATCH_WARNING_ENABLED=false
```
O aviso pode ser reativado no `.env` se necessário:
```env
CLIENTFLOW_CUSTOMER_MISMATCH_WARNING_ENABLED=true
```
## Ajuste visual
A página da oportunidade passa para uma coluna mais cedo em ecrãs/zoom de laptop, evitando que o painel lateral comprima a área principal.
Não há migração de base de dados.

View File

@@ -0,0 +1,43 @@
# ClientFlow v4.6.2 — Workflow Actions Cleanup
Correção pequena focada em operação diária, sem migrações de base de dados e sem alterações em Jasmin/Packlink.
## Alterações
- Remove o aviso operacional por divergência de nome entre contacto e cliente fiscal.
- Contacto pessoal e empresa fiscal podem ser diferentes e estar corretos.
- A validação crítica deve ficar em NIF, dados fiscais obrigatórios, documento emitido e associação fiscal, não em semelhança de nomes.
- Adiciona a ação `MARK_NO_INTEREST`.
- Usada quando o cliente indica que não tem interesse, não tem frota/veículos elétricos, não necessita ou a oferta não se aplica.
- Adiciona regra determinística para mensagens inequívocas, por exemplo:
- “não temos veículos elétricos”
- “não temos na nossa frota veículos elétricos”
- “não estamos interessados”
- “não se aplica”
- `MARK_NO_INTEREST` cria trabalho pendente em vendas para o operador confirmar/fechar o caso.
- Se existir oportunidade aberta, a task pode ficar ligada à oportunidade.
- Ao concluir a task, a oportunidade pode avançar para `LOST`.
- Se não existir oportunidade aberta, `MARK_NO_INTEREST` não cria uma oportunidade nova só para a fechar.
- Ajusta CSS da ficha da oportunidade para reduzir quebra de layout em zoom/larguras intermédias.
## Exemplo
Mensagem:
> Agradecemos a informação, mas não temos na nossa frota veículos elétricos.
Resultado esperado:
- `action_code = MARK_NO_INTEREST`
- `route = vendas`
- `status = pending`
- ação: “Marcar sem interesse”
## Não altera
- Base de dados
- Migrações
- Jasmin
- Packlink
- Outbox worker
- Webhooks Chatwoot

View File

@@ -0,0 +1,27 @@
# ClientFlow v4.6.3 — Operations Queue Cleanup
Esta versão é um hotfix operacional, sem migração de base de dados.
## Alterações
- `MARK_NO_INTEREST` deixa de fechar oportunidades como `LOST`.
- Novo estado de oportunidade: `NO_INTEREST` / “Sem interesse”.
- `LOST` fica reservado para oportunidades perdidas por preço, funcionalidades, desistência ou concorrência.
- O Centro de trabalho deixa de contar/mostrar outbox `failed` cujo erro indique que foi “limpo manualmente” ou “resolvido manualmente”.
- Textos técnicos como “Resposta LLM inválida para action_code” passam a aparecer como “Classificação da mensagem falhou”.
- “Emails por tratar” passa a “Mensagens para revisão”.
- “Sem cliente” passa a “Mensagens sem cliente”.
- Documentos no Centro de trabalho passam a focar documentos que podem exigir ação.
- Novo script: `scripts/ignore_manually_cleaned_outbox.py`.
## Uso do script
```bash
PYTHONPATH=. python scripts/ignore_manually_cleaned_outbox.py --dry-run
PYTHONPATH=. python scripts/ignore_manually_cleaned_outbox.py
```
## Regra conceptual
- `NO_INTEREST`: cliente sem interesse atual após divulgação/campanha, sem necessidade ou sem frota elétrica.
- `LOST`: oportunidade comercial real perdida depois de negociação, por preço, funcionalidades, desistência ou concorrência.

View File

@@ -0,0 +1,44 @@
# ClientFlow v4.6.5 — Operations Work Queue
Objetivo: transformar `/operations` numa lista única de trabalho do operador, em vez de um mini-dashboard técnico.
## Decisão de produto
- Chatwoot continua a ser a inbox.
- Dashboard mostra estado e gargalos globais.
- Operations mostra apenas trabalho humano acionável.
- Outbox, documentos, produtos e clientes só devem aparecer em Operations quando bloqueiam uma ação concreta.
## Alterações principais
- `/operations` passa a mostrar 4 contadores curtos: a fazer agora, atrasadas, bloqueadas e rever associação.
- Adicionados filtros operacionais: Todas, Vendas, Financeiro, Logística, Revisão, Bloqueadas e Concluídas hoje.
- A fila principal passa a ser composta por cartões de trabalho com:
- cliente/contacto;
- oportunidade/processo;
- próxima ação;
- origem;
- estado/fila;
- botões principais.
- Removidas da vista principal as tabelas técnicas de outbox, documentos recentes, produtos sem Jasmin e clientes incompletos.
- Outbox continua a entrar na fila apenas quando representa erro/bloqueio que exige ação humana.
## Botão principal por tipo de ação
Exemplos:
- `SEND_QUOTE` → Preparar orçamento
- `SEND_INFO` → Preparar resposta
- `SEND_PROFORMA` → Preparar pró-forma
- `SEND_INVOICE` → Emitir fatura
- `CONFIRM_PAYMENT` → Confirmar pagamento
- `PREPARE_ORDER` → Preparar encomenda
- `CREATE_SHIPMENT` → Criar envio
- `REVIEW_MANUALLY` → Rever mensagem
- `ASSOCIATE_CUSTOMER` → Associar cliente
- `MARK_NO_INTEREST` → Marcar sem interesse
- `REMOVE_FROM_LIST` → Remover da lista
## Sem migração
Esta versão não altera base de dados, Jasmin, Packlink nem workers. É uma alteração de apresentação e agregação operacional.

View File

@@ -0,0 +1,16 @@
# ClientFlow v4.6.6 — Safe Opportunity Linking
Objetivo: reduzir o risco de associar automaticamente uma mensagem/task do Chatwoot à oportunidade ou cliente fiscal errado.
## Alterações principais
- `conversation_id` passa a ser a correspondência forte para associar task a oportunidade.
- `contact_id` do Chatwoot passa a ser correspondência fraca: só associa automaticamente se existir exatamente uma oportunidade aberta recente para esse contacto.
- Se houver mais de uma oportunidade aberta recente para o mesmo `contact_id`, a task é marcada para revisão de associação e não é criada/atualizada uma nova oportunidade automaticamente.
- A página de detalhe da task deixa de usar `contact_id` do Chatwoot como se fosse ID local de cliente.
- O link “Ver cliente” passa a aparecer apenas quando existe cliente fiscal local seguro.
- No Centro de trabalho, uma associação ambígua aparece como ação humana: “Associar oportunidade”.
## Regra operacional
Chatwoot `contact_id` identifica o contacto/conversa, não necessariamente o cliente fiscal. O cliente fiscal continua a ser confirmado na oportunidade antes de orçamento/fatura/envio.

View File

@@ -0,0 +1,26 @@
# ClientFlow v4.6.7 — Chatwoot Auto-complete Guardrails
Esta versão endurece o comportamento de auto-completar tarefas quando chega uma mensagem `outgoing` do Chatwoot.
## Objetivo
Manter o Chatwoot como inbox, mas evitar que uma resposta enviada feche tarefas que exigem validação humana.
## Alterações
- `CONFIRM_PAYMENT` deixa de estar nos action codes auto-completados por defeito.
- `MARK_NO_INTEREST` e `REVIEW_MANUALLY` também não são auto-completados por resposta outgoing.
- Se a task tiver `opportunity_linking_status=ambiguous`, a resposta outgoing não fecha a tarefa.
- Tarefas auto-completadas registam evento de timeline como `task_auto_completed` quando existe oportunidade associada.
## Continua configurável
É possível sobrescrever a lista via:
```env
CHATWOOT_AUTO_COMPLETE_ACTION_CODES=SEND_INFO,SEND_QUOTE,SUPPORT
```
## Regra operacional
O sistema pode fechar automaticamente tasks em que a resposta ao cliente é a própria ação. Não deve fechar tasks que confirmam pagamento, validam associação ou exigem decisão fiscal/operacional.

View File

@@ -0,0 +1,59 @@
# ClientFlow v4.6.8 — Operational Safety Consolidation
Esta versão consolida as proteções iniciadas em v4.6.5–v4.6.7 e prepara o caminho para v4.7/v4.8.
## 1. Auto-complete com política hard-deny
Mesmo que `CHATWOOT_AUTO_COMPLETE_ACTION_CODES` seja configurado manualmente, estas ações não são fechadas por mensagem outgoing:
- `CONFIRM_PAYMENT`
- `CONFIRM_PAYMENT_AND_PREPARE_SHIPMENT`
- `MARK_NO_INTEREST`
- `REVIEW_MANUALLY`
- `PREPARE_ORDER`
- `CREATE_SHIPMENT`
- `REMOVE_FROM_LIST`
O default fica limitado a ações em que responder no Chatwoot é a execução da tarefa:
- `SEND_INFO`
- `SEND_QUOTE`
- `SEND_PROFORMA`
- `SEND_INVOICE`
- `SUPPORT`
## 2. Outbox com claim transacional
`scripts/process_outbox.py` passa a usar `claim_pending_outbox()`.
O worker marca linhas como `processing` dentro da mesma transação, usando:
```sql
FOR UPDATE SKIP LOCKED
```
Isto reduz o risco de dois workers processarem a mesma ação externa.
## 3. Health operacional
`/api/internal/system/health` passa a expor métricas operacionais:
- tasks pendentes
- tasks concluídas nas últimas 24h
- auto-completes nas últimas 24h
- tasks com oportunidade ambígua
- outbox em processing
- outbox processing stale
- outbox failed/blocked
- configuração efetiva do auto-complete Chatwoot
## 4. Navegação mais limpa
`/tasks` continua disponível para diagnóstico/listagem, mas sai da navegação principal. A entrada diária recomendada é `/operations`.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```

View File

@@ -0,0 +1,99 @@
# ClientFlow v4.6 — Chatwoot Workflow Hardening
## Objetivo
Esta atualização não cria uma segunda inbox. O Chatwoot continua a ser a caixa de entrada e o ClientFlow passa a tratar melhor o trabalho que nasce das mensagens do Chatwoot.
Fluxo alvo:
```text
Chatwoot → webhook → classificação → task/opportunity → Centro de trabalho → timeline
```
## Alterações principais
### 1. `REVIEW_MANUALLY` passa a ser trabalho pendente
Antes, decisões `REVIEW_MANUALLY` ficavam como `skipped`. Isto escondia mensagens que precisavam de operador.
Agora:
```text
REVIEW_MANUALLY → route=rever → status=pending → priority=alta
```
### 2. `REMOVE_FROM_LIST` passa a ser operacional
Antes podia ficar tratado como revisão/skip. Agora fica pendente e orientado para marketing:
```text
REMOVE_FROM_LIST → route=marketing → status=pending
```
### 3. Só spam e “sem ação” ficam `skipped`
```text
IGNORE_SPAM → skipped
NO_ACTION → skipped
```
### 4. Centro de trabalho mostra contexto Chatwoot
Os itens operacionais vindos do Chatwoot passam a trazer:
```text
source_system
conversation_id
contact_id
request_text
chatwoot_url
```
A UI mostra botão para abrir a conversa no Chatwoot quando `CHATWOOT_PUBLIC_URL`/`CHATWOOT_BASE_URL` e `CHATWOOT_ACCOUNT_ID` estão configurados.
### 5. Comunicações deixam de ser navegação principal
A rota técnica `/communications` continua disponível para diagnóstico, mas deixa de ser a inbox principal. O operador deve responder e gerir conversas no Chatwoot.
### 6. Timeline para tasks associadas à oportunidade
Quando uma task criada pelo Chatwoot está ligada a uma oportunidade, é registado evento de timeline `task_created`.
### 7. Script para reabrir revisões antigas
Para corrigir as tasks recentes que já ficaram `skipped` antes da v4.6:
```bash
PYTHONPATH=. python scripts/reopen_chatwoot_review_tasks.py --dry-run
PYTHONPATH=. python scripts/reopen_chatwoot_review_tasks.py --days 7
```
Por defeito só olha para os últimos 7 dias.
## Passos pós-instalação recomendados
```bash
cd /mnt/ssd/home/plx/clientflow_backend
source .venv/bin/activate 2>/dev/null || true
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
PYTHONPATH=. python scripts/reopen_chatwoot_review_tasks.py --dry-run
PYTHONPATH=. python scripts/reopen_chatwoot_review_tasks.py --days 7
sudo systemctl restart clientflow-api
sudo systemctl status clientflow-api --no-pager
```
## Validação funcional
Verificar:
```text
/tasks?status=pending&route=rever
/operations
/opportunities
```
E confirmar que novas mensagens Chatwoot classificadas como revisão aparecem como pendentes.

View File

@@ -0,0 +1,30 @@
# ClientFlow v4.7.1 — Admin Sidebar Scroll Fix
Hotfix visual para a navegação lateral introduzida na v4.7.
## Problema
Em ecrãs com pouca altura, ao abrir o submenu **Admin**, os itens técnicos no final do menu podiam ficar fora da área visível sem scroll próprio no sidebar.
## Correção
- O sidebar desktop passa a ter `overflow-y: auto` e `max-height: 100vh`.
- O scroll fica contido no menu lateral com `overscroll-behavior: contain`.
- Foi adicionado `scrollbar-gutter: stable` para evitar saltos visuais quando o scrollbar aparece.
- O footer e blocos de navegação deixam de encolher de forma imprevisível.
- Em mobile, o menu continua horizontal com `overflow-x: auto` e sem scroll vertical no sidebar.
## Impacto
Não altera rotas, regras de negócio, base de dados, Chatwoot, outbox nem lógica de oportunidades.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```
## Rollback
Reverter apenas `app/admin_ui/styles.py` para a versão v4.7.

View File

@@ -0,0 +1,47 @@
# ClientFlow v4.7.2 — Rotas por domínio
Esta versão separa o registo de rotas da UI administrativa por domínio, reduzindo a responsabilidade de `app/admin_dashboard.py` e preparando a extração gradual de view models/componentes na v4.7.3.
## Objetivo
- Manter comportamento e URLs existentes.
- Não alterar regras Chatwoot, auto-complete, oportunidades, outbox ou DB.
- Criar módulos reais em `app/admin_ui/pages/`.
## Estrutura
```text
app/admin_ui/router.py
app/admin_ui/pages/
dashboard.py
operations.py
opportunities.py
tasks.py
customers.py
products.py
orders.py
finance.py
integrations.py
communications.py
outbox.py
events.py
runs.py
queues.py
conversations.py
system.py
```
## Compatibilidade
As rotas públicas continuam iguais, incluindo aliases em português como `/operacoes`, `/oportunidades`, `/clientes`, `/produtos`, `/encomendas`, `/financeiro` e `/integracoes`.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```
## Migração de base de dados
Não requer migração de base de dados.

View File

@@ -0,0 +1,74 @@
# ClientFlow v4.7.3 — HTMX Partials & View Models
## Objetivo
A v4.7.3 consolida a primeira camada HTMX depois da separação de rotas da v4.7.2.
Não altera regras de negócio, base de dados, Chatwoot, auto-complete, associação de oportunidades ou outbox worker.
## O que muda
### Centro de trabalho
`/operations` passa a ter refresh parcial da lista de trabalho:
- endpoint: `/operations/partials/work-items`
- target: `#operations-work-items`
- filtros com `hx-get`
- `hx-push-url` para manter URL partilhável
- indicador `#operations-loading`
### Tasks
`/tasks` passa a ter refresh parcial da lista:
- endpoint: `/tasks/partials/list`
- target: `#tasks-list`
- tabs e formulário com `hx-get`
- `hx-push-url` para manter URL partilhável
- indicador `#tasks-loading`
### Oportunidades
A v4.7.3 mantém e documenta os partials já usados no detalhe da oportunidade:
- `/opportunities/{id}/partials/products`
- `/opportunities/{id}/partials/jasmin-documents`
Estes partials continuam a ser usados pelos forms HTMX de produtos e documentos.
## View models e labels
Foram criados:
- `app/admin_ui/labels.py`
- `app/admin_ui/htmx.py`
- `app/admin_ui/view_models/operations.py`
A intenção é retirar labels e preparação de dados das rotas antes de avançar para mais partials.
## Critério de segurança
A v4.7.3 é uma versão de UI/estrutura. Não muda:
- schema da base de dados
- workflow Chatwoot
- auto-complete
- regras de oportunidade/cliente
- worker de outbox
- integrações Jasmin/Packlink/Odoo
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```
## Próximo passo
A próxima versão pode consolidar:
- partial do quadro de oportunidades
- partial da outbox operacional
- componentes HTML para `work_item_card`, `task_table` e `opportunity_card`

View File

@@ -0,0 +1,101 @@
# ClientFlow v4.7.4 — HTMX Completion & Operator UX
## Objetivo
Completar a experiência HTMX nas áreas operacionais, sem alterar regras de negócio, schema de base de dados, Chatwoot auto-complete, associação de oportunidades ou worker da outbox.
## O que mudou
### Oportunidades
Novo partial:
```text
/opportunities/partials/board
```
A página `/opportunities` passou a filtrar o quadro por HTMX, com atualização do container:
```text
#opportunities-board
```
Filtros principais:
```text
Todas
Novas
Orçamento enviado
Pró-forma enviada
Pagamento pendente
Enviadas
Bloqueadas
```
### Tasks
Novo partial:
```text
/tasks/{task_id}/partials/detail
```
As ações principais do detalhe de task passam a poder atualizar o painel:
```text
#task-detail-panel
```
Ações com HTMX:
```text
Marcar como feita
Ignorar tarefa
Reclassificar
Preparar pró-forma
Preparar envio
Preparar recolha
```
### Outbox
Novo partial:
```text
/outbox/partials/table
```
A página `/outbox` passa a atualizar filtros e ações manuais no container:
```text
#outbox-table
```
Ações com HTMX:
```text
Reprocessar
Ignorar
```
## Compatibilidade
As rotas antigas continuam disponíveis. Sem HTMX, os forms continuam com `method="post"` e `action`, mantendo fallback por redirect.
## Não muda
```text
Não requer migração de base de dados.
Não altera regras Chatwoot.
Não altera auto-complete.
Não altera associação oportunidade/cliente.
Não altera outbox worker.
Não altera integrações Jasmin/Packlink/Odoo/Mautic.
```
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```

View File

@@ -0,0 +1,95 @@
# ClientFlow v4.7 — Refactor UI e Centro de Trabalho do Operador
## Objetivo
A v4.7 reorganiza a UI administrativa sem alterar regras de negócio, schema da base de dados ou fluxo Chatwoot/auto-complete. A versão parte da v4.6.8 e foca-se em manutenção, clareza para o operador e redução do ficheiro `app/admin_dashboard.py`.
## Modelo operacional
- **Chatwoot** = conversa e inbox.
- **Centro de trabalho / Operations** = trabalho diário do operador.
- **Oportunidades** = contexto comercial e próxima ação.
- **Admin** = diagnóstico técnico, outbox, eventos, runs e comunicações internas.
## Alterações principais
### Navegação
O menu principal fica focado no operador:
- Dashboard
- Centro de trabalho
- Oportunidades
- Clientes
- Produtos
- Encomendas
- Financeiro
- Integrações
As áreas técnicas passam para o grupo **Admin**:
- Tasks
- Comunicações
- Outbox
- Eventos
- Runs
- Filas
- System health
- Configuração
As rotas antigas continuam disponíveis por URL direto.
### Estrutura de código
Foram extraídos módulos para `app/admin_ui/`:
- `styles.py` — CSS da UI admin.
- `navigation.py` — modelo e renderização da navegação.
- `layout.py` — shell HTML comum.
- `components.py` — componentes reutilizáveis, incluindo KPI cards.
`app/admin_dashboard.py` mantém as rotas públicas, mas perde CSS/layout/nav embutidos e fica significativamente menor.
### Oportunidades
Os cards de oportunidade com tarefas pendentes passam a expor a intenção operacional:
- **Concluir tarefa pendente** quando existe trabalho humano aberto.
- **Ver oportunidade** quando não há tarefas pendentes.
Isto aproxima a board do conceito de centro de trabalho.
### Sem migração
A v4.7 não requer migração de base de dados.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```
## Rollback
Como não há migração de DB, o rollback é reposição do diretório anterior e restart do serviço.
```bash
sudo systemctl restart clientflow-api
```
## Atualização v4.7.2 — Rotas por domínio
A v4.7.2 move o registo das rotas administrativas para módulos por domínio em `app/admin_ui/pages/`, mantendo os URLs existentes e sem alterar regras de negócio.
Módulos principais:
- `dashboard.py` — página inicial.
- `operations.py` — centro de trabalho.
- `opportunities.py` — oportunidades, documentos comerciais e ações comerciais.
- `tasks.py` — listagem, detalhe e ações de tasks.
- `customers.py`, `products.py`, `orders.py`, `finance.py`, `integrations.py`.
- `communications.py`, `outbox.py`, `events.py`, `runs.py`, `queues.py`, `system.py` para áreas técnicas/admin.
Esta alteração não requer migração de base de dados.

View File

@@ -0,0 +1,43 @@
# ClientFlow v4.8.1 — Admin Menu Collapsed Default
Hotfix visual em cima da v4.8.0.
## Objetivo
O submenu **Admin** deixa de aparecer expandido por defeito nas páginas operacionais.
Na navegação principal, o operador vê apenas:
- Dashboard
- Centro de trabalho
- Oportunidades
- Clientes
- Produtos
- Encomendas
- Financeiro
- Integrações
- Admin
O submenu Admin abre apenas quando:
1. o utilizador clica em Admin; ou
2. a página atual pertence à área Admin, por exemplo `/outbox`, `/tasks`, `/communications`, `/system/health`.
## Impacto
Não altera:
- base de dados
- Chatwoot
- auto-complete
- outbox worker
- regras de oportunidade
- regras LLM
- integrações
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```

View File

@@ -0,0 +1,109 @@
# ClientFlow v4.8.0 — Operational Automation, Audit & Recovery
Esta versão avança de uma UI HTMX estável para segurança operacional: recuperação de outbox presa, auditoria de ações manuais e confirmações para ações sensíveis.
## Objetivo
- Recuperar ou expor itens de outbox presos em `processing`.
- Registar ações críticas do operador como eventos auditáveis.
- Evitar cliques acidentais em ações sensíveis.
- Melhorar `/system/health` e `/operations` com métricas operacionais reais.
## Outbox stale recovery
Novas funções:
- `outbox_stale_minutes()`
- `recover_stale_processing_outbox()`
Novos ENV:
```env
OUTBOX_STALE_PROCESSING_MINUTES=30
OUTBOX_STALE_RECOVERY_MODE=manual_only
OUTBOX_RECOVER_STALE_BEFORE_PROCESS=true
```
Modos disponíveis:
- `manual_only`: marca `processing` antigo como `stale` para decisão humana.
- `mark_failed`: marca como `failed` com erro explicativo.
- `retry_pending`: devolve para `pending` e incrementa retry.
O worker `scripts/process_outbox.py` chama a recuperação antes de fazer claim de novos itens, por defeito em modo `manual_only`.
Também foi criado:
```bash
python scripts/recover_stale_outbox.py
```
## Auditoria operacional
Novo módulo:
```text
app/operator_audit_service.py
```
Regista eventos `operator_action` em `business_events` sem criar nova tabela. Quando existe `task_id`, também espelha em `task_events`.
Ações auditadas:
- concluir task manualmente
- concluir task com nota
- ignorar task
- reclassificar task
- reprocessar outbox
- marcar outbox como sent/failed/ignored
- recuperação de outbox stale
## Confirmações para ações sensíveis
Foram adicionados `hx-confirm` nas ações HTMX sensíveis:
- concluir task
- ignorar task
- reclassificar task
- reprocessar outbox
- ignorar outbox
- marcar outbox como failed
## Saúde operacional
`/system/health` passa a destacar:
- tasks pendentes
- tasks concluídas 24h
- tasks auto-completed 24h
- tasks com associação ambígua
- outbox processing
- outbox stale
- outbox failed/blocked/stale
- ações do operador nas últimas 24h
- configuração de stale recovery
## Operations
O Centro de trabalho ganha filtros de foco operacional:
- A fazer
- Bloqueadas
- Ambíguas
- Atrasadas
- Vendas
- Financeiro
- Logística
- Revisão
- Concluídas hoje
## Base de dados
Não há nova tabela obrigatória. A auditoria usa `business_events` e `task_events`, já existentes.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```

View File

@@ -0,0 +1,76 @@
# ClientFlow v4.8.2 — Production Stabilization & Guided Operations
Esta versão fecha o ciclo de atualização com melhorias pequenas e focadas na operação diária. Não adiciona novas integrações, não muda regras LLM, não altera auto-complete e não requer migração de base de dados.
## Objetivo
Reduzir erros reais do operador:
- cliente fiscal errado;
- documento emitido sem dados mínimos;
- envio preparado sem morada/telefone;
- outbox falhada sem explicação operacional;
- operador sem próximo passo claro.
## O que muda
### Cliente fiscal e contacto Chatwoot separados
Operations, oportunidades e detalhe de task passam a mostrar explicitamente:
- Cliente fiscal;
- Contacto Chatwoot;
- conversa/contacto de origem quando disponível.
### Prontidão antes de documento ou envio
A UI mostra uma checklist mínima:
- cliente fiscal associado;
- NIF;
- email de faturação;
- morada fiscal;
- código postal;
- localidade;
- telefone para envio quando aplicável.
Quando falta informação, a UI mostra que a ação deve aguardar correção dos dados.
### Próximo passo e bloqueios atuais
A ficha de oportunidade passa a destacar:
- próxima ação;
- bloqueios atuais;
- dados fiscais/contacto;
- prontidão para documentos;
- prontidão para envio.
### Outbox em linguagem operacional
A outbox passa a mostrar uma leitura operacional do erro, por exemplo:
> Motivo provável: Cliente fiscal sem NIF válido ou dados fiscais incompletos.
O detalhe do item continua a incluir o payload e o erro técnico para diagnóstico.
### Health para operação
`/system/health` passa a mostrar estado simples:
- OK;
- Atenção;
- Crítico.
Também mostra contadores de oportunidades sem cliente fiscal, clientes fiscais incompletos em oportunidades ativas, produtos sem código externo e último webhook Chatwoot.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```
## Sem migração
Esta versão é apenas UI/health/readability. Não requer alteração de schema.

View File

@@ -0,0 +1,85 @@
# ClientFlow v4.8.3 — Operations Card Simplification & Opportunity Guardrails
Esta versão fecha o ciclo de estabilização operacional após a v4.8.2. O foco é reduzir ruído em `/operations` e impedir que mensagens sem intenção comercial criem oportunidades falsas.
## Objetivos
- Tornar os cards de Operations mais leves para o operador.
- Nunca mostrar `contact_id` do Chatwoot como se fosse cliente fiscal.
- Mostrar bloqueios apenas quando bloqueiam a próxima ação da jornada.
- Criar oportunidades apenas quando existe intenção comercial real.
- Evitar oportunidades para bounces, spam, Mail Delivery Subsystem, unsubscribe e revisão falhada.
## Regras de criação de oportunidade
Criar oportunidade automaticamente apenas para ações comerciais claras, como:
- `SEND_QUOTE`
- `SEND_PROFORMA`
- `SEND_INVOICE`
- `CONFIRM_PAYMENT`
- `PREPARE_ORDER`
- `CREATE_SHIPMENT`
`SEND_INFO` só cria oportunidade se houver intenção comercial explícita no assunto/corpo/nota, como orçamento, cotação, preço, comprar, encomendar, pró-forma, fatura ou pagamento.
Nunca cria oportunidade para:
- `REVIEW_MANUALLY`
- `REMOVE_FROM_LIST`
- `IGNORE_SPAM`
- `NO_ACTION`
- `SUPPORT`
- `MARK_NO_INTEREST` sem oportunidade existente
- Mail Delivery Subsystem / mailer-daemon / postmaster
- returned mail / undelivered mail / delivery status notification
## Bloqueios por fase da jornada
`Cliente fiscal por associar` só aparece como bloqueio quando a próxima ação exige dados fiscais, documento, pagamento ou envio.
Não aparece como bloqueio em fases iniciais como:
- rever mensagem
- responder informação simples
- suporte simples
- marketing/remover da lista
- mensagem automática/bounce
## Cards de Operations
O card principal passa a mostrar apenas:
- título simples
- contexto curto
- próxima ação
- bloqueio atual, se existir
- botões principais
Campos técnicos foram movidos para `Ver detalhes`:
- fila
- estado
- origem
- contacto Chatwoot
- cliente fiscal quando existir ou for necessário
## Limpeza de oportunidades antigas
Foi adicionado script conservador:
```bash
python scripts/cleanup_non_commercial_opportunities.py --limit 100
```
Por defeito é dry-run. Para aplicar:
```bash
python scripts/cleanup_non_commercial_opportunities.py --apply
```
Só fecha oportunidades abertas com valor 0, sem documentos comerciais e sem envios, que parecem ter sido criadas por mensagens automáticas/bounce.
## Sem migração
Esta versão não cria tabelas nem exige migração de base de dados.

View File

@@ -0,0 +1,30 @@
# ClientFlow v4.8.4 — Fiscal Link Consistency Hotfix
## Objetivo
Corrigir inconsistências entre a ficha da task, a fila Operations e os cards de oportunidade quando a oportunidade já tem cliente fiscal associado.
## Problema corrigido
Em alguns casos a task mostrava corretamente o cliente fiscal ligado pela oportunidade, por exemplo `Elegantlegacy Lda`, mas o card em Operations continuava a mostrar `Cliente fiscal por associar`. Isto acontecia porque a fila operacional lia o cliente fiscal a partir de `tasks.customer_id`, enquanto o detalhe da task usava `opportunities.local_customer_id`.
## Alterações
- Operations passa a procurar primeiro o cliente fiscal da oportunidade (`opportunities.local_customer_id`).
- `tasks.customer_id` fica apenas como fallback quando for um UUID local válido/resolvido.
- O contact_id do Chatwoot nunca é usado como cliente fiscal.
- Operations passa a receber também NIF, email, morada, código postal e localidade do cliente fiscal.
- Bloqueios de Operations passam a mostrar dados fiscais em falta quando há cliente fiscal associado, em vez de dizer apenas “cliente fiscal por associar”.
- O detalhe da task passa a carregar morada/código postal/localidade do cliente fiscal.
- A secção “Dados em falta” da task passa a incluir falhas fiscais críticas, para não contradizer a “Prontidão fiscal da tarefa”.
## Sem migração
Esta versão não altera schema de base de dados.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```

View File

@@ -0,0 +1,74 @@
# ClientFlow v4.8.5 — Bounce Ignore & Card UX Cleanup
Objetivo: fechar o ciclo de estabilização operacional com uma correção pequena e conservadora.
## O que muda
### 1. Emails bounce/NDR não entram no fluxo operacional
Mensagens automáticas como Office 365 / Exchange NDR, Mail Delivery Subsystem, postmaster e mailer-daemon deixam de criar trabalho no ClientFlow.
Exemplos tratados como bounce/NDR:
- `Undeliverable:`
- `Your message couldn't be delivered`
- `Recipient wasn't found`
- `Unknown To address`
- `Delivery Status Notification`
- `Non-Delivery Report`
- `Remote Server returned`
- `550 5.1.1`
- `5.1.10`
Comportamento:
- não cria task;
- não cria oportunidade;
- não cria cliente fiscal;
- não aparece em Operations;
- não exige cliente fiscal.
O email continua disponível na inbox original, como Chatwoot, Thunderbird ou servidor de email.
### 2. Cards de Operations mais leves
O card principal passa a priorizar informação útil para decidir:
- identidade legível do contacto/cliente;
- contexto curto;
- próxima ação;
- bloqueio atual apenas quando existe;
- botões principais.
IDs técnicos do Chatwoot deixam de ser título principal. Quando não existe nome/email/telefone útil, o card mostra `Contacto sem identificação`, e não `Contacto Chatwoot #123`.
O bloco `Ver detalhes` só aparece quando houver informação realmente útil, como cliente fiscal, oportunidade ligada, bloqueio ou contexto de integração.
### 3. Cards de oportunidades mais simples
O card da oportunidade deixa de mostrar informação de baixo valor no primeiro nível, como valor `0,00 €`, data/hora completa e contadores técnicos.
O CTA passa a ser específico sempre que possível:
- `Preparar resposta`
- `Emitir pró-forma`
- `Emitir fatura`
- `Confirmar pagamento`
- `Ver tarefa pendente`
- `Ver oportunidade`
## O que não muda
- Sem migração de base de dados.
- Sem nova integração.
- Sem nova lógica LLM.
- Sem alteração ao outbox worker.
- Sem alteração ao auto-complete Chatwoot.
- Sem alteração às regras documentais/fiscais já existentes.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```

View File

@@ -0,0 +1,35 @@
# ClientFlow v4.8.6 — Opportunity Board Layout Cleanup
Esta versão corrige a legibilidade do quadro de oportunidades.
## Objetivo
Evitar sobreposição de chips, botões e texto nos cards de oportunidades, especialmente em ecrãs com menos largura útil ou zoom diferente de 100%.
## Alterações
- O quadro de oportunidades passou a usar colunas com largura mínima.
- Quando não há espaço suficiente, o quadro usa scroll horizontal em vez de comprimir os cards.
- Os cards deixam de repetir o estado da etapa dentro do próprio card.
- A informação visível no card fica limitada a:
- identificação do cliente/contacto;
- assunto;
- próxima ação;
- bloqueio relevante, se existir;
- botão principal.
- Os chips redundantes de estado/prioridade deixam de ocupar o topo do card.
## Sem alteração funcional
- Não altera regras de negócio.
- Não altera Chatwoot.
- Não altera criação de oportunidades.
- Não altera auto-complete.
- Não altera base de dados.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```

View File

@@ -0,0 +1,28 @@
# ClientFlow v4.8.7 — Operations Workbench Layout
## Objetivo
Limpar o Centro de trabalho para uso diário: a página passa a funcionar como uma fila compacta de ações com painel lateral de contexto.
## O que mudou
- `/operations` deixa de apresentar uma sequência de cards grandes como layout principal.
- A fila à esquerda mostra apenas identidade, contexto curto, próxima ação e bloqueio real.
- O painel lateral mostra a mensagem, cliente/contacto, bloqueios e botões de ação.
- A seleção de uma ação usa HTMX em `/operations/partials/item-detail`.
- Informação técnica fica recolhida em "Detalhes técnicos".
## O que não mudou
- Não requer migração de base de dados.
- Não altera regras de negócio.
- Não altera auto-complete do Chatwoot.
- Não altera criação de oportunidades.
- Não altera o worker da outbox.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```

View File

@@ -0,0 +1,39 @@
# ClientFlow v4.8.8 — Operations Classic Cards
Esta versão ajusta o `Centro de trabalho` depois do teste da v4.8.7.
A v4.8.7 introduziu uma fila compacta com painel lateral fixo. A experiência ficou menos familiar e menos apelativa para uso diário. A v4.8.8 volta a uma apresentação mais parecida com a versão anterior: lista vertical de cards, simples e direta.
## O que muda
- O Centro de trabalho volta a usar cards verticais.
- Remove o painel lateral fixo da v4.8.7; volta a uma experiência sem painel lateral fixo.
- Mantém os cards mais limpos das versões recentes.
- Mantém `Ver detalhes` apenas quando há informação útil.
- Mantém filtros HTMX em `/operations/partials/work-items`.
## O que fica fora do primeiro nível
- IDs técnicos do Chatwoot como título principal.
- Estado técnico repetido.
- Origem Chatwoot repetida.
- Cliente fiscal quando ainda não é necessário para a ação.
- Bloqueios que não impedem a próxima ação.
## O que continua visível
Cada card deve mostrar rapidamente:
- Quem é o contacto ou cliente.
- Qual é o contexto curto.
- Qual é a próxima ação.
- Qual é o bloqueio real, se existir.
- Qual é o botão principal.
## Compatibilidade
Não requer migração de base de dados.
Não altera regras de negócio.
Não altera Chatwoot auto-complete.
Não altera outbox worker.
Não altera criação de oportunidades.

View File

@@ -0,0 +1,35 @@
# ClientFlow v4.8.9 — Operations Card Polish
Esta versão é um hotfix de UX sobre a v4.8.8.
## Objetivo
Manter o Centro de trabalho no layout clássico de cards, mas remover o ruído visual do primeiro nível.
## Alterações
- Remove completamente o bloco **Ver detalhes** dos cards de Operations.
- Mantém informação técnica em páginas próprias, como task, oportunidade e admin.
- Reforça a hierarquia de ações: um botão principal e botões secundários menos fortes.
- Melhora espaçamento, separadores internos e alinhamento dos cards.
## O que não muda
- Não requer migração de base de dados.
- Não altera Chatwoot.
- Não altera auto-complete.
- Não altera outbox worker.
- Não altera criação de oportunidades.
- Não altera regras de negócio.
## Regra de UI
O card de Operations deve mostrar apenas:
1. Quem é.
2. O que aconteceu.
3. Qual é a próxima ação.
4. Bloqueio real, se existir.
5. Botão principal.
Os detalhes técnicos devem ficar fora da fila operacional.

View File

@@ -0,0 +1,48 @@
# ClientFlow v4.9.0 — Operations Noise Cleanup
Esta versão fecha a higiene da fila do **Centro de trabalho**. O objetivo é retirar da fila diária mensagens que pertencem à inbox/email, mas não ao fluxo operacional do ClientFlow.
## O que muda
- Mensagens de `postmaster`, `mailer-daemon`, `Mail Delivery Subsystem`, `Mail Delivery System`, Office 365 / Exchange NDR, `Undeliverable`, `Returned mail`, `Delivery Status Notification`, `Unknown To address` e erros semelhantes deixam de aparecer no Centro de trabalho.
- Tasks novas de `REVIEW_MANUALLY`, `REMOVE_FROM_LIST`, `MARK_NO_INTEREST`, `IGNORE_SPAM`, `NO_ACTION` e `IGNORE_BOUNCE` não entram como prioridade alta por defeito.
- A contagem visível de “A fazer agora” passa a refletir melhor o que aparece realmente na fila, em vez de contar lixo antigo ainda pendente.
- Foi adicionado um detector partilhado em `app/operation_noise.py` para manter as regras de ruído consistentes entre triagem, Operations e limpeza.
## Limpeza de dados antigos
A versão inclui o script:
```bash
python scripts/cleanup_operations_noise.py --limit 200
```
Por defeito é dry-run. Para aplicar:
```bash
python scripts/cleanup_operations_noise.py --apply
```
O script marca tasks pendentes claramente técnicas/sistema/bounce como `skipped`. Não apaga mensagens, eventos brutos nem conversas Chatwoot.
## Depois da limpeza
É normal a fila cair bastante, por exemplo de dezenas de itens para apenas trabalho real:
- faturas a emitir;
- orçamentos a preparar;
- respostas comerciais reais;
- bloqueios de outbox/documentos;
- revisões com identidade/contexto útil.
## Sem migração
Não requer migração de base de dados.
## O que não muda
- Não altera Chatwoot.
- Não altera auto-complete.
- Não altera outbox worker.
- Não altera regras de criação de oportunidades comerciais reais.
- Não adiciona nova integração ou LLM.

View File

@@ -0,0 +1,46 @@
# ClientFlow v4.9.12 — Fiscal Customer Reconciliation & Odoo Fulfilment
## Objetivo
A reconciliação passa a reconstruir melhor o processo operacional quando a evidência vem do Odoo.
Regra base:
- ClientFlow: o **Cliente fiscal** é a referência central da oportunidade.
- Jasmin: procurar documentos por **NIF**.
- Odoo: quando não há NIF/VAT fiável, procurar por **nome fiscal normalizado** e, depois de confirmado, guardar mapeamento `odoo partner_id -> cliente fiscal ClientFlow`.
## O que muda
- O sync Odoo importa linhas da venda, produtos, quantidades entregues/faturadas.
- O sync Odoo procura entregas/stock pickings associadas à venda.
- A timeline candidata pode mostrar:
- venda Odoo encontrada;
- linhas/produtos importados;
- entrega Odoo concluída;
- fatura por emitir.
- Ao ligar um processo à oportunidade, são gravados:
- `operation_links` para venda Odoo;
- `operation_links` para estado físico Odoo;
- eventos de timeline reconstruída.
- Itens ignorados automaticamente pela limpeza de janela voltam a abrir quando regressam à janela sincronizada.
## Segurança
Esta versão não confirma pagamentos, não emite faturas, não fecha oportunidades e não altera Odoo/Jasmin/Packlink. Apenas lê evidência, sugere ligação e reconstrói timeline depois de confirmação do operador.
## Exemplo Elegantlegacy
Odoo:
- venda `S00274`;
- cliente `ELEGANTLEGACY, LDA`;
- entrega `WH/OUT/00295` concluída;
- `invoice_status = to invoice`.
ClientFlow deve sugerir:
- ligar à oportunidade `Elegantlegacy Lda`;
- estado reconstruído: venda/entrega Odoo encontrada;
- próxima ação: emitir fatura.

View File

@@ -0,0 +1,77 @@
# ClientFlow v4.9.13 — Apply Reconstructed Process to Opportunity Pipeline
## Objetivo
A Reconciliação já conseguia reconstruir um processo a partir de evidências externas, por exemplo:
- venda Odoo `S00274`;
- linhas/produtos da venda;
- produções Odoo concluídas;
- delivery `WH/OUT/00295` concluído;
- `invoice_status = to invoice`.
A v4.9.13 fecha a etapa seguinte: quando o operador liga o processo reconstruído a uma oportunidade, o ClientFlow deixa de criar apenas uma ligação solta e passa a aplicar o estado reconstruído à oportunidade.
## O que muda
Ao confirmar **Ligar processo à oportunidade**, o sistema passa a:
1. ligar os reconciliation items à oportunidade;
2. criar eventos na timeline da oportunidade;
3. criar/atualizar links operacionais Odoo;
4. importar linhas Odoo para os produtos da oportunidade;
5. atualizar o valor da oportunidade se ainda estiver a zero;
6. atualizar a fase visual para o estado reconstruído mais avançado;
7. garantir task `SEND_INVOICE` quando a venda/entrega está feita e a fatura está por emitir.
## Caso Elegantlegacy
Para a venda Odoo `S00274`, o resultado esperado depois de ligar à oportunidade é:
- Valor: `537,00 €`;
- Produtos:
- Wallbox 7.4KW;
- Wallbox 11KW;
- Wallbox 22KW;
- Odoo:
- venda `S00274` criada;
- produção concluída;
- estado físico `shipped`;
- validação física/entrega concluída;
- Timeline:
- venda/encomenda encontrada no Odoo;
- linhas/produtos importados;
- entrega Odoo concluída;
- fatura por emitir;
- Próxima ação: `SEND_INVOICE`.
A linha de transporte Odoo com valor zero fica no payload operacional, mas não é importada como linha comercial da oportunidade.
## Janela da Reconciliação
A página `/reconciliation` passa a aceitar janela via query string:
```text
/reconciliation?days=7
/reconciliation?days=30
```
A UI mostra botões rápidos:
- Hoje;
- 3 dias;
- 7 dias;
- 30 dias.
As ações de sincronização e limpeza respeitam a janela ativa.
## Segurança
Esta versão continua conservadora:
- não confirma pagamentos automaticamente;
- não emite faturas automaticamente;
- não fecha oportunidades;
- não altera Odoo/Jasmin/Packlink;
- apenas aplica evidências externas depois da confirmação do operador.

View File

@@ -0,0 +1,38 @@
# ClientFlow v4.9.17 — Reconciliation Cross-Source Grouping
Esta versão corrige um problema na Reconciliação em que evidências do mesmo processo apareciam separadas quando cada sistema fornecia identificadores diferentes.
## Problema
Exemplo real:
- Jasmin tem orçamento para `ACZCO BRAGA ENERGY, LDA` com NIF `517249200`.
- Odoo tem venda `S00279` para `ACZCO BRAGA ENERGY, LDA`, mas sem NIF.
Antes, a Reconciliação podia mostrar o orçamento como processo candidato com `1 evidência` e a venda Odoo apenas como sugestão/elemento separado.
## Correção
A construção de processos candidatos passa a usar todos os sinais de identidade disponíveis no item:
- NIF
- email
- nome fiscal normalizado
Se uma evidência Jasmin tem NIF + nome e uma evidência Odoo tem o mesmo nome, ambas são agrupadas no mesmo processo candidato. O NIF continua a ser a chave de maior confiança do grupo.
## Resultado esperado
O caso deve aparecer como um único processo:
- Orçamento Jasmin encontrado
- Venda/encomenda Odoo encontrada
- Linhas/produtos Odoo importados
- Entrega Odoo concluída, se existir
- Fatura por emitir, se `invoice_status = to invoice`
A ação sugerida deve ser coerente com o estado mais avançado: por exemplo, `SEND_INVOICE` quando existe venda/entrega Odoo por faturar.
## Segurança
A versão não liga automaticamente nada. Continua a ser necessário o operador confirmar a ligação ou reconstrução.

View File

@@ -0,0 +1,124 @@
# ClientFlow v4.9.1 — Operational Reconciliation & External Intake
Esta versão adiciona uma área de **Reconciliação** para organizar informação que existe fora do ClientFlow antes de a transformar em processo comercial.
## Objetivo
O ClientFlow passa a preparar informação solta de:
- documentos Jasmin criados fora do ClientFlow;
- faturas/orçamentos sem oportunidade;
- comprovativos de pagamento recebidos manualmente;
- pedidos vindos de WhatsApp, telefone, email direto ou presencial;
- futuras vendas Odoo sem oportunidade.
A regra principal é:
```text
Sincronizar/detetar automaticamente.
Criar candidato de reconciliação.
Operador confirma ligar, criar oportunidade ou ignorar.
```
## O que mudou
### Nova página
```text
/reconciliation
/reconciliacao
```
A página mostra:
- itens abertos de reconciliação;
- documentos sem ligação;
- comprovativos por associar;
- ações para criar oportunidade, ligar oportunidade existente ou ignorar.
### Novas tabelas aditivas
A versão cria schema adicional no arranque:
```text
reconciliation_items
payment_proofs
```
É uma alteração aditiva: não remove nem altera dados existentes.
### Entrada manual externa
A página permite registar pedidos vindos de canais fora do Chatwoot:
```text
WhatsApp
Telefone
Email direto
Presencial
Outro
```
Ao registar, o sistema cria:
- oportunidade;
- task com a próxima ação;
- evento de timeline.
Não cria IDs falsos de Chatwoot.
### Comprovativos de pagamento
Um comprovativo pode ser guardado e ligado a uma oportunidade.
Importante:
```text
Comprovativo recebido não confirma pagamento.
```
Quando está ligado a uma oportunidade, cria task `CONFIRM_PAYMENT` para validação humana.
### Sincronização local
Script novo:
```bash
PYTHONPATH=. python scripts/sync_reconciliation_candidates.py
```
Ele procura documentos Jasmin locais em `commercial_documents` sem `opportunity_id` e cria candidatos de reconciliação.
## Fluxo operacional recomendado
```text
Documento/Comprovativo/Pedido externo
↓
Reconciliação
↓
Operador decide:
- ligar a oportunidade existente
- criar oportunidade
- ignorar
↓
ClientFlow cria/atualiza oportunidade e task
```
## O que esta versão não faz
- Não confirma pagamentos automaticamente.
- Não emite faturas automaticamente.
- Não apaga nem substitui documentos Jasmin.
- Não fecha oportunidades sozinha.
- Não usa LLM para reorganizar sem confirmação humana.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```
## Rollback
O código pode ser revertido normalmente. As tabelas novas são aditivas e podem ficar sem afetar o fluxo antigo.

View File

@@ -0,0 +1,81 @@
# ClientFlow v4.9.20 — Multi-Purchase Reconciliation
## Objetivo
A reconciliação deixa de assumir que `um cliente fiscal = um processo`.
A regra passa a ser:
- **Cliente fiscal** identifica quem é a entidade: NIF, nome fiscal normalizado, email ou mapeamento externo confirmado.
- **Compra/processo** identifica o ciclo comercial específico: orçamento, venda, pró-forma, fatura, comprovativo, entrega ou histórico associado a essa compra.
- **Oportunidade** representa uma compra/processo concreto, não todo o histórico do cliente.
## Problema corrigido
Clientes com documentos soltos e várias compras podiam ser agrupados numa única timeline apenas porque partilhavam NIF ou nome fiscal.
Exemplo de risco:
- ACZCO BRAGA ENERGY, LDA / NIF 517249200
- ORC.ORC2026.154 / 638,60 €
- S00279 / 638,60 €
- ORC.ORC2026.160 / 190,00 €
- FA2026.80 histórica
Antes, estes itens podiam aparecer como um único processo candidato.
Agora a reconciliação faz duas passagens:
1. Agrupa por cliente fiscal.
2. Divide o cliente em compras/processos separados.
## Regras de separação por compra
São âncoras de compra:
- Venda Odoo (`odoo_sale_order`)
- Orçamento Jasmin (`jasmin_quotation`)
- Pró-forma Jasmin (`jasmin_proforma`)
- Fatura Jasmin (`jasmin_invoice`)
Duas âncoras só são fundidas no mesmo processo quando há evidência forte:
- mesma referência externa; ou
- tipos documentais diferentes com valor igual/aproximado e datas próximas; ou
- ponte Jasmin ↔ Odoo próxima quando o valor Jasmin não foi importado, mantendo vendas Odoo diferentes sempre separadas.
Duas vendas Odoo diferentes nunca são fundidas automaticamente.
Duas cotações Jasmin diferentes nunca são fundidas automaticamente só por terem o mesmo cliente.
## Itens soltos
Comprovativos, envios e outros itens sem âncora própria são atribuídos a uma compra apenas quando o match é inequívoco por valor/data/referência/produto.
Caso contrário, ficam como item/processo separado para revisão manual.
## UI
A página de Reconciliação passou a explicar explicitamente:
> O sistema agrupa primeiro por cliente fiscal e depois separa por compra/processo.
Nos cartões de processo, a chave passa a distinguir:
- Cliente fiscal: NIF / nome fiscal / email
- Compra/processo: S00279, ORC.ORC2026.154, FA2026.80, etc.
## Testes adicionados
- Duas vendas Odoo do mesmo cliente geram dois processos candidatos.
- Um orçamento Jasmin é atribuído à venda Odoo correta por valor/data.
- Duas cotações Jasmin do mesmo cliente ficam em dois processos diferentes.
- Cotação e fatura com mesmo valor e data próxima podem formar uma compra.
- Comprovativo é associado apenas quando o match com a venda é inequívoco.
## Validação
Suite completa:
```text
164 passed
```

View File

@@ -0,0 +1,52 @@
# ClientFlow v4.9.21 — Reset seguro da reconciliação gerada
## Objetivo
Permitir limpar candidatos de reconciliação gerados por sincronização externa e correr novamente o processo com o método multi-compra.
A limpeza é limitada por defeito a:
- `source_system IN ('jasmin', 'odoo', 'packlink')`
- `status IN ('open', 'needs_review', 'ignored')`
- `opportunity_id IS NULL`
Não apaga oportunidades, documentos comerciais, comprovativos de pagamento, vendas Odoo, documentos Jasmin, envios Packlink, operation links ou eventos de oportunidade.
## Comandos
Pré-visualizar:
```bash
PYTHONPATH=. python scripts/reset_reconciliation_generated.py
```
Aplicar:
```bash
PYTHONPATH=. python scripts/reset_reconciliation_generated.py --apply
```
Sincronizar novamente:
```bash
PYTHONPATH=. python scripts/sync_external_reconciliation.py --all --days 30 --limit 200
PYTHONPATH=. python scripts/sync_reconciliation_candidates.py
```
Para voltar à janela operacional curta:
```bash
PYTHONPATH=. python scripts/sync_external_reconciliation.py --all --days 3 --limit 100
```
## Backup
Ao aplicar, o script cria antes uma tabela de backup:
```text
reconciliation_items_reset_backup_YYYYMMDD_HHMMSS
```
## Nota
Se já existirem oportunidades ou ligações erradas criadas pela reconciliação antiga, elas não são removidas por este script. Devem ser revistas em separado, porque já podem ter eventos, tarefas e operação associada.

View File

@@ -0,0 +1,38 @@
# ClientFlow v4.9.22 — Rebuild Reconciliação pela UI
A página de Reconciliação passa a ter uma ação operacional para reconstruir a janela ativa.
## Botão
`Apagar e correr novamente`
O botão executa:
1. Apaga apenas candidatos gerados por `jasmin`, `odoo` e `packlink`.
2. Limita aos estados `open`, `needs_review` e `ignored`.
3. Protege sempre `opportunity_id IS NULL`.
4. Cria uma tabela de backup antes do delete.
5. Volta a sincronizar APIs externas para a mesma janela ativa.
## Segurança
Não apaga:
- clientes
- oportunidades
- documentos reais Jasmin/Odoo/Packlink
- itens já ligados a oportunidades
- comprovativos manuais
- tarefas ou operações
## Endpoint
`POST /reconciliation/rebuild`
Recebe `days` pela UI e mantém a mesma janela ativa: 1, 3, 7 ou 30 dias.
## Função de serviço
`reset_generated_reconciliation_items(...)`
É a versão reutilizável e segura do script `scripts/reset_reconciliation_generated.py`.

View File

@@ -0,0 +1,32 @@
# ClientFlow v4.9.24 — Reconciliation Process Review
Esta versão evolui a Reconciliação para uma área de revisão de processos, não apenas de matching de documentos.
## Alterações principais
- Cartões de processo candidato passam a mostrar **Motivos** e **Riscos**.
- Cada candidato recebe um estado visual de revisão: `pronto`, `rever` ou `conflito`.
- A UI permite marcar processos/itens como `needs_review` ou `historical`.
- A tabela `reconciliation_decisions` guarda decisões do operador para auditoria e aprendizagem futura.
- O resumo da página passa a contar `needs_review`, `conflict` e `historical`.
## Regras preservadas
- A sincronização externa continua a não criar oportunidades automaticamente.
- O botão de reconstrução continua a apagar apenas candidatos gerados sem oportunidade ligada.
- Cliente fiscal continua separado de processo/compra.
- Oportunidades continuam a ser criadas/ligadas apenas por ação explícita do operador.
## Objetivo operacional
Cada cartão deve responder rapidamente:
1. Quem é o cliente fiscal?
2. Que compra/processo é este?
3. Porque é que o sistema agrupou estes documentos?
4. Que riscos existem antes de aplicar?
5. Qual a ação certa: ligar, criar, rever ou arquivar como histórico?
## Validação
Suite completa: `173 passed`.

View File

@@ -0,0 +1,98 @@
# ClientFlow v4.9.25 — Fiscal Enrichment Worker
Esta versão adiciona uma camada autónoma antes da reconciliação:
```text
Oportunidades abertas sem cliente fiscal
→ enriquecimento fiscal por NIF/email/domínio/nome
→ sugestão ou auto-associação segura
→ sincronização Jasmin/Odoo
→ reconciliação por cliente fiscal + processo de compra
```
## Objetivo
Melhorar a ligação entre oportunidades internas, Jasmin e Odoo evitando que a
reconciliação dependa de nomes de contacto ou texto livre.
## Configuração
Adicionar ao `.env` quando a API externa estiver disponível:
```env
EXTERNAL_COMPANY_LOOKUP_ENABLED=true
EXTERNAL_COMPANY_LOOKUP_BASE_URL=http://127.0.0.1:8000
EXTERNAL_COMPANY_LOOKUP_API_KEY=...
EXTERNAL_COMPANY_LOOKUP_TIMEOUT=10
EXTERNAL_COMPANY_LOOKUP_AUTO_THRESHOLD=95
```
## Novas tabelas
```text
external_company_cache
fiscal_customer_suggestions
fiscal_enrichment_runs
```
A cache evita chamadas repetidas à API externa. As sugestões guardam a decisão
pendente/aceite/rejeitada. Os runs permitem auditoria do worker periódico.
## Worker
```bash
python scripts/enrich_fiscal_customers.py --incremental --limit 100
```
Para apenas criar sugestões sem auto-associação:
```bash
python scripts/enrich_fiscal_customers.py --incremental --no-auto-apply
```
## Pipeline completo
```bash
python scripts/run_reconciliation_pipeline.py --days 7 --limit 100
```
Ordem:
```text
1. enriquecer oportunidades sem cliente fiscal
2. seed de clientes fiscais Jasmin/Odoo
3. sincronizar documentos/vendas/envios
4. mostrar processos candidatos na reconciliação
```
## UI
Na página de Reconciliação foi adicionado:
```text
[Enriquecer oportunidades]
```
Na ficha de oportunidade foi adicionado:
```text
Sugestões fiscais
[Enriquecer cliente fiscal]
[Associar]
[Rejeitar]
```
## Regra de auto-associação
A auto-associação só acontece se:
```text
- oportunidade ainda não tem cliente fiscal
- API devolve NIF + nome fiscal
- confiança >= threshold
- match_type forte: nif_exato, contacto_email_exato, email_principal_exato,
email_exato_empresa_inferida, email_principal_dominio, website_dominio
- não existe conflito com cliente fiscal já associado
```
Outros casos ficam como sugestão para operador.

View File

@@ -0,0 +1,21 @@
# ClientFlow v4926.6 — Jasmin quotation to opportunity details
Esta versão faz com que oportunidades criadas/ligadas a partir de documentos Jasmin deixem de ficar vazias.
## Incluído
- Importa documentos Jasmin de reconciliação para `commercial_documents`.
- Recria linhas em `commercial_document_lines`.
- Copia linhas comerciais para `opportunity_items` quando o payload Jasmin contém linhas.
- Atualiza `opportunities.value_amount` a partir do total Jasmin quando ainda está vazio/zero.
- Atualiza `product_interest` com resumo das linhas.
- Aplica o mesmo comportamento em:
- criar oportunidade a partir de item único de reconciliação;
- criar oportunidade a partir de processo/candidato agrupado;
- ligar processo de reconciliação a oportunidade existente.
- Mantém a integração conservadora: documentos soltos continuam a exigir ação do operador.
## Testes
- `compile_ok`
- `7 passed` em testes estáticos v4926.5/v4926.6.

View File

@@ -0,0 +1,30 @@
# ClientFlow v4.9.26.7.1 — stale email identity review cleanup
## Context
The SEMENTENERGIAS opportunity had been corrected at fiscal-customer level, but the technical fiscal-association panel still displayed a stale email identity extraction where `pt` was treated as a company mention and an old PLANETOPTION suggestion was shown.
## Changes
- Applies current company-mention guardrails when reading already-stored `email_identity_extractions`.
- Filters invalid company mentions such as `pt`, `com`, `www`, `http`, domains and TLDs even if they were saved before the guardrail existed.
- Prevents conflict calculation when there is no valid explicit company mention.
- Prevents internal customer suggestion display when the only mention is invalid/stale.
- Fixes identity confidence display: `0.95` is now shown as `95%`, not `1%`.
- Hides stale accepted suggestions that duplicate the currently linked fiscal customer without NIF.
- Improves `scripts/cleanup_invalid_email_identity_suggestions.py`:
- no longer writes to non-existent `resolution_note` column;
- can reject invalid suggestions;
- can rewrite stored email identity extractions with current filters.
## Recommended cleanup for affected opportunity
```bash
PYTHONPATH=. python scripts/cleanup_invalid_email_identity_suggestions.py \
--opportunity-id <OPPORTUNITY_ID> \
--include-accepted \
--fix-extractions \
--apply
```
Then restart the service and reload the opportunity detail.

View File

@@ -0,0 +1,17 @@
# ClientFlow v4926.7 — fiscal identity guardrails + duplicate NIF handling
## Fixes
- Rejects invalid company mentions extracted from email identity, including bare TLD/domain fragments such as `pt`, `com`, `www`, and full domains like `sementenergias.pt`.
- Prevents short fragments such as `pt` from matching inside company names like `PLANETOPTION, LDA`.
- Reduces false fiscal conflicts caused by invalid email identity mentions.
- Adds friendly duplicate-NIF handling when editing customers. Editing a customer with a NIF that already belongs to another customer now returns a business conflict instead of a raw PostgreSQL unique violation.
## Scripts
- `scripts/cleanup_invalid_email_identity_suggestions.py` rejects pending/accepted email-identity suggestions created from invalid lookup values such as `pt`.
- `scripts/inspect_customer_tax_id_conflict.py` lists customers and linked counts for a duplicate NIF conflict.
## Operator notes
For the SEMENTENERGIAS case, refresh email identity extraction after installing and reject old invalid suggestions created from `pt`.

View File

@@ -0,0 +1,40 @@
# ClientFlow v4926.8.1 — Jasmin candidate association + fiscal suggestion cleanup
## Objetivo
Corrige dois problemas observados no detalhe de oportunidade:
1. Quando já existe um orçamento Jasmin para o cliente, a oportunidade mostrava "Ainda sem documentos Jasmin" e não dava uma forma clara de associar o orçamento existente antes de criar novo.
2. Sugestões fiscais antigas `accepted` podiam continuar visíveis com `NIF —`, mesmo quando o cliente fiscal atual já tinha NIF correto.
## Alterações
### Documentos Jasmin
- O painel `Documentos Jasmin` passa a procurar documentos Jasmin compatíveis em `reconciliation_items` por:
- `customer_id` interno;
- NIF fiscal;
- NIF dentro do payload Jasmin;
- email;
- domínio no payload;
- nome fiscal exato.
- Se encontrar candidatos ainda não importados, mostra aviso: associar documento existente antes de criar novo.
- Novo botão por candidato: `Associar e importar`.
- Novo endpoint:
- `POST /opportunities/{opportunity_id}/jasmin/link-candidate/{item_id}`
- A ação liga o `reconciliation_item` à oportunidade e reaproveita o backfill Jasmin para importar:
- `commercial_documents`;
- `commercial_document_lines`;
- `opportunity_items`;
- `value_amount`;
- `product_interest`.
### Sugestões fiscais
- `list_fiscal_suggestions_for_opportunity` passa a fazer `LEFT JOIN customers` para preencher `suggested_nif`/`suggested_name` com o cliente interno quando a sugestão antiga tem NIF vazio.
- A UI deixa de mostrar sugestões `accepted` que apenas confirmam o cliente fiscal já ligado à oportunidade.
- Isto remove ruído como `SEMENTENERGIAS accepted NIF —` quando a ficha fiscal atual já mostra `NIF 516356216`.
## Validação
- `python -m compileall -q app scripts`

View File

@@ -0,0 +1,52 @@
# ClientFlow v4926.8.2
## Objetivo
Evitar que uma oportunidade associe/importa um orçamento Jasmin antigo, fechado, convertido ou anulado quando existe um orçamento/proforma aberto mais recente para o mesmo cliente fiscal.
## Alterações
- Classificação de ciclo de vida dos documentos Jasmin a partir de `payload.record`:
- `documentStatusDescription`
- `documentStatus`
- `documentLineStatusDescription`
- `statusWasCompleted`
- `isDeleted`
- `isDraft`
- Candidatos Jasmin agora são ordenados por:
1. aberto/válido primeiro;
2. quotation/proforma antes de invoice;
3. data/série mais recente;
4. score de match fiscal.
- Documentos fechados/convertidos/anulados aparecem apenas como auditoria e o botão fica desativado.
- `link_and_import_jasmin_candidate_async` rejeita importação de candidato não aberto/válido.
- Novo botão na UI: **Sincronizar Jasmin** para buscar documentos recentes antes de escolher candidato.
- Novo botão na UI: **Substituir atual**, para trocar um orçamento/proforma importado por engano por um candidato aberto mais recente.
- Novo script de inspeção:
- `scripts/inspect_jasmin_candidates_for_opportunity.py`
- Novo script de reparação:
- `scripts/replace_jasmin_document_for_opportunity.py`
## Operação segura
Para diagnosticar:
```bash
PYTHONPATH=. python scripts/inspect_jasmin_candidates_for_opportunity.py \
--opportunity-id <OPPORTUNITY_ID> \
--sync \
--days 30
```
Para substituir por um candidato válido:
```bash
PYTHONPATH=. python scripts/replace_jasmin_document_for_opportunity.py \
--opportunity-id <OPPORTUNITY_ID> \
--item-id <RECONCILIATION_ITEM_ID> \
--dry-run
PYTHONPATH=. python scripts/replace_jasmin_document_for_opportunity.py \
--opportunity-id <OPPORTUNITY_ID> \
--item-id <RECONCILIATION_ITEM_ID>
```

View File

@@ -0,0 +1,44 @@
# ClientFlow v4926.8.3 — Jasmin current document consistency
## Objetivo
Corrige inconsistências observadas em oportunidades que já têm orçamento Jasmin importado:
- não mostrar orçamentos antigos ainda abertos como candidatos principais quando já existe um orçamento Jasmin mais recente associado;
- não sugerir `Criar orçamento Jasmin` no cockpit quando a oportunidade já tem `commercial_documents` Jasmin;
- reduzir risco de duplicar orçamentos por engano.
## Regras novas
### Current document wins
Se a oportunidade já tem um documento Jasmin `quotation` ou `proforma`, candidatos encontrados em `reconciliation_items` com data/série menor ou igual ao documento atual são ocultados por defeito.
Candidatos mais recentes continuam a aparecer como substituição possível.
### Workflow awareness
O plano de ação passa a considerar também `commercial_documents`, não apenas `operation_links`:
- `commercial_documents.system='jasmin' AND document_kind='quotation'` conta como orçamento existente;
- `document_kind='proforma'` conta como pró-forma;
- `document_kind='invoice'` conta como fatura.
Assim o cockpit deixa de mostrar `Depois: Criar orçamento Jasmin` quando já existe orçamento importado.
### Botão criar orçamento
Quando já existe documento Jasmin, o botão principal deixa de ser `Criar orçamento` e passa para uma ação secundária:
`Novo orçamento adicional`
com confirmação explícita.
## Validação recomendada
Na oportunidade SEMENTENERGIAS:
- ORC.ORC2026.158 deve continuar importado;
- ORC.ORC2026.137 não deve aparecer como candidato acionável por defeito;
- cockpit deve deixar de mostrar `Depois: Criar orçamento Jasmin`;
- botão de orçamento deve ser secundário e com confirmação se já existir documento.

View File

@@ -0,0 +1,25 @@
# ClientFlow v4926.8.4 — Opportunity evidence consistency polish
## Objetivo
Reduzir pequenas incoerências visuais no detalhe de oportunidade quando já existe documento Jasmin importado, produtos e tarefa de pagamento.
## Alterações
- Mostra aviso suave quando existe tarefa de confirmar pagamento, mas a evidência documental atual ainda é apenas orçamento Jasmin.
- Mantém o processo desbloqueado; o aviso é informativo e não impede ação do operador.
- Quando não existem mensagens indexadas mas a oportunidade tem `conversation_id`, a UI mostra que a conversa Chatwoot existe mas ainda não está ligada localmente, em vez de dizer simplesmente “sem comunicações”.
- Quando não há eventos de timeline registados, a UI mostra eventos derivados de documentos/produtos atuais, evitando timeline vazia em oportunidades já importadas.
- Normaliza status numérico de documentos Jasmin:
- `1` passa a aparecer como `Aberto` em vez de `1`.
- outros estados numéricos básicos ficam com labels legíveis.
## Impacto esperado
No caso SEMENTENERGIAS:
- `ORC.ORC2026.158` continua como documento atual.
- Valor/produtos mantêm-se coerentes.
- A task “Confirmar pagamento” continua válida, mas a UI avisa se ainda só existe orçamento.
- Mensagens Chatwoot deixam de parecer inexistentes quando existe conversa #498.
- Timeline deixa de ficar vazia quando há documento/produtos importados.

View File

@@ -0,0 +1,83 @@
# ClientFlow v4926.8 — UI maintenance actions
Esta versão consolida as correções v4926.6/v4926.7 e adiciona ações de manutenção diretamente na UI da oportunidade.
## Incluído
### 1. Reimportar detalhes Jasmin pela UI
No painel **Documentos Jasmin** foi adicionado o botão:
- `Reimportar detalhes`
A ação chama:
- `POST /opportunities/{opportunity_id}/jasmin/reimport-details`
E executa o mesmo motor validado pelo script de backfill:
- documentos comerciais
- linhas do documento
- produtos/linhas da oportunidade
- valor total da oportunidade
- product_interest
A importação é idempotente e atualiza linhas já existentes com preço zero.
### 2. Serviço reutilizável de backfill Jasmin
Novo módulo:
- `app/jasmin_backfill_service.py`
Função principal:
- `backfill_jasmin_opportunity_details_async(...)`
O objetivo é deixar de depender apenas do terminal para corrigir oportunidades antigas criadas a partir de reconciliação Jasmin.
### 3. Limpeza de identidade fiscal inválida pela UI
No bloco técnico de identidade extraída foi adicionado o botão:
- `Limpar identidade inválida`
A ação chama:
- `POST /opportunities/{opportunity_id}/email-identity/cleanup-invalid`
E remove/corrige estado antigo causado por tokens inválidos como:
- `pt`
- `com`
- `net`
- `www`
- domínios isolados
### 4. Serviço reutilizável de cleanup de identidade
Novo módulo:
- `app/email_identity_cleanup_service.py`
Função principal:
- `cleanup_invalid_email_identity_state(...)`
Rejeita sugestões inválidas e corrige `email_identity_extractions.company_mentions` quando já estavam guardadas antes dos filtros novos.
## Como testar
### Reimportar Jasmin pela UI
1. Abrir uma oportunidade com documento Jasmin.
2. Ir ao painel **Documentos Jasmin**.
3. Clicar **Reimportar detalhes**.
4. Confirmar que documentos, produtos e valor ficam preenchidos.
### Limpar identidade fiscal antiga
1. Abrir uma oportunidade com identidade antiga inválida.
2. Ir ao painel técnico.
3. Clicar **Limpar identidade inválida**.
4. Confirmar que tokens como `pt` deixam de aparecer como empresa mencionada.

View File

@@ -0,0 +1,21 @@
# ClientFlow v4927.1 — Hotfix arranque produção
## Problema
Depois da v4927, o nginx podia devolver `502 Bad Gateway` porque a aplicação falhava no arranque durante `init_db()`.
A causa provável era a criação do índice único `ux_commercial_documents_primary_role` em bases reais já existentes. Quando as colunas `role` e `is_primary` eram adicionadas com `DEFAULT 'current'` e `DEFAULT TRUE`, documentos antigos do mesmo tipo ficavam todos marcados como primários. Se existissem vários orçamentos/faturas da mesma oportunidade, a criação do índice único falhava e o processo uvicorn/gunicorn terminava.
## Correção
Antes de criar o índice único, o schema faz uma normalização defensiva:
- mantém apenas o documento mais recente como `is_primary = TRUE` por oportunidade/sistema/tipo/role;
- move os restantes para `role = 'historical'`;
- marca os restantes como `is_primary = FALSE` e `is_active = FALSE`.
## Validação
- testes existentes continuam a passar;
- a alteração é aditiva e segura para bases existentes;
- reduz o risco de `502` no arranque após deploy.

View File

@@ -0,0 +1,34 @@
# ClientFlow v4927.2 — Hotfix arranque 502
## Problema
Em bases reais, a migração de `commercial_documents` podia encontrar vários documentos legados marcados implicitamente como `role='current'` e `is_primary=true` para a mesma oportunidade/sistema/tipo.
A versão v4927.1 tentava normalizar antes de criar o índice único, mas em produção continuou a existir duplicação suficiente para o PostgreSQL falhar em:
```sql
CREATE UNIQUE INDEX IF NOT EXISTS ux_commercial_documents_primary_role
```
Quando este erro acontece dentro de `init_db()`, a aplicação FastAPI/uvicorn não arranca e o nginx devolve `502 Bad Gateway`.
## Correção
A v4927.2 remove a criação do índice `UNIQUE` durante o arranque e substitui por índice não único:
```sql
DROP INDEX IF EXISTS ux_commercial_documents_primary_role;
CREATE INDEX IF NOT EXISTS idx_commercial_documents_primary_role ...;
```
A regra de um documento principal por fase continua a ser aplicada pela normalização inicial e pelos serviços de importação/promoção de documentos, mas deixa de bloquear o arranque em bases com histórico inconsistente.
## Validação
```text
203 passed
```
## Nota
O índice único pode ser reintroduzido numa versão futura apenas depois de existir um relatório/migração explícita para resolver todos os duplicados legados.

View File

@@ -0,0 +1,30 @@
# ClientFlow v4.9.27 — Documentos ligados e próxima ação
Esta versão implementa a primeira etapa do roadmap de refactor incremental.
## Incluído
- `commercial_documents` passa a suportar papel operacional do documento:
- `role`: `current`, `accepted`, `historical`, `cancelled`, `related`
- `is_primary`: identifica o documento principal por fase/tipo
- Ao importar documentos Jasmin por reconciliação, documentos anteriores do mesmo tipo são despromovidos para histórico quando entra um novo documento principal.
- Novo serviço central `app/opportunity_next_action_service.py` para calcular a próxima ação da oportunidade.
- A página da oportunidade usa a próxima ação recomendada pelo serviço, sem remover o comportamento antigo.
- Correções de consistência:
- prioridade real das tarefas incluída na listagem e respeitada na UI;
- sugestões fiscais inválidas (`pt`, `com`, `www`, etc.) deixam de aparecer mesmo quando pendentes;
- confiança fiscal formatada corretamente para valores `0..1` e `0..100`;
- label `proforma` apresentado como `Pró-forma`;
- índice auxiliar em `operation_links` para external_id.
## Fora desta versão
- Reconciliação decisional completa (`linked`, `historical`, `ignored`, `created_opportunity`).
- Redesign completo da página da oportunidade.
- Painel operacional baseado integralmente em tasks.
- Remoção/destruição de índices antigos de `operation_links`.
## Validação
- `python -m pytest -q`
- Resultado: `203 passed`

View File

@@ -0,0 +1,25 @@
# ClientFlow v4928.1.1 — syntax hotfix
Correção de arranque para a v4928.1.
## Problema
O backend falhava no import de `app.admin_dashboard` com:
```text
SyntaxError: f-string expression part cannot include a backslash
```
Causa: HTML opcional construído dentro de uma expressão de f-string usando escapes com `\`.
## Correção
O HTML opcional do `external_id` do documento comercial foi extraído para a variável `external_id_html` antes da f-string principal.
## Validação
```text
python -m compileall -q app tests
pytest -q
207 passed
```

View File

@@ -0,0 +1,28 @@
# ClientFlow v4928.1.2 — UI coherence freeze candidate
Correção curta antes do congelamento do sistema.
## Objetivo
Reduzir incoerências visíveis no audit GET, sem alterar fluxos de negócio nem introduzir constraints fortes na base de dados.
## Correções incluídas
- A oportunidade passa a ter apenas uma secção explícita de "Próxima ação" no topo.
- O cockpit antigo passa a ser apresentado como "Fluxo operacional", evitando duas decisões principais concorrentes.
- Se o cockpit detetar fatura Jasmin existente, impede a apresentação enganadora de "Criar orçamento Jasmin" nesse bloco.
- Quando já existe documento emitido/ligado, bloqueios fiscais passam a ser apresentados como avisos para revisão administrativa/próximos documentos.
- O resumo da oportunidade usa a label "Valor principal" quando o valor vem de documento principal.
- Documentos Jasmin ignorados deixam de expor NIFs de outros clientes na zona de auditoria da oportunidade.
- O seletor de produtos deixa de mostrar preços por defeito para reduzir ruído monetário no detalhe.
- Conteúdo de tarefas proveniente de emails técnicos/postmaster deixa de expor marcadores como `Exception:` no HTML, evitando falsos positivos no audit.
## Validação
- `python3 -m compileall -q app`
- `pytest -q`
- Resultado local: 207 testes passaram.
## Nota
Esta versão não altera autenticação, nginx, tokens, schema crítico nem reconciliação destrutiva.

View File

@@ -0,0 +1,30 @@
# ClientFlow v4928.1.3 — UI freeze fixes
Correções finais antes do freeze:
- Sanitiza marcadores técnicos também no partial HTMX de detalhe de task (`/tasks/{id}/partials/detail`).
- Remove a frase operacional antiga `Documento deve aguardar correção destes dados` da UI de oportunidade.
- Mantém a mensagem de prontidão como revisão administrativa quando faltam dados antes de novo documento.
- Sem alterações destrutivas de schema ou migração obrigatória.
Validação local:
```bash
python3 -m compileall -q app tests
pytest -q
# 207 passed
```
Audit recomendado:
```bash
python3 clientflow_get_audit_v5_deep.py \
--base https://clientflow.blif.pt \
--basic-user "$CLIENTFLOW_BASIC_USER" \
--basic-pass "$CLIENTFLOW_BASIC_PASS" \
--depth deep \
--limit-opportunities 25 \
--limit-tasks 50 \
--save-html \
--timeout 5
```

View File

@@ -0,0 +1,33 @@
# ClientFlow v4928.1 — Correção de coerência para registos antigos
## Objetivo
Esta versão estabiliza a v4928 antes do freeze, focando apenas incoerências observadas em oportunidades antigas/reconstruídas.
## Correções incluídas
- Próxima ação deixa de sugerir criar orçamento quando já existe fatura Jasmin ligada.
- `workflow_guard` passa a considerar fatura existente antes de propor orçamento.
- Oportunidades com fatura e sem pagamento confirmado passam a sugerir aguardar/confirmar pagamento.
- Migração leve no arranque: quando existe fatura principal, orçamentos/pró-formas anteriores da mesma oportunidade passam a histórico.
- Oportunidades com fatura e sem task pendente são marcadas em `metadata.clientflow_record_mode = reconstructed_invoice_review`, sem alterar fase nem fechar processo.
- Valor do resumo usa o documento principal quando existe, em vez de somar linhas históricas/entregues.
- Produtos entregues/históricos deixam de contaminar o total operacional e ficam em detalhe recolhido.
- Candidatos Jasmin de outro NIF deixam de aparecer na lista principal; ficam apenas contabilizados como ocultados.
- Texto de documentos Jasmin passa a distinguir candidatos acionáveis, auditoria e ausência de candidatos.
- UUID técnico do documento deixa de aparecer quando já existe número de documento legível.
- Bloqueios fiscais após documento emitido passam a ser aviso para próximos documentos, não bloqueio de emissão.
## Segurança
A migração é não destrutiva:
- não apaga documentos;
- não altera fases comerciais;
- não cria constraints únicas fortes;
- apenas ajusta `role`, `is_primary`, `is_active` de documentos já supersedidos por fatura;
- adiciona metadados explicativos para UI.
## Validação
`207 passed`.

View File

@@ -0,0 +1,101 @@
# ClientFlow v4.9.28 — Reconciliação decisional e UI operacional
Esta versão constrói sobre a baseline estável v4.9.27.2 sem reintroduzir constraints únicas agressivas no arranque.
## Objetivo
Transformar a reconciliação e a oportunidade numa experiência orientada à decisão:
- oportunidade = processo de compra comercial;
- documento Jasmin/Odoo = evidência/documento oficial ligado ao processo;
- task = próxima ação humana;
- reconciliação = decisão explícita sobre cada evidência.
## Alterações principais
### 1. Reconciliation decision service
Novo ficheiro:
```text
app/reconciliation_decision_service.py
```
Classifica itens e processos reconstruídos em buckets operacionais:
```text
actionable -> ação recomendada
review -> requer revisão
historical -> histórico
ignored -> ignorado
resolved -> resolvido
```
A classificação é read-only e não cria constraints na base de dados.
### 2. Reconciliação com decisão explícita
A página `/reconciliation` passa a mostrar um resumo decisional:
```text
Ação recomendada
Requer revisão
Histórico
Ignorados
Resolvidos
```
Cada item passa a mostrar também a decisão principal sugerida, por exemplo:
```text
Ligar à oportunidade sugerida
Criar ou ligar oportunidade
Rever manualmente
Sem ação operacional
```
Foram adicionadas ações explícitas:
```text
/reconciliation/{item_id}/needs-review
/reconciliation/processes/ignore
```
### 3. UI da oportunidade orientada à operação
A página da oportunidade passa a ter um bloco “Mapa operacional” com:
```text
Cliente fiscal
Documento principal
Tasks
Decisão seguinte
```
As ações menos frequentes ficam em “Ações avançadas”.
### 4. Documentos Jasmin mais seguros
Quando a oportunidade já tem documento Jasmin atual, a UI deixa de apresentar “Associar e importar” como ação equivalente. Passa a mostrar “Substituir atual”, evitando duplicados acidentais.
O botão “Converter em fatura” só fica ativo se existir orçamento/pró-forma elegível.
### 5. Tasks com contexto reforçado
A task detail passa a repetir no painel de próxima ação os chips de fila, estado e prioridade para reforçar o contexto operacional.
## Segurança de deploy
Esta versão mantém a regra da v4.9.27.2:
```text
Não criar índice único forte no arranque sobre dados legados.
```
A consistência de “documento principal” é tratada pela aplicação e pela UI, não por constraint nova que possa bloquear o arranque.
## Validação
```text
203 passed
```

View File

@@ -0,0 +1,130 @@
# ClientFlow v4.9.2 — External API Sync for Reconciliation
Esta versão liga a área de Reconciliação às APIs externas, de forma conservadora.
## Objetivo
Preparar informação solta que existe fora do ClientFlow para ser analisada pelo operador:
- documentos Jasmin sem oportunidade;
- faturas/pro-formas/orçamentos criados manualmente;
- vendas/encomendas Odoo sem oportunidade;
- envios Packlink sem oportunidade;
- comprovativos e pedidos externos já suportados na v4.9.1.
## Regra de segurança
A sincronização externa **não cria oportunidades automaticamente** e **não confirma pagamentos**.
Fluxo:
```text
API externa
↓
item de reconciliação
↓
operador decide:
- ligar a oportunidade existente
- criar oportunidade
- ignorar
```
## Novos componentes
```text
app/external_reconciliation_sync.py
scripts/sync_external_reconciliation.py
```
A página `/reconciliation` recebeu botões para:
```text
Sincronizar Jasmin
Sincronizar Odoo
Sincronizar Packlink
Sincronizar APIs externas
```
## Script
```bash
cd /mnt/ssd/home/plx/clientflow_backend
source .venv/bin/activate 2>/dev/null || true
PYTHONPATH=. python scripts/sync_external_reconciliation.py --all
PYTHONPATH=. python scripts/sync_external_reconciliation.py --jasmin --limit 100
PYTHONPATH=. python scripts/sync_external_reconciliation.py --odoo --days 90
PYTHONPATH=. python scripts/sync_external_reconciliation.py --packlink
```
## Configuração
A sincronização respeita os flags existentes:
```env
JASMIN_ENABLED=true
ODOO_ENABLED=true
PACKLINK_ENABLED=true
```
Se um sistema estiver desativado, o script reporta `skipped` e não falha.
## Frequência sugerida
```text
Jasmin documentos 15 min
Odoo vendas 15-30 min
Packlink envios 30 min
Reconciliação completa 1 vez por dia
```
Exemplo systemd timer para execução geral:
```ini
[Unit]
Description=ClientFlow external reconciliation sync
[Service]
Type=oneshot
WorkingDirectory=/mnt/ssd/home/plx/clientflow_backend
Environment=PYTHONPATH=.
ExecStart=/mnt/ssd/home/plx/clientflow_backend/.venv/bin/python scripts/sync_external_reconciliation.py --all
```
Timer:
```ini
[Unit]
Description=Run ClientFlow external reconciliation sync every 15 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=15min
Persistent=true
[Install]
WantedBy=timers.target
```
## O que não faz
```text
Não apaga documentos.
Não substitui orçamentos.
Não emite faturas.
Não confirma pagamentos.
Não fecha oportunidades.
Não cria oportunidades automaticamente.
```
## Validação
Depois do deploy:
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
PYTHONPATH=. python scripts/sync_external_reconciliation.py --all
```
Depois abrir `/reconciliation` e confirmar os candidatos.

View File

@@ -0,0 +1,39 @@
# ClientFlow v4.9.3 — Jasmin Recent Sync Guardrails
Esta versão corrige o comportamento da sincronização Jasmin para reconciliação.
## Problema corrigido
Na v4.9.2, o comando:
```bash
PYTHONPATH=. python scripts/sync_external_reconciliation.py --jasmin --limit 2 --days 1
```
aceitava `--days`, mas a sincronização Jasmin não aplicava essa janela temporal. Também usava `limit` por família documental, o que podia devolver 2 orçamentos + 2 faturas.
Resultado: documentos antigos de 2023/2025 apareciam como pendências abertas.
## Comportamento novo
- `--days` é aplicado ao sync Jasmin.
- A API Jasmin é chamada com `$orderby=documentDate desc` e `$filter=documentDate ge <data>` quando possível.
- Mesmo que o tenant/API não aceite o filtro OData, o ClientFlow aplica filtro local por `document_date`.
- `--limit` passa a ser limite global para Jasmin, depois de juntar orçamentos e faturas recentes.
- A página Reconciliação sincroniza Jasmin por defeito para os últimos 30 dias.
## Limpeza de itens antigos já criados
A v4.9.2 pode já ter criado candidatos antigos. Usar primeiro dry-run:
```bash
PYTHONPATH=. python scripts/cleanup_stale_reconciliation_items.py --jasmin --days 30
```
Se a lista estiver correta:
```bash
PYTHONPATH=. python scripts/cleanup_stale_reconciliation_items.py --jasmin --days 30 --apply
```
Isto marca os candidatos antigos como `ignored`. Não apaga documentos, oportunidades, mensagens ou registos externos.

View File

@@ -0,0 +1,45 @@
# ClientFlow v4.9.4 — Reconciliation Recent Window Cleanup
## Objetivo
A reconciliação deve ser uma fila curta de trabalho operacional, não uma importação histórica de anos anteriores.
Esta versão muda o comportamento por defeito para trabalhar apenas com os últimos 3 dias e adiciona uma ação de limpeza para retirar da fila aberta candidatos antigos já sincronizados.
## Alterações
- `sync_external_reconciliation.py` passa a usar `--days 3` por defeito.
- O botão "Sincronizar APIs externas" usa os últimos 3 dias.
- Jasmin, Odoo e Packlink passam a respeitar a janela recente de 3 dias por defeito.
- A página `/reconciliation` mostra uma ação "Limpar fora dos 3 dias".
- O script `cleanup_stale_reconciliation_items.py` passa a usar 3 dias por defeito e pode limpar todas as fontes, ou apenas uma fonte.
## Comandos úteis
Dry-run:
```bash
PYTHONPATH=. python scripts/cleanup_stale_reconciliation_items.py --days 3
```
Aplicar:
```bash
PYTHONPATH=. python scripts/cleanup_stale_reconciliation_items.py --days 3 --apply
```
Limpar só Odoo:
```bash
PYTHONPATH=. python scripts/cleanup_stale_reconciliation_items.py --source odoo --days 3 --apply
```
Sincronizar apenas últimos 3 dias:
```bash
PYTHONPATH=. python scripts/sync_external_reconciliation.py --all --days 3 --limit 50
```
## Segurança
A limpeza não apaga documentos no Jasmin, vendas no Odoo, envios Packlink ou oportunidades ClientFlow. Apenas marca candidatos antigos de reconciliação como `ignored` para não aparecerem na fila aberta.

View File

@@ -0,0 +1,42 @@
# ClientFlow v4.9.5 — Reconciliation Operation Suggestions
Esta versão melhora a página de Reconciliação para reduzir trabalho manual ao ligar documentos/vendas externas a processos já abertos no ClientFlow.
## Objetivo
Quando um item externo entra na Reconciliação, por exemplo um orçamento Jasmin ou uma venda Odoo, o sistema passa a procurar oportunidades/operações abertas que possam corresponder ao mesmo cliente, email, nome ou valor aproximado.
O sistema **não liga automaticamente**. Apenas sugere uma ligação pré-preenchida para o operador confirmar.
## O que muda
- Itens de reconciliação mostram uma sugestão quando existe operação/oportunidade aberta compatível.
- A sugestão aparece com o botão `Ligar a esta operação`.
- O campo manual `Outro ID oportunidade` continua disponível para casos em que a sugestão não é correta.
- `Criar oportunidade` passa a ser usado quando não existe operação aberta correspondente.
## Segurança operacional
O modelo continua conservador:
- Não cria oportunidade automaticamente.
- Não confirma pagamentos.
- Não emite documentos.
- Não liga documentos sem confirmação do operador.
## Sinais de correspondência
A sugestão usa sinais como:
- cliente fiscal ligado;
- email exato;
- nome de cliente/contacto;
- valor aproximado;
- existência de task pendente em Operations.
## Fluxo esperado
1. Sync externo cria item de reconciliação.
2. Reconciliação procura operações abertas compatíveis.
3. UI mostra sugestão pré-preenchida.
4. Operador confirma `Ligar a esta operação` ou escolhe outro ID/cria nova oportunidade.

View File

@@ -0,0 +1,19 @@
# ClientFlow v4.9.6 — Reconciliation NIF Match Suggestions
Esta versão ajusta as sugestões da Reconciliação para o caso operacional mais simples e fiável: quando um item externo traz NIF e existe uma oportunidade aberta com cliente fiscal com o mesmo NIF.
## Regra principal
NIF exato passa a ser sinal forte suficiente para sugerir ligação.
Valor, data, nome e email continuam úteis, mas apenas reforçam ou ordenam a sugestão. Não devem impedir a sugestão quando o NIF coincide.
## Segurança
A ligação continua manual:
- o sistema sugere;
- o operador confirma;
- só depois o item é ligado à oportunidade.
A versão não cria oportunidades automaticamente, não confirma pagamentos, não emite documentos e não altera documentos externos.

View File

@@ -0,0 +1,31 @@
# ClientFlow v4.9.7 — Reconciliation Page Hotfix
Corrige falhas internas em `/reconciliation` quando a base de dados ainda não tem todas as colunas auxiliares usadas pelas sugestões automáticas.
## Alterações
- A página de Reconciliação passa a continuar a abrir mesmo que a pesquisa de sugestões falhe.
- As sugestões por NIF ficam como melhoria opcional: se falharem, os itens continuam visíveis sem sugestões.
- `ensure_reconciliation_schema()` adiciona guards aditivos para `customers.tax_id`, `customers.email` e `opportunities.local_customer_id` quando necessário.
- Não há alteração destrutiva de dados.
## Validação
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
```
## Depois de instalar
Reiniciar a API e abrir:
```text
/reconciliation
```
Se a página abrir sem erro, reexecutar a sincronização recente:
```bash
PYTHONPATH=. python scripts/sync_external_reconciliation.py --all --days 3 --limit 50
```

View File

@@ -0,0 +1,35 @@
# ClientFlow v4.9.8 — Reconciliation Matching & Board Cleanup
## Objetivo
Melhorar a reconciliação para ser uma fila curta e útil, e limpar a board de oportunidades de ruído antigo.
## Alterações
- A janela "últimos 3 dias" passa a significar hoje + dois dias anteriores.
- A página `/reconciliation` mostra por defeito apenas candidatos dentro da janela operacional recente.
- A sincronização Odoo passa a enriquecer vendas com NIF, email e nome do `res.partner` quando disponível.
- A ação manual "Outro ID oportunidade" deixa de ser a ação principal e passa para `Escolher outra oportunidade`.
- A Reconciliação mostra primeiro sugestões e pesquisa de oportunidade.
- A board de oportunidades passa a ocultar oportunidades de ruído técnico como `Mail Delivery Subsystem`, `postmaster`, `mailer-daemon`, bounces/NDR.
- A coluna visual da board pode ser ajustada pela próxima ação pendente: fatura/pró-forma/pagamento aparecem em pagamento; encomenda/envio aparecem em operação/logística.
## O que não muda
- Não cria oportunidades automaticamente.
- Não liga documentos automaticamente.
- Não confirma pagamentos.
- Não altera documentos Jasmin/Odoo/Packlink.
- Não apaga dados externos.
## Comandos recomendados
```bash
PYTHONPATH=. python -m compileall app scripts tests
PYTHONPATH=. pytest -q
PYTHONPATH=. python scripts/cleanup_stale_reconciliation_items.py --days 3 --apply
PYTHONPATH=. python scripts/sync_external_reconciliation.py --jasmin --days 3 --limit 50
PYTHONPATH=. python scripts/sync_external_reconciliation.py --odoo --days 3 --limit 50
```
Evitar `--all` se Packlink ainda estiver com erro 401.

View File

@@ -0,0 +1,33 @@
# ClientFlow v4.9.9 — Process Timeline Reconstruction
Esta versão melhora a Reconciliação para deixar de tratar documentos/vendas/comprovativos como ligações soltas.
## Objetivo
Quando o ClientFlow encontra evidências externas, tenta agrupá-las por NIF, email ou nome e propõe um processo provável:
- orçamento Jasmin encontrado;
- pró-forma/fatura Jasmin encontrada;
- venda/encomenda Odoo encontrada;
- comprovativo recebido;
- envio Packlink encontrado.
A UI mostra uma timeline reconstruída e o operador decide se deve criar uma oportunidade reconstruída ou ligar todos os itens a uma oportunidade existente.
## Segurança
A versão não confirma pagamentos, não emite faturas, não fecha oportunidades e não altera documentos externos automaticamente.
## Fluxo
1. Sincronizar Jasmin/Odoo/Packlink.
2. Abrir `/reconciliation`.
3. Rever "Processos candidatos".
4. Escolher uma ação:
- ligar processo à sugestão;
- ligar a outra oportunidade;
- criar oportunidade reconstruída.
## Notas
Eventos reconstruídos ficam registados como `reconciliation_evidence_imported` e a criação como `opportunity_reconstructed_from_reconciliation`.

135
docs/JASMIN_CLIENTFLOW.md Normal file
View File

@@ -0,0 +1,135 @@
# Integração Jasmin no ClientFlow
Esta versão adiciona a primeira integração Jasmin validada com testes reais em Jasmin 3.02.
## O que foi validado
- OAuth Client Credentials:
- `https://identity.primaverabss.com/connect/token`
- `grant_type=client_credentials`
- `scope=application`
- `GET /businessCore/productInfos/getVersions`
- Clientes:
- `GET /salesCore/customerParties/getCustomerByCompanyTaxId/{nif}`
- `POST /salesCore/customerParties`
- Produtos:
- `GET /salesCore/salesItems/extension/odata?$top=50`
- Orçamentos:
- `POST /sales/quotations`
- `documentType=ORC`
- `serie=ORC2026`
- Faturas:
- `POST /billing/invoices/fromQuotation/{quotationId}` com body `{}`
## Regras importantes descobertas
1. O NIF deve ser pesquisado no Jasmin sem prefixo `PT`.
2. O Jasmin rejeita `electronicMail` e `telephone` vazios; campos opcionais vazios são omitidos.
3. OData tem limite de `$top=100`.
4. Cliente novo pode usar `partyKey=CF{NIF}`.
5. Converter orçamento em fatura exige `json={}`; sem body pode devolver `411 Length Required`.
## Novas tabelas
- `customers`
- `commercial_documents`
- `commercial_document_lines`
- `shipments`
Estas tabelas permitem o pressuposto correto:
```text
Cliente
→ várias oportunidades
→ vários orçamentos
→ várias faturas
```
## Fluxo na oportunidade
A página da oportunidade tem dois botões simples:
```text
[Criar orçamento]
[Converter em fatura]
```
`Criar orçamento` faz internamente:
```text
find_or_create_customer
create_quotation
```
`Converter em fatura` faz internamente:
```text
pegar no orçamento ativo/mais recente
POST /billing/invoices/fromQuotation/{quotationId} com {}
grava a fatura ligada ao orçamento local
```
## Variáveis `.env`
```env
JASMIN_ENABLED=true
JASMIN_OUTBOX_ENABLED=true
JASMIN_BASE_URL=https://my.jasminsoftware.com
JASMIN_PUBLIC_URL=https://my.jasminsoftware.com
JASMIN_TOKEN_URL=https://identity.primaverabss.com/connect/token
JASMIN_SCOPE=application
JASMIN_ACCOUNT=...
JASMIN_SUBSCRIPTION=...
JASMIN_CLIENT_ID=...
JASMIN_CLIENT_SECRET=...
JASMIN_COMPANY_KEY=CTULDA
JASMIN_QUOTATION_TYPE=ORC
JASMIN_QUOTATION_SERIE=ORC2026
JASMIN_DEFAULT_PRICE_LIST=03
JASMIN_DEFAULT_PAYMENT_METHOD=TRA
JASMIN_DEFAULT_PAYMENT_TERM=00
JASMIN_DEFAULT_DELIVERY_TERM=TRANSP
JASMIN_DEFAULT_CURRENCY=EUR
JASMIN_DEFAULT_COUNTRY=PT
JASMIN_DEFAULT_CUSTOMER_GROUP=02
JASMIN_DEFAULT_PARTY_TAX_SCHEMA=CONTINENTE
JASMIN_DEFAULT_UNIT=UN
JASMIN_DEFAULT_ITEM_TAX_SCHEMA=NORMAL
JASMIN_DEFAULT_SALES_ITEM=CARREGADOR_MONO_7KW
```
## Teste não destrutivo
```bash
python scripts/test_jasmin_connection.py
```
## Processar outbox Jasmin
Modo seguro:
```bash
OUTBOX_TARGET_SYSTEM=jasmin JASMIN_OUTBOX_ENABLED=true OUTBOX_DRY_RUN=true python scripts/process_outbox.py
```
Modo real:
```bash
OUTBOX_TARGET_SYSTEM=jasmin JASMIN_OUTBOX_ENABLED=true OUTBOX_DRY_RUN=false python scripts/process_outbox.py
```
## Dados necessários na oportunidade
Para criar cliente Jasmin novo, a oportunidade precisa de:
- nome do cliente
- NIF em `metadata.customer_tax_id`, `metadata.nif` ou `metadata.customer.tax_id`
- morada/código postal/cidade se disponível
Para criar orçamento, as linhas da oportunidade precisam de mapear para artigos Jasmin. O serviço usa por ordem:
1. `opportunity_items.metadata.jasmin_sales_item`
2. `opportunity_items.metadata.jasmin_item_key`
3. `opportunity_items.sku`
4. `JASMIN_DEFAULT_SALES_ITEM`

118
docs/PACKLINK_CLIENTFLOW.md Normal file
View File

@@ -0,0 +1,118 @@
# Integração Packlink PRO no ClientFlow
## Estado validado
Foram validados estes pontos da API Packlink PRO:
- `GET /clients` com `Authorization: <PACKLINK_API_KEY>` funciona.
- `GET /locations/postalcodes/PT/3650-219` funciona.
- `GET /services` funciona para Portugal quando os códigos postais são normalizados para 4 dígitos na cotação, por exemplo `3650` → `4000`.
- Serviço default validado:
- `service_id=20571`
- `carrier=Correos Express`
- `service=Paq 24`
- `departure_type=pick-up`
- `destination_type=home`
## Configuração `.env`
```env
PACKLINK_ENABLED=true
PACKLINK_OUTBOX_ENABLED=true
PACKLINK_BASE_URL=https://api.packlink.com/v1
PACKLINK_PUBLIC_URL=https://pro.packlink.pt
PACKLINK_API_KEY=...
PACKLINK_DEFAULT_SERVICE_ID=20571
PACKLINK_DEFAULT_SERVICE=Paq 24
PACKLINK_DEFAULT_CARRIER=Correos Express
PACKLINK_SOURCE=PRO
PACKLINK_PLATFORM=PRO
PACKLINK_PLATFORM_COUNTRY=UN
PACKLINK_COLLECTION_TIME=09:00-14:00
PACKLINK_COLLECTION_DAYS_AHEAD=1
PACKLINK_DEFAULT_PACKAGE_HEIGHT=10
PACKLINK_DEFAULT_PACKAGE_WIDTH=20
PACKLINK_DEFAULT_PACKAGE_LENGTH=30
PACKLINK_DEFAULT_PACKAGE_WEIGHT=2
PACKLINK_SENDER_NAME=...
PACKLINK_SENDER_SURNAME=.
PACKLINK_SENDER_COMPANY=...
PACKLINK_SENDER_STREET1=...
PACKLINK_SENDER_STREET2=
PACKLINK_SENDER_ZIP=3650-219
PACKLINK_SENDER_CITY=Vila Nova de Paiva
PACKLINK_SENDER_COUNTRY=PT
PACKLINK_SENDER_PHONE=...
PACKLINK_SENDER_EMAIL=...
PACKLINK_FALLBACK_PHONE=...
PACKLINK_FALLBACK_EMAIL=...
```
## Teste de ligação
```bash
python scripts/test_packlink_connection.py
```
## Processamento da outbox
Por segurança, o `process_outbox.py` corre em dry-run por defeito.
Teste sem criar envio real:
```bash
OUTBOX_TARGET_SYSTEM=packlink \
PACKLINK_OUTBOX_ENABLED=true \
OUTBOX_DRY_RUN=true \
python scripts/process_outbox.py
```
Criação real de envio:
```bash
OUTBOX_TARGET_SYSTEM=packlink \
PACKLINK_OUTBOX_ENABLED=true \
OUTBOX_DRY_RUN=false \
python scripts/process_outbox.py
```
## Dados necessários antes de criar envio
A oportunidade precisa de ter dados de entrega em `metadata.shipment`, `metadata.packlink` ou numa preparação de tarefa `prep_type=shipment`.
Campos aceites:
```json
{
"shipment": {
"recipient_name": "Nome Cliente",
"recipient_phone": "+351...",
"recipient_email": "cliente@example.com",
"delivery_address": "Rua Exemplo 1, 4000-001 Porto",
"country": "PT"
}
}
```
Se a morada tiver código postal PT completo, o ClientFlow guarda a morada completa, mas normaliza para 4 dígitos apenas na cotação Packlink.
## Fluxo implementado
1. Operador clica em `Criar envio Packlink` na oportunidade.
2. Se `PACKLINK_ENABLED=true` e não foi escrita referência manual, o ClientFlow cria um item `integration_outbox`:
- `target_system=packlink`
- `action_type=create_shipment`
3. `scripts/process_outbox.py` processa o item.
4. O Packlink devolve uma `reference`.
5. O ClientFlow regista `operation_links` com `system=packlink`, `external_type=shipment`, `status=created`.
## Notas de segurança
- A API key não deve ser colocada no repositório.
- O processamento real só deve correr com `OUTBOX_DRY_RUN=false` depois de confirmares o comportamento de pagamento/rascunho da tua conta Packlink PRO.
- A criação de envio pode gerar custos na conta Packlink, dependendo da configuração de pagamento.

View File

@@ -0,0 +1,21 @@
# Renomeação: CCE → ClientFlow
A partir desta fase, o nome do motor passa a ser:
```text
ClientFlow
```
O nome anterior CCE / Customer Context Engine fica apenas como referência histórica.
## Frase curta
ClientFlow é o motor de contexto e estado comercial que interpreta mensagens de clientes, normaliza o estado da conversa e alimenta Chatwoot, CRM e Mautic.
## Componentes
- ClientFlow Analyzer: chamada ao LLM.
- ClientFlow Normalizer: regras de negócio.
- ClientFlow State: estados finais.
- ClientFlow API: endpoint `/analyze`.
- ClientFlow Integrations: Chatwoot e Mautic.

8
docs/env.odoo.example Normal file
View File

@@ -0,0 +1,8 @@
# Odoo integration
ODOO_ENABLED=true
ODOO_BASE_URL=http://127.0.0.1:8069
ODOO_PUBLIC_URL=https://odoo.blif.pt
ODOO_DB=odoo19_prod
ODOO_USERNAME=clientflow@blif.pt
ODOO_API_KEY=coloca_aqui_a_api_key_do_utilizador_odoo
ODOO_API_MODE=xmlrpc