BR 4.1 — External Menu Builder и монитор кассы
Статус — драфт
Требования сформированы. Не начинать реализацию — сначала проходит
/process-br 4.1через все слои workflow.
Связь с BR 4.2
BR 4.1 — это базовый конструктор + канал «монитор/JSON». BR 4.2 (отдельная) добавит к нему override модификаторов и push в агрегаторы (Yandex.Eda, Коала). Их разделение оправдано тем что 4.1 само-достаточно — даёт видимую ценность (рекламный монитор в зале) без зависимости от внешних API.
Контекст
В ERP существует базовое меню = вычисляемая сущность (каталог × прейскурант × стоп-листы). Это работает для внутренней кухни (POS, KDS, склад), но не покрывает внешние каналы:
- Монитор над кассой (рекламный баннер «Digital Menu Board») — вешается в зале, должен показывать товары с ценами/фотками для гостей
- Универсальный JSON-экспорт — для интеграций с любыми третьими системами (1C, ERP-партнёра, BI)
- (в BR 4.2) Push в Yandex.Eda / Коала — там нужны cвои особенности: завышенная цена под комиссию, переименование, иерархическая структура модификаторов
Внешние каналы требуют:
- Независимого набора товаров (не все позиции каталога идут в монитор / в Яндекс)
- Override полей (имя, описание, цена) per-канал
- Возможности скрывать без удаления
- Произвольной группировки и порядка
- Жёсткой связи с оригинальным товаром в каталоге — чтобы при правках цены/имени каталог-первоисточник продолжал работать как для кассы, так и для остальных каналов
Цель
Дать владельцу франшизы конструктор для создания сколько угодно внешних меню разных каналов из товаров каталога, с переопределением полей и автоматической работой stop-list.
В BR 4.1 покрываем 2 канала:
tv_screen— рекламный монитор в зале (Live URL для Chromium kiosk + Offline ZIP экспорт)json— универсальный JSON-экспорт (для будущих интеграций)
Другие каналы (yandex_eda, koala) появятся в BR 4.2 как расширение существующей механики.
§1. Скоуп
1.1. Что входит
Сущности:
external_menu— заголовок меню (имя, канал, ТТ-привязка, статус)external_menu_item— товар в меню с override-полями (имя, описание, цена, видимость, порядок)external_menu_category— категории внутри меню (можно переименовать, переупорядочить)
Override per-товар (хранится только если значение задано, иначе fallback в каталог):
- Имя товара (
override_name) - Описание (
override_description) - Цена (
override_price) — приоритет:override > прейскурант > catalog.price - Видимость (
visible: bool) — можно скрыть без удаления - Категория в этом меню (можно положить в категорию отличную от каталога)
- Порядок отображения
Stop-list:
- Товары попавшие в стоп-лист каталога автоматически скрыты при рендере. Возможность раскрытия не делаем — упрощение.
Каналы рендера:
tv_screen— HTML-страница на нашем сервере с 2-3 готовыми шаблонами (классическая сетка карточек, слайдер а-ля McDonalds, список с большой ценой)json— экспорт всего меню в универсальный JSON
Кол-во меню: без ограничений. Один товар может быть в N меню одновременно.
1.2. Что НЕ входит (отложено)
- ❌ Override модификаторов (group + option) — отдельная BR 4.2
- ❌ Push в Yandex.Eda через Aggregator Service — BR 4.2
- ❌ Push в Коалу — BR 4.2
- ❌ Сезонность / расписание показа (завтраки 7-11, обеды 11-15) — P1+
- ❌ Override фотографий товара — P1+ (фотки из каталога 1:1)
- ❌ Версионирование меню (история опубликованных версий) — P1+
- ❌ Маркетинговые баннеры / акции на мониторе — P1+
§2. Сценарии использования
2.1. Создание меню для монитора над кассой
- Владелец заходит в админку → раздел «Внешние меню» → кнопка «Создать»
- Выбирает канал «Монитор кассы», задаёт имя «Бар — основной экран», привязывает к ТТ
- На странице редактора видит каталог товаров → drag-drop в правую панель «Товары меню»
- Группирует по категориям (можно переименовать категории, перетянуть в нужный порядок)
- По желанию — открывает товар → переопределяет цену (например, в зале дороже на 10%) или название
- Нажимает «Опубликовать»
- Получает уникальный URL:
https://menu.nirbi.ru/r/abc123— это URL для Chromium kiosk-mode на мини-ПК монитора - (Опционально) Скачивает ZIP-архив для оффлайн-копии на флешке
2.2. Универсальный JSON-экспорт
Аналогично, но канал = json. По публикации владелец получает endpoint GET /api/v1/external-menus/{id}/export.json (с авторизацией) который отдаёт меню в JSON. Это для будущих интеграций (партнёров, BI-аналитики).
2.3. Stop-list работает прозрачно
- Менеджер ТТ в стоп-листе ставит «Капучино» как недоступный
- Через секунды этот товар исчезает с монитора в зале (live-обновление через WebSocket / SSE)
- Когда возвращают доступность — товар появляется обратно
- Владелец ничего не делает в External Menu — всё автоматом
2.4. Удалили товар в каталоге
- Товар из каталога удалён
- В external_menu_item проставляется
status=orphan - В админке банер «1 товар удалён в каталоге, удалить из этого меню?»
- При рендере на монитор orphan-товары не показываются
§3. Сущности (бизнес-уровень)
External Menu
| Поле | Обязательность | Описание |
|---|---|---|
| Имя | Обязательно | Для админки. «Монитор кассы — Бар», «Универсальный JSON для 1С» |
| Канал | Обязательно | tv_screen / json (в BR 4.2 + yandex_eda, koala) |
| Привязка к ТТ | Опционально | NULL — на всю сеть; иначе конкретная ТТ |
| Статус | Обязательно | draft / published |
| Slug (для tv_screen) | Авто | URL-frienly идентификатор для render URL |
| Шаблон рендера (только tv_screen) | Обязательно для tv_screen | grid / slider / list |
| Дата создания / обновления | Авто |
External Menu Category
| Поле | Обязательность | Описание |
|---|---|---|
| Имя | Обязательно | Можно отличаться от каталог-категории |
| Привязка к каталог-категории | Опционально | Если задана — берётся имя из каталога если override пуст. Можно создать кастомную категорию специально для этого меню |
| Порядок отображения | Обязательно | |
| Иконка | Опционально | Для шаблонов с иконками |
External Menu Item
| Поле | Обязательность | Описание |
|---|---|---|
| Связь с товаром каталога | Обязательно ★ | FK на products.id — якорь, не nullable. Если товар удалён → status=orphan |
| Категория в этом меню | Обязательно | FK на external_menu_category |
| Override имени | Опционально | Если NULL — берём product.name |
| Override описания | Опционально | Если NULL — берём product.description |
| Override цены | Опционально | Если NULL — берём прейскурант ?? product.price |
| Видимость | Обязательно | true/false. Можно скрыть без удаления |
| Порядок | Обязательно | display_order в категории |
| Статус | Обязательно | ok / orphan (если оригинал удалён) |
§4. Бизнес-правила
- Связь с каталогом обязательна — нельзя создать external_menu_item «с нуля». Только по существующему
product_id. - Один товар может быть в N меню — никаких UNIQUE на product_id.
- Iерархия цены:
override_price > price_list_price > product.price. Гдеprice_list_price— цена из прейскуранта для ТТ к которой привязано меню (если ТТ задана). - Stop-list скрывает автоматически — при рендере фильтруем по текущему стопу ТТ. Если у меню
store_id=NULL(на всю сеть) — стоп-листом не управляется (показываем всё что доступно где-либо). - Удаление товара в каталоге — каскадно меняет
status=orphanво всех external_menu_item. Не удаляем физически — для аудита и возможности восстановления. - Восстановление товара — если в каталоге удалённый товар восстановили, владелец должен явно нажать «Восстановить» в external_menu_item. Авто-восстановления нет.
- Категории — могут совпадать с каталог-категорией (тогда наследуется имя), либо быть кастомными (например «Хиты продаж» — собранные руками для монитора).
- Slug для tv_screen — генерируется автоматом (например
menu-{external_menu_id_short}), уникален глобально. Можно изменить вручную. - Live-обновление монитора — при изменении меню (override, видимость, добавление/удаление товара, попадание в стоп-лист) монитор должен подхватить изменение в течение 30 сек.
- Offline ZIP — содержит index.html + assets (картинки, шрифты). Открывается локально без интернета (двойной клик на index.html). На момент скачивания — снимок состояния.
§5. Каналы рендера
5.1. tv_screen — Монитор над кассой
Целевое железо: мини-ПК или встроенный SoC телевизора в Chromium kiosk-mode. Подключение chrome --kiosk https://menu.nirbi.ru/r/{slug} через автостарт Windows.
Шаблоны (минимум 2):
- Grid — классическая сетка карточек (фото + имя + цена). Подходит для кофеен, бургерных
- Slider — крупный фокус на одном товаре, авто-перелистывание. Подходит для рекламного режима
- (опционально) List — длинный список с большой ценой справа. Для табло «бизнес-ланч»
Адаптивность:
- 16:9 1080p (основной)
- 16:9 4K
- 9:16 вертикальный (на стене у входа)
CSS-clamp + media queries автоматически масштабируют.
Live-обновление:
- WebSocket / SSE подключение от страницы к серверу
- Когда меню изменилось — сервер шлёт
menu_updated→ страница плавно перерисовывает (без F5) - Polling fallback каждые 30 сек если WebSocket упал
Offline ZIP:
- Кнопка «Скачать офлайн-копию» в админке
- Архив:
index.html + style.css + assets/{images,fonts} - Без auto-refresh (snapshot на момент скачивания)
- Для редких оффлайн-точек
5.2. json — Универсальный экспорт
- Endpoint:
GET /api/v1/external-menus/{id}/export.json(Bearer JWT) - Структура:
{ "menu_id": "...", "name": "...", "channel": "json", "store_id": "...", "generated_at": "...", "categories": [ { "id": "...", "name": "Кофе", "items": [ { "product_id": "...", "name": "...", "description": "...", "price": 250.00, "image_url": "...", "in_stop_list": false } ] } ] } - Stop-list применён: товары в стопе либо отсутствуют, либо
in_stop_list: true(на выбор)
§6. Ролевая модель
| Действие | Владелец франшизы | Владелец партнёра | Менеджер ТТ | Кассир |
|---|---|---|---|---|
| Видеть раздел «Внешние меню» | ✅ | ✅ (только свои ЮЛ) | ❌ | ❌ |
| Создать меню | ✅ | ✅ (только свои ТТ) | ❌ | ❌ |
| Редактировать меню | ✅ | ✅ (свои) | ❌ | ❌ |
| Опубликовать / снять с публикации | ✅ | ✅ (свои) | ❌ | ❌ |
| Удалить меню | ✅ | ✅ (свои) | ❌ | ❌ |
| Скачать ZIP | ✅ | ✅ (свои) | ❌ | ❌ |
| Открыть live URL | публично (URL знаешь — открыл) | — | — | — |
Permissions: новый external_menus.read + external_menus.edit. Аналогично существующим menu.read/edit но отдельные потому что управление меню для внешних каналов — другая операция (затрагивает имидж, цены для гостей).
§7. Открытые вопросы
- Монитор kiosk-mode конфигурация — кто настраивает Chromium на старте Windows? Прилагаем ли инструкцию владельцу или это его IT-ответственность?
- Несколько мониторов в одной ТТ (бар + зал) — это разные external_menu или один с разными «зонами»? Дефолт: разные external_menu (так гибче).
- Обновление цены при изменении прейскуранта — если override не задан, при пересчёте прейскуранта меню должно обновиться live на мониторе. Подтвердить что Catalog Service публикует событие при изменении прейскуранта.
- Индикация в админке «Этот товар в N меню» — чтобы владелец понимал последствия удаления/правки в каталоге. Полезно, но не критично.
- JSON export — публичный endpoint или с auth? Голосую за JWT.
- Slug для tv_screen — short URL — нужен ли красивый поддомен
menu.nirbi.ruили путьhttps://erp-test.nirbi.ru/r/{slug}ок? Поддомен потребует DNS + nginx + SSL. Дефолт MVP — путь.
§8. Фазинг
| Фаза | Что | Срок |
|---|---|---|
| A. Backend — сущности и API | 3 миграции (external_menu, external_menu_category, external_menu_item) + CRUD endpoints в Catalog Service + JSON export | Спринт 1 |
| B. Frontend — конструктор | Раздел /external-menus в админке + список + редактор с drag-drop + override-формы | Спринт 1-2 (параллельно) |
| C. Render для монитора | Endpoint /r/{slug} с шаблонами + WebSocket live-update + offline ZIP экспорт | Спринт 2 |
| D. Smoke test и pilot | Установить на реальный монитор в demo-coffee → 1 неделя реального использования | Спринт 3 |
§9. План работ — файлы под создание/правку
CREATE
-
08-Specs/Админка Франшизы/Внешние меню.md— бизнес-спека -
09-Frontend Specs/Админка Франшизы/Внешние меню — Список.md -
09-Frontend Specs/Админка Франшизы/Внешние меню — Конструктор.md -
09-Frontend Specs/Админка Франшизы/Внешние меню — Шаблоны монитора.md -
07-Tasks/Decomposition/4.1 External Menu/Overview.md -
07-Tasks/Decomposition/4.1 External Menu/Catalog Service.md -
07-Tasks/Decomposition/4.1 External Menu/Admin BFF.md -
07-Tasks/Decomposition/4.1 External Menu/Admin Franchise.md -
07-Tasks/Decomposition/4.1 External Menu/Menu Renderer.md(может быть отдельным мини-сервисом или endpoint в Catalog BFF — решим в декомпозиции)
EDIT
-
03-Services/Catalog Service/API.md— новые endpoints (/external-menus,/external-menus/{id}/items,/external-menus/{id}/export.json,/r/{slug}) -
03-Services/Catalog Service/Data Model.md— 3 новые таблицы + ER -
03-Services/Catalog Service/Events.md— топикexternal_menu.updated -
03-Services/Catalog Service/Overview.md— функция «External Menu Builder» в зоне ответственности -
08-Specs/Админка Франшизы/Overview.md— добавить модуль «Внешние меню» в навигацию -
08-Specs/Админка Франшизы/Роли.md— добавить permissionsexternal_menus.read/external_menus.edit
CODE (после verify)
-
erp-catalog-service— миграция, entities, repositories, service, controllers, JSON export, WebSocket gateway, render endpoint -
erp-admin/bff— proxy-routes -
erp-admin/web— раздел «Внешние меню» (список, редактор, превью) - (опционально) отдельный
menu-renderer-service— если решим вынести render в свой контейнер
§10. Verification
После реализации (на erp-test.nirbi.ru + demo-coffee tenant):
- Создать меню «Бар основной» канал
tv_screen→ опубликовать → получить URL - Открыть URL в Chrome kiosk-mode на тестовой машине → должен отрисоваться шаблон с товарами
- Изменить цену в каталоге товара → монитор обновляется через ≤30 сек (live)
- Поставить товар в стоп-лист → исчезает с монитора
- Снять стоп → возвращается
- Скачать offline ZIP → распаковать на другой машине → открыть
index.htmlбез интернета → отображается snapshot - JSON export → структура соответствует §5.2
- Удалить товар в каталоге → в админке external_menu_item появляется badge «orphan» → на мониторе товар скрыт
§11. Что входит в BR 4.2 (на будущее)
Чтобы зафиксировать границу скоупа:
external_menu_modifier_group+external_menu_modifier_option— override модификаторов- Канал
yandex_eda— push в Yandex через расширениеAggregator.MenuSnapshotService - Канал
koala— push в Коалу - Listener
external_menu.updatedв Aggregator → инвалидацияmenu_snapshots - Поле
binding.external_menu_idв Aggregator - Расширение
YandexEdaConnectorс поддержкой override модификаторов
BR 4.2 запускается после того как BR 4.1 в проде и работает.
§12. Ссылки
- BR 3.4 — паттерн «expand товаров с модификаторами», переиспользуем для рендера меню (если в каталоге сложный товар)
- Catalog Service
- Каталог — спека
- Прейскуранты — спека
- Стоп-листы — спека
- Агрегаторы доставки — для понимания текущего Yandex flow (не трогаем в этой BR)
- Aggregator Service — для контекста (используется в BR 4.2)