Декомпозиция POS Phase 4 — Aggregator inbox + Auto-release reservations

Источник

План desktop-pos: «Точка управления заведением» — Phase 4. После Phase 1–3.

Цель

  1. Кассир видит входящие заказы Я.Еды/Маркета прямо в POS — отдельный экран /aggregator с inbox по статусам.
  2. Звуковая нотификация + badge counter в навигации при поступлении нового заказа (даже когда экран не открыт).
  3. Просроченные брони столов авто-снимаются Java cron’ом каждую минуту.

Затронутые сервисы / репозитории

СлойРепоЗадачи
desktop-poserp-pos-desktopDesktop POS
Store Serviceerp-store-serviceStore Service
Order Serviceerp-order-serviceOrder Service
POS BFFerp-pos (bff/)без правок (aggregator-orders.ts уже был)

BFF готов до Phase 4

POS BFF routes/aggregator-orders.ts уже покрывал list / get / accept / ready / hand-over / reject (BR 2.5 dine-in flow). Phase 4 = чистый фронт + Java cron + одно поле в OrderListItem.

Decision points

  • Polling vs WebSocket: 15 сек polling в screen + 30 сек глобальный poll в AppShell. Просто, без новой инфраструктуры. WebSocket откладываем.
  • Sound: Web Audio API beep (sine 880→1320 Гц) — без зависимостей и без аудиофайлов. AudioContext ленивый, переиспользуется.
  • «Новые» tracking: store держит knownIds: Set<string>; после reload diff с new статусом → newSinceLastReload. AggregatorOrdersScreen потребляет → играет звук → clearNew. Фоновый AppShell тоже потребляет (для случая когда мы не на /aggregator).
  • Auto-release: используем ZalTableService.cancelReservation (не release). Это безопаснее — он проверяет, что статус действительно reserved (а не occupied). Если стол занят заказом — не трогаем.
  • Cron interval: 60 сек как в TimeTariffScheduler / MenuAvailabilityScheduler — единая cadence schedulers. POS plan-зала auto-poll каждые 30 сек, так что задержка от истечения брони до обновления UI ≤ 90 сек.

Acceptance criteria (PASS)

  1. ✅ Inbox показывает только заказы своей ТТ со статусами new/accepted/ready/in_progress/handed_over — фильтрация на BFF (DEFAULT_STATUSES + scope storeId).
  2. ✅ NEW badge мигает (CSS pulse animation), red, число с round-trip к /aggregator поллингом 30с в AppShell.
  3. ✅ Звук на новый: проигрывается один раз через playNewOrderSound(), store сбрасывает newSinceLastReload.
  4. ✅ Принятый агрегаторский заказ → status=accepted, попадает в kitchen-queue (это уже есть в Phase 2 KitchenQueueScreen).
  5. ✅ Бронь, чей reserved_until истёк → стол free через ≤ 60 сек (Java cron).

Прогресс

  • Store Service@EnableScheduling в Application; ZalTableRepository.findExpiredReservations(now); worker/ReservationExpiryScheduler (@Scheduled fixedDelay=60_000)
  • Order Service — OrderListItem.external_provider/external_order_id (для UI inbox); OrderService.toOrderListItem маппит новые поля
  • desktop-pos:
    • domain: AggregatorOrderListItem, AggregatorProvider, AGGREGATOR_PROVIDER_LABEL
    • api-client: aggregatorOrders endpoints (list/getById/accept/ready/handOver/reject)
    • aggregatorStore: items, knownIds, newSinceLastReload, newCount/acceptedCount, accept/ready/handOver/reject (auto-reload)
    • AggregatorOrdersScreen: 3-column layout (Новые/Готовятся/Готовы), карточки с status badge + provider label + elapsed time + total + actions; usePolling 15с
    • lib/notificationSound.ts: Web Audio API beep
    • AppShell: глобальный 30с poll, NavItem с pulsing red badge числа NEW, fallback toast+sound на не-/aggregator экранах, keyframes pulse
    • mock: 3 seed заказа разных провайдеров и статусов

Verification

  • TypeScript desktop-pos: zero errors
  • Vite build: passing (201 modules, +5)
  • Java backend: 4 файла изменены — собирается в Docker (Java 21)
  • VPS deploy: store-service + order-service rebuild + healthy

Out of scope

  • Phase 5: Tips Нетмонет, KDS by stations
  • Phase 6: Reports в POS, Printer config, hotkeys, kiosk mode
  • WebSocket: для realtime push (заменить polling), отложено
  • Audit Kafka topic для table.reservation.expired events — пока только log в Java

Ссылки