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:
@@ -19,6 +19,7 @@ import { useInsurancePlans } from '@/features/agenda/composables/useInsurancePla
|
||||
import { useServices } from '@/features/agenda/composables/useServices';
|
||||
import { useLayout } from '@/layout/composables/layout';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { tenantDb } from '@/lib/supabase/tenantClient';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { applyThemeEngine } from '@/theme/theme.options';
|
||||
import InputMask from 'primevue/inputmask';
|
||||
@@ -416,7 +417,7 @@ onMounted(async () => {
|
||||
if (!uid.value) return;
|
||||
userEmail.value = user.email || '';
|
||||
|
||||
const { data: cfg } = await supabase.from('agenda_configuracoes').select('setup_concluido, setup_clinica_concluido, atendimento_mode').eq('owner_id', uid.value).maybeSingle();
|
||||
const { data: cfg } = await tenantDb().from('agenda_configuracoes').select('setup_concluido, setup_clinica_concluido, atendimento_mode').eq('owner_id', uid.value).maybeSingle();
|
||||
if (cfg && (cfg.setup_concluido || cfg.setup_clinica_concluido || !!cfg.atendimento_mode)) {
|
||||
router.replace('/pages/notfound');
|
||||
return;
|
||||
@@ -443,7 +444,7 @@ async function loadNegocio() {
|
||||
if (!tenantId.value) return;
|
||||
|
||||
// Fonte única: company_profiles
|
||||
const { data } = await supabase.from('company_profiles').select('nome_fantasia,tipo_empresa,logo_url,cep,logradouro,numero,complemento,bairro,cidade,estado,telefone,email,site,redes_sociais').eq('tenant_id', tenantId.value).maybeSingle();
|
||||
const { data } = await tenantDb().from('company_profiles').select('nome_fantasia,tipo_empresa,logo_url,cep,logradouro,numero,complemento,bairro,cidade,estado,telefone,email,site,redes_sociais').maybeSingle();
|
||||
|
||||
if (!data) return;
|
||||
|
||||
@@ -481,7 +482,7 @@ async function loadNegocio() {
|
||||
}
|
||||
async function loadAtendimento() {
|
||||
if (!uid.value) return;
|
||||
const { data } = await supabase.from('agenda_configuracoes').select('atendimento_mode').eq('owner_id', uid.value).maybeSingle();
|
||||
const { data } = await tenantDb().from('agenda_configuracoes').select('atendimento_mode').eq('owner_id', uid.value).maybeSingle();
|
||||
if (data?.atendimento_mode) {
|
||||
atendimento.value.mode = data.atendimento_mode;
|
||||
markSaved('atendimento');
|
||||
@@ -574,7 +575,6 @@ async function saveNegocio(silent = false) {
|
||||
}
|
||||
|
||||
const payload = {
|
||||
tenant_id: tenantId.value,
|
||||
nome_fantasia: negocio.value.name.trim() || null,
|
||||
tipo_empresa: negocio.value.type || null,
|
||||
logo_url: logoUrl || null,
|
||||
@@ -592,15 +592,15 @@ async function saveNegocio(silent = false) {
|
||||
};
|
||||
|
||||
// Verificar se já existe registro para este tenant
|
||||
const { data: existing } = await supabase.from('company_profiles').select('id').eq('tenant_id', tenantId.value).maybeSingle();
|
||||
const { data: existing } = await tenantDb().from('company_profiles').select('id').maybeSingle();
|
||||
|
||||
let error;
|
||||
if (existing?.id) {
|
||||
// Já existe — usar UPDATE direto pelo id
|
||||
({ error } = await supabase.from('company_profiles').update(payload).eq('id', existing.id));
|
||||
({ error } = await tenantDb().from('company_profiles').update(payload).eq('id', existing.id));
|
||||
} else {
|
||||
// Não existe — INSERT
|
||||
({ error } = await supabase.from('company_profiles').insert(payload));
|
||||
({ error } = await tenantDb().from('company_profiles').insert(payload));
|
||||
}
|
||||
|
||||
if (error) throw error;
|
||||
@@ -626,10 +626,9 @@ async function saveAtendimento(silent = false) {
|
||||
await saveService({ name: 'Atendimento padrão', price: 0, duration_min: 50, owner_id: uid.value, tenant_id: tenantId.value });
|
||||
await loadServices(uid.value);
|
||||
}
|
||||
const { error } = await supabase.from('agenda_configuracoes').upsert(
|
||||
const { error } = await tenantDb().from('agenda_configuracoes').upsert(
|
||||
{
|
||||
owner_id: uid.value,
|
||||
tenant_id: tenantId.value,
|
||||
atendimento_mode: atendimento.value.mode,
|
||||
updated_at: new Date().toISOString()
|
||||
},
|
||||
@@ -703,7 +702,7 @@ async function onFinish() {
|
||||
finishing.value = true;
|
||||
try {
|
||||
const now = new Date().toISOString();
|
||||
const { error: finErr } = await supabase.from('agenda_configuracoes').upsert({ owner_id: uid.value, tenant_id: tenantId.value, setup_concluido: true, setup_concluido_em: now }, { onConflict: 'owner_id' });
|
||||
const { error: finErr } = await tenantDb().from('agenda_configuracoes').upsert({ owner_id: uid.value, setup_concluido: true, setup_concluido_em: now }, { onConflict: 'owner_id' });
|
||||
if (finErr) throw finErr;
|
||||
// Fecha o dialog ANTES de mostrar a tela de parabéns
|
||||
dialogVisible.value = false;
|
||||
|
||||
Reference in New Issue
Block a user