4.7 KiB
Recepção idempotente e outbox de manifestações
Esta entrega captura eventos de domínio; não envia dados ao SuiteCRM. Não depende de SUITECRM_ENABLED: a captura transaccional preserva eventos enquanto a integração externa está desactivada. Não executar uma futura carga histórica sem reconciliar estes eventos pendentes.
Contrato implementado
POST /api/v1/interest-requests mantém o payload actual e aceita Idempotency-Key com UUID aleatório. A chave identifica uma intenção de submissão, não a pessoa. Não derivar a chave de email ou NUIT.
- Primeira submissão válida: 201 com referência e estado de recepção; pedido, recibo e evento confirmados na mesma transacção.
- Mesma chave e payload validado equivalente: mesma confirmação 201; sem novo pedido, notificação ou evento. O estado retornado é o da recepção, não uma consulta do estado comercial actual.
- Mesma chave com dados diferentes: 409, sem alterações.
- Chave inválida/vazia ou campos inválidos: 422. Falha técnica: 500 sanitizado. Os limites de pedidos existentes mantêm-se, incluindo nas repetições.
- Sem chave: compatibilidade com clientes antigos; cada envio é uma nova manifestação, sem garantia de deduplicação de reenvio.
Após retirar uma oportunidade, um pedido previamente aceite pode ser confirmado novamente com a sua chave e payload; novas submissões continuam rejeitadas. As restantes validações, incluindo email/consentimento, são aplicadas antes de devolver a confirmação.
O formulário React gera um UUID e reutiliza-o após falha enquanto os dados e a instância do formulário se mantiverem. Alterar dados cria outra intenção/chave. Não persiste payload pessoal em localStorage/sessionStorage. Recarregar, abandonar a página ou abrir outra aba perde esta chave; persistência segura entre sessões ainda não está implementada.
Persistência e atomicidade
Migration 2026_09_25_120000_add_interest_submission_receipts acrescenta UUID de integração opcional ao pedido e tabela de recibos com hash da chave, HMAC do payload e FK do pedido. Pedidos históricos não são automaticamente migrados. A chave primária única e advisory lock transaccional PostgreSQL serializam a mesma chave entre processos.
O HMAC usa APP_KEY. Preservar a chave durante esta fase; uma rotação precisa de estratégia de versões/revalidação dos recibos antes de ser aplicada. Recibos não expiram automaticamente, para não reabrir a possibilidade de duplicar pedidos; a política de retenção deve ser definida juntamente com pedidos e auditoria.
InterestSubmission envolve a criação, a notificação em database queue, o UUID, a outbox e o recibo numa transacção. Qualquer falha reverte o conjunto. Não há HTTP externo dentro da transacção.
O evento ExpressionOfInterestSubmitted usa source_system=portal-intake, agregado interest_request, UUID de integração, estado pending e classificação confidential. O payload contém apenas schema_version=1 e interest_id (UUID).
Email, telefone, mensagem, IP, token e consentimento integral não são copiados para o evento. O futuro consumidor terá de consultar o pedido e revalidar finalidade, consentimento e permissão de tratamento antes de montar um payload mínimo para o CRM. Referência IR e UUID não conferem autorização de acesso.
Alterações e verificação
- Criados: migration,
InterestSubmission,IdempotencyConflicteInterestSubmissionTest. - Alterados: controller e validação de pedidos, formulário React e documentação de progresso.
- Dependências: PostgreSQL existente, outbox e triggers existentes, APP_KEY preservada e database queue para notificações.
- Testes cobrem repetição, conflito, compatibilidade, evento sem payload pessoal, falha na outbox com rollback de pedido/notificação, oportunidade retirada e protecção de rollback do esquema.
- Navegador Edge: resposta perdida após gravação seguida de reenvio com a mesma chave e confirmação original.
- A serialização entre processos foi implementada com locks PostgreSQL; ainda não foi executado teste de carga concorrente distribuída.
Rollback e pendências
Antes de receber pedidos, é possível reverter esta migration numa base descartável e reaplicá-la. Depois de existirem identidades/recibos, down() recusa a remoção para não perder os vínculos com a outbox. Preferir reverter o código conservando o esquema; reconciliar eventos e dados antes de qualquer remoção aprovada.
Pendentes: dispatcher com leases, workers SuiteCRM, identidade externa única no CRM, recuperação de escritas ambíguas, dead-letter queue, reprocessamento autorizado, monitoria e reconciliação. Nenhum evento está marcado como publicado nesta entrega.