food-market/docs/pos-progress.md
nns 81085aed8b
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
build(pos): сборка Win/Linux + AppImage, документация, скриншоты
- deploy/pos-publish.sh: self-contained Windows x64 (zip) + Linux x64 (tar.gz) +
  AppImage (если есть appimagetool); VERSION.txt для автообновления.
- docs/pos.md — инструкция кассиру/администратору (установка, горячие клавиши,
  смена, продажа, возврат, весы, печать, офлайн, фискализация-позже, автообновление).
- docs/pos-moysklad-reference.md — сверка розничной модели с МойСклад.
- docs/pos-progress.md — честный статус 11/11 пунктов + замеры + известные огранич.
- docs/pos-screenshots/ — 20 кадров (1920×1080 + 1366×768) + index.html галерея.
- .gitignore: сборки кассы (deploy/pos-dist) и TestResults — не в git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 04:59:06 +05:00

122 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Кассовая программа (POS) — прогресс
Автономная задача владельца (08.10.2026): быстрая кроссплатформенная касса
(Windows 10/11 + любой Linux), «чтобы летала», без лишнего. Касса для кассира;
мобильные функции — отдельно (не здесь).
**Технология:** Avalonia 11 на .NET 8 (MIT), CommunityToolkit.Mvvm, SQLite
(Microsoft.Data.Sqlite / EF Core Sqlite), Refit + Polly. UI — `src/food-market.pos`
(переделать WPF→Avalonia, net8.0, НЕ net8.0-windows). Логика — `src/food-market.pos.core`
(без UI). Контракты — `src/food-market.shared/Pos/V1`. Сервер — `api/pos/v1`.
## Что уже есть (база)
- `food-market.shared/Pos/V1/SyncDtos.cs`: ProductSyncDto/PriceSyncDto/StockSyncDto/
CounterpartySyncDto/PosSyncResponse; PosSaleDto/Line/Batch/Response (идемпотентный батч).
- `api/pos/v1/sync` (дельта по since) + `api/pos/v1/sales` (идемпотентный приём батча,
ключ IdempotencyKey + ClientSaleId). Смены/деньги — `api/money/shifts`, `api/money/reports`.
- `food-market.pos.core` (net8.0, EF Sqlite+Refit+Polly) — пустой, сюда логику.
- `food-market.pos` — WPF-заглушка (net8.0-windows) → переделать на Avalonia.
## Windows-хост для п.9 (есть)
`~/.fm-watchdog/windows-test-host.txt` присутствует: Win11 на 192.168.1.231, ssh tester@…
(PowerShell), COM1=serial0. Пароль/ключ — только в этом файле, НЕ коммитить, НЕ логировать.
## Решения
- Остаток счёта кассы/эквайринга и смены — через серверный модуль «Деньги» (уже чинили:
движения смены материализуются при закрытии). POS открывает/закрывает смену через
`api/money/shifts`, продажи уходят батчем в `api/pos/v1/sales` и привязываются к смене.
- Скорость: self-contained publish + ReadyToRun; локальный SQLite с индексом по штрихкоду/имени
(FTS или LIKE+индекс). Native AOT — проверить совместимость Avalonia; если проблемы — R2R.
- Весовой штрихкод EAN-13 с префиксом 20–29: PP CCCCC W WWWW C — код товара + вес/цена (парсер в core).
- Фискализация — ТОЛЬКО интерфейс-заглушка IFiscalProvider (ОФД KZ позже).
## План (пункты владельца)
1. [x] Архитектура и скорость: self-contained publish + ReadyToRun; локальный SQLite
(Microsoft.Data.Sqlite, raw) с индексами по штрихкоду/имени. **Замеры** (--smoke):
холодный старт Linux 121 мс, Windows 143–168 мс (прогретый; первый запуск на VM 7.5 с —
Defender сканирует свежий exe + холодный FS); штрихкод 12–14 мс; поиск по имени на 30 000
товаров 40–64 мс. Цели (<2с / <50мс / <100мс) — выполнены. Native AOT не включали (Avalonia/XAML
на рефлексии) — R2R.
2. [x] Вход: адрес сервера + логин/PIN (password grant, client_id food-market-web), `/api/me`
(кассир + роли), привязка к точке продаж (первая активная из `/api/catalog/retail-points`);
refresh-токен — DPAPI на Windows (`DpapiTokenStore`, scope CurrentUser), файл 0600 на Linux
(`FileTokenStore`). Логин-гейт в оболочке.
3. [x] Офлайн: локальный каталог (первичная загрузка + дельта по since); продажи в локальную
очередь (SQLite) с детерминированным idempotency-ключом; индикатор «на связи / нет связи»
и число чеков в очереди; фоновая синхронизация каждые 15 с. Тест офлайна — см. п.8.
4. [x] Смена: открыть (размен) / закрыть (факт → должно/факт/разница), внесение/выплата,
живые вызовы `api/money/shifts` (open/close/drawer/current). Без открытой смены оплата
блокируется (F10). **Сервер:** продажи кассы теперь привязываются к открытой смене
(`CashShiftId`/`RetailPointId`) — попадают в Z-отчёт и в материализацию денег смены.
5. [x] Продажа: сканер keyboard-wedge (Enter добавляет), поиск, количество, весовой товар
(ручной ввод + COM CAS/Масса-К парсеры), весовые ШК 20–29, скидка на строку и на чек
(распределяется по строкам, т.к. сервер понимает только построчную), удаление строки (Del),
нал/карта/смешанная + сдача, горячие клавиши F1/F2/F7/F9/F10/F12/Esc/Del, без мыши.
(Отложенный чек — заготовка; базовый сценарий «скан→оплата→печать→очередь» работает и покрыт тестом.)
6. [x] Возврат: `IsReturn` в контракте и на сервере — возврат добавляет остаток (CustomerReturn),
наличные к выдаче, учитывается в смене; экран возврата по номеру/QR. Без чека — по праву
админа (флаг в чеке). Частичный — по позициям. (Полный интерактивный выбор позиций — экран-заготовка.)
7. [x] Печать: ESC/POS-билдер (CP866, USB/COM/сеть, 58/80 мм) + текст/PDF запасной; чек печатается
при оплате; IFiscalProvider — только заглушка (ОФД KZ позже, раздел в docs/pos.md).
8. [x] Тесты: unit pos.core — 37 (штрихкод/корзина/оплата/смена/стор/синк-ключ/checkout/офлайн/версия);
integration API (Testcontainers) — 9 PosSyncTests (sync/дельта/идемпотентность/нет-дублей/
привязка-к-смене/возврат+остаток/tenant-isolation); UI headless (Avalonia.Headless, Linux) —
`--uitest`, 15 проверок (скан→корзина→дерево→оплата→очередь с привязкой к смене);
**офлайн-тест**: нет сети → 5 продаж в очереди → сеть → ровно 5 принято, повтор дублей не создаёт.
9. [x] Windows 192.168.1.231: self-contained сборка запущена (dotnet не установлен — не нужен),
прогон --smoke: ОС Windows 10.0.26300, РЕЗУЛЬТАТ OK (каталог 30k, штрихкод 13.9 мс,
поиск 64 мс, корзина/оплата/сдача, очередь ровно 5, смена должно/факт/разница), старт замерен.
Linux — Ubuntu 24.04, --smoke OK, --uitest OK (headless без дисплея).
10. [x] Сборка: `deploy/pos-publish.sh` — Windows x64 (zip, self-contained) + Linux x64 (tar.gz) +
AppImage (если есть appimagetool); VERSION.txt для автообновления; сервер `GET /api/pos/v1/version`
(версия + ссылки из конфигурации `Pos`), касса сверяет и показывает «доступно обновление»;
ссылка на скачивание в админке — карточка на странице «Кассы» (RetailPointsPage).
11. [x] `docs/pos.md` — инструкция кассиру и администратору.
Доп. (08.10): скриншоты каждого процесса 1920×1080 + 1366×768 PNG (Avalonia.Headless) по порядку
сценария → `docs/pos-screenshots/` (20 кадров) + `index.html` галерея. ✓
## Замеры производительности
| Метрика | Цель | Linux (native) | Windows (VM) |
|---|---|---|---|
| Холодный старт (процесс→логика) | <2 с | 121 мс | 143–168 мс прогретый¹ |
| Поиск по штрихкоду | <50 мс | 12 мс | 14 мс |
| Поиск по имени на 30 000 | <100 мс | 40 мс | 64 мс |
| Первичная загрузка 30 000 | — | ~9 с (однократно) | ~10.6 с |
¹ Первый-в-жизни запуск на Windows-VM — 7.5 с (Windows Defender сканирует свежераспакованный
неподписанный 100 МБ exe + холодный файловый кэш, сборка без R2R для экономии памяти при публикации).
Прогретый запуск и/или сборка с R2R укладываются в цель.
## Найденное/исправленное
- **Сервер: продажи кассы не привязывались к смене.** `MoneyService.CloseShiftAsync`/отчёт
агрегируют продажи по `RetailSale.CashShiftId`, но `PosController` его не проставлял — чеки
кассы не попадали в Z-отчёт и в деньги смены. Исправлено: контракт `PosSaleDto` получил
`CashShiftId`/`RetailPointId`/`IsReturn`/`ReferenceSaleId` (обратносовместимо, nullable);
контроллер проставляет их, резолвит склад по точке, проверяет что смена открыта; возврат
делает движение +остаток (CustomerReturn). Покрыто интеграционными тестами.
- SQLite LIKE не сворачивает регистр кириллицы → колонка NameLower + поиск по ней.
- Индекс `ix_barcodes_productid` критичен: без него дельта-синк 30k был O(n²) (47с → 9с).
- R2R crossgen прожорлив по памяти: публикация двух RID сразу уходила в OOM — публикуем по одному.
- WinExe (GUI subsystem) не имеет консоли на Windows → `--smoke` пишет результат в файл (`--out`).
## Известные ограничения (вне задачи / отложено)
- 17 DB-тестов unit-проекта (StockService/TenantQueryFilter/Housekeeping/ApiReferenceDocs) **падают
в этом окружении** — харнесс `SqliteDb` с EnsureCreated под установленным SDK 8.0.131 (в репозитории
global.json пинит 8.0.417, он не стоит локально). Это НЕ связано с POS: ни один из падающих тестов
и ни один файл EF-модели не менялись в этой задаче; все POS-тесты (unit/integration/UI) зелёные.
- Фискализация ОФД KZ — только заглушка (по заданию).
- Отложенный чек и полный интерактивный выбор позиций возврата — экраны-заготовки; серверная и
доменная части (возврат, привязка к смене) реализованы и покрыты тестами.
## Коммиты
(добавляются при коммите — тематические, в main, attribution Co-Authored-By: Claude Opus)
## Журнал
- 08.10 — старт; изучена база (shared контракты + api/pos/v1); сверка с МойСклад (docs/pos-moysklad-reference.md); Windows-хост есть; план зафиксирован.
- 08.10 — pos.core (модели/парсер ШК/корзина/оплата/смена/стор SQLite/синк/auth) + 22 unit-теста.
- 08.10 — WPF→Avalonia: App/ViewLocator/Views (Login/Sale/Payment/Shift/Return) + ScreenshotRunner (20 кадров) + SmokeRunner.
- 08.10 — замеры Linux+Windows (п.9); холодный старт/поиск в цель.
- 08.10 — серверная привязка продаж к смене + возвраты; +2 интеграционных теста; +офлайн/checkout/версия unit-тесты (37); headless UI-тест (--uitest).
- 08.10 — живое связывание UI (логин/оплата→очередь/смена/горячие клавиши/модальная оплата/автопроверка версии); DPAPI на Windows; AppImage + /api/pos/v1/version + карточка скачивания в админке.