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 context
  • store_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. Разрешение прейскуранта

Порядок:

  1. Если price_list_id указан → использовать его
  2. Если не указан → найти дефолтный прейскурант франшизы (is_default=true)
  3. Если нет дефолтного → товары без цен (price=null)

Бизнес-правила

  1. Per-store — меню вычисляется для конкретной ТТ (стоп-лист + прейскурант)
  2. Без кэша на MVP — вычисляется при каждом запросе. Redis кэш — Phase 2
  3. Модификаторы structural — не имеют цены в прейскуранте (цена = цена товара). Опции возвращаются без цены.
  4. Модификаторы free — опции имеют цену в прейскуранте (доплата за добавку)
  5. Пустое меню — если нет активных товаров или все остановлены → пустой массив, не ошибка
  6. Backward compatible — если store_id не указан, эндпоинт работает как раньше (без стоп-листа)

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

ПотребительКак обращаетсяAuth
POS BFFGET /internal/catalog/menuX-Service-Token
Customer BFF (будущее)То же или отдельный endpointX-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

Ссылки