Skip to content

feat(finance): eixo caixa no realizado + DFC pessoal (fases 1 e 2) - #81

Open
alanrezendeee wants to merge 4 commits into
mainfrom
feat/cash-axis-realized
Open

feat(finance): eixo caixa no realizado + DFC pessoal (fases 1 e 2)#81
alanrezendeee wants to merge 4 commits into
mainfrom
feat/cash-axis-realized

Conversation

@alanrezendeee

@alanrezendeee alanrezendeee commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Fases 1 e 2 do plano de profissionalização contábil, juntas neste PR.

Fase 1 — um fato, dois eixos, cada medida declara o seu

Medida Eixo Data
Previsto, a pagar, a receber competência due_date (sem mudança)
Realizado (cards, série anual, categorias, drill-down) caixa COALESCE(DATE(paid_at), due_date)

O sistema aplicava competência até no realizado — regime híbrido não declarado, invisível enquanto se paga perto do vencimento. A antecipação de uma parcela de financiamento expôs a distorção dupla: o mês do pagamento escondia o desembolso real, e o mês do vencimento mostraria "realizado" sem dinheiro sair.

  • Summary: totais divididos em duas queries, uma por eixo
  • MonthlySeries: dois GROUP BY (um por eixo) mesclados em memória — um lançamento pode contribuir para meses diferentes nas duas medidas
  • Categorias e drill-down: cláusula por status, idêntica nos dois pontos — o modal continua batendo com a barra por construção
  • Fallback em due_date para liquidações antigas sem paid_at

Fase 2 — DFC pessoal

GET /finance/dashboard/cashflow?year&family_member_id

Saldo inicial + entradas pagas − saídas pagas = saldo final, mês a mês. A abertura é o líquido de todos os fluxos realizados antes de 1º de janeiro — o caixa que o histórico registrado construiu (fluxos do ledger, não saldo bancário). 12 meses sempre preenchidos, saldo acumulado atravessando meses sem movimento.

Teste da identidade contábil: abertura + soma dos líquidos = fechamento.

Fase 3 — toda liquidação grava paid_at

O Confirm tinha um atalho sem carimbo: sem campos no body, só mudava o status e paid_at ficava NULL — o "Confirmar recebimento" de Receitas usa exatamente esse caminho, então o eixo caixa degradaria em silêncio daqui pra frente. Atalho removido: caminho único carimba paid_at (default: agora) e paid_amount_cents em toda liquidação. Efeito colateral correto: confirmar lançamento cancelado passa a ser rejeitado. setStatus virou código morto e foi removido.

Impacto nos dados

Nenhum — sem migração, nenhum lançamento alterado. Números exibidos mudam apenas onde mês do pagamento ≠ mês do vencimento.

Front: retech-meufin-admin #96 (mesma branch).

🤖 Generated with Claude Code

alanrezendeee and others added 2 commits August 6, 2026 16:31
Um fato, dois eixos, cada medida declara o seu:

- previsto / a pagar / a receber: COMPETÊNCIA (due_date) — a obrigação
  pertence ao mês em que vence, paga ou não. Sem mudança
- realizado: CAIXA (COALESCE(DATE(paid_at), due_date)) — o dinheiro saiu
  quando saiu. Parcela de dezembro antecipada em agosto passa a contar no
  realizado de agosto; a atrasada de junho paga em julho, no de julho

Antes o sistema aplicava competência até no realizado — um regime híbrido
não declarado, invisível enquanto se pagava perto do vencimento. Antecipação
de parcela expôs a distorção dupla: o mês do pagamento escondia o
desembolso, e o mês do vencimento mostraria 'realizado' sem dinheiro sair.

Mudanças:
- Summary: totais separados em duas queries, uma por eixo
- MonthlySeries: dois GROUP BY (um por eixo) mesclados em memória — um
  lançamento pode contribuir para meses DIFERENTES nas duas medidas
- Categorias e drill-down: cláusula por status (prevista→vencimento,
  realizada→pagamento), mantendo modal e barra idênticos por construção
- fallback em due_date cobre liquidações antigas que não gravaram paid_at

Sem migração e sem alteração de dado: só o pivô das agregações.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /finance/dashboard/cashflow?year&family_member_id

