supersam/supabase/schema.sql

792 lines
32 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.

create extension if not exists pgcrypto;
create or replace function public.next_order_group_sms_check_at(
start_from timestamptz default now(),
delay interval default interval '0 minutes'
)
returns timestamptz
language plpgsql
stable
as $$
declare
v_timezone text := 'Europe/Simferopol';
v_local_time timestamp;
v_local_date date;
v_work_start timestamp;
v_work_end timestamp;
v_candidate timestamp;
begin
v_local_time := (start_from at time zone v_timezone) + delay;
v_local_date := v_local_time::date;
v_work_start := v_local_date + time '09:00';
v_work_end := v_local_date + time '20:00';
if v_local_time < v_work_start then
v_candidate := v_work_start;
elsif v_local_time >= v_work_end then
v_candidate := (v_local_date + 1) + time '09:00';
else
v_candidate := v_local_time;
end if;
return v_candidate at time zone v_timezone;
end;
$$;
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.order_groups (
id uuid primary key default gen_random_uuid(),
group_key text not null,
customer jsonb,
order_numbers text[] not null default '{}',
status text not null default 'ready_for_notification',
delivery_status text not null default 'pending_confirmation',
sms_sent_at timestamptz,
created_at timestamptz not null default timezone('utc', now()),
updated_at timestamptz not null default timezone('utc', now()),
created_from_exchange_at timestamptz,
source_key text,
customer_name text,
customer_phone text,
customer_phone_normalized text,
customer_date text,
orders_total integer,
orders_ready integer,
orders_not_ready integer,
source_orders jsonb,
delivery_invitation_id uuid,
delivery_link text,
notification_status text not null default 'not_started',
sms_attempts integer not null default 0,
first_sms_sent_at timestamptz,
second_sms_sent_at timestamptz,
last_sms_error text,
next_notification_check_at timestamptz,
delivery_date date,
delivery_time text
);
create table if not exists public.delivery_invitations (
id uuid primary key default gen_random_uuid(),
order_id uuid references public.orders (id) on delete cascade unique,
order_group_id uuid references public.order_groups (id) on delete cascade,
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 alter column order_id drop not null;
alter table public.delivery_invitations add column if not exists order_group_id uuid references public.order_groups(id) on delete cascade;
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.order_groups add column if not exists delivery_invitation_id uuid references public.delivery_invitations(id) on delete set null;
alter table public.order_groups add column if not exists delivery_link text;
alter table public.order_groups add column if not exists notification_status text not null default 'not_started';
alter table public.order_groups add column if not exists sms_attempts integer not null default 0;
alter table public.order_groups add column if not exists first_sms_sent_at timestamptz;
alter table public.order_groups add column if not exists second_sms_sent_at timestamptz;
alter table public.order_groups add column if not exists last_sms_error text;
alter table public.order_groups add column if not exists next_notification_check_at timestamptz;
alter table public.order_groups add column if not exists delivery_date date;
alter table public.order_groups add column if not exists delivery_time text;
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_order_group_id on public.delivery_invitations (order_group_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_order_groups_status on public.order_groups (status);
create index if not exists idx_order_groups_delivery_status on public.order_groups (delivery_status);
create index if not exists idx_order_groups_notification_status on public.order_groups (notification_status, next_notification_check_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.order_groups 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 "order groups select by role" on public.order_groups;
create policy "order groups select by role" on public.order_groups
for select
using (true);
drop policy if exists "order groups update coordination roles" on public.order_groups;
create policy "order groups update coordination roles" on public.order_groups
for update
using (public.current_role_name() in ('manager', 'logistician', 'admin'))
with check (public.current_role_name() in ('manager', 'logistician', 'admin'));
drop policy if exists "order groups insert service roles" on public.order_groups;
create policy "order groups insert service roles" on public.order_groups
for insert
with check (public.current_role_name() in ('manager', 'logistician', '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');