docs(weighted): unitShort в sync + обновлён mobile-api и прогресс
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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-09 11:18:33 +05:00
parent 195dfb1a2c
commit d465d82248
2 changed files with 42 additions and 36 deletions

View file

@ -57,30 +57,35 @@ advanced-only скрыты; обычная роль — её флаги (в пр
`PromotionsManage`, `PriceTypesManage` (зеркалит серверный `[RequiresAdvancedMode]`).
Имена флагов — из `RolePermissions`. Веб использует тот же источник (`useCan()`).
## 4. Весовой признак в офлайн-каталоге
## 4. Весовой признак и единица в офлайн-каталоге
`GET /api/pos/v1/sync` (каталог для сканирования/офлайна) в `ProductSyncDto` отдаёт:
- `packaging` (1=штучный, 2=весовой, 3=разливной) и **`isWeighed`** (явный bool `packaging==2`),
- `unitCode` — единица измерения (шт/кг/л…).
- `unitCode` — код ОКЕИ единицы измерения («796»=шт, «166»=кг, «112»=л…),
- **`unitShort`** — короткое имя для отображения («шт»/«кг»/«л»), выведено из `unitCode`/названия,
чтобы касса/моб. не держали свою таблицу ОКЕИ. То же поле есть в строках возврата
(`PosReturnLineDto.unitShort`). Маппинг — `foodmarket.Shared.Units.UnitShort`.
## 5. Весовые этикетки (внутримагазинные EAN-13)
Как в МойСкладе: внутримагазинные EAN-13 с префиксом из диапазона 20–29, где зашиты
«код товара» (PLU, N цифр) и величина (вес в граммах → кг, либо цена).
## 5. Поиск по штрихкоду и весовые этикетки
**Реальный сценарий (09.10): весов с печатью этикеток нет.** Штрихкоды весовых товаров —
обычные коды в карточке (у владельца короткие «27xxxxx»). Поэтому:
**Поиск** — `GET /api/catalog/products/by-barcode/{value}`:
- **Сначала точное совпадение штрихкода** карточки (включая короткие «27xxxxx»):
1 → объект товара, несколько → `{ items:[…] }`.
- **Поиск по штрихкоду НИКОГДА не матчит по артикулу** (раньше весовой PLU матчился
по артикулу — товар с артикулом «12345» ошибочно находился; исправлено).
- Разбор весовой этикетки — **только если включён в организации И точного штрихкода нет**.
Тогда встроенный PLU ищется как ШТРИХКОД (не артикул) → `{ product:{…}, weighted:true,
valueKind:"WeightKg"|"Price", quantity:1.5, embeddedPrice:null }`.
- Нет совпадений → `404`.
**Настройка организации** — `GET/PUT /api/organization/settings` (PUT — право Admin):
- `weightBarcodeEnabled` (bool, по умолчанию true),
- `weightBarcodeWeightPrefixes` (CSV префиксов веса, null → `20,21,22,23,24`),
- `weightBarcodePricePrefixes` (CSV префиксов цены, null → `25,26,27,28,29`),
- `weightBarcodeCodeDigits` (длина PLU, по умолчанию 5).
Блок применяется, только если в PUT передан `weightBarcodeEnabled` (иначе настройки не трогаются).
**Разбор при поиске** — `GET /api/catalog/products/by-barcode/{value}`:
- Обычный штрихкод → товар (или `{ items:[…] }` при нескольких совпадениях), `404` если нет.
- **Весовая этикетка** (префикс весовой/ценовой) → `{ product:{…}, weighted:true,
valueKind:"WeightKg"|"Price", quantity:1.5, embeddedPrice:null }`. Товар ищется по
встроенному PLU (как короткий штрихкод товара либо как артикул); `quantity` — вес (кг),
`embeddedPrice` — зашитая цена (для ценовых этикеток).
Пример: этикетка `2112345015007` при дефолтных настройках → PLU `12345`, вес `1.500` кг.
- `weightBarcodeEnabled` (bool, **по умолчанию FALSE**) — разбор этикеток;
- `weightBarcodeWeightPrefixes` / `weightBarcodePricePrefixes` (CSV, null → `20..24`/`25..29`);
- `weightBarcodeCodeDigits` (длина PLU, по умолчанию 5);
- **`weightedCodeFormat`** («short»|«ean13», default «short») — формат кода, который
генерируется для нового ВЕСОВОГО товара; **`weightedCodePrefix`** (default 27),
**`weightedCodeDigits`** (default 5) → код вида «27xxxxx», следующий свободный номер.
Разбор единый для сервера и кассы — `foodmarket.Shared.Barcodes.WeightedBarcode`.

View file

@ -17,25 +17,26 @@
→ внутренние/обычные коды принимались за весовые.
## План / статус
1. [ ] Разбор весовых этикеток ВЫКЛ по умолчанию (server/POS/web/mobile). Поиск по ШК —
только точное совпадение (включая короткие 7-значные «27xxxxx»); убрать матч по артикулу.
2. [ ] Настройка орг «Весы с печатью этикеток» (как префикс МойСклада): выкл по умолчанию;
при вкл — префикс(ы) + формат ПП ККККК ВВВВВ К; **сначала точный ШК, потом разбор**.
3. [ ] Касса: весовой товар → вес с весов (COM/USB, Масса-К/CAS) или ручной ввод; сумма =
цена/кг × вес; «1,235 кг × 690 ₸/кг».
4. [ ] Генератор ШК: штучный — EAN-13 (как сейчас); весовой — EAN-13 или короткий «27 + 5»
(настройка «формат кода весовых», по умолчанию 27 + следующий свободный 5-значный);
проверка уникальности.
5. [ ] `/api/pos/v1/sync`: unitCode «796» → отдавать unitCode (ОКЕИ) + unitShort («шт»/«кг»/«л»);
обновить docs/mobile-api.md.
6. [ ] Тесты (unit/integration) + full-chain без регрессий + деплой.
1. [x] Разбор весовых этикеток ВЫКЛ по умолчанию (`WeightedBarcodeSettings.Enabled=false`,
`Organization.WeightBarcodeEnabled=false` + Phase40 выключает у существующих). Поиск по ШК
(`ByBarcode`) — сначала точное совпадение (вкл. «27xxxxx»); матч по артикулу убран везде.
2. [x] Настройка орг «Весы с печатью этикеток»: выкл по умолчанию; при вкл — префиксы +
формат; **сначала точный ШК, потом разбор**. `OrganizationSettingsController`.
3. [x] Касса: весовой товар → вес с весов (Масса-К/CAS) или ручной ввод (модалка); сумма =
цена/кг × вес; подпись «1,235 кг × 690 ₸/кг». `ScaleMonitor` + Scan/AddWeighed flow.
4. [x] Генератор ШК: штучный — EAN-13; весовой — короткий «27+N» (next-free) или EAN-13 по
`weightedCodeFormat`; уникальность. `GenerateUniqueWeightedShortCodeAsync`.
5. [x] `/api/pos/v1/sync`: + `unitShort` («шт»/«кг»/«л») рядом с `unitCode` (ОКЕИ); в строках
возврата тоже; shared `UnitShort`; docs/mobile-api.md обновлён.
6. [~] Тесты: unit 321/321 ✓ (WeightedBarcode/UnitShort/ScaleMonitor/PosBarcode); integration
MobileApi 9/9 ✓. full-chain + деплой — ниже.
### Автопоиск весов (данные sysadmin: МойСклад-касса спамит «Порт не выбран»/«Таймаут»)
- [ ] Касса сама находит весы: автоскан всех COM (вкл. USB-COM CH340/Prolific) с Масса-К/CAS,
кнопка «Найти весы» в настройках; найденный порт запоминается.
- [ ] Весы не отвечают → значок «Весы не подключены» + мгновенный ручной ввод, без спама,
опрос только при весовом товаре и не чаще нужного.
- [ ] В логе — одна запись о потере связи, не сотни.
- [x] Автоскан всех COM (вкл. USB-COM CH340/Prolific) × Масса-К/CAS × скорости; кнопка
«Найти весы» в верхней панели; найденный порт запоминается (kv) и восстанавливается.
`ScaleDiscovery` + `AppServices.DiscoverScaleAsync/RestoreScaleFromSettings`.
- [x] Весы не отвечают → «Весы не подключены» + ручной ввод; опрос только при весовом товаре.
- [x] В логе — одна запись о потере/восстановлении связи (тест: 100 опросов → 1 запись).
## Что НЕ трогаем
Прод, shared-postgres, global.json, gateway nginx, sklad.