Order Service — BR 5.1

Контракты

  • Data Modelexpected_ready_at + 3 поля per-item KDS
  • API — 3 новых endpoint
  • Events — событие order.item.kitchen_status_changed
  • Бизнес-логика: Заказы §«KDS-flow позиций»

Что делаем

Миграция Liquibase

  • src/main/resources/db/changelog/0XX_add_kds_fields.xml:
    • ALTER TABLE orders ADD COLUMN expected_ready_at timestamp NULL
    • ALTER TABLE order_items ADD COLUMN kitchen_status varchar(20) NOT NULL DEFAULT ‘pending’
    • ALTER TABLE order_items ADD COLUMN kitchen_started_at timestamp NULL
    • ALTER TABLE order_items ADD COLUMN kitchen_ready_at timestamp NULL
    • CHECK CONSTRAINT kitchen_status IN ('pending','preparing','ready')
    • CREATE INDEX idx_order_items_kitchen_status ON order_items (order_id, kitchen_status)
    • Backfill для legacy: UPDATE order_items SET kitchen_status='ready' WHERE order_id IN (SELECT id FROM orders WHERE status IN ('ready','closed','delivered','handed_over','in_delivery'))
    • Регистрация в db.changelog-master.xml

Entities

  • Order — добавить поле expectedReadyAt (LocalDateTime)
  • OrderItem — добавить поля kitchenStatus (enum/varchar), kitchenStartedAt, kitchenReadyAt
  • KitchenStatus enum: PENDING, PREPARING, READY

Repositories

  • OrderItemRepository:
    • int countByOrderIdAndKitchenStatus(UUID orderId, KitchenStatus status) — для авто-перехода
    • List<OrderItem> findAllByOrderIdAndKitchenStationId(UUID orderId, UUID stationId) — для batch endpoint

Services

  • OrderService.startCooking(...) — расширить:

    • Проставлять expected_ready_at = accepted_at + 15 минут (P0 заглушка; P1 — + avg(items.assembly_time_seconds))
    • Все order_items.kitchen_status остаются pending (default)
  • Новый метод OrderService.updateItemKitchenStatus(orderId, itemId, newStatus, userId):

    • Найти item, проверить multi-tenancy (item.order.franchise_id == jwt.franchise_id)
    • Проверить статус заказа = accepted (иначе ORDER_NOT_IN_KITCHEN_FLOW)
    • Проверить что переход не назад (KITCHEN_STATUS_BACKWARD)
    • Обновить статус, проставить kitchen_started_at/kitchen_ready_at
    • Опубликовать событие order.item.kitchen_status_changed
    • Auto-transition check: если все items заказа kitchen_status=ready → перевести orders.status=ready, ready_at=NOW(), опубликовать order.ready с trigger=kds_auto
    • Транзакция
  • Новый метод OrderService.markStationReady(orderId, stationId, userId):

    • Найти все items с kitchen_station_id = stationId AND kitchen_status != 'ready'
    • Если 0 — это идемпотентный success (no-op)
    • Если есть items в pending или preparing — вернуть STATION_NOT_READY (требуем сначала индивидуальные ready)
    • В P0 — простой PATCH каждый item на ready через updateItemKitchenStatus (publishes event per item)
    • Транзакция
  • Новый метод OrderService.findActiveForKds(franchiseId, storeId, stationIds, status):

    • Запрос с фильтром по franchise_id + store_id + status + JOIN order_items WHERE kitchen_station_id IN stationIds
    • Загрузить все items заказов (для контекста серых блоков)
    • Сформировать DTO с kitchen_station_name (из cross-service lookup в Catalog Service)

Controllers

  • InternalOrderController:

    • GET /internal/orders/active-by-stations — для pos-bff. X-Service-Token auth, query: franchise_id, store_id, station_ids (csv), status (default accepted)
  • OrderController (или новый KdsOrderController):

    • PATCH /orders/{id}/items/{itemId}/kitchen-status — JWT с permission kds.access
    • PATCH /orders/{id}/kitchen-status?station_id=... — JWT с kds.access

KafkaPublisher

  • OrderEventPublisher:
    • publishItemKitchenStatusChanged(...) — топик order.item.kitchen_status_changed, ключ order_id
    • Расширить publishOrderReady(...) — добавить поле trigger: "manual" | "kds_auto" в payload

Tests

  • Unit:
    • OrderServiceTest.startCooking_setsExpectedReadyAt
    • OrderServiceTest.updateItemKitchenStatus_blocksBackwardTransition
    • OrderServiceTest.updateItemKitchenStatus_publishesEvent
    • OrderServiceTest.updateItemKitchenStatus_autoTransitionsOrderToReady
    • OrderServiceTest.markStationReady_idempotent
    • OrderServiceTest.markStationReady_failsOnPendingItems
  • Integration:
    • KdsOrderControllerIntegrationTest — full flow с реальной БД и Kafka в Testcontainers

Конфигурация

  • application.yml:
    • app.kds.default-prep-time-minutes: 15 — для расчёта expected_ready_at в P0

Permission на endpoints

  • kds.access обязателен для:
    • PATCH /orders/{id}/items/{itemId}/kitchen-status
    • PATCH /orders/{id}/kitchen-status

Проверка через стандартный Spring Security. Если permission нет → 403 KDS_ACCESS_DENIED.

Что НЕ делаем

  • Не трогаем существующие endpoints (/start-cooking, /mark-ready и т.д.) — они работают как раньше
  • Не реализуем expected_ready_at через avg(assembly_time_seconds) — это P1 в BR 5.X
  • Не подписываемся на новые Kafka события (Order Service — только publisher)

Зависимости от других сервисов

  • kitchen_station_name для DTO — берётся через прямой http-вызов Catalog Service (GET /kitchen-stations с кэшем 5 мин), либо через Kafka snapshot в локальную таблицу kitchen_stations_cache (если решим избежать sync HTTP). В P0 — sync HTTP с Caffeine cache.

Ссылки