Агрегаторы доставки — бизнес-спека
Расширено в BR 2.5
Сервис Aggregator Service (владелец модуля) в BR 2.5 расширен — теперь обрабатывает не только агрегаторов (Яндекс.Еда и т.п.), но и внешние POS-системы (KOALa и подобные локальные фронты) через отдельный механизм webhook-подписок. Отдельная спека: Webhook-подписки.
Что это
Модуль ERP, отвечающий за интеграцию с внешними системами. Изначально (BR 3.1 — Яндекс.Еда) покрывал только маркетплейсы агрегации: приём заказов с Яндекс.Еды / Market Delivery и публикация нашего меню туда. В BR 2.5 расширен до «интеграционного hub’а» и покрывает также внешние POS-системы — локальные фронты (KOALa, KDS-экраны) которые подписываются на события наших заказов.
Типы интеграций
(Добавлено в BR 2.5)
| Тип | Кто | Паттерн | Что отличается |
|---|---|---|---|
| Aggregator (маркетплейс) | Яндекс.Еда, Market Delivery, Chibbis | OAuth-binding: pull меню, push заказов, push lifecycle обратно | Двусторонний full lifecycle. Детали — эта спека. |
| External POS (внешний POS/фронт) | KOALa, KDS | Webhook-подписка: только push событий заказа наружу | Одностороннее уведомление. Заказы создаются извне через публичный API Order Service. Детали — Webhook-подписки. |
Оба типа живут в одном сервисе (Aggregator Service, порт :3013) — переиспользуем инфру retry / dead-letter / логирования.
Ключевые сущности
Канал (channel)
Источник происхождения заказа. Значения: INTERNAL (касса в ресторане), YANDEX_EDA, MARKET_DELIVERY, в будущем OWN_WEBSITE, OWN_MOBILE_APP, CHIBBIS, и т.д.
Канал указан у каждого заказа в Order Service (orders.channel). От канала зависит:
- Нужна ли фискализация на нашем POS (для
INTERNAL— да, для агрегаторов — нет, Яндекс сам фискализирует) - Какой процесс отмены (внутренний / через push в агрегатор)
- Какая выручка учитывается (полная / за вычетом комиссии)
Внешний заказ (external order)
Заказ, созданный не у нас, а в системе агрегатора. Отличается от внутреннего:
- Имеет
external_order_id— ID в системе агрегатора - Имеет
channel != INTERNAL payment_type = EXTERNAL_PREPAID— оплата прошла на стороне агрегатора- Фискальный чек уже выдан агрегатором
Подключение (integration binding)
Пара «наша ТТ ↔ ресторан в агрегаторе». Атрибуты:
store_id(наш)aggregator(YANDEX_EDA и т.д.)external_restaurant_id(их)client_id/client_secret(OAuth для push-запросов)is_enabled(активно/выключено)commission_percent(по договору, для учёта)
Одна ТТ может иметь подключения к нескольким агрегаторам одновременно.
Маппинг SKU
Соответствие нашего product_id ↔ external_sku агрегатора. Создаётся автоматически после первой синхронизации меню. Живёт в Catalog Service как таблица product_external_mappings.
Бизнес-правила
П1. Подключение ТТ к агрегатору
- Подключает владелец франчайзи (
scope=legal_entity_idsилиtype=franchiseу ЮЛ) с permissionstores.edit. - Перед подключением ТТ должна быть одобрена агрегатором (у Яндекса — требования к рейтингу, категории).
client_id/client_secretвводятся вручную на экране «Интеграции» в карточке ТТ.- После сохранения происходит первая синхронизация меню — до её успешного завершения интеграция не считается активной.
П2. Синхронизация меню
- Яндекс пуллит
GET /aggregator/yandex-eda/menu?restaurant_id=Xраз в сутки. - Мы отдаём JSON в формате Яндекса, построенный из нашего Catalog Service.
- Товары с
available_in_aggregator=falseне попадают в снапшот. - Изменения в каталоге (добавлен товар, изменена цена) — видны Яндексу на следующий день. Для срочных обновлений — ручной re-sync.
- Цена в снапшоте: если у товара есть
price_override— берётся она, иначе — базовая цена прейскуранта ТТ.
П3. Стоп-лист
- Яндекс пуллит
GET /aggregator/yandex-eda/stoplist?restaurant_id=Xкаждые ~10 минут. - Товар попадает в стоп-лист, если:
- Вручную установлен в стоп через BR 1.13
- Warehouse Service сообщил
product.stock.depleted(Kafka) - Флаг
available_in_aggregator=false(но тогда и в меню его нет — дублирование)
- Товар возвращается в меню — когда стоп снят / остаток > порога.
П4. Приём заказа
- Яндекс отправляет
POST /aggregator/yandex-eda/orders/newс полным payload. - Валидации до сохранения:
- Ресторан существует и активен
- Все
external_skuнайдены в маппинге (если хоть один нет — заказ отклоняется автоматически с кодом «item_not_found») - Ни один товар не в стоп-листе (иначе — отклонение «out_of_stock»)
- При успехе — Order создаётся со статусом
NEW,payment_type=EXTERNAL_PREPAID, PayKeeper НЕ вызывается. - Нотификация на POS через существующий канал (WebSocket / long-poll OrderService → POS).
П5. Жизненный цикл заказа
stateDiagram-v2 [*] --> NEW: Яндекс → приём NEW --> IN_PROGRESS: кассир "Принять" NEW --> CANCELLED: кассир "Отклонить" / гость отменил IN_PROGRESS --> READY: кассир "Готов" IN_PROGRESS --> CANCELLED: кассир/гость отмена READY --> HANDED_OVER: кассир "Передан курьеру" HANDED_OVER --> [*] CANCELLED --> [*]
Каждая смена статуса → push в агрегатор. Если push упал — retry 3× с экспоненциальной задержкой, потом — алерт в admin UI «требуется ручная синхронизация».
П6. Отмена и возврат
- Гость отменяет в приложении Яндекса → Яндекс нам уведомление → Order →
CANCELLED. - Если заказ в
IN_PROGRESS/READYи уже начато приготовление — агрегатор удерживает часть суммы (по своим правилам) или компенсирует ресторану полностью (по договору). Наша система только фиксирует факт, финансовая сторона — вне нашей логики. - Если отменил кассир «Отклонить» — указывает причину (
out_of_stock,too_busy,technical_issue), передаётся в агрегатор.
П7. PayKeeper не участвует в агрегаторных заказах
- Заказ уже фискализирован агрегатором.
- Если кассир ошибочно пытается пробить чек на PayKeeper — интерфейс кассы блокирует эту опцию (поле
requires_fiscalization=falseпередаётся POS-у). - В отчётах: выручка от агрегаторов идёт отдельной строкой, не сумма PayKeeper Z-отчёта.
П8. Маркированные товары
- Перед «Передан курьеру» — если в составе есть маркированные товары, показываем экран сканирования DataMatrix.
- Коды складируются в Order Service (
order_marked_items) для последующего отчёта. - Если код невалиден — кассир не может закрыть заказ (нужно сообщить в поддержку).
Ролевая матрица
| Действие | Франшиза | Франчайзи | Менеджер ТТ | Кассир |
|---|---|---|---|---|
| Видеть заказы по всей сети | ✓ | — | — | — |
| Видеть заказы своего ЮЛ / своих ТТ | ✓ | ✓ (свои) | ✓ (своя ТТ) | ✓ (текущая ТТ) |
| Подключить агрегатор к ТТ | ✓ | ✓ (своя ТТ) | — | — |
Править price_override | ✓ | ✓ (свои ТТ) | — | — |
| Управлять стоп-листом | ✓ | ✓ | ✓ | — |
| Принять/отклонить заказ | — | — | — | ✓ |
| Отчёты по агрегаторам | ✓ | ✓ | ✓ | — |
Связи с другими модулями
- Catalog Service — источник меню, мастер-данные товаров и цен.
- Store Service — источник ТТ,
store_external_mappingsтаблица подключений. - Order Service — мастер заказов; все агрегаторные заказы сохраняются здесь с
channel. - Warehouse Service — источник сигналов для автоматического стоп-листа.
- PayKeeper — НЕ участвует в агрегаторных заказах (уже фискализированы).