/* |-------------------------------------------------------------------------- | Agência PSI |-------------------------------------------------------------------------- | Criado e desenvolvido por Leonardo Nohama | | Tecnologia aplicada à escuta. | Estrutura para o cuidado. | | Arquivo: src/features/agenda/services/agendaClinicRepository.js | Data: 2026 | Local: São Carlos/SP — Brasil |-------------------------------------------------------------------------- | © 2026 — Todos os direitos reservados |-------------------------------------------------------------------------- */ import { supabase } from '@/lib/supabase/client'; import { tenantDb } from '@/lib/supabase/tenantClient'; import { assertTenantId as assertValidTenantId, assertIsoRange as assertValidIsoRange, sanitizeOwnerIds } from './_tenantGuards'; import { AGENDA_EVENT_SELECT, flattenAgendaRow } from './agendaSelects'; /** * Lista eventos para mosaico da clínica (admin/secretaria) dentro de um tenant específico. * Isolamento multi-tenant garantido pelo schema do tenant (tenantDb). */ export async function listClinicEvents({ tenantId, ownerIds, startISO, endISO } = {}) { assertValidTenantId(tenantId); if (!ownerIds?.length) return []; assertValidIsoRange(startISO, endISO); const safeOwnerIds = sanitizeOwnerIds(ownerIds); if (!safeOwnerIds.length) return []; const { data, error } = await tenantDb().from('agenda_eventos') .select(AGENDA_EVENT_SELECT) .in('owner_id', safeOwnerIds) .gte('inicio_em', startISO) .lt('inicio_em', endISO) .order('inicio_em', { ascending: true }); if (error) throw error; return (data || []).map(flattenAgendaRow); } /** * Lista profissionais/membros para montar colunas no mosaico. * Usando view "v_tenant_staff" (como você já tem). */ export async function listTenantStaff(tenantId) { assertValidTenantId(tenantId); const { data, error } = await supabase.from('v_tenant_staff').select('*').eq('tenant_id', tenantId); if (error) throw error; return data || []; } /** * Criação para a área da clínica (admin/secretária): * - exige tenantId explícito * - permite definir owner_id (terapeuta dono do compromisso) * * Segurança real deve ser garantida por RLS: * - clinic_admin/tenant_admin pode criar para qualquer owner dentro do tenant * - therapist não deve conseguir passar daqui (guard + RLS) */ export async function createClinicAgendaEvento(payload, { tenantId } = {}) { assertValidTenantId(tenantId); if (!payload) throw new Error('Payload vazio.'); const ownerId = payload.owner_id; if (!ownerId || ownerId === 'null' || ownerId === 'undefined') { throw new Error('owner_id é obrigatório para criação pela clínica.'); } // dropa tenant_id se vier no payload (schema-per-tenant não tem a coluna) // eslint-disable-next-line no-unused-vars const { tenant_id: _dropTenantId, ...insertPayload } = payload; const { data, error } = await tenantDb().from('agenda_eventos') .insert(insertPayload) .select(AGENDA_EVENT_SELECT) .single(); if (error) throw error; return flattenAgendaRow(data); } /** * Atualização segura para clínica: * - filtra por id (isolamento via schema do tenant) * - permite editar owner_id (caso você mova evento para outro profissional) */ export async function updateClinicAgendaEvento(id, patch, { tenantId } = {}) { if (!id) throw new Error('ID inválido.'); if (!patch) throw new Error('Patch vazio.'); assertValidTenantId(tenantId); // eslint-disable-next-line no-unused-vars const { tenant_id: _dropTenantId, ...safePatch } = patch; const { data, error } = await tenantDb().from('agenda_eventos') .update(safePatch) .eq('id', id) .select(AGENDA_EVENT_SELECT) .single(); if (error) throw error; return flattenAgendaRow(data); } /** * Delete seguro para clínica: * - filtra por id (isolamento via schema do tenant) */ export async function deleteClinicAgendaEvento(id, { tenantId } = {}) { if (!id) throw new Error('ID inválido.'); assertValidTenantId(tenantId); const { error } = await tenantDb().from('agenda_eventos').delete().eq('id', id); if (error) throw error; return true; }