# HANDOFF — Brief 5 phút cho session tiếp theo **Last updated:** 2026-05-08 (Session 17 wrap-up — **🎯 PE Workflow V2 schema + Service WIRE end-to-end DONE. 13 commit `c847dc0` → `de0f38d`. Mig 22-24. State machine 5 trạng thái. Service PE iterate Steps/Levels match `ApproverUserId`. Designer max 3 cấp × N NV/cấp. Panel 3 flow render thực tế. Test prod cleaned + user `nv.test@solutions.com.vn` tạo. 81 test pass. Contract V2 wire DEFER session sau.**) ## TL;DR Session 17 — PE V2 schema end-to-end User chốt sau Session 16 (drastic refactor flat Mig 21 vẫn sai intent): **viết lại schema riêng + thêm Menu "Duyệt NCC (Mới)"** với cấu trúc explicit: ``` Mã Quy trình - Tên Quy trình * Bước 1 - Phòng A * Cấp 1 - NV X ← 1 user CỤ THỂ qua ApproverUserId * Cấp 2 - NV Y * Bước 2 - Phòng B * Cấp 1 - NV Z ``` Khác Mig 21: mỗi Cấp = 1 NV chính xác, KHÔNG OR-of-many group Dept+PositionLevel/Role/User. **4 commit (3 chunk per-commit + docs):** ### Chunk A (`c847dc0`) — Domain + EF + Mig 22 + Menu - Domain `ApprovalWorkflowsV2/ApprovalWorkflow.cs` — 3 entity (ApprovalWorkflow + Step + Level) + enum `ApprovalWorkflowApplicableType` (DuyetNcc=1 / DuyetNccPhuongAn=2 / Contract=3) - EF `ApprovalWorkflowConfiguration.cs` — UNIQUE (Code, Version), FK Cascade Step→Workflow + Level→Step, FK Restrict Department + ApproverUserId - ApplicationDbContext +3 DbSet - **Migration 22** `AddApprovalWorkflowsV2` — 3 CREATE TABLE + 1 UNIQUE + 4 INDEX. Applied cả `_Design` + `_Dev` LocalDB - DbInitializer SeedMenusAsync: +menu `ApprovalWorkflowsV2` root dưới System (icon Workflow) + leaf `AwV2_DuyetNcc` (icon FileCheck, label "Duyệt NCC (Mới)") - MenuKeys.cs +2 const trong All array ### Chunk B (`f6047d5`) — Application CQRS + API - `Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs`: - `GetAwAdminOverviewQuery(ApplicableType?)` — load 3-level Include + dept/user names map - `CreateAwDefinitionCommand` + Validator — auto-increment Version theo Code, deactivate active version cùng ApplicableType - `DeleteAwDefinitionCommand` — UAT helper unconditional (chưa pin) - DTO AwDefinition/AwStep/AwLevel + AwTypeSummary - IApplicationDbContext +3 DbSet - `Api/Controllers/ApprovalWorkflowsV2Controller` — route `/api/approval-workflows-v2`, GET ?applicableType=N | POST | DELETE/{id}, reuse policy `Workflows.Read` + `Workflows.Create` ### Chunk C (`2781c7e`) — FE Designer - `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx` (~480 LOC) - Overview cards Active+History per ApplicableType - DefinitionCard read-only: Bước (badge phòng emerald) → Cấp (badge violet C1/C2 + tên NV + email) - Designer dialog: Mã/Tên/Mô tả + Add/Remove Step + reorder (chevron up/down) + Add/Remove Level + Select Phòng + Select NV duyệt - Validate: mỗi Step ≥1 Level, mỗi Level phải có ApproverUserId - Auto-assign code mặc định theo type: `QT-DN-V2-001` / `QT-DN-PA-V2-001` / `QT-HD-V2-001` - Layout.tsx resolver +ApprovalWorkflowsV2 root → `/system/approval-workflows-v2`, +AwV2_ leaf → `/system/approval-workflows-v2/` - App.tsx +2 route - menuKeys.ts +2 const sync với BE ### Chunk D — Docs STATUS + HANDOFF + project_solution_erp.md memory. ### Chunk E (UAT iteration, 9 commit) — Designer fix + State machine + Service wire + UX User UAT iter Designer V2 phát hiện multiple issues + chốt spec dần qua state diagram. Per memory `feedback_uat_skip_verify.md` UAT mode iterate nhanh: | Commit | Tóm tắt | |---|---| | `9712778` | Designer iter 1 lock 3 cấp/bước (sai intent) | | `f3bea3c` | Designer iter 2 đúng intent: max 3 cấp × N NV/cấp + sequential gating C2/C3 disabled khi prev empty + filter NV theo Phòng + no-dup same level. Validator BE strict | | `ff21120` | State machine 5 trạng thái Nháp/ĐãGửiDuyệt/TrảLại/TừChối/ĐãDuyệt. TraLai = Phase RIÊNG (không revert DangSoanThao + không jump-back). PE/Contract/Budget Phase enum + Policy + Service Reject branch → TraLai. 4 test mới TraLai entry point | | `0a40c65` | **Mig 23** `AddApprovalWorkflowIdToPurchaseEvaluation` — pin V2 vào PE entity. Workspace Select bắt buộc workflow lúc create. Validate ApplicableType match PE.Type | | `b41484b` | **Mig 24** `AddCurrentApprovalLevelOrderToPe` + Service V2 wire — `ApproveV2Async` iterate Steps/Levels group by Order = Cấp (OR-of-N approvers) match `actor.Id ∈ ApproverUserId`. Synthetic Policy `ForV2Schema()` cho FE nextPhases | | `d814429` | DTO CurrentApproval + banner "Đến lượt bạn" / "Không phải lượt bạn" + button Duyệt forward disabled khi V2 + actor không trong cấp + tooltip "chỉ {NV X / Y} duyệt được". Trả lại + Từ chối vẫn enabled | | `9e63e2d` `d250ae4` `74745a7` | List/Inbox V2-aware (`ResolveV2InboxIdsAsync` precompute IDs). 2 dropdown filter Quy trình + Trạng thái (chỉ ở Duyệt). Inbox endpoint nhận `approvalWorkflowId` | | `ac41d5e` | SQL `clean-transactional-uat.sql` — clean prod (9 PE + 11 HĐ + 19 Notif xóa) giữ master. Run qua SSH VPS `.\SQLEXPRESS` | | `de0f38d` | Panel 3 thay 4 phase cards bằng flow workflow thực tế: Bước (icon ✓/●/○) → Cấp (label "đang chờ"/"đã duyệt" + tên NV). DTO `ApprovalFlow` full snapshot với Status Done/Current/Pending | **Stats final Session 17:** 24 migration (+3), 58 DB tables (+3), ~140 endpoints (+5), 81 test pass (+4). **Test user UAT** (tạo qua API admin): - Email: `nv.test@solutions.com.vn` / Pass: `TestUser@123456` / Role: Drafter / Phòng: CCM ## ⚠️ Điều quan trọng cho Session 18+ 1. **Contract V2 wire CHƯA làm** — chỉ PE wire xong (Mig 23-24). Session sau mirror pattern PE → Contract: - Thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder` (Mig 25) - Update `ContractWorkflowService` thêm `ApproveV2Async` branch - Update Workspace Select V2 trong `ContractCreatePage` - Pin V2 thành mặc định cho Contract types 2. **Phân quyền strict V2** — hiện loose UAT (mọi authenticated user thấy phiếu V2). Sau confirm flow OK: - List: Drafter + bất kỳ approver any-Step + Admin - Inbox: chỉ approver Cấp hiện tại (V2 đã đúng — `ResolveV2InboxIdsAsync`) - Detail: same as List 3. **Drop legacy V1 sau UAT** — khi không còn phiếu nào pin `WorkflowDefinitionId`: - Drop tables `WorkflowDefinitions` + `WorkflowSteps` + `WorkflowStepApprovers` + PE versions - Cleanup migration: drop column `RejectedAtStepIndex` + `RejectedFromPhase` (deprecated từ Session 17) - Drop `ApproveV1LegacyAsync` branch trong Service 4. **Admin role bypass** — hiện code `if (!isAdmin && !isSystem)` skip approver check. By design cho UAT + emergency override. Nếu prod cần audit override → option C trong Session 17 thảo luận: thêm flag `IsAdminOverride=true` trong approval row + banner đỏ trên detail. 5. **81 test pass** — Domain WorkflowPolicyTests + PurchaseEvaluationPolicyTests + BudgetPolicyTests đã update cho TraLai entry point. KHÔNG có test cho V2 Service wire (defer khi UAT confirm + có sample data). --- ## TL;DR Session 16 (08/05 — Drastic refactor flat workflow EXECUTE) Resume từ Session 15 defer plan. Per memory `feedback_drastic_refactor_scope.md`: dedicated session, fresh context, conservative buffer. **Spec:** Workflow flat list (Phòng × Cấp × Approvers). Mỗi step = 1 (Phòng × Cấp). Service iterate steps OrderBy Order, advance pointer. Phase enum simplify ChoDuyet=10. Pin WorkflowDefinitionId. **2 chunk per-commit (5-6 chunk plan rút gọn vì BE tightly coupled):** ### Chunk A (`dbb0089`) — All BE: Domain + Mig 21 + Service + Tests **Domain entities:** - Phase enum (PE + Contract): + ChoDuyet=10 generic intermediate. Legacy 2-6 + 98 deprecated (giữ enum cho data cũ). - WorkflowStep + DepartmentId Guid? FK Restrict + PositionLevel int? - PurchaseEvaluation/Contract + CurrentWorkflowStepIndex int? + RejectedAtStepIndex int? - DROP class WorkflowStepInnerStep + nav (PE + Contract) - DROP *DepartmentApproval.InnerStepId column **EF Configurations:** - DROP InnerStep config (PE + Contract) → table dropped - WorkflowStep config + DeptId/PositionLevel + FK Restrict - DepartmentApprovals: restore simple unique non-filtered (Mig 19/20 filtered split reverse) **ApplicationDbContext:** DROP DbSet<*WorkflowStepInnerStep> × 2 **Migration 21** `RefactorWorkflowToFlatModel` GỘP: - 4 ALTER (PE/Contract +CurrentStepIndex +RejectedAtStepIndex) - 2 ALTER (WorkflowStep +DepartmentId +PositionLevel) PE + Contract - DROP TABLE × 2 (PEWorkflowStepInnerSteps + WorkflowStepInnerSteps Mig 18+20) - DROP COLUMN × 2 (*DeptApproval.InnerStepId) - DROP filtered indexes Mig 19/20 - RESTORE simple UNIQUE (TargetId, Phase, Dept, Stage) non-filtered × 2 **Service rewrite (PE + Contract WorkflowService.TransitionAsync):** - DangSoanThao → ChoDuyet (Drafter trình, init idx=0) - ChoDuyet → ChoDuyet (advance idx per approve) - ChoDuyet → DaDuyet/DaPhatHanh (idx ≥ steps.Count → terminal, gen mã HĐ Contract) - ChoDuyet → DangSoanThao (Trả lại — save RejectedAtStepIndex) - ChoDuyet → TuChoi (Từ chối — khoá vĩnh viễn) - Resume Drafter (DangSoanThao + RejectedAtStepIndex≠null) → ChoDuyet jump-back - Match approver: actor.Dept == step.Dept AND actor.PositionLevel >= step.PositionLevel (OR-of-many cùng cấp/dept) OR Approvers.Kind=User|Role match - Admin role bypass policy **App CQRS:** WorkflowStepDto + WorkflowStepInput drop InnerStep, add DepartmentId/DepartmentName/PositionLevel (PE + Contract mirror). **Tests:** - DROP `PeNStageApprovalTests.cs` (6) + `ContractNStageApprovalTests.cs` (6) + `PeTwoStageApprovalTests.cs` (7) — legacy - UPDATE `PeWorkflowAdminTests` signature for new flat input - **96 → 77 test pass** (-19 legacy) **3-file rule** Mig 21 commit đủ (.cs + Designer + Snapshot). ### Chunk B (`88a5be1`) — FE Designer + types **PeWorkflowsPage + WorkflowsPage rewrite (~210 LOC each):** - Drop InnerStepDto + EditInnerStep types - Drop PHASE_OPTIONS (auto-assign ChoDuyet=10 behind scenes) - StepDto + EditStep + departmentId, departmentName, positionLevel - Designer step UI rewrite: Tên + Phòng Select + Cấp Select + SLA + Approvers (Role/User optional fallback). Drop InnerSteps sub-section. - DefinitionCard view: badge Phòng emerald + Cấp NV/PP/TP violet - Save payload: phase=10 (ChoDuyet) - Hint amber: "User cùng Phòng + Cấp ≥ step → duyệt được (OR-of-many)" **types/purchaseEvaluation.ts (fe-admin + fe-user mirror):** + ChoDuyet=10 enum + label "Đang duyệt" + color amber. Legacy 2-6 + 98 keep. **Chunk C (FE PeWorkflowPanel) SKIPPED** — existing UI compatible (workflow.nextPhases BE-driven, 3-button Trả lại/Từ chối Session 14 reuse với target=DangSoanThao/TuChoi pattern). ### Verify - ✅ dotnet build SolutionErp.slnx 0 error - ✅ dotnet ef database update Mig 21 LocalDB applied OK - ✅ dotnet test 77 pass (54 Domain + 23 Infra) - ✅ npm build fe-admin + fe-user pass ### Cumulative sau Session 16 | | Trước S16 | Sau S16 | |---|---:|---:| | BE LOC | ~15800 | ~15500 (-300 service simplified) | | Migrations | 20 | **21** | | DB tables | 57 | **55** (-2 InnerStep tables) | | Tests | 96 | **77** (-19 legacy N-stage/2-stage) | | FE pages | 32 | 32 (rewrite existing 2 designer) | ## ⚠️ CẢNH BÁO Session 17+ 1. **UAT live test** — workflow flat ready. Tạo new workflow definition qua `/system/pe-workflows/:typeCode` với 3 phòng × N cấp setup. Verify Drafter trình → cấp 1 phòng A → cấp 2 phòng A → cấp 1 phòng B → ... → DaDuyet flow. 2. **Old PE/HĐ pinned legacy workflow definitions** (phase=ChoPurchasing/ChoCCM/etc) — service rewrite chỉ handle ChoDuyet=10 + DangSoanThao/DaDuyet. Old data ở phase 2-6 sẽ stuck (admin manual transition required). Recommend: data migration script convert old workflow → new flat model (defer). 3. **Approver explicit (Role/User Approvers list)** — fallback nếu user không match Dept+PositionLevel của step. Cho phép user external (không thuộc dept) duyệt qua Role match (vd Admin) hoặc User explicit. 4. **Bypass cấp dưới cùng dept** — User TP với CanBypassReview=true cùng dept và PositionLevel cao hơn step.PositionLevel → duyệt qua. KHÔNG batch upsert NV+PP rows như Mig 18 N-stage trước (đơn giản hóa: 1 step approve = 1 row). 5. **N-stage tests dropped** — 19 test legacy (Mig 18, 20 N-stage + Mig 16 2-stage). Có thể viết test mới cho flat workflow flow nếu UAT phát sinh bug. Defer. 6. **Sample data N-stage seed** task vẫn pending (Session 14). Block trên DesignTime vs Runtime DB gotcha + DbInitializer seed flow. 7. **Budget N-stage** vẫn defer (cần versioned WF migration trước). 8. **schema-diagram §17-21 update** defer cron audit 2026-06-01. ## TL;DR Session 15 (07/05 — Tooltip diagnose + drastic refactor DEFER) User UAT live screenshot báo button "Lưu & Gửi Duyệt" KHÔNG hoạt động + suy đoán "trùng ID" giữa các phiếu. **Diagnose (commit `835cc7f`):** - Root cause: button silent disabled khi `evaluation.workflow.nextPhases` không có forward phase (chỉ TuChoi/TraLai). Cause khả năng: workflow definition pinned thiếu adjacent step → `policy.NextPhasesFrom(DangSoanThao)` return empty. - Improvement: tooltip + dialog hiển thị reason rõ ràng: - `submitDisabledReason` text: "Phiếu đã ở phase X — chỉ Bản nháp/Trả lại mới sửa+gửi" / "Workflow không có phase tiếp theo từ X. Liên hệ admin kiểm tra cấu hình" - Button title attribute → hover show reason hoặc forward phase label - Dialog confirm show forward phase explicit ("Sẽ chuyển sang Chờ Purchasing") - Mirror fe-admin + fe-user. Build pass cả 2. KHÔNG đụng BE — chỉ FE diagnostic UX. - "Trùng ID" KHÔNG phải bug FE — `PurchaseEvaluationWorkspacePage` URL state đúng, mỗi PE row unique GUID + MaPhieu. Suy đoán user do button silent. **Plan drastic refactor → DEFER:** User confirm "bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking" — refactor workflow từ phase-based + InnerStep nested model sang flat WorkflowStep model (mỗi step = Phòng × Cấp + Approvers users). Edit working tree 12 files (Domain entities + EF Configurations + DbContext): - Phase enum +ChoDuyet=10, legacy values 2-6 deprecated - WorkflowStep +DepartmentId, +PositionLevel - Drop class WorkflowStepInnerStep + nav (PE + Contract) - PE/Contract +CurrentWorkflowStepIndex int?, +RejectedAtStepIndex int? - *DepartmentApproval drop InnerStepId column - EF Configurations: drop InnerStep config + nav, restore simple unique non-filtered - DbContext: drop DbSet × 2 Reality check scope realistic ~8-10h: 1. Domain + EF + DbContext (~50min) ✓ done in working tree 2. PolicyRegistry rewrite PE+Contract (~45min) 3. App CQRS DTOs rewrite (~45min) 4. Service rewrite PE+Contract (~2-3h) 5. Tests rewrite — drop 12 N-stage tests + update remaining (~1.5h) 6. Migration 21 + LocalDB apply + verify (~30min) 7. FE Designer rewrite (~1.5h) 8. FE PeWorkflowPanel + workflow timeline (~1h) 9. Docs/Skill update (~45min) Vượt session boundary + risk session deep ~30 commits → **REVERT working tree** về `835cc7f` clean state. Test 96 pass intact. **Decision memorized:** add memory `feedback_drastic_refactor_scope.md` — drastic refactor cần dedicated session, scope estimation conservative (2x buffer), tránh mid-session big refactor. ## ⚠️ CẢNH BÁO Session 16+ 1. **Drastic refactor flat workflow chưa làm — DEFER** với plan chi tiết. Khi resume: - Plan kỹ 6 chunk per-commit - Buffer 2x estimate (~16h thực tế) - Tests rewrite biggest risk - Hoặc fall back Approach Y (FE Designer flat UI giới hạn 5 phòng) ROI 1-2h nếu user OK trade-off 2. **Task 2 sample data seed N-stage** vẫn pending (block trên DesignTime vs Runtime DB gotcha + DbInitializer seed flow) 3. **schema-diagram §17-19 Mig 18-20** vẫn defer cron audit 2026-06-01 4. **Hard blockers Ops** giữ nguyên 6 task ## TL;DR Session 14 (07/05 — PE 3-button approval workflow) User chỉ thị thay 2-button approval (Duyệt + Reject mơ hồ) bằng **3 hành động rõ ràng** cho approver: - **Duyệt** = forward phase tiếp theo (decision=Approve) - **Trả lại** = về DangSoanThao + Drafter sửa (decision=Reject + target=DangSoanThao). Smart reject pattern Mig 16 + clear N-stage rows + Drafter resume jump-back tới phase đã reject. - **Từ chối** = phase=TuChoi (decision=Reject + target=TuChoi). Phiếu khoá vĩnh viễn (17 handler Mig 16 lock edit). Drafter phải tạo phiếu mới. **1 commit (`0d77698`):** - **Domain `PurchaseEvaluationPolicy.cs`**: NccOnly + NccWithPlan thêm `(X → TuChoi)` transition cho mọi phase trung gian (ChoPurchasing/ChoDuAn/ChoCCM/ChoCEODuyetPA/ChoCEODuyetNCC) với roles của phase đó. FromDefinition expand: mỗi step (trừ DangSoanThao) thêm (step.Phase → TuChoi) với roles step. - **Service** `PurchaseEvaluationWorkflowService.TransitionAsync` — Reject branch tách 2 case: ``` if (decision == Reject) { if (target != TuChoi) { // Trả lại RejectedFromPhase = fromPhase target = DangSoanThao // force clear N-stage rows tại fromPhase } // else target=TuChoi: giữ nguyên, KHÔNG set RejectedFromPhase, KHÔNG clear } ``` - **FE PeWorkflowPanel (admin + user mirror)**: render 3 button rõ: - "✓ Duyệt →