# Порт улучшений накладных/инвентаризации из sklad (05.10.2026) Источник: `~/ref/sklad` (read-only), handoff: `~/ref/sklad/docs/handoff-2026-10-05-nakladnye-inventarizaciya.md`, диапазон коммитов `f455fa9..b7eb637` + `3ced706` (readme). Правило: ничего из МойСклад — только наша БД (`Product`, `ProductBarcode`), наши документы `Supply` (как InvoiceUpload-черновик→Supply) и `Inventory`, наши права (`SuppliesEdit`, `InventoryEdit`, роли Кладовщик/Администратор). ## Задачи - [x] 1. Серверное автосохранение правок накладной + индикатор + lines до черновика + refetchOnWindowFocus:false - [x] 2. Общее поле скана с панелью «в какую строку?» (цветные сообщения, linesRef) - [x] 3. CameraScanner без перезапуска + глобальный hardware keydown на страницах накл/приёмки/инв - [x] 4. Фото-приёмка (PhotoSession + shots.ts + upload-photos + Claude photos + EAN + миниатюры) - [x] 5. Хранение фото: удаление на draft/delete, 24h для брошенных; без KeepFilesDays - [x] 6. «Файлы» и «камера» в обоих способах приёмки - [x] 7. Excel/CSV через TableReader + шаблон xlsx в docs/ - [x] 8. Инвентаризация: камера + видимое поле + импорт Excel/CSV - [x] 9. Каталог без простоев: индекс штрихкода ✓ (unique `IX_product_barcodes_OrganizationId_Code`), gzip через `UseResponseCompression`, ETag/If-None-Match на `mobile-catalog`. Синтетический замер 31 000 позиций: JSON 3.60 МБ → gzip 0.98 МБ (ratio 27%). - [x] 10. Тесты: unit — EanTests (6), TableReaderTests (5), ClaudeCliInvoicePhotosParseTests (4), InvoicePhotoRetentionTests (3). Build integration/e2e — зелёный. - [x] 11. deploy-stage, ретест, docs/supply-inventory.md ## Решения владельца - **Хранение фото:** НЕ копить. Удалять при создании Supply-черновика, при удалении InvoiceUpload, и через 24 часа для брошенных «на проверке». Никакого `Invoices:KeepFilesDays`. - **Модель Claude:** `claude-sonnet-5-5` (Haiku сочиняет данные). - **Тайм-аут фото-пачки:** `Invoices:ClaudeCli:PhotoTimeoutSeconds` = 600 с. - **Каталог:** наша БД, права `SuppliesEdit`/`InventoryEdit`. - **gateway:** лимит запроса не трогал самостоятельно; если фото-пачка не пройдёт на стенде по размеру — поднять `client_max_body_size` до 64M (сейчас, вероятно, 10M-32M). ## Что лежит где ### Backend | Что | Файл | |---|---| | Контрольная цифра EAN | `src/food-market.api/Infrastructure/Invoices/Ean.cs` | | Табличная читалка .xlsx/.csv + парсер шаблона | `src/food-market.api/Infrastructure/Invoices/TableReader.cs` | | InvoiceLine +Photo/BarcodeSource/Note, PhotoHint, IInvoiceExtractor.ExtractPhotosAsync | `.../InvoiceModels.cs` | | InvoiceMatcher.FindByBarcode | `.../InvoiceMatcher.cs` | | InvoiceProcessor: ветки photos/excel/vision, MatchFromPhoto, SweepAbandonedPhotos, DeleteStored | `.../InvoiceProcessor.cs` | | ClaudeCli ExtractPhotosAsync + BuildPhotosPrompt + FromPhotosJson; дефолт `claude-sonnet-5-5` | `.../ClaudeCliInvoiceExtractor.cs` | | `POST /invoices/upload-photos` + `GET /invoices/{id}/photo/{n}`; apply/delete убирают папку | `Controllers/Purchases/InvoicesController.cs` | | `POST /inventory/inventories/import` (xlsx/csv → {found, unknown}) | `Controllers/Inventory/InventoriesController.cs` | | gzip response-compression + ETag на `mobile-catalog` | `Program.cs`, `InventoriesController.cs` | ### Frontend | Что | Файл | |---|---| | Общий модуль ZXing TRY_HARDER + compress 2400/0.85 + fileToCanvas | `src/food-market.web/src/lib/shots.ts` | | Полноэкранная пачка снимков (камера + готовые фото) | `.../components/PhotoSession.tsx` | | CameraScanner: `onCodeRef` + эффект `[]` (нет перезапуска камеры) | `.../components/CameraScanner.tsx` | | InvoicesPage: `Снять камерой / Загрузить файл / Сфотографировать листы / Фото товаров+накладная` | `.../pages/InvoicesPage.tsx` | | InvoiceReviewPage: автосохранение 700 мс + индикатор + row-picker + linesRef + глобальный keydown + миниатюры | `.../pages/InvoiceReviewPage.tsx` | | SupplyScanPage: глобальный keydown (аппаратный сканер вне фокуса) | `.../pages/SupplyScanPage.tsx` | | InventoryMobilePage: сканер-поле на виду + импорт Excel/CSV + глобальный keydown | `.../pages/InventoryMobilePage.tsx` | | Шаблон для поставщиков (xlsx, 9-я строка — заголовки) | `docs/supplier-invoice-template.xlsx` (`scripts/make_supplier_template.py`) | ### Тесты | Что | Файл | |---|---| | EAN корректность + 1-digit error ловится | `tests/food-market.UnitTests/EanTests.cs` | | xlsx/csv UTF-8/1251 + шаблон + .xls friendly error | `tests/food-market.UnitTests/TableReaderTests.cs` | | JSON ответа фото-пачки + Note из comment + unmatched → Warnings | `tests/food-market.UnitTests/ClaudeCliInvoicePhotosParseTests.cs` | | DeleteStored: файл, папка, идемпотентность | `tests/food-market.UnitTests/InvoicePhotoRetentionTests.cs` | Пре-существующий отказ `ApiReferenceDocsJobTests.Scans_endpoints_with_doubly_nested_generic_return` не связан с портом (Sprint 28, падал до изменений). ## Deploy и retest - Commit `6b0fc68` → `~/deploy-stage.sh` → stage зелёный на https://test.admin.food-market.kz - Smoke по новым endpoint'ам: - `POST /api/purchases/invoices/upload-photos` без файлов → 400 «Нет снимков.» (ранее 404) - `GET /api/inventory/inventories/mobile-catalog` под gzip: JSON 3.44 МБ (31 269 ШК на Demo) → gzip 1.75 МБ, Content-Encoding: gzip, Vary: Accept-Encoding ETag `"inv-cat-f7abeb59d181533a-all"`, Cache-Control `private, max-age=60` - Повтор с `If-None-Match` → `HTTP/2 304` - `tests/e2e/scenarios/stage-ui-menu-walk.spec.ts` на стенде — **3/3 passed, 3.7 min**, 0 4xx/5xx/toast.error/console.error у demo-admin (28 пунктов), demo-keeper (17), demo-cashier (7). ## Журнал - 05.10.2026: порт выполнен в один проход; API build зелёный, web tsc+vite build зелёный, 18 новых unit-тестов зелёные. deploy-stage зелёный, menu-walk 3/3 зелёный.