390 lines
11 KiB
PL/PgSQL
390 lines
11 KiB
PL/PgSQL
create extension if not exists pgcrypto;
|
|
|
|
create or replace function public.get_delivery_invitation_by_token(p_token text)
|
|
returns jsonb
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public, extensions
|
|
as $$
|
|
declare
|
|
v_invitation public.delivery_invitations%rowtype;
|
|
v_group public.order_groups%rowtype;
|
|
v_order record;
|
|
v_token_hash text;
|
|
v_state text;
|
|
v_order_number text;
|
|
v_customer_name text;
|
|
v_customer_phone text;
|
|
v_order_items jsonb;
|
|
v_order_numbers jsonb;
|
|
v_now timestamptz := timezone('utc', now());
|
|
begin
|
|
if nullif(trim(coalesce(p_token, '')), '') is null then
|
|
raise exception 'token is required';
|
|
end if;
|
|
|
|
v_token_hash := encode(digest(p_token, 'sha256'), 'hex');
|
|
|
|
select *
|
|
into v_invitation
|
|
from public.delivery_invitations
|
|
where token_hash = v_token_hash;
|
|
|
|
if not found then
|
|
raise exception 'Invitation not found';
|
|
end if;
|
|
|
|
if v_invitation.revoked_at is not null then
|
|
raise exception 'Invitation expired';
|
|
end if;
|
|
|
|
if v_invitation.expires_at is not null and v_invitation.expires_at <= v_now then
|
|
raise exception 'Invitation expired';
|
|
end if;
|
|
|
|
if v_invitation.order_group_id is not null then
|
|
select *
|
|
into v_group
|
|
from public.order_groups
|
|
where id = v_invitation.order_group_id;
|
|
|
|
if not found then
|
|
raise exception 'Order group not found';
|
|
end if;
|
|
|
|
v_state := case
|
|
when v_group.delivery_status = 'agreed' then 'agreed'
|
|
when v_group.delivery_status = 'delivered' then 'delivered'
|
|
when v_invitation.state in ('awaiting_choice', 'opened', 'reminder_sent') then v_invitation.state
|
|
else 'default'
|
|
end;
|
|
|
|
update public.delivery_invitations
|
|
set
|
|
opened_at = case
|
|
when v_state in ('awaiting_choice', 'opened', 'reminder_sent') and opened_at is null then v_now
|
|
else opened_at
|
|
end,
|
|
access_count = coalesce(access_count, 0) + 1,
|
|
last_accessed_at = v_now
|
|
where id = v_invitation.id
|
|
returning * into v_invitation;
|
|
|
|
v_order_number := coalesce(
|
|
nullif(v_invitation.order_number, ''),
|
|
nullif(v_group.group_key, ''),
|
|
to_jsonb(v_group.order_numbers) ->> 0
|
|
);
|
|
v_customer_name := coalesce(
|
|
nullif(v_group.customer_name, ''),
|
|
nullif(v_group.customer ->> 'name', ''),
|
|
nullif(v_invitation.customer_name, '')
|
|
);
|
|
v_customer_phone := coalesce(
|
|
nullif(v_group.customer_phone, ''),
|
|
nullif(v_group.customer ->> 'phone', ''),
|
|
nullif(v_invitation.customer_phone, '')
|
|
);
|
|
select coalesce(
|
|
jsonb_agg(jsonb_build_object('name', order_number, 'quantity', '')),
|
|
'[]'::jsonb
|
|
)
|
|
into v_order_items
|
|
from jsonb_array_elements_text(
|
|
case
|
|
when jsonb_typeof(to_jsonb(v_group.order_numbers)) = 'array' then to_jsonb(v_group.order_numbers)
|
|
else '[]'::jsonb
|
|
end
|
|
) as order_number;
|
|
|
|
return jsonb_build_object(
|
|
'ok', true,
|
|
'invitation', jsonb_build_object(
|
|
'orderId', coalesce(v_invitation.order_group_id, v_group.id)::text,
|
|
'orderGroupId', coalesce(v_invitation.order_group_id, v_group.id)::text,
|
|
'state', v_state,
|
|
'token', p_token,
|
|
'orderNumber', v_order_number,
|
|
'customerName', v_customer_name,
|
|
'customerPhone', v_customer_phone,
|
|
'orderItems', v_order_items,
|
|
'availableSlots', coalesce(to_jsonb(v_invitation.available_slots), '[]'::jsonb),
|
|
'deliveryDate', v_invitation.delivery_date,
|
|
'deliveryTime', v_invitation.delivery_time,
|
|
'orderStatus', null,
|
|
'deliveryAgreementStatus', null
|
|
)
|
|
);
|
|
end if;
|
|
|
|
select id, order_number, status, delivery_agreement_status, customer
|
|
into v_order
|
|
from public.orders
|
|
where id = v_invitation.order_id;
|
|
|
|
if not found then
|
|
raise exception 'Order not found';
|
|
end if;
|
|
|
|
v_state := case v_order.status
|
|
when 'Ожидает ответа клиента' then 'awaiting_choice'
|
|
when 'Ожидает согласования доставки' then 'opened'
|
|
when 'Напоминание отправлено' then 'reminder_sent'
|
|
when 'Переход отправлен' then 'reminder_sent'
|
|
when 'Передан логисту' then 'transferred_to_logistics'
|
|
when 'Платное хранение' then 'paid_storage'
|
|
when 'Доставлен' then 'delivered'
|
|
when 'Доставка согласована' then 'agreed'
|
|
else 'default'
|
|
end;
|
|
|
|
update public.delivery_invitations
|
|
set
|
|
opened_at = case
|
|
when v_state in ('awaiting_choice', 'opened', 'reminder_sent') and opened_at is null then v_now
|
|
else opened_at
|
|
end,
|
|
access_count = coalesce(access_count, 0) + 1,
|
|
last_accessed_at = v_now
|
|
where id = v_invitation.id
|
|
returning * into v_invitation;
|
|
|
|
v_order_items := case
|
|
when jsonb_typeof(v_order.customer -> 'items') = 'array' then v_order.customer -> 'items'
|
|
else '[]'::jsonb
|
|
end;
|
|
|
|
return jsonb_build_object(
|
|
'ok', true,
|
|
'invitation', jsonb_build_object(
|
|
'orderId', v_invitation.order_id::text,
|
|
'state', v_state,
|
|
'token', p_token,
|
|
'orderNumber', coalesce(nullif(v_order.order_number, ''), nullif(v_invitation.order_number, '')),
|
|
'customerName', coalesce(nullif(v_order.customer ->> 'name', ''), nullif(v_invitation.customer_name, '')),
|
|
'customerPhone', coalesce(nullif(v_order.customer ->> 'phone', ''), nullif(v_invitation.customer_phone, '')),
|
|
'orderItems', v_order_items,
|
|
'availableSlots', coalesce(to_jsonb(v_invitation.available_slots), '[]'::jsonb),
|
|
'deliveryDate', v_invitation.delivery_date,
|
|
'deliveryTime', v_invitation.delivery_time,
|
|
'orderStatus', v_order.status,
|
|
'deliveryAgreementStatus', v_order.delivery_agreement_status
|
|
)
|
|
);
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.confirm_delivery_choice_by_token(
|
|
p_token text,
|
|
p_delivery_date date,
|
|
p_delivery_time text
|
|
)
|
|
returns jsonb
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public, extensions
|
|
as $$
|
|
declare
|
|
v_invitation public.delivery_invitations%rowtype;
|
|
v_group public.order_groups%rowtype;
|
|
v_order record;
|
|
v_token_hash text;
|
|
v_slot_label text;
|
|
v_now timestamptz := timezone('utc', now());
|
|
begin
|
|
if nullif(trim(coalesce(p_token, '')), '') is null then
|
|
raise exception 'token is required';
|
|
end if;
|
|
|
|
if p_delivery_date is null or nullif(trim(coalesce(p_delivery_time, '')), '') is null then
|
|
raise exception 'Selected slot is not available';
|
|
end if;
|
|
|
|
v_token_hash := encode(digest(p_token, 'sha256'), 'hex');
|
|
v_slot_label := concat(p_delivery_date::text, ', ', trim(p_delivery_time));
|
|
|
|
select *
|
|
into v_invitation
|
|
from public.delivery_invitations
|
|
where token_hash = v_token_hash
|
|
for update;
|
|
|
|
if not found then
|
|
raise exception 'Invitation not found';
|
|
end if;
|
|
|
|
if v_invitation.revoked_at is not null then
|
|
raise exception 'Invitation expired';
|
|
end if;
|
|
|
|
if v_invitation.expires_at is not null and v_invitation.expires_at <= v_now then
|
|
raise exception 'Invitation expired';
|
|
end if;
|
|
|
|
if v_invitation.state not in ('awaiting_choice', 'opened', 'reminder_sent') then
|
|
raise exception 'Invitation is no longer active';
|
|
end if;
|
|
|
|
if cardinality(v_invitation.available_slots) > 0 and not (v_slot_label = any(v_invitation.available_slots)) then
|
|
raise exception 'Selected slot is not available';
|
|
end if;
|
|
|
|
if v_invitation.order_group_id is not null then
|
|
select *
|
|
into v_group
|
|
from public.order_groups
|
|
where id = v_invitation.order_group_id
|
|
for update;
|
|
|
|
if not found then
|
|
raise exception 'Order group not found';
|
|
end if;
|
|
|
|
if v_group.delivery_status <> 'pending_confirmation' then
|
|
raise exception 'Invitation is no longer active';
|
|
end if;
|
|
|
|
update public.delivery_invitations
|
|
set
|
|
state = 'agreed',
|
|
delivery_date = p_delivery_date,
|
|
delivery_time = trim(p_delivery_time),
|
|
confirmed_at = v_now,
|
|
access_count = coalesce(access_count, 0) + 1,
|
|
last_accessed_at = v_now
|
|
where id = v_invitation.id;
|
|
|
|
update public.order_groups
|
|
set
|
|
delivery_status = 'agreed',
|
|
delivery_date = p_delivery_date,
|
|
delivery_time = trim(p_delivery_time),
|
|
notification_status = 'confirmed',
|
|
updated_at = v_now
|
|
where id = v_group.id;
|
|
|
|
insert into public.integration_events (
|
|
order_id,
|
|
event_type,
|
|
direction,
|
|
status,
|
|
payload
|
|
)
|
|
values (
|
|
null,
|
|
'delivery_choice_confirmed',
|
|
'inbound',
|
|
'success',
|
|
jsonb_build_object(
|
|
'order_group_id', v_group.id,
|
|
'delivery_invitation_id', v_invitation.id,
|
|
'delivery_date', p_delivery_date,
|
|
'delivery_time', trim(p_delivery_time)
|
|
)
|
|
);
|
|
|
|
return jsonb_build_object(
|
|
'ok', true,
|
|
'orderGroupId', v_group.id,
|
|
'deliveryStatus', 'agreed'
|
|
);
|
|
end if;
|
|
|
|
select id, status, delivery_agreement_status
|
|
into v_order
|
|
from public.orders
|
|
where id = v_invitation.order_id
|
|
for update;
|
|
|
|
if not found then
|
|
raise exception 'Order not found';
|
|
end if;
|
|
|
|
if v_order.status not in ('Ожидает ответа клиента', 'Ожидает согласования доставки') then
|
|
raise exception 'Invitation is no longer active';
|
|
end if;
|
|
|
|
update public.delivery_invitations
|
|
set
|
|
state = 'agreed',
|
|
delivery_date = p_delivery_date,
|
|
delivery_time = trim(p_delivery_time),
|
|
confirmed_at = v_now,
|
|
access_count = coalesce(access_count, 0) + 1,
|
|
last_accessed_at = v_now
|
|
where id = v_invitation.id;
|
|
|
|
update public.orders
|
|
set
|
|
status = 'Доставка согласована',
|
|
delivery_agreement_status = 'Подтверждено клиентом'
|
|
where id = v_order.id;
|
|
|
|
insert into public.delivery_slots (
|
|
order_id,
|
|
delivery_date,
|
|
delivery_time,
|
|
logistician_id,
|
|
status
|
|
)
|
|
values (
|
|
v_order.id,
|
|
p_delivery_date,
|
|
trim(p_delivery_time),
|
|
null,
|
|
'confirmed_by_client'
|
|
);
|
|
|
|
insert into public.order_history (
|
|
order_id,
|
|
action,
|
|
old_status,
|
|
new_status,
|
|
metadata
|
|
)
|
|
values (
|
|
v_order.id,
|
|
'Подтверждение выбора доставки клиентом',
|
|
v_order.status,
|
|
'Доставка согласована',
|
|
jsonb_build_object(
|
|
'old_delivery_agreement_status', v_order.delivery_agreement_status,
|
|
'new_delivery_agreement_status', 'Подтверждено клиентом',
|
|
'delivery_date', p_delivery_date,
|
|
'delivery_time', trim(p_delivery_time)
|
|
)
|
|
);
|
|
|
|
insert into public.integration_events (
|
|
order_id,
|
|
event_type,
|
|
direction,
|
|
status,
|
|
payload
|
|
)
|
|
values (
|
|
v_order.id,
|
|
'delivery_choice_confirmed',
|
|
'inbound',
|
|
'success',
|
|
jsonb_build_object(
|
|
'delivery_date', p_delivery_date,
|
|
'delivery_time', trim(p_delivery_time)
|
|
)
|
|
);
|
|
|
|
return jsonb_build_object(
|
|
'ok', true,
|
|
'orderId', v_order.id,
|
|
'status', 'Доставка согласована',
|
|
'deliveryAgreementStatus', 'Подтверждено клиентом'
|
|
);
|
|
end;
|
|
$$;
|
|
|
|
revoke all on function public.get_delivery_invitation_by_token(text) from public;
|
|
grant execute on function public.get_delivery_invitation_by_token(text) to anon, authenticated;
|
|
|
|
revoke all on function public.confirm_delivery_choice_by_token(text, date, text) from public;
|
|
grant execute on function public.confirm_delivery_choice_by_token(text, date, text) to anon, authenticated;
|