fix(money/seed): back-fill money-прав кассиру и статей расходов для старых орг
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions

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 <noreply@anthropic.com>
This commit is contained in:
nns 2026-10-07 22:16:46 +05:00
parent deef65d819
commit 7e3841ad48

View file

@ -135,6 +135,43 @@ public async Task StartAsync(CancellationToken ct)
db.SystemSettings.Add(new SystemSettings { ArchiveRetentionDays = 30 }); db.SystemSettings.Add(new SystemSettings { ArchiveRetentionDays = 30 });
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
} }
// Sprint 33: бэкофилл дефолтов модуля «Деньги» для УЖЕ существующих
// организаций (их роли/статьи были засижены до появления модуля —
// SeedEmployeeRolesAsync их пропускает по guard'у `anyRole`). Без этого
// на stage/prod кассир демо-орги получил бы 403 на /api/money/shifts.
await EnsureMoneyDefaultsAsync(db, ct);
}
/// <summary>Идемпотентный back-fill модуля «Деньги» по всем организациям:
/// системной роли «Кассир» проставляет MoneyView + CashShiftOperate (если
/// ещё не стоят), и гарантирует 5 системных статей расходов в каждой орге.
/// Нужно только для орг, созданных ДО Sprint 33 — у новых всё ставит
/// <see cref="SeedEmployeeRolesAsync"/>.</summary>
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);
} }
/// <summary>Привязывает существующего admin@food-market.local к /// <summary>Привязывает существующего admin@food-market.local к