Commit graph

4 commits

Author SHA1 Message Date
nns 01b47cc0c8 feat(pos): возврат по чеку — экран выбора позиций + защита от over-return
- Сервер: GET /api/pos/v1/sales/by-number/{number} — чек-основание с позициями
  и доступным к возврату (продано − уже возвращено, по QtyReturned). POS-путь
  возврата теперь валидирует over-return (нельзя вернуть больше остатка) и
  инкрементит QtyReturned на исходных строках (как RetailSalesController).
- Контракт: PosReturnLookupDto/PosReturnLineDto; IServerApi.ReturnLookupAsync.
- Касса: экран возврата — найти чек по номеру/скану QR, степпер количества по
  каждой позиции (0..доступно, кнопка «Всё»), сумма к выдаче, способ (нал/карта
  как в исходном чеке); CheckoutService.CommitReturnAsync пишет возврат в офлайн-
  очередь + печать + выгрузка.
- Тесты: integration — lookup by-number, возврат 2 из 3, over-return в Failed,
  довозврат 1, QtyReturned/остатки корректны; 404 на неизвестный номер. 11/11 PosSyncTests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:16:48 +05:00
nns 5173eaa07c feat(catalog): штрихкоды товара — добавление + весовые этикетки
- POST /api/catalog/products/{id}/barcodes {code, kind}: добавить штрихкод к
  товару, уникальность в организации, 409 «штрихкод уже у товара X» (+название),
  идемпотентно при повторе тому же товару, автоопределение типа по длине.
- Весовые этикетки (внутримагазинные EAN-13, как в МойСкладе): общий парсер
  food-market.shared/Barcodes/WeightedBarcode (настраиваемые префиксы веса/цены,
  длина PLU); касса pos.core делегирует туда. Настройки организации
  (WeightBarcode*: enabled/префиксы/длина кода) + миграция Phase35.
- GET /api/catalog/products/by-barcode/{value} разбирает весовую этикетку →
  товар + количество (вес) / встроенная цена; обычный ШК — как прежде.
- GET/PUT /api/organization/settings отдаёт/принимает настройки (блок весовых
  применяется только если передан — round-trip-safe для текущей веб-формы).
- Офлайн-каталог ProductSyncDto: явный isWeighed (packaging==2) для мобильного/кассы.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 05:39:20 +05:00
nns 606b81b4d9 feat(pos-api): привязка чеков кассы к смене/точке + возвраты + версия
- PosSaleDto: CashShiftId/RetailPointId/IsReturn/ReferenceSaleId (nullable,
  обратносовместимо с v1). PosController проставляет их в RetailSale, резолвит
  склад по точке продаж, проверяет что смена открыта. Без этого чеки кассы не
  попадали в Z-отчёт/материализацию денег смены (MoneyService агрегирует продажи
  по CashShiftId).
- Возврат покупателю (IsReturn): движение +остаток (CustomerReturn), без
  pre-flight на остаток; наличные к выдаче, учитывается в смене.
- GET /api/pos/v1/version (anon): версия + ссылки на сборки из конфигурации Pos
  (автообновление кассы + ссылка в админке).
- Интеграционные тесты: продажа в смене попадает в expected cash при закрытии;
  возврат возвращает остаток (9/9 PosSyncTests зелёные, Testcontainers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 04:58:05 +05:00
nns e022db30aa feat(pos-shared): контракты POS v1 в food-market.shared (P1-12a)
Доступные DTO для оффлайн-кассы (food-market.pos):
• ProductSyncDto/PriceSyncDto/StockSyncDto/CounterpartySyncDto — выгрузка
  изменений для последующей пробивки;
• PosSyncResponse — конверт всего sync-ответа с ServerTime (reference
  time против клок-дрейфа кассы) и DeletedProductIds;
• PosSaleDto/PosSaleLineDto/PosSaleBatchDto — батч продаж от кассы.
  PosSaleBatchDto несёт IdempotencyKey + каждая продажа имеет ClientSaleId
  (двойная идемпотентность);
• PosSaleBatchResponse — Accepted/Failed + ReplayedFromCache флаг.

Версионирование на уровне namespace  — для v2 будет рядом без
breaking changes. Required-поля везде where applicable: компилятор обяжет
заполнить новые обязательные поля при появлении v1.X добавок.

Тесты: 3 unit на сериализационный round-trip (компиляция падёт при удалении
любого поля контракта — это и есть тест public API).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 12:03:04 +05:00