From 59983acabdb9096ff34269bdeed5353bf8a091ba Mon Sep 17 00:00:00 2001 From: nns <278048682+nurdotnet@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:58:23 +0500 Subject: [PATCH] =?UTF-8?q?fix(super-admin):=20=D0=BD=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20org=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20UI=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=83=D1=87=D0=B0=D0=B5=D1=82=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20bootstrap=20(=D0=BA=D0=B0=D0=BA=20Demo)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /api/super-admin/organizations создавал только Store + Admin role в inline-коде — у новой орги не было единиц измерения, типов цен, кастомных ролей шаблонов (Менеджер/Кладовщик/Закупщик/Бухгалтер), кассы. Юзеру приходилось бы заводить всё руками. Решение — переиспользовать DevDataSeeder.SeedTenantReferencesAsync, который уже умеет всё это идемпотентно: - 5 единиц измерения (штука/кг/л/м/упаковка по ОКЕИ) - 2 типа цен (Розничная IsSystem+IsRequired+IsRetail / Оптовая) - «Основной склад» MAIN - «Касса 1» POS-1 - 6 ролей через SeedEmployeeRolesAsync (2 системных + 4 шаблона) Helper повышен с private на public static. В контроллере убран inline Store + AdminRole, после Add(org)+SaveChanges вызывается seed, потом находим уже созданную «Администратор» роль для линковки с Employee. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../SuperAdminOrganizationsController.cs | 25 ++++++++----------- src/food-market.api/Seed/DevDataSeeder.cs | 8 +++++- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/food-market.api/Controllers/SuperAdmin/SuperAdminOrganizationsController.cs b/src/food-market.api/Controllers/SuperAdmin/SuperAdminOrganizationsController.cs index 72d776d..1e34bc0 100644 --- a/src/food-market.api/Controllers/SuperAdmin/SuperAdminOrganizationsController.cs +++ b/src/food-market.api/Controllers/SuperAdmin/SuperAdminOrganizationsController.cs @@ -1,5 +1,5 @@ +using foodmarket.Api.Seed; using foodmarket.Application.Common; -using foodmarket.Domain.Catalog; using foodmarket.Domain.Organizations; using foodmarket.Infrastructure.Identity; using foodmarket.Infrastructure.Persistence; @@ -100,21 +100,16 @@ public async Task> Create([FromBody] CreateOrgRequ DefaultCurrencyId = input.Org.DefaultCurrencyId, }; _db.Organizations.Add(org); - // Системные референсы (Stores, Cashiers, Roles) подсеет DevDataSeeder при - // следующем рестарте; для немедленной готовности создаём минимум руками. - var mainStore = new Store - { - OrganizationId = org.Id, Name = "Основной склад", Code = "MAIN", IsMain = true, - }; - _db.Stores.Add(mainStore); + await _db.SaveChangesAsync(ct); - var adminRole = new EmployeeRole - { - OrganizationId = org.Id, - Name = "Администратор", Description = "Полный доступ ко всем разделам организации", - IsSystem = true, SortOrder = 0, Permissions = RolePermissions.All(), - }; - _db.EmployeeRoles.Add(adminRole); + // Полный bootstrap tenant-сущностей: единицы измерения, типы цен, + // «Основной склад», «Касса 1», 6 ролей (2 системные + 4 кастомные шаблона). + // Один helper и в DevDataSeeder, и здесь — гарантирует одинаковое + // состояние новой орги независимо от пути создания. + await DevDataSeeder.SeedTenantReferencesAsync(_db, org.Id, ct); + + var adminRole = await _db.EmployeeRoles.IgnoreQueryFilters() + .FirstAsync(r => r.OrganizationId == org.Id && r.IsSystem && r.Name == "Администратор", ct); // AppUser админа var existing = await _userMgr.FindByEmailAsync(input.AdminEmail); diff --git a/src/food-market.api/Seed/DevDataSeeder.cs b/src/food-market.api/Seed/DevDataSeeder.cs index bcaa4dc..517535e 100644 --- a/src/food-market.api/Seed/DevDataSeeder.cs +++ b/src/food-market.api/Seed/DevDataSeeder.cs @@ -117,7 +117,13 @@ private static async Task SeedAdminEmployeeAsync(AppDbContext db, Guid orgId, Gu await db.SaveChangesAsync(ct); } - private static async Task SeedTenantReferencesAsync(AppDbContext db, Guid orgId, CancellationToken ct) + /// Bootstrap минимально-достаточного набора tenant-сущностей для + /// новой организации: единицы измерения (ОКЕИ), типы цен (Розничная+Оптовая), + /// «Основной склад» MAIN, «Касса 1» POS-1, и системные роли через + /// SeedEmployeeRolesAsync. Идемпотентно: каждый блок проверяет существующие + /// записи. Используется и при первом старте Demo, и при создании org через + /// SuperAdmin UI. + public static async Task SeedTenantReferencesAsync(AppDbContext db, Guid orgId, CancellationToken ct) { var anyUnit = await db.UnitsOfMeasure.IgnoreQueryFilters().AnyAsync(u => u.OrganizationId == orgId, ct); if (!anyUnit)