build(pos): сборка Win/Linux + AppImage, документация, скриншоты
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
- 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>
4
.gitignore
vendored
|
|
@ -93,3 +93,7 @@ postgres-data/
|
||||||
src/food-market.public/.astro/
|
src/food-market.public/.astro/
|
||||||
src/food-market.public/dist/
|
src/food-market.public/dist/
|
||||||
src/food-market.public/node_modules/
|
src/food-market.public/node_modules/
|
||||||
|
|
||||||
|
## POS build artifacts (self-contained сборки кассы — не в git)
|
||||||
|
deploy/pos-dist/
|
||||||
|
tests/**/TestResults/
|
||||||
|
|
|
||||||
76
deploy/pos-publish.sh
Executable file
|
|
@ -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" <<DESK
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Food Market Касса
|
||||||
|
Exec=foodmarket.Pos
|
||||||
|
Icon=foodmarket-pos
|
||||||
|
Type=Application
|
||||||
|
Categories=Office;
|
||||||
|
DESK
|
||||||
|
# Минимальная иконка-заглушка (1x1 PNG), если нет фирменной.
|
||||||
|
printf '\x89PNG\r\n\x1a\n' > "$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 собрана."
|
||||||
42
docs/pos-moysklad-reference.md
Normal file
|
|
@ -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`.
|
||||||
121
docs/pos-progress.md
Normal file
|
|
@ -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 + карточка скачивания в админке.
|
||||||
BIN
docs/pos-screenshots/01-login-1366x768.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/pos-screenshots/01-login-1920x1080.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/pos-screenshots/02-shift-open-1366x768.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
docs/pos-screenshots/02-shift-open-1920x1080.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/pos-screenshots/03-sale-empty-1366x768.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/pos-screenshots/03-sale-empty-1920x1080.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/pos-screenshots/04-sale-items-1366x768.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
docs/pos-screenshots/04-sale-items-1920x1080.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
docs/pos-screenshots/05-payment-cash-1366x768.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/pos-screenshots/05-payment-cash-1920x1080.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/pos-screenshots/06-payment-mixed-1366x768.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/pos-screenshots/06-payment-mixed-1920x1080.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/pos-screenshots/07-return-1366x768.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/pos-screenshots/07-return-1920x1080.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/pos-screenshots/08-shift-z-1366x768.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
docs/pos-screenshots/08-shift-z-1920x1080.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/pos-screenshots/09-shell-offline-1366x768.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
docs/pos-screenshots/09-shell-offline-1920x1080.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
docs/pos-screenshots/10-shell-online-1366x768.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
docs/pos-screenshots/10-shell-online-1920x1080.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
1
docs/pos-screenshots/index.html
Normal file
89
docs/pos.md
Normal file
|
|
@ -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.
|
||||||