From b40d1d983501715f7a3737d692e3e7a8d8aeed51 Mon Sep 17 00:00:00 2001 From: nns <278048682+nurdotnet@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:01:44 +0500 Subject: [PATCH] =?UTF-8?q?feat(seed):=20system=20roles=20per=20organizati?= =?UTF-8?q?on=20+=20map=20admin=20=E2=86=92=20Employee?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DevDataSeeder.SeedEmployeeRolesAsync — 6 системных ролей с готовыми наборами Permissions: - Администратор — RolePermissions.All() (все 21 флаг) - Менеджер — каталог + закупки + контрагенты + отчёты + остатки - Кладовщик — приёмки + остатки + view товаров - Кассир — продажи + view товаров (привязка к кассе на UI-этапе) - Закупщик — закупки + контрагенты + view товаров - Бухгалтер — все *View, никаких edit IsSystem=true, SortOrder сохраняет порядок отображения в селектах. Сидируется один раз per организацию (anyRole? skip) — чтобы кастомные правки галок админа не сбрасывались на каждый старт. SeedAdminEmployeeAsync — после создания admin@food-market.local (SuperAdmin Identity user) заводит Employee-запись с ролью «Администратор» в Demo Market организации, чтобы UI «Сотрудники» сразу показывал учётку, а не пустой список. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/food-market.api/Seed/DevDataSeeder.cs | 118 ++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/food-market.api/Seed/DevDataSeeder.cs b/src/food-market.api/Seed/DevDataSeeder.cs index 1b66f00..af46b76 100644 --- a/src/food-market.api/Seed/DevDataSeeder.cs +++ b/src/food-market.api/Seed/DevDataSeeder.cs @@ -81,6 +81,34 @@ public async Task StartAsync(CancellationToken ct) await userMgr.AddToRoleAsync(admin, SystemRoles.Admin); } } + + await SeedAdminEmployeeAsync(db, demoOrg.Id, admin?.Id, ct); + } + + /// Привязывает существующего admin@food-market.local к + /// Employee-записи с системной ролью «Администратор» — чтобы UI «Сотрудники» + /// сразу показывал учётку с правильной ролью, а не пустой список. + private static async Task SeedAdminEmployeeAsync(AppDbContext db, Guid orgId, Guid? adminUserId, CancellationToken ct) + { + if (adminUserId is null) return; + var existing = await db.Employees.IgnoreQueryFilters() + .FirstOrDefaultAsync(e => e.OrganizationId == orgId && e.UserId == adminUserId, ct); + if (existing is not null) return; + var adminRole = await db.EmployeeRoles.IgnoreQueryFilters() + .FirstOrDefaultAsync(r => r.OrganizationId == orgId && r.IsSystem && r.Name == "Администратор", ct); + if (adminRole is null) return; + db.Employees.Add(new Employee + { + OrganizationId = orgId, + UserId = adminUserId, + LastName = "Admin", + FirstName = "System", + Position = "Администратор", + Email = "admin@food-market.local", + RoleId = adminRole.Id, + IsActive = true, + }); + await db.SaveChangesAsync(ct); } private static async Task SeedTenantReferencesAsync(AppDbContext db, Guid orgId, CancellationToken ct) @@ -138,6 +166,96 @@ private static async Task SeedTenantReferencesAsync(AppDbContext db, Guid orgId, } await db.SaveChangesAsync(ct); + + await SeedEmployeeRolesAsync(db, orgId, ct); + } + + /// Системные роли (IsSystem=true): Администратор / Менеджер / + /// Кладовщик / Кассир / Закупщик / Бухгалтер. Сидируется один раз + /// per организацию; обновлять не пытаемся, чтобы не сбросить кастомные + /// правки галок которые админ мог сделать. + private static async Task SeedEmployeeRolesAsync(AppDbContext db, Guid orgId, CancellationToken ct) + { + var anyRole = await db.EmployeeRoles.IgnoreQueryFilters().AnyAsync(r => r.OrganizationId == orgId, ct); + if (anyRole) return; + + var admin = new EmployeeRole + { + OrganizationId = orgId, + Name = "Администратор", + Description = "Полный доступ ко всем разделам организации", + IsSystem = true, SortOrder = 0, + Permissions = RolePermissions.All(), + }; + var manager = new EmployeeRole + { + OrganizationId = orgId, + Name = "Менеджер", + Description = "Управление каталогом, документами и контрагентами", + IsSystem = true, SortOrder = 10, + Permissions = new RolePermissions + { + ProductsView = true, ProductsEdit = true, ProductGroupsManage = true, PriceTypesManage = true, + SuppliesView = true, SuppliesEdit = true, SuppliesPost = true, + CounterpartiesView = true, CounterpartiesEdit = true, + ReportsView = true, StocksView = true, + }, + }; + var keeper = new EmployeeRole + { + OrganizationId = orgId, + Name = "Кладовщик", + Description = "Приёмки, инвентаризация, остатки", + IsSystem = true, SortOrder = 20, + Permissions = new RolePermissions + { + ProductsView = true, + SuppliesView = true, SuppliesEdit = true, SuppliesPost = true, + StocksView = true, + }, + }; + var cashier = new EmployeeRole + { + OrganizationId = orgId, + Name = "Кассир", + Description = "Продажи на кассе. Привязка к конкретной кассе обязательна.", + IsSystem = true, SortOrder = 30, + Permissions = new RolePermissions + { + ProductsView = true, + RetailSalesOperate = true, RetailSalesRefund = true, + }, + }; + var buyer = new EmployeeRole + { + OrganizationId = orgId, + Name = "Закупщик", + Description = "Заказы поставщикам и приёмка товара", + IsSystem = true, SortOrder = 40, + Permissions = new RolePermissions + { + ProductsView = true, + SuppliesView = true, SuppliesEdit = true, + CounterpartiesView = true, CounterpartiesEdit = true, + }, + }; + var accountant = new EmployeeRole + { + OrganizationId = orgId, + Name = "Бухгалтер", + Description = "Просмотр всех данных и отчётов, без редактирования", + IsSystem = true, SortOrder = 50, + Permissions = new RolePermissions + { + ProductsView = true, + SuppliesView = true, + CounterpartiesView = true, + ReportsView = true, StocksView = true, + }, + }; + + db.EmployeeRoles.AddRange(admin, manager, keeper, cashier, buyer, accountant); + await db.SaveChangesAsync(ct); } public Task StopAsync(CancellationToken ct) => Task.CompletedTask;