Заказы

Бизнес-спека

Routes

RouteСтраница
/orders/activeАктивные заказы
/orders/historyИстория заказов
/orders/:idКарточка заказа

Группа “Заказы” в навигации (после “Склад”), видна всем ролям кроме Кассира.

Пункты:

  • Активные (/orders/active)
  • История (/orders/history)

1. Активные заказы (ActiveOrdersPage)

Layout

  • Header: “Активные заказы”
  • Store selector — дропдаун выбора ТТ (как на стоп-листах)
  • Таблица активных заказов
  • Auto-refresh каждые 30 секунд

Store selector

РольПоведение
ФраншизаДропдаун со всеми ТТ
ФранчайзиДропдаун только с собственными ТТ
Менеджер ТТАвтовыбор единственной ТТ, дропдаун disabled

API: GET /api/v1/admin/stores выбор ТТ GET /api/v1/admin/orders?store_id=...&status=new,accepted,ready,handed_over,in_delivery,delivered

Активные статусы после BR 2.5 — все кроме closed и cancelled.

Таблица

КолонкаДанныеФормат
order_number”001”
Времяcreated_atОтносительное: “5 мин назад”, “1 ч назад”
Типorder_typeНавынос / Доставка / В зале (dine_in/takeaway/delivery)
Каналchannel + external_providerКасса / Я.Еда / Маркет Деливери
СтатусstatusBadge: new=серый «Новый», in_progress=жёлтый «В работе», accepted=синий «Готовится», ready=зелёный «Готов», handed_over=голубой «У курьера», in_delivery=синий «В пути», delivered=фиолетовый «Доставлен», rejected=красный (BR 2.5 — добавлены in_delivery, delivered)
Суммаtotal”630 ₽“
Оплатаpaid_atBadge: paid=зелёный “Оплачен”, unpaid=серый “Не оплачен”
Кассирcreated_byИмя сотрудника (lookup)
Курьерcourier_idИмя сотрудника-курьера (lookup). Показывается только для order_type=delivery (BR 2.5)

Действия

  • Клик по строке переход на /orders/:id (OrderDetailPage)
  • Для агрегаторских (channel=aggregator) заказов на карточке доступен flow-экшены: Принять (/acceptaccepted), Готов (/readyready), Отдать (/hand-overhanded_over), Отклонить (/rejectrejected).

Состояния

  • Нет выбранной ТТ: “Выберите торговую точку”
  • Нет данных: “Нет активных заказов”
  • Загрузка: skeleton-строки

Auto-refresh

  • Таймер 30 секунд — повторный запрос списка
  • Индикатор: “Обновлено: HH:mm:ss” в правом верхнем углу

2. История заказов (OrderHistoryPage)

Layout

  • Header: “История заказов”
  • Store selector — дропдаун выбора ТТ (аналогично ActiveOrdersPage)
  • Фильтры: диапазон дат, статус
  • Таблица заказов
  • Пагинация

Фильтры

ФильтрТипОписание
ТТDropdownВыбор торговой точки (по роли)
Дата отDate pickerdate_from
Дата доDate pickerdate_to
СтатусDropdownclosed, cancelled, все

API: GET /api/v1/admin/orders?store_id=...&date_from=...&date_to=...&status=...&page=...&per_page=20

Таблица

КолонкаДанныеФормат
order_number”001”
Датаcreated_at”06.04.2026 14:30”
Типorder_type”Навынос”
СтатусstatusBadge: closed=синий «Закрыт», cancelled=красный «Отменён». Для promoting legacy показываются также finalized-состояния delivery (delivered для отфильтрованной истории доставки) (BR 2.5)
Суммаtotal”630 ₽“
Оплатаpayment_methodBadge: cash=серый “Наличные”, card=синий “Карта”, qr=фиолетовый “QR”, mixed=оранжевый “Смешанная”
Кассирcreated_byИмя сотрудника

Действия

  • Клик по строке переход на /orders/:id (OrderDetailPage)

Пагинация

  • 20 записей на страницу
  • Кнопки: ”< Назад”, “Далее >”, номер текущей страницы

Состояния

  • Нет выбранной ТТ: “Выберите торговую точку”
  • Нет данных: “Нет заказов за выбранный период”
  • Загрузка: skeleton-строки

3. Карточка заказа (OrderDetailPage)

Layout

  • Breadcrumb: Заказы > Активные/История > #001
  • Header: номер заказа, статус badge, название ТТ, дата, кассир
  • Блок «Клиент» (если прикреплён) (Добавлено в BR 3.1)
  • Таблица позиций
  • Итого
  • Секция оплаты (если оплачен)

Блок «Клиент»

