food-market/docs/seeder-noise-progress.md
nns 99bf6c9f3e
Some checks failed
Auto-tag / Create date-tag (push) Has been cancelled
CI / Backend (.NET 8) (push) Has been cancelled
CI / Web (React + Vite) (push) Has been cancelled
CI / POS (WPF, Windows) (push) Has been cancelled
Docker API / Build + push API (push) Has been cancelled
Docker API / Deploy API on stage (push) Has been cancelled
fix(seed): DemoTenantSeeder гардит по наличию ЛЮБЫХ товаров в орге
Старый idempotency-маркер — Product с Article LIKE 'DEMO-%'. После того как
«Демо-магазин» на стенде наполнили реальным каталогом из МойСклада
(артикулы не DEMO-*), маркер срабатывал false и QuickLoginDemoSeeder при
каждом рестарте стенда пытался залить фиктивный каталог заново — падая
на unique-индексе IX_product_barcodes_OrganizationId_Code (23505). В БД
ничего лишнего не попадало (транзакция откатывалась), но лог stage-api
получал 9 ERR в сутки.

Фикс: SeedAsync и PeekAsync считают «уже сидировано» при любом Product в
орге. Поведение из UI (POST /api/admin/seed-demo) не меняется для свежих
org'ов. Прочие стартовые сидеры (OpenIddictClientSeeder,
SystemReferenceSeeder, DevDataSeeder) и HangfireJobsConfigurator — уже
идемпотентны, аудит в docs/seeder-noise-progress.md.

Integration-тест:
- SeedDemo_second_call_on_populated_org_returns_already_seeded
- SeedDemo_on_org_with_imported_catalog_skips_without_duplicate_barcode

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-06 00:50:50 +05:00

51 lines
3.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Seeder noise cleanup — прогресс
Контекст: при каждом старте `food-market-stage-api` в логах появлялись `ERR`
от `QuickLoginDemoSeeder → DemoTenantSeeder.SeedAsync` — повторная попытка
залить фиктивный DEMO-каталог в «Демо-магазин», падающая на unique-индексе
`IX_product_barcodes_OrganizationId_Code` (23505). В БД ничего лишнего не
попадало (транзакция откатывалась), но лог был зашумлён ~9 строк/сутки.
Корневая причина: idempotency-маркер `DemoTenantSeeder` — наличие Product
с `Article LIKE 'DEMO-%'`. После того как «Демо-магазин» наполнили настоящим
каталогом из МойСклада (у товаров есть `MoySkladExternalId`, а артикулы
не начинаются с `DEMO-`), маркер срабатывал false → сидер шёл заново,
генерировал новые Product'ы с теми же EAN-13 (seed 200_000_000_001…), и
упирался в unique по `(OrganizationId, Code)` на первом же `SaveChangesAsync`.
## Чеклист
- [x] 1. `DemoTenantSeeder.SeedAsync` ничего не делает, если в организации уже
есть любые товары (а не только `DEMO-*`). Демо-магазин с реальным
каталогом из МойСклада не трогаем.
- [x] 2. Остальные стартовые сидеры/джобы проверены — нет повторных вставок.
- [x] 3. Integration-тест: повторный запуск сидера на организации с товарами
не пишет и не падает.
- [x] 4. `~/deploy-stage.sh`, логи stage-api после старта — 0 строк `ERR`/`fail`.
## Что меняем
1. В `DemoTenantSeeder.SeedAsync` idempotency check расширяется: сейчас
«уже сидировано» = Product с `Article LIKE 'DEMO-%'`. Новая логика —
«уже сидировано» = любой Product в орге (`_db.Products.AnyAsync(p ⇒
p.OrganizationId == orgId)`). При таком раскладе `CountExistingAsync`
вернёт реальные count'ы, `AlreadySeeded = true`, никаких вставок.
2. Поведение из UI (`POST /api/admin/seed-demo`) не меняется для свежих
org'ов. Для орги с товарами возвращаем summary без вставок — ровно
как и раньше, только маркер стал шире.
3. `YearDemoSeeder` уже и так защищён `hasActivity` гардом (Supply или
RetailSale) — к нему претензий нет.
## Какие сидеры/джобы проверены
| Hosted service | Повторные вставки при каждом старте? |
|---|---|
| `OpenIddictClientSeeder` | нет — проверяет `FindByClientIdAsync` + update |
| `SystemReferenceSeeder` | нет — все вставки через `AnyAsync` / `FirstOrDefault` guard |
| `DevDataSeeder` | нет — все вставки идемпотентны (роли/юзеры/единицы/org/stores) |
| `QuickLoginDemoSeeder` | один путь (`TryFillCatalogAsync`) чинится в задаче — он здесь корень шума |
| `HangfireJobsConfigurator` | нет — `AddOrUpdate` по cron-id |
| `HangfireGlobalFilterRegistrar` | нет — pure в-процесс конфиг |
| `InvoiceWorker` | polling-воркер, не сидер |
| `ReferencePriceRefreshJob` | polling-воркер, не сидер |