BR 1.16: Вычисляемое меню ТТ
Зависит от:
Контекст
В YumaPos нет отдельной сущности “меню”. Кассир на POS видит отфильтрованный каталог — товары с ценами, без остановленных позиций, с модификаторами. Меню вычисляется на лету из каталога + прейскуранта + стоп-листа.
У нас уже есть все компоненты по отдельности: товары с категориями, прейскуранты per-ТТ, стоп-листы per-ТТ, модификаторы. Другой разработчик создал первую версию эндпоинта GET /internal/catalog/menu (каталог + цены). Нужно расширить: добавить фильтрацию по стоп-листам, модификаторы, привязку к конкретной ТТ.
Этот эндпоинт — основа для POS-кассы и клиентского приложения.
Требования
1. Формула вычисляемого меню
Меню ТТ =
Активные товары (status=active, deleted_at IS NULL)
× Активные категории (is_active=true)
+ Прейскурант ТТ (per-store или дефолтный)
+ Модификаторы (привязанные к товарам, с опциями и ценами)
− Стоп-лист ТТ (остановленные товары и категории)
= Финальное меню
2. Эндпоинт
Доработать существующий GET /internal/catalog/menu.
Параметры:
franchise_id(обязательный) — из JWT или service contextstore_id(обязательный) — для стоп-листа и привязки прейскурантаprice_list_id(опционально) — если не указан, используется дефолтный прейскурант франшизы
Auth: Service token (X-Service-Token) — internal endpoint для POS BFF.
3. Что возвращает
Категории:
- Только активные (
is_active=true) - Без остановленных в стоп-листе данной ТТ
- Иерархия (parent_id) для дерева
- Порядок (display_order)
Товары:
- Только активные (
status=active,deleted_at IS NULL) - Без остановленных в стоп-листе данной ТТ (напрямую или через категорию)
- С ценой из прейскуранта (или
nullесли нет) is_open_priceтовары — без цены (кассир вводит)- С фото (
image_url)
Модификаторы (для каждого товара):
- Привязанные группы (structural + free)
- Для каждой группы: id, name, binding_type, min/max (с учётом override)
- Опции: id, name, display_order
- Цена опции из прейскуранта (для free модификаторов)
4. Фильтрация стоп-листа
- Если товар в
product_stop_listдля данногоstore_id→ исключить из меню - Если категория в
category_stop_listдля данногоstore_id→ исключить все товары этой категории - Товар может быть остановлен напрямую И через категорию — оба случая исключают
5. Разрешение прейскуранта
Порядок:
- Если
price_list_idуказан → использовать его - Если не указан → найти дефолтный прейскурант франшизы (
is_default=true) - Если нет дефолтного → товары без цен (
price=null)
Бизнес-правила
- Per-store — меню вычисляется для конкретной ТТ (стоп-лист + прейскурант)
- Без кэша на MVP — вычисляется при каждом запросе. Redis кэш — Phase 2
- Модификаторы structural — не имеют цены в прейскуранте (цена = цена товара). Опции возвращаются без цены.
- Модификаторы free — опции имеют цену в прейскуранте (доплата за добавку)
- Пустое меню — если нет активных товаров или все остановлены → пустой массив, не ошибка
- Backward compatible — если
store_idне указан, эндпоинт работает как раньше (без стоп-листа)
Ролевой доступ
| Потребитель | Как обращается | Auth |
|---|---|---|
| POS BFF | GET /internal/catalog/menu | X-Service-Token |
| Customer BFF (будущее) | То же или отдельный endpoint | X-Service-Token |
| Admin BFF | Не использует (у админки свои эндпоинты) | — |
Затронутые сервисы
| Сервис | Что меняется |
|---|---|
| Catalog Service | Доработка InternalCatalogMenuController: добавить store_id, стоп-лист фильтрацию, модификаторы в ответ |
Что НЕ входит
- Redis кэш меню — Phase 2
- Часы доступности (per-category/product time restrictions) — Phase 2
- Per-store привязка товаров (все товары доступны всем ТТ, ограничение через стоп-лист) — Phase 2
- Kafka-событие при изменении меню (инвалидация кэша) — Phase 2
- Customer-facing API (публичный, без auth) — отдельная BR
Ссылки
- Каталог — формула вычисляемого меню
- Стоп-листы
- Модификаторы
- Catalog Service API
- YumaPos: товары + категории + стоп-листы = то что видит кассир