/* |-------------------------------------------------------------------------- | Agência PSI |-------------------------------------------------------------------------- | Arquivo: src/features/insurance/services/insurancePlansRepository.js | | Repository da tabela public.insurance_plans. | Pure functions seguindo blueprints/repository-blueprint.md. | | Schema (servicos_prontuarios.sql): | id, owner_id, | name text, notes text, default_value numeric(10,2), | active boolean DEFAULT true, created_at, updated_at |-------------------------------------------------------------------------- */ import { supabase } from '@/lib/supabase/client'; import { tenantDb } from '@/lib/supabase/tenantClient'; import { useTenantStore } from '@/stores/tenantStore'; import { assertTenantId, getUid } from './_tenantGuards'; import { INSURANCE_PLAN_SELECT } from './insurancePlansSelects'; function resolveTenantId(tenantIdArg) { const tenantStore = useTenantStore(); const tenantId = tenantIdArg || tenantStore.activeTenantId || tenantStore.tenantId; assertTenantId(tenantId); return tenantId; } /** * Lista convênios ativos do owner. Ordenados por name ascending. * * @param {Object} [opts] * @param {string} [opts.ownerId] * @param {string} [opts.tenantId] * @param {boolean} [opts.includeInactive=false] */ export async function listForOwner({ ownerId, tenantId, includeInactive = false } = {}) { const tid = resolveTenantId(tenantId); const uid = ownerId || (await getUid()); let q = tenantDb().from('insurance_plans').select(INSURANCE_PLAN_SELECT).eq('owner_id', uid).order('name', { ascending: true }); if (!includeInactive) q = q.eq('active', true); const { data, error } = await q; if (error) throw error; return data || []; } /** * Lê convênio por id. Filtra owner_id por segurança. */ export async function getById(id, { tenantId } = {}) { if (!id) throw new Error('ID inválido.'); const tid = resolveTenantId(tenantId); const uid = await getUid(); const { data, error } = await tenantDb().from('insurance_plans').select(INSURANCE_PLAN_SELECT).eq('id', id).eq('owner_id', uid).maybeSingle(); if (error) throw error; return data || null; } /** * Procura convênio ativo por nome (case-insensitive). Usado pra duplicate check * antes de criar (uniqueness check do quick-create blueprint). * * @param {Object} opts * @param {string} opts.name * @param {string} [opts.ownerId] * @param {string} [opts.tenantId] */ export async function findByName({ name, ownerId, tenantId } = {}) { if (!name) return null; const tid = resolveTenantId(tenantId); const uid = ownerId || (await getUid()); const safeName = String(name).trim(); if (!safeName) return null; const { data, error } = await tenantDb().from('insurance_plans').select(INSURANCE_PLAN_SELECT).eq('owner_id', uid).eq('active', true).ilike('name', safeName).limit(1).maybeSingle(); if (error) throw error; return data || null; } /** * Cria convênio. Pré-checa duplicidade por nome (case-insensitive) — se já * existe ativo, lança erro PT-BR. Repository injeta owner_id. */ export async function create(payload) { if (!payload) throw new Error('Payload vazio.'); const name = String(payload.name || '').trim(); if (!name) throw new Error('Nome do convênio é obrigatório.'); if (name.length > 120) throw new Error('Nome do convênio muito longo (máx 120).'); const uid = await getUid(); const tid = resolveTenantId(); // Uniqueness check (quick-create blueprint) const dup = await findByName({ name, ownerId: uid, tenantId: tid }); if (dup) { throw new Error('Já existe um convênio com esse nome.'); } const insertPayload = { owner_id: uid, name: name.slice(0, 120), notes: payload.notes ? String(payload.notes).trim().slice(0, 500) || null : null, default_value: payload.default_value != null && payload.default_value !== '' ? Number(payload.default_value) : null, active: payload.active !== false }; const { data, error } = await tenantDb().from('insurance_plans').insert([insertPayload]).select(INSURANCE_PLAN_SELECT).single(); if (error) throw error; return data; } /** * Atualiza convênio. Filtra por id. */ export async function update(id, patch, { tenantId } = {}) { if (!id) throw new Error('ID inválido.'); if (!patch) throw new Error('Patch vazio.'); const tid = resolveTenantId(tenantId); const safePatch = sanitize(patch); safePatch.updated_at = new Date().toISOString(); const { data, error } = await tenantDb().from('insurance_plans').update(safePatch).eq('id', id).select(INSURANCE_PLAN_SELECT).single(); if (error) throw error; return data; } /** * Soft delete: marca active=false. Preserva histórico. */ export async function softDelete(id, { tenantId } = {}) { if (!id) throw new Error('ID inválido.'); const tid = resolveTenantId(tenantId); const { error } = await tenantDb().from('insurance_plans').update({ active: false, updated_at: new Date().toISOString() }).eq('id', id); if (error) throw error; return true; } // ─── helpers internos ──────────────────────────────────────────────────────── function sanitize(payload) { // Dropa tenant_id defensivamente (schema-per-tenant: coluna não existe mais) const { tenant_id: _drop, ...rest } = payload; const out = { ...rest }; if ('name' in out && typeof out.name === 'string') { const t = out.name.trim(); out.name = t === '' ? null : t.slice(0, 120); } if ('notes' in out && typeof out.notes === 'string') { const t = out.notes.trim(); out.notes = t === '' ? null : t.slice(0, 500); } if ('default_value' in out) { const v = out.default_value; out.default_value = v == null || v === '' ? null : Number(v); } return out; }