Files
solution-erp/docs/changelog/migration-todos.md
pqhuy1987 edba4ae147 [CLAUDE] Docs+Memory: S33 wrap — Plan B G-H1 + Plan C B-Wrap deploy prod end-to-end
S33 cumulative wrap update STATUS + HANDOFF + migration-todos tick G-H1
7 task done + session log + CICD MEMORY post-Run #237 entry.

## Updates

- docs/STATUS.md: prepend S33 entry (Plan B + C + D drift patch + multi-agent
  ROI ~250K total + 8 patterns reinforced + new capability UAT-ready)
- docs/HANDOFF.md: prepend S33 wrap + S34 handover recommend sequence (curate
  4 MEMORY → G-O1 Danh bạ → G-H2 HrmConfig)
- docs/changelog/migration-todos.md: tick [x] G-H1 7 task + stats final S33
  + Phase 1.5 backlog
- docs/changelog/sessions/2026-05-26-2030-s33-plan-bc-deploy.md: NEW session
  log full (~250 line: outcomes + plan execution table + multi-agent ROI +
  8 patterns + defer Phase 1.5/S34+ + commits range + UAT capability)
- .claude/agent-memory/cicd-monitor/MEMORY.md: post-Run #237 entry append
  (truncated from S33 startup curate 24.2KB → 32.9KB, curate priority next
  session)

## Stats final S33

| Metric | Pre-S33 | Post-S33 | Δ |
|--------|---:|---:|---:|
| Migrations | 33 | 34 | +1 (Mig 34 AddEmployeeProfiles) |
| Tables | 60 | 67 | +7 (EmployeeProfile + 5 satellite + Sequence) |
| Endpoints | ~148 | ~153 | +5 (/api/employees REST) |
| FE pages | 38 | 40 | +2 (EmployeesListPage + EmployeeCreatePage × 2 app) |
| Menu keys | ~60 | ~62 | +2 (Hrm + HrmHoSo) |
| Tests | 111 | 120 | +9 (BW1-BW7 Plan C, BW6 split 3) |
| Gotchas | 52 | 52 | 0 new (2 existing lesson applied: #50 + #51) |
| Memory user-level | 26 | 26 | 0 new (decision: no rule warrant new entry, all patterns reinforce existing) |
| AppRoles | 14 | 14 | 0 new |
| EmployeeProfile prod | 0 | 33 | +33 (seeded via DbInitializer idempotent) |
| EmployeeCodeSequences | 0 | 1 | NV/2026 LastSeq=33 |

## Multi-agent ROI cumulative S33

🟦 Investigator 2 spawn ~20K + 🟨 Implementer 4 spawn ~110K (2/4 truncated
mid-MEMORY, functional complete) + 🟥 Reviewer 3 spawn ~60K (Smart Friend
6× clean) + 🟩 CICD 3 spawn ~60K (Run #350 + #237 PASS) + em main solo
~150K (Task 2 + Task 3b + Task 6 + 7 commit batch + push).

## CI Runs

- #350 Phase 1 (commit 48a99e1) — 3m38s PASS — Mig 34 prod + 33 seeded + 9 BW
- #237 Phase 2 (commit 79a8343) — 3m50s PASS — 5 NEW employee endpoint + Hrm
  menu seeded + bundle rotate × 2 app

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 22:25:19 +07:00

783 lines
79 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 → `<LevelOpinionsSectionV2/>` (dynamic), else `<DepartmentOpinionsSection readOnly/>` (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/>))`. `LevelOpinionBox` read-only: title "Cấp N — <ApproverFullName>" + badge amber "⚠ Admin <name> 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<User>` 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<User> + AddRoles<Role> + 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
- [ ] 👤 Chủ trì Solo Mig 35 `AddHrmConfigs` — LeaveTypes + Holidays + ShiftPatterns + OtPolicy lookup tables
- [ ] 🟦 Investigator audit NamGroup demo seed (LeaveTypes/Holidays/Shifts/OtPolicy) — reference cho seed sample data
- [ ] 🟨 Implementer Case 2 mirror SOL Catalogs pattern (Plan CA Chunk B) BE CQRS CRUD 4 catalog
- [ ] 🟨 Implementer Case 2 mirror admin Catalogs FE Admin 4 catalog page (list/create/edit dialog inline)
- [ ] 👤 Chủ trì Solo Permission menu `Hrm_Config*` 4 leaf + admin role assign
### Phase 10.2 — Office Core standalone (KHÔNG cần workflow)
#### Plan G-O1 — Danh bạ nội bộ
- [ ] 👤 Chủ trì Solo BE 1 endpoint `GET /api/directory` — filter dept/position/search name+email+phone (reuse Users + EmployeeProfiles + Departments)
- [ ] 🟨 Implementer Case 2 cookie-cutter FE 2 app InternalDirectoryPage (card grid avatar + dept + extension + email + mobile click-to-call)
- [ ] 👤 Chủ trì Solo Permission `Off_DanhBa` 1 leaf
#### Plan G-O2 — Phòng họp (booking calendar standalone)
- [ ] 👤 Chủ trì Solo Mig 36 `AddMeetingRooms` — MeetingRooms + MeetingBookings (RoomId/UserId/StartAt/EndAt/Title/Attendees[]/Status enum)
- [ ] 🟨 Implementer Case 2 BE CQRS Room CRUD + Booking Create/Cancel + GetCalendar + check conflict (SERIALIZABLE transaction race-safe)
- [ ] 🟨 Implementer Case 2 FE 2 app — Admin Rooms CRUD + User BookingCalendar (FullCalendar lib new dep — check license + bundle size impact)
- [ ] 👤 Chủ trì Solo Notification push attendees when booking created (NotificationPushInterceptor reuse)
- [ ] 👤 Chủ trì Solo Permission `Off_PhongHop*` 3 leaf
### 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)
- [ ] 👤 Chủ trì Solo Mig 38 schema + extend `ApplicableType` enum +ProposalGeneral=4
- [ ] 🟨 Implementer Case 2 mirror PE Mig 22-26 BE Proposal entity + ProposalAttachments + workflow pin `ApprovalWorkflowId`
- [ ] 👤 Chủ trì Solo BE ApproveV2Async branch + CodeGen `DX/YYYY/NNN` (cross-stack tight, mirror PE CodeGen)
- [ ] 🟨 Implementer Case 2 cookie-cutter mirror PE Plan B Chunk E1+D+E2+E3 FE 2 app ProposalsList + Create + Detail (Section 1-5)
- [ ] 🟥 Reviewer adversarial Smart Friend pre-commit — ApplicableType=Proposal validate cross-module security catch (gotcha #44 cumulative pattern)
- [ ] 🟩 CICD Monitor post-deploy verify Run + bundle hash rotate
#### 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