diff --git a/src/components/orders/OrderDetailPanel.jsx b/src/components/orders/OrderDetailPanel.jsx index 543e76e..bbb3cd0 100644 --- a/src/components/orders/OrderDetailPanel.jsx +++ b/src/components/orders/OrderDetailPanel.jsx @@ -840,8 +840,7 @@ export const OrderDetailPanel = ({ ) : null} - ) : ( -{deliveryType === "delivery" ? ( + ) : deliveryType === "delivery" ? (
+
) : (
- )} {formMessage ? (

{formMessage}

) : null} diff --git a/src/components/orders/OrderDetailPanel.jsx.bak b/src/components/orders/OrderDetailPanel.jsx.bak new file mode 100644 index 0000000..6f8bd5d --- /dev/null +++ b/src/components/orders/OrderDetailPanel.jsx.bak @@ -0,0 +1,1333 @@ + +const DriverShipmentReport = ({ shipmentData }) => { + if (!Array.isArray(shipmentData) || shipmentData.length === 0) return null; + + return ( + +
+ + + + Проблемы с доставкой позиций +
+

+ Не доставлено {shipmentData.length} {shipmentData.length === 1 ? "позиция" : shipmentData.length < 5 ? "позиции" : "позиций"}. Остальное — доставлено. +

+
+ {shipmentData.map((item) => ( +
+
+ {item.name} + {item.quantity || item.unit ? ( + {[item.quantity, item.unit].filter(Boolean).join(" ")} + ) : null} +
+ {item.comment ? ( +

Причина: {item.comment}

+ ) : ( +

Причина не указана

+ )} +
+ ))} +
+ +
+ ); +}; + +import React from "react"; +import { formatDateTime } from "../../utils/formatters"; +import { Badge } from "../UI/Badge"; +import { Button } from "../UI/Button"; +import { Select } from "../UI/Select"; +import { Panel } from "../UI/Panel"; +import { DriverShipmentPanel } from "../driver/DriverShipmentPanel"; +import { supabase } from "../../supabaseClient"; +import { + getOrderGroupDeliveryStatusLabel, + getOrderGroupDisplayStatusLabel, + getOrderGroupStatusTone, +} from "../../services/orderGroupViews"; + +const DELIVERY_TIME_OPTIONS = ["Первая половина дня", "Вторая половина дня"]; +const WEEK_DAY_LABELS = ["ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ", "ВС"]; +const DELIVERY_TIME_ALIASES = { + "До обеда": "Первая половина дня", + "После обеда": "Вторая половина дня", +}; + +const renderList = (values) => { + if (!Array.isArray(values) || !values.length) { + return

Нет данных

; + } + + return ( +
+ {values.map((value, index) => ( + + {value} + + ))} +
+ ); +}; + +const renderValue = (value) => value || "Нет данных"; + +const normalizeNom = (nom) => { + if (!nom) return ''; + // 1C escapes backslashes: "СФ Т\\ЕА-33584" → normalize for comparison + return String(nom).replace(/\\\\/g, '\\').trim(); +}; + +const getAllBillNumbers = (order) => { + const orders = parseOrderList(order); + if (!orders.length) return order.orderNumbers || []; + return orders.map((o) => o.nom || o.name || '').filter(Boolean); +}; + +const parseOrderList = (order) => { + if (!order) return []; + + // Try orderList first (Supabase JSONB array of positions) + if (order.orderList) { + let parsed = order.orderList; + if (typeof parsed === 'string') { + try { parsed = JSON.parse(parsed); } catch { /* ignore */ } + } + if (Array.isArray(parsed)) return parsed; + } + + // Fallback: orderListStructured (JSONB with { orders: [...] }) + if (order.orderListStructured) { + let parsed = order.orderListStructured; + if (typeof parsed === 'string') { + try { parsed = JSON.parse(parsed); } catch { /* ignore */ } + } + if (parsed && Array.isArray(parsed.orders)) return parsed.orders; + } + + // Fallback: sourceOrders (1C exchange data) + // 1C sends the FULL order composition (main + associated bills) in EVERY source order's orderList. + // We must deduplicate by nom to avoid showing the same items multiple times. + if (order.sourceOrders) { + let parsed = order.sourceOrders; + if (typeof parsed === 'string') { + try { parsed = JSON.parse(parsed); } catch { /* ignore */ } + } + if (Array.isArray(parsed) && parsed.length > 0) { + const seen = new Set(); + const allItems = []; + for (const src of parsed) { + if (src && Array.isArray(src.orderList)) { + for (const ol of src.orderList) { + if (ol && (ol.items || ol.nom || ol.name)) { + const normalizedNom = normalizeNom(ol.nom || ol.name || ''); + // Deduplicate by nom — 1C repeats same orderList in every source order + if (seen.has(normalizedNom)) continue; + seen.add(normalizedNom); + allItems.push(ol); + } + } + } + } + if (allItems.length > 0) return allItems; + // Legacy: return whole array if no orderList structure + if (parsed[0].orderList && Array.isArray(parsed[0].orderList)) { + return parsed[0].orderList; + } + return parsed; + } + } + + return []; +}; + +const getErrorMessage = (error, fallbackMessage) => { + if (!error) { + return fallbackMessage; + } + + if (error instanceof Error) { + return error.message || fallbackMessage; + } + + if (typeof error === "string") { + return error || fallbackMessage; + } + + return error?.message || fallbackMessage; +}; + +const normalizeDeliveryTimeChoice = (value) => { + const normalized = value ? String(value).trim() : ""; + const deliveryTime = DELIVERY_TIME_ALIASES[normalized] || normalized; + return DELIVERY_TIME_OPTIONS.includes(deliveryTime) ? deliveryTime : DELIVERY_TIME_OPTIONS[0]; +}; + +const toDateKey = (date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +}; + +const fromDateKey = (value) => { + const normalized = normalizeDateForInput(value); + + if (!normalized) { + return null; + } + + const [year, month, day] = normalized.split("-").map(Number); + return new Date(year, month - 1, day); +}; + +const addDays = (date, amount) => { + const nextDate = new Date(date); + nextDate.setDate(nextDate.getDate() + amount); + return nextDate; +}; + +const isWeekendDate = (date) => { + const day = date.getDay(); + return day === 0 || day === 6; +}; + +export const getNextSelectableDateKey = (referenceDate = new Date()) => { + let current = addDays(referenceDate, 1); + + while (isWeekendDate(current)) { + current = addDays(current, 1); + } + + return toDateKey(current); +}; + +const normalizePhoneForTel = (phone) => { + const cleaned = String(phone || "").trim(); + if (!cleaned) return ""; + if (cleaned.startsWith("+7")) return cleaned; + if (cleaned.startsWith("8")) return "+7" + cleaned.slice(1); + return "+7" + cleaned; +}; + +const isFutureDeliveryDate = (value) => { + const parsedDate = fromDateKey(value); + + if (!parsedDate) { + return false; + } + + return !isWeekendDate(parsedDate) && toDateKey(parsedDate) >= getNextSelectableDateKey(); +}; + +const isSelectableCalendarDate = (date, minDateKey) => { + const dateKey = toDateKey(date); + return dateKey >= minDateKey && !isWeekendDate(date); +}; + +const formatDateForDisplay = (value) => { + if (!value) { + return "Выберите дату"; + } + + const [year, month, day] = value.split("-").map(Number); + if (!year || !month || !day) { + return value; + } + + return new Date(year, month - 1, day).toLocaleDateString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }); +}; + +const formatDeliveryDateDisplay = (value) => { + const normalized = normalizeDateForInput(value); + + if (!normalized) { + return renderValue(value); + } + + return formatDateForDisplay(normalized); +}; + +const startOfMonth = (date) => new Date(date.getFullYear(), date.getMonth(), 1); + +const addMonths = (date, amount) => new Date(date.getFullYear(), date.getMonth() + amount, 1); + +const buildCalendarDays = (currentMonth) => { + const firstDay = startOfMonth(currentMonth); + const lastDay = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 0); + const firstWeekDay = (firstDay.getDay() + 6) % 7; + const totalDays = lastDay.getDate(); + const cells = []; + + for (let index = 0; index < firstWeekDay; index += 1) { + cells.push(null); + } + + for (let day = 1; day <= totalDays; day += 1) { + cells.push(new Date(currentMonth.getFullYear(), currentMonth.getMonth(), day)); + } + + while (cells.length % 7 !== 0) { + cells.push(null); + } + + return cells; +}; + +const normalizeDateForInput = (value) => { + if (!value) { + return ""; + } + + const normalized = String(value).trim(); + if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) { + return normalized; + } + + const shortDateMatch = normalized.match(/^(\d{2})\.(\d{2})\.(\d{2})$/); + if (shortDateMatch) { + const [, day, month, year] = shortDateMatch; + return `20${year}-${month}-${day}`; + } + + return ""; +}; + +const matchesStopWord = (name, stopWords) => { + if (!stopWords || !stopWords.length) return false; + const lower = name.toLowerCase(); + return stopWords.some((sw) => lower.includes(sw.toLowerCase())); +}; + +const useStopWords = () => { + const [stopWords, setStopWords] = React.useState([]); + const [active, setActive] = React.useState(true); + React.useEffect(() => { + if (!supabase) return; + Promise.all([ + supabase.from("stop_words").select("word").then(r => r.data || []), + supabase.from("stop_words_scope").select("scope").eq("id", 1).single().then(r => r.data), + ]).then(([words, scopeRow]) => { + setStopWords(words.map((d) => d.word)); + setActive(scopeRow?.scope !== "client_only"); + }); + }, []); + return { stopWords, active }; +}; + +const CollapsibleOrderComposition = ({ order }) => { + const [isExpanded, setIsExpanded] = React.useState(false); + const { stopWords, active } = useStopWords(); + + const orders = parseOrderList(order); + const allPositions = orders.reduce((sum, o) => sum + (o.items?.length || 0), 0); + const filteredPositions = active ? orders.reduce((sum, o) => { + if (!o.items) return sum; + return sum + o.items.filter((item) => { + const name = String(item.product_name || item.name || item.title || ""); + return !matchesStopWord(name, stopWords); + }).length; + }, 0) : allPositions; + + return ( +
+ + {isExpanded && ( +
+ {!orders.length ? ( +

Позиции не указаны

+ ) : ( + orders.map((orderItem, idx) => ( +
+
+

{orderItem.nom || orderItem.name || `Заказ ${idx + 1}`}

+
+ {(() => { + const filtered = (orderItem.items || []).filter((item) => { + const name = String(item.product_name || item.name || item.title || ""); + return active ? !matchesStopWord(name, stopWords) : true; + }); + if (filtered.length === 0 && active && (orderItem.items || []).length > 0) { + return

Только услуги — скрыты стоп-словами

; + } + if (filtered.length === 0) { + return

Позиции не указаны

; + } + return ( +
+ {filtered.map((item, itemIdx) => ( +
+ {item.product_name || item.name || item.title || ''} + + {item.product_quantity || item.quantity || item.count || item.amount || ""} {item.product_ed || item.unit || ""} + +
+ ))} +
+ ); + })()} +
+ )) + )} +
+ )} +
+ ); +}; + +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 ? ( +
+

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

+
+ + +
+
+ ) : ( + + )} +
+ ); +}; + + +const PROBLEM_REASONS = [ + { value: "client_absent", label: "Клиент не принял", description: "Клиент отказался или не вышел на связь" }, + { value: "damage", label: "Повреждение заказа", description: "Товар повреждён при транспортировке" }, + { value: "wrong_address", label: "Неверный адрес", description: "Адрес доставки указан неверно" }, + { value: "other", label: "Другое", description: "Иная причина проблемы доставки" }, +]; + +const ProblemReasonModal = ({ onSelect, onCancel }) => ( +
+ e.stopPropagation()}> +

