Поручение владельца (задача 11): галочки состояния остатка объединяются (ИЛИ),
группа/склад/поиск сужают (И). Пусто → positive+negative (нулевые скрыты).
- API StockController: параметр states (positive|zero|negative; через запятую или
повтором) на /api/inventory/stock и /stock/export. Один WHERE:
(positive И qty>0) ИЛИ (zero И qty=0) ИЛИ (negative И qty<0) — по остатку
(COALESCE подзапрос; товар без строки Stock = 0). total/пагинация/сортировка
корректны. Старые includeZero/negativeOnly оставлены для совместимости
(includeZero ≡ все три; negativeOnly ≡ negative); states приоритетнее.
- Веб-админка StockPage: три галочки «В наличии» (>0), «Нулевые» (=0), «В минусе»
(<0) вместо прежних двух; состояние в URL (states=…), экспорт учитывает галочки.
Ссылки NegativeStockBanner/OverdueNegativeStockBlock/NotificationCenter на
минусовые → states=negative.
- Интеграционный тест: сочетания (zero; negative; positive; zero+negative;
+группа; все три = весь каталог; пусто = positive+negative; со складом) и
числовые тождества (positive+negative=пусто; positive+zero+negative=весь
каталог) + совместимость старых флагов. 1/1 зелёный (EF8 переводит предикат).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- HwCheck: флаг --autofind (автопоиск при старте — для скриншотов/проверки).
- docs/pos-scale-2026-10-11: добавлен скриншот автопоиска на VM (COM1 в журнале,
без зависания), отчёт обновлён — новый zip hardware-check и его SHA256; полная
проверка с эмулятором/зависшим виртуальным портом ждёт com0com (решение владельца).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
На ноутбуке магазина автопоиск спотыкался о Bluetooth-порты (COM7/COM8 — Open/Write
висят), выбирал по умолчанию BT и не доходил до COM22 (USB-SERIAL CH340, где весы).
Парсер/протокол исправны (ручной выбор COM22 57600 работает) — баг только в поиске.
- ScaleAutodiscovery.DiscoverAsync: тирами по типу порта — USB-serial первыми,
неопознанные в середине, Bluetooth последними; на каждый порт ЖЁСТКИЙ таймаут
(Open обёрнут в задачу с гонкой против Task.Delay — SerialPort.Open на BT виснет);
порты перебираются параллельно (до 4), общий бюджет ~15 с; любая ошибка/таймаут →
следующий порт; по каждому порту строка в лог («COM22 CH340: ENQ→ACK, вес 0.000 кг»,
«COM7 Bluetooth: пропущен», «COM15: нет ответа»).
- HardwareProbe: Classify (CH340/Prolific/FTDI/CP210x → USB-serial; BTHENUM/Bluetooth
→ Bluetooth; ttyUSB/ttyACM → USB-serial), ClassifyPorts (без открытия портов),
OpenCloseBounded (жёсткий таймаут — листинг не виснет на BT). ListComPorts не
открывает Bluetooth-порты.
- HwCheck: по умолчанию выбирается НЕ Bluetooth-порт; окно показывает журнал
автопоиска по портам; список портов помечает [USB-serial]/[Bluetooth].
- Основная касса (экран «Оборудование») — та же DiscoverScaleAsync; --probe-hw не
открывает BT-порты.
- Из zip hardware-check убран ярлык «(с тестовой печатью).bat» (печать — отдельно
по слову владельца).
- Тесты: зависающий порт (Open 30 с) не задерживает поиск > таймаута, живой порт
вторым — найден; USB-serial проверяется раньше Bluetooth (BT не трогаем). 53/53.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Приоритет владельца: sysadmin проверяет весы/принтер/сканер на ноутбуке магазина
без входа в кассу. Отдельный exe (food-market.pos.hwcheck), ссылается ТОЛЬКО на
pos.core ради Hardware/* — не умеет быть кассой (нет логина, сервера, БД, токенов,
сетевых запросов).
- pos.core/Hardware/HardwareProbe.cs: детект COM-портов (занят/свободен, VID/PID,
описание) и принтеров Windows (имя/порт/в сети/число заданий) — только чтение,
очередь не трогаем. WMI под OperatingSystem.IsWindows(); на Linux — базовый COM.
- pos.core/Hardware/BarcodeScan.cs: контрольная цифра EAN-13/EAN-8 и оценка
«сканер vs ручной набор» по среднему интервалу между символами (чистая логика).
- food-market.pos.hwcheck: окно на Avalonia (код, без XAML), светлая тема, русский.
Весы (Найти/Прочитать/Запустить-Остановить, live-вес ~350 мс, сырой hex, ручной
выбор порта/скорости/протокола), COM-порты с пометкой «занят», принтеры (только
чтение), сканер (EAN + тип ввода), «Сохранить/Копировать отчёт». Тестовая печать
ВЫКЛ по умолчанию, включается только --allow-test-print.
- tests: EAN контрольная цифра (13/8, валид/битый) + классификация ввода. 50/50.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
По фактическому замеру с ноутбука магазина (11.10): весы Масса-К (USB-COM CH340)
отвечают только на 57600 8N1; рукопожатие ENQ(0x05)→ACK(0x06); запрос DC1(0x11) →
кадр SOH STX «флаг вес единица» CTRL ETX EOT.
- pos.core/Hardware/SerialScale.cs: ISerialTransport (абстракция для тестов) +
SystemSerialTransport (8N1, DTR/RTS), MassaK.ParseFrame/Handshake/ReadFrame/
ReadWeight. Control-байт НЕ валидируем (не подтверждён) — только сырой hex в лог.
Единицы kg по умолчанию, «г/g» → делим на 1000.
- pos.core/Hardware/ScaleDiscovery.cs: SerialScaleReader (+ стабильность по двум
одинаковым чтениям) и ScaleAutodiscovery — 57600 и Масса-К-кадр ПЕРВЫМИ, общий
бюджет, отдельно отмечаем занятые порты («порт занят», а не «весы не найдены»).
- pos: ComScale — тонкий адаптер к pos.core; AppServices хранит scale_proto и по
умолчанию MassaKFrame/57600; ProbeRunner (--probe-hw) печатает сырой hex кадра.
- tests: разбор реального нулевого кадра 01 02 53 20 20 30 2E 30 30 30 6B 67 71 03 04
и вариантов (1.235 / 12.500 / отрицательный / граммы / мусор до SOH / обрезанный /
без ACK) + рукопожатие на фейковом порту. 14/14 зелёных.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- kb/quickstart: убраны .msi/«ключ организации»/«импорт по API-токену» —
импорт из файла Excel/CSV, из других систем «в разработке»; касса Windows и
Linux «скоро», вход по адресу сервера + логин + PIN.
- blog/cash-with-scales: убран дублирующий <h1> в теле (как в launch/quick-start).
- kb/faq + og: неподтверждённое «до 100 000 товаров» → нейтрально «большие
каталоги / тысячи позиций» (точного лимита в коде нет; есть RequestSizeLimit 20МБ).
- Конструктор: цена «₸/мес» на кнопке «Выбрать конфигурацию» не переносится
(whitespace-nowrap).
- Форма регистрации: инлайн-ссылки «офертой»/«политикой» — увеличенная зона
нажатия (py-1) без сдвига вёрстки.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Шапка: десктоп-меню с xl (чтобы на ~1050px не ломалось в 2 строки); CTA без
переноса; в мобильной панели добавлены «Интеграции», «Статус», «Что нового»;
Android «Назад» закрывает панель (pushState/popstate), Esc/тап/крестик остаются.
- Конструктор «Бизнес»: ползунки удобнее на тач — крупная ручка, touch-action
pan-y (вертикальный скролл не двигает значение), кнопки −/+; бейдж
«Популярный» (как на главной) и «90 дней бесплатно».
- Кнопка «Настроить тариф» на главной → /pricing#configurator со скроллом к
конструктору; на /pricing убран ложный пункт «Импорт из других систем» у «Старт».
- Типографика markdown (h2/h3, списки, таблицы, ссылки) вынесена в global.css —
/kb/* больше не плоский текст; эмодзи-фолбэк; увеличенная зона нажатия у
инлайн-ссылок; снят дублирующий inline-стиль в blog/[slug].
- Футер: «Status» → «Статус».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ?plan= и параметры конфигуратора читаются в useEffect ПОСЛЕ гидрации, поэтому
первый клиентский рендер совпадает с SSR-разметкой — ошибка React #418
(hydration mismatch) на /signup?plan=business&shops=… больше не возникает.
- plan=business без параметров → выбирается «Бизнес» с дефолтной конфигурацией
1/1/1/5 = 10 000 ₸; plan=network → «Сеть»; неизвестный план → «Старт».
- Клик по плитке тарифа обновляет URL (history.replaceState), ссылку можно
сохранить/поделиться.
- Плитка «Старт» теперь «5 000 ₸/мес»; цены плиток без переноса (whitespace-nowrap).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Чужие СИСТЕМЫ УЧЁТА (МойСклад, 1С) убраны со всего публичного сайта: тексты,
KB, блог, FAQ, og-картинки, meta. Платёжные системы и маркетплейсы (Kaspi,
Wildberries, Ozon и т.п.) оставлены только как честная пометка «в разработке»
на /integrations и главной.
- /integrations, /import, главная, about, features, pricing, blog, kb/faq:
«перенос из МойСклад/1С» → «импорт каталога из файлов Excel/CSV»; импорт из
других систем учёта — «в разработке, в личном кабинете магазина».
- kb/pos-setup: касса для Windows и Linux, вход по адресу сервера + логин + PIN,
установка «скоро»; чековые принтеры 58/80 мм (ESC/POS).
- kb/billing: переписан под реальность — биллинг-UI/подписки в продукте нет
(заглушка до Phase 4); оплата вручную через поддержку, онлайн-оплата «в
разработке»; убраны несуществующие «Настройки→Подписка→История», e-mail-
напоминания, баннер, формула кредита, SLA. Цены «тг» → «₸».
- kb/faq: бэкапы «каждые 24 ч» → «регулярно»; «TLS 1.3» → «TLS»; формулировка
про ЗРК смягчена; «триал» → «бесплатный период».
- /status: убраны фиктивные зелёные индикаторы (мониторинга нет) — честная
пометка «в разработке» + контакты для инцидентов.
- /changelog: убран внутренний жаргон (Phase N, SuperAdmin, retention, tenant,
inline) — человеческие формулировки.
- og.mjs: сабтайтлы без чужих брендов; касса Windows и Linux; «триал»→«бесплатно».
- sitemap.xml: канонические URL со слэшем + статьи KB и блога.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
На реальном объёме перенесённого магазина список движений отдавал >2.5с из-за COUNT по
3-table join. Count теперь по stock_movements с фильтрами (join не меняет число строк).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- PosStore.FindAllByBarcode; SaleViewModel.ScanCandidates/IsScanPickerOpen + PickScanCandidate
- SaleView: модалка выбора; сервер by-barcode уже отдаёт items, веб-приёмка уже показывает выбор
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/migration-debts-top20.md: топ-20 долгов (контрагент-уровень) + находка (paymentout agent=орг)
- MoneyAccountsPage: строка «Начальный остаток при переносе из МойСклад на <дата>» для МС-счетов
- systemd food-market-migration-delta.timer (каждые 10 мин) — непрерывная дельта до перехода
- прогресс: раздел «Исправления по ревью» (все 4 пункта)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- create: артикул-коллизия → суффикс msId (перенести карточку, не падать на unique)
- create/update: ШК не уникален → добавляем код каждой карточке (пары товар|код против
повторов), дубли ловит «Проверка данных»
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DevDataSeeder по умолчанию (dev/stage) сидирует демо-орг; на проде SEED_DEMO_DATA=false
отключает демо-орг/demo-admin/привязку, сохраняя SuperAdmin+роли+глобальные ед.изм.+SystemSettings.
Нужно для чистого (пустого) старта реального магазина.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ProbeRunner: COM-порты (имя/VID/PID/описание через WMI), open→close (занят/свободен без
удержания), опрос весов Масса-К+CAS 9600 (таймаут 1.5с), принтеры Windows (имя/порт/драйвер,
БЕЗ печати), HID-клавиатуры (возможные сканеры). --port COMx — один порт; занятый не трогаем.
- --test-print "<принтер>": отдельный флаг, ESC/POS тест-чек «ТЕСТ ALL MARKET» через spooler
(winspool RAW). Без флага ничего не печатается.
- WinExe → AttachConsole(parent) для видимого вывода; --out пишет чистый UTF-8 JSON.
- System.Management (WMI, только Windows, под guard'ами). Проверено на Win11 VM: выход <10с,
COM1/принтер/HID определяются, test-print на несуществующий принтер — мягкая ошибка (код 4).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- StreamDocumentsAsync: Uri.EscapeDataString всего выражения updated>=<дата> (>= → %3E%3D),
не только даты — иначе часть форм 400/неоднозначна
- delta_sync: повторное чтение job после Succeeded (устранить гонку счётчиков), audit в try
- reconcile: розница демо/возвраты раздельно, URL-энкод фильтра чеков по месяцам
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ImportRetailReturnsAsync (retailsalesreturn): связь с чеком по externalId demand, +остаток
- ImportInventoriesAsync (inventory): Completed без движений, BookQty/ActualQty из calculated/quantity
- Все 7 импортёров принимают updatedFromMsk → filter=updated>= (МСК) для дельты
- Контроллер: ?updatedFrom=<МСК> прокинут во все типы
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
startOffset у всех документных импортов + эндпоинт ?offset=N: при продолжении
пропускаем уже перенесённые через offset API (а не перечитывая сотни страниц).
Идемпотентный skip по msId держит границу.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
По проверенному шаблону приёмок (идемпотентно по msId, резюмируемо, МСК→UTC, тиыны,
дизамбигуация номеров, SaveChanges по документу, движения остатков):
- enter (+остаток), loss (−остаток, WriteOff), purchasereturn (−остаток, SupplierReturn),
retaildemand (−остаток, RetailSale; оплата нал/безнал из cashSum/noCashSum, кассир=owner).
- Общие помощники LoadMaps/Aggregate. Эндпоинт POST /import-supplies?type=enter|loss|
purchasereturn|retaildemand (фон, job с нужным kind).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Номера МойСклада повторяются по годам, а у нас (OrganizationId, Number) уникален →
дубли роняли импорт. Сохраняем оригинальный номер где можно, при коллизии —
«<номер>-<год>[-N]». Оригинал всегда в MoySkladExternalId.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Сбой SaveChanges одного документа оставлял сущности tracked → все последующие
падали (каскад), импорт застревал. Теперь в catch — ChangeTracker.Clear().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Батч из 50 приёмок с общим товаром давал дубль строки stocks (ApplyMovementAsync
создавал новую строку, не видя ещё не сохранённую). Теперь: SaveChanges после КАЖДОГО
документа (следующий видит закоммиченные остатки) + агрегация позиций по товару в
документе (средневзвешенная цена) + ChangeTracker.Clear (не копим граф на 7k докумов).
Идемпотентно по msId — повторный запуск продолжает с места.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
МойСклад отдаёт sum/cashSum/price как число с точкой (900000.0) → long не парсился
(«JSON value could not be converted to System.Int64»). Поля DTO → decimal. Плюс
GET /api/admin/moysklad/jobs/{id} для наблюдения за прогрессом/ошибками переноса.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Тяжёлый expand=positions.assortment раскрывал весь товар в каждой позиции → страница
гигантская и зависала. assortment.meta.href в позиции есть и без раскрытия. Таймаут
HttpClient МойСклада поднят до 130с (страница иногда > 100с).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part 2 (документы) — проверенный шаблон на приёмках:
- Phase41: MoySkladExternalId у counterparties/stores/units/retail_points/employees и
документов (supplies/enters/losses/inventories/supplier_returns/retail_sales/
cash_shifts/money_transactions) + индексы. Свойства на доменных сущностях.
- MoySkladClient.StreamDocumentsAsync: документы в хронологии (order=moment,asc), expand
позиций/связей (limit=100), обработка 429/сбоев.
- MoySkladDocumentImportService.ImportSuppliesAsync: идемпотентно по msId, резюмируемо
(skip перенесённых), moment МСК→UTC, суммы из тиынов, поставщик по externalId контрагента,
товары по externalId, проведение → движения остатков (StockService). Журнал ошибок.
- Импорт контрагентов теперь пишет MoySkladExternalId (нужно для ссылок в документах).
- Эндпоинт POST /api/admin/moysklad/import-supplies (фон, job). Остальные типы — по шаблону.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Найдено Mac-Claude на стенде:
1. Генератор внутреннего EAN-13 давал «02…» (ведущий 0 → зона UPC-A, может совпасть
с реальным товаром). InternalBarcode.PickPrefix теперь берёт 2x из restricted-
circulation 20–29 (без ведущего нуля), не пересекаясь с весовыми префиксами орг;
«02» — только крайний резерв. Юнит-тест обновлён.
2. «Проверка данных» на Демо показывала все 0 и lastCheckedAt=null — ночной cron ещё
не отрабатывал после пересева. Добавлен разовый BackgroundJob.Enqueue(DataQualityJob)
при старте: после деплоя/пересева счётчики считаются сразу по всем организациям.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Часть 3 + автопоиск (данные sysadmin: касса МойСклад спамит «Порт не выбран»):
- Весовой товар при сканировании/плитке → вес с весов (если на связи), иначе
модалка ручного ввода веса. Сумма = цена/кг × вес; подпись «1,235 кг × 690 ₸/кг».
- ScaleMonitor (pos.core): опрос ТОЛЬКО по требованию (при весовом товаре), мягкая
деградация на ручной ввод, запись о потере/восстановлении связи РОВНО ОДИН раз
(не сотни «Таймаут»). Юнит-тесты (100 опросов → 1 запись).
- ScaleDiscovery (pos): автоскан всех COM-портов (вкл. USB-COM CH340/Prolific) ×
протоколы Масса-К/CAS × скорости; кнопка «Найти весы» в верхней панели; найденный
порт запоминается (kv) и восстанавливается при старте.
- UnitShort («шт»/«кг»/«л») протянут через sync→SQLite→PosProduct/CartLine; цена
весового показывается «690 ₸/кг» вместо кода ОКЕИ «690 ₸/796».
- Разбор этикеток в кассе — по настройкам орг (по умолчанию ВЫКЛ, точный штрихкод).
- Юнит-тесты 321/321 (обновлены PosBarcodeTests под дефолт-off).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Реальный сценарий владельца: весов с печатью этикеток нет, штрихкоды весовых —
обычные коды «27xxxxx» в карточке. Исправлены два бага:
- поиск по ШК матчил весовой PLU по АРТИКУЛУ (вода арт.12345 → 1,5 кг) — убрано,
поиск по штрихкоду больше нигде не матчит по артикулу;
- разбор весовых этикеток был дефолт-ON (WeightedBarcodeSettings.Enabled,
Organization.WeightBarcodeEnabled) и ловил обычные коды — теперь дефолт OFF.
- ByBarcode: сначала точное совпадение штрихкода (вкл. короткие «27xxxxx»),
разбор этикетки — только если включён в орг И точного ШК нет.
- Phase40: WeightBarcodeEnabled DEFAULT false + выключение у существующих орг;
WeightedCodeFormat('short'|'ean13')/WeightedCodePrefix(27)/WeightedCodeDigits(5).
- Генератор ШК нового весового товара: короткий «<prefix>+N цифр» (27xxxxx, next-free)
или EAN-13 по настройке; уникальность.
- Настройки орг отдают/принимают формат кода весовых.
- Sync отдаёт unitShort («шт»/«кг»/«л») рядом с unitCode (ОКЕИ) — shared UnitShort.
- Тесты: MobileApi (exact-only по умолчанию; не матчит артикул; PLU как ШК при вкл) 9/9;
unit WeightedBarcode/UnitShort 16/16.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ValidationFilter (FluentValidation-путь): русский заголовок «Проверьте
правильность заполнения» через ModelErrorTranslator вместо дефолтного англ.
«One or more validation errors occurred» (сообщения полей уже были русскими).
- stage-ui-uat-mobile.spec.ts: P1-экраны на 390px без h-scroll/console/5xx +
проверка «Остатки» (артикул/штрихкод, тумблер «показать нулевые»).
- docs/uat-report-2026-10-09.md: матрица покрытия (уже-зелёное vs проверено
сейчас) по требованию владельца; галерея скринов reports/uat-mobile/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
«Последние продажи»/Z-отчёт показывали времена вперёд, потому что дата чека
бралась от клиента (input.Date / POS OccurredAt) без нормализации: наивная
локальная дата или спешащие часы кассы давали будущий инстант. Добавил
BusinessTime.ToInstantUtc: Kind→UTC и clamp будущего (>5 мин) к now — чек не
может быть в будущем. Применено в рознице (create) и в приёме чеков с кассы
(движение берёт тот же инстант). Юнит-тесты на нормализацию/clamp.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CauseUserId в уведомлениях о минусах был null: StockService писал автора только
если вызывающий явно передал CreatedByUserId в черновике, а большинство
документов (касса/списание/перемещение/приёмка/…) его не передавали. Теперь
автор берётся из черновика, иначе — из текущего пользователя (ITenantContext.UserId)
в единой точке ApplyMovementAsync, поэтому пишется и в StockMovement, и в алерт
для всех типов документов. Тест: минус от списания → causeUserId = текущий юзер.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Неверный тип/enum в теле запроса давал пользователю технический англ. текст с
именем класса и путём ($.reason: The JSON value could not be converted to
foodmarket.Api…LossInput…). Теперь InvalidModelStateResponseFactory прогоняет
ModelState через ModelErrorTranslator: технические сообщения → «Неверное значение
поля «Причина»» (имя поля по-русски, без классов/путей/англ.), осмысленные
FluentValidation-сообщения остаются как есть. Сырой текст пишется только в лог
бэкенда. Действует на web (toast) и mobile API. Тест: списание с неверным enum.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Остатки строились ОТ таблицы Stocks (JOIN Products), поэтому товары без строки
Stock (ни разу не приходованные) не попадали в список вообще, а includeZero лишь
снимал фильтр Quantity!=0 у существующих строк. То же в отчёте «Остатки на дату»,
в экспорте и в фильтре по группе (iOS получал total 0).
- GetStock/ExportStock: выборка ОТ Products (не-услуги, не-архивные) с остатком
коррелированным подзапросом (товар без Stock = 0). Склад выбран → строка склада;
без склада → агрегат «Все склады». Нулевые видны при includeZero; negativeOnly и
пагинация/сортировка/поиск/экспорт работают и для нулевых. Остаток/ед./штрихкод
дотягиваются batch-запросом по id страницы (EF8 не переводит подзапросы в SELECT+ORDER BY).
- Отчёт «Остатки на дату»: includeZero добавляет товары без движений (Qty 0);
фильтр по группе теперь с вложенностью.
- groupId/productGroupId — синонимы с вложенными группами на всех списках остатков
и в каталоге (List + Export). Раньше catalog?productGroupId молча игнорировался.
- Штрихкод рядом с артикулом в списке/экспорте — различать одноимённые товары
(4× «BIOBLAS шампунь» с разными артикулами из МойСклада — не удалять).
- Demo (30 894 товара): без флага 12, includeZero → 30 894, <500мс/страница.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Раньше флагировались все товары в группах с ParentId==null (включая реальные
верхнеуровневые категории вроде «Молочные»). Теперь — только товары в служебной
группе «Все товары»/«Без группы». На seed-demo: было 50 (ложно), стало 3 (верно).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Страница /catalog/data-quality: счётчики по проверкам, детальные списки, массовое
исправление (задать группу/цену, сгенерировать штрихкоды), «это разные товары» для
дублей, пересчёт, тумблеры отключения проверок. Ненавязчивый бейдж «N товаров требуют
внимания» на дашборде (исчезает при 0). Пункт меню «Проверка данных» в каталоге.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hangfire DataQualityJob (ночью 02:20 UTC) считает по орг. счётчики проблем: без группы
(корень/«Без группы»), без цены продажи, без штрихкода (кроме услуг), дубль штрихкода,
возможный дубль имя+единица; товары в минусе — ссылкой на центр (не дублируем). Результаты
в data_quality_results (Phase43), живые детальные списки — по запросу.
API /api/data-quality: сводка, {check}/products, recompute, dismiss («это разные товары»),
bulk set-group/set-price/generate-barcodes, PUT settings (отключение проверок). unknown-cost
отложен до FIFO; PLU убран (поправка владельца). Integration-тест: счётчики/список/bulk/dismiss.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
П.2 (POS): закрытие смены печатает Z-отчёт с разбивкой по товарам (продано/возврат)
через ESC/POS; кнопка «Печать Z-отчёта (по товарам)». ShiftReportDto += byProduct/overLimit24h.
П.1 (POS): первая продажа без открытой смены (онлайн) — диалог «Открыть смену. Размен?
(можно пропустить)» → автооткрытие и переход к оплате; офлайн — как было.
П.10: full-chain обновлён — продажа без смены теперь автооткрытие (не 409) + проверка
byProduct в Z-отчёте; прогон на стенде 16 ✓ / 0 ✗.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Скан неизвестного штрихкода (онлайн) → диалог «Новый товар» (наименование + опц.
цена) → POST /products/quick → товар сразу в чеке; попадёт в локальный каталог на
ближайшем синке. Офлайн — как было («не найден»). Smoke/uitest зелёные.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
POS: чек с точки без открытой смены — сервер автооткрывает смену (Cash-счёт точки,
размен 0) и принимает чек (как в вебе). Фоновый ShiftWatchJob (раз в час) уведомляет
кассира и админов о сменах, открытых дольше 24ч (персистентное уведомление + SignalR,
дедуп раз в сутки). Тест POS-автооткрытия.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /api/documents/{type}/{id}/related — основание + связанные (возвраты/оплаты/
опр-спис/возвраты по чеку) для supply/supplier-return/retail-sale/inventory/enter/loss.
Компонент RelatedDocsBlock применён на edit-страницах приёмки, возврата поставщику,
оприходования, списания, чека (инвентаризация имела свой блок). Integration-тест:
приёмка↔возврат (related/basedOn).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Веб Z-отчёт показывает byProduct (товар/продано/выручка/возвраты) и баннер «смена
открыта дольше 24ч». Печать уже была (window.print()).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Проведённая приёмка → «Создать возврат» вызывает from-supply (преднабор строк) и
открывает черновик возврата поставщику. Вместе с секцией оплат приёмки (supply→
payment) и ссылками-основаниями в DTO — развивает «создать на основании».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>