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;