From 838c4cb7aedfcfa12a418b0ba270ff16cc52dfed Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 20 May 2026 11:53:17 +0300 Subject: [PATCH] feat(paid-storage): bypass stale RPC for paid_storage transitions - OrderDetailPanel: paid_storage bypasses update_delivery_status RPC - Repository: direct table update for paid_storage entry/exit - LogisticsBoard: collapsible sections ordered by funnel - SQL: updated paid_storage schema patch - LoginPage: hide dev quick-login in production --- .../logistics/LogisticsReadinessBoard.jsx | 30 ++- src/components/orders/OrderDetailPanel.jsx | 189 +++++++++--------- src/hooks/useOrderGroups.js | 1 + src/pages/LoginPage.jsx | 2 +- src/services/supabase/orderGroupRepository.js | 56 +++++- supabase/paid-storage-status.sql | 20 +- 6 files changed, 191 insertions(+), 107 deletions(-) diff --git a/src/components/logistics/LogisticsReadinessBoard.jsx b/src/components/logistics/LogisticsReadinessBoard.jsx index b86221e..699b539 100644 --- a/src/components/logistics/LogisticsReadinessBoard.jsx +++ b/src/components/logistics/LogisticsReadinessBoard.jsx @@ -52,6 +52,22 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO return map; }, [filteredGroups]); + const FUNNEL_ORDER = [ + "status:ready_for_notification", + "delivery:pending_confirmation", + "delivery:manual_confirmation_required", + "status:first_sms_sent", + "status:second_sms_sent", + "delivery:agreed", + "delivery:driver_assigned", + "delivery:loaded", + "delivery:on_route", + "delivery:delivered", + "delivery:paid_storage", + "delivery:problem", + "delivery:cancelled", + ]; + const totalGroups = filteredGroups.length; return ( @@ -76,9 +92,15 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO По этому поиску ничего не найдено. ) : ( -
- {Array.from(statusGroups.entries()).map(([statusValue, { label, groups }]) => { - if (!groups.length) return null; +
+ {Array.from(statusGroups.entries()).sort(([a], [b]) => { + const idxA = FUNNEL_ORDER.indexOf(a); + const idxB = FUNNEL_ORDER.indexOf(b); + if (idxA === -1 && idxB === -1) return a.localeCompare(b); + if (idxA === -1) return 1; + if (idxB === -1) return -1; + return idxA - idxB; + }).map(([statusValue, { label, groups }]) => { const isCollapsed = collapsedSections.has(statusValue); return ( @@ -100,7 +122,7 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO >

{label}

- {groups.length} + 0 ? "neutral" : "muted"}>{groups.length}
{ ); }; +const PaidStoragePanel = ({ order, onChangeDeliveryStatus, isSavingDeliveryChoice, setFormMessage }) => { + const [showConfirm, setShowConfirm] = React.useState(false); + const isPaidStorage = (order.deliveryStatus || order.delivery_status) === "paid_storage"; + + if (isPaidStorage) { + return ( + +
+ + Платное хранение +
+ {order.paidStorageAt && ( +

+ Переведено: {formatDateTime(order.paidStorageAt)} +

+ )} + + + ); + } + + return ( + +
+ Платное хранение +

+ Переведите заказ в статус платного хранения, если клиент не забрал товар в срок. +

+
+ + {showConfirm ? ( +
+

Перевести заказ в платное хранение? Клиент получит уведомление.

+
+ + +
+
+ ) : ( + + )} +
+ ); +}; + export const OrderDetailPanel = ({ order, canManageDelivery = false, @@ -580,97 +672,12 @@ export const OrderDetailPanel = ({ {["manager", "logistician", "admin"].includes(userRole) && order && onChangeDeliveryStatus ? ( - (() => { - const [showConfirm, setShowConfirm] = React.useState(false); - const isPaidStorage = (order.deliveryStatus || order.delivery_status) === "paid_storage"; - - if (isPaidStorage) { - return ( - -
- - Платное хранение -
- {order.paidStorageAt && ( -

- Переведено: {formatDateTime(order.paidStorageAt)} -

- )} - -
- ); - } - - return ( - -
- Платное хранение -

- Переведите заказ в статус платного хранения, если клиент не забрал товар в срок. -

-
- - {showConfirm ? ( -
-

Перевести заказ в платное хранение? Клиент получит уведомление.

-
- - -
-
- ) : ( - - )} -
- ); - })() + ) : null} {canManageDelivery ? ( diff --git a/src/hooks/useOrderGroups.js b/src/hooks/useOrderGroups.js index bc2fa9e..1c65850 100644 --- a/src/hooks/useOrderGroups.js +++ b/src/hooks/useOrderGroups.js @@ -215,6 +215,7 @@ export const useOrderGroups = () => { deliveryGroupBuckets, saveManualDeliveryChoice, assignDriver, + changeDeliveryStatus, isSavingDeliveryChoice, isLoading, loadError, diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index d5215c6..1ba3035 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -77,7 +77,7 @@ export const LoginPage = () => { error={displayError} /> - {(isDemoMode || import.meta.env.DEV === true && import.meta.env.VITE_ENABLE_DEMO === 'true') ? ( + {isDemoMode ? (

{isDemoMode ? "Демо-режим — войдите под любой ролью" : "Быстрый вход (только для разработки)"} diff --git a/src/services/supabase/orderGroupRepository.js b/src/services/supabase/orderGroupRepository.js index 847a2a3..8c83418 100644 --- a/src/services/supabase/orderGroupRepository.js +++ b/src/services/supabase/orderGroupRepository.js @@ -254,13 +254,53 @@ export const assignDriverToOrderGroup = async ({ export const updateDeliveryStatus = async ({ orderGroupId, status }) => { return safeSupabaseCall(async () => { const client = requireSupabase(); - const { data: rpcData, error: rpcError } = await client.rpc("update_delivery_status", { - p_order_group_id: orderGroupId, - p_status: status, - }); - if (rpcError) { - throw rpcError; + // Bypass stale RPC for paid_storage transitions + // Server-side RPC still enforces driver-assignment checks that block + // manager/logistician from moving groups into/out of paid_storage. + // RLS policy "order groups update coordination roles" allows + // manager/logistician/admin to update order_groups directly. + if (status === "paid_storage") { + const { error: updateError } = await client + .from("order_groups") + .update({ + delivery_status: status, + paid_storage_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }) + .eq("id", orderGroupId); + + if (updateError) throw updateError; + } else { + // For cancelling paid_storage: check current status first + const { data: current, error: fetchError } = await client + .from("order_groups") + .select("delivery_status") + .eq("id", orderGroupId) + .single(); + + if (fetchError) throw fetchError; + + if (current.delivery_status === "paid_storage" && status === "pending_confirmation") { + const { error: updateError } = await client + .from("order_groups") + .update({ + delivery_status: status, + paid_storage_at: null, + updated_at: new Date().toISOString(), + }) + .eq("id", orderGroupId); + + if (updateError) throw updateError; + } else { + // All other statuses use the RPC (driver workflows, etc.) + const { error: rpcError } = await client.rpc("update_delivery_status", { + p_order_group_id: orderGroupId, + p_status: status, + }); + + if (rpcError) throw rpcError; + } } // Fetch updated group @@ -270,9 +310,7 @@ export const updateDeliveryStatus = async ({ orderGroupId, status }) => { .eq("id", orderGroupId) .single(); - if (error) { - throw error; - } + if (error) throw error; return mapOrderGroupRowToDeliveryGroup(data); }, "Ошибка обновления статуса доставки"); diff --git a/supabase/paid-storage-status.sql b/supabase/paid-storage-status.sql index 302dfd6..b03b2fa 100644 --- a/supabase/paid-storage-status.sql +++ b/supabase/paid-storage-status.sql @@ -3,7 +3,23 @@ -- 1. Add paid_storage_at column to order_groups if not exists alter table public.order_groups add column if not exists paid_storage_at timestamptz; --- 2. Update update_delivery_status to allow paid_storage without assigned driver +-- 2. Drop and recreate delivery_status check constraint to include paid_storage +alter table public.order_groups drop constraint if exists order_groups_delivery_status_check; +alter table public.order_groups add constraint order_groups_delivery_status_check + check (delivery_status in ( + 'pending_confirmation', + 'manual_confirmation_required', + 'agreed', + 'driver_assigned', + 'loaded', + 'on_route', + 'delivered', + 'paid_storage', + 'problem', + 'cancelled' + )); + +-- 3. Update update_delivery_status to allow paid_storage without assigned driver create or replace function public.update_delivery_status( p_order_group_id uuid, p_status text @@ -50,7 +66,7 @@ begin end; $$; --- 3. Ensure proper grants +-- 4. Ensure proper grants revoke execute on function public.update_delivery_status(uuid, text) from anon; grant execute on function public.update_delivery_status(uuid, text) to authenticated;