agenda: popover sincroniza com eventos + antecipar nao reusa cancelled

Dois bugs descobertos no ciclo C12 antecipar -> revogar -> reantecipar:

1) confirmAnteciparPagamento reusava record cancelled:
   Quando user revogava (vira cancelled) e antecipava de novo,
   o existRec query pegava o cancelled e UPDATE-ava pra paid no
   MESMO record id. Resultado: notes mantinham historico
   "Cancelada via reversao" + "Antecipacao revogada" + record
   reativo como paid, confuso pra audit trail. Fix: filtrar
   .neq('status', 'cancelled') na busca de existRec — agora a
   re-antecipacao via RPC cria record fresh.

2) Popover snapshot stale (pendencia documentada em
   project_melissa_popover_snapshot, antecipada pra agora):
   eventoSelecionado.value era snapshot do clique e nao acompanhava
   updates do _paymentStateMap pos M.refetch. User antecipava, o
   record paid era criado, mas o popover continuava com paymentState
   antigo -> botao continuava "Antecipar pagamento" em vez de
   alternar pra "Revogar pagamento". Fix: watch em M.eventos sincroniza
   eventoSelecionado com a versao fresh quando id bate. flush:'post'
   pra rodar apos o computed reagir.

Como o popover agora atualiza in-place, removido fecharEvento() de
confirmAnteciparPagamento e onRevogarAntecipacao — o user pode ver
o botao alternar live em vez de precisar reabrir o popover.

Cleanup do estado do Andre: deletado record orfa 3a4c79e0 pra reset
do teste C12.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-20 15:07:11 -03:00
parent 272c804335
commit b5e00a7022
+27 -5
View File
@@ -665,6 +665,21 @@ function fecharEvento() {
eventoBusy.value = false; eventoBusy.value = false;
} }
// Mantém eventoSelecionado em sincronia com a lista reativa M.eventos.
// Sem isso, eventoSelecionado.value é snapshot do clique e não acompanha
// updates do _paymentStateMap pós refetch (caso típico: revogar/antecipar
// pagamento — record muda mas popover continuava mostrando estado antigo).
// Watch dispara apenas quando o ev correspondente aparece novo no eventos
// computed (paymentState atualizado pelo bulk-load).
watch(() => M.eventos.value, (novos) => {
const sel = eventoSelecionado.value;
if (!sel?.id) return;
const fresh = novos.find((e) => e.id === sel.id);
if (fresh && fresh !== sel) {
eventoSelecionado.value = fresh;
}
}, { flush: 'post' });
// ── Actions do MelissaEventoPanel ────────────────────────────── // ── Actions do MelissaEventoPanel ──────────────────────────────
// Fase 5 (2026-05-14): TODOS os status (realizado/faltou/cancelado/etc) // Fase 5 (2026-05-14): TODOS os status (realizado/faltou/cancelado/etc)
// passam por M.onUpdateSeriesEvent — que abre o AgendaStatusChangeConfirmDialog // passam por M.onUpdateSeriesEvent — que abre o AgendaStatusChangeConfirmDialog
@@ -811,12 +826,17 @@ async function confirmAnteciparPagamento() {
} }
} }
// 2) Verifica se já tem financial_record vinculado // 2) Verifica se já tem financial_record vinculado.
// IMPORTANTE: filtra cancelled — caso típico após revogar a antecipação
// (record vira cancelled) e user re-antecipa. Sem o filtro, o handler
// reusava o record cancelled atualizando pra paid, mantendo notes
// da revogação no audit trail (confuso).
const { data: existRec } = await supabase const { data: existRec } = await supabase
.from('financial_records') .from('financial_records')
.select('id, status') .select('id, status')
.eq('agenda_evento_id', eventoId) .eq('agenda_evento_id', eventoId)
.is('deleted_at', null) .is('deleted_at', null)
.neq('status', 'cancelled')
.order('created_at', { ascending: false }) .order('created_at', { ascending: false })
.limit(1) .limit(1)
.maybeSingle(); .maybeSingle();
@@ -869,9 +889,10 @@ async function confirmAnteciparPagamento() {
life: 4000 life: 4000
}); });
anteciparDialogOpen.value = false; anteciparDialogOpen.value = false;
M.refetch(); await M.refetch();
refetchEventosHoje(); refetchEventosHoje();
fecharEvento(); // Não fecha o popover — watch em eventos sincroniza o ev pro novo
// estado (paymentState='paid' agora). Botão alterna pra "Revogar".
} catch (e) { } catch (e) {
toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao antecipar pagamento.', life: 5000 }); toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao antecipar pagamento.', life: 5000 });
} finally { } finally {
@@ -943,9 +964,10 @@ async function onRevogarAntecipacao() {
detail: `Cobrança de R$ ${Number(paidRec.final_amount || paidRec.amount || 0).toFixed(2).replace('.', ',')} cancelada.`, detail: `Cobrança de R$ ${Number(paidRec.final_amount || paidRec.amount || 0).toFixed(2).replace('.', ',')} cancelada.`,
life: 4000 life: 4000
}); });
M.refetch(); await M.refetch();
refetchEventosHoje(); refetchEventosHoje();
fecharEvento(); // Não fecha popover — watch em eventos sincroniza paymentState='none'.
// Botão alterna de volta pra "Antecipar pagamento".
} catch (e) { } catch (e) {
toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao revogar antecipação.', life: 5000 }); toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao revogar antecipação.', life: 5000 });
} finally { } finally {