docs(stage): итоговый отчёт — все 14 пунктов ✓ (94/94 шагов зелёные)

13 stage-сценариев, 94 шага — все green. 6 фиксов в проде:
- EF8 nav-collection (6 контроллеров)
- UNIQUE Article на products
- DateTime Kind=Unspecified→UTC (reports + audit-log)
- Enter.Post → Product.Cost (moving average)
- ABC Pareto по cumBefore
- Swagger operationIds+schemaIds

3 logic gap'a зафиксировано (не P0): stage-public→прод admin,
Enter/Loss без RowVersion, нет CSV-импорта Inventory.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
nns 2026-05-29 17:59:04 +05:00
parent a0b985178b
commit d89d6bf1dc

View file

@ -31,7 +31,7 @@
- [x] **11. OrgAuditLog** — UI /audit-log, CRUD продукта → запись с diff; multi-tenant строго. *(stage-audit-log.yml: 7/7 ✓)* - [x] **11. OrgAuditLog** — UI /audit-log, CRUD продукта → запись с diff; multi-tenant строго. *(stage-audit-log.yml: 7/7 ✓)*
- [x] **12. 2FA TOTP** — enroll (QR), verify, login (двухшаговый), disable; невалидный код → 400. *(stage-2fa.yml: 6/6 ✓)* - [x] **12. 2FA TOTP** — enroll (QR), verify, login (двухшаговый), disable; невалидный код → 400. *(stage-2fa.yml: 6/6 ✓)*
- [x] **13. OpenAPI/Swagger** — все новые контроллеры в /swagger/v1/swagger.json. *(stage-swagger.yml: 3/3 ✓ локально, 117 paths)* - [x] **13. OpenAPI/Swagger** — все новые контроллеры в /swagger/v1/swagger.json. *(stage-swagger.yml: 3/3 ✓ локально, 117 paths)*
- [ ] **14. POS Sync API**`POST /api/pos/sync` (dev-token), `POST /api/pos/sales` (idempotency-key — повтор не дублирует). - [x] **14. POS Sync API**`POST /api/pos/sync` (dev-token), `POST /api/pos/sales` (idempotency-key — повтор не дублирует). *(stage-pos.yml: 7/7 ✓)*
## Журнал ## Журнал
@ -40,6 +40,47 @@
- Проверена доступность `https://test.admin.food-market.kz/health/ready` → 200. - Проверена доступность `https://test.admin.food-market.kz/health/ready` → 200.
- Создан этот файл из задания. - Создан этот файл из задания.
### 2026-05-29 — ИТОГ ✓
Все 14 пунктов чек-листа пройдены. Stage-стенд `https://test.admin.food-market.kz` принимает все новые модули + UI/API контракты, 13 сценариев (94 шага) — все зелёные после последнего deploy-stage.
**Итоговый прогон 13 сценариев:**
| Сценарий | Шагов |
|---|---:|
| stage-smoke | 5 ✓ |
| stage-catalog | 6 ✓ |
| stage-enter | 10 ✓ |
| stage-loss | 8 ✓ |
| stage-transfer | 7 ✓ |
| stage-inventory | 8 ✓ |
| stage-customer-return | 6 ✓ |
| stage-supplier-return | 8 ✓ |
| stage-demand | 8 ✓ |
| stage-reports | 8 ✓ |
| stage-audit-log | 7 ✓ |
| stage-2fa | 6 ✓ |
| stage-pos | 7 ✓ |
| **Итого** | **94 ✓ / 0 ✗** |
**Фиксы в продакшн-коде:**
1. **EF8 nav-collection bug на 6 контроллерах** (Products, Enters, Losses, Transfers, SupplierReturns, Inventories) — добавление новой строки/штрихкода к существующему документу/продукту валилось `DbUpdateConcurrencyException` («Товар изменён в другом окне», 0 rows affected) даже без конкурентной правки. Тот же EF8-баг, что в TD-6 чинили на Supplies/Demands/RetailSales: nav-collection.Add+client-side Id путает EF, UPDATE родителя получает 0 affected. Чиним паттерном `ExecuteDelete + DbSet.Add` (минует трекер). [`d54e1cb`, `4e15359`]
2. **UNIQUE индекс на `(OrganizationId, Article)` у products** — индекс существовал, но не был UNIQUE, поэтому catch-блок на `IX_products_OrganizationId_Article` в ProductsController никогда не срабатывал. Дубликаты артикулов проскакивали. Миграция `Phase8d` делает индекс уникальным; existing-дубликаты нумеруются `-2/-3/…`. [`d54e1cb`]
3. **DateTime Kind=Unspecified → UTC** в Sales/Profit/ABC/Stock-репортах и /audit-log — ASP.NET парсит ISO-даты с `Kind=Unspecified`, Npgsql 8 отказывается слать такие в `timestamp with time zone` → 500. Принудительно конвертим Unspecified→UTC, Local→ToUniversalTime. [`97d5ae5`, `6a5bb52`]
4. **Enter.Post пересчитывает Product.Cost** по скользящему среднему — раньше товары, попавшие через Оприходование (а не Supply), имели Cost=0 → Profit/ABC показывали cost=0, неверную маржу. Применяем ту же формулу `MovingAverageCost.Compute` что в Supply.Post. [`97d5ae5`]
5. **ABC Парето-граница по cumBefore** (а не cumAfter) — единственный товар с cumShare=100% валился в класс C, хотя полностью покрывает Парето. Стандартный алгоритм: товар принадлежит классу A, если он нужен чтобы пересечь порог 80% (`cumBefore < 80%`). [`97d5ae5`]
6. **Swagger OperationIds + SchemaIds**`/swagger/v1/swagger.json` валился в Development. `CustomOperationIds` падал NRE на минимальных API (/health, /metrics, /connect/*) без ключа `action` в RouteValues; `CustomSchemaIds` падал NRE на типах с FullName=null (generic-параметры). Добавили TryGetValue с fallback на RelativePath и `??` для FullName. После фикса 117 paths, schemaId без дубликатов. [`466595b`]
**Logic gaps зафиксированы:**
- Stage-public (`test.food-market.kz`) использует прод-build → форма signup POST'ит в **прод admin**. Для stage-testing регистрируемся напрямую POST на `test.admin.food-market.kz/api/auth/signup` (сценарии так и делают). Для отдельной публичной воронки stage потребует deploy с `PUBLIC_APP_URL=https://test.admin.food-market.kz`.
- **Enter/Loss/SupplierReturn/CustomerReturn без IVersionedEntity** — TD-6 добавил xmin только на Supply/Demand/RetailSale/Transfer/InventoryDoc. На Enter параллельные PUT'ы возможны без конфликта (last-write-wins). Не критично пока редактор однопользовательский, но желательно унифицировать.
- **CSV-импорт фактического количества в Inventory не реализован** — все строки только через JSON-API POST/PUT. Для оператора склада неудобно: реальная инвентаризация = сканер штрихкодов или Excel-таблица. Желательно `POST /api/inventory/inventories/{id}/import-actual` с multipart CSV `{barcode|article, actualQty}`.
**Stage конфиг (вне репо):**
- `~/food-market-stage/deploy/docker-compose.yml` — добавлены env `RateLimiting__PerMinute=200`, `RateLimiting__PerHour=2000` чтобы e2e-сценарии (≥ 2 signup'a на org) не упирались в дефолтный 5/min, 20/hour. На прод-стенд эти env *НЕ* применяются.
### 2026-05-29 — пункт 2 ✓ (2 фикса в проде) ### 2026-05-29 — пункт 2 ✓ (2 фикса в проде)
- Сценарий `stage-catalog` (6 шагов): 2 org через signup, продукты CRUD, контрагенты CRUD, multi-tenant изоляция. - Сценарий `stage-catalog` (6 шагов): 2 org через signup, продукты CRUD, контрагенты CRUD, multi-tenant изоляция.