diff --git a/app/components/orders/OrderStatusBadge.vue b/app/components/orders/OrderStatusBadge.vue index 317d385..da12d48 100644 --- a/app/components/orders/OrderStatusBadge.vue +++ b/app/components/orders/OrderStatusBadge.vue @@ -4,15 +4,12 @@ const props = defineProps<{ }>(); const statusLabel = computed(() => { - if (props.status === 'NEW') return 'Уточнение цены'; - if (props.status === 'MANAGER_PROCESSING') return 'В работе у менеджера'; - if (props.status === 'WAITING_DOUBLE_CONFIRM') return 'Ожидает подтверждения'; - if (props.status === 'CONFIRMED') return 'Подтвержден'; - if (props.status === 'IN_PROGRESS') return 'Выполняется'; + if (props.status === 'NEW' || props.status === 'MANAGER_PROCESSING') return 'Заявка'; + if (props.status === 'WAITING_DOUBLE_CONFIRM' || props.status === 'CONFIRMED') return 'Предложение'; + if (props.status === 'IN_PROGRESS') return 'В работе'; if (props.status === 'COMPLETED') return 'Завершен'; - if (props.status === 'CLIENT_REJECTED') return 'Отклонен клиентом'; - if (props.status === 'MANAGER_REJECTED') return 'Отклонен менеджером'; - if (props.status === 'MANAGER_BLOCKED') return 'Заблокирован'; + if (props.status === 'CLIENT_REJECTED' || props.status === 'MANAGER_REJECTED') return 'Отклонен'; + if (props.status === 'MANAGER_BLOCKED') return 'Пауза'; return props.status; }); @@ -20,7 +17,7 @@ const className = computed(() => { if (props.status === 'COMPLETED') return 'badge badge-success'; if (props.status === 'CLIENT_REJECTED' || props.status === 'MANAGER_REJECTED') return 'badge badge-error'; if (props.status === 'MANAGER_BLOCKED') return 'badge badge-warning'; - if (props.status === 'NEW') return 'badge badge-warning'; + if (props.status === 'NEW' || props.status === 'MANAGER_PROCESSING') return 'badge badge-warning'; return 'badge badge-info'; }); diff --git a/app/components/orders/OrderStatusTimelineCard.vue b/app/components/orders/OrderStatusTimelineCard.vue index 36bc266..2244d9c 100644 --- a/app/components/orders/OrderStatusTimelineCard.vue +++ b/app/components/orders/OrderStatusTimelineCard.vue @@ -5,11 +5,12 @@ import { getOrderStatusPresentation } from '~/composables/useOrderStatusPresenta const props = defineProps<{ status: string; createdAt: string | Date; + audience?: 'client' | 'manager'; }>(); const isExpanded = ref(false); -const presentation = computed(() => getOrderStatusPresentation(props.status, props.createdAt)); +const presentation = computed(() => getOrderStatusPresentation(props.status, props.createdAt, props.audience ?? 'client')); function itemClass(state: 'done' | 'current' | 'upcoming') { if (state === 'current') { diff --git a/app/composables/useOrderStatusPresentation.ts b/app/composables/useOrderStatusPresentation.ts index 45fd482..fcc81b6 100644 --- a/app/composables/useOrderStatusPresentation.ts +++ b/app/composables/useOrderStatusPresentation.ts @@ -65,25 +65,99 @@ function buildDates(createdAt: string | Date) { }; } -export function getOrderStatusPresentation(status: string, createdAt: string | Date): StatusPresentation { +function buildManagerStages(status: string, createdAt: string | Date): StatusPresentation { const dates = buildDates(createdAt); - if (status === 'CLIENT_REJECTED') { + 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: 'Согласование остановлено. Если нужно, заказ можно собрать заново с новыми условиями.', + 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: 'Заказ попал в обработку.', + label: 'Заказ создан', + note: 'Заказ принят в обработку.', dateLabel: formatDay(dates.created), state: 'done', }, { - code: 'CLIENT_REJECTED', - label: 'Клиент отказался от продолжения', - note: 'Текущий заказ закрыт без запуска в работу.', + code: status, + label: 'Заказ остановлен', + note: 'Дальнейшее исполнение не планируется.', dateLabel: formatDay(dates.approval), state: 'current', }, @@ -91,26 +165,22 @@ export function getOrderStatusPresentation(status: string, createdAt: string | D }; } - if (status === 'MANAGER_REJECTED' || status === 'MANAGER_BLOCKED') { + if (status === 'MANAGER_BLOCKED') { return { - title: status === 'MANAGER_BLOCKED' ? 'Заказ ждёт уточнения' : 'Заказ остановлен менеджером', - summary: status === 'MANAGER_BLOCKED' - ? 'Сейчас ждём уточнение параметров, после него заказ вернётся в работу.' - : 'Менеджер остановил обработку. При необходимости можно собрать новый заказ.', + title: 'Заказ ждёт уточнения', + summary: 'Менеджер уточняет параметры заказа перед расчётом.', stages: [ { code: 'NEW', - label: 'Заявка принята', - note: 'Заказ попал в обработку.', + label: 'Заказ создан', + note: 'Заказ принят в обработку.', dateLabel: formatDay(dates.created), state: 'done', }, { code: status, - label: status === 'MANAGER_BLOCKED' ? 'Нужно уточнение по заказу' : 'Обработка остановлена', - note: status === 'MANAGER_BLOCKED' - ? 'Менеджер запросил уточнение перед продолжением.' - : 'Текущий заказ завершён без запуска в производство.', + label: 'Уточняем детали', + note: 'После уточнения покажем плановые даты по исполнению.', dateLabel: formatDay(dates.approval), state: 'current', }, @@ -123,76 +193,62 @@ export function getOrderStatusPresentation(status: string, createdAt: string | D const stages: TimelineStage[] = [ { code: 'NEW', - label: 'Заявка принята', - note: 'Получили состав заказа и начали обработку.', + label: 'Заказ создан', + note: 'Приняли заказ и начали обработку.', dateLabel: formatDay(dates.created), state: currentIndex > 0 ? 'done' : 'current', }, - { - code: 'MANAGER_PROCESSING', - label: 'Готовим предложение', - note: 'Уточняем цену, сроки и условия доставки.', - dateLabel: formatDay(dates.offer), - state: currentIndex > 1 ? 'done' : currentIndex === 1 ? 'current' : 'upcoming', - }, - { - code: 'WAITING_DOUBLE_CONFIRM', - label: 'Ждём подтверждение', - note: 'Показываем согласованные условия и ждём финальное подтверждение.', - dateLabel: formatDay(dates.approval), - state: currentIndex > 2 ? 'done' : currentIndex === 2 ? 'current' : 'upcoming', - }, { code: 'CONFIRMED', - label: 'Заказ подтверждён', - note: `Планируем передать в производство и ориентируемся на выпуск ${formatDay(dates.production)}.`, + label: 'Производство', + note: 'Плановая дата запуска или выхода из производства.', dateLabel: formatDay(dates.production), - state: currentIndex > 3 ? 'done' : currentIndex === 3 ? 'current' : 'upcoming', + state: currentIndex > 3 ? 'done' : currentIndex >= 3 ? 'current' : 'upcoming', }, { code: 'IN_PROGRESS', - label: 'Заказ в работе', - note: `Сейчас идёт исполнение. Следующее ожидаемое обновление около ${formatDay(dates.shipment)}.`, + label: 'Отгрузка', + note: 'Плановая дата передачи в логистику.', dateLabel: formatDay(dates.shipment), - state: currentIndex > 4 ? 'done' : currentIndex === 4 ? 'current' : 'upcoming', + state: currentIndex > 4 ? 'done' : currentIndex >= 4 ? 'current' : 'upcoming', }, { code: 'COMPLETED', - label: 'Заказ завершён', - note: 'Исполнение закрыто, заказ готов к выдаче или уже доставлен.', + label: 'Доставка', + note: 'Плановая дата получения заказа.', dateLabel: formatDay(dates.delivered), - state: currentIndex === 5 ? 'current' : currentIndex > 5 ? 'done' : 'upcoming', + state: currentIndex >= 5 ? 'current' : 'upcoming', }, ]; if (status === 'NEW') { return { - title: 'Собираем предложение по заказу', - summary: `Сейчас уточняем стоимость и комплектацию. Следующее обновление ориентировочно ${formatDay(dates.offer)}.`, + title: 'Заказ создан', + summary: 'Менеджер рассчитывает стоимость и готовит план по исполнению.', stages, }; } if (status === 'MANAGER_PROCESSING') { return { - title: 'Готовим условия по заказу', - summary: `Менеджер собирает финальные условия. Ориентир по следующему апдейту ${formatDay(dates.approval)}.`, + title: 'Готовим предложение', + summary: 'Собираем итоговые условия и скоро покажем плановые даты.', stages, }; } if (status === 'WAITING_DOUBLE_CONFIRM') { return { - title: 'Ожидаем подтверждение условий', - summary: 'Нужно подтвердить текущие условия, после этого сразу двинем заказ дальше.', + title: 'Предложение готово', + summary: 'Стоимость и условия уже рассчитаны. Следующий этап после запуска: производство.', stages, }; } if (status === 'CONFIRMED') { return { - title: 'Заказ подтверждён', - summary: `Ожидаем выпуск с производства ориентировочно ${formatDay(dates.production)}.`, + title: 'Планируем производство', + summary: `Ориентируемся на производство ${formatDay(dates.production)}, затем отгрузку и доставку по плану.`, stages, }; } @@ -200,14 +256,24 @@ export function getOrderStatusPresentation(status: string, createdAt: string | D if (status === 'IN_PROGRESS') { return { title: 'Заказ в работе', - summary: `Сейчас заказ исполняется. Следующее ожидаемое обновление около ${formatDay(dates.shipment)}.`, + summary: `Производство идёт. Следующая плановая дата: отгрузка около ${formatDay(dates.shipment)}.`, stages, }; } return { - title: 'Заказ завершён', - summary: 'Работы по заказу завершены, история этапов сохранена ниже.', + 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); +} diff --git a/app/pages/client-orders/[id].vue b/app/pages/client-orders/[id].vue index 2b4573b..40dcb8f 100644 --- a/app/pages/client-orders/[id].vue +++ b/app/pages/client-orders/[id].vue @@ -1,9 +1,6 @@