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;
applyFine.value = _calcInitialFineApply();
fineAmount.value = _calcInitialFineAmount();
markPaid.value = true;
paymentMethod.value = 'pix';
// Default markPaid:
// - 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;
}
);
@@ -138,12 +146,13 @@ const paymentMethodOptions = [
{ value: 'deposito', label: 'Depósito' },
{ value: 'cartao_maquininha', label: 'Cartão (maquininha)' }
];
const paymentMethodOptionsCobranca = [
{ value: 'link', label: 'Enviar link de pagamento (Asaas)' },
{ value: 'pix', label: 'Já recebi — PIX' },
{ value: 'dinheiro', label: 'Já recebi — Dinheiro' },
{ value: 'deposito', label: 'Já recebi — Depósito' },
{ value: 'cartao_maquininha', label: 'Já recebi — Cartão (maquininha)' }
// Opções pra "Como vai cobrar?" quando markPaid=false (sessão pendente
// no pacote saldo). 'pending' = só registra como pendente, terapeuta
// cobra depois pelo /financeiro. 'link' = gera link Asaas e marca
// payment_method='asaas' no record (pós-confirm o handler updata).
const paymentMethodOptionsPending = [
{ value: 'pending', label: 'Apenas registrar como pendente' },
{ value: 'link', label: 'Enviar link de pagamento (Asaas)' }
];
const regraResumo = computed(() => {
@@ -204,11 +213,16 @@ const fineDefaultReason = computed(() => {
// ─── Actions ───────────────────────────────────────────────────────────
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', {
consumeSaldo: showSaldoBlock.value ? consumeSaldo.value : false,
applyFine: showFineBlock.value ? applyFine.value : false,
fineAmount: showFineBlock.value && applyFine.value ? Number(fineAmount.value) || 0 : 0,
markPaid: showRegistrarPagto.value ? markPaid.value : false,
markPaid: considerMarkPaid ? markPaid.value : false,
paymentMethod: paymentMethod.value,
generatePackageCharge: showCobrancaPacote.value ? generatePackageCharge.value : false
});
@@ -338,11 +352,35 @@ function onCancel() {
<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>
</div>
<div v-if="generatePackageCharge" class="asccd-method-row">
<label class="asccd-method-label">Como cobrar?</label>
<!-- Sub-question 1: a sessão foi paga? (espelha o padrão da avulsa) -->
<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
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"
optionValue="value"
size="small"
@@ -1567,22 +1567,12 @@ function _buildHandlers(deps) {
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'
if (decision.generatePackageCharge && decision.paymentMethod === 'link' && eventoId) {
try {
const { data: newRec } = await supabase
.from('financial_records')
.select('id')
.eq('agenda_evento_id', eventoId)
.order('created_at', { ascending: false })
.limit(1)
.single();
if (newRec?.id) {
await supabase.from('financial_records').update({ payment_method: 'asaas', updated_at: new Date().toISOString() }).eq('id', newRec.id);
}
} catch { /* silencioso */ }
} else if (decision.generatePackageCharge && decision.paymentMethod !== 'link' && eventoId) {
// Já recebi → marca como paid
// Pós-processamento do record gerado pelo pacote saldo. Agora o
// 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 {
const { data: newRec } = await supabase
.from('financial_records')
@@ -1592,6 +1582,7 @@ function _buildHandlers(deps) {
.limit(1)
.single();
if (newRec?.id) {
if (decision.markPaid) {
await supabase
.from('financial_records')
.update({
@@ -1601,6 +1592,14 @@ function _buildHandlers(deps) {
updated_at: new Date().toISOString()
})
.eq('id', newRec.id);
} else if (decision.paymentMethod === 'link') {
await supabase
.from('financial_records')
.update({ payment_method: 'asaas', updated_at: new Date().toISOString() })
.eq('id', newRec.id);
}
// markPaid=false + paymentMethod='pending' → não faz nada
// (record já criado como pending pelo RPC, sem payment_method)
}
} catch { /* silencioso */ }
}