# STATUS — Snapshot hiện tại

> **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`.

**Last updated:** 2026-05-14 (Session 23 turn 1 — **🎯 Plan K: F2 refactor sang per-Approver-slot Mig 31** + UI consistency. 8 commits Plan K `56868bf..<latest>`: Chunk pre-A slot label rename → K1 Mig 31 schema swap (drop `Users.AllowDrafterSkipToFinal` + add `ApprovalWorkflowLevels.AllowApproverSkipToFinal` no BACKFILL) → K2 Service ApproveV2Async Approver F2 branch + DTO refactor → K3 Designer 7th checkbox + banner rewrite → K5 zombie endpoint cleanup + Reviewer Major #1+#2 + Minor #3+#4 → K6 Workspace × 2 app DROP Drafter + ADD Approver toggle → K7 tests regression 104/104 PASS (3 deleted + 3 added cancel) → K8 docs. Multi-agent execution: 🟦 Investigator K0 pre-flight + 🟨 Implementer K1+K3+K5+K7 (4 spawns Case 2+3) + 🟥 Reviewer K2 pre-commit (PASS 0 critical, 2 Major + 2 Minor) + 👤 Chủ trì K0-bis+K2+K6+K8. Bro decision: F2 semantic ĐỔI Drafter from Nháp → Approver during ChoDuyet skip thẳng Cấp cuối (mirror F3+F4 admin opt-in per slot) + setup ALL ở Workflow Designer (KHÔNG ở User Management). UI slot label "#NV {order}" → ApproverFullName. Stats final S23 t1: **31 mig (+1 Mig 31)** · 59 tables · **~145 endpoints (-1 backout /allow-skip-final)** · 34 FE pages · **104 test pass unchanged** (3 deleted + 3 added) · 47 gotcha unchanged · 20 memory (cumulative reinforce `feedback_per_nv_permission_scope` 3×) · 6 skills · **4 sub-agents active 4 spawns (Implementer Case 2+3) + 1 Reviewer**. 4 prod user lose `AllowDrafterSkipToFinal=true` value per Option A (admin re-config qua Designer). Pattern reinforced: per-NV admin opt-in flag 3× proven (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2) — pattern ALSO applies cho refactor existing scope, KHÔNG chỉ greenfield. Plan B Contract V2 wire vẫn pending S23 t2+. CHƯA push remote — chờ bro confirm.)
**S22 chốt cuối:** 2026-05-13 2200 (Session 22 CHỐT — **🎯 Bro chốt sub-agent solution OK**. 11 commits S22 pushed remote `3d725c4..b04a11a`. CICD Monitor Run #188 PASS verified. Highlights: Plan D F2 toggle + Plan C +20 test (test catch-up backlog) + Plan E strict V2 scope + Plan F ABORTED pre-flight (defer sau Plan B) + S22+1 fix disable 3 button bug + S22+2 seed 20 test user role-based naming + S22+3 rename users `{dept}.{level}@solutions.com.vn` + S22+4 attachment view inline PDF + Section "Điều chỉnh ngân sách" + S22+5 Mig 30 `AllowApproverEditBudget` per-NV opt-in (spec fix bro feedback). Stats final S22: **30 mig (+1 Mig 30)** · 59 tables · **~146 endpoints (+3)** · 34 FE pages · **104 test pass (+20: 5 reg #44 + 7 ReturnMode + 7 Guard + 1 V2 actor scope)** · **47 gotcha (+1 #47 paths-ignore agent-memory gap pending bro fix)** · 19 memory · 6 skills · **33 active users prod** (13 cũ + 20 mới role-based) · 4 sub-agents (CICD Monitor Run #188 PASS verify, 3 sub-agents seeds-only em main solo throughout S22). Pattern reinforced: per-NV admin opt-in flag 2× proven (Mig 29 + Mig 30) — anti-pattern default scope expansion. Memory `feedback_per_nv_permission_scope` reinforced S22+5 narrative.)
**Last updated S22 prev:** 2026-05-13 1800 (Session 22 — Plan C + D + E done, Plan F ABORTED pre-flight fail. 5 commits local `60efeed`→`dbda37e`→`215b1e0`→`f149661`→Docs. Plan D BE+FE Admin User F2 toggle UI (PATCH /users/{id}/allow-skip-final + UsersPage column "Skip cuối" violet badge). Plan C task 4 5 reflection regression test gotcha #44 silent 403 ApprovalWorkflowsV2Controller policy split (catch class-level Policy regression). Plan C task 1-3 14 service test catch-up — ApplyReturnMode 4 mode per-NV Mig 29 + skipToFinal per-Drafter + EnsureEditableForDetails F3 gating. Plan E strict V2 scope List + Detail (remove UAT loose `|| ApprovalWorkflowId != null`, replace with V2 approver actor.UserId scope via ApprovalWorkflowLevels join). Plan F ABORTED — pre-flight prod sqlcmd reveal 23 PE + 7 Contract pin V1 + Contract entity HOÀN TOÀN V1 chưa wire V2 → drop V1 BE crash startup. Defer F sau Plan B Contract V2 + migrate 4 V1-only PE + UAT 2-3 tuần. Stats S22: 29 mig (0) · 59 tables · ~144 endpoints (+1) · 34 FE pages · **103 test pass (+19: 5 reg #44 + 7 ReturnMode + 7 Guard)** · 46 gotcha · 19 memory · 6 skills · 4 sub-agents (em main solo). Pattern reusable: SeedWorkflowAsync + SeedApproversAsync helper test infra + reflection Authorize regression test + pre-flight prod check pattern. CHƯA push remote — chờ bro confirm.)
**S21 chốt cuối:** 2026-05-13 1530 (Session 21 chốt cuối — 5 turn cumulative pushed remote, CICD verified PASS 2/2 run, 1 gotcha mới #46 Gitea API path, 2 memory entry mới — `feedback_ef_migration_backfill_reorder` + `feedback_per_nv_permission_scope`. Session 21 5 turn timeline: t1 cicd-monitor add → t2 RAG planning Cách A → t3 fix gotcha #45 → t4 F1+F2+F3 Mig 28 → t5 refactor Allow* per-NV Mig 29. Stats final: 29 mig · 59 tables · ~143 endpoints · 34 FE pages · **84 test pass** · **46 gotcha (+2 từ S20: #45 PE button mismatch + #46 Gitea API)** · **19 memory entries (+3 từ S20: RAG + EF backfill + per-NV scope)** · 6 skills · 4 sub-agents (3 seeds-only + 1 cicd-monitor 2 runs PASS). 12 commits pushed `3a34831..c0af9e0`. Plan G Trial Week 1 evidence: CICD Monitor catch 0 fail vì green 2/2 run, cost 110-120K/run under 150K budget, 3-3.5min CI time stable. KHÔNG còn pending push. UAT mode skip dotnet test mỗi chunk, test-after Plan C bundle cho tất cả turn 3-5 sau UAT 2-3 lần ổn.)
**S21 turn 5:** 2026-05-13 1400 (Session 21 turn 5 — **🎯 Refactor F1+F2+F3 sang PER-NV (Mig 29 drop Mig 28 column workflow-level). 4 chunk per-commit `0366946` (A BE schema+Service refactor) → `63234b2` (B FE Admin Designer 5 checkbox per-Level row) → `5ccb2a7` (C FE eOffice rename currentLevelOptions + drafterAllowSkipToFinal) → this Chunk D Docs. **F1+F3** 5 flag MOVED xuống `ApprovalWorkflowLevels` (per slot Approver, mỗi NV có riêng quyền duyệt). **F2** AllowDrafterSkipToFinal MOVED xuống `Users` (per-Drafter user, admin config ở User Management). Mig 29 4-stage: ADD 5 column Levels + 1 column Users + BACKFILL bulk SQL (copy workflow → all Levels, set TRUE cho Drafter user nào từng dùng workflow Allow) + DROP 6 column workflow `ApprovalWorkflows`. Service `ApplyReturnModeAsync` refactor đọc `currentLevel.AllowXxx` thay vì `workflow.AllowXxx`. Helper `EnsureEditableForDetailsAsync` read `level.AllowApproverEditDetails`. DRAFTER trình branch read `userManager.FindByIdAsync(actorUserId).AllowDrafterSkipToFinal`. DTO refactor: `AwLevelDto +5 Allow*`, `AwDefinitionDto -6 Allow*`, `CreateAwLevelInput +5 Allow*`, `PeDetailBundle.workflowOptions → currentLevelOptions + drafterAllowSkipToFinal`. FE Admin Designer drop section "Cấu hình nâng cao" workflow-level, replace 5 checkbox grid-cols-2 inline mỗi Level entry row (5 flag per slot). FE eOffice rename `wfOptions → levelOptions` đọc `currentLevelOptions`. Backward compat: backfill preserve admin config S21 t4. Test 84/84 PASS unchanged. Stats: **29 mig (+1) · 59 tables · ~143 endpoints · 34 FE pages · 84 test pass · 45 gotcha · 17 memory · 6 skills · 4 sub-agents seeds-only.**)
**S21 turn 4:** 2026-05-13 1200 (Session 21 turn 4 — **🎯 F1+F2+F3 PE Workflow advanced options (Mig 28) — 5 chunk per-commit `0294693`→`c56024b`→`a508564`→`d27caaf`→this Chunk E Docs. **F1** 4 mode Trả lại admin tick: "1 Cấp / 1 Bước / Người chỉ định / Người soạn thảo" — 3 mode đầu giữ Phase=ChoDuyet lùi pointer (peer review chain), mode Drafter giữ Phase=TraLai S17 fallback. **F2** Drafter skip thẳng Cấp cuối — workflow tick + Workspace checkbox dynamic confirm. **F3** Approver edit Section 2 (Hạng mục/NCC/Báo giá) khi workflow tick + actor match CurrentLevel + audit ghi PurchaseEvaluationChangelog. Mig 28 `ApprovalWorkflows +6 bool Allow*` (DEFAULT 1 cho AllowReturnToDrafter backward compat, 5 còn lại 0). BE Service `TransitionAsync` extend 3 optional param (returnMode/returnTargetUserId/skipToFinal) + helper `ApplyReturnModeAsync` switch 4 mode. Detail/Quote/Supplier helper `EnsureEditableForDetailsAsync` mới (kế thừa `EnsureDraftAsync` + add ChoDuyet+F3 branch + Admin bypass). FE Admin Designer "Cấu hình nâng cao" section 6 checkbox 3 group. FE eOffice 3 changes mirror 2 app: Trả lại radio picker 1-4 mode + Workspace skip checkbox violet + Section 2 itemsReadOnly approver banner. UAT mode skip dotnet test mỗi chunk (per `feedback_uat_skip_verify`), `npm run build` × 2 app pass mỗi chunk. Stats: **28 mig (+1)** · 59 tables · **~143 endpoints (+1 user-selectable patch existed)** · **34 FE pages (+1 Designer section)** · **84 test pass unchanged** (UAT defer test-after) · **45 gotcha unchanged** · 17 memory · 6 skills · 4 sub-agents seeds-only.**)
**S21 turn 3:** 2026-05-12 2100 (Session 21 turn 3 — **🎯 Bug fix CRITICAL "Trả về nhưng hệ thống vẫn duyệt" PE workflow (gotcha #45). 2 chunk per-commit `de00887` (BE Chunk A) + `4b29d00` (FE Chunk B) + Chunk C Docs this. Root: PeWorkflowPanel.tsx `isReject` payload (L64-66) thiếu nhánh TraLai → button "← Trả lại" gửi `decision: 1` (Approve) thay vì `2` (Reject) khi target=TraLai(98) → BE ApproveV2Async UPSERT opinion "đã duyệt" + advance Cấp. Inconsistency phụ: dialog `isSendBack` (L247-248) cùng pattern thiếu TraLai → dialog title sai. Fix BE defense-in-depth + FE 3 chỗ × 2 app mirror rule §3.9. Test-before §7 BẮT BUỘC: 3 regression test mới (2 reproduce bug + 1 happy path control) — `dotnet test SolutionErp.slnx` 84 PASS (58 Domain + 26 Infra = +3). `npm run build` × 2 app pass. Stats: 27 mig (no change) · 59 tables · ~142 endpoints · 34 FE pages · **84 test pass (+3)** · **45 gotcha (+1 #45)** · 17 memory entries (no new) · 6 skills. Em main solo (no sub-agent spawn S21 t3 — bug fix reasoning chain cross BE/FE Implementer REFUSE per multi-agent rule).**)
**S21 turn 2:** 2026-05-12 1800 (Session 21 turn 2 — **🎯 RAG Hybrid setup planning + Cách A validation deep dive. 2 commit (`1f8e9af` plan save 1223 LOC + this chốt). Em main solo (no SOLUTION_ERP sub-agent spawn), delegate claude-code-guide × 2 research Anthropic + community practice. Decision chốt: Cách A defensive (giữ blanket 120K em main + RAG retrieve supplement) over Cách B aggressive (cắt 60-70% blanket). Industry-validated cross 4 Anthropic blog + 5 community tools (Cursor/Continue/Cline/Aider all hybrid). Stack: Voyage-3-large + Qdrant local + FastMCP Python + Streamlit dashboard 7 pages + SQLite event log. Multi-agent cost reality: 4 agents → ~520K cumulative blanket → heavy session ~560K (Cách A) vs ~700K (lazy), saving -20%. 3-layer pattern Phase 1-3 rollout (Layer 1 vector → Layer 2 +BM25 → Layer 3 +reranking, recall ~70% → ~92%). Stats: +1 memory entry (`feedback_rag_hybrid_pattern.md`) +1 plan file (`rag-setup-plan.md` 1500 LOC final). 4 sub-agents vẫn seeds-only. Plan I NEW deferred chờ bro confirm 5 dự án path + stack + Voyage API key + disk cleanup 5-8GB.**)
**S21 turn 1:** 2026-05-12 0030 (Session 21 turn 1 — **🎯 Add con thứ 4 cicd-monitor (Path A — post-deploy verifier green READ tier). 1 commit `f1c61c9` pushed `36e21c8..f1c61c9 main -> main`, CI skipped per path filter (`**/*.md` paths-ignore docs-only). Trade-off: +~150K spawn extra mỗi run, đổi lại catch deploy ship fail tự động (bundle hash unchanged / mig drift prod / endpoint 500) — recurring blind spot pattern em main solo S20 quên verify ~30% push. Cost reality update: ~750K spawn setup (3 → 4 agents) · ~1.35M heavy session · ~700K optimized cached. Stats: 4 sub-agents seeds-only (+1 cicd-monitor green) · 16 memory entries (no new, update existing `feedback_multi_agent_setup.md` 3 → 4 agents narrative) · 27 mig · 59 tables · ~142 endpoints · 81 test unchanged · 44 gotcha unchanged · 6 skills unchanged. KHÔNG flush 3 agent MEMORY.md (chưa spawn work S21 t1 nên KHÔNG có findings — em main solo via context + Write file).**)
**S20 wrap:** 2026-05-11 22:00 (Session 20 wrap turns 1-12 — **🎯 14 commit `9dee00d` → `ae1814c`. PE Detail UI restructure 3 yêu cầu (t1-5) + Manual budget drop tên (t6) + Mig 27 admin menu eOffice (t7) + NCC palette 5-màu cycle + Winner icon ✓ đậm + AddSupplier auto-fill master + Responsive laptop nhỏ 4-tầng pattern (t8-11) + Multi-agent infrastructure setup 3 sub-agents (t12). 27 mig (+1) · 59 tables · ~142 endpoints (+1) · 34 FE pages (+1) · 61 menu key (+1) · 81 test pass unchanged · 44 gotcha · 16 memory entries (+2) · 3 sub-agents NEW. Phase 9 UAT iteration mode.**)
**S20 turn 7:** 2026-05-11 17:00 (Session 20 turn 7 — **🎯 Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27). 5 chunk `2ea2d27`→`ef394f8`→`059bfcb`→`1ed6530`→Chunk E Docs. User Q2=b: DisplayLabel CHỈ áp fe-user, admin sidebar giữ Label gốc. Domain MenuItem +IsVisible(true) +DisplayLabel(200). Mig 27 AddVisibilityAndDisplayLabelToMenuItems. BE PATCH /api/menus/{key} [Authorize Policy=Permissions.Update]. NEW FE-admin MenuVisibilityPage ~210 LOC (table inline edit per-row + Save dirty + Khôi phục mặc định + Toggle Eye/EyeOff + 4 StatCard). fe-user Layout filterForUser 2 tầng (USER_HIDDEN_KEYS hardcode + !isVisible dynamic) + effectiveLabel(displayLabel || label) replace 3 callsite. fe-admin Layout KHÔNG đụng. +1 menu key MenuVisibility "Menu eOffice" leaf System Order=94. 27 mig, 59 tables, ~142 endpoints, 34 FE pages, 81 test pass (Q4 UAT defer).**)
**S20 prev:** 2026-05-11 (Session 20 — **🎯 PE Detail UI restructure 3 yêu cầu user UX. 4 chunk per-commit `9dee00d` → `2bba851` → `f2f01f4` → (current Chunk D Docs).** Q1=a (giữ Section "Chọn NCC TP" riêng), Q2=a "1 hạng mục trước tiên" (NCC shared, demo 1 hạng mục), Q3=a (chỉ hiện NV đã ký), Q4 public luôn (skip dotnet test mỗi chunk theo memory `feedback_uat_skip_verify`, vẫn `npm run build` × 2 app mỗi chunk vì có rename/remove function). **Chunk A (`9dee00d`)**: BE `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 hoặc BudgetManualAmount fallback 0; Changelog Insert audit. FE reorder PeDetailTabs (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. **Chunk B (`2bba851`)**: 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 card: GroupCode + NoiDung + 3 stat (KL/ĐG/TT) + NS link Δ nếu có + Pencil/Trash actions + ▼/▶ toggle expand. Expand body: NCC inline table columns 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. Quote inline click cell → QuoteDialog cũ reuse. Add NCC + Sửa NCC reuse AddSupplierDialog/EditSupplierDialog cũ. Winner ✓ button mỗi NCC row. Drop function `SuppliersTab` (dead code ~134 LOC, replace bằng HangMucCard expand panel). Giữ AddSupplierDialog + EditSupplierDialog + SupplierAttachmentsCell (HangMucCard call lại). Section layout cuối: 1.Thông tin / 2.Hạng mục + Báo giá NCC (nested) / 3.Chọn NCC TP thắng thầu / 4.Ý kiến cấp duyệt — 4 section. **Chunk C (`f2f01f4`)**: Section Ý kiến restructure render layer (KHÔNG đụng Mig 26 schema — vẫn UPSERT 1 row / Level). LevelOpinionsSectionV2 forEach step → 1 `StepOpinionsBox` (replace grid-cols-2 cho N approver). Box 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 opinion (tên NV + Cấp badge slate + admin override badge amber nếu có + emerald rounded-full timestamp + comment text). NV chưa duyệt KHÔNG hiển thị (Q3=a). Drop function `LevelOpinionBox` (replaced). Mirror fe-admin + fe-user. Verify build pass cả 2 app sau khi catch TS6133 `SuppliersTab` + `SupplierAttachmentsCell` unused (đã giải quyết: drop SuppliersTab, restore SupplierAttachmentsCell vào HangMucCard cột "File báo giá"). 81 test pass (no change — UAT defer)**)

## 📍 Phase hiện tại: **Phase 9 active — UAT V2 testing với user thật** — **59 DB tables, 29 migrations (Mig 28 +6 column workflow-level S21 t4 → Mig 29 refactor sang per-NV S21 t5 — 5 column trên ApprovalWorkflowLevels + 1 column trên Users + DROP 6 column workflow-level), ~143 API endpoints, 34 FE pages. 84 unit test pass** (58 Domain + 26 Infra = +3 PE guard S21 t3, UAT defer test-after per §7 cho S21 t4+t5). **46 gotcha (+1 S21 t5 chốt: #46 Gitea API path/cache)**. 30 demo user + 1 test user UAT. 6 skill. **5 trạng thái phiếu** (Nháp/Đã gửi duyệt/Trả lại/Từ chối/Đã duyệt). **2 Workflow schemas đồng tồn tại** post-Session 17 + Per-NV refactor S21 t5: (1) Mig 21 `WorkflowDefinition` flat (V1) — pin với PE/Contract cũ. (2) Mig 22-29 `ApprovalWorkflow` (V2) — pin với PE mới, Mig 29 các flag per-NV ở Levels + Users. Service PE branch theo `ApprovalWorkflowId`. **4 sub-agents:** 🟦 Investigator + 🟨 Implementer + 🟥 Reviewer (3 seeds-only S21 — em main solo cross-stack reasoning chain REFUSE) + 🟩 CICD Monitor (2 runs PASS, ~110-120K/run cost). 30 demo user + 1 test user UAT. 6 skill. **5 trạng thái phiếu** (Nháp/Đã gửi duyệt/Trả lại/Từ chối/Đã duyệt). **2 Workflow schemas đồng tồn tại** post-Session 17: (1) Mig 21 `WorkflowDefinition` flat (V1) — pin với PE/Contract cũ + match Dept+PositionLevel. (2) Mig 22-26 `ApprovalWorkflow` (V2) — pin với PE mới + match ApproverUserId 1-1, Steps/Levels group by Order, Bước (Phòng) > Cấp (N NV OR-of-N), Mig 25 +IsUserSelectable admin pin per version, **Mig 26 +PeLevelOpinions sign-off dynamic theo Level**. Service PE branch theo `ApprovalWorkflowId` set or null. Sau UAT chốt → migrate + drop V1 + Contract V2 wire.

### 🌐 Production URLs

- https://api.solutions.com.vn — API (Let's Encrypt, auto-renew via win-acme)
- https://admin.solutions.com.vn — Admin FE (HTTP→HTTPS auto-redirect)
- https://eoffice.solutions.com.vn — User FE (HTTP→HTTPS auto-redirect)
- https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp — Gitea repo + Actions
- Default admin: `admin@solutionerp.local` / `Admin@123456` ⚠️ **RE-ROTATE sau login đầu**

## 🔥 In Progress — Session 7+ (Phase 9 active)

### A. Hard blockers (chờ user / ops)

- [ ] **UAT thật 1 tuần** với 2-3 user (Drafter / CCM / BOD) — hard requirement Phase 5 roadmap
- [ ] **SMTP config** → Email outbox (BLOCKED chờ user cấp host/user/pass)
- [ ] **Rotate creds** — admin + 30 demo + SA + vrapp + JWT secret + Gitea runner token
- [ ] **Schedule SQL backup daily** — `scripts/backup-sql.ps1` đã sẵn, chưa schedule Task Scheduler
- [ ] **Remove binding cũ `.huypham.vn`** sau verify stable: `ssh vietreport-vps ; .\migrate-domains.ps1 -RemoveOld -SkipCert`
- [ ] **win-acme scheduled task "unhealthy"** — auto-renew fix trước 2026-06-18

### B. Carry over feature gap

- [ ] **Export phiếu PDF/Excel** PE — tái dùng `IDocumentConverter` + template `PE-TrinhDuyet.docx` (user pending — không quan trọng lắm)

### C. Optional polish (khi UAT phát sinh bug)

- [ ] Budget MaNganSach atomic sequence (hiện Random.Shared → migration `AddBudgetCodeSequences`)
- [ ] Budget versioned workflow (admin config UI thay hardcoded `BudgetPolicy.Default`)
- [ ] Payment terms PE tách field (JSON blob → 6 column riêng)
- [ ] Auto-map PE Details → Contract per-type Details khi gen HĐ
- [ ] Matrix Quotes bulk paste từ Excel
- [ ] fe-user Inbox thêm section "Phiếu Duyệt NCC chờ tôi"

### D. Tests Phase 3-5 (làm khi gặp bug recurring để justify ROI — rule §7)

- [ ] **Phase 3 full** — Application handler tests cần UserManager DI helper (PE Opinion Upsert, Budget validate, CreateContractFromEvaluation BudgetId carry) ~15 test
- [ ] **Phase 4** — API smoke tests qua WebApplicationFactory ~7 test
- [ ] **Phase 5** — FE Vitest cho lib utility (queryMatches, fmtMoney) ~10 test

### E. 2-stage dept approval — Chunk E-bis ✅ DONE (Session 9)

- [x] **FE Workflow Panel** PE — section "Tiến trình duyệt 2-cấp phòng ban" group by phase × dept, highlight amber khi chờ TPB confirm
- [x] **FE UserManager toggle** `CanBypassReview` — column "Bypass" badge fuchsia + button toggle ShieldCheck (UsersPage)
- [x] **HĐ 2-stage** mở rộng — `ContractWorkflowService` thêm UserManager DI + 2-stage logic mirror PE + `ContractDepartmentApprovalFeatures.cs` + endpoint `GET /contracts/{id}/department-approvals` + FE WorkflowHistoryPanel section mới
- [x] **Budget 2-stage** mở rộng — `TransitionBudgetCommandHandler` thêm INotificationService + IDateTime + 2-stage logic + `BudgetDepartmentApprovalFeatures.cs` + endpoint + FE BudgetWorkflowPanel section
- [x] **Tests 2-stage** (6 test) — `IdentityFixture` setup full Identity stack + 6 test PE workflow service: NV review block / TPB confirm allow / NV bypass / Admin skip / Reject set / Resume jump-back. Pattern reusable.

### F. Audit định kỳ (cron tự fire)

- [x] **2026-05-01** (audit 2026-05-04 manual trigger sau trễ 4 ngày) — combined audit log `docs/changelog/skill-audit-2026-05.md`. Cron Claude SDK KHÔNG fit monthly cadence (auto-expire 7d) → user setup OS Task Scheduler nếu cần auto-remind.
- [ ] **2026-06-01** — combined audit kế. Trigger thủ công khi đến ngày hoặc user nói "audit MD" / "kiểm tra docs" / "định kỳ kiểm tra".

## ✅ Recently Done (newest on top)

| Ngày | Ai | Task | Commit |
|---|---|---|---|
| 2026-05-12 | Claude | **🎯 SESSION 21 turn 2 — RAG Hybrid setup planning + Cách A validation deep dive (2 commit `1f8e9af` plan save + this chốt)** — Sau S21 turn 1 chốt cicd-monitor, user clarify 5 dự án future > 1M MD tokens → cuộc thảo luận deep ~15 turn về RAG infrastructure. **Em main solo** (no SOLUTION_ERP sub-agent spawn), delegate **claude-code-guide × 2** spawn agent research Anthropic + community practice. **Q&A deep dive 10 topics**: (1) RAG fundamentals + Vector DB Qdrant role, (2) Embedding "AI nhúng" + Voyage AI cost mechanics ($0.18/M tokens), (3) Multi-project shared architecture (5 projects → single Qdrant + per-collection), (4) Audit procedure 3-tier (weekly auto + monthly deep + quarterly major), (5) UI/UX Streamlit dashboard 7 pages design (overview + drill-down + compare + audit + cost + change + admin), (6) Cách A defensive (giữ blanket 120K) vs Cách B aggressive (cắt 60-70%), (7) Reasoning depth comparison lazy 60% → A 90% → B 75-80%, (8) Industry validation Anthropic + Cursor + Continue + Cline + Aider all hybrid, (9) Multi-agent cost reality 8-10× multiplier ~520K cumulative blanket 5 entities, (10) 3-layer hybrid pattern Anthropic Contextual Retrieval Sept 2024. **Quyết định chốt Cách A** (defensive hybrid: giữ blanket 120K em main + RAG retrieve supplement, sub-agent spawn baseline ~100K each, 4 agents = ~400K cumulative, heavy session billed ~560K saving -20% vs lazy 700K, quality recall ~85%) over **Cách B bỏ** (aggressive cut 60-70% vi phạm priority em main control flow strong + reasoning fragmented + UX latency +1-2s/state Q + risk severe RAG fail). **Why Cách A** (bro priority chốt): em main control flow strong preserve, decision quality 90% multi-source cohesive, wall-clock -20% (12 phút vs 16), risk-averse graceful fallback, multi-agent leverage cache 70-90%, industry-validated 9 sources. **3-layer hybrid Phase rollout**: P1 (W1-4) vector only Voyage-3-large recall ~70% $1.50/mo · P2 (M2) +BM25 bm25s free recall ~78% $1.50/mo · P3 (M3) +Voyage rerank-2 + Contextual prefix recall ~92% $4-5/mo. **Stack validated** cross-industry: Voyage AI embedding (Anthropic partner, multilingual 26 lang, $0.36 initial), Qdrant local (Rust 50MB, agent-native 2026 leader, ~3GB disk 5 project), FastMCP Python (official SDK, ~100 LOC), SQLite event log (5 tables + audit history), Streamlit 7 pages. **Plan I NEW deferred** — trigger bro confirm 5 dự án path + stack + pilot + Voyage API key + disk cleanup → dedicated session 10-14h weekend (per `feedback_drastic_refactor_scope` rule). **Deliverables**: `docs/rag-setup-plan.md` 1223 LOC commit `1f8e9af` + extend S21 t2 ~300 LOC = ~1500 LOC final, memory `feedback_rag_hybrid_pattern.md` cross-project reusable, session log this chốt, MEMORY.md index +1 entry. **CI skipped** path filter (`.md`). **4 sub-agents vẫn seeds-only** (KHÔNG spawn S21 turn 2 nên KHÔNG flush MEMORY.md per §6.5 KHÔNG add noise). Tests baseline 81 unchanged. | `1f8e9af` (plan save) · this chốt (commit final) |
| 2026-05-12 | Claude | **🎯 SESSION 21 turn 1 — Add con thứ 4 cicd-monitor (Path A — post-deploy verifier green READ tier, 1 commit `f1c61c9`)** — User chốt Path A sau pre-flight Plan G Trial Week 1: thêm sub-agent thứ 4 chuyên post-deploy verify (Gitea Actions poll + bundle hash 2 app verify + sqlcmd mig prod = repo latest + endpoint smoke). Trade-off: +~150K spawn extra mỗi run, đổi lại catch deploy ship fail tự động — recurring blind spot pattern em main solo S20 quên verify ~30% push. **2 file mới**: `.claude/agents/cicd-monitor.md` (~7KB) — system prompt + 8-step workflow (verify push → poll Gitea API → fail log grep → live curl smoke → bundle hash × 2 app + verify changed → sqlcmd mig prod = repo latest → report PASS/FAIL/PARTIAL/TIMEOUT/SKIPPED-DOCS) + 5-stage report table + gotcha #25/#39/#40/#41/#44 cross-ref + skill `iis-deploy-runbook`/`dependency-audit-erp`/`ef-core-migration` preload + Anti-pattern 9 rules. `.claude/agent-memory/cicd-monitor/MEMORY.md` (~5KB seed) — recurring CI bug patterns + 5-stage checklist + baseline build/bundle metrics + bearer test pattern admin/nv.test. **1 file update repo**: `.claude/agents/README.md` — 4-agent architecture diagram (green slot mới) + decision tree (after push code + prod issue diagnose branches) + memory routine 4 SendMessage + skills preload 4 agents + cost reality table 564K → 750K spawn / 1.2M → 1.35M heavy / 600K → 700K optimized + trial workflow Week 1-3 CI/CD Monitor spawn integrated + pass criteria + catch ≥1 deploy ship fail. **Memory user-level update**: `feedback_multi_agent_setup.md` — title 3 → 4 sub-agents, decision tree +CI/CD Monitor invocation branches (after push + user prod issue), skills preload list +CI/CD Monitor (iis-deploy-runbook + dependency-audit-erp + ef-core-migration), cost table update + trade-off rationale (recurring blind spot ~30% push S20). **CI skipped**: all 3 file changed `.md` → match `paths-ignore: '**/*.md'` per gotcha #41 → no Gitea Actions run → no IIS deploy (expected — agent infra là local Claude Code, không cần present trên prod). Push success `36e21c8..f1c61c9 main -> main`. **3 (now 4) sub-agents vẫn seeds-only**: chưa spawn work nào — em main solo via context paste + Write file. KHÔNG flush 3 agent MEMORY.md (chưa spawn work = không findings, per §6.5 KHÔNG add noise entry). cicd-monitor MEMORY.md có entry "setup 2026-05-12" trong seed. Trial Week 1 kick-off ở Session 21 turn 2+ với Plan B Contract V2 wire Mig 28+29 candidate (mirror PE pattern S17-S19 proven 1×). Tests baseline 81 unchanged (no test added — docs-only commit). | `f1c61c9` (Setup cicd-monitor + README 4-agent + memory update) |
| 2026-05-11 | Claude | **🎯 SESSION 20 turns 6 + 8-12 — PE polish (NCC palette + autofill + responsive) + Multi-agent setup (7 commit `f568945` → `ae1814c`)** — Sau turn 7 wrap-up Mig 27, user iterate 7 polish/feature lớn nhỏ. **Turn 6 (`f568945`)** Manual budget "Nhập tay" drop tên field — 3 file × 2 app mirror (BudgetFieldRow + WorkspaceCreateView + HeaderForm) bỏ Input "Tên" UI khỏi manual mode, BE save `budgetManualName: null` luôn, VND format `1.000.000` + suffix đ. **Turn 8 (`3ec7b5a`)** AddSupplier +Số tiền inline + NCC 5-màu palette + Winner badge "🏆 Trúng thầu" — AddSupplierDialog +prop detailId? +form thanhTien, sequential POST /suppliers (response {id}) → POST /quotes (nếu detailId + thanhTien > 0). NCC_PALETTES const 5 màu literal Tailwind (blue/purple/sky/teal/pink) cycle theo idx. Winner row override emerald-500 border-l + bg-emerald-100/70 + shadow-sm + ring-1 emerald-300 + badge rounded-full bg-emerald-600 text-white "🏆 Trúng thầu". **Turn 9 (`83aae8e`)** User feedback bỏ badge → revert icon ✓ stick cũ nhưng đậm hơn (text-base font-bold emerald-700) + tên NCC winner text-emerald-900 + hover transition (winner hover:bg-emerald-200/70, non-winner hover:bg-white/80 hover:shadow-sm). **Turn 10 (`66551db`)** AddSupplierDialog auto-fill từ master data khi chọn NCC dropdown — onChange lookup picked supplier, setForm ghi đè 4 field (contactName ← contactPerson / contactPhone ← phone / contactEmail ← email / note ← note). Hint emerald "✓ Đã tự điền từ Master". User vẫn override được. **Turn 11 (`6e338f7`)** Responsive cho laptop màn hình nhỏ 1280-1366px — 4-tầng pattern: sidebar fe-admin + fe-user `w-72` → `w-60 xl:w-72` (+48px lg) / PE Workspace 2-panel `lg:[320px_1fr]` → `lg:[260px_1fr] xl:[320px_1fr]` (+60px lg) / Section padding `px-5 py-4` → `px-3 py-3 sm:px-5 sm:py-4` (+16px xs) / HangMucCard `gap-3 p-3` → `flex-wrap gap-2 p-2 sm:gap-3 sm:p-3` (+8px xs). Net gain trên 1366px ~+132px width cho NCC table area. Memory `feedback_responsive_laptop_breakpoint.md` capture pattern. **Turn 12 (`ae1814c`)** SETUP MULTI-AGENT INFRASTRUCTURE 3 sub-agents (Investigator READ cyan + Implementer WRITE conditional yellow + Reviewer READ adversarial red) + em main coordinator. Pre-flight decision gate 6/6 ✅. Phase 1-4 execute: `.claude/agents/` 4 file (README ~9.7KB + investigator + implementer + reviewer) + `.claude/agent-memory/` 3 MEMORY.md seed (~6KB each). Customize SOLUTION_ERP: skills preload mỗi agent (reuse 6 skills hiện có) + bearer test (admin@solutions / nv.test@solutions) + prod UAT URL + Phase 9 UAT mode + DB Dev/Design distinct. Windows MAX_PATH pitfall handled — drop `isolation: worktree` khỏi implementer.md (project path 51 chars + Dropbox-managed nested overflow 260+ chars). Memory `feedback_multi_agent_setup.md` capture decision gate + ACCEPT/REFUSE criteria + NAMGROUP s41-s43 ROI reference. 3 agents **chưa spawn work** ở S20 turn 12 — seeds-only state. Trial Week 1 candidate Contract V2 wire Mig 28+29 (mirror PE pattern proven). **Stats cumulative S20:** 27 mig (+1 Mig 27 from turn 7) · 59 tables · ~142 endpoints (+1 PATCH /menus/{key}) · 34 FE pages (+1 MenuVisibilityPage) · ~61 menu key (+1) · 81 test pass unchanged · 44 gotcha unchanged · **16 memory entries (+2: responsive + multi-agent)** · 6 skills unchanged · **3 sub-agents NEW** · 14 commits S20. | `f568945` (t6) · `3ec7b5a` (t8) · `83aae8e` (t9) · `66551db` (t10) · `6e338f7` (t11) · `ae1814c` (t12) · (current Docs t13 wrap) |
| 2026-05-11 | Claude | **🎯 SESSION 20 turn 7 — 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 "tính năng Ẩn Hiện và Đổi tên hiển thị của các Menu bên ngoài Office, làm trong Trang Admin Page". Hỏi xác nhận "chưa có" — đúng. User clarify Q2=b "edit hiển thị bên ngoài, chỉ của eOffice thôi" → admin sidebar luôn giữ Label gốc, DisplayLabel CHỈ áp fe-user. Q1=a global (không per-role), Q3=a giữ USER_HIDDEN_KEYS hardcode + tầng IsVisible dynamic combine, Q4 UAT skip test. **Chunk A** Domain MenuItem +IsVisible bool=true +DisplayLabel string?(200) + EF config + Migration 27 AddVisibilityAndDisplayLabelToMenuItems (2 AddColumn) — 3-file rule, apply LocalDB _Dev + _Design OK. **Chunk B** BE API: MenuNodeDto + MenuItemDto +isVisible +displayLabel (sau CRUD flags trước Children). GetMyMenuTreeQueryHandler pass through, KHÔNG filter server-side — 2 FE app tự quyết. UpdateMenuItemCommand + Validator + Handler (trim DisplayLabel whitespace → null). MenusController +PATCH /api/menus/{key} [Authorize Policy=Permissions.Update] body {isVisible, displayLabel}. **Chunk C** Domain MenuKeys +MenuVisibility const + 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 +isVisible +displayLabel, lib/menuKeys.ts +MenuVisibility, Layout resolver +/system/menu-visibility, App.tsx +Route. NEW pages/system/MenuVisibilityPage.tsx ~210 LOC: PageHeader + 4 StatCard (Tổng/Hiển thị/Đã ẩn/Đã đổi tên) + Search + Table 5 cột (Key mono + parentKey ↳ / Tên gốc / Input "Tên hiển thị" inline placeholder "Mặc định: {label}" / Toggle button emerald-Eye / amber-EyeOff / Lưu khi dirty + Khôi phục khi custom). PATCH endpoint, invalidate ['menus','all'] + ['my-menu'] trigger live update sidebar. Row hidden bg-amber-50/40 highlight, custom label bg-brand-50/40. **Chunk D** fe-user types/menu.ts mirror. Layout.tsx filterForUser 2 tầng (USER_HIDDEN_KEYS structural + !isVisible dynamic). Helper effectiveLabel(n) = displayLabel?.trim() || label. Replace 3 callsite {node.label} → {effectiveLabel(node)}. USER_FIXED_TOP "__inbox" entry +isVisible:true cho type check pass. **fe-admin Layout KHÔNG đụng** — admin sidebar render Label gốc + show hết menu (user Q2=b). **Chunk E Docs (current)**. **Stats Session 20 turn 7**: 26→27 mig, 59 DB tables (no change), ~141→142 endpoints, 33→34 FE pages, ~60→61 menu key, 81 test pass (Q4 UAT defer), 44 gotcha (no new). Memory entries 14 (no new). | `2ea2d27` (A Mig 27) · `ef394f8` (B BE API) · `059bfcb` (C FE admin) · `1ed6530` (D FE user) · (current E Docs) |
| 2026-05-11 | Claude | **🎯 SESSION 20 — PE Detail UI restructure 3 yêu cầu UX (4 chunk: 9dee00d→2bba851→f2f01f4→Chunk D Docs)** — User UAT live phản hồi "Logic OK rồi, điều chỉnh UI Duyệt NCC 1 tý": (1) Hạng mục lên trên + auto-tạo 1 row từ gói thầu, (2) NCC expand dưới hạng mục, (3) Section Ý kiến gộp đồng cấp cùng Phòng. Q&A clarify trước code (4 câu Q1=a/Q2=a "1 hạng mục"/Q3=a "chỉ hiện signed"/Q4 "public luôn demo thôi"). 4 chunk per-commit pattern `feedback_per_chunk_commit`. **Chunk A** BE `CreatePurchaseEvaluationCommandHandler` + INSERT 1 PurchaseEvaluationDetail mặc định + Changelog (GroupCode=01, NoiDung=TenGoiThau, ThanhTienNganSach=Budget.TongNganSach hoặc BudgetManualAmount fallback 0) + FE reorder PeDetailTabs section. **Chunk B** ItemsTab restructure list HangMucCard (1 card / hạng mục, expanded=true default cho demo 1 hạng mục). 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. Add NCC/Edit NCC reuse 2 dialog cũ. Winner ✓ button per row. Bỏ Section 4 "NCC tham gia" (gộp vào Section 2 nested) → 4 section final. Drop SuppliersTab function ~134 LOC dead code (replace bằng HangMucCard expand). Giữ AddSupplierDialog + EditSupplierDialog + SupplierAttachmentsCell (HangMucCard reuse). **Chunk C** Section Ý kiến gộp đồng cấp cùng Phòng. 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 amber + timestamp emerald rounded-full + comment). NV chưa duyệt KHÔNG hiển thị (Q3=a). KHÔNG đụng Mig 26 schema (vẫn UPSERT 1 row / Level qua Service). Drop LevelOpinionBox function. Mirror fe-admin + fe-user mỗi chunk. **Verify**: dotnet build pass (Chunk A) + npm build × 2 app pass (Chunk B/C — catch TS6133 SuppliersTab unused + SupplierAttachmentsCell unused, fix re-add cột File báo giá vào nested table). **Test skip** Phase 9 UAT iteration (81 test pass unchanged). **Stats unchanged**: 26 mig, 59 DB tables, ~141 endpoint, 33 FE pages, 44 gotcha, 81 test. **Pending S21+**: Test regression B4 silent 403 (HIGH §7), Test V2 Service wire ApproveV2Async + Section gộp (Chunk C), Test Mig 25 PATCH user-selectable, Contract V2 (Mig 27/28 mirror PE), phân quyền strict V2, drop legacy V1 cleanup. | `9dee00d` (A) · `2bba851` (B) · `f2f01f4` (C) · (current D Docs) |
| 2026-05-09 | Claude | **🎯 SESSION 19 — PE Section 5 V2 dynamic theo ApprovalWorkflowLevel + Mig 26 (4 commit: 873e7a1 polish 3 button + 77a3058/90baa8e/6e913b3/Chunk D Mig 26)** — User feedback Section 5 hiện CỨNG 4 box (PheDuyet/CCM/MuaHàng/SmPm Mig 15 từ Phase 8) → cần động theo Workflow V2 đã pin: forEach Step (Phòng) → forEach Level (Cấp) → forEach NV → 1 OpinionBox với ý kiến + tên người ý kiến. Bước 1 Phòng A có 2 NV → 2 box ngang hàng. **5 câu chốt spec trước code:** Q1=1B (gắn — Service auto sync khi duyệt, KHÔNG form input rời), Q2=2A+Admin (NV chính chủ + Admin override với SignedByUserId track actual signer), Q3=chuyển V2 hết (phiếu V1 legacy fallback Mig 15 4 box readOnly), Q4=4C+bonus (Phase=DaDuyet/TuChoi khoá; Admin có quyền duyệt thay; comment empty → "(duyệt — không ý kiến)" placeholder), Q5=5A (layout group Step header "Bước N — Phòng X" + grid-cols-2 cho N approvers). **3 chunk per-commit (memory `feedback_per_chunk_commit`):** Chunk A (`77a3058`) Domain entity `PurchaseEvaluationLevelOpinion : AuditableEntity` (PEId+LevelId UNIQUE composite, Comment nvarchar(2000), SignedAt datetime2, SignedByUserId Guid, SignedByFullName nvarchar(200) denorm) + EF config FK Cascade Pe + Restrict Level + ApplicationDbContext + IApplicationDbContext DbSet + **Migration 26** `AddPeLevelOpinionsForV2` (1 CREATE TABLE + 2 FK + 2 index — UNIQUE composite + IX LevelId). 3-file rule. Apply LocalDB SolutionErp_Dev OK. Chunk B (`90baa8e`) Service `PurchaseEvaluationWorkflowService.ApproveV2Async` sau line log approval → UPSERT row PurchaseEvaluationLevelOpinions cho Cấp hiện tại: match level theo `ApproverUserId == actorUserId` (multi-NV cùng Cấp OR-of-N), fallback first khi Admin override (FE detect SignedByUserId !== Level.ApproverUserId hiển thị "Admin duyệt thay"). Reject KHÔNG sync. Empty/whitespace comment → "(duyệt — không ý kiến)" placeholder. Helper `ResolveActorFullNameAsync(actorUserId, isSystem)` lookup denorm SignedByFullName từ Users (fallback "(System)" / "(unknown)"). DTO `PurchaseEvaluationLevelOpinionDto` 15 fields (LevelId/StepOrder/StepName/StepDepartmentId/StepDepartmentName/LevelOrder/LevelName/ApproverUserId/ApproverFullName/Comment/SignedAt/SignedByUserId/SignedByFullName). GET handler `GetPurchaseEvaluationQueryHandler` Include LevelOpinions + helper `BuildLevelOpinionsAsync` JOIN ApprovalWorkflows.Steps.Levels + Departments + Users → denorm DTO list. Empty list cho phiếu V1 / V2 chưa có cấp duyệt → FE fallback. Chunk C (`6e913b3`) FE Section 5 V2 dynamic: type `PeLevelOpinion` + `PeDetailBundle.levelOpinions[]`. Section 5 conditional: `evaluation.approvalWorkflowId` set → `<LevelOpinionsSectionV2/>` (V2 dynamic), else `<DepartmentOpinionsSection/>` readOnly fallback (V1 legacy giữ Mig 15 4 box). Component `LevelOpinionsSectionV2` group theo step.order: header "Bước N — <name>" + dept badge emerald + hint "(N người duyệt)" khi totalApprovers > 1; body grid-cols-2 cho `step.levels.flatMap(level => level.approvers.map(approver => <LevelOpinionBox/>))`; lookup opinion theo (stepOrder, levelOrder, approverUserId). `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" italic gray, footer timestamp signedAt format vi-VN. Workspace mode hint giữ amber "Ý kiến + chữ ký auto đồng bộ khi NV duyệt". Mirror fe-admin + fe-user (rule §3.9). Verify: dotnet build pass + dotnet test 81 pass + npm run build × 2 pass · 0 TS error. Chunk D docs (current) STATUS/HANDOFF/migration-todos/CLAUDE.md/schema-diagram §16 mới + session log. Phiếu V1 cũ KHÔNG migrate (giữ Mig 15 readOnly), drop sau UAT confirm. **Stats:** 26 mig (+1), 59 DB tables (+1), ~141 endpoints (no new), 33 FE pages, 81 test pass. Polish 3 button (873e7a1) Hành động đầu Session 19: rút gọn label "✓ Duyệt / ← Trả lại / ✗ Từ chối" + 3 màu khác nhau (emerald/amber/red) + font-bold cho cả 2 app. | `873e7a1` (3 button) · `77a3058` (Chunk A Mig 26) · `90baa8e` (Chunk B Service+DTO+GET) · `6e913b3` (Chunk C FE) · (current Chunk D Docs) |
| 2026-05-08 19:45 | Claude | **🎯 SESSION 18 WRAP-UP — PE V2 polish + Clone B (DuyetNccPhuongAn) + 4 bug fix UAT + Mig 25 IsUserSelectable (7 commit `aaa1c6c` → `32a8d4d`)** — User UAT live tiếp Session 17, request 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`. **B1 (`aaa1c6c`)** Pe Duyệt (`?pendingMe=1`): bỏ dropdown "Tất cả trạng thái" + filter cứng client-side `getPeDisplayStatus === DaGuiDuyet` (loại Nháp/Trả lại/Đã duyệt/Từ chối). Hint amber "Lọc cố định: Đã gửi duyệt". Header count dùng `rows.length` (inbox không paged). Workaround BE /inbox loose UAT trả phiếu Nháp. Mirror fe-admin + fe-user. **B2 (`917446d`)** PeDetailTabs HistoryTab filter chỉ events Trả lại/Gửi duyệt lại: workflow transition về TraLai (phaseAtChange=98) + transition từ TraLai (summary chứa "TraLai →") + sửa nội dung khi phaseAtChange=TraLai. BE giữ audit data đầy đủ, chỉ FE filter (reversible). Empty state "Chưa có lịch sử trả lại / gửi duyệt lại". Mirror cả 2 app. **B3 (`937eb24`) Clone V2 cho B (DuyetNccPhuongAn)** — User chốt "Quy trình chọn thầu phụ - NCC → Duyệt NCC đúng. Clone toàn bộ updates sang Duyệt NCC và Giải pháp". Audit phát hiện 80% chung qua `ApplicableType` discriminator → chỉ thêm 3 file ~60 LOC: (a) `MenuKeys.cs` +const `ApprovalWorkflowDuyetNccPhuongAnV2` + add vào `All[]`. (b) `DbInitializer.SeedMenusAsync` +leaf "Duyệt NCC và Giải pháp (Mới)" dưới root ApprovalWorkflowsV2 + new method `SeedSampleApprovalWorkflowsV2Async` seed `QT-DN-PA-V2-001 v01` (1 Bước Phòng CCM × 1 Cấp NV test, idempotent). (c) `fe-admin/lib/menuKeys.ts` +`AwV2_DuyetNccPhuongAn`. KHÔNG migration / Service / Designer page (Layout regex `^AwV2_(.+)$` đã match dynamic, ApprovalWorkflowsV2Page có `TYPE_CODE_TO_INT` cả 3 type). Rút memory `feedback_audit_reuse_before_clone.md`. **B4 (`f77ea38`) Fix permission silent 403** — Drafter `nv.test` Workspace dropdown empty mặc dù seed OK. Root: class-level `[Authorize(Policy = "Workflows.Read")]` → non-admin 403, TanStack Query catch silent → UI empty không warning. Fix: class-level `[Authorize]` only (any authenticated). GET = list workflow read-only không nhạy cảm; POST + DELETE giữ `Workflows.Create` admin-only. Pattern reusable cho Contract V2 sau. **B5 (`a9c0857`) Fix sidebar highlight queryMatches** — Click phiếu trong leaf "Danh sách" → URL `?type=1&id=abc` → menu mất highlight (gotcha #34 cũ tái phát). Root: queryMatches exact-set equality {type} vs {type, id} length mismatch. Fix: `TRANSIENT_QUERY_KEYS = {id, q, editHeader, page, phase, awId}` strip trước compare. Edge case verified: Danh sách `?type=1` vs Pending `?type=1&pendingMe=1` distinct (không cross-highlight). Mirror cả 2 app Layout.tsx. **B6 (`2a53107`) Mig 25 + Designer pin toggle + bỏ "(clone)" + Workspace filter** — User feedback Admin Designer: bỏ "(clone)" auto-suffix khi clone version (version đã đủ phân biệt) + thêm pin toggle "Cho user pick lúc create phiếu" (multi-select, độc lập IsActive). Migration 25 `AddIsUserSelectableToApprovalWorkflows`: ALTER ApprovalWorkflows +`IsUserSelectable bit NOT NULL DEFAULT 0` + Sql backfill `UPDATE WHERE IsActive=1 SET 1` (giữ behavior cũ active workflow vẫn pickable). Domain ApprovalWorkflow +property. DTO AwDefinitionDto +field. CreateAwDefinitionCommand set default `true` cho version mới (mirror IsActive). New `SetAwUserSelectableCommand` + Handler. API `PATCH /api/approval-workflows-v2/{id}/user-selectable` policy `Workflows.Create`. DbInitializer SeedSampleApprovalWorkflowsV2Async +`IsUserSelectable=true`. FE Designer: `DefinitionDto` +field; badge amber "📌 Cho user chọn"; button "Ghim cho user / Bỏ ghim" + mutation `toggleSelectable`. Designer `name = cloneFrom.name` (bỏ ` (clone)` suffix). Workspace fetch filter `w.isUserSelectable === true` (cả fe-admin + fe-user). **B7 (`32a8d4d`)** Cleanup orphan `.claude.zip + docs.zip` từ harness session start, +`*.zip` rule .gitignore. **Cumulative Session 18:** 25 mig (+1), 58 tables (no new), ~141 endpoints (+1), 33 FE pages, **81 test pass** (no change — feature mới UAT defer test theo §7), 44 gotcha (+1 silent 403). Memory +1 entry. **Pending Session 19+:** Contract V2 wire (Mig 26 mirror PE), phân quyền strict V2, drop legacy V1 cleanup. | `aaa1c6c` (B1) · `917446d` (B2) · `937eb24` (B3) · `f77ea38` (B4) · `a9c0857` (B5) · `2a53107` (B6) · `32a8d4d` (B7) |
| 2026-05-08 | Claude | **🎯 SESSION 17 WRAP-UP — PE Workflow V2 schema + Service wire end-to-end (13 commit `c847dc0` → `de0f38d`)** — User chốt sau Session 16 "Thấy vẫn không đúng" → viết lại schema riêng + thêm Menu "Duyệt NCC (Mới)" UAT. Cấu trúc rõ ràng: Quy trình > Bước (Phòng) > Cấp (NV cụ thể qua ApproverUserId). 3 chunk lớn: **Schema design + Designer** (Mig 22 — `c847dc0/f6047d5/2781c7e/12daa7f`): 3 entity ApprovalWorkflow/Step/Level + enum ApplicableType (DuyetNcc/DuyetNccPhuongAn/Contract). Designer page `/system/approval-workflows-v2/:typeCode` — iter 1 lock 3 cấp (`9712778`, sai intent) → iter 2 đúng intent max 3 cấp × N NV/cấp + sequential gating C2/C3 disabled khi cấp trước empty + filter NV theo Phòng + no-dup same level (`f3bea3c`). Validator BE Order∈{1,2,3} + HaveSequentialOrders + HaveNoDuplicateApproverInSameLevel. **State machine 5 trạng thái** (`ff21120`): Nháp→Đã gửi duyệt→Đã duyệt (terminal) | Trả lại (Phase riêng TraLai=98, KHÔNG revert DangSoanThao + KHÔNG jump-back) | Từ chối (terminal). Drafter từ TraLai sửa+gửi lại chạy LẠI từ Cấp 1 Bước 1 (Option A user chốt diagram). PE/Contract/Budget Phase enum +TraLai=98 + Policy + Service Reject branch trỏ → TraLai + bỏ smart-reject (RejectedAtStepIndex giữ DB column deprecated). 4 test mới TraLai entry point. **Pin V2 vào PE + Service wire** (Mig 23-24 — `0a40c65/b41484b`): PE.ApprovalWorkflowId Guid? + PE.CurrentApprovalLevelOrder int? + EF FK Restrict. CreatePurchaseEvaluationCommand+Validate ApplicableType match PE.Type. UpdateDraft cho phép sửa Phase=Nháp/TraLai. Workspace Select bắt buộc (filter ApplicableType=type). Service `ApproveV2Async` + `ApproveV1LegacyAsync` branch theo ApprovalWorkflowId set or null: V2 group Levels by Order = Cấp (OR-of-N approvers cùng cấp), match `actor.Id ∈ ApproverUserId`, advance levelOrder++ trong Step → idx++ + reset levelOrder=1 → DaDuyet. Synthetic Policy `ForV2Schema()` cho FE nextPhases (DangSoanThao/TraLai → ChoDuyet/TuChoi; ChoDuyet → ChoDuyet/TraLai/TuChoi). **UX V2-aware** (`d814429/9e63e2d/d250ae4/74745a7/de0f38d`): DTO `CurrentApproval { stepIdx, levelOrder, approvers[] }` + `ApprovalFlow { steps[]: { Order, Name, Dept, Levels[]: { Order, Approvers[], 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 V2 + actor không trong cấp + tooltip. Trả lại + Từ chối vẫn enabled (BE không gating reject theo cấp). Inbox V2-aware (`ResolveV2InboxIdsAsync` precompute Set IDs khớp actor.Id ∈ Cấp hiện tại). 2 dropdown filter "Quy trình duyệt" + "Trạng thái" (chỉ ở Duyệt sau user feedback, Danh sách giữ 1 dropdown trạng thái). Panel 3 thay 4 phase cards bằng flow workflow thực tế: Bước (icon ✓/●/○ + dept badge) → Cấp (icon nhỏ + label "đang chờ" / "đã duyệt" + tên NV). Phiếu V1 legacy fallback note. **Test setup** (`ac41d5e`): SQL `clean-transactional-uat.sql` xóa 9 PE + 11 HĐ + Budget + 19 Notif + reset CodeSequences trên prod, giữ master (Users/Suppliers/Projects/Departments/Workflows V1+V2). Tạo test user `nv.test@solutions.com.vn`/`TestUser@123456` (Drafter, Phòng CCM) qua API. **77→81 test pass** (+4 TraLai entry point Domain). FE rename "Bản nháp" → "Nháp" + ChoDuyet=10 + TraLai=98 thêm vào types/contracts.ts + types/budget.ts. **Pending session sau:** Contract V2 wire (mirror PE pattern), Budget V2 (defer xa hơn), phân quyền strict V2 (hiện loose UAT cho mọi authenticated user xem phiếu V2), drop legacy V1 sau khi UAT chốt + cleanup migration drop RejectedAtStepIndex/RejectedFromPhase. | 13 commit (xem `git log --since='2026-05-08'`) |
| 2026-05-08 | Claude | **🎯 SESSION 16 — DRASTIC REFACTOR flat workflow Phòng × Cấp (Mig 21, 2 commit Chunk A+B)** — Resume từ Session 15 defer plan. User chốt "bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking". Per memory `feedback_drastic_refactor_scope`: dedicated session với context fresh, scope conservative 2x buffer (~8-10h estimate, actual ~3h). **Chunk A (`dbb0089`)** — Domain enum simplify (DangSoanThao=1, ChoDuyet=10 NEW, DaDuyet=7, TuChoi=99; legacy 2-6 + 98 deprecated giữ cho data cũ). WorkflowStep + DepartmentId Guid? FK Restrict + PositionLevel int? (PE + Contract mirror). PE/Contract entity + CurrentWorkflowStepIndex int? + RejectedAtStepIndex int?. Drop class WorkflowStepInnerStep + nav (PE + Contract). Drop *DepartmentApproval.InnerStepId column. EF Configurations: drop InnerStep config + restore simple unique non-filtered (Mig 19/20 filtered split reverse). DbContext drop DbSet<*WorkflowStepInnerStep> × 2. **Migration 21** `RefactorWorkflowToFlatModel` GỘP: 4 ALTER cols (PE/Contract CurrentStepIndex+RejectedAtStepIndex) + 2 ALTER (WorkflowStep DeptId+PositionLevel) + DROP TABLE x 2 (PEWorkflowStepInnerSteps + WorkflowStepInnerSteps Mig 18+20) + DROP InnerStepId column x 2 (PE+Contract DeptApproval) + DROP filtered indexes x 2 + restore simple unique x 2. PE + Contract Service rewrite TransitionAsync: phase transitions DangSoanThao→ChoDuyet (Drafter trình init idx=0) / ChoDuyet→ChoDuyet (advance idx) / ChoDuyet→DaDuyet/DaPhatHanh (last step done) / ChoDuyet→DangSoanThao (Trả lại save RejectedAtStepIndex) / ChoDuyet→TuChoi (Từ chối khoá vĩnh viễn). Match approver: actor.Dept==step.Dept AND actor.PositionLevel>=step.PositionLevel (OR cùng cấp/dept) OR Approvers.Kind=User match OR Kind=Role match. Admin role bypass policy. Last step done → gen mã HĐ (Contract only). App CQRS WorkflowStepDto + WorkflowStepInput drop InnerStep, add DepartmentId/DepartmentName/PositionLevel (PE + Contract mirror). Tests rewrite: DROP `PeNStageApprovalTests.cs` (6) + `ContractNStageApprovalTests.cs` (6) + `PeTwoStageApprovalTests.cs` (7) — legacy N-stage/2-stage no longer applicable. UPDATE `PeWorkflowAdminTests` signature. **96 → 77 test pass** (-19 legacy). 3-file rule Mig 21 (.cs + Designer + Snapshot) commit đủ. **Chunk B (`88a5be1`)** — FE-Admin Designer rewrite (PeWorkflowsPage + WorkflowsPage): drop InnerStepDto + EditInnerStep types, drop PHASE_OPTIONS auto-assign ChoDuyet=10, StepDto + EditStep + departmentId/positionLevel, copyFromDefinition simplified, Designer step UI rewrite (Tên + Phòng Select + Cấp Select + SLA + Approvers Role/User optional fallback, drop entire InnerSteps sub-section), DefinitionCard view hiển thị badge Phòng emerald + Cấp NV/PP/TP violet, save payload phase=10. types/purchaseEvaluation.ts (fe-admin + fe-user mirror) + ChoDuyet=10 enum + label "Đang duyệt" + color amber. **Chunk C (FE PeWorkflowPanel) SKIP** — existing UI compatible (workflow.nextPhases driven by BE simplified policy), reuse 3-button Trả lại/Từ chối logic Session 14 hoạt động trên ChoDuyet phase tự động. **KHÔNG đụng** Service Notify pattern + Changelog pattern (giữ hành vi Mig 16). Verify: dotnet build pass + Mig 21 LocalDB applied + 77 test pass + npm build × 2 pass. Memory `feedback_drastic_refactor_scope.md` validated: dedicated session approach hoạt động đúng dự đoán. | `dbb0089` (A) · `88a5be1` (B) |
| 2026-05-07 | Claude | **🎯 SESSION 15 — Tooltip diagnose "Lưu & Gửi Duyệt" + Plan drastic refactor flat workflow → DEFER** — User UAT live screenshot phiếu PE Bản nháp + báo "Lưu & Gửi Duyệt" KHÔNG hoạt động + suy đoán "trùng ID với phiếu khác". Chẩn đoán: button silent disabled khi `evaluation.workflow.nextPhases` không có forward phase (chỉ TuChoi/TraLai). FE chưa có visual feedback → user không biết. Improvement (commit `835cc7f`): compute `forwardPhase` once + add `submitDisabledReason` string giải thích reason (canEditPhase=false / readOnly / !forwardPhase với hint admin kiểm tra cấu hình quy trình) + button title attribute show reason hover hoặc forward phase label khi enabled + Dialog confirm show forward phase explicit "Sẽ chuyển sang Chờ Purchasing". Mirror fe-admin + fe-user. Build pass cả 2. **"Trùng ID" KHÔNG phải bug FE** — `PurchaseEvaluationWorkspacePage` URL state đúng (`+ Thêm mới` clear `id`, save set new), mỗi PE row unique GUID + MaPhieu. **Tiếp theo plan drastic refactor**: User chốt "bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking" + workflow flat list (Phòng × Cấp × Users[]) thay InnerStep model. Surface 6 chunk plan + start Chunk A: edit Domain entities (Phase enum +ChoDuyet=10, WorkflowStep +DeptId/PositionLevel, drop InnerStep class+nav, PE/Contract +CurrentWorkflowStepIndex/RejectedAtStepIndex, *DeptApproval drop InnerStepId) + EF Configurations (drop InnerStep config + nav, restore simple unique non-filtered) + DbContext drop DbSets — 12 files trong working tree. Realize scope realistic ~8-10h (PolicyRegistry rewrite + 2 Service rewrite + App CQRS + 12 tests rewrite + Designer FE + Migration 21 + Docs) vượt session boundary + risk session context deep ~30 commits. **REVERT working tree** về `835cc7f` clean. Add memory `feedback_drastic_refactor_scope` decision rule: drastic refactor cần dedicated session, ước tính conservative (2x buffer), tránh mid-session big refactor. **Stats unchanged**: 96 test pass, 20 mig, 57 bảng. | `835cc7f` |
| 2026-05-07 | Claude | **🎯 SESSION 14 — PE 3-button workflow Duyệt/Trả lại/Từ chối + Task 2 sample seed in-progress** — User chỉ thị thay 2-button approval bằng 3 hành động rõ ràng cho approver: **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, Drafter phải tạo phiếu mới). 1 commit (`0d77698`): Domain `PurchaseEvaluationPolicy.cs` NccOnly + NccWithPlan thêm `(X → TuChoi)` transition cho mọi phase trung gian (ChoPurchasing/ChoCCM/ChoDuAn/ChoCEODuyetPA/ChoCEODuyetNCC) với roles của phase. FromDefinition expand: mỗi step (trừ DangSoanThao) thêm (step.Phase → TuChoi) với roles step. Service `PurchaseEvaluationWorkflowService.TransitionAsync` — Reject branch tách 2 case: target=TuChoi giữ nguyên (KHÔNG override + KHÔNG set RejectedFromPhase + KHÔNG clear N-stage); target khác (DangSoanThao) → smart reject (force DangSoanThao + RejectedFromPhase + clear N-stage). FE PeWorkflowPanel (admin + user mirror): render 3 button rõ ràng "✓ Duyệt → X" brand / "← Trả lại (về Drafter sửa)" red / "✗ Hủy / Từ chối" red. Decision logic: target=TuChoi || isSendBack → Reject (2), else Approve (1). Dialog confirm: title rõ + Cancel case warning red "phiếu sẽ bị khoá hoàn toàn" + SendBack case hint amber "Phiếu về DangSoanThao, Drafter sửa rồi trình lại — workflow tự jump tới phase này". Tests: rename `Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThao` → `Reject_To_DangSoanThao_Sets_RejectedFromPhase_TraLai` (target từ TuChoi → DangSoanThao). NEW `Reject_To_TuChoi_Locks_Permanently_No_RejectedFromPhase`. Update `NStage_Reject_Clears_InnerStep_Rows_At_Phase` target → DangSoanThao. **95 → 96 test pass** (+1 Từ chối). **Task 2 sample seed in-progress**: dotnet ef database update applied Mig 9-20 lên LocalDB SolutionErp_Dev (trước đó chỉ Mig 1-8 vì DesignTimeDbContextFactory hardcoded SolutionErp_Design ≠ runtime SolutionErp_Dev — gotcha tooling distinction). API start để DbInitializer auto-seed 30 demo user nhưng exit 255 sớm khi log buffer full → seed dở dang. Defer Task 2 cho session sau (cần guidance: seed manual SQL hoặc manual API run + sample N-stage workflow def + Update PositionLevel cho 30 users existing). | `0d77698` |
| 2026-05-07 | Claude | **🎯 SESSION 13 — Mirror N-stage workflow sang Contract (Mig 20, 5 commit per-chunk + skip Chunk E API)** — User chỉ thị mirror N-stage từ PE sang Contract. Budget defer (cần versioned WF migration trước, hardcoded BudgetPolicy hiện tại chưa có WorkflowDefinition). 5 chunk per-commit (build + ef + test pass mỗi chunk): **Chunk A (`951ffa3`)** Domain entity `WorkflowStepInnerStep` (Domain/Contracts/) + nav WorkflowStep.InnerSteps + ALTER ContractDepartmentApproval.InnerStepId Guid? + 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` + 3 IX + 3 FK). **Chunk B (`04cf2a0`)** Application CQRS DTO — `WorkflowStepInnerStepDto` + extend `WorkflowStepDto` + GetWorkflowAdminOverview Include InnerSteps + DeptNames map + `CreateWorkflowStepInnerStepInput` + `CreateWorkflowStepInput` extend (default null backward compat) + Validator child rules + Handler atomic batch insert. **Chunk C (`e247b67`)** ContractWorkflowService refactor mirror PE — load definition InnerSteps eager, reject branch clear N-stage rows tại fromPhase, dept approval block split hasInnerSteps→N-stage logic / else→legacy 2-stage. N-stage flow giống PE: yêu cầu actor có DeptId+PositionLevel, match firstPending Order asc + (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)". **Chunk D (`7c0772a`)** Tests 6 N-stage Contract mirror PE pattern + helper SeedWorkflowDefinitionAsync 2 step adjacent (DangGopY + DangDamPhan) + SeedContractAsync với Project + Supplier seed + FakeChangelogService + FakeContractCodeGenerator stubs. Bug fix: legacy fallback test ban đầu fail (Standard policy DangGopY → DangDamPhan chỉ cho [Drafter, DeptManager], không Procurement) → switched phase pair sang DangKiemTraCCM → DangTrinhKy + role CostControl khớp. Total 89 → **95 test pass**. **Chunk E SKIP** — WorkflowsController auto-bind `[FromBody] CreateWorkflowDefinitionCommand` record qua JSON, no code change cần. **Chunk F (current)** FE-Admin types/users.ts đã có PositionLevel const từ Session 12 reuse. 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 { Phòng × Cấp + required } + 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). Docs/Skill update. **Backward compat 100%**: workflow Contract no InnerSteps → fallback legacy 2-stage Mig 16. Data legacy InnerStepId=null vẫn enforce unique cũ qua filtered index. Defer Budget mirror cho session sau (cần migration `AddBudgetVersionedWorkflow` trước). | `951ffa3` (A) · `04cf2a0` (B) · `e247b67` (C) · `7c0772a` (D) · (current F) |
| 2026-05-07 | Claude | **🎯 SESSION 12 — N-stage workflow approval Phòng × PositionLevel cấu hình động (PE-only first, 6 commit per-chunk + Mig 18+19)** — User yêu cầu mở rộng từ 2-stage Mig 16 (NV.Review/TPB.Confirm) sang N-stage cấu hình động: mỗi WorkflowStep cha (= 1 phase) có thể cấu hình chuỗi InnerSteps con theo Department × PositionLevel với Order sequential. Spec defaults chốt 6 câu (PositionLevel int 1=NV/2=PP/3=TP, sequential pure, bypass cùng dept TP skip NV+PP, smart reject reset N-stage rows về DangSoanThao, PE-only first, designer 1 sub-section InnerSteps). 6 chunk per-commit (build + ef + test pass mỗi chunk per `feedback_per_chunk_commit.md`): **Chunk A (`13ab533`)** Domain enum `PositionLevel` (NV/PP/TP) + entity `PurchaseEvaluationWorkflowStepInnerStep` + ALTER User.PositionLevel int? + ALTER PEDeptApprovals.InnerStepId Guid? + EF config + **Migration 18** `AddPeWorkflowInnerStepsAndPositionLevel` (1 CREATE TABLE + 2 ALTER + 3 index + FK Cascade Step / Restrict Dept/InnerStep). 3-file rule. **Chunk B (`0e56bd0`)** Application CQRS DTO — `PeWorkflowStepInnerStepDto` + extend `PeWorkflowStepDto` + `CreatePeWorkflowStepInnerStepInput` (default null backward compat existing PeWorkflowAdminTests) + Validator child rules + Handler atomic batch insert + UserDto +PositionLevel field + `SetUserPositionLevelCommand` mirror SetBypassReview. **Chunk C (`0c62e24`)** Service N-stage logic — **Migration 19** `AlterPeDeptApprovalsUniqueFilteredForInnerSteps` (filtered unique: legacy `WHERE InnerStepId IS NULL` + N-stage `WHERE InnerStepId IS NOT NULL`) cho phép multi-row cùng dept khác inner step. PurchaseEvaluationWorkflowService refactor: load definition InnerSteps eager + reject branch clear N-stage rows + dept block split hasInnerSteps→N-stage logic / else→legacy 2-stage. N-stage flow: yêu cầu actor có DeptId+PositionLevel, match firstPending (Order asc IsRequired) same dept + (exact level OR canBypass + level≥), exact match upsert 1 row InnerStepId, bypass batch upsert NV+PP+TP cùng dept ≤ actor level (audit IsBypassed cho cấp dưới skip), recheck stillPending → BLOCK + log "duyệt cấp X (còn Y pending)" / all done → fall through phase transition. Backward compat: workflow no InnerSteps fallback legacy + InnerStepId=null filter unique cũ vẫn enforce. **Chunk D (`3d76c6b`)** Tests N-stage 6 test mới (NV first blocks / 3-level sequential pass / TP bypass skips / wrong dept throws 403 / reject clears rows / legacy fallback no inner) + IdentityFixture extend `+positionLevel` arg + helper SeedWorkflowDefinitionAsync 2 step adjacent (ChoPurchasing+ChoCCM) cho FromDefinition build transition policy guard pass. Total 83→**89 test pass**. **Chunk E (`83ffabd`)** API `PATCH /users/{id}/position-level` mirror SetBypassReview pattern + body `{positionLevel:int?}` Authorize Users.Update. **Chunk F (current)** FE-Admin types/users.ts +positionLevel field + PositionLevel const + Label/Short maps. PeWorkflowsPage Designer extend: InnerStepDto type + EditInnerStep type + copyFromDefinition include + departmentsList query + sub-section "Cấp duyệt nhỏ trong phòng" per step card với drag-drop list { Phòng × Cấp + required checkbox } + button "+ Thêm cấp duyệt" (xanh emerald) + payload include innerSteps Order asc. UsersPage column "Cấp" badge NV/PP/TP emerald + action button cycle null→1→2→3→null call positionLevelMut PATCH. KHÔNG đụng fe-user (admin-only feature). Docs/Skill update. PE-only first. Backward compat 100%: workflow no InnerSteps + data legacy 2-stage rows không phá. | `13ab533` (A) · `0e56bd0` (B) · `0c62e24` (C) · `3d76c6b` (D) · `83ffabd` (E) · (current F) |
| 2026-05-08 00:30 | Claude | **🎯 SESSION PHASE 2 WRAP-UP — B12-B14 PE detail polish iterate (3 commit FE-only sau wrap-up `6e7a6db`)** — User UAT iteration tiếp, áp rule strict verify khi rename/remove (lesson hotfix CI). 3 batch nhỏ: **B12 (`378c993`)** "Lưu" no-close (chỉ toast + invalidate, KHÔNG đóng workspace) + nút "Xóa phiếu" red bottom CHỈ Bản nháp (soft-delete `IsDeleted=true` qua AuditableEntity, không xóa hoàn toàn DB) + bỏ header bar workspace mode "Sửa header"/"Xóa"/"Đóng" (chuyển hết xuống bottom action bar) + Section 4 column header dùng `s.supplierName` thay `displayName` (NCC master) + Section 3 row chặn xóa NCC khi đã có quotes (`hasQuotes` computed) + tooltip "xóa báo giá trước". **B13 (`e320027`)** InfoTab `useEffect` watch `[autoEdit, canEdit, ev.id, ...]` → re-trigger edit mode khi pencil click phiếu khác (fix useState mount-time only) + sync values từ ev mới (tránh stale state) + Pencil "sáng lên" active state khi `editingRowId === p.id` (bg-brand-100 + text-brand-700 + ring-brand-300 + shadow-sm + tooltip cập nhật) + wire `editingRowId={autoEditHeader ? selectedId : null}` từ Workspace → PeListPanel. **B14 (`d2306b8`)** QuoteDialog bỏ checkbox "Chọn NCC này cho hạng mục" (consolidate winner ở Section 2.a NccSelectorRow, isSelected vẫn gửi BE giữ trạng thái cũ) + winner column Section 4 matrix highlight emerald (header `bg-emerald-50` + `✓ ` prefix + cells `bg-emerald-50 font-semibold` cho ENTIRE column, không chỉ cell có quote) + loading overlay full-screen QuoteDialog (`bg-white/70 backdrop-blur-sm` + spinner ring brand-600 + text "Đang lưu báo giá…"/"Đang xóa…") + NccSelectorRow inline spinner "Đang chọn NCC + sync cột giá Section 4…" + disable Hủy/Xóa/Lưu buttons khi `isSaving`. Verify: `npm run build` × 2 app pass mỗi commit · `dotnet test` 83 pass (KHÔNG regression). | `378c993` (B12) · `e320027` (B13) · `d2306b8` (B14) |
| 2026-05-07 | Claude | **🎯 SESSION WRAP-UP S10-11+++++++ — PE Workspace UX overhaul đầy đủ (23 commit / ~3500 LOC FE + Mig 17 BE)** — User UAT live mode iterate liên tục, áp rule `feedback_uat_skip_verify` (skip dotnet test sau mỗi chunk, push ngay). 7 batch chính: **B1 (S10) PE Thao tác 2-panel workspace** — leaf `Pe_*_Create` từ page Create header riêng → workspace 2-panel `[320px_1fr]` mirror HĐ Thầu phụ; PeListPanel pure picker + sticky "+ Thêm mới"; PeDetailTabs `mode='workspace'` ẩn Workflow/Approvals/History + Section 5 disabled "nhập khi duyệt" (4 commit). **B2 (S11) Migration 17** `AddManualBudgetFieldsToPeAndContract` — 4 ALTER (PE + HĐ × `BudgetManualName` nvarchar(200) + `BudgetManualAmount` decimal(18,2)) cho fallback "user nhập tay khi không link Budget entity approved". Domain + EF config + App CQRS Create/Update + DTO + Validator + carry-forward `CreateContractFromEvaluation`. FE toggle "Nhập tay" trong PeHeaderForm + ContractCreatePage NewForm/EditForm × 2 app (5 commit). **B3 (S11+) BudgetFieldRow inline editor** — Section 2 "b. Ngân sách" thay FormRow tĩnh → editable component (toggle + Select OR 2 input + Save dirty + Hủy). canEdit cho cả 3 view (Workspace/Danh sách/Duyệt mode), readOnly chỉ display (3 commit). **B4 (S11++) InfoTab inline edit + PeListPanel pencil hover** — Section 1 "✎ Sửa" button flip display↔inputs (Tên/Địa điểm/Mô tả/Payment editable, Dự án locked). PeListPanel thêm pencil icon group-hover absolute right + URL `?editHeader=1` chain → `autoEditHeader` prop trigger mount-time edit (3 commit). **B5 (S11+++) Workspace "new" sectioned create view** — `PeWorkspaceCreateView.tsx` ~230 LOC layout 5 sections giống PeDetailTabs visual. S1 + S2.b editable, S3-5 LockedHint "Lưu phiếu trước". POST trigger create. Replace `PeHeaderForm` trong workspace mode='new' (1 commit). **B6 (S11++++) Danh sách disable toàn bộ interactions** — PurchaseEvaluationsListPage `readOnly={true}` hardcoded cho PeDetailTabs + `readOnly={!pendingMe}` cho PeWorkflowPanel (List view → ẩn Chuyển tiếp + show hint "Vào menu Duyệt"; Pending vẫn approve được) (2 commit). **B7 (S11+++++) Lock Loại quy trình + payment preset** — workspace `<Select>` Loại quy trình → `<Input disabled>` theo URL `?type=N`. `<Textarea>` JSON Điều khoản TT → `<Select>` 8 preset Việt + "Khác (nhập tay)" → text input fallback (1 commit). **B8 (S11++++++) Display status meta** — `PeDisplayStatus` enum 4-5 trạng thái UI (Bản nháp / Đã gửi duyệt / Trả lại / Đã duyệt / Từ chối) gom phase chi tiết. `getPeDisplayStatus()` helper. Workflow timeline Panel 3 vẫn giữ phase chi tiết (1 commit). **B9 (S11+++++++) Phase TraLai + pencil always visible + edit gating** — Domain `PurchaseEvaluationPhase` thêm `TraLai = 98` (giữa DaDuyet=7 + TuChoi=99). FE label/color + display status badge. `isEditablePhase()` helper: chỉ DangSoanThao + TraLai. PeListPanel pencil bỏ hover-only → LUÔN visible (bright khi editable / xám disabled khi không) + click guard. Workspace `editableOnly` filter client-side (1 commit). **B10 (hotfix CI)** — TS strict catch fail 2 commit B7+B8 do skip-verify (`forcedPhase` rename quên xóa destructuring args + unused `PurchaseEvaluationType` import). Update memory `feedback_uat_skip_verify.md` thêm exception "rename/remove → BẮT BUỘC `npm run build`" (1 commit). **B11 (last) PE detail polish** — `NccSelectorRow` Section 2.a thay FormRow tĩnh → Select dropdown từ ev.suppliers (Section 3 list) wire `/select-winner` API. Section 2.c text rõ "(chọn NCC trước)" / "(chưa nhập báo giá)". Section 3 row khi `isWinner` → ẩn ✏ + 🗑 (chỉ giữ ✓ active). Bottom action bar workspace mode: **2 nút "Lưu (đóng)" + "Lưu & Gửi Duyệt →"** confirm dialog → POST `/transitions` với `targetPhase = first nextPhase` skip TuChoi/TraLai → workflow chuyển từ Bản nháp/Trả lại → Đã gửi duyệt (ChoPurchasing) → onBack đóng workspace (1 commit). | `ee0d360` (S10 C1) → `4c0625c` (B11) — 23 commit total |
| 2026-05-07 | Claude | **PE InfoTab inline edit Section 1 + PeListPanel pencil edit hover** — User feedback 2026-05-07: muốn thêm nút edit kế bên row trong Panel 1, click sáng nội dung Section 1 lên cho user sửa header inline (KHÔNG cần đi "Sửa header" page). 2 chunk per-commit (build pass mỗi chunk): C1 fe-admin (3 file) — InfoTab thêm prop `readOnly + autoEdit`, canEdit=`!readOnly && isDraft`: display mode hiển thị FormRow + button "✎ Sửa" góc trên phải, editing mode card border brand-200 + 4 input (Tên */Dự án locked/Địa điểm/Mô tả/Payment) + Save (PUT /pe/:id full payload + invalidate detail+list)/Hủy. PeListPanel thêm prop `onEditClick`, pencil icon absolute right-2 top-2 mỗi row, opacity-0 group-hover:opacity-100. PurchaseEvaluationWorkspacePage đọc URL `?editHeader=1` → pass `autoEditHeader` xuống PeDetailTabs → trigger edit auto. C2 fe-user mirror y hệt 3 file (rule §3.9). KHÔNG đụng BE. KHÔNG refactor workspace "new" mode (defer — PeHeaderForm hiện tại đủ dùng, làm thêm khi user feedback). | `5a89dd2` (C1) · (current C2) · (current C3 docs) |
| 2026-05-07 | Claude | **PE BudgetFieldRow inline editor — toggle + 2 fields trong Section 2 (cả 3 view)** — User feedback after Session 11: muốn toggle "Nhập tay" + 2 input fields hiển thị trực tiếp trong Section 2 "b. Ngân sách" của PeDetailTabs — KHÔNG chỉ ở "Sửa header" page, mà cả 3 view (Workspace / Danh sách / Duyệt). Empty values cứ hiển thị empty (không text "(chưa link)" verbose). 2 chunk mirror per-commit (build pass mỗi chunk): C1 fe-admin BudgetFieldRow component (~125 LOC) thay FormRow tĩnh ở `b. Ngân sách`. canEdit=`!readOnly && isDraft`: render toggle + Select Budget OR 2 input grid 2-col + nút "Lưu ngân sách" (chỉ hiện khi dirty) + "Hủy thay đổi". Save: full PUT `/pe/:id` với current entity values + new budget payload. Read-only mode (Duyệt + !isDraft): chỉ display, KHÔNG toggle/inputs, empty hiển thị "—". C2 fe-user mirror y hệt 1 file (rule §3.9). Mỗi chunk: build pass + 0 TS error. KHÔNG đụng BE — re-use existing PUT endpoint. | `19712d8` (C1) · `d5c6f12` (C2) · (current C3) |
| 2026-05-07 | Claude | **Migration 17 — manual budget fields fallback cho PE + HĐ (toggle "Nhập tay")** — User feedback: khi project chưa có Budget approved (Phase=DaDuyet eligible), user phải break flow đi tạo Budget + duyệt + quay lại link. UX kém. Solution: thêm fallback "Nhập tay" — checkbox toggle cạnh Label Ngân sách, khi ON → hide Select Budget, show 2 input field grid 2-col (Tên tham chiếu text + Số tiền number formatted VND). Lưu trên entity row, KHÔNG cần Budget entity. Q1-3 chốt: 1 = stick-toggle reveal 2 input fields; 2 = cả BudgetId + manual fields cùng null OK (KHÔNG XOR validate); 3 = mirror logic sang HĐ luôn (cả 7 ContractType qua ContractCreatePage). 5 chunk per-commit (build + 83 test pass mỗi chunk): C1 Migration 17 `AddManualBudgetFieldsToPeAndContract` 4 ALTER (PE + HĐ × Name nvarchar(200) + Amount decimal(18,2)) + Domain 2 entity + 2 EF config (HasMaxLength + HasPrecision) — applied LocalDB OK, 3-file rule. C2 App CQRS — CreatePurchaseEvaluationCommand + Update + DTO + Validator (>=0 when has value), CreateContractCommand + Update + DTO + diff log audit, CreateContractFromEvaluation carry forward pe.BudgetManualName/Amount → contract khi gen HĐ từ phiếu. C3 FE-Admin — types +2 field, PeHeaderForm toggle + 2 input + payload conditional (manual mode clear budgetId, link mode clear manual), PeDetailTabs Section "b. Ngân sách" fallback display "Tên · Số tiền + badge nhập tay" khi !budget + có manual data, refactor PurchaseEvaluationCreatePage wrap PeHeaderForm DRY (222→30 LOC), ContractCreatePage NewContractForm + EditContractForm cùng pattern + read-only display branch khi !isDraft. C4 fe-user mirror y hệt 6 file. C5 docs (this row + HANDOFF + session log). KHÔNG đụng Budget entity / Phase=DaDuyet validation (giữ invariant). | `ecd5f7e` (C1) · `0f7901c` (C2) · `bab5031` (C3) · `14f8d9d` (C4) · (current C5) |
| 2026-05-07 | Claude | **PE "Thao tác" 2-panel workspace + Panel 1 read-only picker + Section 5 disabled** — User chỉ thị restructure leaf "Thao tác" (Pe_DuyetNcc_Create + Pe_DuyetNccPhuongAn_Create) từ page tạo header riêng (`/purchase-evaluations/new?type=N` — chỉ form Tên/Project/Địa điểm/Payment/Budget) sang workspace 2-panel mirror pattern HĐ Thầu phụ ContractCreatePage. 5 câu chốt spec trước code: Q1 Panel 2 KHÔNG render Workflow Panel + Approvals + History (chỉ data entry); Panel 1 = pure picker, KHÔNG inline edit/delete; Q2 mirror HĐ Thầu phụ pattern (sticky "+ Thêm mới" + Panel 2 transition new→edit form); Q3 leaf "Danh sách" + "Duyệt" giữ 3-panel hiện tại; Q4 route mới `/purchase-evaluations/workspace?type={1\|2}`; Q5 Section 5 Ý kiến 4PB disable trong workspace (vì người ta nhập khi duyệt, không phải lúc nhập liệu). 2 chunk per-commit (build + 83 test pass mỗi chunk): C1 fe-admin (3 file mới `PeListPanel.tsx` ~180 LOC pure picker reuse + `PeHeaderForm.tsx` ~210 LOC extract + `PurchaseEvaluationWorkspacePage.tsx` ~120 LOC, 3 file sửa `PeDetailTabs.tsx` thêm prop `mode?: 'detail' \| 'workspace'` + Section 5 hint banner amber + Layout.tsx resolver `Pe_*_Create`→`/workspace?type=N` + App.tsx route mới). C2 fe-user mirror y hệt 6 file (rule §3.9). KHÔNG đụng BE / migration / schema / endpoint. Route `/new` cũ giữ tồn tại cho deep-link "Sửa header" button. **Total +1142 LOC FE / 0 BE / 32 FE pages.** | `ee0d360` (C1) · `ecf3c59` (C2) · (current C3) |
| 2026-05-04 | Claude | **User Manual 7 file rewrite compact cho end-user** — User feedback "ko cần quá đầy đủ chi tiết, cho end-user họ làm". Setup `package.json` + `npm install docx@9.5.0` + script `npm run gen:all`. Rewrite 7 generator scripts theo style end-user friendly: BỎ field validation table 5 cột (Tên field/Kiểu/Bắt buộc/Validation/Ví dụ), BỎ error troubleshoot table 3 cột (Lỗi/Nguyên nhân/Cách xử lý), BỎ FAQ chi tiết 8 câu (giữ 1 chương "Khi gặp lỗi" 4-5 bullet), BỎ phím tắt table. GIỮ: tổng quan ngắn 1-2 câu mỗi chức năng, numbered steps đơn giản 3-7 bước, note/warn/tip chỉ critical, bullet liệt kê. 7 file: 01-Bat-dau (12.1KB cũ 21.7KB ↓44%) / 02-Hop-dong / 03-Duyet-Workflow (mention 2-stage NV/TPB Mig 16) / 04-PE-Phieu-Duyet-NCC (A/B + 4PB + tạo HĐ) / 05-Budget / 06-7-Loai-HD-Cheatsheet / admin-02-Quan-ly-Users-Roles (mention bypass review S9). Refactor user-01 dùng `_helpers.js` shared (trước có helpers inline 793 dòng, giờ ~110 dòng). Tổng output ~86KB / 7 file (cũ ~123KB ↓30%). | `16c2c9c` |
| 2026-05-04 | Claude | **Optional polish — fe-user Inbox thêm section "Phiếu Duyệt NCC chờ tôi"** — User chỉ thị "tiếp tục plan tổng" → pick task không blocked. useQuery thứ 2 cho `/purchase-evaluations/inbox` (endpoint có sẵn), peRows filter theo search. Stats overdue/dueSoon đếm cả PE rows (totalValue chỉ HĐ vì PE không có giá trị). Panel 1 chia 2 section sticky header: "Hợp đồng (N)" giữ behavior cũ click → inline detail Panel 2; "Phiếu Duyệt NCC (M)" click → navigate `/purchase-evaluations/:id` page riêng (PE entity shape khác Contract, không inline). EmptyState mới: "Không có HĐ hoặc Phiếu Duyệt NCC nào chờ". Chỉ fe-user (Drafter + TPB dùng Inbox) — fe-admin defer. Build pass. | `332a90f` |
| 2026-05-04 | Claude | **Audit định kỳ 2026-05 (combined skill + doc drift theo §6.4 + §9.4)** — Cron `solution-erp-skill-audit-monthly` empty (`No scheduled jobs`), trễ 4 ngày so schedule 2026-05-01 → manual trigger sau Session 9 close. Phase 1 cross-check counts: tests=83, migrations=16, gotchas=41, skills=6, STATUS rows=12 (<30 chưa archive) — toàn bộ khớp. Drift patches 5 file: docs/CLAUDE.md (52→55 bảng + §14 DepartmentApprovals), docs/rules.md (Phase 8→9, 77→83 test), docs/architecture.md (77→83 test 2 chỗ), .claude/skills/ef-core-migration (77→83 + ghi 6 PE 2-stage), .claude/skills/dependency-audit-erp (26+→41 bẫy). Phase 2 skill staleness: contract-workflow thêm "Phase 9 cross-ref Mig 16" block + section "Phase 9 done" (2-stage + smart reject + lock edit + bypass). KHÔNG tạo skill mới (§9.5 anti-pattern "viết skill chỉ để có thêm" — pattern 2-stage đủ generic, đã ghi đủ Domain/Service/6 test reusable). Cron recreate SKIP (CronCreate Claude SDK auto-expire 7 days, không fit monthly cadence). Audit log `docs/changelog/skill-audit-2026-05.md` (1 page). 6 file ~+30/~12 line patch, validation §6.5 OK. | `7dc0233` |
| 2026-05-04 | Claude | **Session 9 — Chunk E-bis complete: FE 2-stage panel + UserManager bypass toggle + HĐ/Budget 2-stage mirror PE + 6 test + IdentityFixture** — User chỉ thị "làm hết cho xong tính năng luôn". 5 chunk per-commit (build + 83 test pass mỗi chunk): (E2) FE PeWorkflowPanel section "Tiến trình duyệt 2-cấp phòng ban" group by phase × dept, highlight amber chờ TPB, badge fuchsia bypass — cả 2 app (rule §3.9). (E3) FE UsersPage column "Bypass" + ShieldCheck toggle button + UserDto.CanBypassReview field. (E4) ContractWorkflowService thêm UserManager DI + mirror 2-stage logic từ PE + `ContractDepartmentApprovalFeatures.cs` (List query) + endpoint `GET /contracts/{id}/department-approvals` + FE WorkflowHistoryPanel section. (E5) Budget mirror đầy đủ — `TransitionBudgetCommandHandler` thêm INotificationService + IDateTime DI + 2-stage logic + `BudgetDepartmentApprovalFeatures.cs` + endpoint + FE BudgetWorkflowPanel. (E6) `IdentityFixture` setup ServiceProvider với Identity stack đầy đủ (DbContext SQLite + AddIdentityCore + AddRoles<Role> + EF stores) + 6 test PE 2-stage: NV_Review_Blocks / TPB_Confirm_Allows / NV_Bypass / Admin_Skip / Reject_Sets_RejectedFromPhase / Resume_Jumps_Back. Tests Contract + Budget skip vì logic identical PE, ROI thấp. **Total 77→83 test pass.** 5 commit pushed lên Gitea. | `f8eebd5` (E2) · `4380bdc` (E3) · `b6f5a16` (E4) · `1fc439b` (E5) · `8353fe8` (E6) · (current E7) |
| 2026-05-04 | Claude | **Session 8 — Migration 16: 2-stage dept approval + smart reject + lock edit (đóng bug anh Kiệt)** — Anh Kiệt báo: NV.PRO tạo phiếu PE → duyệt được hết phase = phân quyền sai. Schema mới: 3 bảng `*DepartmentApprovals` (Contract/PE/Budget) UNIQUE (TargetId, Phase, Dept, Stage). 4 cột mới: `Users.CanBypassReview` bit + 3 `RejectedFromPhase` int. Logic 2-stage trong `PurchaseEvaluationWorkflowService.TransitionAsync`: user.DepartmentId != null → DeptManager (TPB) Stage=Confirm; CanBypassReview=true → Stage=Confirm+IsBypassed; else NV → Stage=Review only, BLOCK transition cho đến khi TPB confirm. Smart reject: Decision=Reject → set RejectedFromPhase, force về DangSoanThao. Resume sau reject: Drafter trình lại từ DangSoanThao + RejectedFromPhase != null → jump straight tới phase đã reject (skip phase trung gian). Lock edit: 17 handler thêm guard Phase != DangSoanThao (Contract Detail × 15, PE Detail × 5, Budget Detail × 3). 3 endpoint mới: `GET /pe/{id}/department-approvals` (FE Workflow Panel hiển thị progress) + `PATCH /users/{id}/bypass-review` (admin toggle) + Notify TPB cùng dept khi NV review. **HĐ + Budget 2-stage scope defer** (chỉ PE first đóng bug). FE update + Tests defer Chunk E-bis. | `5fe61cc` (A) · `14f3c9f` (B) · `9747f8c` (C) · `a532ba6` (D) · (current) |
| 2026-04-30 | Claude | **Session 6 — MD audit + compact + 3 skill refresh + 2 rule mới** — Compact 3 file core (-288 dòng): STATUS -27%, HANDOFF -32%, migration-todos -35%. Archive 51 row Recently Done Phase 0-7 → `changelog/recently-done-archive-2026-04.md`. Refresh 3 skill stale: `form-engine` (Phase 2 MVP → Tier 3 feature-complete + bỏ section duplicate gen mã HĐ), `permission-matrix` (12 menu → ~60 menu key + Bg_*/Pe_*/PeWf_* + inheritance roots), `ef-core-migration` (24 DbSet → 52 bảng + ERD update). Rule mới `rules.md §7 Khi nào viết test — timing rule` (5-row table compact, sau khi rút gọn từ 70 dòng overkill). Rule mới `rules.md §6.4 Audit + compact MD định kỳ` (cadence + checklist + anti-pattern, KHÔNG rewrite toàn bộ). `rules.md §9.4 Skill audit` mở rộng cross-ref §6.4. | (current) |
| 2026-04-29 | Claude | **Tests Phase 3 mini + 3 gotcha CI mới (#39 #40 #41)** — `tests/.../Application/PeWorkflowAdminTests.cs` 6 test versioning logic (CreatePeWorkflowDefinition: first version IsActive=true, second deactivates first, different EvaluationType independent, persists steps ordered + approvers per step, third version increments to v3). Total **77 test** (54 Domain + 17 Infra + 6 PE WF Application). Gotcha #39 act_runner github.com TCP timeout 21s + manual checkout fix. #40 npm junction cache fail `tsc not found` rolled back. #41 paths-ignore behavior + workflow file exclusion. | `b874743` |
| 2026-04-29 | Claude | **CI Path filter docs-only skip live** — `paths-ignore` trong on:push lookup `docs/**`/`**/*.md`/`.claude/skills/**`/`.gitignore`. Commit chỉ touch docs SKIP CI hoàn toàn (saving ~196s/commit, ~30% commit thuộc loại này). Verify `512880c` (docs-only) → Gitea NO trigger run #113. | `29eb5d9` · `a21790d` · `512880c` |
| 2026-04-29 | Claude | **CI manual checkout bypass github.com (fix #108/#109)** — Run #108/#109 fail TCP timeout 21s khi act_runner fetch `actions/checkout@v4` từ github.com. Replace `uses: actions/checkout@v4` + `actions/upload-artifact@v4` bằng manual `git init` + `git fetch` từ Gitea internal. Token `${{ github.token }}` auth tự sẵn per-job. Fetch by ref + depth=30. Run #110 pass 3m16s. | `14b7d18` · `26075c4` |
| 2026-04-29 | Claude | **Tests Phase 2 — Code generator format + sequence (SQLite in-memory)** — `tests/SolutionErp.Infrastructure.Tests/` xUnit + EF SQLite 10. `SqliteDbFixture` + `TestApplicationDbContext` subclass override `nvarchar(max) → TEXT` (SQLite không support `max`). 17 test: ContractCodeGenerator (format RG-001 5 type + Framework year scope vs Project scope + sequence per prefix + year boundary reset + persistence verify) + PurchaseEvaluationCodeGenerator (format A/B + 3-digit pad + independent A/B sequences + year boundary). CI gate +1 step. Total 71 test pass / 2.1s. | `df5988b` |
| 2026-04-29 | Claude | **Tests Phase 1 — Domain unit tests + CI gate** — `tests/SolutionErp.Domain.Tests/` xUnit 2.9 + FluentAssertions 7.2 (pin trước v8 commercial). 54 test pure function (no DB/IO): WorkflowPolicy (Standard 9-phase + SkipCcm 7-phase + Registry per ContractType + FromDefinition versioned + UserKindApprover) / PEPolicy (NccOnly 3-step + NccWithPlan 5-step + reject paths) / BudgetPolicy (Default 3-step + terminals + SLA spec). `.gitea/workflows/deploy.yml` thêm step "Run unit tests" trước build, fail → `exit $LASTEXITCODE` → no deploy. SolutionErp.slnx + folder `/tests/`. | `d3f9346` |
| 2026-04-29 | Claude | **PE Workflow designer admin UI + Ý kiến 4 phòng ban** — Migration 15 `AddPurchaseEvaluationDepartmentOpinions` (UNIQUE PEId+Kind, 1 row/phòng/phiếu). Domain `PurchaseEvaluationDepartmentOpinion` + enum `PeDepartmentKind` (PheDuyet/Ccm/MuaHang/SmPm). BE: `PeWorkflowAdminFeatures.cs` ~250 LOC mirror Contract pattern (GetOverview + Create version, deactivate cũ atomic) + `PeWorkflowsController` 2 endpoint reuse policy `Workflows.*`. `PeDepartmentOpinionFeatures.cs` Upsert (sign=true→set SignedAt+UserId, sign=false giữ chữ ký cũ) + Delete + 2 endpoint. FE: `PeWorkflowsPage.tsx` ~500 LOC + designer dialog (clone version + add/remove steps + +Role/+User approvers). Section "5. Ý kiến 4 phòng ban (sign-off)" 2x2 grid OpinionBox (read mode chữ ký vs edit textarea + 2 button Lưu/Lưu&Ký). | `5d94bb4` |
| 2026-04-29 | Claude | **PE Detail UI restructure theo spec form PHIẾU TRÌNH KÝ** — 4 section đánh số match form chính thức: "1. Thông tin gói thầu" (a/b chỉ Tên + Dự án) / "2. Chọn NCC/TP" (a NCC chọn / b Ngân sách / c Giá chào thầu auto-compute từ winner quotes / d Bản so sánh embed GeneralAttachments) / "3. NCC/TP tham gia" / "4. Hạng mục + Báo giá". FormRow helper (label 176px + value flex) thay cho dl grid 2-col cũ. | `7e36241` |
| 2026-04-29 | Claude | **PE/Contract → Budget integration + cột "So với ngân sách"** — BE: `BudgetSummaryDto` shared (PE & Contract DetailBundle), Create/Update PE+Contract commands + `BudgetId?` validate cùng Project + Phase=DaDuyet. `CreateContractFromEvaluation` carry forward pe.BudgetId → contract.BudgetId. FE: PE & Contract Create form + Select "Ngân sách" filter Phase=DaDuyet + Project match. PE InfoTab + Contract Edit display Budget link clickable. PE ItemsTab matrix + cột "NS link · Δ" — match per-row qua key `groupCode\|itemCode`, fetch /budgets/{id} riêng + footer aggregate (xanh dưới / đỏ vượt / xám khớp). | `61e5d4d` |
| 2026-04-29 | Claude | **Budget FE 3-panel pages cả 2 app** — `types/budget.ts` (BudgetPhase 5-state enum + DTO) + `BudgetsListPage` 3-panel `[340px_1fr_360px]` + filter Phase + Năm + alias `?phase=Pending` + readOnly mode menu Duyệt + `BudgetCreatePage` form Header + `BudgetDetailTabs` flat (Section Thông tin Header + Section Hạng mục table CRUD inline auto-compute ThanhTien=KL×ĐG) + `BudgetWorkflowPanel` Panel 3 timeline + dialog comment + Approvals/Changelog. Mirror fe-user. App.tsx 3 route + Layout resolver `Bg_*`. TS build pass cả 2 app. | `df12fb1` |


> **51 row Phase 0-7 (2026-04-21..28) đã archive →** [`changelog/recently-done-archive-2026-04.md`](changelog/recently-done-archive-2026-04.md)

Session logs: [P0](changelog/sessions/2026-04-21-1045-phase0-scaffold.md) · [P1f](changelog/sessions/2026-04-21-1100-phase1-foundation.md) · [P1.2](changelog/sessions/2026-04-21-1130-phase1-cruds-permission.md) · [P2](changelog/sessions/2026-04-21-1200-phase2-form-engine.md) · [P3](changelog/sessions/2026-04-21-1330-phase3-workflow.md) · [P4](changelog/sessions/2026-04-21-1430-phase4-report.md) · [P5prep](changelog/sessions/2026-04-21-1530-phase5-prep.md) · [Tier 3](changelog/sessions/2026-04-22-0300-tier3-feature-complete.md) · [Skill gov](changelog/sessions/2026-04-23-0900-skill-governance.md) · [Toolkit+4-bảng+Roles VN](changelog/sessions/2026-04-23-1500-toolkit-data-roles.md) · [Roles+Demo+Pending](changelog/sessions/2026-04-23-2200-roles-demo-pending-cleanup.md) · [PE polish iter 2 + rebrand](changelog/sessions/2026-04-24-chot-session-3-pe-polish.md) · [Budget BE + 14 Solutions users](changelog/sessions/2026-04-28-chot-session-4-budget.md) · [Budget FE + PE/HD-Budget + PE WF Designer + Tests Phase 1-2](changelog/sessions/2026-04-29-chot-session-5-budget-fe-pe-tests.md) · [Drastic refactor flat workflow Mig 21](changelog/sessions/2026-05-08-0500-drastic-refactor-flat-workflow.md) · [PE V2 schema end-to-end Mig 22-24](changelog/sessions/2026-05-08-1100-pe-workflow-v2-end-to-end.md) · [**S18 PE V2 polish + Clone B + Mig 25 IsUserSelectable**](changelog/sessions/2026-05-08-1945-s18-pe-v2-polish-clone-b.md)

**Docs entry points:**

- [`rules.md`](rules.md) · [`architecture.md`](architecture.md) · [`HANDOFF.md`](HANDOFF.md)
- [`workflow-contract.md`](workflow-contract.md) · [`forms-spec.md`](forms-spec.md)
- [`database/database-guide.md`](database/database-guide.md) · [`database/schema-diagram.md`](database/schema-diagram.md)
- [`flows/`](flows/) (7 file) · [`guides/`](guides/) (4 file) · [`gotchas.md`](gotchas.md)
- [`changelog/migration-todos.md`](changelog/migration-todos.md) · [`changelog/sessions/`](changelog/sessions/) (17 file)
- [`.claude/skills/README.md`](../.claude/skills/README.md) — 6 skill (3 domain + 3 ops) · audit định kỳ 1/tháng (cron `solution-erp-skill-audit-monthly` next 2026-05-01)

## 🎯 Next up

### Hard blockers (chờ user / ops)

- [ ] **UAT 1 tuần 2-3 user thật** — hard requirement từ roadmap Phase 5
- [ ] **Email outbox** — MailKit + SMTP (BLOCKED chờ user cấp SMTP host/user/pass)
- [ ] **Rotate credentials** — SA, vrapp, JWT secret, runner token (đã post chat)
- [ ] **SQL backup daily** — Task Scheduler (script `scripts/backup-sql.ps1` đã có, chưa schedule)

### Optional polish (khi rảnh / UAT phát sinh)

- [ ] Roles CRUD — admin tạo custom role ngoài 12 hardcoded (schema sẵn, chỉ cần CQRS + FE)
- [ ] User-level approver targeting runtime — data model đã có (`WorkflowStepApprover.Kind=User`), chỉ cần wire User-kind vào `ContractWorkflowService.TransitionAsync` guard
- [ ] PermissionsPage: grant `Workflows.Read` cho non-admin role → menu Wf_* visible
- [ ] Warning notification khi còn 20% SLA (`SlaWarningSent` flag đã có, chỉ thiếu job emit)
- [ ] E2E test reject → quay về DangSoanThao (multi-role)
- [ ] Dependencies scan CI (`dotnet list package --vulnerable`, `npm audit`)

### Tier 3 ERP roadmap ✓ (close)

- [x] Attachment upload BE + FE ✓
- [x] SignalR real-time push ✓
- [x] Form template builder CRUD + DynamicForm ✓
- [x] PDF export qua LibreOffice headless ✓
- [x] .doc/.xls → .docx/.xlsx auto-conversion ✓
- [x] Dynamic workflow policy per ContractType ✓
- [x] **Versioned workflow (WorkflowDefinition pinned per Contract)** ✓
- [x] **Admin workflow designer UI (per-type, per-step approvers)** ✓
- [x] **Nested sidebar menu per ContractType (fe-user) + menu split admin/user** ✓
- [x] **PermissionsPage 3-panel layout** ✓
- [ ] Email outbox for Notification (blocked — SMTP config)

## 📊 Thông số cumulative

| | P0 | P1f | P1.2 | P2 | P3 | P4 | P5prep | Tier3 | +Toolkit | +RolesPg+Demo | +PE module | +PE polish | +Budget+30 users | **+Session 5** |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| BE LOC | 0 | ~400 | ~1500 | ~1900 | ~2700 | ~3100 | ~3300 | ~4800 | ~7800 | ~8800 | ~11100 | ~11400 | ~11750 | **~13050** (+1300 PE WF Designer + Opinion + Budget integration) |
| DB tables | 0 | 7 | 12 | 14 | 19 | 19 | 19 | 24 | 36 | 36 | 46 | 47 | 51 | **52** (+1 PEDepartmentOpinions) |
| API endpoints | 0 | 4 | 20 | 23 | 31 | 33 | 35 | ~50 | ~80 | ~93 | ~110 | ~113 | ~124 | **~128** (+2 PE WF + 2 Opinion) |
| Migrations | 0 | 1 | 3 | 4 | 5 | 5 | 5 | 8 | 11 | 11 | 12 | 13 | 14 | **15** (`AddPEDepartmentOpinions`) |
| FE pages | 0 | 2 | 6 | 7 | 14 | 16 | 16 | ~20 | ~22 | ~23 | ~26 | ~26 | ~26 | **~31** (+5 Budget × 2 app + PeWorkflowsPage) |
| FE components | — | — | — | — | — | — | — | many | many+ | +EditRowDialog | +PE 5-tab | +Compare section | — | **+Budget tabs/panel + PE OpinionBox + PE 4-section restructure** |
| Scripts PS | 0 | 0 | 0 | 1 | 1 | 1 | 3 | 4 | 4 | 5 | 5 | 6 | 6 | 6 |
| CI/CD workflow | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | **1+test gate+path filter+manual checkout** |
| **Tests** | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | **77** (54 Domain + 17 Infra + 6 PE WF Application) |
| Docs | 10 | 13 | 14 | 24 | 26 | 30 | 35 | ~40 | ~42 | ~44 | ~46 | ~48 | ~50 | **~52** (+session log + Test plan) |
| Demo data | 0 | 0 | empty | 0 | 0 | 0 | 0 | 0 | 5+3 | 15+8+7+13+4 | +PE 4 phiếu | +rebrand email | 30 user | 30 user |
| Commits | 1 | 2 | 3 | 5 | 6 | 7 | 8 | ~25 | ~47 | ~52 | ~63 | ~70 | ~75 | **~82** (+6 session 5) |

## 🚨 Blockers / risks

- ⚠️ **Email SMTP chưa có** — blocker cho notification outbound
- ⚠️ **UAT real user chưa chạy** — risk phát sinh bug edge-case quan trọng
- ⚠️ **Credentials leaked trong chat** — cần rotate trước go-live thật
- ⚠️ **SQL backup không auto** — risk data loss nếu VPS crash
- ⚠️ **Permission `Workflows.Read` cho non-admin** — cần grant để họ thấy menu Wf_* (hiện chỉ admin thấy)
- ⚠️ **User-kind approver chưa enable runtime** — designer cho chọn User nhưng guard fall back DeptManager

## Credentials + URLs

```
admin@solutionerp.local / Admin@123456
```

- API prod: https://api.solutions.com.vn — Health `/health/live` + `/health/ready`
- API dev: http://localhost:5443 — Swagger `/swagger`
- Admin FE prod: https://admin.solutions.com.vn · dev `http://localhost:8082`
- User FE prod: https://eoffice.solutions.com.vn · dev `http://localhost:8080`
- SQL prod: `.\SQLEXPRESS` / `SolutionErp` / `vrapp`
- SQL dev: `(localdb)\MSSQLLocalDB` / `SolutionErp_Dev`
