# Кассовая программа (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), нал/карта/смешанная + сдача, отложенный чек (park/resume, независимый снимок корзины), горячие клавиши 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 — только заглушка (по заданию). - Полный интерактивный выбор позиций при возврате по чеку — экран-заготовка (поиск/отметка позиций); серверная и доменная части возврата (IsReturn, +остаток, привязка к смене) реализованы и покрыты тестами. ## Коммиты (добавляются при коммите — тематические, в 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 + карточка скачивания в админке.