Saldo inicial + entradas pagas − saídas pagas = saldo final, mês a mês, tudo
no eixo caixa que a fase 1 estabeleceu. Responde a pergunta que o dashboard
de compromissos não responde: quanto sobrou de verdade.

- abertura = líquido de todos os fluxos realizados antes de 1º de janeiro
  (o caixa que o histórico registrado construiu; não é saldo bancário — o
  ledger conhece fluxos, não contas)
- 12 meses sempre preenchidos (zeros onde não houve movimento) e saldo
  acumulado carregando pelos meses vazios
- mesmas exclusões do dashboard: topo apenas, soft-deleted fora; realizada
  é o único status que move caixa

Teste da identidade contábil: abertura + soma dos líquidos = fechamento,
com acumulado atravessando meses sem movimento.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alanrezendeee alanrezendeee changed the title feat(finance): realizado migra para o eixo caixa (data de pagamento) feat(finance): eixo caixa no realizado + DFC pessoal (fases 1 e 2) Aug 6, 2026
alanrezendeee and others added 2 commits August 6, 2026 16:45
… caixa)

O Confirm tinha um atalho: sem desconto/valor/data no body, só mudava o
status — paid_at ficava NULL. O modal de despesas sempre manda a data, mas o
'Confirmar recebimento' de Receitas chama sem payload: toda receita
confirmada por ali caía no fallback por vencimento, degradando em silêncio o
eixo caixa das fases 1 e 2 daqui pra frente.

- atalho removido: caminho único carimba paid_at (default: agora) e
  paid_amount_cents (pagamento integral) em toda liquidação
- efeito colateral correto: confirmar lançamento cancelado agora é rejeitado
  (o atalho antigo permitia; a guarda sempre existiu no caminho com campos)
- setStatus removido (código morto — era só o atalho que o usava)

Testes: carimbo na liquidação rápida (paid_at + paid_amount + cascata com a
data), rejeição de cancelada.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fase 4)

Trilha (finance_entry_events, migration 000042, append-only):
- confirmed/settled com paid_at e valor; reopened; cancelled com motivo;
  due_date_changed com vencimento original preservado (evidência de
  prorrogação/antecipação que o modelo não tinha)
- ator: o auth injeta o user_id no context da requisição (pacote appctx,
  neutro — nem application→interfaces nem o inverso); rotinas automáticas
  ficam com actor nulo
- falha na gravação do evento não derruba a operação (o pagamento já
  persistiu; reverter por causa do log seria pior) — vai para o slog
- GET /finance/entries/:id/events
- motivação: o momento da liquidação nunca foi fato próprio; updated_at é
  'último toque' e foi contaminado por edições em massa. Evento não se edita

Imutabilidade (lançamento realizado é fato consumado):
- editar valor, vencimento, natureza ou status via PUT é rejeitado com
  mensagem apontando o caminho certo (Reopen/Cancelar); rótulos (descrição,
  notas, categoria, membro, fornecedor) continuam livres
- propagação de dia na série deixa de tocar parcelas pagas (antes o
  'esta e futuras' movia vencimento de realizada)

Fora do escopo da trilha: origens encerradas por renegociação (evento
próprio em finance_renegotiations) e cascata de itens de fatura (a ação é
uma só, no pai).

9 casos de teste novos.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alanrezendeee

Copy link
Copy Markdown
Owner Author

Fase 4 adicionada — trilha de eventos + imutabilidade.

  • finance_entry_events (migration 000042, append-only): confirmed/settled com data e valor, reopened, cancelled com motivo, due_date_changed com o vencimento original preservado. Ator vem do token via context (appctx); rotinas automáticas ficam com actor nulo. GET /finance/entries/:id/events.
  • Imutabilidade: lançamento pago não muda valor, vencimento, natureza nem status via edição — só rótulos. A propagação de dia em série deixa de tocar parcelas pagas.
  • Backfill executado em prod à parte (dados): 513 realizadas sem paid_at ganharam paid_at = updated_at — verificado antes que 100% tinham updated_at no mesmo mês do vencimento e sem contaminação dos renames; nenhum mês de agregação se moveu.
  • Seed dos 128 marcadores de saúde aplicado em prod (93 novos).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant