# Session 2026-04-23 ~15:00 — Toolkit + 4-bảng overhaul + master data + roles VN **Focus:** Sang phiên 2 trong ngày — UX redesign toàn bộ surface HĐ (3-panel List/Detail/Workflow + Inbox + Thao tác), data model overhaul 4-bảng (Header/Details/Workflow/Changelog), master catalogs cho Details, chi tiết Users/Departments/Roles VN. 11 commit (b75448e → ae59cfe), 2 migration (9 + 10 + 11), DB 24→36 bảng. ## Outcomes ### A. 3-panel layout cho List/Inbox (UX) ✓ - `MyContractsPage` (fe-user): `lg:grid-cols-[320px_1fr_360px]` — Panel 1 List + search + filter | Panel 2 Detail content | Panel 3 Workflow + Lịch sử duyệt - `ContractsListPage` (fe-admin): same pattern + Pagination compact - `InboxPage` (fe-user): same + 4 StatPill inline (Cần xử lý / Sắp QH / Quá hạn / Giá trị) + vai trò amber banner - URL state `?id=` selected — bookmarkable, refresh giữ - Mobile fallback (` expand cùng lúc (accordion) - Auto-expand theo URL `?type=X` qua useEffect + AccordionContext - Highlight active group: bg-slate-50 + text-slate-900 - Commit: 7ea3957 ### C. Tách "Tổng quan" → `/dashboard` riêng (fix bug trùng /inbox) ✓ - `UserDashboardPage` mới: 5-card "Của tôi" (DraftsInProgress / Pending / DueSoon / Overdue / TotalValue) + section HĐ gần đây - Layout resolvePath: Dashboard → /dashboard (không trùng Hộp thư) - App route: / redirect → /dashboard - Commit: d326e80 ### D. Data model 4-bảng overhaul (Tier 3 follow-up) ✓ User decision Option B: bảng riêng cho mỗi loại HĐ. **Migration 9 — `AddContractDetailsAndChangelog`:** - 7 Details bảng: ThauPhuDetails, GiaoKhoanDetails, NhaCungCapDetails, DichVuDetails, MuaBanDetails, NguyenTacNccDetails, NguyenTacDvDetails - Common base: ContractId FK Cascade, Order, ThanhTien decimal(18,2), GhiChu, audit fields - Schema chuyên biệt per loại HĐ (vd MuaBan có ThueVAT, NguyenTac có DonGiaToiThieu/ToiDa) - 1 ContractChangelogs bảng — unified audit log: - EntityType enum: Contract | Detail | Workflow | Comment | Attachment - Action enum: Insert | Update | Delete | Transition - PhaseAtChange + UserName denormalize + FieldChangesJson + ContextNote - DB 24 → 32 bảng **IChangelogService (App + Infra):** - 5 method: LogContractChange / LogDetailChange / LogWorkflowTransition / LogCommentAdded / LogAttachment - Wired vào: CreateContract + UpdateContractDraft (with diff) + AddComment + Upload/Delete Attachment + ContractWorkflowService.Transition **CQRS + API:** - GetContractDetailsQuery dispatch theo Type → bundle 7 lists - 7 typed AddXxxDetailCommand handler (Insert + log changelog) - DeleteContractDetailCommand generic (dispatch theo Type) - ListContractChangelogsQuery (read-only, desc CreatedAt, top 200) - 9 endpoints mới ContractsController Commits: 70810e1, 71c035d, e684455 ### E. FE Panel 2 — tabs → 7/3 grid layout (UX iter 2) ✓ **Iter 1 (b3762af):** Tabs Tổng quan / Chi tiết / Lịch sử **Iter 2 (ad0652d):** Bỏ tabs, hiển thị flat: - Tổng quan content (Info + Comments + Attachments) ở trên - Bên dưới grid 7-3: [Chi tiết Tab] [Lịch sử Changelog Tab] `ContractDetailsTab`: - 7 table renderers per type (HEADERS_BY_TYPE + DeleteBtn) - AddRowForm với FIELDS_BY_TYPE (5-7 field per type) - buildPayload auto compute thanhTien (SL × DonGia × (1+VAT/100) cho MuaBan) - canEdit chỉ khi Phase=DangSoanThao `ContractChangelogsTab`: - Render unified timeline desc CreatedAt - Icon + tone color theo EntityType - Expandable detail row hiển thị FieldChangesJson (Cũ → Mới) ### F. "Thao tác" — 2-panel + Edit/Xóa hover + Mở fullpage ✓ `ContractCreatePage` rewrite: - 2-panel: Panel 1 List HĐ theo type + button "+ Thêm mới" cuối | Panel 2 Header form (new/edit) + Chi tiết section - URL state: ?type / ?id / ?mode=new - Empty state khi chưa chọn - Action buttons hover row: Edit ✏ + Xóa 🗑 (luôn hiện, mờ khi Phase != DangSoanThao + tooltip "Chỉ sửa được khi Đang soạn thảo") - ContractDetailsPreview cho create mode — show table headers + lock icon + disabled add button (user thấy structure trước khi tạo Header) Commits: 8c4b4da, ec0c983, 501b4de, 7f26ff9, 39031ca ### G. Mã HĐ gen ngay tại Create + backfill ✓ User feedback: HĐ phải có mã ngay khi tạo, không đợi DangDongDau. - `CreateContractCommandHandler`: gen mã trước khi `db.Contracts.Add` — GenerateAsync chạy SERIALIZABLE riêng, entity chưa tracked nên không bị save kèm - TransitionAsync giữ defensive `if (MaHopDong is null) gen` cho legacy - `BackfillContractCodesAsync` trong DbInitializer — chạy 1 lần startup, idempotent (count NULL → skip nếu 0), log success/failed Commit: 51449d6 ### H. 4 master catalogs cho Details (migration 10) ✓ User feedback: thêm Master Data cho phần Chi tiết. **Domain entities (4 mới):** - `UnitOfMeasure` (UnitsOfMeasure) — m2, kg, ngc, ... - `MaterialItem` (MaterialItems) — Code, Name, Category (NhomSP), DefaultUnit, Specification, OriginCountry, IsActive - `ServiceItem` (ServiceItems) — vận chuyển, bảo trì, tư vấn, ... - `WorkItem` (WorkItems) — đào móng, đổ bê tông, xây tường, ... Common pattern: AuditableEntity, Code unique, Category index, IsActive. **Migration 10 — `AddMasterCatalogs`:** DB 32 → 36 bảng. **Seed defaults idempotent:** - 20 UnitsOfMeasure (m2/m3/kg/tan/l/cai/bo/goi/ngc/h/ca/...) - 15 MaterialItems demo (xi măng PCB40, cát vàng, đá 1x2, thép D10, ...) - 10 ServiceItems demo (vận chuyển, bảo trì, tư vấn, kiểm định, ...) - 15 WorkItems demo (đào móng, đổ bê tông, xây tường, lát gạch, ...) **CQRS:** CatalogsFeatures.cs (~290 dòng) — 4 catalog × 5 handlers (List + Create + Update + Delete + unique code guard). **Controller:** 13 endpoints — GET open cho mọi role (autocomplete); POST/PUT/DELETE Admin role only. **Menu mới (5):** - Catalogs group "Danh mục chi tiết" dưới Master - 4 leaves: Đơn vị tính / Vật tư SP / Dịch vụ / Hạng mục công việc **FE Admin CatalogsPage:** 1 page generic, URL `/master/catalogs/:kind`, 4 sub-tabs (units/materials/services/work-items), per-kind FIELDS config, form Dialog với input/textarea/checkbox. **FE Details Add form datalist autocomplete:** - Fetch 4 catalogs qua TanStack Query (cache 'catalogs') - HTML5 `` per relevant field (donViTinh/maSP/tenSP/hangMuc/...) - Smart-fill: user pick code → autofill name + defaultUnit (sibling fields) qua handleFieldChange Commits: e27c547, 16e24ed ### I. Roles VN labels + Users dept/position + 13 demo users (migration 11) ✓ **Entity changes:** - `Role` + `ShortName` (max 50) — Mã viết tắt VN - `User` + `DepartmentId` Guid? FK Departments (Restrict) + `Position` **Migration 11 — `AddRoleShortNameAndUserDepartment`:** thêm cột, không table mới. DB vẫn 36 bảng. **12 roles VN labels** (idempotent backfill nếu existing role thiếu): | Code | Short | Description | |---|---|---| | Admin | QTV | Quản trị viên hệ thống | | Drafter | NV.PB | Nhân viên phòng ban (soạn HĐ) | | DeptManager | TPB | Trưởng phòng ban | | ProjectManager | PM | Giám đốc dự án | | Procurement | PRO | Phòng Cung ứng | | CostControl | CCM | Phòng Kiểm soát chi phí | | Finance | FIN | Phòng Tài chính | | Accounting | ACT | Phòng Kế toán | | Equipment | EQU | Phòng Thiết bị | | Director | BOD | Ban Giám đốc | | AuthorizedSigner | NĐUQ | Người được Ủy quyền ký HĐ | | HrAdmin | HRA | Phòng Nhân sự - Hành chính | **Identity Name = code English giữ nguyên** (FK + [Authorize] attr). Chỉ thêm 2 cột display VN. **13 Demo users seed (password `User@123456`):** - bod.huynh + bod.le (BOD) - pm.nguyen (PM) - TPB cho CCM/PRO/FIN/ACT/EQU/HRA (6 user) - qs.hoang + qs.ngo (QS Drafter) - nv.cao + nv.dinh (PRO/FIN Drafter) **FE Admin updates:** - `types/users.ts`: RoleShortName + RoleLabel + roleDisplayName helper - `UsersPage`: column Phòng ban + Chức vụ, badge Vai trò = ShortName (tooltip full label), Edit dialog mới (dept/position/active), Create dialog 2-col grid + dropdown Phòng ban, Roles checkbox "ShortName — Full Label" - `PermissionsPage` Panel 1: 2-line per role (ShortName semibold + Description small) Commits: 330d529, ae59cfe ## Stats cumulative | | Trước session | Sau session | Δ | |---|---|---|---| | BE LOC | ~4800 | ~7800 | +3000 | | DB tables | 24 | **36** | +12 (7 details + 1 changelog + 4 catalogs) | | Migrations | 8 | **11** | +3 (Migration 9/10/11) | | API endpoints | ~50 | **~80** | +30 (details CRUD + catalogs CRUD + changelogs) | | FE pages | ~20 | **~22** | +2 (UserDashboardPage + CatalogsPage) | | FE components | many | many+ | +ContractDetailContent/WorkflowHistoryPanel/ContractDetailsTab/ContractChangelogsTab/ContractDetailsPreview | | Commits session này | — | **22** | b75448e → ae59cfe | ## Architectural decisions 1. **Option B per-type Details** (vs single bảng + JSON): chuẩn schema, strict typing, TS strict mode FE bắt typo. Trade-off: 7 bảng riêng + nhiều handler lặp pattern. Worth. 2. **ContractChangelogs unified** — 1 bảng audit cho mọi entity HĐ (Contract/Detail/Workflow/Comment/Attachment), KHÁC ContractApprovals (workflow-only, dùng cho guard). View layer cho user đọc lịch sử. 3. **Mã HĐ gen tại Create** (vs DangDongDau) — trade-off: gap trong sequence khi reject, nhưng user có mã ngay từ đầu để reference. 4. **Datalist HTML5 native autocomplete** (vs library combobox) — đủ cho MVP, không add dependency, smart-fill qua handleFieldChange. 5. **Identity Name English giữ nguyên + thêm ShortName/Description VN** — không break FK, không cần data migration; FE/BE display layer convert qua dict mapping. 6. **Per-type bảng Details không có User-level approver runtime guard yet** — data model `WorkflowStepApprover.Kind=User` đã có, nhưng transition guard v1 chỉ check Role-kind. Iter sau enable. ## Next session priority 1. **UAT 2-3 user thật** — hard requirement 2. Email outbox (chờ SMTP config) 3. Rotate creds (SA/vrapp/JWT/admin) 4. SQL backup Task Scheduler 5. Roles CRUD admin (custom role ngoài 12 hardcoded) 6. User-kind approver runtime guard 7. PermissionsPage: grant Workflows.Read non-admin → Wf_* visible 8. Update docs (sẽ làm trong commit chốt session này) ## Cron audit fire 2026-05-01 Cron task `solution-erp-skill-audit-monthly` sẽ chạy tự động đầu tháng sau — log vào `docs/changelog/skill-audit-2026-05.md` + commit auto.