supersam/src/services/orderViews.js

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);
};