168 lines
5.0 KiB
JavaScript
168 lines
5.0 KiB
JavaScript
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);
|
|
};
|