# Múltiplas integrações por empresa e unicidade documental — requisitos e plano

Data: 2026-07-23 · Estado: proposto, por executar · Sem desenvolvimento neste documento

## Pressupostos

- O plano `moloni-integration-plan.md` (Fases A–E) está concluído: ledger neutro
  (`IntegrationCommit`/`IntegrationOperation`/`IntegrationSales*`/bindings com
  `Provider`), interfaces de provider + dispatcher, `MoloniCompanyConnection`,
  compras push e vendas pull Moloni funcionais.
- Limitação a resolver: `CompanyIntegrationSettings.Provider` permite **um único**
  sistema por empresa. O dispatcher resolve **um** adaptador por empresa.
- O Excel (workbook) é hoje uma projeção com switches **globais**
  (`ProjectPurchasesToExcel`/`ProjectSalesToExcel`), não configurável por empresa.

## Caso de negócio

| Empresa | Excel | Moloni | Cegid |
|---|---|---|---|
| GOTT | análise (compras+vendas) | faturação de vendas | compras + vendas (contabilidade) |
| Empresa B | compras+vendas | — | compras + vendas |
| Empresa C | — | compras + vendas | — |

A fatura de compra da empresa interna X, fornecedor Y, número Z, data de emissão
aaaa-MM-dd é **o mesmo documento** em todas as plataformas onde exista. O sistema
tem de a tratar como uma entidade canónica única com N representações externas.

## Decisões tomadas (com o utilizador, 2026-07-23)

1. **Excel torna-se um alvo de integração** na mesma estrutura que os ERPs
   (provider `ExcelWorkbook`), configurável por empresa. Os switches globais
   passam a kill-switch operacional; a ativação é por empresa.
2. **Vendas Moloni → Cegid (push)**: só se prepara a estrutura (documento único
   com N representações). O fluxo de replicação fica identificado como fase
   futura, fora deste plano.
3. **N sistemas por fluxo**: a mesma empresa pode ter vários sistemas ativos para
   o mesmo fluxo (ex.: vendas presentes em Moloni e Cegid). A unicidade
   documental resolve a sobreposição.

## Requisitos

### Funcionais

- RF1 — Uma empresa interna pode ter **0..N ligações de integração**, uma por
  provider (`Cegid`, `Moloni`, `ExcelWorkbook`, futuros PHC/Primavera/Artsoft).
  Máximo uma ligação por (empresa, provider).
- RF1a — Um workbook Excel pode ser **partilhado por várias empresas** (caso
  atual: GOTT/ITOORER/Toorist num único ficheiro, coluna "Empresa" por linha);
  a ligação é por empresa, o ficheiro é um recurso referenciado.
- RF2 — Cada ligação declara os **fluxos ativos**, limitados às capacidades do
  provider: `PurchasesOut` (push de compras), `SalesIn` (pull de vendas),
  `SalesOut` (push de vendas — reservado, nenhum provider o ativa neste plano).
  Excel: `PurchasesOut` + `SalesOut` (projeção). Cegid/Moloni: `PurchasesOut` +
  `SalesIn`.
- RF3 — **Unicidade documental de compras**: chave natural
  (empresa interna, NIF do fornecedor, tipo de documento, número, data de
  emissão) identifica um único documento canónico. Índice único; qualquer
  entrada (upload, pull futuro) que colida é associada ao documento existente,
  nunca duplicada.
- RF4 — **Unicidade documental de vendas**: chave natural
  (empresa interna, tipo de documento, série+número, data de emissão). Vendas
  puxadas de fontes distintas com a mesma chave fundem-se num único documento
  canónico com N representações de origem.
- RF5 — **Representações externas**: cada documento canónico tem 0..N ligações
  (documento, provider) com `ExternalId`, estado e timestamps próprios. O estado
  `Integrated` de um documento de compra significa "confirmado em **todos** os
  alvos ativos da empresa" (extensão do conceito atual Cegid+workbook).
