From 5965b05378f04ee9ff57578eb8dccfdc5f2ee824 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Wed, 20 May 2026 12:21:04 -0300 Subject: [PATCH] agenda: link universal pacote + refresh saldo no reverse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dois bugs descobertos durante C11/C+D: 1) Faltou+multa SEM consumeSaldo nao amarrava billing_contract_id no agenda_evento (so amarrava se consumeSaldo=true). Resultado: sessao 27/05 do Andre faltou+multa-sem-consume ficou sem rastro do contrato no DB. Reverse posterior nao detectaria saldoConsumed. Fix: bloco 1b) universal — sempre amarra quando forward (realizado/ faltou/cancelado) + tem contract + eventoId. Cobre todos os combos (multa-sem-consume, multa-com-consume, generatePackageCharge, consumeSaldo solo). 2) Reverse decrementar saldo as vezes nao persistia. Suspeita: race com ctx.billingContract.sessions_used stale do _loadStatusChangeContext quando flows rapidos sequenciais (Realizada+gerar -> Agendada imediato). Fix: refetch FRESH do billing_contracts.sessions_used direto do DB ANTES de calcular newUsed. Mais robusto contra qualquer race condition. Adicionado console.log pra futura debug. Removida duplicidade do amarra-billing_contract_id no bloco consumeSaldo (universal cobre). Backfill Andre Green: 27/05 amarrado, saldo voltou pra 2/4. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../melissa/composables/useMelissaAgenda.js | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/layout/melissa/composables/useMelissaAgenda.js b/src/layout/melissa/composables/useMelissaAgenda.js index bbc7420..9204c15 100644 --- a/src/layout/melissa/composables/useMelissaAgenda.js +++ b/src/layout/melissa/composables/useMelissaAgenda.js @@ -1529,14 +1529,26 @@ function _buildHandlers(deps) { } // 2) Devolver saldo ao pacote (se decidiu) + // Refetch sessions_used FRESH antes de decrementar pra evitar + // race condition com flows que rodaram entre _loadStatusChangeContext + // e este ponto (ex: Realizada+gerar imediatamente seguido de Agendada). if (decision.reverseRestoreSaldo && r.saldoConsumed && ctx.billingContract?.id) { try { - const newUsed = Math.max(0, (ctx.billingContract.sessions_used ?? 0) - 1); + const { data: freshContract, error: fetchErr } = await supabase + .from('billing_contracts') + .select('sessions_used, total_sessions, status') + .eq('id', ctx.billingContract.id) + .maybeSingle(); + if (fetchErr) throw fetchErr; + const currentUsed = freshContract?.sessions_used ?? 0; + const totalSessions = freshContract?.total_sessions ?? 0; + const newUsed = Math.max(0, currentUsed - 1); const patch = { sessions_used: newUsed }; // Se contrato estava 'completed' (atingiu total) e voltou abaixo, reativa. - if ((ctx.billingContract.sessions_used ?? 0) >= (ctx.billingContract.total_sessions ?? 0)) { + if (currentUsed >= totalSessions) { patch.status = 'active'; } + console.log('[Fase5/reverse] decrementando saldo:', { from: currentUsed, to: newUsed, contractId: ctx.billingContract.id }); const { error: dErr } = await supabase.from('billing_contracts').update(patch).eq('id', ctx.billingContract.id); if (dErr) throw dErr; } catch (e) { @@ -1564,9 +1576,7 @@ function _buildHandlers(deps) { // 1) Consumir saldo (pacote saldo + faltou/cancelado + decisão sim) // ⚠ billing_contracts NÃO tem coluna updated_at — passar esse campo // causa "column does not exist" silenciosamente em Promise.allSettled. - // Também precisa amarrar billing_contract_id no evento — sem isso, o - // reverse não detecta saldoConsumed depois (bug cascata descoberto - // durante teste C11/B: Falta+Descontar, depois Agendada não devolvia). + // Amarração de billing_contract_id no evento é feita em 1b) universal. if (decision.consumeSaldo && ctx.billingContract?.id) { tasks.push( supabase @@ -1576,7 +1586,15 @@ function _buildHandlers(deps) { }) .eq('id', ctx.billingContract.id) ); - // Amarra evento ao contrato pra rastreabilidade + reverse correto + } + + // 1b) Amarra evento ao contrato — universal pra forward em pacote. + // Antes só rodava em consumeSaldo / generatePackageCharge. Faltou+multa + // SEM consume era exceção: evento ficava sem billing_contract_id, + // impedindo o reverse de detectar o vínculo depois. Fix: amarrar + // sempre que há contract envolvido + status forward + eventoId real. + const isForwardStatus = novoStatus === 'realizado' || novoStatus === 'faltou' || novoStatus === 'cancelado'; + if (isForwardStatus && ctx.billingContract?.id && eventoId) { tasks.push( supabase .from('agenda_eventos')