[CLAUDE] App+Api+FE-Admin: RolesPage CRUD (/system/roles)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m58s

User feedback: /system/roles trỏ tới placeholder "chưa được build" — build
trang quản lý 12 role mặc định + custom role admin tự thêm.

## BE — PermissionFeatures.cs

3 command mới:
- CreateRoleCommand — Name regex `^[A-Za-z][A-Za-z0-9_]*$` (chỉ chữ/số/
  underscore, bắt đầu chữ), throw ConflictException nếu code đã tồn tại
- UpdateRoleCommand — CHỈ update ShortName + Description. KHÔNG đổi
  Name (Identity FK trong UserRoles + WorkflowStepApprover.AssignmentValue
  + [Authorize(Roles="...")] attr — đổi = data corruption widespread)
- DeleteRoleCommand — block 2 trường hợp:
  * Role thuộc AppRoles.All hardcoded (workflow guard reference)
  * Còn user assigned (UserManager.GetUsersInRoleAsync count > 0)

ValidationException reference fully-qualified để tránh ambiguous với
FluentValidation.ValidationException.

## BE — RolesController

3 endpoint mới (POST/PUT/DELETE) — Authorize Admin role.

## FE — RolesPage

Table list 12 + custom roles với 5 column (Mã code / Mã viết tắt / Tên
đầy đủ / Loại badge / Ngày tạo) + actions Edit/Delete:
- Edit dialog: chỉ ShortName + Description editable, Name disabled với
  hint "Không đổi được sau khi tạo"
- Delete: block với toast nếu role mặc định (HARDCODED_ROLES set check
  client-side trước khi gọi BE — UX faster, BE vẫn double-check)
- Create dialog: 3 field Name (regex pattern HTML5) + ShortName + Description
- Banner amber warning về Mã code FK constraint
- Loại badge: Mặc định (slate) vs Tùy chỉnh (brand)

## FE — App.tsx

+ import RolesPage + route /system/roles → RolesPage.

## Build

- BE: dotnet build pass (0 error)
- fe-admin: tsc + vite pass (13.88s)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-23 14:57:36 +07:00
parent ff5e35f279
commit 072ad6d014
4 changed files with 398 additions and 0 deletions

View File

@ -10,6 +10,7 @@ import { ProjectsPage } from '@/pages/master/ProjectsPage'
import { DepartmentsPage } from '@/pages/master/DepartmentsPage'
import { CatalogsPage } from '@/pages/master/CatalogsPage'
import { PermissionsPage } from '@/pages/system/PermissionsPage'
import { RolesPage } from '@/pages/system/RolesPage'
import { WorkflowsPage } from '@/pages/system/WorkflowsPage'
import { FormsPage } from '@/pages/forms/FormsPage'
import { ContractsListPage } from '@/pages/contracts/ContractsListPage'
@ -38,6 +39,7 @@ function App() {
<Route path="/master/catalogs" element={<Navigate to="/master/catalogs/units" replace />} />
<Route path="/master/catalogs/:kind" element={<CatalogsPage />} />
<Route path="/system/users" element={<UsersPage />} />
<Route path="/system/roles" element={<RolesPage />} />
<Route path="/system/permissions" element={<PermissionsPage />} />
<Route path="/system/workflows" element={<WorkflowsPage />} />
<Route path="/system/workflows/:typeCode" element={<WorkflowsPage />} />