Декомпозиция POS Phase 2 — Append-items + Waiter mode + TableOrderScreen

Источник

План desktop-pos: «Точка управления заведением» — Phase 2. Идёт после Phase 1 (Tables Canvas).

Цель

Открытый dine_in заказ можно дополнять; столу назначен официант; кликнув на занятый стол — видим активный заказ с позициями, официантом, действиями (добавить, сменить официанта, закрыть с оплатой).

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

СлойРепоЗадачи
desktop-poserp-pos-desktopDesktop POS
POS BFFerp-pos (bff/)POS BFF
Backend (order-service, user-service)без правок (всё уже было)

Backend полностью готов до Phase 2

InternalOrderController.addItems / closeWithPayment / findOpenByTable (BR 2.5 dine-in flow) и InternalEmployeeController.listPOSEmployees (BR 1.4.4) уже реализованы. Phase 2 = чистый фронт + один тонкий BFF route для проксирования.

Decision points

  • Append-mode storage: вместо отдельного store — расширили cartStore.mode = 'new' | 'append:{...}'. Когда mode=‘append’ — UI чека меняется, кнопка одна («Добавить в заказ №N»), order_type зафиксирован как dine_in, стол выставлен.
  • Waiter list: используем pos-employees endpoint (все сотрудники с pos.access), без жёсткой фильтрации по «роли Официант». В реальности на ТТ один человек часто кассир+официант+бариста (BR 1.4.4 убрал enum-роли). UI показывает поиск по имени/роли, чтобы оператор быстро нашёл нужного.
  • WaiterPickerModal позиционирование: доступен и из TablesScreen action sheet (для free и occupied столов), и из TableOrderScreen — единый компонент.
  • Close-with-payment: TableOrderScreen даёт 3 кнопки — наличными, картой, через PK. Cash/card → close-with-payment сразу. PK → переход на OrderDetailScreen где уже есть весь polling-flow для PK invoice (Phase 1 BR 3.3).
  • OrderDetailScreen для dine_in: показывает «Стол №X» и «Официант» через tablesStore.byId(table_id) + staffStore.byId(t.current_waiter_id). Backend OrderResponse не нужно расширять — фетчим параллельно.
  • Mock Item validation: при попытке E2E-проверки add-items с product_id=null PostgreSQL вернул NOT NULL constraint violation — это валидно, реальный POS всегда отправляет настоящий product_id. После исправления теста add-items работает корректно (тот же order_number, total пересчитан).

Acceptance criteria (PASS)

  1. ✅ На занятом столе кликом виден активный заказ с timestamps позиций (TableOrderScreen).
  2. ✅ «Добавить позиции» НЕ создаёт новый заказ — order_number тот же, новые позиции в order_items с тем же order_id, total пересчитан.
  3. ✅ Назначение официанта — current_waiter_id обновляется в zal_tables через PATCH /tables/:id/waiter.
  4. OrderDetailScreen для dine_in показывает Стол №X + имя официанта (через tablesStore + staffStore).
  5. ✅ E2E проверен на VPS: add-items с реальным product_id увеличил total с 2.00 → 201.00 (2×99.50), order_number 007 не изменился.

Прогресс

  • POS BFFroutes/staff.ts (proxy на user-service pos-employees); routes/orders.ts уже имел add-items / close-with-payment / by-table из Phase 1
  • desktop-pos:
    • domain types: PosEmployee, AddItemsRequest, CloseWithPaymentRequest, Order.table_id
    • api-client: orders.findOpenByTable / addItems / closeWithPayment, endpoints/staff.ts
    • cartStore.mode (new | append) + setMode(); clear сбрасывает в new
    • staffStore — кэш на сессию для PosEmployee[]
    • WaiterPickerModal — поиск по имени/роли, “Снять назначение”
    • TableOrderScreen (/tables/:tableId) — позиции, метаданные стола (вместимость, официант), кнопки: «Добавить», «Сменить официанта», «Закрыть наличными/картой/PK», «Освободить» если нет заказа
    • MainScreen append-mode: header «Дозаказ к №X», красный bar «Стол №X · добавляем позиции», скрыт OrderTypeSwitcher, единственная кнопка «Добавить в заказ №N»
    • TablesScreen: occupied tap → /tables/:id; «Сменить официанта» в action sheet (free + occupied)
    • OrderDetailScreen для dine_in: Meta «Стол №X» + Meta «Официант» (через tablesStore + staffStore)
    • mock: addItems / findOpenByTable / closeWithPayment / 4 mock employees; demo1 теперь dine_in на mock-table-2
    • App.tsx route /tables/:tableId

Verification

  • TypeScript desktop-pos: zero errors (pnpm typecheck)
  • Vite build: passing (188 модулей, +5 от Phase 1)
  • POS BFF tsc: новые правки без ошибок
  • VPS deploy: pos-bff rebuilt + healthy
  • Smoke на VPS:
    • GET /api/v1/pos/staff → 401 без auth (route жив)
    • GET /internal/users/by-store/{id}/pos-employees → реальные сотрудники Мария/Дмитрий
    • POST /internal/orders/{id}/add-items → total пересчитан, order_number сохранён, новый item в той же order_id

Out of scope (отложено в следующие фазы)

  • Phase 3: Customer attach по телефону, Manager-PIN approval (override крупного refund/discount), ручная скидка %
  • Phase 4: Aggregator inbox в POS, авто-снятие истёкших броней (cron в Java)
  • Phase 5: Tips Нетмонет, KDS by stations
  • Phase 6: Reports в POS, Printer config

Ссылки