diff --git a/.gitignore b/.gitignore index a11565a..956e073 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,7 @@ postgres-data/ src/food-market.public/.astro/ src/food-market.public/dist/ src/food-market.public/node_modules/ + +## POS build artifacts (self-contained сборки кассы — не в git) +deploy/pos-dist/ +tests/**/TestResults/ diff --git a/deploy/pos-publish.sh b/deploy/pos-publish.sh new file mode 100755 index 0000000..674c550 --- /dev/null +++ b/deploy/pos-publish.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Сборка кассы food-market.pos (Avalonia) под Windows x64 и Linux x64. +# Self-contained + ReadyToRun (быстрый старт, без установленного .NET на кассе). +# Trimming выключен по умолчанию (Avalonia/XAML использует рефлексию; AOT/trim — +# отдельная задача, см. docs/pos.md). Результат — zip/tar.gz в deploy/pos-dist/. +# +# Запуск: deploy/pos-publish.sh [win|linux|all] (по умолчанию all) +set -euo pipefail +cd "$(dirname "$0")/.." + +PROJ=src/food-market.pos/food-market.pos.csproj +OUT=deploy/pos-dist +VER="$(date +%Y.%m.%d)" +mkdir -p "$OUT" +TARGET="${1:-all}" + +publish() { + local rid="$1" label="$2" + echo "[pos-publish] $label ($rid) …" + local dir="$OUT/food-market-pos-$rid" + rm -rf "$dir" + dotnet publish "$PROJ" -c Release -r "$rid" --self-contained true \ + -p:PublishReadyToRun=true -p:Version="$VER" -p:DebugType=none \ + -o "$dir" >/tmp/pos-publish-$rid.log 2>&1 || { tail -25 /tmp/pos-publish-$rid.log; exit 1; } + # версия для автообновления кассой + echo "$VER" > "$dir/VERSION.txt" + local size; size=$(du -sh "$dir" | cut -f1) + echo "[pos-publish] $label готово: $dir ($size)" +} + +# AppImage для Linux (переносимый single-file). Требует appimagetool в PATH; +# если его нет — пропускаем (tar.gz остаётся основным артефактом Linux). +build_appimage() { + local rid="$1" ver="$2" + local src="$OUT/food-market-pos-$rid" + if ! command -v appimagetool >/dev/null 2>&1; then + echo "[pos-publish] appimagetool не найден — AppImage пропущен (есть tar.gz)." + return 0 + fi + local appdir="$OUT/FoodMarketPos.AppDir" + rm -rf "$appdir"; mkdir -p "$appdir/usr/bin" + cp -r "$src/." "$appdir/usr/bin/" + cat > "$appdir/foodmarket-pos.desktop" < "$appdir/foodmarket-pos.png" + cat > "$appdir/AppRun" <<'RUN' +#!/bin/sh +HERE="$(dirname "$(readlink -f "$0")")" +exec "$HERE/usr/bin/foodmarket.Pos" "$@" +RUN + chmod +x "$appdir/AppRun" "$appdir/usr/bin/foodmarket.Pos" + ( cd "$OUT" && ARCH=x86_64 appimagetool "FoodMarketPos.AppDir" "food-market-pos-$ver-x86_64.AppImage" >/tmp/pos-appimage.log 2>&1 ) \ + && echo "[pos-publish] AppImage: $OUT/food-market-pos-$ver-x86_64.AppImage" \ + || { echo "[pos-publish] AppImage не собрался (см. /tmp/pos-appimage.log) — tar.gz остаётся."; } + rm -rf "$appdir" +} + +if [[ "$TARGET" == "win" || "$TARGET" == "all" ]]; then + publish win-x64 "Windows x64" + ( cd "$OUT" && zip -qr "food-market-pos-win-x64-$VER.zip" food-market-pos-win-x64 ) + echo "[pos-publish] zip: $OUT/food-market-pos-win-x64-$VER.zip" +fi +if [[ "$TARGET" == "linux" || "$TARGET" == "all" ]]; then + publish linux-x64 "Linux x64" + ( cd "$OUT" && tar czf "food-market-pos-linux-x64-$VER.tar.gz" food-market-pos-linux-x64 ) + echo "[pos-publish] tar: $OUT/food-market-pos-linux-x64-$VER.tar.gz" + build_appimage linux-x64 "$VER" +fi +echo "[pos-publish] версия $VER собрана." diff --git a/docs/pos-moysklad-reference.md b/docs/pos-moysklad-reference.md new file mode 100644 index 0000000..62dcee5 --- /dev/null +++ b/docs/pos-moysklad-reference.md @@ -0,0 +1,42 @@ +# Касса МойСклад — reference для POS food-market + +Сверка перед проектированием кассы (задача владельца). Источник: +`https://dev.moysklad.ru/doc/api/remap/1.2/documents/` — розничные документы. +Скрейп через headless Playwright (tests/e2e, chromium): SPA МойСклада грузит +разделы по фрагменту нестабильно (часть якорей → «Файл не найден»), поэтому ниже +— сводка модели по известной семантике МойСклада + успешно считанные разделы +(внесение/выплата денег). Нам нужна не калька API, а поведение кассы. + +## Сущности розницы (МойСклад) +- **Розничная смена** `retailshift` — открывается на точке продаж (ККМ/`retailStore`). + Поля: `open`/`close` (bool), `proceedsCash`/`proceedsNoCash` (выручка нал/безнал), + `balanceStart` (размен), `balanceEnd`, суммы продаж/возвратов. Пока смена открыта — + к ней привязываются продажи/возвраты/внесения/выплаты. +- **Розничная продажа** `retaildemand` — чек. Привязан к смене (`retailShift`) и точке. + Оплата: `cashSum` (нал) + безнал; позиции `positions` (товар, цена, кол-во, скидка). +- **Возврат розничной продажи** `retailsalesreturn` — возврат покупателю, со ссылкой + на исходную продажу (`demand`) или без неё, нал/безнал, частичный. +- **Внесение денег** `retaildrawercashin` — приход наличных в кассу ККМ внутри смены + (размен/инкассация внутрь). Увеличивает наличные в ящике. +- **Выплата денег** `retaildrawercashout` — изъятие наличных из кассы ККМ (инкассация + наружу/расход). Уменьшает наличные в ящике. + +## Деньги и остатки +- Касса = `balanceStart` (размен) + Σ продаж нал − Σ возвратов нал + Σ внесений − Σ выплат. +- Безнал (карта) идёт на эквайринг/расчётный счёт отдельно. +- Закрытие смены: фиксируется факт наличных; расхождение (излишек/недостача) видно в отчёте. +- X-отчёт — промежуточный (без закрытия), Z-отчёт — при закрытии смены. + +## Как ложится на food-market +- У нас уже есть серверный модуль «Деньги» с точно такой моделью (движения смены + материализуются при закрытии — см. `docs/money.md`). POS НЕ дублирует эту логику: + - смену открывает/закрывает через `POST api/money/shifts/open|{id}/close`; + - внесение/выплату — `POST api/money/shifts/{id}/drawer` (In/Out); + - продажи уходят батчем в `api/pos/v1/sales` (идемпотентно), сервер создаёт + `RetailSale` (Posted), привязанный к открытой смене точки; возвраты — так же + (IsReturn + ReferenceSaleId); + - остаток кассы/эквайринга и Z-отчёт считает сервер (`api/money/reports`, + `api/money/shifts/{id}/report`). +- Весовой товар: МойСклад — `weighed`; у нас `Packaging=2` (весовой). Весовой ШК с + префиксом 20–29 парсит POS локально (код товара + вес/цена). +- Фискализация (ОФД KZ) — отдельно; в POS только интерфейс-заглушка `IFiscalProvider`. diff --git a/docs/pos-progress.md b/docs/pos-progress.md new file mode 100644 index 0000000..b12a37a --- /dev/null +++ b/docs/pos-progress.md @@ -0,0 +1,121 @@ +# Кассовая программа (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 + карточка скачивания в админке. diff --git a/docs/pos-screenshots/01-login-1366x768.png b/docs/pos-screenshots/01-login-1366x768.png new file mode 100644 index 0000000..47d1d1a Binary files /dev/null and b/docs/pos-screenshots/01-login-1366x768.png differ diff --git a/docs/pos-screenshots/01-login-1920x1080.png b/docs/pos-screenshots/01-login-1920x1080.png new file mode 100644 index 0000000..b48d57d Binary files /dev/null and b/docs/pos-screenshots/01-login-1920x1080.png differ diff --git a/docs/pos-screenshots/02-shift-open-1366x768.png b/docs/pos-screenshots/02-shift-open-1366x768.png new file mode 100644 index 0000000..0800870 Binary files /dev/null and b/docs/pos-screenshots/02-shift-open-1366x768.png differ diff --git a/docs/pos-screenshots/02-shift-open-1920x1080.png b/docs/pos-screenshots/02-shift-open-1920x1080.png new file mode 100644 index 0000000..985f414 Binary files /dev/null and b/docs/pos-screenshots/02-shift-open-1920x1080.png differ diff --git a/docs/pos-screenshots/03-sale-empty-1366x768.png b/docs/pos-screenshots/03-sale-empty-1366x768.png new file mode 100644 index 0000000..0806a27 Binary files /dev/null and b/docs/pos-screenshots/03-sale-empty-1366x768.png differ diff --git a/docs/pos-screenshots/03-sale-empty-1920x1080.png b/docs/pos-screenshots/03-sale-empty-1920x1080.png new file mode 100644 index 0000000..d0a9a9d Binary files /dev/null and b/docs/pos-screenshots/03-sale-empty-1920x1080.png differ diff --git a/docs/pos-screenshots/04-sale-items-1366x768.png b/docs/pos-screenshots/04-sale-items-1366x768.png new file mode 100644 index 0000000..1a093c9 Binary files /dev/null and b/docs/pos-screenshots/04-sale-items-1366x768.png differ diff --git a/docs/pos-screenshots/04-sale-items-1920x1080.png b/docs/pos-screenshots/04-sale-items-1920x1080.png new file mode 100644 index 0000000..e08f70c Binary files /dev/null and b/docs/pos-screenshots/04-sale-items-1920x1080.png differ diff --git a/docs/pos-screenshots/05-payment-cash-1366x768.png b/docs/pos-screenshots/05-payment-cash-1366x768.png new file mode 100644 index 0000000..27ef21f Binary files /dev/null and b/docs/pos-screenshots/05-payment-cash-1366x768.png differ diff --git a/docs/pos-screenshots/05-payment-cash-1920x1080.png b/docs/pos-screenshots/05-payment-cash-1920x1080.png new file mode 100644 index 0000000..46a6054 Binary files /dev/null and b/docs/pos-screenshots/05-payment-cash-1920x1080.png differ diff --git a/docs/pos-screenshots/06-payment-mixed-1366x768.png b/docs/pos-screenshots/06-payment-mixed-1366x768.png new file mode 100644 index 0000000..ed7b882 Binary files /dev/null and b/docs/pos-screenshots/06-payment-mixed-1366x768.png differ diff --git a/docs/pos-screenshots/06-payment-mixed-1920x1080.png b/docs/pos-screenshots/06-payment-mixed-1920x1080.png new file mode 100644 index 0000000..baa3502 Binary files /dev/null and b/docs/pos-screenshots/06-payment-mixed-1920x1080.png differ diff --git a/docs/pos-screenshots/07-return-1366x768.png b/docs/pos-screenshots/07-return-1366x768.png new file mode 100644 index 0000000..e2b1b7b Binary files /dev/null and b/docs/pos-screenshots/07-return-1366x768.png differ diff --git a/docs/pos-screenshots/07-return-1920x1080.png b/docs/pos-screenshots/07-return-1920x1080.png new file mode 100644 index 0000000..cf262ab Binary files /dev/null and b/docs/pos-screenshots/07-return-1920x1080.png differ diff --git a/docs/pos-screenshots/08-shift-z-1366x768.png b/docs/pos-screenshots/08-shift-z-1366x768.png new file mode 100644 index 0000000..0099a9c Binary files /dev/null and b/docs/pos-screenshots/08-shift-z-1366x768.png differ diff --git a/docs/pos-screenshots/08-shift-z-1920x1080.png b/docs/pos-screenshots/08-shift-z-1920x1080.png new file mode 100644 index 0000000..a2d15a8 Binary files /dev/null and b/docs/pos-screenshots/08-shift-z-1920x1080.png differ diff --git a/docs/pos-screenshots/09-shell-offline-1366x768.png b/docs/pos-screenshots/09-shell-offline-1366x768.png new file mode 100644 index 0000000..f9aa5d0 Binary files /dev/null and b/docs/pos-screenshots/09-shell-offline-1366x768.png differ diff --git a/docs/pos-screenshots/09-shell-offline-1920x1080.png b/docs/pos-screenshots/09-shell-offline-1920x1080.png new file mode 100644 index 0000000..da480c3 Binary files /dev/null and b/docs/pos-screenshots/09-shell-offline-1920x1080.png differ diff --git a/docs/pos-screenshots/10-shell-online-1366x768.png b/docs/pos-screenshots/10-shell-online-1366x768.png new file mode 100644 index 0000000..abf3f36 Binary files /dev/null and b/docs/pos-screenshots/10-shell-online-1366x768.png differ diff --git a/docs/pos-screenshots/10-shell-online-1920x1080.png b/docs/pos-screenshots/10-shell-online-1920x1080.png new file mode 100644 index 0000000..2d734c4 Binary files /dev/null and b/docs/pos-screenshots/10-shell-online-1920x1080.png differ diff --git a/docs/pos-screenshots/index.html b/docs/pos-screenshots/index.html new file mode 100644 index 0000000..1a93516 --- /dev/null +++ b/docs/pos-screenshots/index.html @@ -0,0 +1 @@ +Касса food-market — скриншоты

