# 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 20 WRAP done (2026-05-11 chốt 22:00) — 14 commit `9dee00d` → `ae1814c`
12 turn 1 ngày — PE UI restructure 3 chủ đề + Mig 27 menu eOffice + Multi-agent setup.
**Session log:** 3 file
- `2026-05-11-1100-pe-ui-restructure-s20.md` (turns 1-5: PE Detail nested grid + Section gộp)
- `2026-05-11-1700-menu-visibility-mig27.md` (turn 7: admin menu eOffice Mig 27)
- `2026-05-11-2200-pe-polish-responsive-multiagent.md` (turns 6 + 8-12: polish + responsive + multi-agent)
**Stats final S20:**
| Metric | Δ | Final |
|---|---|---|
| Migrations | +1 (Mig 27) | 27 |
| Endpoints | +1 (PATCH /menus/{key}) | ~142 |
| FE pages | +1 (MenuVisibilityPage) | 34 |
| Menu keys | +1 (MenuVisibility) | ~61 |
| Memory entries | +2 (responsive + multi-agent) | 16 |
| Sub-agents | +3 NEW (Inv + Imp + Rev seeds) | 3 |
| Commits | 14 | (`9dee00d` → `ae1814c`) |
| DB tables | 0 | 59 |
| Tests | 0 (Phase 9 UAT defer) | 81 PASS |
| Gotchas | 0 | 44 |
| Skills | 0 (reuse trong agents) | 6 |
**Memory entries mới (2):**
- `feedback_responsive_laptop_breakpoint.md` (t11) — 4-tầng responsive pattern
- `feedback_multi_agent_setup.md` (t12) — decision gate + ACCEPT/REFUSE + Windows MAX_PATH
**Defer Session 21+ NEW:**
- [ ] **Trial Week 1 — Contract V2 wire Mig 28+29** (Plan cha B HIGH) — kick off multi-agent với Investigator pre-flight + Implementer Chunk A-E + Reviewer pre-commit. Audit-reuse pattern PE V2 (memory `feedback_audit_reuse_before_clone`).
- [ ] **Test regression Mig 27 PATCH /menus/{key}** (Plan cha C MED) — validate Key required + DisplayLabel trim
- [ ] **Skill `permission-matrix`** cross-ref section "menu visibility" — defer cron audit 2026-06-01
### ✅ Session 20 turn 7 done (2026-05-11) — Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27, 5 chunk `2ea2d27` → `ef394f8` → `059bfcb` → `1ed6530` → Chunk E Docs)
User UAT yêu cầu admin quản lý menu eOffice (fe-user) — Ẩn/Hiện + Đổi tên. Confirm "chưa có" → tạo mới. User Q2=b clarify quan trọng: DisplayLabel CHỈ áp fe-user, admin sidebar giữ Label gốc.
- [x] **Chunk A (`2ea2d27`) Schema + Mig 27** — Domain MenuItem +IsVisible bool=true +DisplayLabel string?(200). EF config HasDefaultValue + HasMaxLength. Migration 27 `AddVisibilityAndDisplayLabelToMenuItems` (2 AddColumn) — 3-file rule. Apply LocalDB `_Dev` + `_Design` qua --connection override (memory `feedback_designtime_runtime_db`).
- [x] **Chunk B (`ef394f8`) BE API** — DTO MenuNodeDto + MenuItemDto +isVisible +displayLabel. GetMyMenuTreeQueryHandler pass through (KHÔNG filter server-side, 2 FE tự quyết). NEW UpdateMenuItemCommand + Validator + Handler (whitespace → null). MenusController +PATCH /api/menus/{key} [Authorize Policy="Permissions.Update"] body `{isVisible, displayLabel}`.
- [x] **Chunk C (`059bfcb`) FE Admin** — Domain MenuKeys +MenuVisibility + All[]. DbInitializer +leaf "Menu eOffice" Icon=Eye Order=94 (Workflows shift 94→95). Manual seed Mig 27 LocalDB Dev (INSERT MenuItems + Permissions Admin). FE Admin types/menu.ts mirror, menuKeys.ts +const, Layout resolver +/system/menu-visibility, App.tsx +Route. NEW pages/system/MenuVisibilityPage.tsx ~210 LOC: PageHeader + 4 StatCard + Search + Table 5 cột (Key mono + parentKey↳ / Tên gốc / Input "Tên hiển thị" inline / Toggle Eye-EyeOff emerald-amber / Save dirty + Khôi phục custom). onSuccess invalidate ['menus','all'] + ['my-menu'] live update sidebar.
- [x] **Chunk D (`1ed6530`) FE User** — fe-user types/menu.ts mirror. Layout.tsx filterForUser 2 tầng (USER_HIDDEN_KEYS hardcode structural + !isVisible dynamic). Helper effectiveLabel(n) = displayLabel?.trim() || label. Replace 3 callsite {node.label} → {effectiveLabel(node)}. USER_FIXED_TOP "__inbox" +isVisible:true cho type check. **fe-admin Layout KHÔNG đụng** — admin sidebar render Label gốc + show hết menu (user Q2=b).
- [x] **Chunk E Docs (current)** — STATUS Recently Done top + Last updated S20 turn 7. HANDOFF TL;DR Session 20 turn 7 trên đầu (giữ S20 prev nguyên §6.5). migration-todos done section (file này) + pending S21+. Session log `2026-05-11-1700-menu-visibility-mig27.md`. KHÔNG đụng rules / architecture / PROJECT-MAP / workflow-contract / forms-spec / database-guide / schema-diagram / CLAUDE.md (defer cron audit 2026-06-01).
**Stats Session 20 turn 7:** 26→**27 mig** (+1 AddVisibilityAndDisplayLabelToMenuItems), 59 DB tables (no change), ~141→**142 endpoints** (+1 PATCH /menus/{key}), 33→**34 FE pages** (+1 MenuVisibilityPage), ~60→**61 menu key** (+1 MenuVisibility), 81 test pass (Q4 UAT defer), 44 gotcha (no new). Memory entries 14 (no new).
**Defer Session 21+ (mới sau S20 turn 7):**
- [ ] Test PATCH /api/menus/{key} validate Key required + DisplayLabel trim
- [ ] Skill `permission-matrix` cross-ref section "menu visibility" — defer cron audit 2026-06-01
- [ ] UX verify trong UAT: admin ẩn menu cha → children có ẩn theo không? (FE filter check per-node `!n.isVisible`, parent vẫn hiện thì children render. Có thể cần propagate hidden tree-level nếu UAT phản hồi)
### ✅ Session 20 done (2026-05-11) — PE Detail UI restructure 3 yêu cầu UX user (4 chunk `9dee00d` → `2bba851` → `f2f01f4` → Chunk D Docs)
User UAT live feedback: "Logic khá OK rồi, điều chỉnh UI Duyệt NCC 1 tý". 3 yêu cầu cụ thể chốt qua Q&A 4 câu (Q1=a giữ Section "Chọn NCC TP" / Q2=a NCC shared + 1 hạng mục demo / Q3=a chỉ hiện NV đã ký / Q4 public luôn skip dotnet test). FE-only restructure (1 hook BE nhẹ auto-seed Detail).
- [x] **Chunk A (`9dee00d`) BE auto-seed + FE reorder section** — `CreatePurchaseEvaluationCommandHandler` thêm 1 PurchaseEvaluationDetail mặc định khi tạo phiếu: GroupCode="01", GroupName="Hạng mục chính", NoiDung=TenGoiThau, DonGiaNganSach=ThanhTienNganSach=Budget.TongNganSach (nếu link) hoặc BudgetManualAmount fallback hoặc 0; Changelog Insert audit. FE reorder PeDetailTabs section (mirror 2 app): 1.Thông tin / **2.Hạng mục lên #2** / 3.Chọn NCC / 4.NCC tham gia / 5.Ý kiến. Verify dotnet build pass.
- [x] **Chunk B (`2bba851`) Nested grid Hạng mục → NCC expand** — ItemsTab restructure thành list `HangMucCard` (1 card / 1 hạng mục, expanded=true mặc định cho 1 hạng mục demo). Header: GroupCode + NoiDung + 3 stat (KL/ĐG/TT) + NS link Δ + Pencil/Trash + ▼/▶ toggle. Expand body: NCC inline table 8 cột (NCC/Liên hệ/Điều khoản TT/**File báo giá**/ĐG chưa VAT/ĐG có VAT/Thành tiền/Action). Click cell quote → QuoteDialog reuse. + Thêm NCC / Sửa NCC reuse 2 dialog cũ. Winner ✓ button per row. Drop `SuppliersTab` function dead code ~134 LOC. Giữ AddSupplierDialog + EditSupplierDialog + SupplierAttachmentsCell (HangMucCard call lại). Section 4 NCC tham gia cũ bỏ → 4 section final (1.Thông tin / 2.Hạng mục nested / 3.Chọn NCC TP thắng thầu / 4.Ý kiến). Verify npm build × 2 app pass sau khi catch TS6133 SuppliersTab + SupplierAttachmentsCell unused.
- [x] **Chunk C (`f2f01f4`) Section Ý kiến gộp đồng cấp cùng Phòng** — FE-only KHÔNG đụng Mig 26 schema (vẫn UPSERT 1 row / Level qua Service). LevelOpinionsSectionV2 forEach step → 1 `StepOpinionsBox` (replace grid-cols-2 N approvers). Header: "Bước N — Tên" + dept badge emerald + "X/Y đã duyệt" counter. Body filter opinions theo step.order → sort levelOrder asc + signedAt asc → render `StepOpinionEntry` per signed (tên NV + Cấp badge slate + admin override badge amber nếu có + emerald rounded-full timestamp + comment whitespace-pre-wrap). NV chưa duyệt KHÔNG hiển thị (Q3=a). Drop `LevelOpinionBox` function. Mirror fe-admin + fe-user. Verify npm build × 2 app pass.
- [x] **Chunk D Docs (current)** — STATUS Recently Done top + header narrative · HANDOFF TL;DR Session 20 + pending S21+ + hard blockers ops carry · migration-todos Phase 9 Session 20 done section (file này) · Session log mới `2026-05-11-1100-pe-ui-restructure-s20.md`. KHÔNG đụng rules/architecture/PROJECT-MAP/workflow-contract/forms-spec/database-guide/schema-diagram/CLAUDE.md (defer cron audit 2026-06-01 — per §6.5 không cố sửa khi không cần; S20 không thêm migration / gotcha mới nên count drift không đổi từ S19).
**Stats final Session 20:** 26 mig (no new), 59 DB tables (no new), ~141 endpoints (no new — reuse + 1 BE hook trong existing CreatePE handler), 33 FE pages (no new), **81 test pass** (no change — Phase 9 UAT iteration skip test mỗi chunk Q4), 44 gotcha (no new). LOC delta net ~+25 FE (gross ~+700 / ~−725).
**Defer Session 21+:**
- [ ] **Test V2 Service wire** (Chunk B Service hook S19 + Section gộp Chunk C S20) — defer khi UAT user confirm + có sample data Production.
- [ ] **Test regression B4 silent 403 S18** (HIGH §7 priority — vi phạm rule test-before bug fix) — per-action `[Authorize(Policy=...)]` ApprovalWorkflowsV2Controller.
- [ ] **Test Mig 25 PATCH user-selectable** endpoint (MED — admin scope hẹp).
- [ ] **Contract V2 wire (Mig 27/28 mirror PE pattern)** — biggest pending Plan. Mig 27 Contract.ApprovalWorkflowId + CurrentApprovalLevelOrder; Mig 28 ContractLevelOpinions; Service ApproveV2Async; ContractCreatePage Workspace Select V2; pin V2 mặc định cho ContractType; ContractDetailContent Section "Ý kiến cấp duyệt" V2 dynamic mirror S20 Chunk C.
- [ ] **Phân quyền strict V2** — vẫn loose UAT. Sau confirm V2 flow → list/inbox/detail filter actor scope.
- [ ] **Drop legacy V1 cleanup** sau UAT chốt (drop tables WorkflowDefinitions/Steps/Approvers + drop column RejectedAtStepIndex/RejectedFromPhase deprecated S17 + drop ApproveV1LegacyAsync branch).
- [ ] **Drop Mig 15 cho V2 phiếu cleanup** sau UAT confirm (Mig 30 drop PurchaseEvaluationDepartmentOpinions, hoặc giữ cả 2 backward compat — Q3 user chốt giữ legacy).
- [ ] **schema-diagram §16 PE Level Opinions V2 + §17-21 Mig 18-21** — defer cron audit 2026-06-01.
- [ ] **Skill `ef-core-migration` frontmatter "21 migration" stale (thực 26) + `dependency-audit-erp` count gotcha 41 stale (thực 44)** — defer cron audit 2026-06-01.
### ✅ Session 19 done (2026-05-09) — PE Section 5 V2 dynamic theo ApprovalWorkflowLevel + Mig 26 (4 commit `873e7a1` → Chunk D Docs)
User UAT live tiếp Session 18. 1 polish nhỏ + 1 feature lớn (Section 5 dynamic). Spec chốt 5 câu Q&A trước code (Q1=1B sync auto / Q2=2A+Admin / Q3=V2 hết / Q4=4C+placeholder / Q5=5A grid-cols-2).
- [x] **Polish 3 button (`873e7a1`) Hành động Workflow Panel** — rút gọn label "✓ Duyệt / ← Trả lại / ✗ Từ chối" (bỏ "→ Chờ X" / "(về Drafter sửa)" / "Hủy /") + 3 màu phân biệt (emerald/amber/red) + font-medium → font-bold. Phase đích vẫn hiện qua tooltip title hover. Mirror fe-admin + fe-user.
- [x] **Chunk A (`77a3058`) Domain + Mig 26 + EF** — Entity `PurchaseEvaluationLevelOpinion : AuditableEntity` (PEId+LevelId UNIQUE composite, Comment nvarchar(2000), SignedAt datetime2, SignedByUserId Guid, SignedByFullName nvarchar(200) denorm). EF FK Cascade Pe + Restrict Level. **Migration 26** `AddPeLevelOpinionsForV2` (1 CREATE TABLE + 2 FK + 2 index — UNIQUE composite + IX LevelId). 3-file rule. Apply LocalDB SolutionErp_Dev OK (Mig 25 + 26 catchup).
- [x] **Chunk B (`90baa8e`) Service V2 hook + DTO + GET include** — Service `ApproveV2Async` sau line log approval → UPSERT row LevelOpinion cho Cấp hiện tại (match level theo ApproverUserId == actorUserId, fallback first khi Admin override). Reject KHÔNG sync. Comment empty → "(duyệt — không ý kiến)" placeholder. Helper `ResolveActorFullNameAsync` denorm SignedByFullName. DTO `PurchaseEvaluationLevelOpinionDto` 15 fields. GET handler Include LevelOpinions + helper `BuildLevelOpinionsAsync` JOIN Steps/Levels + Departments + Users → denorm DTO list. Empty cho V1 / V2 chưa có cấp duyệt.
- [x] **Chunk C (`6e913b3`) FE Section 5 V2 dynamic mirror 2 app** — Type `PeLevelOpinion` + `PeDetailBundle.levelOpinions[]`. Section 5 conditional: `evaluation.approvalWorkflowId` set → `` (dynamic), else `` (V1 legacy fallback Mig 15). `LevelOpinionsSectionV2`: forEach Step (header "Bước N — Phòng X" badge emerald + hint số người duyệt) → grid-cols-2 cho `step.levels.flatMap(level => level.approvers.map(approver => ))`. `LevelOpinionBox` read-only: title "Cấp N — " + badge amber "⚠ Admin duyệt thay" khi override + badge emerald "✓ Đã duyệt" + empty "— chưa duyệt" + footer signedAt. Mirror fe-admin + fe-user (rule §3.9).
- [x] **Chunk D Docs (current)** — STATUS Recently Done top + header narrative · HANDOFF TL;DR Session 19 + 7 cảnh báo Session 20+ (giữ S18 nguyên văn theo §6.5) · CLAUDE.md (root) count 25→26 mig + 58→59 tables + Mig 26 description block · migration-todos Phase 9 Session 19 done section + Defer Session 20+ checklist · Session log mới `2026-05-09-0400-pe-section-5-v2-dynamic-mig26.md`. KHÔNG đụng rules/architecture/PROJECT-MAP/workflow-contract/forms-spec/database-guide/schema-diagram (defer cron audit 2026-06-01) — per §6.5 không cố sửa khi không cần.
**Stats final Session 19:** 26 mig (+1), 59 DB tables (+1), ~141 endpoints (no new — UPSERT auto qua Service hook không endpoint riêng vì Q1=1B), 33 FE pages, **81 test pass** (no change — feature mới UAT defer test §7), 44 gotcha (no new). Memory entries 14 (no new).
**Defer Session 20+:**
- [ ] **Test V2 Service wire mới** (Chunk B Service hook) — defer khi UAT user confirm + có sample data Production. Domain test ApproveV2 + UPSERT opinion match logic + Admin override match firstLevel + comment empty placeholder.
- [ ] **Drop Mig 15 cho V2 phiếu (cleanup sau UAT confirm)** — sau khi không còn phiếu V2 dùng `PurchaseEvaluationDepartmentOpinions`. Mig 27 cleanup drop bảng + entity. Hoặc giữ cả 2 backward compat.
- [ ] **Migrate phiếu V1 cũ sang V2 (data migration)** — admin tool chuyển ApprovalWorkflowId. Hiện chưa làm (Q3 user nói chuyển V2 hết = phiếu MỚI dùng V2, V1 cũ giữ legacy).
- [ ] **Contract V2 wire (Mig 27/28)** — mirror PE pattern: Contract.ApprovalWorkflowId + ContractLevelOpinions Mig 28 + Service ApproveV2Async + ContractDetailContent Section 5 V2. Audit-reuse pattern.
- [ ] **Phân quyền strict V2** — vẫn loose UAT. Sau confirm V2 flow → list/inbox/detail filter actor scope.
- [ ] **schema-diagram §16 PE Level Opinions V2 + §17-21 Mig 18-21** — defer cron audit 2026-06-01.
- [ ] **Skill `ef-core-migration` frontmatter** "21 migration" stale (thực 26). Defer cron audit 2026-06-01.
- [ ] **Skill `dependency-audit-erp`** count stale. Defer cron audit 2026-06-01.
### ✅ Session 18 done (2026-05-08 19:45) — PE V2 polish + Clone B + Mig 25 IsUserSelectable + 4 bug fix UAT (7 commit `aaa1c6c` → `32a8d4d`)
User UAT live tiếp Session 17, chuỗi polish nhỏ + clone V2 cho type B. Áp memory `feedback_uat_skip_verify` (skip dotnet test mỗi chunk, push ngay) + lesson rename/remove → bắt buộc `npm run build`.
- [x] **B1 (`aaa1c6c`) Pe Duyệt filter cứng "Đã gửi duyệt"** — bỏ dropdown trạng thái + filter cứng client-side `getPeDisplayStatus === DaGuiDuyet`. Hint amber "Lọc cố định". Workaround BE /inbox loose UAT trả phiếu Nháp (phân quyền strict V2 pending).
- [x] **B2 (`917446d`) HistoryTab filter Trả lại / Gửi duyệt lại** — FE filter (BE keep audit data đầy đủ): chỉ events Workflow Transition về TraLai (phaseAtChange=98) + từ TraLai (summary "TraLai →") + sửa nội dung khi phaseAtChange=TraLai.
- [x] **B3 (`937eb24`) Clone V2 cho B (DuyetNccPhuongAn)** — Audit reuse trước thay vì duplicate. Schema chung qua ApplicableType discriminator → chỉ 3 file ~60 LOC: MenuKeys.cs +const + All array, DbInitializer.SeedMenusAsync +leaf B (Order=2) + new SeedSampleApprovalWorkflowsV2Async (idempotent skip nếu admin đã tạo workflow B), fe-admin/menuKeys.ts +const. Memory `feedback_audit_reuse_before_clone.md` capture pattern.
- [x] **B4 (`f77ea38`) Fix silent 403 ApprovalWorkflowsV2** — Drafter `nv.test` Workspace dropdown empty silent. Root: class-level `[Authorize(Policy = "Workflows.Read")]` → non-admin 403, TanStack Query catch silent. Fix: class-level `[Authorize]` only, GET cho any authenticated; POST/DELETE giữ `Workflows.Create` admin-only. Gotcha #44.
- [x] **B5 (`a9c0857`) Fix sidebar highlight queryMatches transient keys** — Click row → URL có id transient → exact-set mismatch → menu unhighlight. Fix: `TRANSIENT_QUERY_KEYS = {id, q, editHeader, page, phase, awId}` strip trước compare. Mirror fe-admin + fe-user Layout.tsx.
- [x] **B6 (`2a53107`) Mig 25 + Designer pin toggle + bỏ "(clone)" + Workspace filter** — Migration 25 `AddIsUserSelectableToApprovalWorkflows`: ALTER `IsUserSelectable bit` + Sql backfill `WHERE IsActive=1 SET 1`. Domain +property. DTO +field. CreateAwDefinitionCommand set default true. New SetAwUserSelectableCommand + Handler. API PATCH `/api/approval-workflows-v2/{id}/user-selectable`. DbInitializer SeedSample +`IsUserSelectable=true`. FE Designer +badge "Cho user chọn" + button Ghim/Bỏ ghim + mutation toggleSelectable. Designer name auto-fill bỏ "(clone)" suffix. FE Workspace fetch filter `w.isUserSelectable === true` (cả fe-admin + fe-user).
- [x] **B7 (`32a8d4d`) Cleanup orphan zip** — `.claude.zip + docs.zip` lỡ tay vào commit B6 (`git add -A`). Untrack + add `*.zip` rule .gitignore.
**Stats final Session 18:** 25 mig (+1), 58 DB tables (no new — Mig 25 chỉ ALTER cột), ~141 endpoints (+1 PATCH), 33 FE pages, **81 test pass** (no change — feature mới UAT defer test §7), 44 gotcha (+1 #44 silent 403). Memory +1 entry.
**Defer Session 19+:**
- [ ] **Contract V2 wire (Mig 26)** — mirror PE pattern: thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder` + `ContractWorkflowService.ApproveV2Async` + Workspace Select V2 trong ContractCreatePage. Pin V2 mặc định cho ContractType.
- [ ] **Phân quyền strict V2** — hiện loose UAT (mọi authenticated thấy mọi phiếu V2). List = Drafter + approver any-Step + Admin. Cũng giải quyết bug "/inbox loose trả phiếu Nháp" — sau khi BE filter strict, B1 FE filter có thể relax.
- [ ] **Drop legacy V1 (Mig 27 cleanup)** sau khi không còn phiếu pin `WorkflowDefinitionId` (V1): drop `WorkflowDefinitions` + `WorkflowSteps` + `WorkflowStepApprovers` + drop deprecated columns `RejectedAtStepIndex` / `RejectedFromPhase`. Drop `ApproveV1LegacyAsync` branch trong Service.
- [ ] **Test V2 Service wire** (defer khi UAT confirm + có sample data thật) — Domain test ApproveV2Async + match logic + TraLai entry → Cấp 1 reset.
- [ ] **Budget V2 wire** (defer xa hơn — sau Contract V2)
- [ ] **Sample seed B** — sau UAT có thể remove (admin đã tạo workflow thật), hoặc giữ làm fallback. Idempotent skip không clobber.
- [ ] **schema-diagram §17-21 Mig 18-21** vẫn chưa update (defer cron audit 2026-06-01)
- [ ] **Skill `ef-core-migration` frontmatter** "21 migration" stale (thực 25). Defer cron audit 2026-06-01.
- [ ] **Skill `dependency-audit-erp`** "26+/41 bẫy" stale (thực 44). Defer cron audit 2026-06-01.
### ✅ Session 17 done (2026-05-08) — PE Workflow V2 schema + Service wire end-to-end (Mig 22-24, 13 commit `c847dc0` → `de0f38d`)
User chốt sau Session 16 drastic refactor flat (Mig 21) vẫn chưa đúng intent. Yêu cầu schema riêng + Menu mới "Duyệt NCC (Mới)" — Quy trình > Bước (Phòng) > Cấp (NV cụ thể qua ApproverUserId). State machine 5 trạng thái với Trả lại = Phase RIÊNG (Option A user chốt diagram).
- [x] **Mig 22** `AddApprovalWorkflowsV2` — 3 entity ApprovalWorkflow/Step/Level + ApplicableType enum (DuyetNcc/DuyetNccPhuongAn/Contract). 3 CREATE TABLE + UNIQUE (Code, Version) + FK Cascade Step→Workflow + Level→Step + FK Restrict Department + ApproverUserId. DbInitializer +menu V2. Designer page `/system/approval-workflows-v2/:typeCode` ~480 LOC. (`c847dc0/f6047d5/2781c7e/12daa7f`)
- [x] **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 Order∈{1,2,3} + HaveSequentialOrders + HaveNoDuplicateApproverInSameLevel. (`9712778` iter 1 sai → `f3bea3c` iter 2 đúng)
- [x] **State machine 5 trạng thái** Nháp / Đã gửi duyệt / Trả lại / Từ chối / Đã duyệt. TraLai = Phase RIÊNG (98), KHÔNG revert DangSoanThao + KHÔNG jump-back step. Drafter từ TraLai sửa+gửi lại chạy LẠI từ Cấp 1 Bước 1. ContractPhase + BudgetPhase +TraLai. PE/Contract/Budget Policy + Service Reject branch trỏ → TraLai. RejectedAtStepIndex/RejectedFromPhase deprecated (giữ DB column). 4 test mới TraLai entry point. FE rename "Bản nháp" → "Nháp". (`ff21120`)
- [x] **Mig 23** `AddApprovalWorkflowIdToPurchaseEvaluation` — pin V2 vào PE entity. CreatePurchaseEvaluationCommand +Validate ApplicableType match PE.Type. UpdateDraft cho phép sửa Phase=Nháp/TraLai. Workspace Select bắt buộc workflow lúc create + display "QT-DN-V2-001 v01 — Tên (đang áp dụng)". (`0a40c65`)
- [x] **Mig 24** `AddCurrentApprovalLevelOrderToPe` + Service V2 wire. PE Service branch theo `ApprovalWorkflowId` set or null: V2 `ApproveV2Async` group Levels by Order = Cấp (OR-of-N approvers cùng cấp), match `actor.Id ∈ ApproverUserId`, advance levelOrder++ → idx++ + reset levelOrder=1 → DaDuyet. V1 `ApproveV1LegacyAsync` giữ logic cũ. Synthetic Policy `ForV2Schema()` cho FE nextPhases. (`b41484b`)
- [x] **UX V2-aware** disable button + banner. DTO `CurrentApproval` + `ApprovalFlow` (full Steps/Levels Status Done/Current/Pending). Banner emerald "Đến lượt bạn" / amber "Không phải lượt bạn — chỉ {NV X / Y} duyệt được". Button Duyệt forward disabled khi non-approver + tooltip. Trả lại + Từ chối vẫn enabled. Inbox V2-aware `ResolveV2InboxIdsAsync`. 2 dropdown filter "Quy trình" + "Trạng thái" (chỉ ở Duyệt). Panel 3 thay 4 phase cards bằng flow workflow thực tế. (`d814429/9e63e2d/d250ae4/74745a7/de0f38d`)
- [x] **Test setup**: SQL `clean-transactional-uat.sql` clean prod (9 PE + 11 HĐ + 19 Notif xóa) giữ master via SSH VPS. Test user `nv.test@solutions.com.vn`/`TestUser@123456` (Drafter, CCM) tạo qua API admin. (`ac41d5e`)
**Stats final Session 17:** 24 mig (+3), 58 DB tables (+3), ~140 endpoints (+5), 33 FE pages (+1 Designer V2), **81 test pass** (+4 TraLai entry point Domain).
**Defer Session 18+:**
- [ ] **Contract V2 wire** (Mig 25) — mirror PE pattern: thêm `Contract.ApprovalWorkflowId` + `CurrentApprovalLevelOrder` + `ContractWorkflowService.ApproveV2Async` + Workspace Select V2 trong ContractCreatePage
- [ ] **Phân quyền strict V2** — hiện loose UAT (mọi authenticated user thấy phiếu V2). List chỉ Drafter + approver any-Step + Admin
- [ ] **Drop legacy V1** sau khi không còn phiếu pin `WorkflowDefinitionId`: drop tables + cleanup migration drop `RejectedAtStepIndex`/`RejectedFromPhase` columns
- [ ] **Admin role bypass** decision prod — option C có audit log "[Admin override]" nếu cần (hiện UAT bypass không log riêng)
- [ ] **Test V2 Service wire** (defer khi UAT confirm + có sample data) — Domain test cho ApproveV2Async match logic
- [ ] **Budget V2 wire** (defer xa hơn — sau Contract V2)
### ✅ 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) — Plan C1 status S32 2026-05-26
- [ ] UAT thật 1 tuần với 2-3 user (30 demo: 16 sample + 14 Solutions thật) — anh main coordinate
- [ ] **SMTP config** → Email outbox — BLOCKED chờ anh main cấp host/user/pass + appsettings.Production.json update
- [ ] **Rotate credentials** (admin + 30 demo + SA + vrapp + JWT secret + Gitea runner token) — BLOCKED chờ anh main approve cycle plan
- [ ] **Schedule SQL backup daily Task Scheduler** — `scripts/backup-sql.ps1` READY. Anh main register Task Scheduler trên VPS qua: `ssh vietreport-vps "Register-ScheduledTask -TaskName SolutionErpBackupDaily -Trigger (New-ScheduledTaskTrigger -Daily -At 2am) -Action (New-ScheduledTaskAction -Execute powershell.exe -Argument '-File C:\solution-erp\scripts\backup-sql.ps1 -Server .\SQLEXPRESS')"`
### 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
### D-Bis. Plan B-Wrap — Contract V2 test bundle (defer dedicated session ~2h post-S32 chốt anh main)
> **Trigger:** S29 Plan B Contract V2 wire deploy prod 4 CI Runs PASS nhưng `ContractWorkflowService.ApproveV2Async` ~227 LOC NO test cover (UAT mode skip per `feedback_uat_skip_verify`). Risk gotcha #48 high (Service refactor > 100 LOC touching audit + changelog paths). Anh main chốt defer dedicated session post-Phase 9 stabilize.
**Test infra dependencies (NEW — chưa có precedent từ PE):**
- ✅ `TestApplicationDbContext` SQLite existing reuse
- ✅ `IdentityFixture` existing reuse
- ✅ `FixedDateTime` existing reuse
- ✅ `NoOpNotificationService` existing reuse
- 🆕 `NoOpChangelogService` mock OR use real `ChangelogService` với TestApplicationDbContext
- 🆕 `TestContractCodeGenerator` mock (return predetermined `HD-TEST-001`) OR real với ContractCodeSequences seeded
- 🆕 `SeedContractAsync` helper + `SeedContractWorkflowAsync` helper + `SeedSupplierAndProjectAsync` helper
**File new test class:** `tests/SolutionErp.Infrastructure.Tests/Services/ContractWorkflowServiceApproveV2Tests.cs` (mirror `PurchaseEvaluationWorkflowServiceReturnModeTests.cs` structure)
#### BW1 — ApproveV2Async happy path step advance (Cấp 1 → Cấp 2 cùng Bước)
- [ ] Setup: 1 Step + 2 Levels OR-of-N + 2 approver
- [ ] Approver Cấp 1 TransitionAsync(Approve) → expect `contract.CurrentApprovalLevelOrder = 2` + Phase giữ ChoDuyet + SlaDeadline reset 7 ngày + ContractApproval row added + ContractLevelOpinion UPSERT Comment + LogTransition `"Hoàn tất Cấp 1, sang Cấp 2 cùng Bước 1"`
#### BW2 — ApproveV2Async terminal (Cấp cuối Bước cuối → DaPhatHanh + gen mã HĐ)
- [ ] Setup: 1 Step + 1 Level + 1 approver + Supplier(`SUP-001`) + Project(`PRJ-001`)
- [ ] Approver TransitionAsync(Approve) → expect `contract.Phase = DaPhatHanh` + `contract.MaHopDong != null` (gen via `IContractCodeGenerator.GenerateAsync`) + `contract.CurrentWorkflowStepIndex = null` + `contract.CurrentApprovalLevelOrder = null` + ContractApproval row + ContractLevelOpinion final + LogTransition transition
#### BW3 — ApproveV2Async skipToFinal F2 (admin opt-in `AllowApproverSkipToFinal` per slot)
- [ ] Setup: 3 Step × 2 Levels each + approver Cấp 1 Bước 1 với `AllowApproverSkipToFinal = true`
- [ ] Approver Cấp 1 Bước 1 TransitionAsync(Approve, skipToFinal=true) → expect `CurrentWorkflowStepIndex = 2` (Bước cuối) + `CurrentApprovalLevelOrder = 2` (Cấp cuối) + Phase giữ ChoDuyet + LogTransition `"[Approver skip thẳng tới Bước 3 Cấp 2..."` + ContractApproval comment prefix `"[Duyệt vượt cấp tới Cấp cuối]"`
#### BW4 — ApproveV2Async ForbiddenException khi actor không trong pendingLevelGroup.ApproverUserId
- [ ] Setup: 1 Step + 2 Levels OR-of-N với approver A, B + outsider C
- [ ] Outsider C TransitionAsync(Approve) → expect `ForbiddenException` message `"Bước 1 ... Cấp 1: bạn không có trong danh sách NV duyệt..."`
#### BW5 — `CreateContractCommand` ApplicableType=Contract validation (Reviewer S29 MAJOR catch)
- [ ] Test class mới `tests/SolutionErp.Infrastructure.Tests/Application/CreateContractCommandApplicableTypeTests.cs`
- [ ] Setup: ApprovalWorkflow với `ApplicableType = DuyetNcc` (id=1, PE-only)
- [ ] CreateContractCommand pin `ApprovalWorkflowId` trỏ workflow ApplicableType=1 → expect `ValidationException` `"Workflow phải ApplicableType=Contract"` (security catch — cross-module misuse)
#### BW6 — Mig 32+33 schema persistence test (ContractLevelOpinion UPSERT + UNIQUE composite)
- [ ] Test class mới `tests/SolutionErp.Infrastructure.Tests/Common/ContractV2SchemaPersistenceTests.cs`
- [ ] Setup: 1 Contract + 1 ApprovalWorkflowLevel
- [ ] Add 2 ContractLevelOpinion cùng (ContractId, ApprovalWorkflowLevelId) → expect `DbUpdateException` (UNIQUE composite Mig 33)
- [ ] Update existing Opinion via UPSERT pattern → expect 1 row only (mirror ApproveV2Async line 292-316)
- [ ] FK Cascade test: delete Contract → ContractLevelOpinions auto-deleted (Restrict on ApprovalWorkflowLevel)
#### BW7 — ContractWorkflowService V1 fallback skipToFinal ConflictException
- [ ] Setup: Contract V1 legacy (`WorkflowDefinitionId` pin, `ApprovalWorkflowId = null`)
- [ ] Approver TransitionAsync(Approve, skipToFinal=true, isAdmin=false) → expect `ConflictException` `"skipToFinal chỉ hỗ trợ HĐ V2 (ApprovalWorkflowsV2). HĐ V1 legacy không có per-Approver-slot flag."`
**Bundle review:** 🟥 Reviewer pre-commit verify 7 test scenario + verify NO regression baseline 111 PASS. Post-commit 🟩 CICD Monitor verify Run PASS bundle baseline preserve.
**Owner allocation:** 👤 Chủ trì Solo helper methods + mock services (3 file) + 🟨 Implementer Case 2 cookie-cutter mirror PE pattern cho 7 test method skeleton (deterministic spec above) + 🟥 Reviewer pre-commit + 🟩 CICD verify.
### E. Ops chưa xong — Plan C1 status S32 2026-05-26
- [ ] **Remove binding cũ `.huypham.vn`** sau verify stable — manual cmd `ssh vietreport-vps ; cd C:\solution-erp\scripts ; .\migrate-domains.ps1 -RemoveOld -SkipCert`
- [x] **win-acme cert status** — cert **api.solutions.com.vn** notAfter=`2026-07-23` (renewed 2026-04-24). Not urgent — auto-renewal ~2026-06-23 (30d before expire). STATUS.md note `2026-06-18` STALE (Plan F win-acme schedule task fix may have resolved silently). Re-verify auto-renew trigger date qua `ssh vietreport-vps "Get-ScheduledTask -TaskName 'win-acme*' | Select State,LastRunTime,NextRunTime"`
## 🔁 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.
## 🚀 Phase 10 — Plan G: Port 11 module NamGroup → SOL (backlog defer sau Phase 9 stabilize)
> **Trigger:** S32 anh main yêu cầu plan kỹ 11 module NamGroup (NHÂN SỰ + VĂN PHÒNG SỐ + CÁ NHÂN — khung đỏ screenshot 2026-05-26). **4 quyết định chốt anh main:**
> 1. **Scope:** FULL 11 module (3 nhóm — không subset)
> 2. **DB:** Single schema `dbo` mở rộng Mig 34→43+ (no multi-schema, no separate DbContext)
> 3. **Reuse:** Workflow Engine V2 (Mig 22-26) extend `ApplicableType` enum +5 values cho Đề xuất/Đơn từ/OT/Đặt xe/Ticket CNTT
> 4. **Rollout:** Chunk per-module Plan riêng (10 Plan G-* atomic sprint ~3-5 ngày mỗi cái), aggressive multi-agent parallel ROI ~70%
>
> **Sequence chốt anh main S32:** Tuần tự theo kế hoạch tốt nhất — Phase 9 UAT stabilize trước → Phase 10.1 kick off. KHÔNG rush parallel với Phase 9.
> **Timeline target:** ~3 tháng (T6-T8/2026) realistic với multi-agent aggressive.
> **G-P1 Chấm công scope:** Pure web GPS check-in (KHÔNG máy vân tay/face recog hiện tại — anh chốt "tạm thời vậy trước").
> **G-H2 HrmConfig seed:** Reference NamGroup demo seed (LeaveTypes/Holidays/Shifts/OtPolicy) — Investigator audit NamGroup `DbInitializer` + `DataSeeder` lúc G-H2 kick off.
### 📐 Reference findings từ NamGroup audit S32
| NamGroup pattern | Decision SOL |
|---|---|
| `TblNhanVien*` 8+ bảng (QtCongTac/QtDaoTao/QuanHeThanNhan/KyNangViTinh/...) | Giữ structure deep — 1 main + N satellites (EmployeeProfile family) |
| `SoDoToChuc`/`SoDoKhoi`/`ChucDanh`/`ViTri` 4 bảng org chart | Tận dụng SOL `Department` + `User.Position` + `PositionLevel` — skip 3 bảng SoDo redundant |
| `Tbl*` Vietnamese table naming + Repository pattern .NET FW 4.0 | Reject — giữ SOL Clean Arch + Entity PascalCase English + IApplicationDbContext |
| WF approval ad-hoc per module | Reject — reuse `ApprovalWorkflow V2` extend enum +5 values |
| Announcement/InternalDocument/Menu flat tables | Adapt — reuse SOL `MenuItem` tree + `Notification` + Attachment infra |
### Phase 10.1 — Foundation HRM (UNBLOCK toàn bộ Phase 10 sau)
#### Plan G-H1 — Hồ sơ nhân sự ⭐ CRITICAL FIRST (depend by 8/11 module sau) — ✅ DONE S33
- [x] 🟦 Investigator pre-flight audit NamGroup `TblNhanVien*` 10 bảng (NOT 8) + map fields → SOL EmployeeProfile schema (S33 a103d20)
- [x] 👤 Chủ trì Solo design Mig 34 `AddEmployeeProfiles` schema (1 main + 5 satellite: WorkHistory/Education/FamilyRelation/Skill polymorphic Kind/Document — defer 3 HĐLĐ Plan H2) + 4 decision (5 satellite/Skill poly/DiaChi FK+freetext/NV/YYYY/NNNN code) (S33 em main)
- [x] 🟨 Implementer Case 2 cookie-cutter BE entity scaffold 7 entity (6 + EmployeeCodeSequence) + 7 EF Config + IEmployeeCodeGenerator + EmployeeCodeGenerator + IApplicationDbContext +7 DbSet + Mig 34 3-file rule (S33 a8f4567, 17 file mới + 4 modified)
- [x] 👤 Chủ trì Solo DbInitializer SeedDemoEmployeeProfilesAsync ~90 LOC mirror 33 user @solutions.com.vn + EmployeeCodeSequence row NV/2026 LastSeq=33, NOT gated DemoSeed flag per gotcha #51 (S33 em main commit 48a99e1)
- [x] 🟨 Implementer Case 2 BE CQRS 5 pair (Create/Update/Delete/Get/List) + EmployeesController 5 REST endpoint + Dtos 5 satellite (S33 a9bb9f3, 3 file mới)
- [x] 🟨 Implementer Case 2 FE 2 app types/employee.ts 10 enum + EmployeesListPage 2-panel + 6 section inline collapsible + EmployeeCreatePage Header form minimal — Phase 1 ULTRA-MINIMAL scope (S33 afdc812, 6 mới × 2 app SHA256 IDENTICAL + 6 modified Layout/menuKeys/App)
- [x] 👤 Chủ trì Solo Permission menu MenuKeys.cs +Hrm+HrmHoSo const + All[] update + DbInitializer SeedMenuTreeAsync +2 entry order=28 (S33 em main commit 0e191de)
- [x] 🟥 Reviewer pre-commit Smart Friend 6× clean (0 critical/major, 3 minor defer Phase 1.5) + 🟩 CICD Monitor Run #237 PASS 3m50s (6/6 endpoint 200, bundle rotate × 2 app, Hrm menu seeded, EmployeeProfile 33 preserved idempotent) (S33 ae752c0 + ae2a01f)
**Stats final S33 Plan G-H1:** **34 mig (+1)** · **67 tables (+7)** · **~153 endpoints (+5)** · **40 FE pages (+2)** · **62 menu keys (+2 Hrm+HrmHoSo)** · **120 test (+9 BW Plan C)** · 52 gotcha unchanged · 26 memory user-level · 7 commit S33 push remote `5400983..79a8343` 2 CI Run PASS · 8 pattern reinforced cumulative.
**Phase 1.5 backlog (defer S34+):** Per-action policy Hrm_HoSo_View/Create/Edit/Delete + bool partial update + Satellite CRUD endpoint + Test bundle Plan B Phase 2 + UAT non-admin smoke + fe-admin menuKeys sync Bg_*/Catalog*.
#### Plan G-H2 — Cấu hình chung HRM ✅ FULL DEPLOYED PROD (S34 schema + S35 BE CRUD + FE Admin)
- [x] 👤 Chủ trì Solo Mig 35 `AddHrmConfigs` — LeaveTypes + Holidays + ShiftPatterns + OtPolicy lookup tables (S34 commit `07b3f3b`)
- [x] 🟦 Investigator audit NamGroup demo seed → **MISS verdict** SOL clean-room mạnh hơn NamGroup, KHÔNG cần port (S35 `a42bd882c367f6e47`)
- [x] 🟨 Implementer Case 2 BE CQRS CRUD 4 catalog mega `HrmConfigFeatures.cs` 439 LOC + Controller 137 LOC = 16 endpoint (S35 commit `909655c` Run #243 PASS)
- [x] 🟨 Implementer Case 2 FE Admin `HrmConfigsPage.tsx` declarative `KIND_CONFIG` Record × 2 app SHA256 IDENTICAL + types/hrm-config.ts + Layout staticMap (S35 commit `021674a` Run #244 PASS)
- [x] 👤 Chủ trì Solo Permission menu `Hrm_Config*` 5 row (1 root + 4 leaf Order=2) seeded prod (S34 commit `07b3f3b`)
**Pattern Smart Friend Implementer 2 catch S35 G-H2:**
1. MaxLength validator vs EF config mismatch → EF source-of-truth aligned (Code 50, Name 200, Description 500)
2. HRM entities NO HasQueryFilter → explicit `.Where(!IsDeleted)` 8 site
**Pattern Smart Friend Implementer 1 catch S35 FE Admin:**
3. Em main spec gap Layout staticMap miss → Pattern 16-bis 4-place enforced (gotcha #50 silent sidebar prevention)
**NEW Pattern Declarative KIND_CONFIG Record** documented memory user-level `feedback_declarative_kind_config_pattern.md` — reusable cross-module single-page multi-kind CRUD.
### Phase 10.2 — Office Core standalone (KHÔNG cần workflow)
#### Plan G-O1 — Danh bạ nội bộ ✅ DONE S34 (commit `ea440da` Run #238 PASS)
- [x] 👤 Chủ trì Solo BE 1 endpoint `GET /api/directory` — filter dept/position/search name+email+phone (reuse Users + EmployeeProfiles + Departments)
- [x] 🟨 Implementer Case 2 cookie-cutter FE 2 app InternalDirectoryPage (card grid avatar + dept + extension + email + mobile click-to-call) — SHA256 IDENTICAL × 2 app
- [x] 👤 Chủ trì Solo Permission `Off_DanhBa` 1 leaf + parent `Off` (Order=29)
#### Plan G-O2 — Phòng họp (booking calendar standalone) ✅ DONE S36
- [x] 👤 Chủ trì Solo Mig 36 `AddMeetingRooms` — 3 entity (MeetingRoom catalog + MeetingBooking header + MeetingBookingAttendees N-to-N join — clean-room SOL, NOT JSON per Investigator verdict)
- [x] 🟨 Implementer Case 2 BE CQRS Room CRUD + Booking Create/Cancel + GetCalendar + check conflict (SERIALIZABLE transaction + EXISTS query overlap detect race-safe) — 584 LOC MeetingFeatures + 2 Controller + Application.csproj +Relational package (em main fix gotcha #53 4th occurrence Implementer truncated mid-diagnose)
- [x] 🟨 Implementer Case 2 FE 2 app — MeetingCalendarPage (custom HTML 7-day grid 8h-20h slot, NO FullCalendar dep save ~80 KB bundle per Investigator verdict alternative) + MeetingRoomsPage (admin catalog CRUD) — SHA256 IDENTICAL × 2 app 3 file pair
- [ ] 👤 Chủ trì Solo Notification push attendees when booking created (DEFER — NotificationPushInterceptor reuse khi UAT confirm need)
- [x] 👤 Chủ trì Solo Permission `Off_PhongHop_View/Manage/Book` 3 leaf + sub-group `Off_PhongHop` Order=2 under Off root + 4 sample MeetingRoom seed (PH-A/PH-B/PHG-501/ONL-1)
### Phase 10.3 — Workflow Apps (extend ApprovalWorkflow V2 enum +5 values)
> ⚠️ PHẢI extend `ApprovalWorkflowApplicableType` enum first: `+ProposalGeneral=4, +LeaveRequest=5, +OtRequest=6, +VehicleBooking=7, +ItTicket=8` (Mig 37 cookie-cutter Mig 22 pattern). Gotcha #51 INFRASTRUCTURE vs DEMO seed gate caution — `SeedSampleWorkflow*` OUT of `DemoSeed:Disabled` flag.
#### Plan G-O3 — Đề xuất (Proposal) ✅ DONE S37 (commit `de1c378` Run #246 PASS)
- [x] 👤 Chủ trì Solo Mig 37 extend `ApplicableType` enum +5 values (ProposalGeneral=4 + LeaveRequest=5 + OtRequest=6 + VehicleBooking=7 + ItTicket=8) cookie-cutter Mig 22 pattern (Up/Down empty — enum mức Domain)
- [x] 👤 Chủ trì Solo Mig 38 schema 4 entity (Proposal + ProposalAttachment + ProposalLevelOpinion UNIQUE composite mirror PE Mig 26 + ProposalCodeSequence atomic Prefix PK) + 4 EF Config + 2 DbContext mod
- [x] 👤 Chủ trì Solo BE ApproveV2Async inline + CodeGen `DX/YYYY/NNN` SERIALIZABLE tx — em main solo ~700 LOC ProposalFeatures.cs fallback after Implementer gotcha #53 5th truncate mid-exploration + 529 Overload spawn fail
- [x] 👤 Chủ trì Solo FE 2 app fallback (Implementer 529 spawn fail) — 4 file × 2 app SHA256 IDENTICAL (types/proposal.ts `95607052ff1138f2` + ProposalsListPage `603f0d9cf74cd09a` + ProposalCreatePage `6aed3a76563dd576` + ProposalDetailPage `3dc229ea8dcc9bc0`) + Pattern 16-bis 8× cumulative (App.tsx + menuKeys + Layout staticMap 3 entry)
- [x] 👤 Chủ trì Solo Permission `Off_DeXuat_*` 4 leaf + DbInitializer SeedSampleProposalWorkflowV2Async `QT-DX-V2-001` IsUserSelectable=true NOT gated DemoSeed gotcha #51
- [x] 🟩 CICD Monitor Run #246 PASS 3m53s — bundle rotate × 2 (admin `C9kzTTmq→CGueDk22` + user `CC4DQ-Tr→CEt0QRgX`) + Mig 37+38 prod TOP 2 + 4 menu Off_DeXuat seed + workflow QT-DX-V2-001 ApplicableType=4 verified + 0 regression
#### Plan G-O4 — Đơn từ (LeaveRequest + OtRequest + TravelRequest)
- [ ] 👤 Chủ trì Solo Mig 39 schema 3 entity + extend enum +LeaveRequest=5 +OtRequest=6
- [ ] 🟨 Implementer Case 2 ×3 spawn parallel BE 3 entity scaffold + CQRS handler mirror PE cookie-cutter
- [ ] 👤 Chủ trì Solo BE LeaveBalance calculation (Hrm_Config LeaveTypes + EmployeeProfile.HireDate seniority) — business logic tight
- [ ] 🟨 Implementer Case 2 ×3 FE 2 app 3 page mirror cookie-cutter
- [ ] 🟥 Reviewer pre-commit + 🟩 CICD verify
#### Plan G-O5 — Đặt xe công (VehicleBooking)
- [ ] 👤 Chủ trì Solo Mig 40 schema Vehicles + Drivers + VehicleBookings + extend enum +VehicleBooking=7
- [ ] 🟨 Implementer Case 2 mirror G-O2 MeetingRoom + workflow mirror G-O3 BE CRUD + workflow + check conflict + driver assignment
- [ ] 🟨 Implementer Case 2 cookie-cutter FE 2 app
- [ ] 🟥 Reviewer + 🟩 CICD
#### Plan G-O6 — Ticket CNTT (IT Helpdesk)
- [ ] 👤 Chủ trì Solo Mig 41 schema ItTickets + ItTicketComments thread
- [ ] 👤 Chủ trì Solo BE CQRS + Auto-assign round-robin per category + SLA timer warning (business logic tight)
- [ ] 🟨 Implementer Case 2 FE 2 app TicketsList + Detail (thread comment + status board kanban-ish)
- [ ] 👤 Chủ trì Solo Notification SLA expire warning (reuse SlaTimer pattern Contract)
- [ ] 🟥 Reviewer + 🟩 CICD
### Phase 10.4 — Dashboards + Attendance
#### Plan G-H3 — Dashboard Nhân sự (depend H1 data)
- [ ] 👤 Chủ trì Solo BE `GET /api/hr/dashboard` aggregate KPI (TongNV + HDActive + HDExpiring + GenderRatio + Birthday7d + StatusBreakdown)
- [ ] 🟨 Implementer Case 2 cookie-cutter mirror MyDashboard role-aware FE 2 app HrmDashboardPage (4 KPI card + 2 donut chart + birthday timeline)
- [ ] 👤 Chủ trì Solo Permission `Hrm_Dashboard` 1 leaf HR role only
#### Plan G-P1 — Chấm công (Pure web GPS check-in — no device integration per anh chốt S32)
- [ ] 👤 Chủ trì Solo Mig 42 `AddAttendances` — AttendanceLogs (UserId/Date/CheckInAt/CheckOutAt/Source enum Web/Mobile/GPS lat-long/IpAddress/Note)
- [ ] 👤 Chủ trì Solo BE Web GPS check-in endpoint + monthly report calc (OT từ Hrm_Config OtPolicy) — business logic tight
- [ ] 🟨 Implementer Case 2 FE 2 app User MyAttendance (calendar tháng + click-in button GPS prompt) + Admin AttendanceReport (filter dept + export Excel)
- [ ] 🟥 Reviewer + 🟩 CICD
### Stats target Phase 10 end (estimate)
| Metric | Phase 9 | Phase 10 end | Δ |
|---|---|---|---|
| Migrations | 33 | ~42 | +9 (Mig 34-42 batch nhỏ) |
| Tables | 60 | ~85 | +25 |
| Endpoints | ~148 | ~250 | +100 |
| FE pages | 38 | ~60 | +22 |
| AppRoles | 14 | ~18 | +4 (HrAdmin/OfficeAdmin/Driver/ItStaff) |
| Permission menu | ~60 | ~110 | +50 (Hrm_/Off_/Per_) |
| Tests | 111 | ~200 | +90 (Phase 10 test-after UAT + critical algo test-before: workflow guard + CodeGen Proposal + LeaveBalance calc + Attendance OT) |
### Risk + Gotcha cần catch trước Phase 10 kick off
1. **Mig 37 enum extend `ApplicableType`** — gotcha #51 INFRASTRUCTURE vs DEMO seed gate caution
2. **Cross-stack tight cho LeaveBalance + Attendance OT + Ticket Auto-assign** — Implementer REFUSE, em main solo bắt buộc
3. **MeetingRoom + Vehicle conflict check** — race condition 2 user book cùng time slot — SERIALIZABLE transaction mirror Contract gen mã
4. **EmployeeProfile depend by 8/11 module sau** — KHÔNG được rush G-H1, Reviewer adversarial 100% pre-commit
5. **30 demo profile cần seed thật cho 30 user existing** — DbInitializer extend, KHÔNG demo seed gate (infrastructure)
6. **Permission leaf 50 NEW** — admin Designer phải seed cẩn thận tránh broken inheritance tree (gotcha #35)
7. **FullCalendar lib new dep** cho G-O2 + G-O5 + G-P1 — skill `dependency-audit-erp` chạy trước commit
## 📦 Post-launch (Phase 11+ — future, defer Phase 10 done)
- [ ] **Email outbox** (MailKit + SMTP) — blocked chờ SMTP config (Phase 9 hard blocker)
- [ ] 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ờ — depend G-P1 mobile GPS check-in done
- [ ] 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
- [ ] G-P1+ — Chấm công device integration (vân tay/face recog/CSV import) khi anh có máy