MelissaPaciente: fix openWhatsapp + dialog inline novo lancamento financeiro

DOIS BUGS DE COMPORTAMENTO:

1. openWhatsapp nao abria o drawer
   conversationDrawerStore.openForPatient(patientId) espera STRING id,
   nao objeto. Eu passava { id, name, phone, avatar_url } — store
   ignorava e drawer nunca abria.
   FIX: passar String(props.patientId) (mesmo pattern que MelissaPacientes).
   BONUS: a store seta this.error sem dar throw quando paciente nao tem
   telefone cadastrado. Detectamos com `if (err && !isOpen)` e mostramos
   toast warn com a mensagem da store ("Paciente sem telefone cadastrado").
   Funcao virou async pra aguardar o openForPatient.

2. addFinancial era placeholder "Em breve"
   User correto: o sistema ja tem suporte (composables/useFinancialRecords
   tem createManualRecord). Implementado dialog inline simples no
   prontuario.

NOVO em src/features/patients/composables/usePatientFinancial.js
- createRecord(patientId, payload) — INSERT financial_records com
  type='receita', resolve owner_id (auth.getUser) e tenant_id (lazy
  import tenantStore pra evitar circular). Auto-reload via _lastPatientId.
  Retorna {ok, data?, error?}.

NOVO em MelissaPaciente.vue
- Refs novoLancOpen + novoLancForm (description/amount/due_date/payment_method)
- PAYMENT_METHODS array (Pix/Cartao/Dinheiro/Transferencia/Boleto/Convenio)
- addFinancial() agora abre o dialog (era toast "em breve")
- salvarLancamento() handler com validacao (valor > 0, due_date obrigatorio)
- <Dialog> v-model:visible 420px com form: descricao + grid 2-col
  (valor InputNumber BRL + vencimento date input) + select forma
- CSS .mpa-novo-lanc + responsive (1-col em <540px)

ESLint: 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-08 11:12:22 -03:00
parent 301a7124a7
commit 64005a5b07
2 changed files with 226 additions and 20 deletions
@@ -138,6 +138,58 @@ export function usePatientFinancial() {
}
}
/**
* Cria um novo lancamento manual (type=receita) pro paciente.
* Insere com tenant_id + owner_id resolvidos via auth/tenant store.
* Auto-reload ao final pra refletir nos KPIs e tabela.
*
* payload: { description, amount, due_date, payment_method? }
* Retorna {ok, data?, error?}.
*/
async function createRecord(patientId, payload = {}) {
if (!patientId || busy.value) return { ok: false, error: 'busy' };
if (!payload?.amount || Number.isNaN(Number(payload.amount))) {
return { ok: false, error: 'Valor invalido' };
}
busy.value = true;
try {
const { data: userData } = await supabase.auth.getUser();
const ownerId = userData?.user?.id;
// tenant_id: tenta tenantStore lazy import, fallback null (RLS
// via owner_id ainda permite insert).
let tenantId = null;
try {
const { useTenantStore } = await import('@/stores/tenantStore');
tenantId = useTenantStore().activeTenantId || null;
} catch { /* sem tenant store — segue */ }
const row = {
patient_id: patientId,
owner_id: ownerId,
tenant_id: tenantId,
type: 'receita',
amount: Number(payload.amount),
due_date: payload.due_date || null,
description: String(payload.description || '').trim() || null,
payment_method: payload.payment_method || null,
paid_at: null
};
const { data, error: err } = await supabase
.from('financial_records')
.insert([row])
.select()
.single();
if (err) throw err;
if (_lastPatientId) await load(_lastPatientId);
return { ok: true, data };
} catch (e) {
return { ok: false, error: e?.message || 'Erro ao criar lançamento' };
} finally {
busy.value = false;
}
}
/**
* Reverte: remove paid_at (volta pra pendente). Auto-reload.
*/
@@ -172,6 +224,7 @@ export function usePatientFinancial() {
statusFinanceiro,
recordsOrdenados,
markPaid,
markUnpaid
markUnpaid,
createRecord
};
}