# 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` + `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` — 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 - `` 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