agenda: dialog pacote saldo realizada — 2 sub-questions claras

Antes (UX confusa): bloco "Gerar cobranca no pacote?" tinha so um
Select "Como cobrar?" com options mixadas:
- "Enviar link de pagamento (Asaas)"
- "Ja recebi - PIX"
- "Ja recebi - Dinheiro"
- etc

User selecionou "Ja recebi - PIX" pensando que era "cobrar via PIX"
durante teste C11/A com Andre Green. Resultado: fatura virou paid
sem o user ter recebido de verdade. Ambiguidade entre "como cobrar"
(header) e "ja recebi" (options).

Refactor: espelhar o padrao da avulsa (showRegistrarPagto):
1. Sub-question "A sessao ja foi paga?" radio Sim/Nao (default Nao)
2. Se Nao -> Select "Como vai cobrar?" [Apenas registrar pendente |
   Enviar link de pagamento (Asaas)]
3. Se Sim -> Select "Como recebeu?" [PIX | Dinheiro | Deposito |
   Maquininha] (sem prefixo "Ja recebi" — header ja deixa claro)

Defaults safer: markPaid=false em ambos contextos (avulsa e pacote)
pra evitar marcar paid sem querer. paymentMethod='pending' inicial.

Handler em useMelissaAgenda._applyStatusDecisions: pos-processamento
agora usa decision.markPaid explicito no caso pacote saldo:
- markPaid=true -> record vira paid + payment_method=X
- markPaid=false + paymentMethod='link' -> pending + payment_method='asaas'
- markPaid=false + paymentMethod='pending' -> pending sem metodo

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-20 11:11:28 -03:00
parent 7dc7dcede0
commit 079509e001
2 changed files with 74 additions and 37 deletions
@@ -72,8 +72,16 @@ watch(
consumeSaldo.value = !!props.regraExcecao?.default_consume_on_miss; consumeSaldo.value = !!props.regraExcecao?.default_consume_on_miss;
applyFine.value = _calcInitialFineApply(); applyFine.value = _calcInitialFineApply();
fineAmount.value = _calcInitialFineAmount(); fineAmount.value = _calcInitialFineAmount();
markPaid.value = true; // Default markPaid:
paymentMethod.value = 'pix'; // - Avulsa realizada (showRegistrarPagto): default false (manter pendente)
// - Pacote saldo realizada (showCobrancaPacote): default false (gerar pendente)
// Em ambos casos o user precisa selecionar ativamente "Sim, já recebi"
// pra registrar paid — evita marcar paid sem querer.
markPaid.value = false;
// paymentMethod default depende do contexto. Inicia 'pending' (que cai
// no select de "Como vai cobrar?" quando markPaid=false). Quando user
// troca pra "Sim, já recebi", precisa escolher PIX/Dinheiro/etc.
paymentMethod.value = 'pending';
generatePackageCharge.value = true; generatePackageCharge.value = true;
} }
); );
@@ -138,12 +146,13 @@ const paymentMethodOptions = [
{ value: 'deposito', label: 'Depósito' }, { value: 'deposito', label: 'Depósito' },
{ value: 'cartao_maquininha', label: 'Cartão (maquininha)' } { value: 'cartao_maquininha', label: 'Cartão (maquininha)' }
]; ];
const paymentMethodOptionsCobranca = [ // Opções pra "Como vai cobrar?" quando markPaid=false (sessão pendente
{ value: 'link', label: 'Enviar link de pagamento (Asaas)' }, // no pacote saldo). 'pending' = só registra como pendente, terapeuta
{ value: 'pix', label: 'Já recebi — PIX' }, // cobra depois pelo /financeiro. 'link' = gera link Asaas e marca
{ value: 'dinheiro', label: 'Já recebi — Dinheiro' }, // payment_method='asaas' no record (pós-confirm o handler updata).
{ value: 'deposito', label: 'Já recebi — Depósito' }, const paymentMethodOptionsPending = [
{ value: 'cartao_maquininha', label: 'Já recebi — Cartão (maquininha)' } { value: 'pending', label: 'Apenas registrar como pendente' },
{ value: 'link', label: 'Enviar link de pagamento (Asaas)' }
]; ];
const regraResumo = computed(() => { const regraResumo = computed(() => {
@@ -204,11 +213,16 @@ const fineDefaultReason = computed(() => {
// ─── Actions ─────────────────────────────────────────────────────────── // ─── Actions ───────────────────────────────────────────────────────────
function onConfirm() { function onConfirm() {
// markPaid agora é considerado em DOIS contextos:
// 1. Avulsa pendente (showRegistrarPagto): paciente já pagou a cobrança?
// 2. Pacote saldo realizado (showCobrancaPacote): já recebeu o valor da sessão?
// Em ambos casos: markPaid=true → record vira paid; false → fica pending.
const considerMarkPaid = showRegistrarPagto.value || (showCobrancaPacote.value && generatePackageCharge.value);
emit('confirm', { emit('confirm', {
consumeSaldo: showSaldoBlock.value ? consumeSaldo.value : false, consumeSaldo: showSaldoBlock.value ? consumeSaldo.value : false,
applyFine: showFineBlock.value ? applyFine.value : false, applyFine: showFineBlock.value ? applyFine.value : false,
fineAmount: showFineBlock.value && applyFine.value ? Number(fineAmount.value) || 0 : 0, fineAmount: showFineBlock.value && applyFine.value ? Number(fineAmount.value) || 0 : 0,
markPaid: showRegistrarPagto.value ? markPaid.value : false, markPaid: considerMarkPaid ? markPaid.value : false,
paymentMethod: paymentMethod.value, paymentMethod: paymentMethod.value,
generatePackageCharge: showCobrancaPacote.value ? generatePackageCharge.value : false generatePackageCharge: showCobrancaPacote.value ? generatePackageCharge.value : false
}); });
@@ -338,11 +352,35 @@ function onCancel() {
<Checkbox v-model="generatePackageCharge" inputId="asccd-gen-charge" binary /> <Checkbox v-model="generatePackageCharge" inputId="asccd-gen-charge" binary />
<label for="asccd-gen-charge" class="cursor-pointer">Gerar cobrança e consumir 1 sessão</label> <label for="asccd-gen-charge" class="cursor-pointer">Gerar cobrança e consumir 1 sessão</label>
</div> </div>
<div v-if="generatePackageCharge" class="asccd-method-row"> <!-- Sub-question 1: a sessão foi paga? (espelha o padrão da avulsa) -->
<label class="asccd-method-label">Como cobrar?</label> <div v-if="generatePackageCharge" class="asccd-radio-group mt-2">
<label class="asccd-radio">
<input type="radio" :value="false" v-model="markPaid" />
<span>Não, gerar como cobrança pendente</span>
</label>
<label class="asccd-radio">
<input type="radio" :value="true" v-model="markPaid" />
<span>Sim, recebi</span>
</label>
</div>
<!-- Sub-question 2a: se "Já recebi" método de recebimento (sem prefixo) -->
<div v-if="generatePackageCharge && markPaid" class="asccd-method-row">
<label class="asccd-method-label">Como recebeu?</label>
<Select <Select
v-model="paymentMethod" v-model="paymentMethod"
:options="paymentMethodOptionsCobranca" :options="paymentMethodOptions"
optionLabel="label"
optionValue="value"
size="small"
class="asccd-method-select"
/>
</div>
<!-- Sub-question 2b: se "Pendente" forma de cobrança (link Asaas vs registrar simples) -->
<div v-if="generatePackageCharge && !markPaid" class="asccd-method-row">
<label class="asccd-method-label">Como vai cobrar?</label>
<Select
v-model="paymentMethod"
:options="paymentMethodOptionsPending"
optionLabel="label" optionLabel="label"
optionValue="value" optionValue="value"
size="small" size="small"
@@ -1567,8 +1567,12 @@ function _buildHandlers(deps) {
toast.add({ severity: 'success', summary: 'Status atualizado', detail: 'Decisões aplicadas.', life: 2500 }); toast.add({ severity: 'success', summary: 'Status atualizado', detail: 'Decisões aplicadas.', life: 2500 });
} }
// Pós: se gerou cobrança via link Asaas, marcar payment_method='asaas' // Pós-processamento do record gerado pelo pacote saldo. Agora o
if (decision.generatePackageCharge && decision.paymentMethod === 'link' && eventoId) { // decision tem markPaid explícito:
// - markPaid=true → vira paid + payment_method=PIX/dinheiro/etc
// - markPaid=false + paymentMethod='link' → pending + payment_method='asaas'
// - markPaid=false + paymentMethod='pending' → pending sem método (default)
if (decision.generatePackageCharge && eventoId) {
try { try {
const { data: newRec } = await supabase const { data: newRec } = await supabase
.from('financial_records') .from('financial_records')
@@ -1578,29 +1582,24 @@ function _buildHandlers(deps) {
.limit(1) .limit(1)
.single(); .single();
if (newRec?.id) { if (newRec?.id) {
await supabase.from('financial_records').update({ payment_method: 'asaas', updated_at: new Date().toISOString() }).eq('id', newRec.id); if (decision.markPaid) {
} await supabase
} catch { /* silencioso */ } .from('financial_records')
} else if (decision.generatePackageCharge && decision.paymentMethod !== 'link' && eventoId) { .update({
// Já recebi → marca como paid status: 'paid',
try { paid_at: new Date().toISOString(),
const { data: newRec } = await supabase payment_method: decision.paymentMethod,
.from('financial_records') updated_at: new Date().toISOString()
.select('id') })
.eq('agenda_evento_id', eventoId) .eq('id', newRec.id);
.order('created_at', { ascending: false }) } else if (decision.paymentMethod === 'link') {
.limit(1) await supabase
.single(); .from('financial_records')
if (newRec?.id) { .update({ payment_method: 'asaas', updated_at: new Date().toISOString() })
await supabase .eq('id', newRec.id);
.from('financial_records') }
.update({ // markPaid=false + paymentMethod='pending' → não faz nada
status: 'paid', // (record já criado como pending pelo RPC, sem payment_method)
paid_at: new Date().toISOString(),
payment_method: decision.paymentMethod,
updated_at: new Date().toISOString()
})
.eq('id', newRec.id);
} }
} catch { /* silencioso */ } } catch { /* silencioso */ }
} }