import { demoOrders } from "../data/mockAppData"; import { getAvailableTransitionsByRole, getStatusOwnerRole, getStatusStageKey, LOGISTICS_STATUSES, PRODUCTION_STATUSES, } from "../constants/deliveryWorkflow"; import { getOrderAgingState } from "./orderViews"; const DELIVERY_HANDOFF_STATUS = "Назначен водитель"; const DELIVERY_HANDOFF_REQUIRES_DRIVER_MESSAGE = "Сначала назначьте водителя, потом заказ можно передать в доставку."; const DELIVERY_HANDOFF_ROLE_MESSAGE = "Логист может передать заказ в доставку только через статус «Назначен водитель»."; export const cloneOrders = (orders = demoOrders) => orders.map((order) => ({ ...order, customer: { ...order.customer }, history: [...order.history], chatMessages: [...order.chatMessages], internalMessages: [...(order.internalMessages || [])], orderNotes: [...(order.orderNotes || [])], deliverySlots: [...order.deliverySlots], logisticianIds: [...order.logisticianIds], assignedDriverId: order.assignedDriverId || null, driverRouteOrder: order.driverRouteOrder ?? null, deliveryAgreementStatus: order.deliveryAgreementStatus || "Не начато", comments: [...order.comments], items: [...(order.items || [])], tags: [...order.tags], })); export const buildSearchBlob = (order) => { return [ order.orderNumber, order.customer.name, order.customer.phone, order.status, order.deliveryAgreementStatus, order.exception || "", order.customer.messenger, ...(order.items || []), ...order.comments, ...order.tags, ] .join(" ") .toLowerCase(); }; export const filterOrdersByView = ({ orders, currentUser, filters, now }) => { const normalizedFilters = { query: "", status: "all", stage: "all", ownerRole: "all", agingState: "all", managerId: "all", logisticianId: "all", messenger: "all", ...filters, }; const visibleOrders = orders.filter((order) => { if (!currentUser) { return false; } if (currentUser.role === "manager" || currentUser.role === "admin" || currentUser.role === "mega_admin") { return true; } return getStatusOwnerRole(order.status) === currentUser.role; }); const normalizedQuery = normalizedFilters.query.trim().toLowerCase(); const filteredOrders = visibleOrders.filter((order) => { const stageKey = getStatusStageKey(order.status); const ownerRole = getStatusOwnerRole(order.status); const { agingState } = getOrderAgingState(order, { now }); if (normalizedFilters.status !== "all" && order.status !== normalizedFilters.status) { return false; } if (normalizedFilters.stage !== "all" && stageKey !== normalizedFilters.stage) { return false; } if (normalizedFilters.ownerRole !== "all" && ownerRole !== normalizedFilters.ownerRole) { return false; } if (normalizedFilters.agingState !== "all" && agingState !== normalizedFilters.agingState) { return false; } if (normalizedFilters.managerId !== "all" && order.managerId !== normalizedFilters.managerId) { return false; } if ( normalizedFilters.logisticianId !== "all" && !order.logisticianIds.includes(normalizedFilters.logisticianId) ) { return false; } if ( normalizedFilters.messenger !== "all" && order.customer.messenger !== normalizedFilters.messenger ) { return false; } if (normalizedQuery && !buildSearchBlob(order).includes(normalizedQuery)) { return false; } return true; }); return { visibleOrders, filteredOrders }; }; export const appendHistoryEntry = (order, entry) => ({ ...order, history: [ { id: `history-${Date.now()}`, at: new Date().toISOString(), ...entry, }, ...order.history, ], }); export const getAvailableTransitions = ({ status, role }) => getAvailableTransitionsByRole({ status, role }); export const getAvailableTransitionsForOrder = ({ order, role }) => getAvailableTransitionsByRole({ status: order.status, role }).filter((nextStatus) => { if (nextStatus === DELIVERY_HANDOFF_STATUS && !order.assignedDriverId) { return false; } return true; }); export const getTransitionBlockedReason = ({ order, nextStatus, role }) => { if (nextStatus === DELIVERY_HANDOFF_STATUS && !order.assignedDriverId) { return DELIVERY_HANDOFF_REQUIRES_DRIVER_MESSAGE; } const roleTransitions = getAvailableTransitionsByRole({ status: order.status, role }); if (!roleTransitions.includes(nextStatus) && role === "logistician" && ["Загружен", "В пути", "Доставлен"].includes(nextStatus)) { return DELIVERY_HANDOFF_ROLE_MESSAGE; } return "Этот переход сейчас недоступен для вашей роли."; }; export const getKanbanDropResolution = ({ order, column, role }) => { const allowedStatuses = getAvailableTransitionsForOrder({ order, role }); const nextStatus = column.statuses.find((status) => allowedStatuses.includes(status)); if (nextStatus) { return { nextStatus, reason: null, }; } if (column.statuses.includes(DELIVERY_HANDOFF_STATUS)) { return { nextStatus: null, reason: getTransitionBlockedReason({ order, nextStatus: DELIVERY_HANDOFF_STATUS, role, }), }; } return { nextStatus: null, reason: getTransitionBlockedReason({ order, nextStatus: column.statuses[0], role, }), }; }; const deriveAgreementStatus = (currentOrder, nextStatus) => { if (nextStatus === "Ожидает ответа клиента") { return currentOrder.deliveryAgreementStatus === "Подтверждено клиентом" ? "Отправлено клиенту" : "Ожидание ответа"; } if (nextStatus === "Ожидает согласования доставки") { return currentOrder.deliveryAgreementStatus === "Подтверждено клиентом" ? "Отправлено клиенту" : "Ожидание ответа"; } if (nextStatus === "Доставка согласована") { return "Подтверждено клиентом"; } if (nextStatus === "Передан логисту") { return currentOrder.deliveryAgreementStatus === "Подтверждено клиентом" ? "Перенос запрошен" : "Нет ответа"; } if (nextStatus === "Платное хранение") { return "Нет ответа"; } if (nextStatus === "Проблема доставки") { return currentOrder.deliveryAgreementStatus === "Не начато" ? "Ошибка отправки" : currentOrder.deliveryAgreementStatus; } return currentOrder.deliveryAgreementStatus || "Не начато"; }; export const applyStatusUpdate = (order, nextStatus, actorName) => { return appendHistoryEntry( { ...order, status: nextStatus, deliveryAgreementStatus: deriveAgreementStatus(order, nextStatus), updatedAt: new Date().toISOString(), }, { action: "Изменение статуса", oldStatus: order.status, newStatus: nextStatus, userName: actorName, }, ); }; export const assignDriverToOrder = (order, driverId, actorName) => { const normalizedDriverId = driverId || null; return appendHistoryEntry( { ...order, assignedDriverId: normalizedDriverId, driverRouteOrder: normalizedDriverId ? order.driverRouteOrder : null, updatedAt: new Date().toISOString(), }, { action: "Назначение водителя", oldStatus: order.status, newStatus: order.status, userName: actorName, }, ); }; export const appendChatMessageToOrder = (order, message) => ({ ...order, updatedAt: new Date().toISOString(), chatMessages: [ { id: `chat-${Date.now()}`, sentAt: new Date().toISOString(), ...message, }, ...order.chatMessages, ], }); export const appendInternalMessageToOrder = (order, message) => ({ ...order, updatedAt: new Date().toISOString(), internalMessages: [ { id: `internal-${Date.now()}`, sentAt: new Date().toISOString(), ...message, }, ...(order.internalMessages || []), ], }); export const appendOrderNote = (order, note) => ({ ...order, updatedAt: new Date().toISOString(), orderNotes: [ { id: `note-${Date.now()}`, createdAt: new Date().toISOString(), ...note, }, ...(order.orderNotes || []), ], }); export const applyDeliveryReschedule = (order, deliverySlot, actorName) => { const normalizedTime = deliverySlot.time === "Вторая половина дня" ? "13:00:00Z" : "09:00:00Z"; return appendHistoryEntry( { ...order, status: "Ожидает согласования доставки", deliveryAgreementStatus: "Перенос запрошен", updatedAt: new Date().toISOString(), scheduledDelivery: `${deliverySlot.date}T${normalizedTime}`, deliverySlots: [ { id: `slot-${Date.now()}`, ...deliverySlot, status: "Ожидает подтверждения", }, ...order.deliverySlots, ], }, { action: "Перенос доставки", oldStatus: order.status, newStatus: "Ожидает согласования доставки", userName: actorName, }, ); }; export const updateOrderDetails = (order, payload, actorName) => { return appendHistoryEntry( { ...order, customer: { ...order.customer, name: payload.customerName, phone: payload.customerPhone, address: payload.customerAddress, messenger: payload.messenger, }, orderNumber: payload.orderNumber, managerId: payload.managerId, items: payload.items .split("\n") .map((item) => item.trim()) .filter(Boolean), comments: payload.comments .split(",") .map((item) => item.trim()) .filter(Boolean), tags: payload.tags .split(",") .map((item) => item.trim()) .filter(Boolean), updatedAt: new Date().toISOString(), }, { action: "Редактирование заказа", oldStatus: order.status, newStatus: order.status, userName: actorName, }, ); }; export const createOrderRecord = ({ payload, actorName, availableLogisticians = [] }) => { const assignedLogistician = availableLogisticians[0]?.id || null; return { id: `order-${Date.now()}`, orderNumber: payload.orderNumber, customer: { name: payload.customerName, phone: payload.customerPhone, address: payload.customerAddress, messenger: payload.messenger, }, status: "Новый", managerId: payload.managerId, logisticianIds: assignedLogistician ? [assignedLogistician] : [], assignedDriverId: null, driverRouteOrder: null, deliveryAgreementStatus: "Не начато", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), scheduledDelivery: payload.deliveryDate ? `${payload.deliveryDate}T10:00:00Z` : new Date().toISOString(), tags: payload.tags .split(",") .map((item) => item.trim()) .filter(Boolean), items: payload.items .split("\n") .map((item) => item.trim()) .filter(Boolean), comments: payload.comments .split(",") .map((item) => item.trim()) .filter(Boolean), history: [ { id: `history-${Date.now()}`, action: "Создание заказа", oldStatus: null, newStatus: "Новый", userName: actorName, at: new Date().toISOString(), }, ], chatMessages: [], internalMessages: [], orderNotes: [], deliverySlots: payload.deliveryDate ? [ { id: `slot-${Date.now()}`, date: payload.deliveryDate, time: "Первая половина дня", logisticianId: assignedLogistician, status: "Черновик", }, ] : [], exception: null, }; }; export const autoAssignOrders = (orders, logisticians) => { if (!logisticians.length) { return orders; } return orders.map((order, index) => appendHistoryEntry( { ...order, logisticianIds: [logisticians[index % logisticians.length].id], updatedAt: new Date().toISOString(), }, { action: "Автораспределение логиста", oldStatus: order.status, newStatus: order.status, userName: "Система", }, ), ); }; export const buildMetrics = (orders) => { const byStatus = orders.reduce((accumulator, order) => { accumulator[order.status] = (accumulator[order.status] || 0) + 1; return accumulator; }, {}); return { total: orders.length, readyToShip: byStatus["Готов к отгрузке"] || 0, awaitingDeliveryCoordination: byStatus["Ожидает согласования доставки"] || 0, inProduction: PRODUCTION_STATUSES.reduce((sum, status) => sum + (byStatus[status] || 0), 0), inLogistics: LOGISTICS_STATUSES.reduce((sum, status) => sum + (byStatus[status] || 0), 0), exceptions: byStatus["Проблема доставки"] || 0, }; };