diff --git a/src/layout/melissa/MelissaEventoPanel.vue b/src/layout/melissa/MelissaEventoPanel.vue index 4013aca..00b17f0 100644 --- a/src/layout/melissa/MelissaEventoPanel.vue +++ b/src/layout/melissa/MelissaEventoPanel.vue @@ -40,6 +40,7 @@ const emit = defineEmits([ 'delete-series', // botão "Excluir série inteira" — hard delete da regra + materializadas + records pendentes 'ver-lancamentos', // botão "Lançamentos" — abre dialog com financial_records vinculados 'antecipar-pagamento', // botão "Antecipar pagamento" — paciente quer pagar antes da sessão (pacote saldo) + 'trocar-metodo-antecipacao', // botão "Trocar método" — UPDATE no record paid sem cancel+criar novo (evita lixo cancelled) 'gerar-cobranca', // botão inline "Gerar fatura" ao lado de "A cobrar R$ X" — atalho sem precisar abrir dialog 'usar-sessao', // botão "Usar" no card de pacote saldo — materializa+realizada+gera cobrança individual 'revogar-sessao' // botão "Revogar" — desfaz Usar (cancela record + decrementa saldo). Bloqueado se já pago @@ -507,16 +508,26 @@ function modalidadeIcon(mod) { Antecipar pagamento - + + + + diff --git a/src/layout/melissa/MelissaLayout.vue b/src/layout/melissa/MelissaLayout.vue index af4c069..4644a34 100644 --- a/src/layout/melissa/MelissaLayout.vue +++ b/src/layout/melissa/MelissaLayout.vue @@ -759,6 +759,9 @@ const anteciparDialogOpen = ref(false); const anteciparMethod = ref('pix'); const anteciparBusy = ref(false); const anteciparEventoRef = ref(null); // snapshot do evento no momento do click +// mode: 'create' (Antecipar pagamento — novo record) vs 'update' (Trocar método +// — UPDATE no record paid existente, sem cancel+criar lixo de cancelled). +const anteciparMode = ref('create'); const anteciparMethodOptions = [ { value: 'pix', label: 'Já recebi — PIX' }, { value: 'dinheiro', label: 'Já recebi — Dinheiro' }, @@ -782,6 +785,39 @@ async function onAnteciparPagamento() { } anteciparEventoRef.value = ev; anteciparMethod.value = 'pix'; + anteciparMode.value = 'create'; + anteciparDialogOpen.value = true; +} + +// Trocar método de pagamento de uma antecipação ATIVA (record paid existente). +// Mesmo dialog do Antecipar, mas no submit faz UPDATE no record existente +// em vez de cancel+criar novo — evita acumular records cancelled no audit +// trail. Default seleciona o método atual pra UX clara. +async function onTrocarMetodoAntecipacao() { + const ev = eventoSelecionado.value; + if (!ev?.id) return; + const isVirtualId = typeof ev.id === 'string' && ev.id.startsWith('rec::'); + if (isVirtualId) { + toast.add({ severity: 'warn', summary: 'Sessão virtual', detail: 'Sessão sem antecipação ativa.', life: 3000 }); + return; + } + // Busca método atual do record paid pra pré-selecionar no dialog + try { + const { data: paidRec } = await supabase + .from('financial_records') + .select('payment_method') + .eq('agenda_evento_id', ev.id) + .eq('status', 'paid') + .order('paid_at', { ascending: false }) + .limit(1) + .maybeSingle(); + const current = paidRec?.payment_method; + anteciparMethod.value = ['pix', 'dinheiro', 'deposito', 'cartao_maquininha'].includes(current) ? current : 'pix'; + } catch { + anteciparMethod.value = 'pix'; + } + anteciparEventoRef.value = ev; + anteciparMode.value = 'update'; anteciparDialogOpen.value = true; } @@ -790,6 +826,53 @@ async function confirmAnteciparPagamento() { if (!ev || anteciparBusy.value) return; anteciparBusy.value = true; try { + // ─── MODE 'update': Trocar método ─────────────────────────── + // Apenas UPDATE no record paid existente. Sem materializar (já é real), + // sem RPC, sem novo record. Evita lixo cancelled no audit trail. + if (anteciparMode.value === 'update') { + const settlement = anteciparMethod.value; + const today = new Date().toISOString(); + const { data: paidRec, error: fetchErr } = await supabase + .from('financial_records') + .select('id, payment_method, notes') + .eq('agenda_evento_id', ev.id) + .eq('status', 'paid') + .order('paid_at', { ascending: false }) + .limit(1) + .maybeSingle(); + if (fetchErr) throw fetchErr; + if (!paidRec?.id) { + throw new Error('Antecipação não encontrada para troca de método.'); + } + const oldMethod = paidRec.payment_method || '—'; + const noteEntry = `[${today.slice(0, 10)}] Método trocado: ${oldMethod} → ${settlement}`; + const newNotes = paidRec.notes ? `${paidRec.notes}\n${noteEntry}` : noteEntry; + const patch = { + payment_method: settlement === 'link' ? 'asaas' : settlement, + status: settlement === 'link' ? 'pending' : 'paid', + paid_at: settlement === 'link' ? null : today, + notes: newNotes, + updated_at: today + }; + const { error: upErr } = await supabase + .from('financial_records') + .update(patch) + .eq('id', paidRec.id); + if (upErr) throw upErr; + + const methodLabel = anteciparMethodOptions.find((o) => o.value === settlement)?.label || settlement; + toast.add({ + severity: 'success', + summary: 'Método atualizado', + detail: methodLabel, + life: 3500 + }); + anteciparDialogOpen.value = false; + await M.refetch(); + refetchEventosHoje(); + return; + } + // ─── MODE 'create' (Antecipar pagamento — fluxo original) ── const tenantId = tenantStore.activeTenantId || tenantStore.tenantId || tenantStore.tenant?.id || null; // ownerId: ev.owner_id é prioridade. Fallback pra M.ownerId (composable // que conhece o user logado). Pra virtuais ou snapshots stale, ev pode @@ -2629,6 +2712,7 @@ function onKeydown(e) { @revogar-sessao="onRevogarSessao" @ver-lancamentos="onVerLancamentos" @antecipar-pagamento="onAnteciparPagamento" + @trocar-metodo-antecipacao="onTrocarMetodoAntecipacao" @revogar-antecipacao="onRevogarAntecipacao" @edit-paciente="onEditPaciente" @abrir-prontuario="onAbrirProntuario" @@ -3181,19 +3265,24 @@ function onKeydown(e) { v-model:visible="anteciparDialogOpen" modal :draggable="false" - header="Antecipar pagamento" + :header="anteciparMode === 'update' ? 'Trocar método de pagamento' : 'Antecipar pagamento'" :style="{ width: '480px', maxWidth: '96vw' }" >