[CLAUDE] Phase1.2: CRUD Master + Permission Matrix + FE admin pages

Backend:
- Domain/Master: Supplier (+ SupplierType 5 loai), Project, Department (AuditableEntity)
- Domain/Identity: MenuItem, Permission, MenuKeys const (12 menu)
- EF Configurations voi unique Code + query filter IsDeleted
- DbSets + IApplicationDbContext interface update
- Application: PagedResult + PagedRequest generic
- Application/Master CQRS CRUD 3 entity (Create/Update/Delete/Get/List voi paging search sort)
- Application/Permissions: GetMyMenuTree (union OR role, filter tree), ListMenuItems, ListPermissionsByRole, UpsertPermission (guard admin khong tu giam quyen), ListRoles
- Api/Authorization: MenuPermissionRequirement + Handler (Admin bypass, query DB)
- Program.cs: register 48 policy {menu}.{action} tu MenuKeys x Actions
- Api/Controllers: Suppliers, Projects, Departments, Menus, Roles, Permissions
- DbInitializer: seed 12 menu + admin full CRUD permissions
- Migration AddMasterData + AddPermissions

Frontend (fe-admin):
- Types: menuKeys.ts const, menu.ts (MenuNode/Role/Permission), master.ts (Supplier/Project/Department + SupplierType const-object)
- AuthContext: load menu from /menus/me, cache localStorage, refreshMenu()
- usePermission hook + PermissionGuard component (wrap button)
- UI kit them: Dialog (modal overlay), Textarea, Select
- Generic: DataTable (column config, sortable, loading, empty) + Pagination
- PageHeader component
- apiError helper extract message tu ProblemDetails
- Layout rewrite: render menu dong tu AuthContext.menu (MenuGroup collapsible + NavLink + lucide icon map)
- Pages: master/Suppliers, master/Projects, master/Departments (CRUD + search + sort + paging + Dialog form)
- Page system/Permissions: ma tran Role x MenuKey x CRUD checkbox (tick tu dong PUT upsert)
- App.tsx them 4 route moi

Bug fix:
- MenuPermissionHandler: EF expression tree khong support switch expression -> tach switch ra ngoai AnyAsync
- TS erasableSyntaxOnly khong cho enum -> SupplierType const-object pattern (typeof[keyof])

E2E verified via Vite proxy:
- GET /menus/me -> 6 root + 6 child nodes (12 menus)
- GET /roles -> 12 roles
- POST/GET/PUT/DELETE /suppliers -> full CRUD, soft delete OK
- tsc -b fe-admin pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-21 11:30:14 +07:00
parent 49a5f57a50
commit 54d6c9ba52
63 changed files with 4422 additions and 93 deletions

View File

