caterium-app/ops/sql/CATERIUM-OWNER-ACCOUNTS-V31.sql
2026-09-12 08:47:40 +03:00

472 lines
22 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Caterium v31: one company owner account, owner-only employee administration,
-- and a self-service profile for every authenticated user.
create or replace function public.caterium_is_workspace_owner(p_workspace uuid)
returns boolean
language sql
stable
security definer
set search_path='public'
as $$
select auth.uid() is not null
and exists(
select 1
from public.sun_workspaces w
join public.sun_workspace_members m
on m.workspace_id=w.id
and m.user_id=auth.uid()
and m.is_active=true
where w.id=p_workspace
and w.created_by=auth.uid()
);
$$;
revoke all on function public.caterium_is_workspace_owner(uuid) from public,anon;
grant execute on function public.caterium_is_workspace_owner(uuid) to authenticated;
create or replace function public.caterium_account_snapshot(p_workspace uuid)
returns jsonb
language plpgsql
stable
security definer
set search_path='public','auth'
as $$
declare
v_user uuid:=auth.uid();
v_row record;
begin
if v_user is null then raise exception 'Сначала войдите в Caterium'; end if;
select w.id as workspace_id,
w.name as company_name,
w.company_number,
w.created_by,
m.display_name,
m.role,
m.is_active,
coalesce(m.permissions,'{}'::jsonb) as permissions,
u.email::text as email
into v_row
from public.sun_workspaces w
join public.sun_workspace_members m
on m.workspace_id=w.id
and m.user_id=v_user
join auth.users u on u.id=v_user
where w.id=p_workspace
and m.is_active=true
limit 1;
if not found then raise exception 'Нет доступа к компании'; end if;
return jsonb_build_object(
'user_id',v_user,
'email',v_row.email,
'display_name',coalesce(nullif(v_row.display_name,''),split_part(coalesce(v_row.email,''),'@',1)),
'role',v_row.role,
'is_owner',(v_row.created_by=v_user),
'workspace_id',v_row.workspace_id,
'company_name',v_row.company_name,
'company_number',v_row.company_number,
'permissions',v_row.permissions
);
end;
$$;
revoke all on function public.caterium_account_snapshot(uuid) from public,anon;
grant execute on function public.caterium_account_snapshot(uuid) to authenticated;
create or replace function public.caterium_update_my_name(p_display_name text)
returns text
language plpgsql
security definer
set search_path='public','auth'
as $$
declare
v_user uuid:=auth.uid();
v_name text:=trim(coalesce(p_display_name,''));
begin
if v_user is null then raise exception 'Сначала войдите в Caterium'; end if;
if char_length(v_name)<1 then raise exception 'Введите имя'; end if;
if char_length(v_name)>120 then raise exception 'Имя слишком длинное'; end if;
update public.sun_workspace_members
set display_name=v_name,
updated_at=now()
where user_id=v_user;
update auth.users
set raw_user_meta_data=jsonb_set(coalesce(raw_user_meta_data,'{}'::jsonb),'{name}',to_jsonb(v_name),true)
where id=v_user;
return v_name;
end;
$$;
revoke all on function public.caterium_update_my_name(text) from public,anon;
grant execute on function public.caterium_update_my_name(text) to authenticated;
-- Existing company creators are the only company administrators.
update public.sun_workspace_members m
set role='admin',
permissions=jsonb_set(coalesce(m.permissions,public.sun_role_default_permissions('admin')),'{users.manage}','true'::jsonb,true),
updated_at=now()
from public.sun_workspaces w
where w.id=m.workspace_id
and m.user_id=w.created_by;
-- Any non-owner account is an employee account and can never manage users.
update public.sun_workspace_members m
set role=case when m.role='admin' then 'manager' else m.role end,
permissions=jsonb_set(coalesce(m.permissions,public.sun_role_default_permissions(case when m.role='admin' then 'manager' else m.role end)),'{users.manage}','false'::jsonb,true),
updated_at=now()
from public.sun_workspaces w
where w.id=m.workspace_id
and m.user_id<>w.created_by;
-- No pending invitation may create a second company administrator.
delete from public.sun_workspace_invites where used_at is null and role='admin';
create or replace function public.sun_list_workspace_members(p_workspace uuid)
returns table(user_id uuid,email text,display_name text,role text,is_active boolean,permissions jsonb,created_at timestamptz,updated_at timestamptz)
language plpgsql
stable
security definer
set search_path='public','auth'
as $$
begin
if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then
raise exception 'Только владелец компании может управлять пользователями';
end if;
return query
select m.user_id,u.email::text,m.display_name,m.role,m.is_active,
coalesce(m.permissions,public.sun_role_default_permissions(m.role)),m.created_at,m.updated_at
from public.sun_workspace_members m
join auth.users u on u.id=m.user_id
join public.sun_workspaces w on w.id=m.workspace_id
where m.workspace_id=p_workspace
order by (m.user_id=w.created_by) desc,coalesce(m.display_name,u.email::text);
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_role text:=lower(coalesce(p_role,''));
v_owner uuid;
v_max integer;
v_active_count integer;
v_permissions jsonb;
begin
perform pg_advisory_xact_lock(hashtext(p_workspace::text));
if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then
raise exception 'Только владелец компании может управлять сотрудниками';
end if;
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 v_role not in ('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 created_by into v_owner from public.sun_workspaces where id=p_workspace;
if v_owner is null then raise exception 'Компания не найдена'; end if;
if p_user=v_owner then raise exception 'Главный аккаунт компании нельзя изменить через управление сотрудниками'; end if;
if not exists(select 1 from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user) then raise exception 'Пользователь не найден'; end if;
if coalesce(p_is_active,false)
and not coalesce((select is_active from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user),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;
v_permissions:=coalesce(p_permissions,'{}'::jsonb)||jsonb_build_object('users.manage',false);
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=v_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_owner uuid;
begin
perform pg_advisory_xact_lock(hashtext(p_workspace::text));
if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then
raise exception 'Только владелец компании может удалять сотрудников';
end if;
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;
select created_by into v_owner from public.sun_workspaces where id=p_workspace;
if p_user=v_owner then raise exception 'Главный аккаунт компании удалить нельзя'; end if;
delete from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user;
end;
$$;
create or replace function public.sun_employee_prepare_v28(
p_workspace uuid,
p_email text,
p_display_name text default '',
p_role text default 'manager'
)
returns jsonb
language plpgsql
security definer
set search_path='public','auth'
as $$
declare
v_email text:=lower(trim(coalesce(p_email,'')));
v_name text:=trim(coalesce(p_display_name,''));
v_role text:=lower(coalesce(p_role,'manager'));
v_user uuid;
v_member public.sun_workspace_members%rowtype;
v_max integer;
v_count integer;
v_mode text;
begin
if auth.uid() is null then raise exception 'Сначала войдите в Caterium'; end if;
if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if;
if v_email='' or v_email !~* '^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$' then raise exception 'Введите корректный email сотрудника'; end if;
if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Сотруднику нельзя назначить роль владельца'; end if;
if v_name='' then v_name:=split_part(v_email,'@',1); end if;
v_mode:=public.sun_workspace_access_mode_internal_v28(p_workspace);
if v_mode<>'full' then raise exception 'Подписка компании не позволяет добавлять сотрудников'; end if;
if not public.sun_workspace_feature_internal_v28(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if;
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_count from public.sun_workspace_members
where workspace_id=p_workspace and is_active=true;
select u.id into v_user from auth.users u where lower(coalesce(u.email,''))=v_email order by u.created_at asc limit 1;
if v_user is not null then
select * into v_member from public.sun_workspace_members
where workspace_id=p_workspace and user_id=v_user limit 1;
if found and v_member.is_active then
return jsonb_build_object('status','already_member','user_id',v_user,'email',v_email,'display_name',coalesce(nullif(v_member.display_name,''),v_name),'role',v_member.role);
end if;
end if;
if v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if;
return jsonb_build_object(
'status',case when v_user is null then 'new' else 'existing' end,
'user_id',v_user,
'email',v_email,
'display_name',v_name,
'role',v_role,
'max_members',v_max,
'active_members',v_count
);
end;
$$;
create or replace function public.sun_employee_finalize_v28(
p_workspace uuid,
p_user_id uuid,
p_display_name text default '',
p_role text default 'manager'
)
returns jsonb
language plpgsql
security definer
set search_path='public','auth'
as $$
declare
v_name text:=trim(coalesce(p_display_name,''));
v_role text:=lower(coalesce(p_role,'manager'));
v_email text;
v_max integer;
v_count integer;
v_already boolean:=false;
begin
if auth.uid() is null then raise exception 'Сначала войдите в Caterium'; end if;
if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if;
if public.sun_workspace_access_mode_internal_v28(p_workspace)<>'full' then raise exception 'Подписка компании не позволяет добавлять сотрудников'; end if;
if not public.sun_workspace_feature_internal_v28(p_workspace,'users_manage') then raise exception 'Добавление сотрудников недоступно на текущем тарифе'; end if;
if v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Сотруднику нельзя назначить роль владельца'; end if;
select lower(email) into v_email from auth.users where id=p_user_id;
if v_email is null then raise exception 'Аккаунт сотрудника не найден'; end if;
if v_name='' then v_name:=split_part(v_email,'@',1); end if;
select exists(select 1 from public.sun_workspace_members where workspace_id=p_workspace and user_id=p_user_id and is_active=true) into v_already;
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_count from public.sun_workspace_members where workspace_id=p_workspace and is_active=true;
if not v_already and v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if;
insert into public.sun_workspace_members(workspace_id,user_id,role,display_name,is_active,permissions,updated_at)
values(p_workspace,p_user_id,v_role,left(v_name,120),true,public.sun_role_default_permissions(v_role)||jsonb_build_object('users.manage',false),now())
on conflict(workspace_id,user_id) do update
set role=excluded.role,display_name=excluded.display_name,is_active=true,permissions=excluded.permissions,updated_at=now();
delete from public.sun_workspace_invites where workspace_id=p_workspace and used_at is null and lower(coalesce(email,''))=v_email;
return jsonb_build_object('status',case when v_already then 'updated' else 'added' end,'user_id',p_user_id,'email',v_email,'display_name',v_name,'role',v_role);
end;
$$;
create or replace function public.sun_create_invite(p_workspace uuid,p_role text default 'manager')
returns uuid
language plpgsql
security definer
set search_path='public'
as $$
declare
v_token uuid;
v_role text:=lower(coalesce(p_role,'manager'));
v_max integer;
v_count integer;
begin
if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if;
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 v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Некорректная роль сотрудника'; end if;
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_count from public.sun_workspace_members where workspace_id=p_workspace and is_active=true;
if v_max is not null and v_count>=v_max then raise exception 'Достигнут лимит сотрудников тарифа (%).',v_max; end if;
insert into public.sun_workspace_invites(workspace_id,role,permissions,created_by)
values(p_workspace,v_role,public.sun_role_default_permissions(v_role)||jsonb_build_object('users.manage',false),auth.uid()) returning token into v_token;
return v_token;
end;
$$;
create or replace function public.sun_create_invite_v27(
p_workspace uuid,
p_email text,
p_display_name text default '',
p_role text default 'manager'
)
returns uuid
language plpgsql
security definer
set search_path='public','auth'
as $$
declare
v_token uuid;
v_role text:=lower(coalesce(p_role,'manager'));
v_email text:=lower(trim(coalesce(p_email,'')));
v_name text:=trim(coalesce(p_display_name,''));
v_max integer;
v_active integer;
v_pending integer;
begin
if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может добавлять сотрудников'; end if;
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 v_role not in ('manager','kitchen','courier','viewer') then raise exception 'Некорректная роль сотрудника'; end if;
if v_email='' or v_email !~* '^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$' then raise exception 'Введите корректный email сотрудника'; end if;
if v_name='' then v_name:=split_part(v_email,'@',1); end if;
if exists(
select 1 from public.sun_workspace_members m
join auth.users u on u.id=m.user_id
where m.workspace_id=p_workspace and m.is_active=true and lower(coalesce(u.email,''))=v_email
) then raise exception 'Пользователь с этим email уже добавлен в компанию'; end if;
delete from public.sun_workspace_invites
where workspace_id=p_workspace and used_at is null
and (expires_at<=now() or lower(coalesce(email,''))=v_email);
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;
if v_max is not null then
select count(*)::int into v_active from public.sun_workspace_members where workspace_id=p_workspace and is_active=true;
select count(*)::int into v_pending from public.sun_workspace_invites where workspace_id=p_workspace and used_at is null and expires_at>now();
if v_active+v_pending>=v_max then raise exception 'Достигнут лимит пользователей тарифа (%)',v_max; end if;
end if;
insert into public.sun_workspace_invites(workspace_id,role,permissions,created_by,email,display_name)
values(p_workspace,v_role,public.sun_role_default_permissions(v_role)||jsonb_build_object('users.manage',false),auth.uid(),v_email,left(v_name,120))
returning token into v_token;
return v_token;
end;
$$;
create or replace function public.sun_list_workspace_invites_v27(p_workspace uuid)
returns table(token uuid,email text,display_name text,role text,created_at timestamptz,expires_at timestamptz,status text)
language plpgsql
stable
security definer
set search_path='public'
as $$
begin
if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может просматривать приглашения'; end if;
return query
select i.token,i.email,i.display_name,i.role,i.created_at,i.expires_at,
case when i.expires_at<=now() then 'expired' else 'pending' end::text
from public.sun_workspace_invites i
where i.workspace_id=p_workspace and i.used_at is null
order by i.created_at desc;
end;
$$;
create or replace function public.sun_cancel_invite_v27(p_workspace uuid,p_token uuid)
returns boolean
language plpgsql
security definer
set search_path='public'
as $$
declare v_deleted integer;
begin
if not public.caterium_is_workspace_owner(p_workspace) and not public.sun_is_platform_admin() then raise exception 'Только владелец компании может отменять приглашения'; end if;
delete from public.sun_workspace_invites where workspace_id=p_workspace and token=p_token and used_at is null;
get diagnostics v_deleted=row_count;
return v_deleted>0;
end;
$$;
revoke all on function public.sun_list_workspace_members(uuid) 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;
revoke all on function public.sun_employee_prepare_v28(uuid,text,text,text) from public,anon;
revoke all on function public.sun_employee_finalize_v28(uuid,uuid,text,text) from public,anon;
revoke all on function public.sun_create_invite(uuid,text) from public,anon;
revoke all on function public.sun_create_invite_v27(uuid,text,text,text) from public,anon;
revoke all on function public.sun_list_workspace_invites_v27(uuid) from public,anon;
revoke all on function public.sun_cancel_invite_v27(uuid,uuid) from public,anon;
grant execute on function public.sun_list_workspace_members(uuid) 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;
grant execute on function public.sun_employee_prepare_v28(uuid,text,text,text) to authenticated;
grant execute on function public.sun_employee_finalize_v28(uuid,uuid,text,text) to authenticated;
grant execute on function public.sun_create_invite(uuid,text) to authenticated;
grant execute on function public.sun_create_invite_v27(uuid,text,text,text) to authenticated;
grant execute on function public.sun_list_workspace_invites_v27(uuid) to authenticated;
grant execute on function public.sun_cancel_invite_v27(uuid,uuid) to authenticated;