# Migration To-dos — Atomic Roadmap > Tick `[x]` khi xong. Phase 0-8 đã DONE — collapsed. Detail xem session > logs trong `docs/changelog/sessions/`. Active work: Phase 9 (UAT + Ops + carry over PE PDF export + Tests Phase 3-5). ## ✅ Phase 0-5 + Tier 3 — Done (2026-04-21..22) | Phase | Focus | Trạng thái | |---|---|---| | 0 Draft | Scaffold .NET 10 + 2 Vite + parse FORM/QT + docs | ✅ | | 1 Alpha Core | Auth + 12 role + Permission Matrix + 3 master CRUD + Contract draft | ✅ | | 2 Form Engine | OpenXml + ClosedXML render + LibreOffice PDF + DynamicForm | ✅ | | 3 Workflow | State machine 9 phase + RG-001 code gen + SLA job + attachments + SignalR realtime | ✅ | | 4 Reporting | Dashboard KPI + Excel export + MyDashboard role-aware + brand identity #1F7DC1 | ✅ | | 5 Production | CI/CD Gitea Actions + 3 IIS site + Let's Encrypt + Security headers + Users CRUD | ✅ | | Tier 3 | Versioned workflow + 3-panel layout + 4-bảng overhaul + 4 master catalogs + 16 demo users + RolesPage CRUD + 7 demo HĐ varied | ✅ | Detail chi tiết: `docs/changelog/sessions/2026-04-21-*.md` + `2026-04-22-0300-tier3-feature-complete.md` + `2026-04-23-*.md`. ## ✅ Phase 6 — Module Duyệt NCC (tiền-HĐ) — Done ### Iter 1 (2026-04-23) - [x] Migration 12 `AddPurchaseEvaluations` — 10 bảng (Header/Suppliers/Details/Quotes/Approvals/Changelogs/Attachments/WorkflowDefinitions/Steps/StepApprovers) - [x] Domain — 2 enum (Type A/B, Phase 7-state) + Policy record + Registry + FromDefinition builder - [x] Seed — 13 menu Pe_*/PeWf_* + 2 WorkflowDefinition v01 (QT-DN-A 3-step, QT-DN-B 5-step) - [x] Application CQRS ~900 LOC — Create/Update/Transition/List/Inbox/Get/Delete + Supplier CRUD + Detail CRUD + Quote Upsert + SelectWinner + Changelog - [x] PurchaseEvaluationWorkflowService — policy guard + approval + notification + changelog - [x] PurchaseEvaluationsController — 17 endpoint REST - [x] FE 2 app — Types + PurchaseEvaluationsListPage 3-panel + Create page + PeDetailTabs + PeWorkflowPanel + Menu resolver Pe_* - [x] Kế thừa HĐ — `CreateContractFromEvaluationCommand` (guard DaDuyet + SelectedSupplier + !ContractId) → Contract draft. FE CreateContractDialog pick ContractType. - [x] **Migration 13** `AddPurchaseEvaluationCodeSequences` — atomic MaPhieu sequence `PE/{YYYY}/{A|B}/{Seq:D3}` - [x] Demo PE seed — 4 phiếu varied phase (A-001/A-002/A-003/B-001) + Pe_* permission defaults 7 role × 9 menu key Session log: `2026-04-23-2300-purchase-evaluations.md` + `2026-04-24-1030-pe-polish-demo-maphieu-perms.md`. ### Iter 2 — UX polish (2026-04-24) - [x] Rename menu "Phương Án" → "Giải pháp" + backfill DB (zero breaking change) - [x] Menu tree inheritance extend Pe_*/PeWf_* (`GetMyMenuTreeQuery` + 4 root) - [x] Accordion mutex Pe_* groups + sidebar w-72 + label nowrap - [x] NavLink active check query string (queryMatches helper) — fix 2 leaf cùng highlight - [x] PE detail flat layout: Panel 2 = 4 section (Thông tin/NCC/Hạng mục/**Bảng so sánh**), Panel 3 += Approvals + Changelog - [x] Upload file đính kèm per-NCC (SupplierAttachmentsCell) + Bảng so sánh tổng (GeneralAttachmentsSection, supplierRowId=null) + enum `ComparisonTable=4` - [x] readOnly mode menu "Duyệt" (pendingMe=1) — hide Sửa/Xóa/Thêm/Edit/Upload/Delete, giữ download + transition + comment - [x] Contract: move Lịch sử điều chỉnh Panel 2 → Panel 3 (Chi tiết HĐ full-width) - [x] Demo email rebrand `@solutionerp.local` → `@solutions.com.vn` + `BackfillUserEmailDomainAsync` (idempotent rename 4 field Email/NormalizedEmail/UserName/NormalizedUserName) Session log: `2026-04-24-chot-session-3-pe-polish.md`. ### ✅ Domain rebrand `.huypham.vn` → `.solutions.com.vn` (2026-04-24) - [x] 18 file repo (FE env + scripts + CI/CD + docs + skill + code comments) - [x] `scripts/migrate-domains.ps1` (ASCII-only #30) — 3 IIS binding + 3 cert Let's Encrypt + auto HTTPS + redirect - [x] CI/CD auto rebuild BE CORS + FE bundle VITE_API_BASE_URL - [x] E2E verified 3 domain live + preflight OK Sub: `api.solutions.com.vn` · `admin.solutions.com.vn` · `eoffice.solutions.com.vn`. Old `.huypham.vn` vẫn fallback (chưa remove — Phase 9 Ops). ## 📝 Phase 7 — PE feature gap + Budget BE (Session 4 partial done) ### A. PE feature gap (3 task — phần lớn đóng ở Phase 8 S5) - [x] **PE Workflow admin designer UI** `/system/pe-workflows/:typeCode` — done S5 (`5d94bb4`) - BE `Application/PurchaseEvaluations/PeWorkflowAdminFeatures.cs` (mirror `WorkflowAdminFeatures.cs`) - `Api/Controllers/PeWorkflowsController.cs` - FE `fe-admin/src/pages/system/PeWorkflowsPage.tsx` + `PeWorkflowDesigner.tsx` - Route `/system/pe-workflows/:typeCode` (menu PeWf_* + resolver đã sẵn) - [x] **Ý kiến 4 phòng ban** (Phê duyệt / P.CCM / P.MuaHàng / SM-PM) ở tab Thông tin — done S5 (`5d94bb4`, Migration 15) - Option A: 4 text field + signoff date + UserId vào header - Option B: dùng `PurchaseEvaluationApprovals` với roleKind extra field - **Chốt:** dùng Migration 15 `AddPurchaseEvaluationDepartmentOpinions` (separate table UNIQUE PEId+Kind, 4 box sign-off 2x2 grid OpinionBox như Excel mẫu) — tốt hơn Option A/B vì audit qua Changelog + Upsert preserve chữ ký cũ khi text-only edit. - [ ] **Export phiếu PDF/Excel** — tái dùng `IDocumentConverter` + template `PE-TrinhDuyet.docx` → carry over Phase 9 (user pending — không quan trọng lắm) ### B. Optional polish (carry over Phase 9 — làm khi UAT phát sinh) - [ ] Auto-map PE Details → Contract per-type Details khi gen HĐ (phức tạp vì 7 schema khác nhau) - [ ] Payment terms tách field từ JSON → 6 column (Tạm ứng/TT tạm/Quyết toán/Bảo hành/Hạn mức/Đánh giá) - [ ] Matrix Quotes bulk paste từ Excel - [ ] fe-user Inbox thêm section "Phiếu Duyệt NCC chờ tôi" ### C. Ops (carry over Phase 9 — Hard blockers) - [ ] Remove binding cũ `.huypham.vn` sau verify stable: `ssh vietreport-vps ; cd C:\solution-erp\scripts ; .\migrate-domains.ps1 -RemoveOld -SkipCert` - [ ] win-acme scheduled task fix unhealthy (cert expire 2026-06-18) - [ ] UAT thật 1 tuần với 2-3 user (30 demo user — 16 sample + 14 Solutions thật) - [ ] SMTP config → Email outbox - [ ] Rotate credentials (admin + 30 demo + SA + vrapp + JWT) - [ ] Schedule SQL backup Task Scheduler ### D. Module Ngân sách (Budget) — Session 4 ✅ partial done - [x] **Migration 14** `AddBudgets` — 4 bảng (Budgets/BudgetDetails/BudgetApprovals/BudgetChangelogs) + index BudgetId nullable trên Contract & PurchaseEvaluation - [x] Domain — `Budget` (Header) + `BudgetDetail` (flat row) + `BudgetApproval` + `BudgetChangelog` + enum `BudgetPhase` 5-state + `BudgetEntityType` Header/Detail/Workflow - [x] `BudgetPolicy.Default` hardcoded simple 3-step (Drafter→CCM→CEO + Reject từ ChoCCM/ChoCEO về DangSoanThao) - [x] Application CQRS ~340 LOC — Create + UpdateDraft + Transition + List + GetDetail + Delete (only DangSoanThao/TuChoi) + Detail CRUD (auto-recompute TongNganSach) + ListChangelogs - [x] `BudgetsController` 11 endpoint REST - [x] Menu seed `Budgets` root + 3 leaf (Bg_List/Bg_Create/Bg_Pending) order=27 icon Wallet - [x] **14 demo user Solutions thật** — PRO 5 + CCM 7 + ISO 1 + CEO 1 (pwd `User@123456`). Reconcile pattern (gotcha #38 4-field rename). Tổng 30 user (16 sample cũ + 14 Solutions thật mới). Session log: `2026-04-28-chot-session-4-budget.md`. ### E. Pending migrations - [ ] `AddPePaymentTermFields` (nếu chốt UX tách field — JSON blob → 6 column) - [x] **`AddPurchaseEvaluationDepartmentOpinions`** ✅ migration 15 (S5) - [ ] `AddBudgetCodeSequences` (nếu chốt format MaNganSach atomic — hiện Random.Shared) - [ ] `AddBudgetVersionedWorkflow` (nếu user cần admin config UI thay vì hardcoded `BudgetPolicy.Default`) ## ✅ Phase 8 — Budget FE + PE/HD integration (Session 5 done) ### A. FE Budget pages — done ✅ - [x] `fe-admin/src/types/budget.ts` (BudgetPhase 5-state enum + DTO types) - [x] `fe-admin/src/pages/budgets/BudgetsListPage.tsx` (3-panel `[340px_1fr_360px]` + filter Phase/Năm + ?phase=Pending alias + readOnly mode + BudgetDetailPage fullpage mobile) - [x] `fe-admin/src/pages/budgets/BudgetCreatePage.tsx` (form Header — Tên/Năm/Dự án/Phòng ban/Mô tả) - [x] `fe-admin/src/components/budgets/BudgetDetailTabs.tsx` (Section Thông tin Header + Section Hạng mục table CRUD inline auto-compute ThanhTien=KL×ĐG) - [x] `fe-admin/src/components/budgets/BudgetWorkflowPanel.tsx` (Panel 3 timeline activePhases + nextPhases buttons + Dialog comment + Approvals/Changelog) - [x] Mirror tất cả sang `fe-user/` - [x] App.tsx routes `/budgets`, `/budgets/new`, `/budgets/:id` cả 2 app - [x] Menu resolver `Bg_*` (Bg_List → `/budgets`, Bg_Pending → `/budgets?phase=Pending`, Bg_Create → `/budgets/new`) ### B. PE/Contract → Budget integration — done ✅ - [x] **PE form** + Select "Ngân sách" filter Phase=DaDuyet, ProjectId match, BE validate - [x] **Contract form** (Header + Edit) tương tự, EditForm read-only link card khi !isDraft - [x] PE Detail Hạng mục thêm cột "NS link · Δ" — match per-row qua `groupCode|itemCode` + footer aggregate (xanh dưới / đỏ vượt / xám khớp) - [x] PE Detail UI restructure 4 section đánh số match form spec PHIẾU TRÌNH KÝ - [x] BE: BudgetSummaryDto shared + Create/Update PE+Contract commands + BudgetId? + GetQueries load Budget - [x] CreateContractFromEvaluation carry forward pe.BudgetId → contract.BudgetId ### C. PE Workflow Designer admin UI — done ✅ - [x] BE `PeWorkflowAdminFeatures.cs` ~250 LOC mirror Contract pattern - [x] BE `PeWorkflowsController` 2 endpoint reuse policy `Workflows.*` - [x] FE `PeWorkflowsPage.tsx` ~500 LOC + designer dialog (clone/edit/+Role/+User) - [x] App.tsx route `/system/pe-workflows/:typeCode` ### D. Ý kiến 4 phòng ban — done ✅ - [x] Migration 15 `AddPurchaseEvaluationDepartmentOpinions` (UNIQUE PEId+Kind) - [x] Domain entity + enum `PeDepartmentKind` (PheDuyet/Ccm/MuaHang/SmPm) - [x] BE Upsert (sign=true → set SignedAt+UserId, sign=false giữ chữ ký cũ) + Delete + 2 endpoint - [x] FE Section "5. Ý kiến 4 phòng ban (sign-off)" 2x2 grid OpinionBox ### E. Tests Phase 1-2-3mini + CI optimize — done ✅ - [x] **Phase 1** — `tests/SolutionErp.Domain.Tests/` (xUnit + FluentAssertions 7.2): 54 test policy state machine (Contract WF + PE WF + Budget) + Registry + FromDefinition versioned + UserKindApprover - [x] **Phase 2** — `tests/SolutionErp.Infrastructure.Tests/` (EF SQLite + TestApplicationDbContext override `nvarchar(max) → TEXT`): 17 test code generator format + sequence + year boundary + persistence verify - [x] **Phase 3 mini** — `tests/.../Application/PeWorkflowAdminTests.cs`: 6 test CreatePeWorkflowDefinitionCommand versioning (auto-increment + deactivate cũ + EvaluationType independence + steps/approvers persistence) - [x] CI gate `.gitea/workflows/deploy.yml` — 2 step `dotnet test` trước build, fail → no deploy - [x] **Total 77 test pass / ~3s** - [x] **CI manual checkout bypass github.com** — fix gotcha #39 (act_runner TCP timeout 21s) - [x] **CI path filter docs-only skip** — gotcha #41 (paths-ignore behavior) - [ ] **Tests Phase 3 full** — Opinion Upsert + Budget link validation (cần Identity UserManager setup helper) - [ ] **npm junction cache CI optimize** (rollback ở `a21790d` — gotcha #40 chưa debug) ## 📝 Phase 9 — UAT + Ops + carry over (Session 6+ active) ### ✅ Session 16 done (2026-05-08) — DRASTIC REFACTOR flat workflow Phòng × Cấp (Mig 21, 2 commit Chunk A+B) User chốt drastic refactor: bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking. Workflow flat list (Phòng × Cấp × Approvers). Pin WorkflowDefinitionId. Per memory `feedback_drastic_refactor_scope.md`: dedicated session + conservative buffer. - [x] **Chunk A (`dbb0089`)** All BE — Domain enum simplify (ChoDuyet=10, legacy 2-6+98 deprecated giữ data cũ) + WorkflowStep +DepartmentId/PositionLevel + PE/Contract +CurrentWorkflowStepIndex/RejectedAtStepIndex + drop InnerStep entity/nav (PE+Contract) + drop *DeptApproval.InnerStepId + EF Configurations restore simple unique non-filtered + DbContext drop DbSets. **Migration 21** `RefactorWorkflowToFlatModel` GỘP (4 ALTER cols PE/Contract + 2 ALTER WorkflowStep + DROP TABLE × 2 + DROP COLUMN InnerStepId × 2 + restore simple unique × 2). Service rewrite TransitionAsync flat logic (Drafter trình → init idx=0, advance idx per approve, last step → DaDuyet/DaPhatHanh, Trả lại save RejectedAtStepIndex, Resume jump-back, Match approver Dept+PositionLevel OR Approvers Role/User). App CQRS DTOs simplified. Tests DROP PeNStageApprovalTests + ContractNStageApprovalTests + PeTwoStageApprovalTests (19 test legacy). UPDATE PeWorkflowAdminTests signature. **96 → 77 test pass**. 3-file rule Mig 21 commit đủ. - [x] **Chunk B (`88a5be1`)** FE Designer — PeWorkflowsPage + WorkflowsPage rewrite (~210 LOC each): drop InnerStep types + PHASE_OPTIONS, auto-assign ChoDuyet=10, step UI Tên + Phòng Select + Cấp Select + SLA + Approvers Role/User optional fallback, drop InnerSteps sub-section, DefinitionCard view badge Phòng/Cấp. types/purchaseEvaluation.ts (fe-admin + fe-user mirror) + ChoDuyet=10 enum + label "Đang duyệt" + color amber. KHÔNG đụng PeWorkflowPanel (Chunk C SKIP — existing UI compatible). - [⊘] **Chunk C (FE PeWorkflowPanel + workflow timeline) SKIP** — existing UI dùng `workflow.nextPhases` BE-driven, 3-button Trả lại/Từ chối Session 14 reuse với target=DangSoanThao/TuChoi pattern. KHÔNG cần đụng. **Defer Session 17+:** - [ ] UAT live test workflow flat (3 phòng × N cấp setup) - [ ] Old PE/HĐ data migration (pinned legacy workflow phase 2-6 stuck) — admin manual transition hoặc data migration script - [ ] Sample data seed N-stage (block DesignTime vs Runtime DB) - [ ] Budget N-stage (cần versioned WF migration) - [ ] schema-diagram §17-21 update (cron audit 2026-06-01) - [ ] Skill ef-core-migration + contract-workflow refresh (cron audit) - [ ] Tests cho flat workflow flow (làm khi UAT bug) ### ✅ Session 15 done (2026-05-07) — Tooltip diagnose "Lưu & Gửi Duyệt" + drastic refactor flat workflow DEFER (1 commit) User UAT live báo button "Lưu & Gửi Duyệt" KHÔNG hoạt động + suy đoán "trùng ID". Diagnose: silent disabled khi `nextPhases` không có forward phase. Add tooltip + dialog warning. "Trùng ID" KHÔNG phải bug FE. - [x] **Tooltip diagnose** (commit `835cc7f`) — fe-admin + fe-user PeDetailTabs: compute `forwardPhase` once + `submitDisabledReason` reason string + button title attribute hover + dialog confirm show forward phase label explicit. Build pass × 2. KHÔNG đụng BE. - [⏸] **Drastic refactor flat workflow** — User chốt "bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking". Attempt edit working tree 12 file Domain/Configurations/DbContext, realize scope realistic ~8-10h (PolicyRegistry + Service + App CQRS + 12 tests + FE Designer + Migration 21 + Docs) vượt session. REVERT working tree về `835cc7f` clean. **Defer Session 16+** dedicated session. **Decision memorized:** add memory `feedback_drastic_refactor_scope.md` — drastic refactor cần dedicated session với context fresh, scope estimation conservative (2x buffer), tránh mid-session big refactor (risk session context deep + breaking states giữa chunk). **Defer Session 16+:** - [ ] Drastic refactor flat workflow (Mig 21, ~8-10h, dedicated session) — flat WorkflowStep với DepartmentId+PositionLevel, drop InnerStep, PE/Contract CurrentWorkflowStepIndex tracking, Phase enum simplify ChoDuyet=10, Service rewrite, Designer FE rewrite. - [ ] Hoặc fallback Approach Y — FE Designer flat UI giới hạn 5 phòng (auto-assign Phase behind scenes), ROI 1-2h, KHÔNG drastic. - [ ] Task 2 sample data seed N-stage (block trên DesignTime vs Runtime DB) ### ✅ Session 14 done (2026-05-07) — PE 3-button workflow Duyệt/Trả lại/Từ chối (1 commit) User chỉ thị thay 2-button approval (Duyệt + Reject mơ hồ) bằng 3 hành động rõ: - Duyệt = forward - Trả lại = về DangSoanThao + Drafter sửa (smart reject Mig 16 + clear N-stage rows + Drafter resume jump-back) - Từ chối = Phase=TuChoi, phiếu khoá vĩnh viễn (17 handler Mig 16 lock edit) - [x] **Domain `PurchaseEvaluationPolicy.cs`** — NccOnly + NccWithPlan thêm (X → TuChoi) transition cho mọi phase trung gian + FromDefinition expand step (trừ DangSoanThao) thêm (step.Phase → TuChoi). - [x] **Service** `PurchaseEvaluationWorkflowService.TransitionAsync` — Reject branch tách 2 case: target=TuChoi giữ nguyên (no override / no RejectedFromPhase / no N-stage clear). target khác (DangSoanThao) → smart reject pattern. - [x] **FE PeWorkflowPanel** (admin + user mirror) — render 3 button "✓ Duyệt → X" / "← Trả lại" / "✗ Hủy / Từ chối" + decision logic + dialog confirm với warning red (Cancel) / amber hint (SendBack). - [x] **Tests** — rename Reject test target TuChoi→DangSoanThao + NEW Reject_To_TuChoi_Locks_Permanently + update NStage_Reject_Clears target. **95 → 96 test pass**. **Defer Session 15+:** - [ ] Task 2 sample seed N-stage (block trên DesignTime vs Runtime DB gotcha + API exit sớm khi DbInitializer seeding). - [ ] Phase TraLai = 98 enum orphan — có thể remove migration nếu cleanup, ko gây hại. ### ✅ Session 13 done (2026-05-07) — Mirror N-stage Contract (Mig 20, 5 commit per-chunk + skip Chunk E auto-bind) User chỉ thị mirror N-stage từ PE sang Contract. Budget defer (cần migration `AddBudgetVersionedWorkflow` trước — hardcoded `BudgetPolicy.Default` chưa có WorkflowDefinition entity). Pattern reusable đầy đủ từ PE. - [x] **Chunk A (`951ffa3`)** Domain entity `WorkflowStepInnerStep` (Domain/Contracts/) + nav WorkflowStep.InnerSteps + ALTER ContractDeptApproval.InnerStepId + EF config FK Cascade Step / Restrict Dept+InnerStep + **Migration 20** `AddContractWorkflowInnerStepsAndAlterDeptApprovalUnique` GỘP 1 (CREATE TABLE WorkflowStepInnerSteps + ALTER InnerStepId + DropIndex old + Recreate filtered legacy `WHERE InnerStepId IS NULL` + new filtered N-stage `WHERE InnerStepId IS NOT NULL`). - [x] **Chunk B (`04cf2a0`)** Application CQRS DTO mirror PE Chunk B — `WorkflowStepInnerStepDto` + extend `WorkflowStepDto` + `CreateWorkflowStepInnerStepInput` + extend `CreateWorkflowStepInput` (default null backward compat) + Validator child rules + Handler atomic batch insert. - [x] **Chunk C (`e247b67`)** ContractWorkflowService refactor mirror PE — load InnerSteps eager + reject branch clear N-stage rows + dept block split hasInnerSteps→N-stage logic / else→legacy 2-stage. N-stage flow giống PE: match firstPending Order asc + (exact level OR canBypass + level≥), exact upsert / bypass batch upsert, recheck stillPending → BLOCK. - [x] **Chunk D (`7c0772a`)** ContractNStageApprovalTests 6 test mirror PE pattern + helper SeedWorkflowDefinitionAsync 2 step adjacent (DangGopY + DangDamPhan) + SeedContractAsync với Project + Supplier + FakeChangelogService + FakeContractCodeGenerator stubs. Bug fix legacy fallback test: switched phase pair sang DangKiemTraCCM → DangTrinhKy + role CostControl khớp Standard.Transitions. **89→95 test pass**. - [x] **Chunk E SKIP** — WorkflowsController auto-bind `[FromBody] CreateWorkflowDefinitionCommand` record qua JSON, no code change cần. - [x] **Chunk F (current)** FE WorkflowsPage Designer extend mirror PeWorkflowsPage Chunk F: InnerStepDto + EditInnerStep types + copyFromDefinition include + departmentsList query + sub-section "Cấp duyệt nhỏ trong phòng" drag-list + button "+ Thêm cấp duyệt" emerald + payload include Order asc. Empty state hint fallback 2-cấp legacy. KHÔNG đụng fe-user (admin-only). **Backward compat 100%**: workflow Contract no InnerSteps configured → service fallback legacy 2-stage Mig 16. Data legacy InnerStepId=null vẫn enforce unique cũ qua filtered index. **Defer Session 14+:** - [ ] **Budget N-stage** — cần migration `AddBudgetVersionedWorkflow` trước (4 bảng + ALTER Budget.WorkflowDefinitionId), sau đó migration `AddBudgetWorkflowInnerSteps`. User quyết riêng (feature mở rộng module lớn). - [ ] schema-diagram.md §17 Mig 20 update — defer cron audit 2026-06-01. - [ ] Skill ef-core-migration row Mig 20 — defer cron audit. ### ✅ Session 12 done (2026-05-07) — N-stage workflow approval (Mig 18+19, 6 commit per-chunk, PE-only) User yêu cầu: workflow level cha (= phase) cấu hình được level con (Phòng × Cấp NV/PP/TP) sequential, mỗi cấp = 1 inner step duyệt riêng, có bypass cùng dept. 6 spec defaults chốt + 6 chunk per-commit. - [x] **Chunk A (`13ab533`)** Domain + Migration 18 `AddPeWorkflowInnerStepsAndPositionLevel` — enum PositionLevel (NV/PP/TP), entity PurchaseEvaluationWorkflowStepInnerStep + nav, User.PositionLevel int? + PEDeptApproval.InnerStepId Guid?. EF config FK Cascade Step / Restrict Dept+InnerStep. 3-file rule. - [x] **Chunk B (`0e56bd0`)** Application CQRS DTO — PeWorkflowStepInnerStepDto + extend PeWorkflowStepDto + CreatePeWorkflowStepInnerStepInput (default null backward compat) + Validator child rules + Handler atomic batch insert + UserDto +PositionLevel + SetUserPositionLevelCommand mirror SetBypassReview pattern. - [x] **Chunk C (`0c62e24`)** Service N-stage logic + **Migration 19** `AlterPeDeptApprovalsUniqueFilteredForInnerSteps` (filtered unique legacy `WHERE InnerStepId IS NULL` + new N-stage `WHERE InnerStepId IS NOT NULL`). PurchaseEvaluationWorkflowService refactor — load InnerSteps eager, reject clear N-stage rows tại fromPhase, dept block split hasInnerSteps→N-stage / else→legacy 2-stage. N-stage: match firstPending (Order asc IsRequired) same dept + (exact level OR canBypass + level≥), exact upsert 1 row InnerStepId, bypass batch upsert NV+PP+TP cùng dept ≤ actor (audit IsBypassed cho cấp dưới), recheck stillPending → BLOCK + log "duyệt cấp X (còn Y pending)". - [x] **Chunk D (`3d76c6b`)** Tests N-stage 6 test mới (FirstInner_NV_blocks / All_3_levels_sequential_pass / TP_bypass_skips_lower / Wrong_dept_403 / Reject_clears_rows / Legacy_fallback_no_inner) + IdentityFixture extend `+positionLevel` + helper SeedWorkflowDefinitionAsync 2 step adjacent. **83→89 test pass**. - [x] **Chunk E (`83ffabd`)** API `PATCH /users/{id}/position-level` mirror SetBypassReview + body `{positionLevel:int?}` + Authorize Users.Update. - [x] **Chunk F (current)** FE-Admin types/users.ts + positionLevel field + PositionLevel const + Label/Short maps. PeWorkflowsPage Designer extend InnerStep DTO + EditInnerStep type + sub-section "Cấp duyệt nhỏ trong phòng" drag-list { Phòng × Cấp + required } + button "+ Thêm cấp duyệt" emerald + departmentsList query + payload include. UsersPage column "Cấp" badge NV/PP/TP emerald + action button cycle null→1→2→3→null. KHÔNG đụng fe-user (admin-only). **Backward compat 100%:** workflow no InnerSteps configured → service fallback legacy 2-stage Mig 16. Data legacy rows InnerStepId=null vẫn enforce unique cũ qua filtered index. **Defer Session 13+:** - [ ] Mirror Contract + Budget N-stage (sau khi UAT PE 2-3 tuần ổn). Pattern lặp lại Domain entity + Service logic + Tests reusable từ PE. - [ ] Seed/migrate User.PositionLevel cho 30 demo user (hiện chỉ admin set qua UsersPage cycle). - [ ] schema-diagram.md §15 Mig 18 + §16 Mig 19 update (defer cron audit 2026-06-01 — small drift). - [ ] Skill ef-core-migration row Mig 18+19 (defer cron audit). - [ ] Skill contract-workflow N-stage cross-ref section (defer cron audit). ### ✅ Session phase 2 done (2026-05-08 00:30) — B12-B14 PE detail polish iterate (3 commit FE-only) User UAT iteration tiếp sau wrap-up `6e7a6db`. Áp rule strict verify khi rename/remove (lesson hotfix CI). - [x] **B12 (`378c993`)** — "Lưu" no-close + "Xóa phiếu" red bottom (CHỈ Bản nháp soft-delete) + bỏ header bar workspace "Sửa header"/"Xóa"/"Đóng" + Section 4 column header `s.supplierName` + Section 3 chặn xóa NCC khi có quotes - [x] **B13 (`e320027`)** — InfoTab `useEffect` re-trigger edit khi pencil click phiếu khác + sync values + Pencil "sáng lên" active state (`bg-brand-100 + ring`) khi `editingRowId === p.id` + wire `editingRowId` từ Workspace → PeListPanel - [x] **B14 (`d2306b8`)** — QuoteDialog bỏ checkbox `isSelected` (consolidate winner ở Section 2.a) + winner column Section 4 LUÔN highlight emerald (header `✓ ` prefix + cells full column) + QuoteDialog full overlay loading + spinner + NccSelectorRow inline spinner "Đang chọn NCC + sync cột giá Section 4…" **Verify:** `npm run build` × 2 app pass · `dotnet test` 83 pass · push OK. ### ✅ Session S10-11+++++++ done (2026-05-07) — PE Workspace UX overhaul đầy đủ (23 commit) User UAT live mode iterate liên tục. Áp rule `feedback_uat_skip_verify` (memory): skip dotnet test + npm build sau mỗi chunk, push ngay. Lesson hotfix CI `0ae3fe2`: rename/remove → BẮT BUỘC `npm run build` 1 lần trước commit. **11 batch deliverable** (chi tiết narrative đầy đủ xem `STATUS.md` Recently Done row đầu tiên + session log `2026-05-07-2359-pe-workspace-ux-overhaul.md`): - [x] **B1 (S10)** PE Thao tác 2-panel workspace mirror HĐ Thầu phụ pattern (4 commit `ee0d360`→`d04bd88`) - [x] **B2 (S11)** Migration 17 `AddManualBudgetFieldsToPeAndContract` — 4 cột manual budget cho PE + HĐ + App CQRS + FE (5 commit `ecd5f7e`→`bf17740`) - [x] **B3 (S11+)** BudgetFieldRow inline editor Section 2.b — toggle + Select OR 2 input + auto-detect mode (3 commit `19712d8`→`7f38c02`) - [x] **B4 (S11++)** InfoTab inline edit Section 1 + PeListPanel pencil hover + URL `?editHeader=1` (3 commit `5a89dd2`→`cb0598d`) - [x] **B5 (S11+++)** Workspace "new" sectioned create view 5 sections + LockedHint S3-5 (1 commit `66fa469`) - [x] **B6 (S11++++)** Pe_*_List Danh sách disable toàn bộ tương tác (2 commit `7dfeb1a`+`a1665ee`) - [x] **B7 (S11+++++)** Workspace "new" lock Loại quy trình theo URL + Select preset Điều khoản TT (1 commit `18ebfa1`) - [x] **B8 (S11++++++)** PE Display status meta — Bản nháp / Đã gửi duyệt / Đã duyệt / Từ chối (1 commit `0c5db13`) - [x] **B9 (S11+++++++)** Phase TraLai = 98 + pencil always visible + edit gating + editableOnly filter (1 commit `d15398f`) - [x] **B10 (hotfix CI)** TS errors `forcedPhase` rename + unused `PurchaseEvaluationType` import (1 commit `0ae3fe2`) - [x] **B11 (last)** PE detail polish — NCC selector dropdown + Section 3 winner protect + Bottom action bar Lưu/Gửi Duyệt (1 commit `4c0625c`) **Defer cho Session 12+** (cần explicit UAT trigger): - [ ] Workflow transition vào TraLai — BE workflow service chưa wire button "Trả lại" cho approver. FE đã ready accept. - [ ] BE multi-phase filter param `?phases=2,3,4,5,6` — cho FE display status "Đã gửi duyệt" precision filter. - [ ] Section 5 Opinion sign trong Duyệt mode — verify nếu UAT user cần sign opinions ở leaf "Duyệt" (hiện code path opinionsReadOnly = readOnly khi mode='detail'). ### ✅ Session 11 done (2026-05-07) — Migration 17 manual budget fields PE + HĐ (Note: Session 11 + 11+ + 11++ + ... đã merge vào batch S10-11+++++++ ở trên — đây là sub-row chronological, KHÔNG cắt narrative cũ.) ### ✅ Session 9 done (2026-05-04) — Chunk E-bis complete (FE 2-stage + HĐ/Budget mirror + 6 test) User chỉ thị "làm hết cho xong tính năng luôn" sau Session 8 close bug fix anh Kiệt phía BE PE. Session 9 đóng toàn bộ pending Chunk E-bis (defer từ session 8). - [x] **Chunk E2 — FE Workflow Panel PE** hiển thị progress 2-stage timeline per phase × dept (commit `f8eebd5`). Component `DeptApprovalsSection` group by phase × dept, highlight amber khi current phase có Review nhưng chưa Confirm, badge fuchsia "bypass" khi NV.CanBypassReview. Cả fe-admin + fe-user (rule §3.9). - [x] **Chunk E3 — FE UserManager toggle CanBypassReview** (commit `4380bdc`). UserDto BE thêm field `CanBypassReview`. UsersPage column "Bypass" + button ShieldCheck (icon highlight fuchsia khi enabled). Endpoint backend đã có sẵn từ Session 8 Chunk E1. fe-user KHÔNG có UsersPage (admin-only). - [x] **Chunk E4 — HĐ 2-stage logic mở rộng** (commit `b6f5a16`). ContractWorkflowService thêm `UserManager` DI + mirror toàn bộ logic 2-stage từ PE service (sau policy guard, trước gen mã HĐ). ContractDepartmentApprovalFeatures.cs (List query mirror PE pattern). Endpoint `GET /contracts/{id}/department-approvals`. FE WorkflowHistoryPanel section "Tiến trình duyệt 2-cấp phòng ban" insert giữa WorkflowSummaryCard và Lịch sử duyệt. - [x] **Chunk E5 — Budget 2-stage logic mở rộng** (commit `1fc439b`). TransitionBudgetCommandHandler thêm `INotificationService` + `IDateTime` DI + mirror 2-stage logic. BudgetDepartmentApprovalFeatures.cs + endpoint. FE BudgetWorkflowPanel section "Tiến trình duyệt 2-cấp phòng ban". Note: low-priority cho Budget (ít user duyệt budget per dept) nhưng giữ consistent UX 3 module. - [x] **Chunk E6 — 6 test 2-stage + IdentityFixture helper** (commit `8353fe8`). IdentityFixture (Common/) setup ServiceProvider với Identity stack đầy đủ — DbContext SQLite shared connection + AddIdentityCore + AddRoles + AddEntityFrameworkStores. Single shared scope cho fixture lifetime đảm bảo DbContext + UserManager đồng instance. Helper `CreateUserAsync(email, name, deptId, roles, canBypassReview)` reusable. 6 test PeTwoStageApprovalTests: NV_Review_Blocks_Phase_Transition (đóng bug anh Kiệt — test chính xác) / TPB_Confirm_After_NV_Review_Allows_Transition / NV_With_BypassReview_Allows_Transition_With_IsBypassed_True / Admin_Skips_TwoStage_Logic_Entirely / Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThao / Resume_After_Reject_Jumps_Back_To_RejectedPhase. Tests Contract + Budget 2-stage skipped — logic identical PE, ROI thấp; pattern reusable nếu UAT phát hiện regression riêng. **Total 77→83 test pass**. Stats sau session 9: - BE LOC: ~13750 → ~14400 (+650, gồm Contract + Budget 2-stage logic + 2 List feature + Test fixture) - API endpoints: ~131 → ~133 (+2 List dept-approvals HĐ/Budget) - Tests: 77 → **83** (+6 PE 2-stage) - Schema không đổi (Migration 16 đã có sẵn từ session 8) - 5 commit per-chunk pushed → 1 CI run trigger qua HEAD = `8353fe8` Pending còn lại Phase 9: chỉ Hard blockers Ops (UAT thật / SMTP / Rotate creds / SQL backup). Feature 2-stage đầy đủ. ### ✅ Session 8 done (2026-05-04) — Migration 16: 2-stage dept approval + smart reject + lock edit **Bối cảnh:** Anh Kiệt (FDC) báo bug PE workflow: NV.PRO tạo phiếu → duyệt được hết phase. Phân quyền sai vì policy chỉ check role, không check Stage 2-cấp. **3 ràng buộc gộp 1 migration:** - [x] **Lock edit khi Phase != DangSoanThao** — 17 handler thêm guard (Contract Detail × 15 qua helper `EnsureContractType`, PE Detail × 5 qua helper mới `PurchaseEvaluationDraftGuard`, Budget Detail × 3 inline). KHÔNG lock Comment + Attachment + Opinion (workflow design intent). - [x] **Smart reject + Resume** — `Decision=Reject` → `entity.RejectedFromPhase = currentPhase` + force `targetPhase=DangSoanThao`. Resume: `Drafter trình từ DangSoanThao + RejectedFromPhase != null → jump tới phase đã reject + clear field`. Bypass policy guard ở resume. - [x] **2-stage dept approval (PE only v1)** — User.DepartmentId != null + role guard: - DeptManager (TPB) → Stage=Confirm trực tiếp - User.CanBypassReview=true → Stage=Confirm + IsBypassed=true - Else (NV) → Stage=Review only, BLOCK transition cho đến khi TPB confirm - Schema: 3 bảng `*DepartmentApprovals` UNIQUE (TargetId, Phase, Dept, Stage) - [x] **Migration 16** `AddTwoStageDeptApprovalAndSmartReject` — 4 ALTER + 3 CREATE TABLE + 12 indexes + FK Cascade - [x] **Endpoint mới**: `GET /api/purchase-evaluations/{id}/department-approvals` (List), `PATCH /api/users/{id}/bypass-review` (toggle) - [x] **Notify TPB cùng dept** khi NV review (best effort, fail non-critical) - [x] **Verify**: Build pass + 77 test pass + Migration applied LocalDB OK + schema verified qua sqlcmd - [x] 5 commit per-chunk: `5fe61cc` (A) · `14f3c9f` (B) · `9747f8c` (C) · `a532ba6` (D) · current (E1) Session log: `2026-05-04-1230-chot-session-8-2-stage-dept-approval.md`. **Pending Chunk E-bis** ✅ TẤT CẢ DONE ở Session 9 (xem section trên): - [x] FE Workflow Panel hiển thị progress 2-stage timeline (E2 — PE) + HĐ (E4) + Budget (E5) - [x] FE UserManager toggle `CanBypassReview` checkbox (E3) - [x] HĐ 2-stage mở rộng (E4) - [x] Budget 2-stage mở rộng (E5) - [x] Tests 2-stage logic Service-layer (E6 — 6 test PE + IdentityFixture) ### ✅ Session 6 done (2026-04-30 — pure docs work) - [x] **MD audit + compact** — STATUS -27%, HANDOFF -32%, migration-todos -35%, archive 51 row Phase 0-7 cũ - [x] **3 skill refresh** — `form-engine` Phase 2 MVP → Tier 3 feature-complete, `permission-matrix` 12 menu → ~60 + inheritance roots, `ef-core-migration` 24 DbSet → 52 bảng - [x] **Rule mới rules.md §7** — Khi nào viết test (timing rule 5-row table) - [x] **Rule mới rules.md §6.4** — Audit + compact MD định kỳ (cadence + checklist + anti-pattern) - [x] **rules.md §9.4** — Mở rộng skill audit cross-ref §6.4 ### ✅ Session 9+ housekeeping done (2026-05-04 — sau Session 9 close) - [x] **Audit định kỳ 2026-05** — combined skill + doc drift (commit `7dc0233`). Log `skill-audit-2026-05.md`. - [x] **Optional polish — fe-user Inbox PE section** (commit `332a90f`). HĐ + PE 2 section trong InboxPage. - [x] **User Manual 7 file rewrite compact** (commit `16c2c9c`). End-user style: bỏ field/error tables, giữ numbered steps đơn giản. ~86 KB total. ### ✅ Session 11 done (2026-05-07) — Migration 17 manual budget fields PE + HĐ User feedback: PE/HĐ link Budget Select chỉ Phase=DaDuyet → user phải break flow tạo Budget approved trước. Solution: toggle "Nhập tay" + 2 input field fallback (Tên text + Số tiền number) lưu trên entity, KHÔNG cần Budget entity. Mirror logic PE ↔ HĐ (Q3 chốt). - [x] **Chunk 1 Domain+Infra** (commit `ecd5f7e`) — Migration 17 `AddManualBudgetFieldsToPeAndContract` 4 ALTER + 2 entity property + 2 EF config (HasMaxLength + HasPrecision). Applied LocalDB. 3-file rule. - [x] **Chunk 2 App CQRS** (commit `0f7901c`) — Create/Update PE + Contract commands + Validator (>=0 when has value) + Handlers + DTO + diff log audit + CreateContractFromEvaluation carry forward. - [x] **Chunk 3 FE-Admin** (commit `bab5031`) — types +2 field, PeHeaderForm toggle + 2 input + payload conditional, PeDetailTabs Section "b. Ngân sách" fallback display + badge "nhập tay", refactor PurchaseEvaluationCreatePage wrap PeHeaderForm DRY (222→30 LOC), ContractCreatePage NewForm + EditForm cùng pattern + read-only branch khi !isDraft. - [x] **Chunk 4 FE-User mirror** (commit `14f8d9d`) — 6 file y hệt content (rule §3.9). - [x] **Chunk 5 Docs** (commit current) — STATUS row + HANDOFF TL;DR Session 11 + session log `2026-05-07-2300-pe-hd-manual-budget-mig17.md`. - [x] **Verify**: dotnet build + 83 test pass mỗi chunk · npm build fe-admin + fe-user pass · LocalDB migration applied. - [x] **KHÔNG đụng** Budget entity / Phase=DaDuyet validation (giữ invariant). Manual fields chỉ là fallback display/note, KHÔNG join với Budget.Details cho per-row comparison ở PE matrix Section 4. ### ✅ Session 10 done (2026-05-07) — PE "Thao tác" 2-panel workspace User chỉ thị restructure menu PE: leaf "Thao tác" (Pe_*_Create) từ page Create header riêng `/new` sang workspace 2-panel mirror pattern HĐ Thầu phụ ContractCreatePage. Spec chốt 5 câu trước code (xem session log đầy đủ rationale). - [x] **Chunk 1 fe-admin** (commit `ee0d360`) — `PeListPanel.tsx` (~180 LOC pure picker reuse + sticky "+ Thêm mới") + `PeHeaderForm.tsx` (~210 LOC extract) + `PurchaseEvaluationWorkspacePage.tsx` (~120 LOC 2-panel). PeDetailTabs thêm prop `mode?: 'detail' \| 'workspace'` + Section 5 hint amber + force opinionsReadOnly. Layout resolver remap. App.tsx route mới. - [x] **Chunk 2 fe-user mirror** (commit `ecf3c59`) — 6 file y hệt content (rule §3.9 duplicate có chủ đích). - [x] **Chunk 3 docs** (commit `7e3cfa5`) — STATUS Recently Done + HANDOFF TL;DR Session 10 + session log `2026-05-07-2100-pe-workspace-2panel.md`. KHÔNG update skill (per §9.5 — không drift đáng audit, FE pure refactor). - [x] **Verify**: 2 build (fe-admin + fe-user) pass + dotnet test 83 pass mỗi chunk. Route `/new` cũ giữ tồn tại cho deep-link "Sửa header" button. - [x] **KHÔNG đụng** BE / migration / schema / endpoint / test count. ### A. Hard blockers (chờ user / ops) - [ ] UAT thật 1 tuần với 2-3 user (30 demo: 16 sample + 14 Solutions thật) - [ ] SMTP config → Email outbox (BLOCKED chờ user cấp host/user/pass) - [ ] Rotate credentials (admin + 30 demo + SA + vrapp + JWT secret + Gitea runner token) - [ ] Schedule SQL backup daily Task Scheduler ### B. PE feature gap còn lại - [ ] Export phiếu PDF/Excel — `IDocumentConverter` + template `PE-TrinhDuyet.docx` (user pending — không quan trọng lắm) ### C. Optional polish (làm khi UAT phát sinh) - [ ] Budget MaNganSach atomic sequence + migration `AddBudgetCodeSequences` - [ ] Budget versioned workflow + migration `AddBudgetVersionedWorkflow` - [ ] Payment terms PE tách field (JSON → 6 column) - [ ] Auto-map PE Details → Contract Details khi gen HĐ - [ ] Matrix Quotes bulk paste từ Excel - [x] **fe-user Inbox thêm section "Phiếu Duyệt NCC chờ tôi"** — done 2026-05-04 (commit `332a90f`). useQuery thứ 2 cho `/pe/inbox` + Panel 1 chia 2 section sticky header. Click PE → navigate `/purchase-evaluations/:id` (page riêng). ### D. Tests Phase 3-5 (làm khi gặp bug recurring để justify ROI) - [ ] **Phase 3** — Application handler tests (CQRS + EF InMemory) ~15 test - [ ] **Phase 4** — API smoke tests (WebApplicationFactory) ~7 test - [ ] **Phase 5** — FE Vitest cho lib utility (queryMatches, fmtMoney) ~10 test ### E. Ops chưa xong - [ ] Remove binding cũ `.huypham.vn` sau verify stable - [ ] win-acme scheduled task fix unhealthy (cert expire 2026-06-18) ## 🔁 Skill governance (recurring) Quy tắc: `docs/rules.md §9`. Audit định kỳ mỗi đầu tháng — workflow §9.4. - [x] **Setup ban đầu** — 6 skill (3 domain + 3 ops), rules §9 ← `661f859` - [ ] **Audit 2026-05-01** — log `docs/changelog/skill-audit-2026-05.md` - [ ] **Audit 2026-06-01** - [ ] **Audit 2026-07-01** Cron task `solution-erp-skill-audit-monthly` fire 9:00 AM ngày 1 mỗi tháng. ## 📦 Post-launch (Phase 10+ — future) - [ ] **Email outbox** (MailKit + SMTP) — blocked chờ SMTP config - [ ] E-signature integration (VNPT CA hoặc FPT CA) - [ ] Tích hợp Bravo / SAP ERP import NCC - [ ] Mobile app (React Native?) cho BOD duyệt ngoài giờ - [ ] AI: gợi ý điền form dựa HĐ cũ, OCR scan HĐ đối tác - [ ] Multi-tenant nếu có công ty thứ 2