Desktop POS — Phase 2
Что сделано
Domain layer
packages/domain/src/types/staff.ts—PosEmployee(id, first_name, last_name, role_names[]) + хелперfullName(e)packages/domain/src/types/order.ts— добавленыOrder.table_id?: string | null,AddItemsRequest,CloseWithPaymentRequestpackages/domain/src/types/index.ts—export * from "./staff.ts"
API client
packages/api-client/src/endpoints/staff.ts(new) —listPosEmployees()packages/api-client/src/endpoints/orders.ts(extended):findOpenByTable(tableId)— возвращаетOrder | null(404 трактуется как “нет открытого заказа”)addItems(orderId, items)— POST в BFF /add-items с уже распакованными itemscloseWithPayment(orderId, req)— POST в BFF /close-with-payment (мапит payment в плоский shape)
Stores
stores/cartStore.ts—mode: CartMode = { type: 'new' } | { type: 'append', orderId, orderNumber, tableId, tableNumber }.setMode(append)автоматически выставляетorderType=dine_in+ tableId/tableNumber.clear()сбрасывает в{ type: 'new' }.stores/staffStore.ts(new) — items, loaded, byId;load()ленивый,reload()форсированный.
Components
components/WaiterPickerModal.tsx(new) — поиск по имени/роли, кнопка «Снять назначение» если естьcurrentWaiterId, выбор →onSelect(waiter_id | null). ПодгружаетstaffStore.load()при открытии.
Screens
-
screens/TableOrderScreen.tsx(new,/tables/:tableId):- Загружает
tablesStore.load()+staffStore.load()+orders.findOpenByTable(tableId) - Header: «Стол №X — Label» + StatusBadge заказа
- Метаданные: вместимость, текущий официант (с кнопкой «Назначить»/«Сменить»)
- Если активный заказ есть — позиции с timestamps (created_at заказа), Итого
- Действия: «Добавить позиции» (→ MainScreen append-mode), «Закрыть наличными/картой» (close-with-payment напрямую), «Оплата через PayKeeper» (→ /orders/:id для PK polling-flow)
- Если заказа нет — плейсхолдер + «Освободить стол» если стол занят (рассинхрон)
- Загружает
-
screens/MainScreen.tsx(extended):- В append-mode: header «Дозаказ к №X» + кнопка «Отменить дозаказ» (clearCart)
- Красный bar «Стол №X · добавляем позиции в открытый заказ»
- Скрыт OrderTypeSwitcher и индикатор стола (всё уже зафиксировано append)
- Единственная кнопка «Добавить в заказ №N» (вместо Оплатить + На кухню)
handleAppendItems()→addItems(mode.orderId, items)→clearCart()→navigate('/tables/${tableId}')
-
screens/TablesScreen.tsx(extended):handleViewOrderдля occupied →navigate('/tables/:id')(раньше →/orders/:id)handleOpenOrderчистит корзину + сбрасывает mode перед навигацией- В action sheet добавлен
onAssignWaitercallback →WaiterPickerModalоткрывается прямо отсюда (для free и occupied)
-
screens/OrderDetailScreen.tsx(extended):- Подгружает tablesStore + staffStore при mount
- Для
order_type === 'dine_in'показывает Meta «Стол №X (label)» + Meta «Официант» (имя из staffStore через current_waiter_id стола)
-
components/TableActionSheet.tsx— добавлен proponAssignWaiter; кнопка «Сменить/Назначить официанта» показывается в free и occupied
Wiring
api/client.ts—getStaffEndpoints()factory (mock или real)api/mockEndpoints.ts:addItems(orderId, items)— append + пересчёт totalfindOpenByTable(tableId)— поиск order по table_id где status not in (closed/cancelled)closeWithPayment(orderId, req)— выставляет paid_at + status=closed + автоматически освобождает столcreateMockStaffEndpoints+ 4 employees (Иван Иванов / Анна Петрова / Дмитрий Сидоров / Елена Козлова)seedMockData: demo1 теперьorder_type=dine_inнаmock-table-2(для теста append-flow)
App.tsx— добавлен<Route path="/tables/:tableId">под Protected+RequireShift+AppShell
Файлы
Created (5):
packages/domain/src/types/staff.tspackages/api-client/src/endpoints/staff.tsapps/desktop/src/stores/staffStore.tsapps/desktop/src/components/WaiterPickerModal.tsxapps/desktop/src/screens/TableOrderScreen.tsx
Modified (12):
packages/domain/src/types/index.tspackages/domain/src/types/order.tspackages/api-client/src/index.tspackages/api-client/src/endpoints/orders.tsapps/desktop/src/api/client.tsapps/desktop/src/api/mockEndpoints.tsapps/desktop/src/stores/cartStore.tsapps/desktop/src/components/TableActionSheet.tsxapps/desktop/src/screens/MainScreen.tsxapps/desktop/src/screens/OrderDetailScreen.tsxapps/desktop/src/screens/TablesScreen.tsxapps/desktop/src/App.tsx
Тесты
pnpm typecheck— zero errors во всём monorepopnpm exec vite build— passing (188 modules, +5 от Phase 1)- E2E на VPS:
findOpenByTable(17d5214f...)→ возвращает order 007 со 1 позициейaddItems(50c45f4f..., [{Тестовая пицца ×2}])→ total 2.00 → 201.00 (199 = 2×99.50), order_number 007 НЕ изменился, новые items в той жеorder_idpos-employeesдля реальной ТТ → Мария Петрова + Дмитрий Сидоров