From e6dc1972fb9954f3841890674d145a40daa8d486 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 21 May 2026 11:52:37 +0300 Subject: [PATCH] feat: show full order composition on delivery page - Extract OrderCompositionPanel as standalone component - Support nested order items with sub-positions from source_orders - Enrich order items from Edge Function when RPC only has invoice refs - Update RPC SQL to extract product items from source_orders - Update Edge Function to include source_orders items in invitation - Add detail rows (account, customer) when no order items available --- docs/sql/public-delivery-choice-rpc.sql | 54 +++++++-- src/components/client/DeliveryChoiceFlow.jsx | 52 +------- .../client/DeliveryChoiceFlow.test.jsx | 83 +++++++++---- .../client/OrderCompositionPanel.jsx | 111 ++++++++++++++++++ src/pages/ClientDeliveryPage.jsx | 3 + src/services/deliveryInvitationApi.js | 57 ++++++++- .../functions/_shared/delivery-invitations.ts | 48 +++++++- 7 files changed, 317 insertions(+), 91 deletions(-) create mode 100644 src/components/client/OrderCompositionPanel.jsx diff --git a/docs/sql/public-delivery-choice-rpc.sql b/docs/sql/public-delivery-choice-rpc.sql index 5694c9b..1552f91 100644 --- a/docs/sql/public-delivery-choice-rpc.sql +++ b/docs/sql/public-delivery-choice-rpc.sql @@ -84,17 +84,49 @@ begin nullif(v_group.customer_phone_normalized, ''), 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; + -- Build orderItems from source_orders if available (real product lines), + -- 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', onum), + 'quantity', '', + 'items', coalesce( + src -> 'orderList', + src -> 'items', + '[]'::jsonb + ) + ) + ) + from jsonb_array_elements(v_group.source_orders) with ordinality as t(src, idx) + cross join lateral ( + select coalesce( + nullif(v_group.order_numbers[idx], ''), + src ->> 'nom', + src ->> 'name', + '' + ) as onum + ) as names + ), + '[]'::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, diff --git a/src/components/client/DeliveryChoiceFlow.jsx b/src/components/client/DeliveryChoiceFlow.jsx index 5e2874c..56cbaba 100644 --- a/src/components/client/DeliveryChoiceFlow.jsx +++ b/src/components/client/DeliveryChoiceFlow.jsx @@ -18,36 +18,6 @@ const STATE_LABELS = { agreed: "Доставка согласована", }; -const splitOrderItem = (item) => { - if (!item) { - return null; - } - - if (typeof item === "string") { - const [name, quantity] = item.split("|").map((part) => part.trim()); - return { - name: name || item.trim(), - quantity: quantity || "", - }; - } - - if (typeof item === "object") { - const name = typeof item.name === "string" ? item.name.trim() : typeof item.label === "string" ? item.label.trim() : ""; - const quantity = typeof item.quantity === "string" - ? item.quantity.trim() - : typeof item.quantity === "number" - ? String(item.quantity) - : ""; - - return { - name: name || "Позиция", - quantity, - }; - } - - return null; -}; - export const DeliveryChoiceFlow = ({ invitation = {}, selectedSlot = null, @@ -56,9 +26,6 @@ export const DeliveryChoiceFlow = ({ const state = invitation.state || "awaiting_choice"; const isActive = ACTIVE_STATES.has(state); const invitationReference = getInvitationReferenceLabel(invitation); - const orderItems = (invitation.orderItems || invitation.items || []) - .map(splitOrderItem) - .filter(Boolean); const slotSummary = selectedSlot ? formatDeliverySlotLabel(selectedSlot) : ""; if (!isActive) { @@ -78,27 +45,10 @@ export const DeliveryChoiceFlow = ({ {STATE_LABELS[state]}

- {invitationReference}. Проверьте состав заказа и выберите удобную половину дня. + {invitationReference}. Выберите удобную половину дня.

- {orderItems.length ? ( -
-

Состав заказа

-
- {orderItems.map((item) => ( -
- {item.name} - {item.quantity ? {item.quantity} : null} -
- ))} -
-
- ) : null} -