/* patient_groups_setup.sql Setup completo para: - public.patient_groups - public.patient_group_patient (tabela ponte) - view public.v_patient_groups_with_counts - índice único por owner + nome (case-insensitive) - 3 grupos padrão do sistema (Crianças, Adolescentes, Idosos) NÃO editáveis / NÃO removíveis - triggers de proteção Observação (importante): - Os grupos padrão são criados com owner_id = '00000000-0000-0000-0000-000000000000' (SYSTEM_OWNER), para ficarem "globais" e não dependerem de auth.uid() em migrations. - Se você quiser que os grupos padrão pertençam a um owner específico (tenant), basta trocar o SYSTEM_OWNER abaixo por esse UUID. */ begin; -- =========================== -- 0) Constante de "dono do sistema" -- =========================== -- Troque aqui se você quiser que os grupos padrão pertençam a um owner específico. -- Ex.: '816b24fe-a0c3-4409-b79b-c6c0a6935d03' do $$ begin -- só para documentar; não cria nada end $$; -- =========================== -- 1) Tabela principal: patient_groups -- =========================== create table if not exists public.patient_groups ( id uuid primary key default gen_random_uuid(), name text not null, description text, color text, is_active boolean not null default true, is_system boolean not null default false, owner_id uuid not null, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); -- (Opcional, mas recomendado) Garante que name não seja só espaços -- e evita nomes vazios. alter table public.patient_groups drop constraint if exists patient_groups_name_not_blank_check; alter table public.patient_groups add constraint patient_groups_name_not_blank_check check (length(btrim(name)) > 0); -- =========================== -- 2) Tabela ponte: patient_group_patient -- =========================== -- Se você já tiver essa tabela com FKs, ajuste aqui conforme seu schema. create table if not exists public.patient_group_patient ( patient_group_id uuid not null references public.patient_groups(id) on delete cascade, patient_id uuid not null references public.patients(id) on delete cascade, created_at timestamptz not null default now() ); -- Evita duplicar vínculo paciente<->grupo create unique index if not exists patient_group_patient_unique on public.patient_group_patient (patient_group_id, patient_id); -- =========================== -- 3) View com contagem -- =========================== create or replace view public.v_patient_groups_with_counts as select g.*, coalesce(count(distinct pgp.patient_id), 0)::int as patients_count from public.patient_groups g left join public.patient_group_patient pgp on pgp.patient_group_id = g.id group by g.id; -- =========================== -- 4) Índice único: não permitir mesmo nome por owner (case-insensitive) -- =========================== -- Atenção: se já existirem duplicados, este índice pode falhar ao criar. create unique index if not exists patient_groups_owner_name_unique on public.patient_groups (owner_id, (lower(name))); -- =========================== -- 5) Triggers de proteção: system não edita / não remove -- =========================== create or replace function public.prevent_system_group_changes() returns trigger language plpgsql as $$ begin if old.is_system = true then raise exception 'Grupos padrão do sistema não podem ser alterados ou excluídos.'; end if; if tg_op = 'DELETE' then return old; end if; return new; end; $$; drop trigger if exists trg_prevent_system_group_changes on public.patient_groups; create trigger trg_prevent_system_group_changes before update or delete on public.patient_groups for each row execute function public.prevent_system_group_changes(); -- Impede "promover" um grupo comum para system create or replace function public.prevent_promoting_to_system() returns trigger language plpgsql as $$ begin if new.is_system = true and old.is_system is distinct from true then raise exception 'Não é permitido transformar um grupo comum em grupo do sistema.'; end if; return new; end; $$; drop trigger if exists trg_prevent_promoting_to_system on public.patient_groups; create trigger trg_prevent_promoting_to_system before update on public.patient_groups for each row execute function public.prevent_promoting_to_system(); -- =========================== -- 6) Inserir 3 grupos padrão (imutáveis) -- =========================== -- Dono "global" do sistema (mude se quiser): -- 00000000-0000-0000-0000-000000000000 with sys_owner as ( select '00000000-0000-0000-0000-000000000000'::uuid as owner_id ) insert into public.patient_groups (name, description, color, is_active, is_system, owner_id) select v.name, v.description, v.color, v.is_active, v.is_system, s.owner_id from sys_owner s join (values ('Crianças', 'Grupo padrão do sistema', null, true, true), ('Adolescentes', 'Grupo padrão do sistema', null, true, true), ('Idosos', 'Grupo padrão do sistema', null, true, true) ) as v(name, description, color, is_active, is_system) on true where not exists ( select 1 from public.patient_groups g where g.owner_id = s.owner_id and lower(g.name) = lower(v.name) ); commit; /* Testes rápidos: 1) Ver tudo: select * from public.v_patient_groups_with_counts order by is_system desc, name; 2) Tentar editar um system (deve falhar): update public.patient_groups set name='X' where name='Crianças'; 3) Tentar deletar um system (deve falhar): delete from public.patient_groups where name='Crianças'; 4) Tentar duplicar nome no mesmo owner (deve falhar por índice único): insert into public.patient_groups (name, is_active, is_system, owner_id) values ('teste22', true, false, '816b24fe-a0c3-4409-b79b-c6c0a6935d03'); */