BR 4.2 — External Menu × агрегаторы + override модификаторов

Backlog — не работаем

Это черновик-памятка. Запускаем работу только после того как BR 4.1 в проде и стабильна. До тех пор просто копим идеи и вопросы здесь.

Зачем

BR 4.1 даёт конструктор + монитор. Этого мало для агрегаторов — Yandex.Eda и Коала требуют:

  1. Завышенную цену под комиссию агрегатора (25-35% сверху) — есть в 4.1 через override_price. ✅
  2. Иерархичные модификаторы с возможностью переименовать — например, в Yandex показать «Цельное молоко» вместо нашего «Молоко обычное», или скрыть «Овсяное» которое мы не доставляем. Этого нет в 4.1.
  3. Push в их API при каждом изменении меню. Этого нет в 4.1 (там только pull через JSON и tv_screen рендер).

Плюс есть тех. долг в Aggregator Service: listener catalog.product.updated для инвалидации menu_snapshots не реализован. Заодно закроем.

§1. Что входит

1.1. Override модификаторов

Расширяем external_menu двумя таблицами:

  • external_menu_modifier_group

    • external_menu_item_id (FK)
    • original_modifier_group_id ★ (FK на каталог, не nullable)
    • override_name — переименовать «Молоко» → «Тип молока»
    • override_min / override_max — изменить обязательность выбора
    • visible — можно скрыть всю группу для этого канала
  • external_menu_modifier_option

    • external_menu_modifier_group_id (FK)
    • original_modifier_option_id ★ (FK на каталог)
    • override_name — «Овсяное» → «Овсяное (без сахара)»
    • override_price_delta — другая доплата для канала
    • visible

Принцип якоря работает на всех уровнях — orphan-обработка для group и option как в 4.1 для items.

UI: при раскрытии товара в редакторе появляется блок «Модификаторы» с возможностью per-group / per-option overrides. Свернуто по дефолту (вид как в 4.1) — раскрывают только если что-то меняют.

1.2. Канал yandex_eda

  • Поле binding.external_menu_id (UUID, NULL) в Aggregator Service
  • При rebuild() в MenuSnapshotService:
    • Если NULL → текущий flow (вычисляемое меню каталога) — не сломан
    • Если задан → HTTP к Catalog Service GET /internal/catalog/external-menus/{id}/resolve?store_id=Y → возвращает уже резолвенный CatalogMenu с применёнными overrides
  • YandexEdaConnector ничего не меняет — остаётся iерархичный JSON формат (group → options c min/max)

1.3. Канал koala

Аналогично yandex_eda, но через свой connector. Конкретные поля API Коалы — открытый вопрос §3.

1.4. Listener external_menu.updated

В Aggregator Service:

  • Подписаться на Kafka-топик external_menu.updated (публикуется в Catalog Service в 4.1)
  • При получении event’а — найти все bindings WHERE external_menu_id = X, инвалидировать их menu_snapshots
  • Заодно реализовать catalog.product.updated listener (текущий тех. долг — TODO в Events.md)

1.5. Логи push’ей

При каждом push в агрегатор — запись в aggregator_push_logs (или расширить существующий aggregator_logs):

  • timestamp, binding_id, response_code, payload_hash
  • Чтобы потом разбираться «почему Yandex не подхватил новую цену»

§2. Что НЕ входит (даже сюда)

Эти штуки откладываем явно, чтобы 4.2 не разрослась:

  • Override фотографий per-канал — в 4.1 единые с каталога, оставляем так. Если Yandex попросит специфичные фотки — отдельная BR
  • Версионирование external_menu (история опубликованных) — отдельная BR если потребуется откат
  • A/B тесты меню в Yandex — нет
  • Сезонность / расписание (завтраки 7-11) — отдельная BR
  • Маркетинговые баннеры / акции — отдельная BR
  • Канал-специфичные поля (allergies, vegan-tag, калорийность для Yandex) — может быть P3 если у Yandex это обязательно

