-- Caterium v17.5.30 - granular administrator permissions and correct last-admin checks. create or replace function public.sun_has_permission(p_workspace uuid, p_permission text) returns boolean language plpgsql stable security definer set search_path='public' as $$ declare v_role text; v_permissions jsonb; v_active boolean; begin select role,permissions,is_active into v_role,v_permissions,v_active from public.sun_workspace_members where workspace_id=p_workspace and user_id=auth.uid() limit 1; if not coalesce(v_active,false) then return false; end if; -- Explicit member permissions override the role template for every role, -- including administrators. Missing keys fall back to the role defaults. if coalesce(v_permissions,'{}'::jsonb) ? p_permission then return coalesce((v_permissions->>p_permission)::boolean,false); end if; return coalesce((public.sun_role_default_permissions(v_role)->>p_permission)::boolean,false); end; $$; create or replace function public.sun_admin_update_member( p_workspace uuid, p_user uuid, p_display_name text, p_role text, p_is_active boolean, p_permissions jsonb ) returns void language plpgsql security definer set search_path='public' as $$ declare v_old_role text; v_old_active boolean; v_role text:=lower(coalesce(p_role,'')); v_other_admins integer:=0; v_other_managing_admins integer:=0; v_target_will_manage boolean:=false; v_max integer; v_active_count integer; begin -- Serialize membership administration inside one workspace so two admins -- cannot simultaneously remove/demote the last administrators. perform pg_advisory_xact_lock(hashtext(p_workspace::text)); if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if; if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if; if not public.sun_has_permission(p_workspace,'users.manage') then raise exception 'Недостаточно прав для управления сотрудниками'; end if; if v_role not in ('admin','manager','kitchen','courier','viewer') then raise exception 'Некорректная роль'; end if; if p_permissions is null or jsonb_typeof(p_permissions)<>'object' then raise exception 'Некорректные права пользователя'; end if; select role,is_active into v_old_role,v_old_active from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user for update; if not found then raise exception 'Пользователь не найден'; end if; select count(*)::int into v_other_admins from public.sun_workspace_members where workspace_id=p_workspace and user_id<>p_user and role='admin' and is_active=true; if v_old_role='admin' and coalesce(v_old_active,false) and (v_role<>'admin' or not coalesce(p_is_active,false)) and v_other_admins=0 then raise exception 'Нельзя отключить или понизить последнего активного администратора'; end if; select count(*)::int into v_other_managing_admins from public.sun_workspace_members where workspace_id=p_workspace and user_id<>p_user and role='admin' and is_active=true and coalesce( case when coalesce(permissions,'{}'::jsonb) ? 'users.manage' then (permissions->>'users.manage')::boolean else null end, true )=true; v_target_will_manage := v_role='admin' and coalesce(p_is_active,false) and coalesce( case when p_permissions ? 'users.manage' then (p_permissions->>'users.manage')::boolean else null end, true ); if not v_target_will_manage and v_other_managing_admins=0 then raise exception 'У хотя бы одного активного администратора должно оставаться право «Пользователи и права»'; end if; if coalesce(p_is_active,false) and not coalesce(v_old_active,false) then select p.max_members into v_max from public.sun_workspace_subscriptions s join public.sun_plans p on p.id=s.plan_id where s.workspace_id=p_workspace; select count(*)::int into v_active_count from public.sun_workspace_members where workspace_id=p_workspace and is_active=true; if v_max is not null and v_active_count>=v_max then raise exception 'Достигнут лимит сотрудников тарифа (%).',v_max; end if; end if; update public.sun_workspace_members set display_name=nullif(trim(coalesce(p_display_name,'')),''), role=v_role, is_active=coalesce(p_is_active,false), permissions=p_permissions, updated_at=now() where workspace_id=p_workspace and user_id=p_user; end; $$; create or replace function public.sun_admin_remove_member(p_workspace uuid,p_user uuid) returns void language plpgsql security definer set search_path='public' as $$ declare v_role text; v_active boolean; v_other_admins integer:=0; v_other_managing_admins integer:=0; begin perform pg_advisory_xact_lock(hashtext(p_workspace::text)); if public.sun_subscription_access_mode(p_workspace)<>'full' then raise exception 'Подписка не позволяет изменять пользователей'; end if; if not public.sun_workspace_has_feature(p_workspace,'users_manage') then raise exception 'Управление сотрудниками недоступно на текущем тарифе'; end if; if not public.sun_has_permission(p_workspace,'users.manage') then raise exception 'Недостаточно прав для управления сотрудниками'; end if; select role,is_active into v_role,v_active from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user for update; if not found then return; end if; if v_role='admin' and coalesce(v_active,false) then select count(*)::int into v_other_admins from public.sun_workspace_members where workspace_id=p_workspace and user_id<>p_user and role='admin' and is_active=true; if v_other_admins=0 then raise exception 'Нельзя удалить последнего активного администратора'; end if; select count(*)::int into v_other_managing_admins from public.sun_workspace_members where workspace_id=p_workspace and user_id<>p_user and role='admin' and is_active=true and coalesce( case when coalesce(permissions,'{}'::jsonb) ? 'users.manage' then (permissions->>'users.manage')::boolean else null end, true )=true; if v_other_managing_admins=0 then raise exception 'Нельзя удалить администратора: после удаления никто не сможет управлять пользователями'; end if; end if; delete from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user; end; $$; revoke all on function public.sun_has_permission(uuid,text) from public,anon; revoke all on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) from public,anon; revoke all on function public.sun_admin_remove_member(uuid,uuid) from public,anon; grant execute on function public.sun_has_permission(uuid,text) to authenticated; grant execute on function public.sun_admin_update_member(uuid,uuid,text,text,boolean,jsonb) to authenticated; grant execute on function public.sun_admin_remove_member(uuid,uuid) to authenticated;