@ -0,0 +1,89 @@
# Session 2026-04-21 11:30 — Phase 1 đợt 2 complete
**Dev:** Claude (Opus 4.7)
**Duration:** ~1h30m
**Base commit:** `49a5f57`
**Commits:** (sắp tạo — 1 commit lớn cho toàn bộ đợt 2)
## Làm được
### Chunk A — Backend Master data
- Domain: `Supplier` (+ `SupplierType` enum 5 loại: NCC/NTP/TĐ/ĐVDV/CĐT), `Project`, `Department` — extend `AuditableEntity`
- EF `IEntityTypeConfiguration` với unique index `Code`, query filter `IsDeleted`
- DbSets trong `ApplicationDbContext` + `IApplicationDbContext` (thêm package `Microsoft.EntityFrameworkCore` vào Application)
- `Common/Models/PagedResult<T>` + `PagedRequest` (page, pageSize 1-200, search, sortBy, sortDesc)
- CQRS cho Supplier: `CreateSupplierCommand` + Validator + Handler, `UpdateSupplierCommand`, `DeleteSupplierCommand` (soft-delete qua AuditingInterceptor), `GetSupplierQuery`, `ListSuppliersQuery` (với filter Type)
- CQRS cho Project + Department: gom vào 1 file mỗi entity (`ProjectFeatures.cs`, `DepartmentFeatures.cs`) — pattern giống Supplier nhưng compact
- 3 Controller: `SuppliersController`, `ProjectsController`, `DepartmentsController` — full REST (list, get, create, update, delete)
- Migration `AddMasterData`
### Chunk B — Backend Permission system
- Domain: `MenuKeys` const (12 menu key: Dashboard, Master, Suppliers, Projects, Departments, Contracts, Forms, Reports, System, Users, Roles, Permissions), `MenuItem` (Key PK, Label, ParentKey, Order, Icon), `Permission` (RoleId, MenuKey, CanRead/Create/Update/Delete)
- EF configs + unique index `(RoleId, MenuKey)`
- DTOs: `MenuNodeDto` (tree node có resolved CRUD), `PermissionDto`, `RoleDto`, `MenuItemDto`
- `GetMyMenuTreeQuery` — resolve menu tree theo roles của user, **union OR** CRUD flags qua roles, **filter** chỉ trả về node có CanRead hoặc có child CanRead
- `ListMenuItemsQuery` + `ListPermissionsByRoleQuery` + `UpsertPermissionCommand` (guard: admin không tự giảm quyền mình)
- `ListRolesQuery`
- **Authorization handler** `MenuPermissionHandler : AuthorizationHandler<MenuPermissionRequirement>` — check Admin bypass, query DB qua roleIds
- Register 48 policy (`{menu}.{action}` cho 12 menu × 4 action) trong Program.cs
- Controllers: `MenusController` (GET /me, GET /), `RolesController`, `PermissionsController` (Authorize policy `Permissions.Read/Update`)
- Update `DbInitializer`: seed 12 menu items + default **admin có full CRUD mọi menu**
- Migration `AddPermissions`
### Chunk C — Frontend (fe-admin)
- Types: `menuKeys.ts` const + `menu.ts` (MenuNode/MenuItem/Role/Permission) + `master.ts` (Supplier/Project/Department + SupplierType const-object với erasableSyntaxOnly)
- `contexts/AuthContext.tsx`: thêm `menu` state + `loadMenu()` khi login + localStorage cache + `refreshMenu()`
- `hooks/usePermission.ts`: `can(menuKey, action)` — search tree đệ quy
- `components/PermissionGuard.tsx`: wrapper component
- `components/ui/`: Dialog (modal overlay + Escape close), Textarea, Select
- `components/DataTable.tsx`: generic table với columns config, sort (sortBy/sortDesc callback), loading, empty state, click row; `Pagination` component kèm theo
- `components/PageHeader.tsx`: title + description + actions
- `components/Layout.tsx` rewrite: render menu **động** từ AuthContext.menu (không hardcode nữa), MenuGroup collapsible cho parent có children, NavLink active highlight, icon map từ lucide-react theo field `icon`
- `lib/apiError.ts`: extract user-friendly message từ ProblemDetails
- 3 trang CRUD admin (`master/SuppliersPage.tsx`, `ProjectsPage.tsx`, `DepartmentsPage.tsx`):
- Full CRUD với Dialog form, search, sort, paging
- `<PermissionGuard>` wrap Create/Update/Delete button
- `useQuery` + `useMutation` + `invalidateQueries` TanStack
- Toast success/error
- `system/PermissionsPage.tsx`: grid ma trận role × menu × CRUD, select role dropdown → tick checkbox tự động PUT `/permissions` upsert
- `App.tsx`: thêm 4 route mới
## Bug gặp + fix
| Bug | Root cause | Fix |
|---|---|---|
| Build CS8514 in MenuPermissionHandler | EF expression tree không support switch expression | Tách `switch` ra ngoài `AnyAsync()` — switch trên Action, mỗi case gọi AnyAsync với predicate riêng |
| POST /api/suppliers lỗi với Unicode tiếng Việt qua curl command line | Windows bash command line encoding không đúng UTF-8 | Dùng `--data-binary @file.json` — API handle UTF-8 OK |
| TS1294 erasableSyntaxOnly + `enum SupplierType` | TS 6 erasableSyntaxOnly không cho enum | Dùng `const + as const + type = typeof[keyof]` pattern |
| fe-admin npm run dev báo port 8082 in use | Background task trước chưa stop hẳn | Tình trạng tạm — E2E vẫn pass qua port đã bind |
## E2E verified
- `GET /api/menus/me` với admin token → **6 root + 6 child nodes** (12 menu items) ✅
- `GET /api/roles`**12 roles**
- `POST /api/suppliers` → 201 + id ✅
- `GET /api/suppliers` → PagedResult ✅
- `PUT /api/suppliers/{id}` → 204 ✅
- `DELETE /api/suppliers/{id}` → 204, list GET sau đó không còn ✅ (soft delete OK)
- `GET /api/permissions/by-role/{adminRoleId}` → 12 permissions all true ✅
- `tsc -b` fe-admin → pass ✅
## Handoff cho session tiếp theo
**Phase 2 — Form Engine:**
- Convert 3 `.doc` files → `.docx` via PowerShell COM automation
- Parse chi tiết field specs cho 5 contract templates
- `IFormRenderer` service với OpenXml + ClosedXML
- Form builder UI cho admin upload template
- Preview + export flow
Xem [`docs/flows/form-render-flow.md`](../../flows/form-render-flow.md) + [`docs/changelog/migration-todos.md`](../migration-todos.md) section Phase 2.
**Còn thiếu trong Phase 1 (có thể làm parallel với Phase 2 nếu muốn):**
- FE: thêm trang Users management (tạo user mới, gán role)
- FE: trang Roles CRUD (create/rename/delete custom role)
- Contract entity + CRUD draft (skeleton cho Phase 3 chuẩn bị)
- Test 1 role khác Admin → verify permission guard hoạt động đúng
**Blocker:**
- ⏳ Gitea remote URL