Aggregator Service — Kafka события

Consumer (подписки)

order.status.changed ✅ реализован

Источник: Order Service. Триггер: заказ сменил статус (кассир нажал Принять/Готов/Передан). Действие: если channel != INTERNAL — найти binding по store_id + external_provider, добавить задачу в status_push_queue (StatusChangedConsumer).

Планируемые consumer’ы (🕓 M3)

Эти подписки описаны как проектные, но в коде отсутствуют (нет соответствующих @KafkaListener). Пока инвалидация menu_snapshots и stoplist происходит по TTL снапшота (1 час), не по событиям:

catalog.product.updated — 🕓

Источник: Catalog Service. Триггер: изменение товара в нашем каталоге. Действие: инвалидация menu_snapshots для всех bindings где этот товар присутствует.

catalog.availability.changed — 🕓

Источник: Catalog Service. Триггер: изменился флаг available_in_aggregators или available_in_stores. Действие: пересчёт снапшота + stoplist.

warehouse.product.stock.depleted — 🕓

Источник: Warehouse Service. Триггер: остаток товара упал ниже порога. Действие: добавить в стоп-лист (отразится при следующем pull агрегатором).

warehouse.product.stock.replenished — 🕓

Источник: Warehouse Service. Триггер: остаток восстановлен. Действие: убрать из стоп-листа.

stoplist.manual.changed — 🕓

Источник: Catalog Service. Триггер: менеджер вручную поставил/снял стоп. Действие: то же что warehouse.stock.depleted/replenished — пересчёт.

Producer (публикуем)

aggregator.order.received

Когда: пришёл новый заказ от агрегатора, валидация прошла. Payload:

{
  "event_id": "uuid",
  "timestamp": "iso",
  "version": 1,
  "payload": {
    "provider": "yandex-eda",
    "external_order_id": "ye-123",
    "binding_id": "uuid",
    "store_id": "uuid",
    "franchise_id": "uuid",
    "items": [
      { "product_id": "uuid", "quantity": 1, "unit_price": 750, "modifiers": [...] }
    ],
    "total": 1250.00,
    "payment": { "type": "EXTERNAL_PREPAID", "status": "paid" },
    "customer": { "first_name": "Иван", "phone_mask": "+7***1234" },
    "delivery": { "type": "courier", "estimated_pickup_at": "2026-04-17T13:10:00Z" },
    "comment": "..."
  }
}

Подписчик: Order Service — создаёт Order с соответствующим channel/payment_type.

aggregator.order.rejected

Когда: заказ не прошёл валидацию (item_not_found, out_of_stock, duplicate). Payload: { provider, external_order_id, reason, details }. Подписчики: Admin BFF (для алертов).

aggregator.order.cancelled_by_customer

Когда: Яндекс уведомил об отмене гостем. Payload: { provider, external_order_id, internal_order_id, cancelled_at }. Подписчик: Order Service — переводит заказ в CANCELLED, флаг cancelled_by_customer=true.

aggregator.status.pushed

Когда: успешно отправили смену статуса в агрегатор. Payload: { binding_id, order_id, target_status, provider_response }. Подписчик: для аудита, логов.

aggregator.status.push_failed

Когда: исчерпали retry. Подписчик: Admin BFF для алертов «push failed, требуется ручное вмешательство».

aggregator.binding.connected / aggregator.binding.disconnected

Когда: ТТ подключили/отключили от агрегатора. Подписчик: Admin BFF (для UI-нотификаций).

tips.received (BR 3.2)

Когда: получен webhook от Нетмонета о новых чаевых, валидирован, сохранён в tip_events. Payload:

{
  "event_id": "uuid",
  "timestamp": "datetime",
  "version": 1,
  "payload": {
    "tip_event_id": "uuid",
    "binding_id": "uuid",
    "provider": "netmonet",
    "external_tip_id": "string",
    "store_id": "uuid",
    "franchise_id": "uuid",
    "order_number": "string | null",
    "table_number": "integer | null",
    "waiter_id": "uuid | null",
    "amount": "decimal",
    "currency": "RUB",
    "received_at": "datetime"
  }
}

Подписчики:

  • Order ServiceTipsEventConsumer: если order_number != null → найти Order, обновить tip_amount, waiter_id
  • User Service (в будущем) — агрегат в статистику по сотруднику для дашборда «Мои чаевые»

Топики

ТопикТипRetention
aggregator.ordersвсе события по заказам агрегаторов7 дней
aggregator.bindingsconnect/disconnect7 дней
aggregator.status.syncpush результаты3 дня

Ссылки