diff --git a/src/food-market.api/Controllers/Organizations/EmployeesController.cs b/src/food-market.api/Controllers/Organizations/EmployeesController.cs index 0af6fe6..da5c0a4 100644 --- a/src/food-market.api/Controllers/Organizations/EmployeesController.cs +++ b/src/food-market.api/Controllers/Organizations/EmployeesController.cs @@ -13,7 +13,7 @@ namespace foodmarket.Api.Controllers.Organizations; [ApiController] -[Authorize] +[Authorize(Roles = "SuperAdmin,Admin")] [Route("api/organization/employees")] public class EmployeesController : ControllerBase { @@ -111,12 +111,12 @@ public async Task> Get(Guid id, CancellationToken ct) return dto is null ? NotFound() : Ok(dto); } - [HttpPost, Authorize(Roles = "SuperAdmin,Admin")] + [HttpPost] public async Task> Create([FromBody] EmployeeInput input, CancellationToken ct) { var orgId = _tenant.OrganizationId ?? throw new InvalidOperationException("No tenant."); - var roleExists = await _db.EmployeeRoles.AnyAsync(r => r.Id == input.RoleId, ct); - if (!roleExists) return BadRequest(new { error = "Роль не найдена." }); + var role = await _db.EmployeeRoles.FirstOrDefaultAsync(r => r.Id == input.RoleId, ct); + if (role is null) return BadRequest(new { error = "Роль не найдена." }); var employee = new Employee { @@ -151,6 +151,15 @@ public async Task> Create([FromBody] Employee if (!result.Succeeded) return BadRequest(new { error = string.Join("; ", result.Errors.Select(e => e.Description)) }); employee.UserId = user.Id; + + // Identity-роль маппится из orgRole.Name. Кастомные orgRole не + // получают Identity-роли — они только дают UI-permissions, без + // доступа к role-locked endpoint'ам. + var identityRole = foodmarket.Api.Infrastructure.IdentityRoleMapper.FromOrgRoleName(role.Name); + if (identityRole is not null) + { + await _userMgr.AddToRoleAsync(user, identityRole); + } } foreach (var rpId in input.RetailPointIds ?? []) diff --git a/src/food-market.api/Controllers/SuperAdmin/SuperAdminEmployeesController.cs b/src/food-market.api/Controllers/SuperAdmin/SuperAdminEmployeesController.cs index 3d5967d..02a3337 100644 --- a/src/food-market.api/Controllers/SuperAdmin/SuperAdminEmployeesController.cs +++ b/src/food-market.api/Controllers/SuperAdmin/SuperAdminEmployeesController.cs @@ -162,7 +162,9 @@ public async Task> Create(Guid orgId, [FromBody] Cr var ur = await _userMgr.CreateAsync(u, tempPassword); if (!ur.Succeeded) return BadRequest(new { error = string.Join("; ", ur.Errors.Select(x => x.Description)) }); - await _userMgr.AddToRoleAsync(u, "Admin"); + // Identity-роль из orgRole (Администратор/Кладовщик/Кассир) — кастомные не получают. + var identityRole = foodmarket.Api.Infrastructure.IdentityRoleMapper.FromOrgRoleName(role.Name) ?? "Admin"; + await _userMgr.AddToRoleAsync(u, identityRole); newUserId = u.Id; } diff --git a/src/food-market.api/Infrastructure/IdentityRoleMapper.cs b/src/food-market.api/Infrastructure/IdentityRoleMapper.cs new file mode 100644 index 0000000..60f483d --- /dev/null +++ b/src/food-market.api/Infrastructure/IdentityRoleMapper.cs @@ -0,0 +1,25 @@ +namespace foodmarket.Api.Infrastructure; + +/// Маппинг доменной orgRole.Name (русский, видимый юзеру в UI) +/// на Identity-роль (английский, используется в [Authorize(Roles=...)]). +/// Identity-роль определяет ЧТО юзер может вызывать через API; orgRole — +/// видимое имя в UI и набор прав для конкретной орги. +/// +/// Системные orgRole сидятся при создании org (см. DevDataSeeder.SeedEmployeeRolesAsync). +/// Кастомные orgRole создаются админом орги — для них Identity-роль НЕ +/// присваивается (юзер не сможет дёрнуть Admin/Cashier/Storekeeper-only +/// endpoints, что и нужно: кастомные роли — только UI-permissions внутри org). +public static class IdentityRoleMapper +{ + public static string? FromOrgRoleName(string? orgRoleName) + { + if (string.IsNullOrWhiteSpace(orgRoleName)) return null; + return orgRoleName.Trim() switch + { + "Администратор" => "Admin", + "Кладовщик" => "Storekeeper", + "Кассир" => "Cashier", + _ => null, + }; + } +}