diff --git a/HANDOFF.md b/HANDOFF.md index 7131306..18cf795 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,19 +1,28 @@ -# HANDOFF — 2026-05-20 madrugada (C1-C9 ✅, próximo C10) +# HANDOFF — 2026-05-20 (C10 pré-teste · code-fix + seeds prontos) 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-C9 ✅**. -> Próximo: **Cenário 10** (status change avulsa — Joyce/Ana/Sándor: -> marcar como realizado/faltou/cancelado e ver consequências no -> financeiro via STATUS_TO_EXCEPTION + financial_exceptions). +> **🎯 SE A FORÇA CAIR / SESSÃO PERDER CONTEXTO:** estamos no **Cenário 10** +> (status change avulsa). Code-fix pra cancelar pendingRecord aplicado + +> financial_exceptions da Melissa seedadas no DB. **Falta**: rodar os 5 +> sub-testes (a/a2/b/c/c2) e validar. -> **🟢 WORKING TREE LIMPO** após commit/push do checkpoint pós-C8/C9. -> Per-session funcionando (12 events + 12 records gerados em batch). -> Financeiro com rowGroup por paciente (expand/collapse). Bubble-up -> @cobranca-atualizada → M.refetch faz o card da agenda atualizar -> badge/borda imediatamente após pagar. +> **🟢 PRÉ-TESTE C10 SALVO**. Pós-teste, próximos cenários: C11 (status +> change pacote saldo) → C12 (antecipar pagamento) → C13 (edit cobrada). + +### Code-fix aplicado em 20/05 (pré-C10) +- **`useMelissaAgenda.js:1450-1505`** — `_applyStatusDecisions` agora cancela + o `ctx.pendingRecord` quando faltou/cancelado (com ou sem multa). Antes + inseria a multa mas DEIXAVA o original pending → cobrança dupla + (R$ 200 + R$ 30 = R$ 230). Audit trail vai em `notes` do record + cancelado, descrição da multa nova carrega data: "Multa por falta · sessão 20/05/26". +- **`useAgendaFinanceiro.js:59`** — fix dormente `'fixed'` → `'fixed_fee'` + (off-by-key contra schema; path nunca exercitado na Melissa, mas iria + quebrar se algum dia fosse). + +### Financial exceptions seedadas (tenant Bruno Terapeuta / owner Leonardo) +- `patient_no_show` → `fixed_fee R$ 30` +- `patient_cancellation` → `full`, `min_hours_notice=2`, `default_consume_on_miss=true` --- diff --git a/src/composables/useAgendaFinanceiro.js b/src/composables/useAgendaFinanceiro.js index 6dca753..46aef5f 100644 --- a/src/composables/useAgendaFinanceiro.js +++ b/src/composables/useAgendaFinanceiro.js @@ -56,7 +56,7 @@ const STATUS_TO_EXCEPTION = { function calcChargeAmount(originalAmount, rule) { if (!rule || rule.charge_mode === 'none') return 0; if (rule.charge_mode === 'full') return originalAmount; - if (rule.charge_mode === 'fixed') return rule.charge_value ?? 0; + if (rule.charge_mode === 'fixed_fee') return rule.charge_value ?? 0; if (rule.charge_mode === 'percentage') { const pct = rule.charge_pct ?? 0; return parseFloat(((originalAmount * pct) / 100).toFixed(2)); diff --git a/src/layout/melissa/composables/useMelissaAgenda.js b/src/layout/melissa/composables/useMelissaAgenda.js index 06f0889..eb1ea6c 100644 --- a/src/layout/melissa/composables/useMelissaAgenda.js +++ b/src/layout/melissa/composables/useMelissaAgenda.js @@ -1447,9 +1447,12 @@ function _buildHandlers(deps) { ); } - // 2) Aplicar multa (cria financial_record avulsa) + // 2) Aplicar multa (cria financial_record avulsa). Description leva + // data da sessão pra paciente identificar na fatura mesmo após cancel. if (decision.applyFine && decision.fineAmount > 0) { const dueIso = row.inicio_em ? new Date(row.inicio_em).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10); + const sessaoLabel = row.inicio_em ? new Date(row.inicio_em).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit', year: '2-digit' }) : ''; + const fineDesc = novoStatus === 'faltou' ? `Multa por falta · sessão ${sessaoLabel}` : `Taxa de cancelamento tardio · sessão ${sessaoLabel}`; const finePayload = { owner_id: uid, tenant_id: tenantId, @@ -1457,7 +1460,7 @@ function _buildHandlers(deps) { agenda_evento_id: eventoId, amount: decision.fineAmount, final_amount: decision.fineAmount, - description: novoStatus === 'faltou' ? 'Multa por falta (no-show)' : 'Taxa de cancelamento', + description: fineDesc.trim(), status: 'pending', due_date: dueIso, type: 'receita' @@ -1475,6 +1478,35 @@ function _buildHandlers(deps) { ); } + // 2b) Cancelar cobrança original (faltou/cancelado + pendingRecord). + // A sessão não aconteceu/foi cancelada → original substituída pela + // multa (se aplicada) ou simplesmente cancelada. Sem isso cobrava + // dobrado: original R$200 pending + multa R$30 = R$230. Audit trail + // preserva original em notes. + const isFaltouOuCancelado = novoStatus === 'faltou' || novoStatus === 'cancelado'; + if (isFaltouOuCancelado && ctx.pendingRecord?.id) { + const reasonText = decision.applyFine + ? novoStatus === 'faltou' + ? 'Cancelada — substituída por multa de no-show' + : 'Cancelada — substituída por taxa de cancelamento tardio' + : novoStatus === 'faltou' + ? 'Cancelada — sessão não realizada (paciente faltou)' + : 'Cancelada — sessão cancelada'; + const today = new Date().toISOString().slice(0, 10); + const noteEntry = `[${today}] ${reasonText}`; + const noteText = ctx.pendingRecord.notes ? `${ctx.pendingRecord.notes}\n${noteEntry}` : noteEntry; + tasks.push( + supabase + .from('financial_records') + .update({ + status: 'cancelled', + notes: noteText, + updated_at: new Date().toISOString() + }) + .eq('id', ctx.pendingRecord.id) + ); + } + // 3) Realizado avulsa pendente: marcar pendingRecord como pago (ou só status) if (decision.markPaid && ctx.pendingRecord?.id) { tasks.push(