feat(roles): системные роли read-only + русские имена + чистка дубликата у admin
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 40s
CI / Web (React + Vite) (push) Successful in 39s
Docker API / Build + push API (push) Successful in 45s
Docker Web / Build + push Web (push) Successful in 34s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 12s

Концепция: «Супер администратор» — платформенная Identity-роль
SuperAdmin. «Администратор» — организационная роль внутри Employee
(IsSystem=true в EmployeeRole). Они НЕ должны дублироваться у
одного юзера.

- Сидер: admin@food-market.local получает только Identity-роль
  SuperAdmin. Догоняющая ветка для существующих стендов:
  если есть Identity-роль Admin — RemoveFromRoleAsync. На стенде
  AspNetUserRoles почищен SQL'ом.
- AppLayout: translateRoles() переводит SuperAdmin → «Супер
  администратор», скрывает Identity-роль Admin (org-уровень
  показывается через Employee/Role, не через Identity).
- EmployeeRolesPage: клик по строке системной роли → alert
  «Системная роль, изменения недоступны». Edit-модалка для
  системных была частично defensive (disabled чекбоксы Phase 2c),
  теперь точка входа закрыта целиком. Кастомные роли — без изменений.

EmployeeRole.IsSystem поле уже было — миграция не нужна.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-26 16:11:14 +05:00
parent 6395cf348d
commit 8466cf928c
3 changed files with 35 additions and 9 deletions

View file

@ -78,14 +78,18 @@ public async Task StartAsync(CancellationToken ct)
var result = await userMgr.CreateAsync(admin, "Admin12345!");
if (result.Succeeded)
{
await userMgr.AddToRoleAsync(admin, SystemRoles.Admin);
// Только SuperAdmin как Identity-роль. «Администратор» —
// организационная роль внутри Employee, не Identity.
await userMgr.AddToRoleAsync(admin, SystemRoles.SuperAdmin);
}
}
else if (!await userMgr.IsInRoleAsync(admin, SystemRoles.SuperAdmin))
else
{
// Существующий admin без SuperAdmin — догоняем (для уже развёрнутых стендов).
if (!await userMgr.IsInRoleAsync(admin, SystemRoles.SuperAdmin))
await userMgr.AddToRoleAsync(admin, SystemRoles.SuperAdmin);
// Чистим дублирующую Identity-роль Admin (если оставалась с прошлых сидов).
if (await userMgr.IsInRoleAsync(admin, SystemRoles.Admin))
await userMgr.RemoveFromRoleAsync(admin, SystemRoles.Admin);
}
await SeedAdminEmployeeAsync(db, demoOrg.Id, admin?.Id, ct);

View file

@ -23,6 +23,22 @@ interface MeResponse {
type NavItem = { to: string; icon: typeof LayoutDashboard; label: string; end?: boolean }
type NavSection = { group: string; items: NavItem[] }
const ROLE_RU: Record<string, string> = {
SuperAdmin: 'Супер администратор',
Admin: 'Администратор',
Manager: 'Менеджер',
Cashier: 'Кассир',
Storekeeper: 'Кладовщик',
}
const HIDDEN_ROLES = new Set(['Admin']) // org-уровневая роль показывается через Employee, не Identity
function translateRoles(roles: string[]): string {
return roles
.filter((r) => !HIDDEN_ROLES.has(r))
.map((r) => ROLE_RU[r] ?? r)
.join(', ')
}
function buildNav(isSuperAdmin: boolean): NavSection[] {
const catalog: NavItem[] = [
{ to: '/catalog/products', icon: Package, label: 'Товары' },
@ -147,7 +163,7 @@ export function AppLayout() {
{me && (
<div className="px-2 pb-2 text-xs text-slate-500">
<div className="truncate font-medium text-slate-700 dark:text-slate-200">{me.name}</div>
<div className="truncate">{me.roles.join(', ')}</div>
<div className="truncate">{translateRoles(me.roles)}</div>
</div>
)}
<button

View file

@ -157,10 +157,16 @@ export function EmployeeRolesPage() {
sortKey={sortKey}
sortOrder={sortOrder}
onSortChange={setSort}
onRowClick={(r) => setForm({
onRowClick={(r) => {
if (r.isSystem) {
alert('Системная роль, изменения недоступны.')
return
}
setForm({
id: r.id, name: r.name, description: r.description ?? '',
isSystem: r.isSystem, permissions: { ...blankPerms(), ...r.permissions },
})}
})
}}
columns={[
{ header: 'Название', cell: (r) => (
<div>