§3. Открытые вопросы — узнать когда возьмём в работу

  1. Yandex.Eda API requirements — какие поля обязательны (allergies, нутриенты, теги, размеры фотографий)? Сейчас у нас этих полей в каталоге нет. Если обязательны — потребуется расширение products или channel_specific JSONB на external_menu_item.
  2. Yandex API rate limits — как часто можно пушить меню? Возможно есть тротлинг и при частых правках в external_menu мы упрёмся.
  3. Yandex stop-list webhook — у них есть push-механизм для оперативного скрытия товара? Или только наш pull через GET /stoplist?
  4. API Коалы — где документация? Какой формат меню? Push или pull? Какие поля? Без этого 4.2 не запускается.
  5. product_external_mappings — текущая планируемая таблица в Catalog Service для маппинга product_id → external_sku per-provider. Не пересекается ли с external_menu_item? Скорее всего нет (это про синхронизацию заказов обратно), но проверить.
  6. MenuSnapshotService TTL — сейчас жёсткие 1ч. С listener’ом мы инвалидируем сразу — нужно ли всё ещё держать TTL как backup или можно убрать?
  7. Несколько external_menu для одного binding — может ли binding ссылаться сразу на несколько (например, основное меню + сезонное)? Дефолт: один binding = один external_menu. Если потом надо несколько — отдельная BR.
  8. Override модификатора + push в Yandex — Yandex принимает иерархично, значит нам не нужно «разворачивать» в плоский список как для PayKeeper. Прямой маппинг group/option работает.
  9. Конфликт UI — текущая планируемая админка агрегаторов (binding settings) vs наш конструктор external_menu. После 4.1 владелец редактирует меню в /external-menus. Если в админке агрегатора есть отдельная вкладка с переопределениями — её можно будет убрать или сделать read-only ссылкой на external_menu. Уточнить во время декомпозиции 4.2.

§4. Зависимости

  • BR 4.1 в проде — сущности external_menu, external_menu_item, UI конструктора, событие external_menu.updated уже работают.
  • Yandex sandbox / тестовый stand — нужен доступ к ним для smoke-тестов push’ей. Сейчас работает pull через наш endpoint, для push надо OAuth-токены и разрешение на push API.
  • API Коалы — документация и тестовый аккаунт. Это внешний блокер Коалы.

§5. Файлы под create/edit (предварительно)

CREATE

  • 08-Specs/Интеграции/External Menu × Aggregators.md — бизнес-спека интеграции
  • 09-Frontend Specs/Админка Франшизы/Внешние меню — Конструктор модификаторов.md — расширение фронт-спеки 4.1
  • 07-Tasks/Decomposition/4.2 External Menu Aggregators/Overview.md
  • 07-Tasks/Decomposition/4.2 External Menu Aggregators/Catalog Service.md
  • 07-Tasks/Decomposition/4.2 External Menu Aggregators/Aggregator Service.md
  • 07-Tasks/Decomposition/4.2 External Menu Aggregators/Admin Franchise.md

EDIT

  • 03-Services/Catalog Service/Data Model.md — 2 новые таблицы (modifier_group + option)
  • 03-Services/Catalog Service/API.md — расширить endpoint /external-menus/{id}/resolve форматом с модификаторами
  • 03-Services/Aggregator Service/Data Model.md — поле binding.external_menu_id
  • 03-Services/Aggregator Service/API.md — без изменений (pull-flow тот же)
  • 03-Services/Aggregator Service/Events.md — listener external_menu.updated и catalog.product.updated
  • 03-Services/Aggregator Service/Overview.md — расширение зоны ответственности
  • 08-Specs/Интеграции/PayKeeper.md — никак не трогаем (для PK уже есть свой expand-flow в BR 3.4)
  • 08-Specs/Агрегаторы доставки/Yandex.Eda Adapter.md — добавить блок про external_menu source

CODE

  • erp-catalog-service — миграции, entities (modifier_group, modifier_option), расширение ExternalMenuResolveService, расширение controller
  • erp-aggregator-service — миграция (binding.external_menu_id), KafkaListener для external_menu.updated, расширение MenuSnapshotService.rebuild()
  • erp-admin/web — раскрытие товара в редакторе → форма модификатор-overrides

§6. Ссылки

  • BR 4.1 — родительская
  • BR 3.4 — паттерн expand для PayKeeper (используется в логике резолва меню при разворачивании структурных модификаторов в плоский список — для канала paykeeper если когда-нибудь подружим External Menu с PayKeeper тоже)
  • Aggregator Service
  • Агрегаторы доставки
  • Aggregator · Events — текущий TODO про catalog.product.updated