# Session log — 2026-04-28 (Phase 7 — Module Ngân sách BE + 14 Solutions users) **Topic:** Module Ngân sách (Budget) BE chốt + 14 demo user Solutions thật + chốt MD session 4 → 5 **Commits:** - `309dcd9` · `e71e0eb` · `e65578a` — Docs cleanup từ session 3 (audit MD + tái cấu trúc + archive raw) - `8097892` — Infra: 14 demo user Solutions company (PRO 5 + CCM 7 + ISO 1 + CEO 1) - `a05c57b` — Domain+App+Api: Module Ngân sách (Budget) — 4 bảng + workflow simple 3-step - (Session log + chốt MD này — commit cuối session 4) **Migration:** 14 `AddBudgets` — 4 bảng Budgets / BudgetDetails / BudgetApprovals / BudgetChangelogs + 2 cột nullable FK `Contracts.BudgetId` & `PurchaseEvaluations.BudgetId`. ## A. Module Ngân sách BE ### Domain (`SolutionErp.Domain/Budgets/`) - `BudgetPhase` enum 5-state — DangSoanThao(1) → ChoCCM(2) → ChoCEO(3) → DaDuyet(4) + TuChoi(99) - `Budget` aggregate root (AuditableEntity): - MaNganSach `NS-{YYYYMM}-{Random:4d}` (chưa atomic), TenNganSach required, Description - NamNganSach int (year), ProjectId FK Restrict, DepartmentId? FK Restrict, DrafterUserId FK Restrict - Phase enum, TongNganSach decimal(18,2) **auto-recompute**, SlaDeadline?, SlaWarningSent - Navigation: Details / Approvals / Changelogs collections - `BudgetDetail` flat row pattern — KHÔNG nested per-type: - GroupCode/GroupName (phân nhóm hạng mục), ItemCode, NoiDung, DonViTinh - KhoiLuong dec(18,4), DonGia dec(18,2), ThanhTien dec(18,2), Order, GhiChu - `BudgetApproval` workflow history (FromPhase/ToPhase/Decision reuse `ApprovalDecision`) - `BudgetChangelog` audit log với enum `BudgetEntityType` (Header/Detail/Workflow) - `BudgetPolicy` record + `BudgetPolicy.Default` hardcoded simple 3-step: - Drafter/DeptManager: DangSoanThao → ChoCCM (Trình) hoặc TuChoi - CostControl: ChoCCM → ChoCEO hoặc DangSoanThao (trả về) - Director/AuthorizedSigner: ChoCEO → DaDuyet hoặc DangSoanThao ### Modify Contract & PE - `Contract.BudgetId? Guid` + index - `PurchaseEvaluation.BudgetId? Guid` + index - Không có FK constraint cứng — app layer guard validate khi pick Budget ### Application (`SolutionErp.Application/Budgets/`) `BudgetFeatures.cs` ~340 LOC, 11 CQRS handler: 1. `CreateBudgetCommand` + Validator + Handler — gen MaNganSach, set Phase=DangSoanThao, log Changelog 2. `UpdateBudgetDraftCommand` — chỉ DangSoanThao, log field changes JSON 3. `TransitionBudgetCommand` — guard `BudgetPolicy.Default.IsTransitionAllowed`, append BudgetApproval, log Changelog Transition 4. `ListBudgetsQuery` — Page/Search/Filter Phase/ProjectId/NamNganSach 5. `GetBudgetQuery` — return `BudgetDetailBundleDto` (Header + Details + Approvals + Workflow summary) 6. `DeleteBudgetCommand` — chỉ DangSoanThao/TuChoi (soft delete) 7. `AddBudgetDetailCommand` — auto recompute `Sum(ThanhTien)` lại Header 8. `UpdateBudgetDetailCommand` — auto recompute 9. `DeleteBudgetDetailCommand` — auto recompute 10. `ListBudgetChangelogsQuery` — order CreatedAt DESC ### Api (`SolutionErp.Api/Controllers/`) `BudgetsController.cs` 11 endpoint REST: ``` GET /api/budgets list paged GET /api/budgets/{id} bundle POST /api/budgets create PUT /api/budgets/{id} update draft POST /api/budgets/{id}/transitions phase transition DELETE /api/budgets/{id} soft delete POST /api/budgets/{id}/details add detail PUT /api/budgets/{id}/details/{detailId} update detail DELETE /api/budgets/{id}/details/{detailId} delete detail GET /api/budgets/{id}/changelogs audit log ``` ### Menu seed (DbInitializer) - Root `Budgets` (`MenuKeys.Budgets`) order=27 icon `Wallet` - 3 leaf: - `Bg_List` "Danh sách ngân sách" → `/budgets` - `Bg_Create` "Tạo ngân sách" → `/budgets/new` - `Bg_Pending` "Ngân sách chờ duyệt" → `/budgets?phase=Pending` - **FE chưa wire route + menu resolver — Priority 0 session 5** ## B. 14 demo user Solutions company thật `SeedDemoUsersAsync` thêm 14 user với reconcile pattern (per-user try-catch, gotcha #38 4-field Email/NormalizedEmail/UserName/NormalizedUserName). ### PRO (Phòng Cung ứng) 5 user | Email | Họ tên | Vai trò | |---|---|---| | tra.bui@solutions.com.vn | Bùi Lê Thủy Trà | TPB.PRO | | phuong.nguyen@solutions.com.vn | Nguyễn Thị Bích Phượng | NV.PRO | | binh.lethanh@solutions.com.vn | Lê Thanh Bình | NV.PRO | | danh.huynh@solutions.com.vn | Huỳnh Tài Danh | NV.PRO | | dat.tran@solutions.com.vn | Trần Văn Đạt | NV.PRO | ### CCM (Phòng Kiểm soát chi phí) 7 user | Email | Họ tên | Vai trò | |---|---|---| | ngocanh.huynh@solutions.com.vn | Huỳnh Thị Ngọc Ánh | TPB.CCM | | ha.dao@solutions.com.vn | Đào Đức Hà | NV.CCM | | cuong.do@solutions.com.vn | Đỗ Mạnh Cường | NV.CCM | | long.le@solutions.com.vn | Lê Phụng Long | NV.CCM | | ha.nguyen@solutions.com.vn | Nguyễn Thị Thu Hà | NV.CCM | | dung.nguyen@solutions.com.vn | Nguyễn Phúc Dũng | NV.CCM | | anh.nguyen@solutions.com.vn | Nguyễn Phước Tuấn Anh | NV.CCM | ### ISO 1 user | Email | Họ tên | Vai trò | |---|---|---| | chau.le@solutions.com.vn | Lê Bảo Châu | NV.ISO | ### CEO 1 user | Email | Họ tên | Vai trò | |---|---|---| | huy.duong@solutions.com.vn | Dương Quang Huy | BOD | Pwd toàn bộ: `User@123456`. Tổng demo user = **30** (16 sample @solutionerp.local cũ + 14 Solutions thật @solutions.com.vn). ## C. Docs cleanup từ session 3 3 commit dọn MD trước: - `e65578a` — chốt session 3 PE polish iter 2 + domain rebrand + 5 gotcha mới (#34-38) - `e71e0eb` — tái cấu trúc cleanup: archive raw dump, compact migration-todos, update CLAUDE+flows - `309dcd9` — chốt final session 3 audit MD + session log + minor fixes ## D. Chốt MD session 4 → 5 Updated: - `docs/STATUS.md` — header + Recently Done 3 row mới (Budget BE / 14 users / Docs cleanup), cumulative table thêm cột "+Budget+30 users" - `docs/HANDOFF.md` — TL;DR session 4 (2 milestone Budget BE + 14 users) + Cảnh báo session 5 + Priority 0 (FE Budget + PE/HD integration + PE feature gap) + Credentials 30 user - `docs/changelog/migration-todos.md` — Phase 7 thêm section D Budget done, Phase 8 mới (FE Budget pages + integration), pending migrations Budget - `docs/architecture.md` — §10 Budget module mới (sơ đồ ERD + state machine + auto-recompute + integration roadmap), renumber §11 Liên quan - `docs/database/schema-diagram.md` — Migration history rows 13+14, §12 Budget ERD chi tiết (4 bảng + state machine + auto-recompute + pending refinement), §13 Liên quan - `.claude/skills/ef-core-migration/SKILL.md` — Migration 14 entry, total 51 bảng, Phase 8 pending Budget refinement - `CLAUDE.md` (root + docs/) — modules table thêm Budget row, scope `Budget`, count 51 bảng / 14 migration - `~/.claude/projects/.../memory/project_solution_erp.md` — Phase 7 Budget BE + 14 user + tổng số mới + Session 5 priority ## E. Session 5 carry-over ### Priority 0 — FE Budget pages Copy pattern PE: - `fe-admin/src/types/budget.ts` - `fe-admin/src/pages/budgets/BudgetsListPage.tsx` (3-panel) - `fe-admin/src/pages/budgets/BudgetCreatePage.tsx` - `fe-admin/src/components/budgets/BudgetDetailTabs.tsx` - `fe-admin/src/components/budgets/BudgetWorkflowPanel.tsx` - Mirror sang fe-user - Routes `/budgets`, `/budgets/new`, `/budgets/:id` - Menu resolver `Bg_*` ### Priority 1 — PE/Contract → Budget integration - PE form thêm field `Ngân sách` select Budget (filter `Phase=DaDuyet && NamNganSach=current && ProjectId match`) - Contract form tương tự - PE Detail tab thêm cột "So với ngân sách" (compute từ BudgetDetail tương ứng) ### Priority 2 — PE feature gap - PE Workflow admin designer UI `/system/pe-workflows/:typeCode` - Ý kiến 4 phòng ban (Phê duyệt/CCM/MuaHàng/SM-PM) - Export phiếu PDF/Excel ### Optional - Budget MaNganSach atomic sequence (chốt format chính thức) - Budget versioned workflow (admin config UI) - Payment terms PE tách field - Auto-map PE Details → Contract Details - Matrix Quotes bulk paste ### Ops - Remove binding cũ `.huypham.vn` - win-acme fix unhealthy - UAT thật 1 tuần với 2-3 user (30 user demo) - SMTP config Email outbox - Rotate credentials - Schedule SQL backup ## F. Stats sau session 4 | | Trước session 4 | Sau session 4 | |---|---:|---:| | BE LOC | ~11400 | ~11750 (+340 Budget) | | DB tables | 47 | **51** (+4 Budget) | | API endpoints | ~113 | **~124** (+11 Budget) | | Migrations | 13 | **14** (`AddBudgets`) | | FE pages | ~26 | ~26 (Budget FE TODO) | | Demo user | 16 | **30** (+14 Solutions thật) | | Docs | ~48 | **~50** (+ session log + chốt MD) | | Gotchas | 38 | 38 (no new) | | Commits | ~70 | **~75** | ## G. Lessons learned session 4 1. **Workflow simple hardcoded chấp nhận được khi user yêu cầu "tạm thời chưa có"** — không over-engineer versioned WF + atomic sequence ngay từ đầu. User confirm `BudgetPolicy.Default` static + Random.Shared MaNganSach OK cho UAT, refine sau khi format chính thức chốt. 2. **Auto-recompute ở app layer thay vì DB trigger** — đơn giản hơn, debug dễ. Mỗi handler Detail mutation gọi `budget.TongNganSach = budget.Details.Sum(d => d.ThanhTien)`. Trade-off: nếu N người sửa Detail concurrent thì cuối cùng còn đúng do tx wrap. 3. **Reconcile pattern per-user try-catch** chính xác cho seed user — 1 user fail (gotcha #38 4-field rename) không kill toàn bộ seed. Drift dept/position/role tự fix nếu user đã tồn tại. 4. **Link nullable thay vì required FK** giữa Budget và Contract/PE — không bắt buộc tất cả HĐ phải có ngân sách → FE form optional + filter `Phase=DaDuyet` khi pick. Tránh chicken-and-egg khi roll-out module mới. ## H. Cảnh báo session 5 1. **FE Budget chưa có gì** — BE tested OK qua Swagger nhưng người dùng cuối chưa truy cập được. Đây là Priority 0. 2. **PE/Contract chưa thấy field Ngân sách** — BE đã có FK nhưng FE chưa wire. Priority 1. 3. **Login email** — `admin@solutionerp.local` vẫn dùng (chỉ rebrand demo + sample user). Update khi UAT thật. 4. **Atomic sequence Budget chưa chốt** — Random.Shared race condition risk thấp nhưng không zero. Nếu Solutions cần serial number nghiêm túc → migration `AddBudgetCodeSequences` + `IBudgetCodeGenerator`. 5. **Versioned workflow Budget chưa có** — nếu Solutions muốn admin config 5-step thay vì 3-step → migration `AddBudgetVersionedWorkflow` (3 bảng + pin per Budget). ## Files touched session 4 ``` src/Backend/SolutionErp.Domain/Budgets/ (NEW — 5 file) ├── BudgetPhase.cs ├── Budget.cs ├── BudgetDetail.cs ├── BudgetApproval.cs ├── BudgetChangelog.cs └── BudgetPolicy.cs src/Backend/SolutionErp.Domain/Contracts/Contract.cs (mod: +BudgetId?) src/Backend/SolutionErp.Domain/PurchaseEvaluations/PurchaseEvaluation.cs (mod: +BudgetId?) src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs (mod: +Budgets keys) src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/ ├── BudgetConfiguration.cs (NEW — 4 entity config) ├── ContractConfiguration.cs (mod: +BudgetId index) └── PurchaseEvaluationConfiguration.cs (mod: +BudgetId index) src/Backend/SolutionErp.Infrastructure/Persistence/ ├── ApplicationDbContext.cs (mod: +4 DbSet Budget*) └── DbInitializer.cs (mod: +14 Solutions user + Budget menu seed) src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/ └── 20260428043508_AddBudgets.cs (NEW — migration 14) └── 20260428043508_AddBudgets.Designer.cs (NEW) └── ApplicationDbContextModelSnapshot.cs (mod) src/Backend/SolutionErp.Application/Common/Interfaces/IApplicationDbContext.cs (mod: +4 DbSet) src/Backend/SolutionErp.Application/Budgets/ (NEW — 2 file) ├── BudgetFeatures.cs (~340 LOC, 11 CQRS handler) └── Dtos/BudgetDtos.cs (6 DTO) src/Backend/SolutionErp.Api/Controllers/BudgetsController.cs (NEW — 11 endpoint) docs/STATUS.md (mod: header + Recently Done + cumulative) docs/HANDOFF.md (mod: TL;DR + Priority 0 + creds) docs/changelog/migration-todos.md (mod: Phase 7 partial + Phase 8 mới) docs/architecture.md (mod: +§10 Budget) docs/database/schema-diagram.md (mod: +migration 13+14, +§12 Budget) docs/CLAUDE.md (mod: Phase 7 row + scope Budget) CLAUDE.md (mod: modules table + scope) .claude/skills/ef-core-migration/SKILL.md (mod: migration 14) docs/changelog/sessions/2026-04-28-chot-session-4-budget.md (NEW — file này) ~/.claude/projects/.../memory/project_solution_erp.md (mod: Phase 7 + 14 user) ```