Декомпозиция POS Phase 2 — Append-items + Waiter mode + TableOrderScreen
Источник
План
desktop-pos: «Точка управления заведением»— Phase 2. Идёт после Phase 1 (Tables Canvas).
Цель
Открытый dine_in заказ можно дополнять; столу назначен официант; кликнув на занятый стол — видим активный заказ с позициями, официантом, действиями (добавить, сменить официанта, закрыть с оплатой).
Затронутые сервисы / репозитории
| Слой | Репо | Задачи |
|---|---|---|
| desktop-pos | erp-pos-desktop | Desktop POS |
| POS BFF | erp-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-employeesendpoint (все сотрудники с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). BackendOrderResponseне нужно расширять — фетчим параллельно. - Mock Item validation: при попытке E2E-проверки add-items с
product_id=nullPostgreSQL вернул NOT NULL constraint violation — это валидно, реальный POS всегда отправляет настоящий product_id. После исправления теста add-items работает корректно (тот же order_number, total пересчитан).
Acceptance criteria (PASS)
- ✅ На занятом столе кликом виден активный заказ с timestamps позиций (TableOrderScreen).
- ✅ «Добавить позиции» НЕ создаёт новый заказ —
order_numberтот же, новые позиции вorder_itemsс тем жеorder_id, total пересчитан. - ✅ Назначение официанта —
current_waiter_idобновляется вzal_tablesчерезPATCH /tables/:id/waiter. - ✅
OrderDetailScreenдля dine_in показывает Стол №X + имя официанта (через tablesStore + staffStore). - ✅ E2E проверен на VPS:
add-itemsс реальным product_id увеличил total с 2.00 → 201.00 (2×99.50), order_number 007 не изменился.
Прогресс
- POS BFF —
routes/staff.ts(proxy на user-servicepos-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 сбрасывает вnewstaffStore— кэш на сессию для PosEmployee[]WaiterPickerModal— поиск по имени/роли, “Снять назначение”TableOrderScreen(/tables/:tableId) — позиции, метаданные стола (вместимость, официант), кнопки: «Добавить», «Сменить официанта», «Закрыть наличными/картой/PK», «Освободить» если нет заказаMainScreenappend-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.tsxroute/tables/:tableId
- domain types:
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
Ссылки
- POS Phase 1 — base infra
- Order Service API — internal endpoints для dine_in