Декомпозиция POS Phase 4 — Aggregator inbox + Auto-release reservations
Источник
План
desktop-pos: «Точка управления заведением»— Phase 4. После Phase 1–3.
Цель
- Кассир видит входящие заказы Я.Еды/Маркета прямо в POS — отдельный экран
/aggregatorс inbox по статусам. - Звуковая нотификация + badge counter в навигации при поступлении нового заказа (даже когда экран не открыт).
- Просроченные брони столов авто-снимаются Java cron’ом каждую минуту.
Затронутые сервисы / репозитории
| Слой | Репо | Задачи |
|---|---|---|
| desktop-pos | erp-pos-desktop | Desktop POS |
| Store Service | erp-store-service | Store Service |
| Order Service | erp-order-service | Order Service |
| POS BFF | erp-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)
- ✅ Inbox показывает только заказы своей ТТ со статусами
new/accepted/ready/in_progress/handed_over— фильтрация на BFF (DEFAULT_STATUSES + scope storeId). - ✅ NEW badge мигает (CSS pulse animation), red, число с round-trip к /aggregator поллингом 30с в AppShell.
- ✅ Звук на новый: проигрывается один раз через
playNewOrderSound(), store сбрасываетnewSinceLastReload. - ✅ Принятый агрегаторский заказ → status=accepted, попадает в kitchen-queue (это уже есть в Phase 2 KitchenQueueScreen).
- ✅ Бронь, чей
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.expiredevents — пока только log в Java