Files
web-frontend/app/composables/useOrderStatusPresentation.ts
2026-04-04 14:01:46 +07:00

280 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

type OrderStatusCode =
| 'NEW'
| 'MANAGER_PROCESSING'
| 'WAITING_DOUBLE_CONFIRM'
| 'CLIENT_REJECTED'
| 'MANAGER_REJECTED'
| 'MANAGER_BLOCKED'
| 'CONFIRMED'
| 'IN_PROGRESS'
| 'COMPLETED';
type TimelineStage = {
code: string;
label: string;
note: string;
dateLabel: string;
state: 'done' | 'current' | 'upcoming';
};
type StatusPresentation = {
title: string;
summary: string;
stages: TimelineStage[];
};
const STAGE_ORDER: OrderStatusCode[] = [
'NEW',
'MANAGER_PROCESSING',
'WAITING_DOUBLE_CONFIRM',
'CONFIRMED',
'IN_PROGRESS',
'COMPLETED',
];
const DAY_FORMATTER = new Intl.DateTimeFormat('ru-RU', {
day: 'numeric',
month: 'long',
});
function addDays(date: Date, days: number) {
const next = new Date(date);
next.setDate(next.getDate() + days);
return next;
}
function formatDay(date: Date) {
return DAY_FORMATTER.format(date);
}
function stageIndex(status: string) {
const index = STAGE_ORDER.indexOf(status as OrderStatusCode);
return index >= 0 ? index : 0;
}
function buildDates(createdAt: string | Date) {
const base = new Date(createdAt);
return {
created: base,
offer: addDays(base, 1),
approval: addDays(base, 2),
production: addDays(base, 4),
shipment: addDays(base, 6),
delivered: addDays(base, 8),
};
}
function buildManagerStages(status: string, createdAt: string | Date): StatusPresentation {
const dates = buildDates(createdAt);
const isOfferStage = ['WAITING_DOUBLE_CONFIRM', 'CONFIRMED'].includes(status);
const isWorkStage = ['IN_PROGRESS', 'COMPLETED'].includes(status);
const isStopped = ['CLIENT_REJECTED', 'MANAGER_REJECTED', 'MANAGER_BLOCKED'].includes(status);
const stages: TimelineStage[] = [
{
code: 'NEW',
label: 'Заявка',
note: 'Заказ создан и ждёт расчёта.',
dateLabel: formatDay(dates.created),
state: isOfferStage || isWorkStage || isStopped ? 'done' : 'current',
},
{
code: 'WAITING_DOUBLE_CONFIRM',
label: 'Предложение',
note: 'Цена и условия опубликованы клиенту.',
dateLabel: formatDay(dates.offer),
state: isWorkStage ? 'done' : isOfferStage ? 'current' : 'upcoming',
},
{
code: 'IN_PROGRESS',
label: status === 'COMPLETED' ? 'Исполнение завершено' : 'В работе',
note: status === 'COMPLETED'
? 'Заказ передан в работу и закрыт.'
: 'Заказ передан в производство или исполнение.',
dateLabel: formatDay(dates.production),
state: isWorkStage ? 'current' : 'upcoming',
},
];
if (status === 'MANAGER_BLOCKED') {
return {
title: 'Заказ на паузе',
summary: 'Нужно уточнение по заказу перед публикацией предложения.',
stages,
};
}
if (status === 'CLIENT_REJECTED' || status === 'MANAGER_REJECTED') {
return {
title: 'Заказ остановлен',
summary: 'Текущий заказ завершён без запуска в работу.',
stages,
};
}
if (isWorkStage) {
return {
title: status === 'COMPLETED' ? 'Заказ завершён' : 'Заказ в работе',
summary: status === 'COMPLETED'
? 'Исполнение завершено, история этапов сохранена.'
: 'Предложение согласовано, заказ уже в работе.',
stages,
};
}
if (isOfferStage) {
return {
title: 'Предложение отправлено',
summary: 'Клиент уже видит цену и условия. Следующий шаг для менеджера: пустить заказ в работу.',
stages,
};
}
return {
title: 'Ждём расчёт по заявке',
summary: 'Заполните цену по позициям и логистике, после этого предложение уйдёт клиенту автоматически.',
stages,
};
}
function buildClientStages(status: string, createdAt: string | Date): StatusPresentation {
const dates = buildDates(createdAt);
if (status === 'CLIENT_REJECTED' || status === 'MANAGER_REJECTED') {
return {
title: 'Заказ остановлен',
summary: 'Текущий заказ закрыт. При необходимости можно оформить новый заказ.',
stages: [
{
code: 'NEW',
label: 'Заказ создан',
note: 'Заказ принят в обработку.',
dateLabel: formatDay(dates.created),
state: 'done',
},
{
code: status,
label: 'Заказ остановлен',
note: 'Дальнейшее исполнение не планируется.',
dateLabel: formatDay(dates.approval),
state: 'current',
},
],
};
}
if (status === 'MANAGER_BLOCKED') {
return {
title: 'Заказ ждёт уточнения',
summary: 'Менеджер уточняет параметры заказа перед расчётом.',
stages: [
{
code: 'NEW',
label: 'Заказ создан',
note: 'Заказ принят в обработку.',
dateLabel: formatDay(dates.created),
state: 'done',
},
{
code: status,
label: 'Уточняем детали',
note: 'После уточнения покажем плановые даты по исполнению.',
dateLabel: formatDay(dates.approval),
state: 'current',
},
],
};
}
const currentIndex = stageIndex(status);
const stages: TimelineStage[] = [
{
code: 'NEW',
label: 'Заказ создан',
note: 'Приняли заказ и начали обработку.',
dateLabel: formatDay(dates.created),
state: currentIndex > 0 ? 'done' : 'current',
},
{
code: 'CONFIRMED',
label: 'Производство',
note: 'Плановая дата запуска или выхода из производства.',
dateLabel: formatDay(dates.production),
state: currentIndex > 3 ? 'done' : currentIndex >= 3 ? 'current' : 'upcoming',
},
{
code: 'IN_PROGRESS',
label: 'Отгрузка',
note: 'Плановая дата передачи в логистику.',
dateLabel: formatDay(dates.shipment),
state: currentIndex > 4 ? 'done' : currentIndex >= 4 ? 'current' : 'upcoming',
},
{
code: 'COMPLETED',
label: 'Доставка',
note: 'Плановая дата получения заказа.',
dateLabel: formatDay(dates.delivered),
state: currentIndex >= 5 ? 'current' : 'upcoming',
},
];
if (status === 'NEW') {
return {
title: 'Заказ создан',
summary: 'Менеджер рассчитывает стоимость и готовит план по исполнению.',
stages,
};
}
if (status === 'MANAGER_PROCESSING') {
return {
title: 'Готовим предложение',
summary: 'Собираем итоговые условия и скоро покажем плановые даты.',
stages,
};
}
if (status === 'WAITING_DOUBLE_CONFIRM') {
return {
title: 'Предложение готово',
summary: 'Стоимость и условия уже рассчитаны. Следующий этап после запуска: производство.',
stages,
};
}
if (status === 'CONFIRMED') {
return {
title: 'Планируем производство',
summary: `Ориентируемся на производство ${formatDay(dates.production)}, затем отгрузку и доставку по плану.`,
stages,
};
}
if (status === 'IN_PROGRESS') {
return {
title: 'Заказ в работе',
summary: `Производство идёт. Следующая плановая дата: отгрузка около ${formatDay(dates.shipment)}.`,
stages,
};
}
return {
title: 'Доставка по плану',
summary: `Финальный ориентир по заказу: доставка ${formatDay(dates.delivered)}.`,
stages,
};
}
export function getOrderStatusPresentation(
status: string,
createdAt: string | Date,
audience: 'client' | 'manager' = 'client',
): StatusPresentation {
return audience === 'manager'
? buildManagerStages(status, createdAt)
: buildClientStages(status, createdAt);
}