- RF6 — **Deteção de divergência**: se a mesma chave natural chegar de dois
  providers com totais/linhas diferentes, o documento fica em estado de
  discrepância com alerta ao operador; nenhuma fusão silenciosa de valores.
- RF7 — Falha num alvo não bloqueia os restantes: cada (documento, alvo) tem o
  seu ciclo Prepared → Writing → Confirmed/WriteUnknown/Failed; retry e
  reconciliação por alvo.
- RF8 — UI de empresas: lista de ligações (adicionar/remover provider, ativar
  fluxos, configuração específica por provider). Remoção bloqueada enquanto a
  ligação estiver ativa ou tiver operações pendentes (generalização da guarda
  atual de mudança de provider).
- RF9 — Migração sem perda: cada linha atual de `CompanyIntegrationSettings`
  (`Cegid`/`Moloni`) converte-se numa ligação com os fluxos hoje ativos; os
  switches globais de Excel geram ligações `ExcelWorkbook` para as empresas que
  hoje projetam para o workbook.

### Não funcionais

- RNF1 — Comportamento observável das empresas existentes inalterado após a
  migração (mesmos alvos, mesmos fluxos).
- RNF2 — Idempotência: reprocessar o mesmo documento/fonte nunca cria segunda
  linha canónica nem segunda operação confirmada por alvo.
- RNF3 — Adicionar um provider futuro não altera ledger, máquina de estados,
  jobs, reconciliação nem UI genérica (mantém o critério do plano Moloni).
- RNF4 — Normalização da chave natural documentada e estável: NIF sem prefixo de
  país, número/série sem espaços, comparação case-insensitive, data em ISO.

## Modelo de dados (alterações)

1. **`CompanyIntegrationSettings` → `CompanyIntegrationConnection`** (0..N por
   empresa): `CompanyEntityId`, `Provider`, `PurchasesOutEnabled`,
   `SalesInEnabled`, `SalesOutEnabled` (sempre false neste plano), `Enabled`,
   `Version`, auditoria. Índice único (CompanyEntityId, Provider). As tabelas de
   configuração específicas (`CegidCompanyConnection`, `MoloniCompanyConnection`)
   ligam 1:1 à ligação genérica. Para o Excel, o modelo é N:1: uma
   **`WorkbookDefinition`** descreve o ficheiro partilhado (drive/item Graph,
   tabelas, fingerprints — o conteúdo atual de `GraphWorkbookOptions`, que passa
   de configuração global a dados) e cada ligação `ExcelWorkbook` referencia uma
   `WorkbookDefinitionId`. GOTT, ITOORER e Toorist apontam para a **mesma**
   definição — o workbook é comum e a coluna "Empresa" (já escrita hoje pelo
   projector) distingue as linhas; outra empresa pode apontar para outro
   workbook ou não ter ligação Excel.
2. **Chave natural canónica**: colunas normalizadas + índice único no documento
   de compra (empresa, NIF fornecedor, tipo, número, data) e no documento de
   venda (empresa, tipo, série+número, data). Backfill de normalização antes de
   criar o índice; colisões pré-existentes são resolvidas manualmente (relatório
   de duplicados na migração, migração falha fechado se houver colisões).
3. **`IntegrationDocumentLink`** (novo): documento canónico ↔ (provider,
   `ExternalId`, direção in/out, estado, hash do payload, timestamps). Substitui
   a relação implícita 1:1 documento↔provider; `IntegrationSalesDocument`
   passa de "documento por provider" a representação ligada ao canónico.
4. **Ledger**: sem alteração estrutural — `IntegrationCommit`/`Operation` já têm
   `Provider`; o commit de um documento passa a agrupar operações de vários
   providers ou há um commit por (documento, provider) — decidir no desenho
   detalhado (recomendação: um commit por alvo, agregação só na leitura).

## Orquestração

