From 7e3841ad4816a5df5645f2b952328b031537a14c Mon Sep 17 00:00:00 2001 From: nns Date: Wed, 7 Oct 2026 22:16:46 +0500 Subject: [PATCH] =?UTF-8?q?fix(money/seed):=20back-fill=20money-=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=20=D0=BA=D0=B0=D1=81=D1=81=D0=B8=D1=80=D1=83?= =?UTF-8?q?=20=D0=B8=20=D1=81=D1=82=D0=B0=D1=82=D0=B5=D0=B9=20=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=85=D0=BE=D0=B4=D0=BE=D0=B2=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=80=D1=8B=D1=85=20=D0=BE=D1=80=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SeedEmployeeRolesAsync пропускает уже засиженные орги (guard anyRole), поэтому демо-орга на stage/prod осталась бы без MoneyView/CashShiftOperate у кассира → 403 на /api/money/shifts и красный menu-walk. EnsureMoneyDefaultsAsync (идемпотентно, во всех env на старте) проставляет эти флаги системной роли «Кассир» по всем оргам и гарантирует 5 системных статей расходов. Прочие права кассира сохраняются (in-place мутация owned JSON). DemoSeederIdempotency 2/2 ✓. Co-Authored-By: Claude Opus 4.8 --- src/food-market.api/Seed/DevDataSeeder.cs | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/food-market.api/Seed/DevDataSeeder.cs b/src/food-market.api/Seed/DevDataSeeder.cs index a2055af..20ce0fe 100644 --- a/src/food-market.api/Seed/DevDataSeeder.cs +++ b/src/food-market.api/Seed/DevDataSeeder.cs @@ -135,6 +135,43 @@ public async Task StartAsync(CancellationToken ct) db.SystemSettings.Add(new SystemSettings { ArchiveRetentionDays = 30 }); await db.SaveChangesAsync(ct); } + + // Sprint 33: бэкофилл дефолтов модуля «Деньги» для УЖЕ существующих + // организаций (их роли/статьи были засижены до появления модуля — + // SeedEmployeeRolesAsync их пропускает по guard'у `anyRole`). Без этого + // на stage/prod кассир демо-орги получил бы 403 на /api/money/shifts. + await EnsureMoneyDefaultsAsync(db, ct); + } + + /// Идемпотентный back-fill модуля «Деньги» по всем организациям: + /// системной роли «Кассир» проставляет MoneyView + CashShiftOperate (если + /// ещё не стоят), и гарантирует 5 системных статей расходов в каждой орге. + /// Нужно только для орг, созданных ДО Sprint 33 — у новых всё ставит + /// . + private static async Task EnsureMoneyDefaultsAsync(AppDbContext db, CancellationToken ct) + { + var cashierRoles = await db.EmployeeRoles.IgnoreQueryFilters() + .Where(r => r.IsSystem && r.Name == "Кассир") + .ToListAsync(ct); + var changed = false; + foreach (var role in cashierRoles) + { + if (!role.Permissions.MoneyView || !role.Permissions.CashShiftOperate) + { + // Permissions — owned-тип; мутация скалярных флагов на + // отслеживаемой сущности ловится DetectChanges при SaveChanges. + // Остальные права кассира (ProductsView/RetailSalesOperate/…) + // сохраняются — мы их не трогаем. + role.Permissions.MoneyView = true; + role.Permissions.CashShiftOperate = true; + changed = true; + } + } + if (changed) await db.SaveChangesAsync(ct); + + var orgIds = await db.Organizations.IgnoreQueryFilters().Select(o => o.Id).ToListAsync(ct); + foreach (var orgId in orgIds) + await SeedExpenseItemsAsync(db, orgId, ct); } /// Привязывает существующего admin@food-market.local к