Причина проблемы

+

Укажите причину возникшей проблемы с доставкой.

+
+ {PROBLEM_REASONS.map((reason) => ( + + ))} +
+
+ +
+
+
+); + +export const OrderDetailPanel = ({ + order, + canManageDelivery = false, + onSaveManualDeliveryChoice, + isSavingDeliveryChoice = false, + drivers = [], + onAssignDriver, + onChangeDeliveryStatus, + userRole, +}) => { + const [problemReason, setProblemReason] = React.useState(null); + const [pendingStatus, setPendingStatus] = React.useState(null); + const [deliveryDate, setDeliveryDate] = React.useState(""); + const [deliveryTime, setDeliveryTime] = React.useState(DELIVERY_TIME_OPTIONS[0]); + const [formMessage, setFormMessage] = React.useState(""); + const [shipmentState, setShipmentState] = React.useState(null); + const [isCalendarOpen, setIsCalendarOpen] = React.useState(false); + const [driverMessage, setDriverMessage] = React.useState(""); + const [selectedDriverId, setSelectedDriverId] = React.useState(order?.assignedDriverId || ""); + const [deliveryType, setDeliveryType] = React.useState(order?.deliveryType || "delivery"); + const [pickupDate, setPickupDate] = React.useState(order?.pickupDate || ""); + const [pickupTimeSlot, setPickupTimeSlot] = React.useState(DELIVERY_TIME_OPTIONS[0]); + const minSelectableDateKey = React.useMemo(() => getNextSelectableDateKey(), []); + const [currentMonth, setCurrentMonth] = React.useState(() => { + const existingDeliveryDate = fromDateKey(order?.deliveryDate); + const fallbackDate = fromDateKey(minSelectableDateKey) || new Date(); + const sourceDate = existingDeliveryDate && isFutureDeliveryDate(toDateKey(existingDeliveryDate)) + ? existingDeliveryDate + : fallbackDate; + + return startOfMonth(sourceDate); + }); + const calendarDays = React.useMemo(() => buildCalendarDays(currentMonth), [currentMonth]); + const monthLabel = React.useMemo( + () => + currentMonth.toLocaleDateString("ru-RU", { + month: "long", + year: "numeric", + }), + [currentMonth], + ); + const canGoBack = toDateKey(currentMonth) > toDateKey(startOfMonth(fromDateKey(minSelectableDateKey) || new Date())); + + React.useEffect(() => { + setSelectedDriverId(order?.assignedDriverId || ""); + setIsEditingDate(false); + }, [order?.id, order?.assignedDriverId]); + + React.useEffect(() => { + const normalizedDeliveryDate = normalizeDateForInput(order?.deliveryDate); + const nextSelectableDateKey = getNextSelectableDateKey(); + const selectedDateKey = isFutureDeliveryDate(normalizedDeliveryDate) ? normalizedDeliveryDate : nextSelectableDateKey; + setDeliveryDate(selectedDateKey); + const selectedDate = fromDateKey(selectedDateKey) || new Date(); + setCurrentMonth(startOfMonth(selectedDate)); + setDeliveryTime(normalizeDeliveryTimeChoice(order?.deliveryTime || order?.deliveryHalfDay)); + setDeliveryType(order?.deliveryType || "delivery"); + setPickupDate(order?.pickupDate || ""); + setPickupTimeSlot(normalizeDeliveryTimeChoice(order?.pickupTimeSlot || order?.deliveryTime || order?.deliveryHalfDay)); + setFormMessage(""); + }, [order?.id, order?.deliveryDate, order?.deliveryHalfDay, order?.deliveryTime, order?.deliveryType, order?.pickupDate, order?.pickupTimeSlot]); + + if (!order) { + return ( + +

Выберите группу для просмотра деталей.

+
+ ); + } + + const isDeliveryAgreed = ["agreed", "driver_assigned", "loaded", "on_route", "delivered"].includes(order.deliveryStatus || order.delivery_status); + const canEditDelivery = canManageDelivery && ["admin", "mega_admin", "logistician"].includes(userRole); + const [isEditingDate, setIsEditingDate] = React.useState(false); + const agreedDeliveryLabel = [ + formatDeliveryDateDisplay(order.deliveryDate), + order.deliveryTime || order.deliveryHalfDay, + ].filter((value) => value && value !== "Нет данных").join(" · "); + + const handleShipmentChange = React.useCallback((state) => { + setShipmentState(state); + }, []); + + const handleSaveDeliveryChoice = async () => { + const effectiveDate = deliveryType === "pickup" ? pickupDate : deliveryDate; + const effectiveTime = deliveryType === "pickup" ? pickupTimeSlot : deliveryTime; + if (!effectiveDate || !effectiveTime) { + setFormMessage(deliveryType === "pickup" ? "Укажите дату и время самовывоза." : "Укажите дату и половину дня доставки."); + return; + } + + if (!isFutureDeliveryDate(effectiveDate)) { + setFormMessage(deliveryType === "pickup" ? "Выберите дату самовывоза позже сегодняшнего дня." : "Выберите дату доставки позже сегодняшнего дня."); + return; + } + + try { + const result = await onSaveManualDeliveryChoice?.({ + orderGroupId: order.id, + deliveryDate: deliveryType === "pickup" ? pickupDate : deliveryDate, + deliveryTime: deliveryType === "pickup" ? pickupTimeSlot : deliveryTime, + deliveryType, + ...(deliveryType === "pickup" ? { pickupDate, pickupTimeSlot } : {}), + }); + + if (result?.success) { + setFormMessage("Доставка согласована вручную."); + return; + } + + setFormMessage(getErrorMessage(result?.error, "Не удалось сохранить согласование доставки.")); + } catch (error) { + setFormMessage(getErrorMessage(error, "Не удалось сохранить согласование доставки.")); + } + }; + + const handleAssignDriver = async () => { + if (!selectedDriverId) { + setDriverMessage("Выберите водителя"); + return; + } + + if (!order.deliveryDate) { + setDriverMessage("Сначала укажите дату и время доставки."); + return; + } + + setDriverMessage(""); + const response = await onAssignDriver({ + orderGroupId: order.id, + driverId: selectedDriverId, + }); + + if (!response.success) { + setDriverMessage(response.error || "Не удалось назначить водителя"); + } else { + setDriverMessage("Водитель назначен"); + } + }; + + return ( +
+ +
+
+

+ Карточка группы доставки +

+

+ {order.displayTitle || order.customerName || order.groupKey} +

+

+ {order.displaySubtitle || [order.customerPhone, order.customerDate].filter(Boolean).join(" · ") || "Не указано"} +

+
+ {getOrderGroupDisplayStatusLabel(order)} +
+ +
+
+

+ Дата доставки +

+

{formatDeliveryDateDisplay(order.deliveryDate)}

+
+
+

+ Время доставки +

+

{renderValue(order.deliveryTime || order.deliveryHalfDay)}

+
+
+

+ Тип доставки +

+

{order.deliveryType === "pickup" ? "Самовывоз" : "Доставка"}

+
+
+

+ Водитель +

+

{order.assignedDriverId ? renderValue(order.assignedDriverName) : "Не назначен"}

+
+
+

+ Телефон +

+ + {renderValue(order.customerPhone)} + +
+
+

+ Адрес доставки +

+

{renderValue(order.deliveryAddress)}

+
+
+ +
+
+

Номер счёта

+

{renderValue(order.orderNumberSummary)}

+
+
+

Клиент

+

{renderValue(order.customerName)}

+
+
+

Дата счёта

+

{renderValue(order.customerDate)}

+
+
+

Всего заказов

+

{order.ordersCount ?? 0}

+
+
+

Готово

+

{order.readyCount ?? 0}

+
+
+

Не готово

+

{order.notReadyCount ?? 0}

+
+
+

Обновлена

+

{formatDateTime(order.updatedAt)}

+
+
+

Статус доставки

+

{getOrderGroupDeliveryStatusLabel(order.deliveryStatus || order.delivery_status)}

+
+
+
+ + {canManageDelivery ? ( + +
+ Ручное согласование доставки +

+ {isDeliveryAgreed + ? "Дата и половина дня доставки уже зафиксированы." + : "Если клиент согласовал доставку по телефону, сохраните дату и половину дня здесь."} +

+
+ {/* Delivery type tabs */} +
+ + +
+ {deliveryType === "pickup" && ( +
+

ℹ️ Условия хранения

+

Бесплатное хранение — 2 рабочих дня с даты готовности.

+

Начиная с 3-го рабочего дня — 300 ₽/день платного хранения.

+
+ )} + {isDeliveryAgreed && !isEditingDate ? ( +
+
+
+
+

+ Доставка согласована +

+

+ {agreedDeliveryLabel || "Дата и время сохранены"} +

+
+ Согласовано +
+
+ {canEditDelivery ? ( + + ) : null} +
+ ) : deliveryType === "delivery" ? ( +
+
+ + {isCalendarOpen ? ( +
+
+
+

+ Календарь доставки +

+

+ {monthLabel} +

+
+
+ + +
+
+
+ {WEEK_DAY_LABELS.map((day) => ( +
+ {day} +
+ ))} +
+
+ {calendarDays.map((day, index) => { + if (!day) { + return
; + } + + const dateKey = toDateKey(day); + const isWeekend = isWeekendDate(day); + const isSelectable = isSelectableCalendarDate(day, minSelectableDateKey); + const isSelected = dateKey === deliveryDate; + const isDisabled = !isSelectable; + const dayNumber = String(day.getDate()).padStart(2, "0"); + + return ( + + ); + })} +
+

+ Выходные отмечены пунктиром и недоступны. +

+
+ ) : null} +
+
+ {DELIVERY_TIME_OPTIONS.map((option) => ( + + ))} +
+ ) : ( +
+ + {isCalendarOpen ? ( +
+
+
+

Календарь самовывоза

+

{monthLabel}

+
+
+ + +
+
+
+ {WEEK_DAY_LABELS.map((day) => (
{day}
))} +
+
+ {calendarDays.map((day, index) => { + if (!day) return
; + const dateKey = toDateKey(day); + const isWeekend = isWeekendDate(day); + const isSelectable = isSelectableCalendarDate(day, minSelectableDateKey); + const isSelected = dateKey === pickupDate; + const isDisabled = !isSelectable; + const dayNumber = String(day.getDate()).padStart(2, "0"); + return ( + + ); + })} +
+

Выходные отмечены пунктиром и недоступны.

+
+ ) : null} +
+ {DELIVERY_TIME_OPTIONS.map((option) => ( + + ))} +
+
+ )} + + )} + {formMessage ? ( +

{formMessage}

+ ) : null} + + ) : null} + + + {canManageDelivery && ["manager", "logistician", "admin", "mega_admin"].includes(userRole) ? ( + +
+ Назначение водителя +

+ {(() => { + const ds = order.deliveryStatus || order.delivery_status; + if (["loaded", "on_route", "delivered"].includes(ds)) { + return "Доставка в процессе — сменить водителя нельзя."; + } + return order.assignedDriverId + ? "Назначен водитель. Вы можете изменить назначение." + : "Выберите водителя для доставки."; + })()} +

+
+ {order.assignedDriverId ? ( +
+
+
+

+ Водитель назначен +

+

+ {order.assignedDriverName || "Неизвестно"} +

+
+ Назначен +
+
+ ) : null} + {(() => { + const ds = order.deliveryStatus || order.delivery_status; + const isDriverLocked = ["loaded", "on_route", "delivered"].includes(ds); + return !isDriverLocked ? ( +
+ + +
+ ) : null; + })()} + {driverMessage ? ( +

{driverMessage}

+ ) : null} +
+ ) : null} + + + {["manager", "logistician", "admin", "mega_admin"].includes(userRole) && order && onChangeDeliveryStatus ? ( + +
+ Статус доставки +

+ Измените статус, если водитель забыл обновить или нужна корректировка. +

+
+
+ {[ + { value: "pending_confirmation", label: "Ожидает согласования", manual: true }, + { value: "agreed", label: "Согласовано", manual: false, hint: "Согласуйте дату доставки выше" }, + { value: "driver_assigned", label: "Назначен водитель", manual: false, hint: "Назначьте водителя из списка" }, + { value: "loaded", label: "Загружено", manual: true }, + { value: "on_route", label: "В пути", manual: true }, + { value: "delivered", label: "Доставлено", manual: true }, + { value: "pickup", label: "Самовывоз", manual: true }, + { value: "problem", label: "Проблема", manual: true }, + { value: "cancelled", label: "Отменено", manual: true }, + ].map((statusOption) => { + const isCurrent = (order.deliveryStatus || order.delivery_status) === statusOption.value; + const isClickable = statusOption.manual !== false && !isCurrent; + return ( +
+ +
+ ); + })} +
+ {formMessage ? ( +

{formMessage}

+ ) : null} +
+ ) : null} + + + {["manager", "logistician", "admin", "mega_admin"].includes(userRole) && order && onChangeDeliveryStatus ? ( + + ) : null} + + {userRole === "driver" && order ? ( + + ) : null} + +{userRole === "driver" && order && onChangeDeliveryStatus ? ( + +
+ Статус доставки +

+ Выберите статус и нажмите «Сохранить». +

+
+ {problemReason !== null ? ( + { + setPendingStatus({ value: "problem", reason: reasonValue, reasonLabel }); + setProblemReason(null); + }} + onCancel={() => setProblemReason(null)} + /> + ) : null} +
+ {(() => { + const currentStatus = order.deliveryStatus || order.delivery_status; + const IN_TRANSIT_STATUSES = ["loaded", "on_route"]; + const isOnRoute = IN_TRANSIT_STATUSES.includes(currentStatus); + + let statusOptions = []; + if (currentStatus === "delivered" || currentStatus === "problem" || currentStatus === "cancelled" || currentStatus === "paid_storage") { + statusOptions = []; + } else { + statusOptions = [ + { value: "delivered", label: "Доставлено" }, + { value: "problem", label: "Проблема" }, + ]; + } + + if (statusOptions.length === 0) return null; + + return statusOptions.map((statusOption) => { + const isSelected = pendingStatus?.value === statusOption.value; + const isDeliveredBtn = statusOption.value === "delivered"; + const deliveryBlocked = isDeliveredBtn && shipmentState && !shipmentState.canMarkDelivered; + return ( + + ); + }); + })()} +
+ {pendingStatus ? ( +
+ + +
+ ) : null} + {formMessage ? ( +

{formMessage}

+ ) : null} +
+ ) : null} + + + Счета + {renderList(getAllBillNumbers(order))} + + + + + + {userRole !== "driver" && (order?.driver_shipment_data || order?.driverShipmentData) ? ( + + ) : null} + {userRole !== "driver" ? ( + + Дополнительные данные +
+ {order.firstSmsSentAt ? ( +
+

1-е SMS отправлено

+

{formatDateTime(order.firstSmsSentAt)}

+
+ ) : null} + {order.secondSmsSentAt ? ( +
+

2-е SMS отправлено

+

{formatDateTime(order.secondSmsSentAt)}

+
+ ) : null} + {!order.firstSmsSentAt && !order.secondSmsSentAt ? ( +
+

SMS отправлено

+

Нет

+
+ ) : null} +
+

Ручное согласование выполнено

+

{order.manualConfirmationAt ? formatDateTime(order.manualConfirmationAt) : "Нет"}

+
+
+

Платное хранение

+

{order.paidStorageAt ? formatDateTime(order.paidStorageAt) : "Нет"}

+
+ {order.createdFromExchangeAt ? ( +
+

Создано из обмена

+

{formatDateTime(order.createdFromExchangeAt)}

+
+ ) : null} +
+
+ ) : null} +
+ ); +}; diff --git a/src/services/supabase/orderGroupRepository.js b/src/services/supabase/orderGroupRepository.js index c699cb2..a4eb953 100644 --- a/src/services/supabase/orderGroupRepository.js +++ b/src/services/supabase/orderGroupRepository.js @@ -281,18 +281,6 @@ export const updateOrderGroupDeliveryChoice = async ({ }, "Ошибка сохранения согласования доставки"); }; - .eq("id", orderGroupId) - .single(); - - if (error) { - throw error; - } - - await logAction({ orderGroupId, action: "date_assigned", newValue: "manual: " + deliveryDate + " " + (deliveryTime || ""), details: { delivery_date_source: "manual" } }).catch(() => {}); - - return mapOrderGroupRowToDeliveryGroup(data); - }, "Ошибка сохранения согласования доставки"); -}; export const assignDriverToOrderGroup = async ({ orderGroupId, @@ -456,3 +444,4 @@ export const fetchOrderGroups = async () => { return group; }).filter(Boolean); }, "Ошибка загрузки групп доставки"); +};