(Добавлено в BR 3.1)

Показывается если customer_id != null. Слева от блока позиций.

ПолеДанныеФормат
Имяcustomer.first_name + last_nameКликабельное — переход в /customers/{customer_id} (карточка клиента)
Телефонcustomer.phone+7 (999) 123-45-67
Группыcustomer.groups[].nameChips (max 3 видимых)

Если customer_id == null — блок не отображается (заказ анонимный).

ПолеДанныеФормат
Номерorder_number”Заказ #001”
СтатусstatusBadge (цвет по статусу)
ТТstore nameНазвание ТТ (lookup)
Датаcreated_at”06.04.2026 14:30”
Кассирcreated_byИмя сотрудника
Типorder_type”Навынос”

Таблица позиций

КолонкаДанныеФормат
Товарproduct_name”Шаурма классическая”
Кол-воquantity”2”
Ценаunit_price”250 ₽“
Модификаторыmodifiers”Соус: Чесночный (+30 ₽)” — каждый модификатор с новой строки
Стоимостьtotal_price”500 ₽“
Комментарийnotes”без лука” (курсив, мелкий шрифт)

Итого

  • Итого: total — жирный, справа внизу таблицы

Секция оплаты

Показывается если paid_at != null.

ПолеДанныеФормат
Способ оплатыpayment_method”Наличные” / “Карта” / “QR” / “Смешанная”
Суммаpaid_amount”630 ₽“
RRNrrnПоказывается если есть (для карты/QR)
Картаcard_last4”**** 1234” (если есть)
Оплаченpaid_at”06.04.2026 14:35”

Секция отмены

Показывается если status == cancelled.

ПолеДанныеФормат
Причина отменыcancel_reasonТекст
Отменёнcancelled_at”06.04.2026 14:40”

Секция возвратов (BR 3.3)

(Добавлено в BR 3.3)

Показывается если у заказа есть хотя бы один RefundRecord. Список возвратов с полями:

ПолеДанныеФормат
Суммаamount”500 ₽“
СтатусstatusBadge: started серый «В процессе», done зелёный «Выполнен», failed красный «Ошибка»
ПричинаreasonТекст
Инициированinitiated_by, created_at, cashier_id → имя”23.04.2026 15:00, Иванов И.И.”
Ошибка (если failed)error_messageТекст от PK

Для статуса started — автообновление полем раз в 10 сек (через poll GET /api/v1/admin/orders/:id) пока не перейдёт в финальный (done/failed).

Кнопка «Вернуть» (админский возврат через PK)

Видна в шапке карточки при:

  • order.status IN (closed, delivered) или (ready/handed_over/in_delivery с paid_at != null)
  • Permission orders.refund
  • orders.pk_payment_id != null (если null — legacy-возврат через старый flow без PK)

Клик → модалка:

  1. Radio: «Полный возврат» / «Частичный возврат»
  2. Если частичный — список позиций с чекбоксами и количеством (max = оригинал)
  3. Поле «Причина» (textarea, обязательно)
  4. Итого к возврату (автосумма)
  5. Кнопка «Вернуть»

API: POST /api/v1/admin/orders/:id/refund → Order Service публикует order.refund_requested → Paykeeper Adapter вызывает PK → через N минут PK шлёт webhook → статус переходит в done/failed.

Детали flow — Интеграция PayKeeper.

Секция фискальных данных (BR 3.3)

Показывается если у заказа fiscal_data != null (заполняется после paykeeper.receipt.fiscalized).

ПолеДанныеФормат
Фискальный признак (ФПД)fiscal_data.fpdМоноширинный
Номер ФДfiscal_data.fnd
Номер ФНfiscal_data.fn
РН ККТfiscal_data.rnkkt
Номер сменыfiscal_data.shift_number
Номер чекаfiscal_data.receipt_number
Чекpk_fop_receipt_keyСсылка «Открыть чек» → URL формата https://{pk_host}/receipt/{pk_payment_id}/{fop_receipt_key}

Если fiscal_failed=true — красный бейдж «Фискализация не удалась» + ссылка на подробности в журнале PK.

Режим

Read-only

Большинство данных заказа в админке — только просмотр. Единственные действия из админки: возврат (через PK, см. выше) и просмотр фискальных данных. Управление статусами заказа происходит через POS.

API: GET /api/v1/admin/orders/:id, POST /api/v1/admin/orders/:id/refund


Ролевой доступ

РольДоступ
ФраншизаПросмотр заказов всех ТТ
ФранчайзиПросмотр заказов своих ТТ
Менеджер ТТПросмотр заказов своей ТТ
КассирНет доступа (раздел скрыт в админке)

Ссылки