From 1feb7112ff8246d3c9b20f0228b5702c6a80f61e Mon Sep 17 00:00:00 2001 From: Leonardo Date: Tue, 19 May 2026 20:54:23 -0300 Subject: [PATCH] agenda: C7 OK + Fase 6 lock-edit ativada em Melissa + cross-week payment propagation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cenário 7 (Pacote UPFRONT — Ana Souza Ferreira 4×R$ 200 = R$ 800) - Testado e passou. User criou Ana, pagou os R$ 800 em dinheiro pelo Financeiro. Borda verde + popover "Pago R$ 800" funcionando. Fase 6 (lock-edit cobrada) ativada em Melissa - Removido guard `if (!props.occurrenceMode) return;` em loadOccFinancialRecord (useAgendaEventLifecycle.js:217+). Agora ele carrega em ambos modos (Rail/Clínica E Melissa) - loadOccFinancialRecord SINTETIZA record paid/pending pra siblings de contrato upfront ativo — assim TODAS as ocorrências da série mostram "Cobrança paga R$ 800 do pacote" no AgendaEventDialog - AgendaEventDialog card Sessão/Honorários (flow Melissa) ganhou lock template: Tag em vez de Select billingType quando occFinancialRecord existe; Message com cadeado "Cobrança de R$ X já emitida" - AgendaEventoFinanceiroPanel só renderiza dentro do lock quando record é REAL (não sintetizado) — evita "Gerar cobrança" indevido em sibling - paymentSummary do Resumo lateral unificado pra usar occFinancialRecord (em vez do sessionPaymentRecord paralelo de antes) Cross-week propagation de pacote upfront - BUG: ao navegar pra semana só com virtuais (sem reais), bulk-load caía no else `_rulePaymentMap.value = {}` — virtuais perdiam estado paid herdado - FIX em useMelissaAgenda._reloadRange: * Maps (payment/amount/rule) inicializados SEMPRE no início * Propagação roda independente de realIds.length (depende só de ruleIdsInView.size>0, considera reais E virtuais com recurrence_id) * Query cross-week: pra cada rule em view, busca QUALQUER evento sibling em qualquer semana + seus records pra determinar estado do contrato. Encontra o record do pacote mesmo em outra semana - Saldo NÃO propaga (filter: charging_style='upfront' || NULL); cada sessão de saldo gera cobrança individual ao realizar - Memória durável: memory/project_cross_week_propagation.md Visualização de virtuais cobertas - MelissaEventoPanel.showPaymentRow: virtuais só escondem quando state ='none'. Com paid/pending herdado, exibem linha colorida - MelissaAgenda fcEvents: isPaidSession e badge $ pendente removeram exigência de !is_occurrence. Virtuais herdadas via propagação mostram borda verde / badge amber Atalho "Gerar fatura" no popover - Pill amber pequeno ao lado de "A cobrar R$ X" quando paymentVariant ='none' && !is_occurrence. Click → gerarCobrancaManual direto, fecha popover pra impedir double-click. Tooltip: "Gerar fatura agora" - Wire em MelissaLayout via novo emit gerar-cobranca + handler onGerarCobrancaQuick Info de pacote no popover - Header agora mostra "Sessão · Pacote · N sessões" (computed seriesLabel lê de _raw do rule) Botão "Excluir série inteira" - Novo emit delete-series em MelissaEventoPanel + botão ao lado de "Excluir sessão" quando evento tem recurrence_id - Handler onDeleteSeries em MelissaLayout: hard delete em 3 etapas (financial_records pendentes → agenda_eventos materializados → recurrence_rules CASCADE leva exceptions). Bloqueia se algum record paid (estorno via Financeiro primeiro) cancel_session some da agenda - useRecurrence.expandRules agora pula occurrence com exception.type= 'cancel_session' (era visível com status cancelado; doc dizia que some). patient_missed/therapist_canceled/holiday_block permanecem como histórico recurrence_exceptions cancel idempotente - MelissaLayout onDeleteEvento usa upsert com onConflict pra exception cancel — não quebra mais com unique violation em re-cancel billing_contract_id na 1ª materializada - _createPackageContract agora .select() o contrato após insert e seta billing_contract_id no insert da 1ª agenda_eventos materializada onVerLancamentos cobre virtual de upfront - Antes virtual sempre toast "Sem lançamentos". Agora busca records via siblings da série pra encontrar o do pacote. Saldo/sem pacote continua com toast Co-Authored-By: Claude Opus 4.7 (1M context) --- HANDOFF.md | 113 ++++++++-- Obsidian/Brain/log.md | 59 +++++ .../agenda/components/AgendaEventDialog.vue | 53 ++++- .../composables/useAgendaEventLifecycle.js | 90 +++++++- src/layout/melissa/MelissaAgenda.vue | 19 +- src/layout/melissa/MelissaEventoPanel.vue | 16 +- src/layout/melissa/MelissaLayout.vue | 67 ++++-- .../melissa/composables/useMelissaAgenda.js | 208 ++++++++++++++++-- 8 files changed, 530 insertions(+), 95 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index 0c39f1f..8a6f757 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,39 +1,112 @@ -# HANDOFF — 2026-05-19 (C1-C6 ✅, próximo C7 — pacote upfront) +# HANDOFF — 2026-05-19 noite (C1-C7 ✅, próximo C8 — pacote saldo) Documento de continuidade. **Quando voltar, comece lendo esta página até o fim.** > **🎯 SE A FORÇA CAIR / SESSÃO PERDER CONTEXTO:** estamos na rodada de > testes manuais dos 13 cenários do doc viva -> `src/docs/agenda-compromisso-financeiro-cenarios.html`. **C1-C6 ✅**. -> Próximo passo: **Cenário 7** (Donald Winnicott · pacote UPFRONT · 4 × -> R$ 200 = cobrança única de R$ 800). +> `src/docs/agenda-compromisso-financeiro-cenarios.html`. **C1-C7 ✅**. +> Próximo: **Cenário 8** (Carl Jung · pacote SALDO · 4 × R$ 40 — modelo +> Cliniko: contrato sem cobrança imediata, cada sessão gera record ao +> ser realizada). -> **🟢 WORKING TREE LIMPO** após commit/push de 19/05 da manhã. Migration -> nova (20260519000001) já rodada no DB local. Pronto pra próximo bloco. +> **🟢 WORKING TREE LIMPO** após commit/push de 19/05 noite. Fase 6 +> (lock-edit cobrada) ativada em Melissa também. Lock + popover atalho +> "Gerar fatura" + propagação cross-week de pacote upfront tudo +> funcionando. --- -## 🔴 PRÓXIMO PASSO IMEDIATO — Cenário 7 (Pacote UPFRONT) +## 🔴 PRÓXIMO PASSO IMEDIATO — Cenário 8 (Pacote SALDO) | Campo | Valor | |---|---| -| Paciente | **Donald Winnicott** | +| Paciente | **Carl Jung** | | Frequência | **Semanal · 4 ocorrências** | -| Serviço | qualquer (ex: Sessão particular R$ 200) | -| Cobrança ao salvar | **Gerar cobrança** + estilo **"Pacote único (upfront)"** | -| Total esperado | **R$ 800** (4 × 200) | +| Serviço | Sessão (R$ 40 cada) | +| Cobrança ao salvar | **Pacote único** + estilo **"Saldo (Cliniko)"** | +| Total contrato | **R$ 160** (4 × 40) | **Esperado:** -- 1 row em `recurrence_rules` + **1 row em `agenda_eventos`** (1ª materializada) -- 1 row em `billing_contracts` (type=package, charging_style=upfront, total_sessions=4, package_price=800) -- **1 row em `financial_records`** com amount=800, status=pending (cobrança ÚNICA do pacote, vencimento na 1ª sessão) -- Agenda: 1ª com badge $ (R$ 800 a cobrar) + 3 virtuais limpas (cobertas pelo pacote) +- 1 row em `recurrence_rules` +- **0 rows em `agenda_eventos`** materializadas inicialmente (saldo NÃO materializa 1ª) +- 1 row em `billing_contracts` (type=package, charging_style=**saldo**, total_sessions=4, package_price=160) +- **0 rows em `financial_records`** (sem cobrança imediata — modelo Cliniko) +- Agenda: 4 ocorrências virtuais **TODAS LIMPAS** (sem badge $, sem barra verde) — saldo intencionalmente não propaga estado, cada sessão gera cobrança individual quando vira realidade -Após C7: C8 (saldo) → C9 (per_session) → C10-C13 (status change + edit cobrada). +Diferença chave vs upfront: +- Upfront: 1 cobrança única (paga ou pendente) cobre todas as 4 sessões +- Saldo: contrato sem cobrança; cada sessão materializada GERA sua própria cobrança ao virar realidade (status realizado/faltou via flow C10-C12) + +Após C8: C9 (per_session) → C10-C13 (status change + edit cobrada). Quando todos passarem, replicar em **Rail** (`AgendaTerapeutaPage.vue`) e **Clínica** (`AgendaClinicaPage.vue`). --- +## 📦 O que foi feito em 19/05 noite (C7 + lock-edit + propagação cross-week) + +### Cenário 7 ✅ (Pacote UPFRONT — Ana Souza Ferreira) +Testado e validado. Usuária criou Ana, R$ 200/sessão × 4 = R$ 800, marcou como pago em dinheiro pelo Financeiro. Visualização correta em mês AND em semana navegando pelas 4 semanas. + +### Fase 6 (lock-edit cobrada) ativada em Melissa +Antes: `loadOccFinancialRecord` tinha guard `if (!props.occurrenceMode) return;` — só carregava em Rail/Clínica (edição de ocorrência). Em Melissa, `sessionPaymentRecord` paralelo alimentava só o Resumo lateral, sem trigger de lock. + +Agora unificado: `occFinancialRecord` carrega em ambos modos: +- Card Sessão / Honorários ganha **Tag** (em vez de Select billingType) quando há cobrança +- Body do card mostra **Message "Cobrança de R$ X já emitida"** + cadeado +- Tipo de cobrança (Particular/Convênio/Gratuito) bloqueado +- Edição de serviços/preço bloqueada + +### Propagação cross-week de pacote upfront pago/pendente +**Bug descoberto durante C7:** ao navegar pra semanas futuras (onde só virtual da Ana 2/3/4 aparecia, sem real event paid na view), o `_rulePaymentMap` era zerado pelo else branch do bulk-load → virtuais perdiam estado paid. + +Fix em `useMelissaAgenda.js _reloadRange`: +- Maps (paymentStateMap, amountMap, rulePaymentMap) inicializados SEMPRE no início +- Propagação agora roda **independente de realIds.length** (ie, mesmo em semanas só-com-virtuais) +- Coleta `ruleIdsInView` de TODOS eventos da view (reais + virtuais com recurrence_id) +- Cross-week query: pra cada rule em view, busca QUALQUER evento sibling (inclusive em outras semanas) + seus records paid/pending → determina estado do contrato +- Propaga estado pra eventos reais (via map) + virtuais (via rulePaymentMap acessado pelo normalize) + +### Atalho "Gerar fatura" no popover +- Pill amber pequeno ao lado de "A cobrar R$ X" no popover (`paymentVariant === 'none' && !is_occurrence`) +- Click → `gerarCobrancaManual` direto, fecha popover pra impedir double-click +- Tooltip: "Gerar fatura agora" + +### Info de pacote no popover +- Header agora mostra `Sessão · Pacote · N sessões` (computed `seriesLabel` lê de `_raw` do rule) + +### Botão "Excluir série inteira" +- Novo emit `delete-series` em `MelissaEventoPanel` + botão ao lado de "Excluir sessão" quando evento tem `recurrence_id` +- Handler `onDeleteSeries` em MelissaLayout faz hard delete: `financial_records` pendentes → `agenda_eventos` materializados → `recurrence_rules` (CASCADE leva exceptions + rule_services) +- Bloqueia se algum record tem `status='paid'` (estornar primeiro) + +### RPC `create_financial_record_for_session` ignora cancelled +**Migration 20260519000001:** idempotência da RPC passou a filtrar `AND status != 'cancelled'` além de `deleted_at IS NULL`. Antes: cancelar cobrança sem querer → todo "Gerar fatura" subsequente retornava o cancelado em vez de criar nova. Toast verde mentindo. + +Memória durável em `memory/project_rpc_idempotency_cancelled.md`. + +### `cancel_session` exception some da agenda +- `useRecurrence.expandRules` agora pula ocorrência com `exception.type === 'cancel_session'` (era visível com status cancelado; doc dizia "some da agenda" mas código mantinha) +- `patient_missed` / `therapist_canceled` / `holiday_block` permanecem visíveis como histórico + +### `recurrence_exceptions` cancel idempotente +- Cancel de ocorrência (virtual e materializada) usa `upsert` com `onConflict: 'recurrence_id,original_date'` — não quebra mais com unique violation quando há exception zumbi de tentativa anterior. + +### Visualização paid/pending de upfront em virtuais +- `MelissaEventoPanel.showPaymentRow` antes excluía virtuais incondicionalmente. Agora só esconde quando `paymentState === 'none'` (saldo/sem pacote continua limpo; upfront propagado mostra). +- `MelissaAgenda.fcEvents`: removida exigência de `!is_occurrence` no `isPaidSession` e no badge $ pendente. Virtuais herdadas via propagação mostram borda verde/badge amber. + +### `onVerLancamentos` cobre virtual de upfront +- Antes: virtual sempre toast "Sem lançamentos". Agora: busca records via siblings da série pra encontrar o do pacote. Saldo/sem pacote continua com toast. + +### Confirmação 3 decisões UX (não codar) +Antes de C7, user perguntou e concordou: +1. Editar serviço já lançado e pago → **NÃO** (cobrança fiscal imutável) +2. Alternar Particular/Convênio/Gratuito em série com cobrança ativa → **NÃO** (mesma razão) +3. "Gerar fatura" extra em sessão coberta por contrato upfront → **NÃO** (duplicaria cobrança) +Tudo isso o lock-edit (Fase 6 ativada acima) cobre. + +--- + ## 📦 O que foi feito em 18/05 ### Cenário 4 (Joyce · "Já recebi") ✅ @@ -150,15 +223,13 @@ User tentou rodar C5 e bateu em 3 problemas seguidos. Cada um virou um fix: | 4 | Avulsa "já recebi" no salvar | ✅ | | 5 | Avulsa convênio (Sándor + Unimed) | ✅ | | 6 | Recorrente sem pacote (Maria Magali / Anna Freud) | ✅ | -| **7** | **Pacote UPFRONT (Donald Winnicott 4 × R$ 200)** | 🔴 **PRÓXIMO** | -| 6 | Recorrente sem pacote (Anna Freud 4 sem) | ⏳ | -| 7 | Pacote upfront (Donald Winnicott 4× R$ 200) | ⏳ | -| 8 | Pacote saldo (Carl Jung 4× R$ 40) | ⏳ | +| 7 | Pacote upfront (Ana Souza Ferreira 4 × R$ 200) | ✅ | +| **8** | **Pacote saldo (Carl Jung 4 × R$ 40)** | 🔴 **PRÓXIMO** | | 9 | 1 por sessão (Michael Balint 12 sem) | ⏳ | | 10 | Status change avulsa (realizado/faltou/cancelado) | ⏳ | | 11 | Status change pacote saldo | ⏳ | | 12 | Antecipar pagamento (Carl Jung) | ⏳ | -| 13 | Edit cobrada | ⏳ | +| 13 | Edit cobrada | ⏳ (parcialmente — lock ativo em Melissa pós-19/05 noite) | --- diff --git a/Obsidian/Brain/log.md b/Obsidian/Brain/log.md index eae2218..a86c16d 100644 --- a/Obsidian/Brain/log.md +++ b/Obsidian/Brain/log.md @@ -14,6 +14,65 @@ Chronological, append-only record of everything that's happened in this wiki. --- +## [2026-05-19 23:00] session | C7 OK + Fase 6 lock em Melissa + cross-week propagation +Touched: project_cross_week_propagation (nova) +Detalhes: rodada longa cobrindo C7 (pacote upfront, Ana Souza Ferreira 4xR$200=R$800), +ativacao do lock-edit (Fase 6) em Melissa e correcao de bug cross-week +na propagacao de estado de pagamento. + +CENARIO 7 (Pacote UPFRONT): +- User criou Ana, pagou os R$800 em dinheiro pelo Financeiro +- Borda verde + popover "Pago R$800" no card materializado funcionou +- 4 pills no dialog da serie, todas com "Cobranca paga R$800 do pacote" + ate as virtuais (via synthesized record em loadOccFinancialRecord) + +LOCK-EDIT (Fase 6) ATIVADO EM MELISSA: +- Antes: loadOccFinancialRecord tinha guard occurrenceMode, so ativo + em Rail/Clinica. Em Melissa havia sessionPaymentRecord paralelo so + pro Resumo lateral (sem trigger de lock). +- Agora unificado: removido guard, occFinancialRecord carrega em ambos + modos. Card Sessao/Honorarios ganha Tag em vez de Select billingType + quando ha cobranca. Body mostra Message lock + cadeado. + AgendaEventoFinanceiroPanel so renderiza se record real (nao + sintetizado). +- Antes do codar, alinhei 3 perguntas de UX com user: + 1) Editar servico pago? NAO (cobranca fiscal imutavel) + 2) Alternar Particular/Convenio/Gratuito em serie cobrada? NAO + 3) Gerar fatura individual em upfront? NAO (duplicaria cobranca) + +CROSS-WEEK PROPAGATION (descoberto durante C7): +- Bug: virtuais isoladas em semanas futuras nao mostravam paid. +- Root cause: bulk-load tinha "if (realIds.length) { ... propagacao ... } + else { _rulePaymentMap.value = {} }". Quando user navegava pra semana + so com virtuais (sem reais), else zerava o ruleMap. +- Fix: maps inicializados SEMPRE, propagacao roda sempre, atribuicao + final tambem fora do if. Propagacao tambem ficou cross-week — pega + ruleIdsInView de TODOS eventos da view (real+virtual com recurrence_id), + busca records de QUALQUER evento da rule (em qualquer semana). Memoria + durador em project_cross_week_propagation.md. + +OUTROS FIXES NA RODADA: +- Atalho "Gerar fatura" no popover (pill amber ao lado de "A cobrar R$ X"). +- Info de pacote no header popover ("Sessao · Pacote · N sessoes"). +- Botao "Excluir serie inteira" no popover (hard delete, bloqueia se + algum record paid). +- Migration 20260519000001: RPC create_financial_record_for_session + ignora cancelled na idempotencia. Memoria em + project_rpc_idempotency_cancelled.md. +- cancel_session exception some da agenda (era visivel com status + cancelado, doc dizia que sumia). +- recurrence_exceptions cancel agora upsert idempotente (nao quebra + com unique violation em re-cancel). +- onVerLancamentos busca records via siblings da serie pra virtuais de + upfront. +- Visualizacao: virtuais herdadas via propagacao agora mostram borda + verde + linha verde no popover (showPaymentRow e isPaidSession + relaxados pra paymentState !== 'none'). + +PROXIMO: Cenario 8 (Carl Jung pacote SALDO 4x R$40 = R$160). +Esperado: 0 records iniciais, 4 virtuais limpas (saldo NAO propaga — +cada sessao gera cobranca quando vira realidade). + ## [2026-05-19 14:00] session | C5+C6 OK + atalho gerar fatura + RPC idempotencia fix Touched: project_rpc_idempotency_cancelled (nova) Detalhes: sessao longa cobrindo C5 e C6 ate green checkmark, com varios diff --git a/src/features/agenda/components/AgendaEventDialog.vue b/src/features/agenda/components/AgendaEventDialog.vue index ace059b..5459cbf 100644 --- a/src/features/agenda/components/AgendaEventDialog.vue +++ b/src/features/agenda/components/AgendaEventDialog.vue @@ -965,12 +965,15 @@ const occBillingStatusSeverity = computed(() => { // da cobrança (Pago / Pendente / Atrasada / Sem cobrança). Espelha // os 3 canais visuais da agenda (verde pago / amber pendente / // neutro). Sources: -// - sessionPaymentRecord (1 query em useAgendaEventLifecycle) +// - occFinancialRecord (lifecycle, agora carrega em ambos modos — +// guard de occurrenceMode foi removido em 2026-05-19 pra ativar +// lock-edit em Melissa tambem). Cobre record direto OU sintetizado +// a partir de contrato upfront ativo. // - eventRow.price (fallback pra "Sem cobrança · R$ X" quando // ainda nao ha record) const paymentSummary = computed(() => { if (!isSessionEvent.value) return null; - const rec = sessionPaymentRecord.value; + const rec = occFinancialRecord.value; const fmtDate = (d) => { if (!d) return ''; try { @@ -1770,16 +1773,22 @@ onBeforeUnmount(() => { />
- + @@ -2204,7 +2213,13 @@ onBeforeUnmount(() => { Sessão / Honorários + +