242 lines
8.4 KiB
PL/PgSQL
242 lines
8.4 KiB
PL/PgSQL
-- Migration: flatten ALL products from source_orders into simple orderItems list
|
|
-- This replaces the previous nested orderItems building section.
|
|
|
|
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_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: flatten ALL products from source_orders into a flat list.
|
|
-- Strategy 1: products inside orderList[].items[]
|
|
-- Strategy 2: products inside items[] directly on source_orders entry
|
|
-- Strategy 3: fallback to invoice names 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(
|
|
-- Strategy 1: flatten products from orderList[].items[]
|
|
(SELECT jsonb_agg(
|
|
jsonb_build_object(
|
|
'name', p ->> 'product_name',
|
|
'quantity', COALESCE(NULLIF(p ->> 'product_quantity', ''), ''),
|
|
'unit', COALESCE(NULLIF(p ->> 'product_ed', ''), '')
|
|
)
|
|
)
|
|
FROM jsonb_array_elements(v_group.source_orders) AS src,
|
|
LATERAL jsonb_array_elements(
|
|
CASE
|
|
WHEN jsonb_typeof(src -> 'orderList') = 'array' THEN src -> 'orderList'
|
|
ELSE '[]'::jsonb
|
|
END
|
|
) AS so,
|
|
LATERAL jsonb_array_elements(
|
|
CASE
|
|
WHEN jsonb_typeof(so -> 'items') = 'array' THEN so -> 'items'
|
|
ELSE '[]'::jsonb
|
|
END
|
|
) AS p
|
|
WHERE p ->> 'product_name' IS NOT NULL
|
|
AND p ->> 'product_name' != ''),
|
|
|
|
-- Strategy 2: products inside items[] directly on source_orders entry
|
|
(SELECT jsonb_agg(
|
|
jsonb_build_object(
|
|
'name', p ->> 'product_name',
|
|
'quantity', COALESCE(NULLIF(p ->> 'product_quantity', ''), ''),
|
|
'unit', COALESCE(NULLIF(p ->> 'product_ed', ''), '')
|
|
)
|
|
)
|
|
FROM jsonb_array_elements(v_group.source_orders) AS src,
|
|
LATERAL jsonb_array_elements(
|
|
CASE
|
|
WHEN jsonb_typeof(src -> 'items') = 'array' THEN src -> 'items'
|
|
ELSE '[]'::jsonb
|
|
END
|
|
) AS p
|
|
WHERE p ->> 'product_name' IS NOT NULL
|
|
AND p ->> 'product_name' != ''
|
|
AND NOT (p ? 'nom' AND p ? 'items')), -- exclude sub-order entries
|
|
|
|
-- Strategy 3: fallback to invoice names
|
|
(SELECT jsonb_agg(jsonb_build_object('name', on_num, 'quantity', ''))
|
|
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 on_num),
|
|
|
|
'[]'::jsonb
|
|
)
|
|
ELSE COALESCE(
|
|
(SELECT jsonb_agg(jsonb_build_object('name', order_number, 'quantity', ''))
|
|
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),
|
|
'[]'::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;
|