- **Dispatcher**: `ResolveTargets(companyId, flow)` devolve a lista de
  adaptadores ativos (era 1). `IntegrateDocument` itera os alvos `PurchasesOut`;
  o estado agregado do documento deriva dos estados por alvo.
- **Vendas pull**: o hosted service despacha por (empresa, ligação com
  `SalesIn`). O passo de ingestão resolve a chave natural: documento novo → cria
  canónico + link; chave existente → só acrescenta/atualiza o link; payload
  divergente → RF6.
- **Excel adapter**: `FinancialWorkbookProjector` embrulhado como adaptador
  (`PurchasesOut`/`SalesOut`, `SupportsAttachments = false`), removendo os
  caminhos especiais "Cegid + workbook" do job handler.

## UI / API

- `Companies.razor`: substitui o seletor único por uma tabela de ligações com
  estado por provider; secções de configuração existentes reaproveitadas.
  Coluna "Integração" da listagem mostra os providers ativos.
- API: `GET/PUT /api/admin/companies/{id}/integrations` (coleção) substitui o
  endpoint singular `integration-provider`; endpoints por provider mantêm-se.
- Ecrãs de operação (commits, reconciliação, vendas) ganham filtro por provider
  e mostram, por documento, o estado em cada alvo.

## Fases de execução

1. **F1 — Modelo**: `CompanyIntegrationConnection` + migração (RF9), chave
   natural normalizada + índices únicos + relatório de colisões,
   `IntegrationDocumentLink`.
2. **F2 — Orquestração**: dispatcher multi-alvo, estado agregado por documento,
   ingestão de vendas com fusão pela chave natural (RF4/RF6).
3. **F3 — Excel como alvo**: `WorkbookDefinition` + ligações `ExcelWorkbook`
   por empresa + adaptador; migração cria uma definição a partir do
   `GraphWorkbookOptions` atual e liga-lhe as empresas que hoje projetam
   (GOTT/ITOORER/Toorist → mesma definição); serialização de escritas por
   workbook (várias empresas, um ficheiro — lock/fila por `WorkbookDefinition`);
   kill-switch global mantido.
4. **F4 — UI/API**: gestão de ligações, guardas de remoção, filtros por
   provider, textos PT/EN.
5. **F5 — Testes e docs**: matriz de casos (GOTT, Empresa B, Empresa C),
   idempotência, divergência, migração; atualizar `database-erd.md`,
   `cegid-integration.md`, `moloni-integration.md` e runbooks.

## Testes mínimos

- Migração: empresa só-Cegid, só-Moloni, sem provider; Excel global on/off →
  ligações corretas, comportamento igual ao anterior.
- Mesmo documento de compra entra por upload e (futuro) pull → uma linha
  canónica, dois links.
- Venda presente em Moloni e Cegid com a mesma chave → um canónico, dois links;
  com totais diferentes → discrepância, sem fusão.
- Compra com 3 alvos (Cegid, Moloni, Excel): falha num alvo não trava os
  outros; `Integrated` só com os 3 confirmados; retry só do alvo falhado.
- Guarda de remoção de ligação com operações pendentes.
- Workbook partilhado: documentos de GOTT e Toorist projetados em simultâneo →
  linhas corretas com "Empresa" respetiva, sem corrupção/interleaving (lock por
  workbook); desligar o Excel de uma empresa não afeta as outras.

## Riscos e em aberto

- Colisões reais na chave natural (fornecedores com numeração reutilizada entre
  anos/séries) — validar com dados reais antes do índice único; a data de
  emissão na chave mitiga, mas confirmar se o tipo de documento chega em todas
  as fontes.
- Fusão de vendas multi-fonte com granularidade de linhas diferente entre
  providers (RF6 pode disparar em excesso) — calibrar a comparação (totais vs
  linhas) no piloto.
- Commit por alvo vs commit agregado (decisão de desenho na F2).
- `SalesOut` (vendas Moloni → Cegid) fica estruturalmente pronto mas sem
  requisitos definidos — plano próprio quando for priorizado.
