import { ORDER_STATUSES, WORKFLOW_STAGES, getStatusOwnerRole, getStatusSla, getStatusStageKey, getStatusStageLabel, } from "../constants/deliveryWorkflow"; const COMPLETED_ORDER_STATUSES = new Set(["Доставлен", "Закрыт", "Отменён"]); const ARCHIVE_ONLY_ORDER_STATUSES = new Set(["Закрыт", "Отменён"]); const EXCEPTION_ORDER_STATUSES = new Set(["Проблема доставки"]); const formatAgeLabel = (hours) => { if (!Number.isFinite(hours) || hours < 1) { return "меньше часа"; } if (hours < 24) { return `${Math.floor(hours)}ч в статусе`; } const days = Math.floor(hours / 24); const remainingHours = Math.floor(hours % 24); if (remainingHours === 0) { return `${days}д в статусе`; } return `${days}д ${remainingHours}ч в статусе`; }; const getStatusEnteredAt = (order) => { const matchingEntries = (order.history || []) .filter((entry) => entry.newStatus === order.status) .sort((left, right) => new Date(right.at) - new Date(left.at)); return matchingEntries[0]?.at || order.updatedAt || order.createdAt || new Date().toISOString(); }; export const isCompletedOrderStatus = (status) => COMPLETED_ORDER_STATUSES.has(status); export const isExceptionOrderStatus = (status) => EXCEPTION_ORDER_STATUSES.has(status); export const filterRegistryOrders = (orders, { includeCompleted = false } = {}) => orders.filter((order) => includeCompleted || !isCompletedOrderStatus(order.status)); export const filterArchiveOrders = (orders) => orders.filter((order) => isCompletedOrderStatus(order.status)); export const getOrderAgingState = (order, { now = new Date().toISOString() } = {}) => { const enteredAt = getStatusEnteredAt(order); const statusAgeHours = Math.max( 0, (new Date(now).getTime() - new Date(enteredAt).getTime()) / (1000 * 60 * 60), ); const { warningAfterHours, criticalAfterHours } = getStatusSla(order.status); if (criticalAfterHours !== null && statusAgeHours >= criticalAfterHours) { return { enteredAt, statusAgeHours, statusAgeLabel: formatAgeLabel(statusAgeHours), agingState: "critical", }; } if (warningAfterHours !== null && statusAgeHours >= warningAfterHours) { return { enteredAt, statusAgeHours, statusAgeLabel: formatAgeLabel(statusAgeHours), agingState: "warning", }; } return { enteredAt, statusAgeHours, statusAgeLabel: formatAgeLabel(statusAgeHours), agingState: "normal", }; }; const enrichOrderForKanban = (order, options) => { const aging = getOrderAgingState(order, options); return { ...order, ownerRole: getStatusOwnerRole(order.status), stageKey: getStatusStageKey(order.status), stageLabel: getStatusStageLabel(order.status), ...aging, }; }; const buildStageColumns = (orders, { includeCompleted }) => { const mainStages = WORKFLOW_STAGES.filter((stage) => stage.key !== "completed"); const columns = mainStages.map((stage) => ({ key: stage.key, title: stage.label, stageKey: stage.key, statuses: ORDER_STATUSES.filter((status) => getStatusStageKey(status) === stage.key), items: orders.filter((order) => order.stageKey === stage.key), })); columns.push({ key: "delivered", title: "Доставлен", stageKey: "completed", statuses: ["Доставлен"], dropStatus: "Доставлен", items: orders.filter((order) => order.status === "Доставлен"), }); columns.push({ key: "completed", title: "Завершено", stageKey: "completed", statuses: ["Закрыт", "Отменён"], items: includeCompleted ? orders.filter((order) => ARCHIVE_ONLY_ORDER_STATUSES.has(order.status)) : [], }); return columns; }; const buildStatusColumns = (orders, { includeCompleted }) => { const statuses = includeCompleted ? ORDER_STATUSES : ORDER_STATUSES.filter((status) => !ARCHIVE_ONLY_ORDER_STATUSES.has(status)); return statuses.map((status) => ({ key: status, title: status, stageKey: getStatusStageKey(status), statuses: [status], dropStatus: status, items: orders.filter((order) => order.status === status), })); }; export const buildKanbanColumns = ( orders, { includeCompleted = false, mode = "by_stage", now = new Date().toISOString() } = {}, ) => { const enrichedOrders = orders.map((order) => enrichOrderForKanban(order, { now })); const columns = mode === "by_status" ? buildStatusColumns(enrichedOrders, { includeCompleted }) : buildStageColumns(enrichedOrders, { includeCompleted }); return columns.map((column) => ({ ...column, warningCount: column.items.filter((item) => item.agingState === "warning").length, criticalCount: column.items.filter((item) => item.agingState === "critical").length, })); }; export const filterKanbanColumnsByStage = (columns, stageKey = "all") => { if (stageKey === "all") { return columns; } return columns.filter((column) => column.stageKey === stageKey); };