-- ============================================================================= -- F6.2 (wiring) — tenants NOVOS nascem com todos os triggers de negócio -- -- ⚠️ APLICAR COMO supabase_admin. -- -- Até aqui os 9 schemas existentes ganharam os triggers via backfills (Lotes -- A/B/C). Um tenant NOVO (clone_tenant_template) só ganhava channel-routing + -- RLS. Este wiring: -- 1. attach_agnostic_triggers → SELF-CONTAINED (dirigido por colunas, não lê -- public; sobrevive ao DROP da F6.3). -- 2. trigger AFTER INSERT em tenant_schemas dispara os 3 attach (agnostic + -- schema_aware + notif) pro schema novo — clone_tenant_template não precisa -- ser tocado (ele insere em tenant_schemas). -- 3. provision_account_tenant: clone ANTES do seed (seed é no-op se o schema -- não existe; precisa do schema criado primeiro). -- ============================================================================= BEGIN; -- 1) attach_agnostic_triggers self-contained --------------------------------- CREATE OR REPLACE FUNCTION public.attach_agnostic_triggers(p_schema text) RETURNS int LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public','pg_temp' AS $$ DECLARE r record; v_count int := 0; BEGIN IF p_schema NOT LIKE 'tenant\_%' THEN RAISE EXCEPTION 'schema inválido %', p_schema; END IF; -- set_updated_at em toda tabela do schema que tem coluna updated_at FOR r IN SELECT c.relname AS tab FROM pg_class c JOIN pg_attribute a ON a.attrelid = c.oid WHERE c.relnamespace = p_schema::regnamespace AND c.relkind = 'r' AND a.attname = 'updated_at' AND NOT a.attisdropped AND c.relname NOT LIKE '\_%' LOOP EXECUTE format('DROP TRIGGER IF EXISTS set_updated_at ON %I.%I', p_schema, r.tab); EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %I.%I FOR EACH ROW EXECUTE FUNCTION public.set_updated_at()', p_schema, r.tab); v_count := v_count + 1; END LOOP; -- prevent_* em patient_groups IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = p_schema AND table_name = 'patient_groups') THEN EXECUTE format('DROP TRIGGER IF EXISTS prevent_promoting_to_system ON %I.patient_groups', p_schema); EXECUTE format('CREATE TRIGGER prevent_promoting_to_system BEFORE UPDATE ON %I.patient_groups FOR EACH ROW EXECUTE FUNCTION public.prevent_promoting_to_system()', p_schema); EXECUTE format('DROP TRIGGER IF EXISTS prevent_system_group_changes ON %I.patient_groups', p_schema); EXECUTE format('CREATE TRIGGER prevent_system_group_changes BEFORE UPDATE OR DELETE ON %I.patient_groups FOR EACH ROW EXECUTE FUNCTION public.prevent_system_group_changes()', p_schema); v_count := v_count + 2; END IF; RETURN v_count; END $$; -- 2) trigger de wiring em tenant_schemas ------------------------------------- GRANT EXECUTE ON FUNCTION public.attach_agnostic_triggers(text) TO postgres, service_role; GRANT EXECUTE ON FUNCTION public.attach_schema_aware_triggers(text) TO postgres, service_role; GRANT EXECUTE ON FUNCTION public.attach_notif_triggers(text) TO postgres, service_role; CREATE OR REPLACE FUNCTION public.trg_attach_business_triggers() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public','pg_temp' AS $$ BEGIN PERFORM public.attach_agnostic_triggers(NEW.schema_name); PERFORM public.attach_schema_aware_triggers(NEW.schema_name); PERFORM public.attach_notif_triggers(NEW.schema_name); RETURN NULL; END $$; ALTER FUNCTION public.trg_attach_business_triggers() OWNER TO supabase_admin; DROP TRIGGER IF EXISTS trg_tenant_schemas_attach ON public.tenant_schemas; CREATE TRIGGER trg_tenant_schemas_attach AFTER INSERT ON public.tenant_schemas FOR EACH ROW EXECUTE FUNCTION public.trg_attach_business_triggers(); -- 3) provision_account_tenant: clone ANTES do seed --------------------------- 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 $$ 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; -- F6 wiring: clone PRIMEIRO (cria o schema), seed DEPOIS (escreve no schema) PERFORM public.clone_tenant_template(v_tenant_id); PERFORM public.seed_determined_commitments(v_tenant_id); RETURN v_tenant_id; END $$; COMMIT;