supersam/docs/sql/public-delivery-choice-rpc.sql

407 lines
12 KiB
PL/PgSQL

-- Migration: add source_orders items to get_delivery_invitation_by_token
-- This replaces ONLY the orderItems building section for the group path.
-- Apply AFTER the base function is restored.
-- Step 1: First restore the original function (run restore-rpc-original.sql if needed)
-- Step 2: Then run this migration
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, ''),
to_jsonb(v_group.order_numbers) ->> 0,
NULLIF(v_group.group_key, '')
);
v_customer_name := COALESCE(
NULLIF(v_group.customer_name, ''),
NULLIF(v_invitation.customer_name, '')
);
v_customer_phone := COALESCE(
NULLIF(v_group.customer_phone, ''),
NULLIF(v_group.customer_phone_normalized, ''),
NULLIF(v_invitation.customer_phone, '')
);
-- Build orderItems: use source_orders for real product lines if available,
-- otherwise fall back to invoice numbers from order_numbers.
v_order_items := CASE
WHEN v_group.source_orders IS NOT NULL
AND jsonb_typeof(v_group.source_orders) = 'array'
AND jsonb_array_length(v_group.source_orders) > 0
THEN COALESCE(
(SELECT jsonb_agg(
jsonb_build_object(
'name', COALESCE(src ->> 'nom', src ->> 'name', ''),
'quantity', '',
'items', COALESCE(src -> 'orderList', src -> 'items', '[]'::jsonb)
)
) FROM jsonb_array_elements(v_group.source_orders) AS src),
'[]'::jsonb
)
ELSE COALESCE(
(SELECT jsonb_agg(jsonb_build_object('name', onum, 'quantity', ''))
FROM unnest(v_group.order_numbers) AS onum
WHERE onum IS NOT NULL AND onum <> ''),
'[]'::jsonb
)
END;
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;
$$;
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;
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.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;