F3 schema-per-tenant: frontend usa tenantDb() pra tabelas tenant
- useTenantDb composable + lib/supabase/tenantClient (tenantDb/tenantSchemaName)
- tenantStore: getters activeTenantSlug/activeTenantSchema; my_tenants() RPC
passa a devolver slug+nome (migration 07)
- codemod scripts/codemod-tenant-db.py: supabase.from('<84 tabelas + 6 views
tenant>') -> tenantDb().from(...) em 139 arquivos (777 chamadas), remove
.eq('tenant_id') das cadeias tenant (173)
- passada manual (4 agentes): remove tenant_id de payloads insert/upsert/update,
selects, .or/.is de defaults; onConflict ajustado pros uniques sem tenant_id
(singletons usam 'singleton'); realtime de tabelas tenant aponta pro schema
do tenant ativo; repos dropam tenant_id defensivamente de payloads externos
- agendaSelects: tenant_id fora do AGENDA_EVENT_SELECT (quebraria PostgREST)
- zero embeds cross-schema (todos FK embeds sao tenant->tenant ou global->global)
- build de producao passa; 67 .js checados
Pendente (fora do escopo F3, sao cross-tenant/anon -> F4/F6):
- AgendadorPublicoPage (anon, resolve tenant por link_slug)
- Saas{Feriados,NotificationTemplates,DocumentTemplates,Whatsapp}Page
(gerenciam defaults do sistema / views cross-tenant)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
import { dateToISO } from '@/features/agenda/utils/timeHelpers';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
|
||||
// ── Helpers puros ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -155,10 +156,9 @@ export async function loadStatusChangeContext({ supabase, row, eventoId, status,
|
||||
const excType = exceptionTypeMap[status];
|
||||
if (excType && tenantId) {
|
||||
try {
|
||||
const { data } = await supabase
|
||||
.from('financial_exceptions')
|
||||
const { data } = await tenantDb().from('financial_exceptions')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.eq('exception_type', excType)
|
||||
.or(`owner_id.eq.${ownerId},owner_id.is.null`)
|
||||
.order('owner_id', { ascending: false, nullsLast: true })
|
||||
@@ -177,8 +177,7 @@ export async function loadStatusChangeContext({ supabase, row, eventoId, status,
|
||||
const contractId = row?.billing_contract_id ?? null;
|
||||
if (contractId) {
|
||||
try {
|
||||
const { data } = await supabase
|
||||
.from('billing_contracts')
|
||||
const { data } = await tenantDb().from('billing_contracts')
|
||||
.select('*')
|
||||
.eq('id', contractId)
|
||||
.maybeSingle();
|
||||
@@ -189,14 +188,12 @@ export async function loadStatusChangeContext({ supabase, row, eventoId, status,
|
||||
}
|
||||
if (!ctx.billingContract && eventoId) {
|
||||
try {
|
||||
const { data: ev } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: ev } = await tenantDb().from('agenda_eventos')
|
||||
.select('billing_contract_id')
|
||||
.eq('id', eventoId)
|
||||
.maybeSingle();
|
||||
if (ev?.billing_contract_id) {
|
||||
const { data: c } = await supabase
|
||||
.from('billing_contracts')
|
||||
const { data: c } = await tenantDb().from('billing_contracts')
|
||||
.select('*')
|
||||
.eq('id', ev.billing_contract_id)
|
||||
.maybeSingle();
|
||||
@@ -208,10 +205,9 @@ export async function loadStatusChangeContext({ supabase, row, eventoId, status,
|
||||
}
|
||||
if (!ctx.billingContract && patientId && tenantId) {
|
||||
try {
|
||||
const { data: c } = await supabase
|
||||
.from('billing_contracts')
|
||||
const { data: c } = await tenantDb().from('billing_contracts')
|
||||
.select('*')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.eq('patient_id', patientId)
|
||||
.eq('status', 'active')
|
||||
.eq('type', 'package')
|
||||
@@ -227,8 +223,7 @@ export async function loadStatusChangeContext({ supabase, row, eventoId, status,
|
||||
// 3) Pending record
|
||||
if (eventoId) {
|
||||
try {
|
||||
const { data } = await supabase
|
||||
.from('financial_records')
|
||||
const { data } = await tenantDb().from('financial_records')
|
||||
.select('*')
|
||||
.eq('agenda_evento_id', eventoId)
|
||||
.in('status', ['pending', 'overdue'])
|
||||
@@ -244,8 +239,7 @@ export async function loadStatusChangeContext({ supabase, row, eventoId, status,
|
||||
// 3b) Paid record pré-existente (caso C12: antecipar pagamento).
|
||||
if (eventoId) {
|
||||
try {
|
||||
const { data } = await supabase
|
||||
.from('financial_records')
|
||||
const { data } = await tenantDb().from('financial_records')
|
||||
.select('id, status, amount, final_amount, paid_at, payment_method')
|
||||
.eq('agenda_evento_id', eventoId)
|
||||
.eq('status', 'paid')
|
||||
@@ -266,16 +260,14 @@ export async function loadStatusChangeContext({ supabase, row, eventoId, status,
|
||||
saldoConsumed: false
|
||||
};
|
||||
try {
|
||||
const { data: evRow } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: evRow } = await tenantDb().from('agenda_eventos')
|
||||
.select('status, billing_contract_id')
|
||||
.eq('id', eventoId)
|
||||
.maybeSingle();
|
||||
if (evRow) {
|
||||
ctx.reverseArtifacts.previousStatus = evRow.status;
|
||||
}
|
||||
const { data: recs } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: recs } = await tenantDb().from('financial_records')
|
||||
.select('id, status, amount, final_amount, description, paid_at, payment_method')
|
||||
.eq('agenda_evento_id', eventoId)
|
||||
.neq('status', 'cancelled')
|
||||
@@ -336,8 +328,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const reason = `Cancelada via reversão de status (${r.previousStatus} → agendado) em ${today}`;
|
||||
for (const id of pendingIds) {
|
||||
const { error: cErr } = await supabase
|
||||
.from('financial_records')
|
||||
const { error: cErr } = await tenantDb().from('financial_records')
|
||||
.update({
|
||||
status: 'cancelled',
|
||||
notes: `[${today}] ${reason}`,
|
||||
@@ -356,8 +347,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
// 2) Devolver saldo
|
||||
if (decision.reverseRestoreSaldo && r.saldoConsumed && ctx.billingContract?.id) {
|
||||
try {
|
||||
const { data: freshContract, error: fetchErr } = await supabase
|
||||
.from('billing_contracts')
|
||||
const { data: freshContract, error: fetchErr } = await tenantDb().from('billing_contracts')
|
||||
.select('sessions_used, total_sessions, status')
|
||||
.eq('id', ctx.billingContract.id)
|
||||
.maybeSingle();
|
||||
@@ -369,7 +359,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
if (currentUsed >= totalSessions) {
|
||||
patch.status = 'active';
|
||||
}
|
||||
const { error: dErr } = await supabase.from('billing_contracts').update(patch).eq('id', ctx.billingContract.id);
|
||||
const { error: dErr } = await tenantDb().from('billing_contracts').update(patch).eq('id', ctx.billingContract.id);
|
||||
if (dErr) throw dErr;
|
||||
} catch (e) {
|
||||
console.error('[agendaBilling/reverse] erro decrementando saldo:', e?.message);
|
||||
@@ -380,7 +370,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
// 3) Desamarrar billing_contract_id (só se devolveu saldo)
|
||||
if (decision.reverseRestoreSaldo && r.saldoConsumed) {
|
||||
try {
|
||||
await supabase.from('agenda_eventos').update({ billing_contract_id: null, updated_at: new Date().toISOString() }).eq('id', eventoId);
|
||||
await tenantDb().from('agenda_eventos').update({ billing_contract_id: null, updated_at: new Date().toISOString() }).eq('id', eventoId);
|
||||
} catch (e) {
|
||||
console.warn('[agendaBilling/reverse] erro desamarrando billing_contract_id:', e?.message);
|
||||
}
|
||||
@@ -393,8 +383,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
// 1) Consumir saldo
|
||||
if (decision.consumeSaldo && ctx.billingContract?.id) {
|
||||
tasks.push(
|
||||
supabase
|
||||
.from('billing_contracts')
|
||||
tenantDb().from('billing_contracts')
|
||||
.update({ sessions_used: (ctx.billingContract.sessions_used ?? 0) + 1 })
|
||||
.eq('id', ctx.billingContract.id)
|
||||
);
|
||||
@@ -404,8 +393,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
const isForwardStatus = novoStatus === 'realizado' || novoStatus === 'faltou' || novoStatus === 'cancelado';
|
||||
if (isForwardStatus && ctx.billingContract?.id && eventoId) {
|
||||
tasks.push(
|
||||
supabase
|
||||
.from('agenda_eventos')
|
||||
tenantDb().from('agenda_eventos')
|
||||
.update({ billing_contract_id: ctx.billingContract.id, updated_at: new Date().toISOString() })
|
||||
.eq('id', eventoId)
|
||||
);
|
||||
@@ -418,7 +406,6 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
const fineDesc = novoStatus === 'faltou' ? `Multa por falta · sessão ${sessaoLabel}` : `Taxa de cancelamento tardio · sessão ${sessaoLabel}`;
|
||||
const finePayload = {
|
||||
owner_id: uid,
|
||||
tenant_id: tenantId,
|
||||
patient_id: patientId,
|
||||
agenda_evento_id: eventoId,
|
||||
amount: decision.fineAmount,
|
||||
@@ -429,8 +416,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
type: 'receita'
|
||||
};
|
||||
tasks.push(
|
||||
supabase
|
||||
.from('financial_records')
|
||||
tenantDb().from('financial_records')
|
||||
.insert(finePayload)
|
||||
.then(({ error }) => {
|
||||
if (error) {
|
||||
@@ -455,8 +441,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
const noteEntry = `[${today}] ${reasonText}`;
|
||||
const noteText = ctx.pendingRecord.notes ? `${ctx.pendingRecord.notes}\n${noteEntry}` : noteEntry;
|
||||
tasks.push(
|
||||
supabase
|
||||
.from('financial_records')
|
||||
tenantDb().from('financial_records')
|
||||
.update({
|
||||
status: 'cancelled',
|
||||
notes: noteText,
|
||||
@@ -469,8 +454,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
// 3) Realizado avulsa pendente: marcar pendingRecord como pago (ou só status)
|
||||
if (decision.markPaid && ctx.pendingRecord?.id) {
|
||||
tasks.push(
|
||||
supabase
|
||||
.from('financial_records')
|
||||
tenantDb().from('financial_records')
|
||||
.update({
|
||||
status: 'paid',
|
||||
paid_at: new Date().toISOString(),
|
||||
@@ -492,8 +476,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
}
|
||||
}
|
||||
try {
|
||||
const { data: freshContract, error: fetchErr } = await supabase
|
||||
.from('billing_contracts')
|
||||
const { data: freshContract, error: fetchErr } = await tenantDb().from('billing_contracts')
|
||||
.select('sessions_used, total_sessions, status')
|
||||
.eq('id', ctx.billingContract.id)
|
||||
.maybeSingle();
|
||||
@@ -504,7 +487,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
if (newUsed >= (freshContract?.total_sessions ?? 0)) {
|
||||
patch.status = 'completed';
|
||||
}
|
||||
const { error: incErr } = await supabase.from('billing_contracts').update(patch).eq('id', ctx.billingContract.id);
|
||||
const { error: incErr } = await tenantDb().from('billing_contracts').update(patch).eq('id', ctx.billingContract.id);
|
||||
if (incErr) throw incErr;
|
||||
tx({ severity: 'success', summary: 'Sessão consumida', detail: `Saldo: ${newUsed}/${freshContract?.total_sessions ?? '?'}. Pagamento já estava registrado.`, life: 4000 });
|
||||
} catch (e) {
|
||||
@@ -520,7 +503,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
const dueIso = row.inicio_em ? new Date(row.inicio_em).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10);
|
||||
|
||||
try {
|
||||
const { error: linkErr } = await supabase.from('agenda_eventos').update({ billing_contract_id: ctx.billingContract.id, updated_at: new Date().toISOString() }).eq('id', eventoId);
|
||||
const { error: linkErr } = await tenantDb().from('agenda_eventos').update({ billing_contract_id: ctx.billingContract.id, updated_at: new Date().toISOString() }).eq('id', eventoId);
|
||||
if (linkErr) throw linkErr;
|
||||
} catch (e) {
|
||||
console.error('[agendaBilling] erro amarrando billing_contract_id:', e?.message);
|
||||
@@ -548,7 +531,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
if (newUsed >= (ctx.billingContract.total_sessions ?? 0)) {
|
||||
patchContract.status = 'completed';
|
||||
}
|
||||
const { error: incErr } = await supabase.from('billing_contracts').update(patchContract).eq('id', ctx.billingContract.id);
|
||||
const { error: incErr } = await tenantDb().from('billing_contracts').update(patchContract).eq('id', ctx.billingContract.id);
|
||||
if (incErr) throw incErr;
|
||||
} catch (e) {
|
||||
console.error('[agendaBilling] erro incrementando sessions_used:', e?.message);
|
||||
@@ -570,8 +553,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
// Pós-processamento do record gerado pelo pacote saldo
|
||||
if (decision.generatePackageCharge && eventoId) {
|
||||
try {
|
||||
const { data: newRec } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: newRec } = await tenantDb().from('financial_records')
|
||||
.select('id')
|
||||
.eq('agenda_evento_id', eventoId)
|
||||
.order('created_at', { ascending: false })
|
||||
@@ -579,8 +561,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
.single();
|
||||
if (newRec?.id) {
|
||||
if (decision.markPaid) {
|
||||
await supabase
|
||||
.from('financial_records')
|
||||
await tenantDb().from('financial_records')
|
||||
.update({
|
||||
status: 'paid',
|
||||
paid_at: new Date().toISOString(),
|
||||
@@ -589,8 +570,7 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
})
|
||||
.eq('id', newRec.id);
|
||||
} else if (decision.paymentMethod === 'link') {
|
||||
await supabase
|
||||
.from('financial_records')
|
||||
await tenantDb().from('financial_records')
|
||||
.update({ payment_method: 'asaas', updated_at: new Date().toISOString() })
|
||||
.eq('id', newRec.id);
|
||||
}
|
||||
@@ -609,11 +589,9 @@ export async function applyStatusDecisions({ supabase, toast, eventoId, row, nov
|
||||
export async function createPackageContract({ supabase, rule, normalized, recorrencia, tenantId, packageStyle = 'upfront', paymentMethod = 'link', markPaidNow = false }) {
|
||||
const { n, packagePrice } = computeSeriePrice(recorrencia);
|
||||
try {
|
||||
const { data: createdContract, error: contractErr } = await supabase
|
||||
.from('billing_contracts')
|
||||
const { data: createdContract, error: contractErr } = await tenantDb().from('billing_contracts')
|
||||
.insert({
|
||||
owner_id: normalized.owner_id,
|
||||
tenant_id: tenantId,
|
||||
patient_id: normalized.paciente_id,
|
||||
type: 'package',
|
||||
total_sessions: n,
|
||||
@@ -645,11 +623,9 @@ export async function createPackageContract({ supabase, rule, normalized, recorr
|
||||
startDt.setHours(hh, mm, 0, 0);
|
||||
const endDt = new Date(startDt.getTime() + durMin * 60 * 1000);
|
||||
|
||||
const { data: createdEvent, error: evErr } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data: createdEvent, error: evErr } = await tenantDb().from('agenda_eventos')
|
||||
.insert({
|
||||
owner_id: rule.owner_id,
|
||||
tenant_id: tenantId,
|
||||
terapeuta_id: rule.therapist_id ?? null,
|
||||
recurrence_id: rule.id,
|
||||
recurrence_date: firstISO,
|
||||
@@ -680,8 +656,7 @@ export async function createPackageContract({ supabase, rule, normalized, recorr
|
||||
if (cobErr) throw cobErr;
|
||||
|
||||
const paidNow = markPaidNow === true && paymentMethod !== 'link';
|
||||
const { data: recRow } = await supabase
|
||||
.from('financial_records')
|
||||
const { data: recRow } = await tenantDb().from('financial_records')
|
||||
.select('id')
|
||||
.eq('agenda_evento_id', createdEvent.id)
|
||||
.order('created_at', { ascending: false })
|
||||
@@ -696,7 +671,7 @@ export async function createPackageContract({ supabase, rule, normalized, recorr
|
||||
patch.status = 'paid';
|
||||
patch.paid_at = new Date().toISOString();
|
||||
}
|
||||
await supabase.from('financial_records').update(patch).eq('id', recRow.id);
|
||||
await tenantDb().from('financial_records').update(patch).eq('id', recRow.id);
|
||||
}
|
||||
|
||||
const methodLabel = {
|
||||
@@ -745,7 +720,6 @@ export async function materializeAndChargePerSession({ supabase, rule, normalize
|
||||
const endDt = new Date(startDt.getTime() + durMin * 60 * 1000);
|
||||
return {
|
||||
owner_id: rule.owner_id,
|
||||
tenant_id: tenantId,
|
||||
terapeuta_id: rule.therapist_id ?? null,
|
||||
recurrence_id: rule.id,
|
||||
recurrence_date: iso,
|
||||
@@ -762,7 +736,7 @@ export async function materializeAndChargePerSession({ supabase, rule, normalize
|
||||
};
|
||||
});
|
||||
|
||||
const { data: createdEvents, error: evErr } = await supabase.from('agenda_eventos').insert(rows).select('id, inicio_em');
|
||||
const { data: createdEvents, error: evErr } = await tenantDb().from('agenda_eventos').insert(rows).select('id, inicio_em');
|
||||
if (evErr) throw evErr;
|
||||
|
||||
let okCount = 0;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import {
|
||||
assertTenantId as assertValidTenantId,
|
||||
assertIsoRange as assertValidIsoRange,
|
||||
@@ -24,7 +25,7 @@ import { AGENDA_EVENT_SELECT, flattenAgendaRow } from './agendaSelects';
|
||||
|
||||
/**
|
||||
* Lista eventos para mosaico da clínica (admin/secretaria) dentro de um tenant específico.
|
||||
* IMPORTANTE: SEM tenant_id aqui vira vazamento multi-tenant.
|
||||
* Isolamento multi-tenant garantido pelo schema do tenant (tenantDb).
|
||||
*/
|
||||
export async function listClinicEvents({ tenantId, ownerIds, startISO, endISO } = {}) {
|
||||
assertValidTenantId(tenantId);
|
||||
@@ -34,10 +35,9 @@ export async function listClinicEvents({ tenantId, ownerIds, startISO, endISO }
|
||||
const safeOwnerIds = sanitizeOwnerIds(ownerIds);
|
||||
if (!safeOwnerIds.length) return [];
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error } = await tenantDb().from('agenda_eventos')
|
||||
.select(AGENDA_EVENT_SELECT)
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.in('owner_id', safeOwnerIds)
|
||||
.gte('inicio_em', startISO)
|
||||
.lt('inicio_em', endISO)
|
||||
@@ -78,13 +78,11 @@ export async function createClinicAgendaEvento(payload, { tenantId } = {}) {
|
||||
throw new Error('owner_id é obrigatório para criação pela clínica.');
|
||||
}
|
||||
|
||||
const insertPayload = {
|
||||
...payload,
|
||||
tenant_id: tenantId
|
||||
};
|
||||
// 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 supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error } = await tenantDb().from('agenda_eventos')
|
||||
.insert(insertPayload)
|
||||
.select(AGENDA_EVENT_SELECT)
|
||||
.single();
|
||||
@@ -95,7 +93,7 @@ export async function createClinicAgendaEvento(payload, { tenantId } = {}) {
|
||||
|
||||
/**
|
||||
* Atualização segura para clínica:
|
||||
* - filtra por id + tenant_id (evita update cruzado)
|
||||
* - 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 } = {}) {
|
||||
@@ -103,11 +101,13 @@ export async function updateClinicAgendaEvento(id, patch, { tenantId } = {}) {
|
||||
if (!patch) throw new Error('Patch vazio.');
|
||||
assertValidTenantId(tenantId);
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
.update(patch)
|
||||
// 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)
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
.select(AGENDA_EVENT_SELECT)
|
||||
.single();
|
||||
|
||||
@@ -117,13 +117,13 @@ export async function updateClinicAgendaEvento(id, patch, { tenantId } = {}) {
|
||||
|
||||
/**
|
||||
* Delete seguro para clínica:
|
||||
* - filtra por id + tenant_id
|
||||
* - 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 supabase.from('agenda_eventos').delete().eq('id', id).eq('tenant_id', tenantId);
|
||||
const { error } = await tenantDb().from('agenda_eventos').delete().eq('id', id);
|
||||
|
||||
if (error) throw error;
|
||||
return true;
|
||||
|
||||
@@ -143,8 +143,7 @@ function _mapRow(r) {
|
||||
|
||||
// timestamps
|
||||
inicio_em: r.inicio_em,
|
||||
fim_em: r.fim_em,
|
||||
tenant_id: r.tenant_id ?? null
|
||||
fim_em: r.fim_em
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { assertTenantId as assertValidTenantId, assertIsoRange, getUid } from './_tenantGuards';
|
||||
import { AGENDA_EVENT_SELECT, flattenAgendaRow } from './agendaSelects';
|
||||
@@ -23,8 +24,7 @@ import { AGENDA_EVENT_SELECT, flattenAgendaRow } from './agendaSelects';
|
||||
|
||||
export async function getMyAgendaSettings() {
|
||||
const uid = await getUid();
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_configuracoes')
|
||||
const { data, error } = await tenantDb().from('agenda_configuracoes')
|
||||
.select('*')
|
||||
.eq('owner_id', uid)
|
||||
.order('created_at', { ascending: false })
|
||||
@@ -36,8 +36,7 @@ export async function getMyAgendaSettings() {
|
||||
|
||||
export async function getMyWorkSchedule() {
|
||||
const uid = await getUid();
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_regras_semanais')
|
||||
const { data, error } = await tenantDb().from('agenda_regras_semanais')
|
||||
.select('dia_semana, hora_inicio, hora_fim, ativo')
|
||||
.eq('owner_id', uid)
|
||||
.eq('ativo', true)
|
||||
@@ -78,10 +77,9 @@ export async function listMyAgendaEvents({ startISO, endISO, ownerId, tenantId,
|
||||
const uid = ownerId || (await getUid());
|
||||
const tid = resolveTenantId(tenantId);
|
||||
|
||||
let q = supabase
|
||||
.from('agenda_eventos')
|
||||
let q = tenantDb().from('agenda_eventos')
|
||||
.select(AGENDA_EVENT_SELECT)
|
||||
.eq('tenant_id', tid)
|
||||
|
||||
.eq('owner_id', uid)
|
||||
.gte('inicio_em', startISO)
|
||||
.lt('inicio_em', endISO)
|
||||
@@ -96,9 +94,8 @@ export async function listMyAgendaEvents({ startISO, endISO, ownerId, tenantId,
|
||||
|
||||
/**
|
||||
* Criação segura:
|
||||
* - injeta tenant_id do tenantStore
|
||||
* - injeta owner_id do usuário logado (ignora owner_id vindo de fora)
|
||||
* - dropa paciente_id (campo legado) se vier no payload
|
||||
* - dropa paciente_id (campo legado) e tenant_id (schema-per-tenant não tem a coluna) se vierem no payload
|
||||
*/
|
||||
export async function createAgendaEvento(payload) {
|
||||
if (!payload) throw new Error('Payload vazio.');
|
||||
@@ -106,11 +103,10 @@ export async function createAgendaEvento(payload) {
|
||||
const tid = resolveTenantId();
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { paciente_id: _dropped, ...rest } = payload;
|
||||
const insertPayload = { ...rest, tenant_id: tid, owner_id: uid };
|
||||
const { paciente_id: _dropped, tenant_id: _dropTenantId, ...rest } = payload;
|
||||
const insertPayload = { ...rest, owner_id: uid };
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error } = await tenantDb().from('agenda_eventos')
|
||||
.insert([insertPayload])
|
||||
.select(AGENDA_EVENT_SELECT)
|
||||
.single();
|
||||
@@ -120,7 +116,7 @@ export async function createAgendaEvento(payload) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Atualização segura: filtra por id + tenant_id (RLS reforça no banco).
|
||||
* Atualização segura: filtra por id (isolamento via schema do tenant; RLS reforça no banco).
|
||||
*/
|
||||
export async function updateAgendaEvento(id, patch, { tenantId } = {}) {
|
||||
if (!id) throw new Error('ID inválido.');
|
||||
@@ -128,13 +124,12 @@ export async function updateAgendaEvento(id, patch, { tenantId } = {}) {
|
||||
const tid = resolveTenantId(tenantId);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { paciente_id: _dropped, ...safePatch } = patch;
|
||||
const { paciente_id: _dropped, tenant_id: _dropTenantId, ...safePatch } = patch;
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('agenda_eventos')
|
||||
const { data, error } = await tenantDb().from('agenda_eventos')
|
||||
.update(safePatch)
|
||||
.eq('id', id)
|
||||
.eq('tenant_id', tid)
|
||||
|
||||
.select(AGENDA_EVENT_SELECT)
|
||||
.single();
|
||||
|
||||
@@ -143,13 +138,13 @@ export async function updateAgendaEvento(id, patch, { tenantId } = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete seguro: filtra por id + tenant_id.
|
||||
* Delete seguro: filtra por id (isolamento via schema do tenant).
|
||||
*/
|
||||
export async function deleteAgendaEvento(id, { tenantId } = {}) {
|
||||
if (!id) throw new Error('ID inválido.');
|
||||
const tid = resolveTenantId(tenantId);
|
||||
|
||||
const { error } = await supabase.from('agenda_eventos').delete().eq('id', id).eq('tenant_id', tid);
|
||||
const { error } = await tenantDb().from('agenda_eventos').delete().eq('id', id);
|
||||
if (error) throw error;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
export const AGENDA_EVENT_SELECT = `
|
||||
id, owner_id, patient_id, tipo, status,
|
||||
titulo, titulo_custom, observacoes, inicio_em, fim_em,
|
||||
terapeuta_id, tenant_id, visibility_scope,
|
||||
terapeuta_id, visibility_scope,
|
||||
determined_commitment_id, link_online, extra_fields, modalidade,
|
||||
recurrence_id, recurrence_date,
|
||||
mirror_of_event_id, price,
|
||||
|
||||
Reference in New Issue
Block a user