Касса food-market — скриншоты процессов

кадров: 20
Вход кассира (адрес сервера, логин, PIN) (1920x1080)
Вход кассира (адрес сервера, логин, PIN) (1366x768)
Смена: Z-отчёт/состояние смены (1920x1080)
Смена: Z-отчёт/состояние смены (1366x768)
Продажа: пустой чек, ожидание сканирования (1920x1080)
Продажа: пустой чек, ожидание сканирования (1366x768)
Продажа: чек с товарами (вкл. весовой) (1920x1080)
Продажа: чек с товарами (вкл. весовой) (1366x768)
Оплата наличными со сдачей (1920x1080)
Оплата наличными со сдачей (1366x768)
Оплата смешанная (нал+карта) (1920x1080)
Оплата смешанная (нал+карта) (1366x768)
Возврат по чеку (выбор позиций) (1920x1080)
Возврат по чеку (выбор позиций) (1366x768)
Z-отчёт при закрытии (должно/факт/разница) (1920x1080)
Z-отчёт при закрытии (должно/факт/разница) (1366x768)
Статус-бар: нет связи, чеки в очереди (1920x1080)
Статус-бар: нет связи, чеки в очереди (1366x768)
Статус-бар: на связи, очередь пуста (1920x1080)
Статус-бар: на связи, очередь пуста (1366x768)
\ No newline at end of file diff --git a/docs/pos.md b/docs/pos.md new file mode 100644 index 0000000..46272a2 --- /dev/null +++ b/docs/pos.md @@ -0,0 +1,89 @@ +# Касса food-market — инструкция (кассир и администратор) + +Кроссплатформенная касса на Avalonia 11 / .NET 8. Работает на Windows 10/11 и +Linux x64. Быстрая, без лишнего; офлайн-режим; сервер — food-market (`api/pos/v1`). + +## Установка + +### Windows 10/11 +1. Скачайте `food-market-pos-win-x64-<версия>.zip` (ссылка — в админке, раздел + «Точки продаж») и распакуйте в любую папку, напр. `C:\food-market-pos`. +2. Запустите `foodmarket.Pos.exe`. .NET устанавливать НЕ нужно (сборка self-contained). +3. Ярлык на рабочий стол — по желанию (правой кнопкой по exe → «Создать ярлык»). + +### Linux x64 (Ubuntu и др.) +1. Распакуйте `food-market-pos-linux-x64-<версия>.tar.gz`. +2. `chmod +x foodmarket.Pos && ./foodmarket.Pos`. +3. Для весов/принтера по COM добавьте пользователя в группу `dialout`. +4. Либо запустите переносимый `food-market-pos-<версия>-x86_64.AppImage` (если собран). + +### Скачать (администратор) +В админке на странице **«Кассы»** (точки продаж) — карточка «Кассовое приложение» +со ссылками «Скачать для Windows / для Linux». Ссылки и версия задаются на сервере +в конфигурации (секция `Pos`: `LatestVersion`, `DownloadWindows`, `DownloadLinux`). + +### Автообновление +При старте касса сверяет свою версию (`VERSION.txt` в сборке) с `GET /api/pos/v1/version` +и при более новой версии показывает в статус-баре «Доступно обновление …» со ссылкой. + +## Первый запуск (администратор точки) +1. **Адрес сервера** — URL вашего food-market (напр. `https://admin.food-market.kz`). +2. **Регистрация устройства** — касса привязывается к точке продаж (retail point). + Кассир видит только свои точки (к которым он привязан в админке). +3. **Вход кассира** — логин и PIN/пароль учётной записи кассира. + +## Горячие клавиши (работа без мыши) +- **Сканер** — печатает штрихкод в поле ввода, **Enter** добавляет товар (keyboard-wedge, фокус не нужен). +- **F1** — помощь · **F2** — поиск товара по названию · **F7** — внесение/выплата. +- **F9** — возврат · **F10** — смена (открыть/закрыть, X/Z) · **F12** — оплата · **Esc** — отмена/очистить чек. +- **Del** — удалить выделенную строку · **+/−** — количество. + +## Смена +- Открыть смену (ввести размен). Без открытой смены продавать нельзя. +- Внесение / выплата наличных (инкассация) — с причиной. +- X-отчёт — промежуточный (смена не закрывается); Z-отчёт — при закрытии (должно + быть / факт / разница). Деньги смены учитываются сервером в остатках счетов + (см. docs/money.md). + +## Продажа +- Пробитие по сканеру или поиск по названию. +- Весовой товар: вес с весов по COM (CAS / Масса-К) или ручной ввод; весовой + штрихкод с префиксом 20–29 (код + вес/цена) распознаётся автоматически. +- Скидка на позицию и на чек (по праву). +- Оплата: наличные / карта / смешанная; показывается сдача. +- Отложенный чек — отложить и вернуться. + +## Возврат +- По чеку (номер или скан QR) — частичный или полный. +- Без чека — по праву администратора. + +## Весы (COM/serial) +- Протоколы: CAS, Масса-К (ASCII). Настройка: порт (COM1 / /dev/ttyUSB0), скорость. +- Нет весов/связи — ручной ввод веса. + +## Печать чека +- ESC/POS на чековый принтер: USB (`/dev/usb/lp0` или `COMn`), сеть (`tcp:IP:9100`), + ширина 58 мм (32 симв.) или 80 мм (48 симв.). +- Запасной вариант — текстовый/обычный принтер/PDF. +- X/Z-отчёты печатаются так же. + +## Офлайн +- Каталог (товары, цены, штрихкоды, весовые) хранится локально (SQLite) — первичная + загрузка + дельта-синхронизация. +- Без сети касса продолжает работать: чеки пишутся локально и уходят на сервер + очередью с идемпотентным ключом (повтор не создаёт дублей). +- В статус-баре: «на связи / нет связи» и число чеков в очереди. + +## Фискализация (ОФД Казахстана) +Сейчас касса НЕ фискализирует чеки (интерфейс-заглушка `IFiscalProvider`). Для +реальной фискализации KZ потребуется: +- Договор с ОФД-оператором (Казахтелеком/Webkassa/др.) и регистрация онлайн-ККМ. +- Реализация `IFiscalProvider.RegisterSaleAsync` под API выбранного оператора + (регистрация чека, получение фискального номера и QR, печать на чеке). +- Хранение ключей/токенов ОФД защищённо (как refresh-токен кассира). +- Серверная синхронизация фискальных признаков (поля уже есть в RetailSale). + +## Производительность (замеры) +- Поиск по штрихкоду — ~14 мс, по названию на 30 000 товаров — ~47 мс (локальный SQLite с индексами). +- Первичная загрузка 30 000 товаров — ~9 с (однократно при настройке). +- Сборка self-contained + ReadyToRun; замеры холодного старта — см. docs/pos-progress.md.