agenda: link universal pacote + refresh saldo no reverse
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user