agenda: expandir e materializar ocorrencias de recorrencia (cross-layout)
PROBLEMA 1 — recorrencias virtuais nao apareciam em listas de sessao
============================================================
Sistema cria 1 row real em agenda_eventos por recorrencia (a primeira
ocorrencia) + 1 regra em recurrence_rules. As N-1 sessoes seguintes sao
geradas em runtime via useRecurrence.loadAndExpand. AgendaTerapeutaPage e
AgendaClinicaPage ja usavam loadAndExpand, mas composables compartilhados
("Hoje", widget, prontuario, ver todas) so liam agenda_eventos direto —
serie semanal de 4 sessoes aparecia como 1.
Fix em 3 composables cross-layout:
- usePatientSessions.load — range padrao -6mo a +12mo, filtra virtuais
por patient_id apos loadAndExpand. Cobre MelissaPaciente Tab Agenda +
PatientProntuario legacy.
- useMelissaEventos._fetchRange — merge real + virtual no range visivel.
Cobre widget "Hoje" (MelissaLayout), mini-cal, fallback standalone do
MelissaAgenda. Falha do expand cai silencioso pra so-reais.
- useMelissaTodasSessoesPaciente.fetch — mesma logica do paciente, range
-6mo a +12mo. Cobre "Ver todas as sessoes" do MelissaAgenda.
normalizeEvent agora aceita shape de virtual (paciente_nome/patient_name)
alem de joined query (patients.nome_completo). Expoe is_occurrence +
recurrence_id pra consumidores diferenciarem.
PROBLEMA 2 — UPDATE em id virtual quebra com "invalid input syntax for type uuid"
============================================================
Apos #1, ocorrencias virtuais aparecem na UI. Quando o user mudava status
(via botoes do MelissaEventoPanel, watcher do form.status no
AgendaEventDialog, ou botoes diretos no MelissaPaciente Tab Agenda), o
UPDATE caia direto no PostgreSQL com id "rec::ruleId::date" — sintaxe
invalida pra coluna UUID.
Materializacao em 4 caminhos:
- usePatientSessions.updateStatus(sessionOrId, status) — aceita row inteira
agora. Se virtual, busca row real por recurrence_id+date, ou cria nova
copiando campos da virtual (com status aplicado).
- useAgendaEventActions watcher do form.status — emit('updateSeriesEvent',
{ ..., row: form }) em vez de UPDATE direto. Parent materializa.
- MelissaLayout.updateEventoStatus — detecta virtual, delega pro
M.onUpdateSeriesEvent passando row: ev (sem isso, dialogEventRow ficaria
vazio porque user nao abriu o dialog antes — criava row orfa sem
patient_id).
- MelissaPaciente — @updateSeriesEvent do dialog local aponta pro
onSessaoDialogUpdateSeries (wrapper que delega pro composable que sabe
materializar). Antes apontava pro save normal.
useMelissaAgenda.onUpdateSeriesEvent atualizado:
- aceita row opcional do chamador (prioridade > dialogEventRow > vazio).
- guard: aborta com toast se rid (recurrence_id) for null, em vez de
criar row orfa.
- error check no .maybeSingle (antes ignorado — query falhando seguia pro
insert e duplicava sessoes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -806,7 +806,11 @@ function _buildHandlers(deps) {
|
||||
}
|
||||
|
||||
// ── onUpdateSeriesEvent — mudança de status numa ocorrência ──
|
||||
async function onUpdateSeriesEvent({ id, status, recurrence_date, inicio_em, fim_em, is_virtual }) {
|
||||
//
|
||||
// `row` opcional: row completa quando o chamador NÃO abriu o dialog antes
|
||||
// (MelissaEventoPanel clica direto no evento → não há dialogEventRow ainda).
|
||||
// Sem isso, recurrence_id/patient_id caem pra null e criavam row órfã.
|
||||
async function onUpdateSeriesEvent({ id, status, recurrence_date, inicio_em, fim_em, is_virtual, row: callerRow }) {
|
||||
try {
|
||||
if (id) {
|
||||
await update(id, { status });
|
||||
@@ -814,20 +818,29 @@ function _buildHandlers(deps) {
|
||||
}
|
||||
if (!is_virtual || !inicio_em) return;
|
||||
|
||||
const rid = dialogEventRow.value?.recurrence_id ?? dialogEventRow.value?.serie_id ?? null;
|
||||
// Prioridade: row passado pelo chamador > dialogEventRow > vazio.
|
||||
// dialogEventRow só está populado se o user abriu o dialog antes.
|
||||
const row = callerRow || dialogEventRow.value || {};
|
||||
const rid = row.recurrence_id ?? row.serie_id ?? dialogEventRow.value?.recurrence_id ?? dialogEventRow.value?.serie_id ?? null;
|
||||
const rDate = recurrence_date || inicio_em?.slice(0, 10);
|
||||
|
||||
const { data: existing } = await supabase
|
||||
if (!rid) {
|
||||
toast.add({ severity: 'warn', summary: 'Erro', detail: 'Não foi possível identificar a regra de recorrência desta ocorrência.', life: 4000 });
|
||||
return;
|
||||
}
|
||||
|
||||
// .is() pra null seria inválido aqui — rid já validado acima.
|
||||
const { data: existing, error: exErr } = await supabase
|
||||
.from('agenda_eventos')
|
||||
.select('id')
|
||||
.eq('recurrence_id', rid)
|
||||
.eq('recurrence_date', rDate)
|
||||
.maybeSingle();
|
||||
if (exErr) throw exErr;
|
||||
|
||||
if (existing?.id) {
|
||||
await update(existing.id, { status });
|
||||
} else {
|
||||
const row = dialogEventRow.value || {};
|
||||
await create({
|
||||
owner_id: ownerId.value,
|
||||
tenant_id: clinicTenantId.value,
|
||||
@@ -841,6 +854,7 @@ function _buildHandlers(deps) {
|
||||
titulo: row.titulo || 'Sessão',
|
||||
patient_id: row.patient_id || row.paciente_id || null,
|
||||
determined_commitment_id: row.determined_commitment_id || null,
|
||||
modalidade: row.modalidade || 'presencial',
|
||||
price: row.price ?? null
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user