-- ============================================================================= -- F2 — Schema-per-tenant: provisionamento cria o schema físico -- -- Os 3 pontos de criação de tenant passam a chamar clone_tenant_template() -- logo após inserir em tenants/tenant_members. Tudo na mesma transação: -- se o clone falhar, o tenant não nasce (atomicidade). -- -- Pontos cobertos (F0 §levantamento — não há outros INSERT INTO tenants): -- * provision_account_tenant — wizard de cadastro (therapist/clinic_*) -- * create_clinic_tenant — criação avulsa de clínica -- * ensure_personal_tenant_for_user — tenant pessoal (kind='saas'), -- chamado também pelo trigger de signup (handle_new_user_create_personal_tenant) -- -- Decisão Q2: TODO tenant ganha schema, inclusive therapist e pessoal. -- Clones nascem sem triggers de negócio (F6) e fora do PostgREST (F5). -- ============================================================================= BEGIN; CREATE OR REPLACE FUNCTION public.create_clinic_tenant(p_name text) RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER AS $function$ declare v_uid uuid; v_tenant uuid; v_name text; begin v_uid := auth.uid(); if v_uid is null then raise exception 'Not authenticated'; end if; v_name := nullif(trim(coalesce(p_name, '')), ''); if v_name is null then v_name := 'Clínica'; end if; insert into public.tenants (name, kind, created_at) values (v_name, 'clinic', now()) returning id into v_tenant; insert into public.tenant_members (tenant_id, user_id, role, status, created_at) values (v_tenant, v_uid, 'tenant_admin', 'active', now()); -- F2: schema físico do tenant (rollback junto se algo falhar) perform public.clone_tenant_template(v_tenant); return v_tenant; end; $function$; CREATE OR REPLACE FUNCTION public.ensure_personal_tenant_for_user(p_user_id uuid) RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER AS $function$ declare v_uid uuid; v_existing uuid; v_tenant uuid; v_email text; v_name text; begin v_uid := p_user_id; if v_uid is null then raise exception 'Missing user id'; end if; -- só considera tenant pessoal (kind='saas') select tm.tenant_id into v_existing from public.tenant_members tm join public.tenants t on t.id = tm.tenant_id where tm.user_id = v_uid and tm.status = 'active' and t.kind = 'saas' order by tm.created_at desc limit 1; if v_existing is not null then return v_existing; end if; select email into v_email from auth.users where id = v_uid; v_name := coalesce(split_part(v_email, '@', 1), 'Conta'); insert into public.tenants (name, kind, created_at) values (v_name || ' (Pessoal)', 'saas', now()) returning id into v_tenant; insert into public.tenant_members (tenant_id, user_id, role, status, created_at) values (v_tenant, v_uid, 'tenant_admin', 'active', now()); -- F2: schema físico do tenant (rollback junto se algo falhar) perform public.clone_tenant_template(v_tenant); return v_tenant; end; $function$; CREATE OR REPLACE FUNCTION public.provision_account_tenant(p_user_id uuid, p_kind text, p_name text DEFAULT NULL::text) RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER AS $function$ DECLARE v_tenant_id uuid; v_account_type text; v_name text; BEGIN IF p_kind NOT IN ('therapist', 'clinic_coworking', 'clinic_reception', 'clinic_full') THEN RAISE EXCEPTION 'kind inválido: "%". Use: therapist, clinic_coworking, clinic_reception, clinic_full.', p_kind USING ERRCODE = 'P0001'; END IF; v_account_type := CASE WHEN p_kind = 'therapist' THEN 'therapist' ELSE 'clinic' END; IF EXISTS ( SELECT 1 FROM public.tenant_members tm JOIN public.tenants t ON t.id = tm.tenant_id WHERE tm.user_id = p_user_id AND tm.role = 'tenant_admin' AND tm.status = 'active' AND t.kind = p_kind ) THEN RAISE EXCEPTION 'Usuário já possui um tenant do tipo "%".', p_kind USING ERRCODE = 'P0001'; END IF; v_name := COALESCE( NULLIF(TRIM(p_name), ''), ( SELECT COALESCE(NULLIF(TRIM(pr.full_name), ''), SPLIT_PART(au.email, '@', 1)) FROM public.profiles pr JOIN auth.users au ON au.id = pr.id WHERE pr.id = p_user_id ), 'Conta' ); INSERT INTO public.tenants (name, kind, created_at) VALUES (v_name, p_kind, now()) RETURNING id INTO v_tenant_id; INSERT INTO public.tenant_members (tenant_id, user_id, role, status, created_at) VALUES (v_tenant_id, p_user_id, 'tenant_admin', 'active', now()); UPDATE public.profiles SET account_type = v_account_type WHERE id = p_user_id; PERFORM public.seed_determined_commitments(v_tenant_id); -- F2: schema físico do tenant (rollback junto se algo falhar) PERFORM public.clone_tenant_template(v_tenant_id); RETURN v_tenant_id; END; $function$; COMMIT;