agenda: C8 OK + Usar/Revogar pacote saldo + UI de contract + ajustes UX
Cenário 8 (Pacote SALDO — Otávio Souza Ferreira 12 × R$ 50)
- Testado e passou. DB: 1 rule, 0 events, 1 contract (saldo), 0 records.
Visual: 12 virtuais limpas no calendário.
UI de pacote (saldo + upfront)
- _ruleContractMap em useMelissaAgenda: bulk-load popula contract info
(id, style, totalSessions, sessionsUsed, packagePrice) por
recurrence_id. Query recurrence_rules.patient_id como fonte
autoritativa — cobre saldo sem materializadas (sem isso, ruleToPatient
via records vinha vazio pra saldo)
- normalize injeta `contract` no evento via ruleContractMap
- MelissaEventoPanel: nova linha colorida (violeta saldo, verde upfront)
com "Pacote saldo · N/M usadas" ou "Pacote · N/M realizadas"
- AgendaEventDialog: info card mt-4 com header+body+hint explicando
modelo, gateado por occFinancialLoading (spinner durante carga
pra evitar piscar entre Usar/Revogar)
Handlers Usar/Revogar atômicos
- onUsarSessao em MelissaLayout: materializa virtual (preserva
determined_commitment_id da regra) → status=realizado +
billing_contract_id → create_financial_record_for_session →
sessions_used++ → (se atingiu total) contract.status=completed
- onRevogarSessao: cancela record + sessions_used-- + reativa contract
se estava completed + status=agendado. Bloqueia se record paid
(precisa estorno formal pelo Financeiro)
- Ambos aceitam payload {eventRow, contract} do dialog OU fallback
pra eventoSelecionado do popover
- Botão "Usar" verde no popover (paymentState=none) substituído por
"Revogar" vermelho (paymentState=pending). Equivalente "Usar agora"/
"Revogar uso" no info card do dialog
Fix enum status_evento_agenda
- 'realizada' não existe no enum — DB exige 'realizado' (masculino).
Corrigido em todas as ocorrências do handler
Fix campo "Título" indevido em sessão
- Sessão sem determined_commitment_id → selectedCommitment=null →
isSessionEvent=false → mostra campo Título (que é só pra não-sessão)
- Fix: materialize do Usar inclui determined_commitment_id (insert
path); update path backfilla via query da rule se NULL; Revogar
também backfilla pra consistência
Fix "Gerar fatura" não cabe em saldo
- Botão "Gerar fatura" do popover hide quando há contractInfo. Em
saldo, gerar fatura solta criaria cobrança duplicada sem incrementar
sessions_used. Fluxo correto: "Usar"
Recorrências Aplicadas — UI
- Header stats coloridos: total **azul**, realizadas **verde**,
faltaram **amber**, canceladas **cinza**, remarcadas **violeta**
- Pills com badge sólido por status (emerald-600 realizado, amber-600
faltou, stone-500 cancelado, violet-600 remarcado)
Race condition no dialog
- AgendaEventDialog mostrava botões Usar/Revogar baseado em
occFinancialRecord async; durante ~500ms de load, botão errado
podia piscar. Fix: spinner "Verificando estado…" enquanto
occFinancialLoading=true; botões só renderizam após
- Popover não fixado (race window pequena, fechar/reabrir resolve)
3 decisões UX confirmadas antes de codar
- Editar serviço pago → NÃO (cobrança fiscal imutável)
- Alternar Particular/Convênio/Gratuito em série cobrada → NÃO
- Gerar fatura individual em pacote upfront → NÃO (duplicação)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,14 @@ export function useAgendaEventLifecycle({
|
||||
// continua via occFinancialRecord (território da Fase 6/C13).
|
||||
const sessionPaymentRecord = ref(null);
|
||||
|
||||
// sessionContract (2026-05-19 noite): billing_contract ativo do paciente
|
||||
// quando a sessão pertence a uma série com pacote (upfront ou saldo).
|
||||
// Usado pra exibir info card no dialog explicando o pacote (qtd
|
||||
// sessões usadas/restantes, valor, comportamento). Pra saldo, é a
|
||||
// única forma do user entender o que tá acontecendo (nada aparece
|
||||
// no /financeiro até realizar sessão).
|
||||
const sessionContract = ref(null);
|
||||
|
||||
// ── computeds locais ───────────────────────────────────────
|
||||
const serieCountByStatus = computed(() => {
|
||||
const counts = {};
|
||||
@@ -323,6 +331,33 @@ export function useAgendaEventLifecycle({
|
||||
}
|
||||
}
|
||||
|
||||
// loadSessionContract (2026-05-19 noite): busca billing_contract ativo
|
||||
// do paciente quando o evento pertence a uma série com pacote. Usado
|
||||
// pra info card no dialog explicando o pacote saldo/upfront.
|
||||
async function loadSessionContract() {
|
||||
sessionContract.value = null;
|
||||
const patientId = props.eventRow?.paciente_id || props.eventRow?.patient_id;
|
||||
const ruleId = props.eventRow?.recurrence_id;
|
||||
// Só faz sentido pra sessão de série
|
||||
if (!patientId || !ruleId) return;
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('billing_contracts')
|
||||
.select('id, type, total_sessions, sessions_used, package_price, charging_style, status, active_from')
|
||||
.eq('patient_id', patientId)
|
||||
.eq('type', 'package')
|
||||
.eq('status', 'active')
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(1)
|
||||
.maybeSingle();
|
||||
if (error) throw error;
|
||||
sessionContract.value = data ?? null;
|
||||
} catch (e) {
|
||||
console.warn('[session-contract] erro ao carregar:', e?.message);
|
||||
sessionContract.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
function onPillEditClick(ev) {
|
||||
emit('editSeriesOccurrence', {
|
||||
id: ev.id,
|
||||
@@ -508,6 +543,7 @@ export function useAgendaEventLifecycle({
|
||||
// sessionPaymentRecord: carrega em qualquer edit (Melissa
|
||||
// tambem) pra alimentar a linha "Cobrança" do Resumo lateral.
|
||||
loadSessionPaymentRecord();
|
||||
loadSessionContract();
|
||||
|
||||
// occurrenceMode: editando UMA ocorrencia de serie ja existente —
|
||||
// tipo de compromisso ja foi escolhido (paciente + sessao). Pular
|
||||
@@ -628,6 +664,8 @@ export function useAgendaEventLifecycle({
|
||||
loadSerieEvents,
|
||||
loadOccFinancialRecord,
|
||||
loadSessionPaymentRecord,
|
||||
sessionContract,
|
||||
loadSessionContract,
|
||||
onPillEditClick,
|
||||
onPillStatusChange,
|
||||
onPillDeleteClick,
|
||||
|
||||
Reference in New Issue
Block a user