# 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-воркер, не сидер |