692 lines
27 KiB
PL/PgSQL
692 lines
27 KiB
PL/PgSQL
create extension if not exists pgcrypto;
|
||
|
||
create table if not exists public.roles (
|
||
id uuid primary key default gen_random_uuid(),
|
||
name text not null unique,
|
||
permissions jsonb not null default '[]'::jsonb,
|
||
created_at timestamptz not null default timezone('utc', now())
|
||
);
|
||
|
||
create table if not exists public.users (
|
||
id uuid primary key references auth.users (id) on delete cascade,
|
||
email text not null unique,
|
||
name text not null,
|
||
role_id uuid not null references public.roles (id),
|
||
last_login timestamptz,
|
||
created_at timestamptz not null default timezone('utc', now())
|
||
);
|
||
|
||
create table if not exists public.orders (
|
||
id uuid primary key default gen_random_uuid(),
|
||
order_number text not null unique,
|
||
customer jsonb not null,
|
||
status text not null,
|
||
delivery_agreement_status text not null default 'Не начато',
|
||
manager_id uuid references public.users (id),
|
||
logistician_id uuid references public.users (id),
|
||
assigned_driver_id uuid references public.users (id),
|
||
ready_for_delivery_at timestamptz,
|
||
delivery_flow_started_at timestamptz,
|
||
delivery_flow_source text,
|
||
created_at timestamptz not null default timezone('utc', now()),
|
||
updated_at timestamptz not null default timezone('utc', now())
|
||
);
|
||
|
||
create table if not exists public.order_logisticians (
|
||
id uuid primary key default gen_random_uuid(),
|
||
order_id uuid not null references public.orders (id) on delete cascade,
|
||
logistician_id uuid not null references public.users (id) on delete cascade,
|
||
assigned_at timestamptz not null default timezone('utc', now()),
|
||
assigned_by uuid references public.users (id),
|
||
unique (order_id, logistician_id)
|
||
);
|
||
|
||
create table if not exists public.order_history (
|
||
id uuid primary key default gen_random_uuid(),
|
||
order_id uuid not null references public.orders (id) on delete cascade,
|
||
action text not null,
|
||
old_status text,
|
||
new_status text,
|
||
user_id uuid references public.users (id),
|
||
metadata jsonb not null default '{}'::jsonb,
|
||
created_at timestamptz not null default timezone('utc', now())
|
||
);
|
||
|
||
create table if not exists public.delivery_slots (
|
||
id uuid primary key default gen_random_uuid(),
|
||
order_id uuid not null references public.orders (id) on delete cascade,
|
||
delivery_date date not null,
|
||
delivery_time text not null,
|
||
logistician_id uuid references public.users (id),
|
||
status text not null default 'pending_confirmation',
|
||
selected_by_client_at timestamptz,
|
||
created_at timestamptz not null default timezone('utc', now())
|
||
);
|
||
|
||
create table if not exists public.chat_messages (
|
||
id uuid primary key default gen_random_uuid(),
|
||
order_id uuid not null references public.orders (id) on delete cascade,
|
||
sender_name text,
|
||
sender_type text not null check (sender_type in ('client', 'bot', 'operator', 'system')),
|
||
channel text not null check (channel in ('telegram', 'vk', 'messenger_max', 'sms', 'email')),
|
||
text text not null,
|
||
external_message_id text,
|
||
payload jsonb not null default '{}'::jsonb,
|
||
created_at timestamptz not null default timezone('utc', now())
|
||
);
|
||
|
||
create table if not exists public.error_logs (
|
||
id uuid primary key default gen_random_uuid(),
|
||
order_id uuid references public.orders (id) on delete set null,
|
||
provider text,
|
||
level text not null default 'error',
|
||
message text not null,
|
||
details jsonb not null default '{}'::jsonb,
|
||
created_at timestamptz not null default timezone('utc', now())
|
||
);
|
||
|
||
create table if not exists public.delivery_invitations (
|
||
id uuid primary key default gen_random_uuid(),
|
||
order_id uuid not null references public.orders (id) on delete cascade unique,
|
||
token_hash text not null unique,
|
||
state text not null default 'awaiting_choice',
|
||
order_number text,
|
||
customer_name text,
|
||
customer_phone text,
|
||
customer_messenger text,
|
||
available_slots text[] not null default array['Первая половина дня', 'Вторая половина дня'],
|
||
expires_at timestamptz,
|
||
revoked_at timestamptz,
|
||
access_count integer not null default 0,
|
||
last_accessed_at timestamptz,
|
||
delivery_date date,
|
||
delivery_time text,
|
||
sent_at timestamptz,
|
||
opened_at timestamptz,
|
||
confirmed_at timestamptz,
|
||
logistics_transferred_at timestamptz,
|
||
paid_storage_at timestamptz,
|
||
delivered_at timestamptz,
|
||
created_at timestamptz not null default timezone('utc', now()),
|
||
updated_at timestamptz not null default timezone('utc', now())
|
||
);
|
||
|
||
create table if not exists public.integration_events (
|
||
id uuid primary key default gen_random_uuid(),
|
||
order_id uuid references public.orders (id) on delete set null,
|
||
event_type text not null,
|
||
direction text not null default 'internal',
|
||
source text not null default 'supabase-function',
|
||
status text not null default 'success',
|
||
payload jsonb not null default '{}'::jsonb,
|
||
error_message text,
|
||
created_at timestamptz not null default timezone('utc', now())
|
||
);
|
||
|
||
create table if not exists public.rate_limits (
|
||
id uuid primary key default gen_random_uuid(),
|
||
scope text not null,
|
||
rate_key text not null,
|
||
window_start timestamptz not null,
|
||
count integer not null default 1,
|
||
blocked_until timestamptz,
|
||
created_at timestamptz not null default timezone('utc', now()),
|
||
updated_at timestamptz not null default timezone('utc', now()),
|
||
unique (scope, rate_key, window_start)
|
||
);
|
||
|
||
alter table public.orders add column if not exists delivery_agreement_status text not null default 'Не начато';
|
||
alter table public.orders add column if not exists assigned_driver_id uuid references public.users (id);
|
||
alter table public.orders add column if not exists ready_for_delivery_at timestamptz;
|
||
alter table public.orders add column if not exists delivery_flow_started_at timestamptz;
|
||
alter table public.orders add column if not exists delivery_flow_source text;
|
||
alter table public.chat_messages drop constraint if exists chat_messages_channel_check;
|
||
alter table public.chat_messages
|
||
add constraint chat_messages_channel_check
|
||
check (channel in ('telegram', 'vk', 'messenger_max', 'sms', 'email'));
|
||
|
||
alter table public.delivery_invitations add column if not exists state text not null default 'awaiting_choice';
|
||
alter table public.delivery_invitations add column if not exists expires_at timestamptz;
|
||
alter table public.delivery_invitations add column if not exists revoked_at timestamptz;
|
||
alter table public.delivery_invitations add column if not exists access_count integer not null default 0;
|
||
alter table public.delivery_invitations add column if not exists last_accessed_at timestamptz;
|
||
alter table public.delivery_invitations add column if not exists delivery_date date;
|
||
alter table public.delivery_invitations add column if not exists delivery_time text;
|
||
alter table public.delivery_invitations add column if not exists sent_at timestamptz;
|
||
alter table public.delivery_invitations add column if not exists opened_at timestamptz;
|
||
alter table public.delivery_invitations add column if not exists confirmed_at timestamptz;
|
||
alter table public.delivery_invitations add column if not exists logistics_transferred_at timestamptz;
|
||
alter table public.delivery_invitations add column if not exists paid_storage_at timestamptz;
|
||
alter table public.delivery_invitations add column if not exists delivered_at timestamptz;
|
||
alter table public.delivery_invitations add column if not exists updated_at timestamptz not null default timezone('utc', now());
|
||
|
||
alter table public.orders add column if not exists source_order_number text;
|
||
alter table public.orders add column if not exists source_order_date date;
|
||
alter table public.orders add column if not exists source_customer_name text;
|
||
alter table public.orders add column if not exists source_customer_phone text;
|
||
alter table public.orders add column if not exists source_customer_email text;
|
||
alter table public.orders add column if not exists source_customer_city text;
|
||
alter table public.orders add column if not exists source_total_sum numeric;
|
||
alter table public.orders add column if not exists source_paid_at timestamptz;
|
||
alter table public.orders add column if not exists source_gateway text;
|
||
alter table public.orders add column if not exists source_associated_bills_text text;
|
||
alter table public.orders add column if not exists source_production_at timestamptz;
|
||
alter table public.orders add column if not exists source_saw_at timestamptz;
|
||
alter table public.orders add column if not exists source_glue_at timestamptz;
|
||
alter table public.orders add column if not exists source_h_glue_at timestamptz;
|
||
alter table public.orders add column if not exists source_curve_at timestamptz;
|
||
alter table public.orders add column if not exists source_accept_at timestamptz;
|
||
alter table public.orders add column if not exists source_ship_at timestamptz;
|
||
alter table public.orders add column if not exists source_payload jsonb;
|
||
alter table public.orders add column if not exists delivery_set_key text;
|
||
alter table public.orders add column if not exists delivery_set_name text;
|
||
alter table public.orders add column if not exists delivery_set_status text;
|
||
alter table public.orders add column if not exists delivery_set_ready_at timestamptz;
|
||
alter table public.orders add column if not exists delivery_ready_reason text;
|
||
alter table public.orders add column if not exists source_sms_legacy_at timestamptz;
|
||
|
||
comment on column public.orders.source_sms_legacy_at is 'Informational only: legacy 1C SMS timestamp. Must NOT be used to start new delivery automation scenarios.';
|
||
|
||
alter table public.integration_events add column if not exists direction text not null default 'internal';
|
||
alter table public.integration_events add column if not exists source text not null default 'supabase-function';
|
||
alter table public.integration_events add column if not exists status text not null default 'success';
|
||
alter table public.integration_events add column if not exists payload jsonb not null default '{}'::jsonb;
|
||
alter table public.integration_events add column if not exists error_message text;
|
||
alter table public.rate_limits add column if not exists scope text not null;
|
||
alter table public.rate_limits add column if not exists rate_key text not null;
|
||
alter table public.rate_limits add column if not exists window_start timestamptz not null;
|
||
alter table public.rate_limits add column if not exists count integer not null default 1;
|
||
alter table public.rate_limits add column if not exists blocked_until timestamptz;
|
||
alter table public.rate_limits add column if not exists created_at timestamptz not null default timezone('utc', now());
|
||
alter table public.rate_limits add column if not exists updated_at timestamptz not null default timezone('utc', now());
|
||
|
||
create index if not exists idx_orders_delivery_set_key on public.orders (delivery_set_key);
|
||
create index if not exists idx_orders_delivery_set_status on public.orders (delivery_set_status);
|
||
create index if not exists idx_orders_source_accept_at on public.orders (source_accept_at);
|
||
create index if not exists idx_orders_source_ship_at on public.orders (source_ship_at);
|
||
|
||
insert into public.roles (name, permissions)
|
||
values
|
||
(
|
||
'manager',
|
||
'["orders.create","orders.update.own","orders.read.own","comments.manage"]'::jsonb
|
||
),
|
||
(
|
||
'production_lead',
|
||
'["orders.read.all","production.queue.manage","orders.status.production"]'::jsonb
|
||
),
|
||
(
|
||
'logistician',
|
||
'["orders.read.assigned","delivery.manage","chatbots.manage"]'::jsonb
|
||
),
|
||
(
|
||
'driver',
|
||
'["orders.read.assigned_driver","orders.status.driver"]'::jsonb
|
||
),
|
||
(
|
||
'admin',
|
||
'["*"]'::jsonb
|
||
)
|
||
on conflict (name) do nothing;
|
||
|
||
create or replace function public.set_updated_at()
|
||
returns trigger
|
||
language plpgsql
|
||
as $$
|
||
begin
|
||
new.updated_at = timezone('utc', now());
|
||
return new;
|
||
end;
|
||
$$;
|
||
|
||
drop trigger if exists orders_set_updated_at on public.orders;
|
||
create trigger orders_set_updated_at
|
||
before update on public.orders
|
||
for each row
|
||
execute function public.set_updated_at();
|
||
|
||
drop trigger if exists delivery_invitations_set_updated_at on public.delivery_invitations;
|
||
create trigger delivery_invitations_set_updated_at
|
||
before update on public.delivery_invitations
|
||
for each row
|
||
execute function public.set_updated_at();
|
||
|
||
create or replace function public.current_role_name()
|
||
returns text
|
||
language sql
|
||
stable
|
||
security definer
|
||
set search_path = public
|
||
as $$
|
||
select r.name
|
||
from public.users u
|
||
join public.roles r on r.id = u.role_id
|
||
where u.id = auth.uid()
|
||
$$;
|
||
|
||
create or replace function public.handle_new_user()
|
||
returns trigger
|
||
language plpgsql
|
||
security definer
|
||
set search_path = public
|
||
as $$
|
||
declare
|
||
default_role_id uuid;
|
||
begin
|
||
select id
|
||
into default_role_id
|
||
from public.roles
|
||
where name = coalesce(new.raw_user_meta_data ->> 'role', 'manager')
|
||
limit 1;
|
||
|
||
if default_role_id is null then
|
||
select id into default_role_id from public.roles where name = 'manager' limit 1;
|
||
end if;
|
||
|
||
insert into public.users (id, email, name, role_id, last_login)
|
||
values (
|
||
new.id,
|
||
new.email,
|
||
coalesce(new.raw_user_meta_data ->> 'name', split_part(new.email, '@', 1)),
|
||
default_role_id,
|
||
timezone('utc', now())
|
||
)
|
||
on conflict (id) do update
|
||
set email = excluded.email,
|
||
last_login = timezone('utc', now());
|
||
|
||
return new;
|
||
end;
|
||
$$;
|
||
|
||
drop trigger if exists on_auth_user_created on auth.users;
|
||
create trigger on_auth_user_created
|
||
after insert on auth.users
|
||
for each row
|
||
execute function public.handle_new_user();
|
||
|
||
create or replace function public.log_order_status_change()
|
||
returns trigger
|
||
language plpgsql
|
||
security definer
|
||
as $$
|
||
begin
|
||
if tg_op = 'INSERT' then
|
||
insert into public.order_history (order_id, action, old_status, new_status, user_id)
|
||
values (new.id, 'Создан заказ', null, new.status, auth.uid());
|
||
return new;
|
||
end if;
|
||
|
||
if old.status is distinct from new.status then
|
||
insert into public.order_history (order_id, action, old_status, new_status, user_id)
|
||
values (new.id, 'Изменение статуса', old.status, new.status, auth.uid());
|
||
end if;
|
||
|
||
if old.delivery_agreement_status is distinct from new.delivery_agreement_status then
|
||
insert into public.order_history (order_id, action, old_status, new_status, user_id, metadata)
|
||
values (
|
||
new.id,
|
||
'Изменение согласования доставки',
|
||
old.delivery_agreement_status,
|
||
new.delivery_agreement_status,
|
||
auth.uid(),
|
||
jsonb_build_object('scope', 'delivery_agreement')
|
||
);
|
||
end if;
|
||
|
||
return new;
|
||
end;
|
||
$$;
|
||
|
||
drop trigger if exists orders_history_insert on public.orders;
|
||
create trigger orders_history_insert
|
||
after insert or update on public.orders
|
||
for each row
|
||
execute function public.log_order_status_change();
|
||
|
||
create index if not exists idx_users_role_id on public.users (role_id);
|
||
create index if not exists idx_orders_status on public.orders (status);
|
||
create index if not exists idx_orders_manager_id on public.orders (manager_id);
|
||
create index if not exists idx_orders_logistician_id on public.orders (logistician_id);
|
||
create index if not exists idx_orders_assigned_driver_id on public.orders (assigned_driver_id);
|
||
create index if not exists idx_orders_ready_for_delivery_at on public.orders (ready_for_delivery_at);
|
||
create index if not exists idx_orders_delivery_flow_started_at on public.orders (delivery_flow_started_at);
|
||
create index if not exists idx_orders_created_at on public.orders (created_at desc);
|
||
create index if not exists idx_order_logisticians_order_id on public.order_logisticians (order_id);
|
||
create index if not exists idx_order_logisticians_logistician_id on public.order_logisticians (logistician_id);
|
||
create index if not exists idx_order_history_order_id on public.order_history (order_id, created_at desc);
|
||
create index if not exists idx_delivery_slots_order_id on public.delivery_slots (order_id);
|
||
create index if not exists idx_delivery_slots_logistician_id on public.delivery_slots (logistician_id);
|
||
create index if not exists idx_delivery_slots_selected_by_client_at on public.delivery_slots (selected_by_client_at);
|
||
create index if not exists idx_chat_messages_order_id on public.chat_messages (order_id, created_at desc);
|
||
create index if not exists idx_chat_messages_external_message_id on public.chat_messages (external_message_id);
|
||
create unique index if not exists idx_chat_messages_channel_external_unique
|
||
on public.chat_messages (channel, external_message_id)
|
||
where external_message_id is not null;
|
||
create index if not exists idx_orders_search on public.orders using gin (
|
||
to_tsvector(
|
||
'simple',
|
||
coalesce(order_number, '') || ' ' || coalesce(customer ->> 'name', '') || ' ' || coalesce(customer ->> 'phone', '')
|
||
)
|
||
);
|
||
create index if not exists idx_chat_messages_search on public.chat_messages using gin (
|
||
to_tsvector('russian', coalesce(text, ''))
|
||
);
|
||
create index if not exists idx_delivery_invitations_order_id on public.delivery_invitations (order_id);
|
||
create index if not exists idx_delivery_invitations_token_hash on public.delivery_invitations (token_hash);
|
||
create index if not exists idx_delivery_invitations_state on public.delivery_invitations (state);
|
||
create index if not exists idx_delivery_invitations_expires_at on public.delivery_invitations (expires_at);
|
||
create index if not exists idx_integration_events_order_id on public.integration_events (order_id, created_at desc);
|
||
create index if not exists idx_integration_events_event_type on public.integration_events (event_type);
|
||
create index if not exists idx_rate_limits_scope_key_window on public.rate_limits (scope, rate_key, window_start desc);
|
||
create index if not exists idx_rate_limits_blocked_until on public.rate_limits (blocked_until);
|
||
|
||
create or replace function public.check_rate_limit(
|
||
p_scope text,
|
||
p_key text,
|
||
p_max_count integer,
|
||
p_window_seconds integer,
|
||
p_block_seconds integer default 0
|
||
)
|
||
returns table (
|
||
allowed boolean,
|
||
current_count integer,
|
||
limit_count integer,
|
||
blocked_until timestamptz,
|
||
window_start timestamptz
|
||
)
|
||
language plpgsql
|
||
security definer
|
||
set search_path = public
|
||
as $$
|
||
declare
|
||
v_now timestamptz := timezone('utc', now());
|
||
v_window_start timestamptz;
|
||
v_count integer;
|
||
v_blocked_until timestamptz;
|
||
begin
|
||
if p_max_count <= 0 then
|
||
raise exception 'max_count must be positive';
|
||
end if;
|
||
|
||
if p_window_seconds <= 0 then
|
||
raise exception 'window_seconds must be positive';
|
||
end if;
|
||
|
||
v_window_start := to_timestamp(floor(extract(epoch from v_now) / p_window_seconds) * p_window_seconds);
|
||
|
||
select rl.blocked_until
|
||
into v_blocked_until
|
||
from public.rate_limits rl
|
||
where rl.scope = p_scope
|
||
and rl.rate_key = p_key
|
||
and rl.blocked_until is not null
|
||
and rl.blocked_until > v_now
|
||
order by rl.blocked_until desc
|
||
limit 1;
|
||
|
||
if v_blocked_until is not null then
|
||
return query
|
||
select false, 0, p_max_count, v_blocked_until, v_window_start;
|
||
return;
|
||
end if;
|
||
|
||
insert into public.rate_limits (scope, rate_key, window_start, count, blocked_until)
|
||
values (p_scope, p_key, v_window_start, 1, null)
|
||
on conflict (scope, rate_key, window_start)
|
||
do update set
|
||
count = public.rate_limits.count + 1,
|
||
blocked_until = case
|
||
when public.rate_limits.count + 1 > p_max_count and p_block_seconds > 0 then greatest(
|
||
coalesce(public.rate_limits.blocked_until, v_now),
|
||
v_now + make_interval(secs => p_block_seconds)
|
||
)
|
||
else public.rate_limits.blocked_until
|
||
end,
|
||
updated_at = v_now
|
||
returning count, blocked_until
|
||
into v_count, v_blocked_until;
|
||
|
||
return query
|
||
select
|
||
v_count <= p_max_count and (v_blocked_until is null or v_blocked_until <= v_now),
|
||
v_count,
|
||
p_max_count,
|
||
v_blocked_until,
|
||
v_window_start;
|
||
end;
|
||
$$;
|
||
|
||
alter table public.roles enable row level security;
|
||
alter table public.users enable row level security;
|
||
alter table public.orders enable row level security;
|
||
alter table public.order_logisticians enable row level security;
|
||
alter table public.order_history enable row level security;
|
||
alter table public.delivery_slots enable row level security;
|
||
alter table public.chat_messages enable row level security;
|
||
alter table public.error_logs enable row level security;
|
||
alter table public.delivery_invitations enable row level security;
|
||
alter table public.integration_events enable row level security;
|
||
|
||
drop policy if exists "roles select authenticated" on public.roles;
|
||
create policy "roles select authenticated" on public.roles
|
||
for select
|
||
using (public.current_role_name() is not null);
|
||
|
||
drop policy if exists "roles admin mutate" on public.roles;
|
||
create policy "roles admin mutate" on public.roles
|
||
for insert
|
||
with check (public.current_role_name() = 'admin');
|
||
|
||
drop policy if exists "roles admin update" on public.roles;
|
||
create policy "roles admin update" on public.roles
|
||
for update
|
||
using (public.current_role_name() = 'admin')
|
||
with check (public.current_role_name() = 'admin');
|
||
|
||
drop policy if exists "roles admin delete" on public.roles;
|
||
create policy "roles admin delete" on public.roles
|
||
for delete
|
||
using (public.current_role_name() = 'admin');
|
||
|
||
drop policy if exists "users self or admin" on public.users;
|
||
create policy "users self or admin" on public.users
|
||
for select
|
||
using (public.current_role_name() = 'admin' or id = auth.uid());
|
||
|
||
drop policy if exists "users admin update" on public.users;
|
||
create policy "users admin update" on public.users
|
||
for all
|
||
using (public.current_role_name() = 'admin')
|
||
with check (public.current_role_name() = 'admin');
|
||
|
||
drop policy if exists "orders select by role" on public.orders;
|
||
create policy "orders select by role" on public.orders
|
||
for select
|
||
using (
|
||
public.current_role_name() = 'admin'
|
||
or public.current_role_name() = 'production_lead'
|
||
or (public.current_role_name() = 'manager' and manager_id = auth.uid())
|
||
or (public.current_role_name() = 'driver' and assigned_driver_id = auth.uid())
|
||
or (
|
||
public.current_role_name() = 'logistician'
|
||
and (
|
||
logistician_id = auth.uid()
|
||
or exists (
|
||
select 1
|
||
from public.order_logisticians ol
|
||
where ol.order_id = orders.id and ol.logistician_id = auth.uid()
|
||
)
|
||
)
|
||
)
|
||
);
|
||
|
||
drop policy if exists "orders insert managers admin" on public.orders;
|
||
create policy "orders insert managers admin" on public.orders
|
||
for insert
|
||
with check (public.current_role_name() in ('manager', 'admin'));
|
||
|
||
drop policy if exists "orders update by workflow role" on public.orders;
|
||
create policy "orders update by workflow role" on public.orders
|
||
for update
|
||
using (
|
||
public.current_role_name() = 'admin'
|
||
or (public.current_role_name() = 'manager' and manager_id = auth.uid())
|
||
or (public.current_role_name() = 'driver' and assigned_driver_id = auth.uid())
|
||
or public.current_role_name() = 'production_lead'
|
||
or (
|
||
public.current_role_name() = 'logistician'
|
||
and (
|
||
logistician_id = auth.uid()
|
||
or exists (
|
||
select 1
|
||
from public.order_logisticians ol
|
||
where ol.order_id = orders.id and ol.logistician_id = auth.uid()
|
||
)
|
||
)
|
||
)
|
||
)
|
||
with check (
|
||
public.current_role_name() = 'admin'
|
||
or (public.current_role_name() = 'manager' and manager_id = auth.uid())
|
||
or (public.current_role_name() = 'driver' and assigned_driver_id = auth.uid())
|
||
or public.current_role_name() = 'production_lead'
|
||
or (
|
||
public.current_role_name() = 'logistician'
|
||
and (
|
||
logistician_id = auth.uid()
|
||
or exists (
|
||
select 1
|
||
from public.order_logisticians ol
|
||
where ol.order_id = orders.id and ol.logistician_id = auth.uid()
|
||
)
|
||
)
|
||
)
|
||
);
|
||
|
||
drop policy if exists "history select by order role" on public.order_history;
|
||
create policy "history select by order role" on public.order_history
|
||
for select
|
||
using (
|
||
exists (
|
||
select 1
|
||
from public.orders o
|
||
where o.id = order_history.order_id
|
||
and (
|
||
public.current_role_name() = 'admin'
|
||
or public.current_role_name() = 'production_lead'
|
||
or (public.current_role_name() = 'manager' and o.manager_id = auth.uid())
|
||
or (public.current_role_name() = 'driver' and o.assigned_driver_id = auth.uid())
|
||
or (
|
||
public.current_role_name() = 'logistician'
|
||
and (
|
||
o.logistician_id = auth.uid()
|
||
or exists (
|
||
select 1
|
||
from public.order_logisticians ol
|
||
where ol.order_id = o.id and ol.logistician_id = auth.uid()
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
);
|
||
|
||
drop policy if exists "history insert workflow" on public.order_history;
|
||
create policy "history insert workflow" on public.order_history
|
||
for insert
|
||
with check (public.current_role_name() in ('manager', 'production_lead', 'logistician', 'driver', 'admin'));
|
||
|
||
drop policy if exists "slots by order role" on public.delivery_slots;
|
||
create policy "slots by order role" on public.delivery_slots
|
||
for all
|
||
using (
|
||
exists (
|
||
select 1
|
||
from public.orders o
|
||
where o.id = delivery_slots.order_id
|
||
and (
|
||
public.current_role_name() = 'admin'
|
||
or public.current_role_name() = 'production_lead'
|
||
or (public.current_role_name() = 'manager' and o.manager_id = auth.uid())
|
||
or (public.current_role_name() = 'driver' and o.assigned_driver_id = auth.uid())
|
||
or (
|
||
public.current_role_name() = 'logistician'
|
||
and (
|
||
o.logistician_id = auth.uid()
|
||
or exists (
|
||
select 1
|
||
from public.order_logisticians ol
|
||
where ol.order_id = o.id and ol.logistician_id = auth.uid()
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
with check (public.current_role_name() in ('logistician', 'admin'));
|
||
|
||
drop policy if exists "chat by order role" on public.chat_messages;
|
||
create policy "chat by order role" on public.chat_messages
|
||
for select
|
||
using (
|
||
exists (
|
||
select 1
|
||
from public.orders o
|
||
where o.id = chat_messages.order_id
|
||
and (
|
||
public.current_role_name() = 'admin'
|
||
or public.current_role_name() = 'production_lead'
|
||
or (public.current_role_name() = 'manager' and o.manager_id = auth.uid())
|
||
or (public.current_role_name() = 'driver' and o.assigned_driver_id = auth.uid())
|
||
or (
|
||
public.current_role_name() = 'logistician'
|
||
and (
|
||
o.logistician_id = auth.uid()
|
||
or exists (
|
||
select 1
|
||
from public.order_logisticians ol
|
||
where ol.order_id = o.id and ol.logistician_id = auth.uid()
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
);
|
||
|
||
drop policy if exists "chat insert workflow" on public.chat_messages;
|
||
create policy "chat insert workflow" on public.chat_messages
|
||
for insert
|
||
with check (public.current_role_name() in ('manager', 'logistician', 'admin'));
|
||
|
||
drop policy if exists "order logisticians by role" on public.order_logisticians;
|
||
create policy "order logisticians by role" on public.order_logisticians
|
||
for all
|
||
using (public.current_role_name() in ('logistician', 'admin'))
|
||
with check (public.current_role_name() in ('logistician', 'admin'));
|
||
|
||
drop policy if exists "error logs admin only" on public.error_logs;
|
||
create policy "error logs admin only" on public.error_logs
|
||
for all
|
||
using (public.current_role_name() = 'admin')
|
||
with check (public.current_role_name() = 'admin');
|
||
|
||
drop policy if exists "delivery invitations admin only" on public.delivery_invitations;
|
||
create policy "delivery invitations admin only" on public.delivery_invitations
|
||
for all
|
||
using (public.current_role_name() = 'admin')
|
||
with check (public.current_role_name() = 'admin');
|
||
|
||
alter table public.rate_limits enable row level security;
|
||
drop policy if exists "rate limits admin only" on public.rate_limits;
|
||
create policy "rate limits admin only" on public.rate_limits
|
||
for all
|
||
using (public.current_role_name() = 'admin')
|
||
with check (public.current_role_name() = 'admin');
|
||
|
||
drop policy if exists "integration events admin only" on public.integration_events;
|
||
create policy "integration events admin only" on public.integration_events
|
||
for all
|
||
using (public.current_role_name() = 'admin')
|
||
with check (public.current_role_name() = 'admin');
|