# Stage Testing Progress — test.admin.food-market.kz

Автономный прогон тестирования новых модулей на изолированном stage-стенде.
Стенд: https://test.admin.food-market.kz (отдельная БД, миграции применены).
Публичный сайт: https://test.food-market.kz.

Запуск: 2026-05-29. Исполнитель: Claude Opus 4.7 (автономный режим).

## Принципы

- Один пункт = один UI/API-сценарий + поиск багов + фикс + redeploy + retest.
- Сценарии складываются в `tests/e2e/scenarios/stage-{module}.{yml,steps.ts}`.
- Отчёты — `tests/e2e/reports/stage-{module}-{timestamp}.md`.
- После каждого набора фиксов — `~/deploy-stage.sh` (build+push+redeploy+health-wait).
- UX-проблемы (нет confirm на delete, ошибки не показываются юзеру и т.п.) трактуются как баги.
- Multi-tenant утечка — P0, фикс немедленно.
- Не трогать: global.json, gateway nginx, POS WPF, прод-стек.

## Чек-лист

- [x] **1. Smoke + signup flow** — signup создаёт org "TestStage", bootstrap (магазин/роли/единицы/группа "Все товары"), логин даёт access+refresh. *(stage-smoke.yml: 5/5 ✓)*
- [x] **2. Каталог (товары/группы/контрагенты)** — UI CRUD, дубликаты, дочерние группы, FK-защита, multi-tenant изоляция (2 org). *(stage-catalog.yml: 6/6 ✓, 2 фикса)*
- [ ] **3. Склад. Enter (Оприходование)** — UI создание/проведение/Unpost → Stock + StockMovement; RowVersion concurrency; multi-tenant.
- [ ] **4. Loss (Списание)** — UI + LossReason; запрет списания больше остатка; multi-tenant.
- [ ] **5. Transfer (Перемещение)** — два склада, From!=To, atomic post, Unpost без orphan-движений; multi-tenant.
- [ ] **6. Inventory (Инвентаризация)** — bookQty подгрузка, импорт CSV факта, корректирующий StockMovement на diff; multi-tenant.
- [ ] **7. CustomerReturn (Возврат от покупателя)** — UI из RetailSale, по чеку и без; возврат больше продано → 400.
- [ ] **8. SupplierReturn (Возврат поставщику)** — аналог для Supply.
- [ ] **9. Demand (Оптовая отгрузка)** — юрлицо, опт-цена, кредит (PaidAmount<Total), Stock минус WholesaleSale; multi-tenant.
- [ ] **10. Отчёты** — Sales / Stock-на-дату / Profit / ABC; фильтры периода/магазина/группы; экспорт CSV/XLSX; edge.
- [ ] **11. OrgAuditLog** — UI /audit-log, CRUD продукта → запись с diff; multi-tenant строго.
- [ ] **12. 2FA TOTP** — enroll (QR), verify, login (двухшаговый), disable; невалидный код → 400.
- [ ] **13. OpenAPI/Swagger** — все новые контроллеры в /swagger/v1/swagger.json.
- [ ] **14. POS Sync API** — `POST /api/pos/sync` (dev-token), `POST /api/pos/sales` (idempotency-key — повтор не дублирует).

## Журнал

### 2026-05-29 — старт

- Проверена доступность `https://test.admin.food-market.kz/health/ready` → 200.
- Создан этот файл из задания.

### 2026-05-29 — пункт 2 ✓ (2 фикса в проде)

- Сценарий `stage-catalog` (6 шагов): 2 org через signup, продукты CRUD, контрагенты CRUD, multi-tenant изоляция.
- **Bug fix #1 — PUT product с новым barcode'ом валился 409.** Тот же EF8-баг что в TD-6 на документах: nav-collection.Add при наличии client-side Id даёт UPDATE 0 affected. Чиним паттерном ExecuteDelete+DbSet.Add — как в Supplies/Demands/RetailSales. Воспроизведение: создал продукт с 1 barcode → PUT с 2 barcodes → 409. Фикс → 204.
- **Bug fix #2 — IX_products_OrganizationId_Article не уникальный.** Контроллер ловил нарушение по имени индекса (`catch (DbUpdateException ex) when (...Contains("IX_products_..."))`), но индекс был неуникальным. Дубликаты артикулов проскакивали. Миграция Phase8d делает индекс UNIQUE; existing-дубликаты нумеруются `-2/-3/…`.
- Multi-tenant изоляция строгая: org B → 404 на products/counterparties org A (GET/PUT/DELETE), списки пустые.
- Commit `d54e1cb`. Deploy на stage прошёл, retest зелёный.

### 2026-05-29 — пункт 1 ✓

- Сценарий `stage-smoke` (5 шагов): `tests/e2e/scenarios/stage-smoke.{yml,steps.ts}`.
- `POST /api/auth/signup` — счастливый путь, дубликат email, короткий пароль, пустое название, кривой телефон. Все валидации работают, сообщения локализованы, rate-limiter на signup активен (5+ запросов в окне → 429).
- Bootstrap новой org: Store `MAIN`, EmployeeRole `Администратор` (system), 5 enabled Units, корневая `Все товары`, retail PriceType, RetailPoint `Касса 1` — всё на месте.
- Логин → JWT с `org_id`, `role=Admin`, refresh_token выпускается (offline_access).
- **Logic gap (informational):** stage-public (test.food-market.kz) использует прод-build, поэтому его форма signup POST'ит в **прод admin** (admin.food-market.kz). Для stage-testing регистрируемся напрямую через `test.admin.food-market.kz/api/auth/signup` (как и делает сценарий). Если потребуется тестовая публичная воронка — отдельный stage-public с `PUBLIC_APP_URL=https://test.admin.food-market.kz` в build-args.
