Bro UAT confirm Plan P+Q wire OK + chỉ thị cleanup test data: > "OK Tao thấy tạm ổn rồi đấy, mày xóa hết các phiếu test cũ đi nhé, > các quy trình cũ ko ghim cũng xóa hết đi. Cho gọn đẹp." Investigator pre-flight audit prod DB ~64K spawn confirm scope: - 35 PE rows total (28 active + 7 soft-deleted) - 17 ApprovalWorkflowsV2 (15 IsUserSelectable=false + 2 ghim+active) - 4 PurchaseEvaluationWorkflowDefinitions V1 (2 IsActive=false + 2 active) Critical gotchas: 1. PE.ApprovalWorkflowId FK Restrict → soft-delete KHÔNG release FK, hard-DELETE PE first 2. ApprovalWorkflow extend BaseEntity (NO soft-delete) → hard DELETE only 3. Filtered indexes Mig 29+ require SET QUOTED_IDENTIFIER ON 4. SQL Express constraints: NO BACKUP COMPRESSION + RESTORE VERIFYONLY require sysadmin (vrapp KHÔNG có) Execute via scripts/plan-r-*.sql upload scp + sqlcmd -i workflow: Step 1+2 — BACKUP DATABASE: - C:\Backup\SolutionErp_pre_cleanup_2026-05-15.bak (18.5MB, 2249 pages) - Verified via Get-Item file size Step 3-5 — 3 separate transactions DELETE: - 28 PE active + 7 soft-deleted → cascade 446 child rows (42 Details + 49 Suppliers + 64 Approvals + 238 Changelogs + 10 Attachments + 43 LevelOpinions) - 15 V2 workflows unghim → cascade ~140 Steps+Levels - 2 V1 workflows inactive → cascade ~37 Steps+Approvers Total: 52 rows direct + ~600 cascade child = ~650+ rows wiped. Step 6 — Verify post-cleanup state: - PE total: 35 → 0 ✓ - V2 workflows: 17 → 2 (QT-DN-V2-001 v16 + QT-DN-PA-V2-001 v2 ghim+active) - V1 workflows: 4 → 2 (QT-DN-A v3 + QT-DN-B v1 active, PE pin protected) Step 7 — BE smoke verify alive post-cleanup: - /api/auth/login → 200 - /api/purchase-evaluations → 200 - /api/approval-workflows-v2 → 200 - /api/pe-workflows → 200 → KHÔNG crash startup (Plan F precedent avoid được) Multi-agent ROI: Investigator save em main hard-delete blind without backup + catch SQL Express constraint + catch FK Restrict gotcha. Pattern reinforced: - Destructive operation prod BẮT BUỘC pre-flight audit + backup + verify - scp + sqlcmd -i workflow cho complex SQL trên prod (avoid shell escape hell qua SSH PowerShell) - Plan F precedent: KHÔNG drop active workflow (PE pin → BE crash) Stats final S23 t8: - 31 mig · 59 tables · ~145 endpoints · 34 FE pages · 111 test unchanged - 47 gotcha · 20 memory · 6 skills · 4 sub-agents - **0 PE phiếu test + 4 workflow ghim/active** — UAT clean slate - Backup rollback ready: vietreport-vps:C:\Backup\SolutionErp_pre_cleanup_2026-05-15.bak Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
153 KiB
HANDOFF — Brief 5 phút cho session tiếp theo
Last updated: 2026-05-15 (Session 23 turn 8 — 🧹 Plan R: Cleanup destructive prod database. Bro chốt sau Plan P+Q wire OK: "xóa hết phiếu test + quy trình ko ghim". Investigator pre-flight audit ~64K confirm scope 28 PE active + 7 soft + 15 V2 unghim + 2 V1 inactive. Backup mandatory SolutionErp_pre_cleanup_2026-05-15.bak 18.5MB. Hard-DELETE 52 rows + ~600 cascade child (PE child + workflow Steps+Levels+Approvers). Post-cleanup: 0 PE · 2 V2 ghim · 2 V1 active. Smoke verify 3/3 endpoints 200 — BE healthy. Stats: 31 mig · 59 tables · ~145 endpoints · 111 test · 47 gotcha · 20 memory · 6 skills. UAT clean slate. Bro test workflow fresh.)
Last updated S23 t6: 2026-05-15 (Session 23 turn 6 — 🎯 Plan P HOTFIX: Controller TransitionPeBody record missing 3 fields — bug ROOT CAUSE thực sự F1+F2 fail. CICD Monitor Plan O Run #202 catch CRITICAL caveat: Controller TransitionPeBody:267 MISSING ReturnMode + ReturnTargetUserId + SkipToFinal. Investigator audit FE confirm: FE × 2 app mirror SEND ĐÚNG 7 fields qua api.post() — BE drop tại Controller body record + mediator.Send line 70. Bug present 2 ngày prod từ Mig 28 deploy 2026-05-13 → F1+F2 wire fail từ FE side. Plan N + Plan O fix lookup sites nhưng controller bug block flow trước khi đến lookup. Plan P fix BE-only ~10 LOC: TransitionPeBody +3 field default null/false + mediator.Send pass 7 fields + using import WorkflowReturnMode namespace. 111/111 PASS unchanged. Pattern reinforced: Controller body record MUST mirror Command record fields. Multi-agent ROI: Investigator avoid cross-stack fix sai + CICD Monitor catch root cause invaluable chain. Stats: 31 mig · 59 tables · ~145 endpoints · 34 FE pages · 111 test · 47 gotcha · 20 memory · 6 skills.)
Last updated S23 t5: 2026-05-15 (Session 23 turn 5 — 🎯 Plan O HOTFIX: 4 lookup sites cùng pattern per-NV cascade — Plan N chỉ catch 1/5 sites. Bro UAT sau Plan N: Actor NV Test trong OR-of-N click "Trả lại Người chỉ định" → "Không phải lượt bạn" mặc dù đúng slot. Em main grep audit phát hiện 5 lookup sites cùng bug, Plan N chỉ catch 1. Plan O fix 4 sites còn lại: Service.cs:201 (EnsureCanRejectV2Async = bug bro), Service.cs:248 (ApplyReturnModeAsync read Allow flag), DetailFeatures.cs:72 (F3 guard), Features.cs:311 (F4 AdjustBudget guard). ApplyReturnModeAsync signature +Guid? actorUserId 4th param + caller TransitionAsync:94 update. 3 regression test mới PurchaseEvaluationPerNvLookupRegressionTests. 111/111 PASS (+3 từ 108). Pattern reinforced 5 sites checklist + audit grep FirstOrDefault.*Order ==. Bug 2 (F2 chỉ đến Phan Văn Chương) defer follow-up — F2 logic đúng, verify workflow v14 DB. Stats: 31 mig · 59 tables · ~145 endpoints · 34 FE pages · 111 test (+3) · 47 gotcha · 20 memory · 6 skills.)
Last updated S23 t4: 2026-05-15 (Session 23 turn 4 — 🎯 Plan N HOTFIX: BE per-NV lookup site discrimination — bug critical UAT block. Bro UAT screenshot phát hiện admin Designer tick 7 flag TRUE cho NV Test (UAT V2) nhưng dialog Duyệt/Trả lại KHÔNG có F1+F2+F3+F4 options. Investigator audit verify Hypothesis B: PurchaseEvaluationFeatures.cs:765 FirstOrDefault(Order==X) thiếu ApproverUserId == currentUser.UserId discriminator. Schema Mig 29 OR-of-N: 4 row cùng Order → handler luôn lấy row đầu DB (Lê Văn Bính, Drafter only), bỏ qua admin tick per-NV. Bug PRESENT 2 ngày prod từ Mig 29 deploy 2026-05-13 — chỉ bộc lộ khi lần đầu admin tick selectively. Fix 5 LOC: thêm match ApproverUserId == currentUser.UserId + fallback row đầu cho admin/non-approver. Test regression GetPurchaseEvaluationCurrentLevelOptionsTests 2 method: 4 actor distinct flag profile + admin fallback. 108/108 PASS (+2 từ 106). Multi-agent ROI: Investigator catch root cause 1 spawn ~80K — em main solo fix nhanh. Pattern reinforced: per-NV admin opt-in flag wire checklist 9 surface points (thêm point 9 handler lookup site discrimination). Stats: 31 mig · 59 tables · ~145 endpoints · 34 FE pages · 108 test (+2) · 47 gotcha · 20 memory (1 entry reinforced) · 6 skills.)
Last updated S23 t3: 2026-05-15 (Session 23 turn 3 — 🎯 Plan M: Fix F1.OneLevel/OneStep edge case Bước 1 → giữ ChoDuyet + FE label phase rename. 3 commits Plan M c2042ef..4dd6f9c local (chưa push, chờ Reviewer verdict). Bro UAT post-Plan L deploy: "Hiện logic cũ là khi trả lại 1 cấp hoặc chỉ định hoặc edit là trạng thái draft → thay đổi lại". Investigator audit confirm 4 mode F1.OneLevel/Assignee + F2 + F3 + F4 main path đã giữ ChoDuyet đúng (Mig 28-31 cumulative). Edge case F1.OneLevel ở Bước 1 Cấp 1 + F1.OneStep ở Bước 1 còn fallback Drafter (Phase=TraLai) — gap "draft" bro phát hiện. Plan M fix: reset (0, 1) giữ Phase=ChoDuyet + audit log "không lùi được" + SLA reset 7d (no-op effective). M3 FE label phase Phase=TraLai badge "Trả lại" → "Cần chỉnh sửa lại" (rõ end-user). M2 add 2 edge case tests 106/106 PASS (+2 từ 104). F1.Drafter mode 4 GIỮ NGUYÊN Phase=TraLai semantic (explicit role mode). Multi-agent ROI evidence: Investigator catch fact code main path đã đúng (avoid em main spam fix sai) + Implementer Case 2+3 cookie-cutter 2 spawns + Reviewer pre-commit pending. Memory user-level update 2 entry. Stats: 31 mig · 59 tables · ~145 endpoints · 34 FE pages · 106 test (+2) · 47 gotcha · 20 memory (2 entry reinforced) · 6 skills · 4 sub-agents. CHƯA push remote.)
Last updated S23 t1: 2026-05-14 (Session 23 turn 1 — 🎯 Plan K Mig 31 F2 refactor sang per-Approver-slot — DONE 9 commits 56868bf..0062fcb pushed remote. Verify result K9 + K11: Mig 31 prod TOP 1 ✅, Levels.AllowApproverSkipToFinal col added ✅, Users.AllowDrafterSkipToFinal col dropped ✅, zombie PATCH /allow-skip-final → 404 ✅, bundle hash rotated 2/2 (admin CpI5OL8n→CRsX6cFo, user d064StNa→X7qb4Zl4) ✅, 33 active users preserved ✅, AwLevelDto allowApproverSkipToFinal field PRESENT (13 keys, default False opt-in) ✅. CICD Monitor K9 catch CRITICAL wire gap: AwLevelDto admin DTO miss field — em main K2 + Reviewer K2 cùng miss audit ApprovalWorkflowV2AdminFeatures.cs. K10 hotfix ~15 LOC 1 file (AwLevelDto + ToDto + CreateAwLevelInput + entity init) pushed 0062fcb. Run #195 success → K11 self-verify field present. Pattern lesson: per-NV admin opt-in flag wire 8 surface points required (NOT 6 — admin overview DTO + Create input là 2 gap em main miss S22+5 và S23 t1). Memory feedback_per_nv_permission_scope.md cần add wire checklist gotcha S23 t2+.
S23 t1 prev: 2026-05-14 (Session 23 turn 1 — 🎯 Plan K Mig 31 F2 refactor sang per-Approver-slot — DONE 8 commits Plan K 56868bf..<latest>. Bro phát hiện inconsistency S22: F1+F3+F4 đều per-slot ở Designer, F2 lone wolf ở User Management (Plan D S22 wire) + bro chốt đổi semantic F2: Drafter from Nháp → Approver during ChoDuyet skip thẳng Cấp cuối. Plan K 8 chunk: pre-A slot label rename "#NV {order}" → ApproverFullName + K1 Mig 31 schema swap (drop Users + add Levels, NO BACKFILL Option A) + K2 Service ApproveV2Async +skipToFinal 8th param APPROVE STEP branch + DTO 7th Allow* field + K3 Designer 7th checkbox + banner rewrite + K5 zombie endpoint cleanup (PATCH /users/{id}/allow-skip-final + Command/Handler/DTO/UI all backout Plan D S22) + K6 Workspace × 2 app DROP Drafter checkbox + ADD Approver toggle Dialog amber warning + K7 tests 104/104 PASS regression (3 deleted Drafter F2 + 3 added Approver F2 cancel out) + K8 docs cumulative. Multi-agent ROI: 🟦 Investigator K0 pre-flight + 🟨 Implementer 4 spawns (pre-A + K1 + K3 + K5 + K7 Case 2+3) + 🟥 Reviewer K2 pre-commit catch zombie endpoint Major + 👤 Chủ trì K0-bis sqlcmd + K2 cross-stack reasoning + K6 UX flow + K8 docs. 4 prod user lose AllowDrafterSkipToFinal=true value per Option A (admin re-config qua Designer). Reviewer K2 PASS 0 critical, 2 Major + 2 Minor flagged → K5 + K8 resolved. New pattern caught: "Transient sentinel zombie" anti-pattern (K1 sentinel-false patch + chunk scope shift → endpoint NoOp swallow silent). Per-NV admin opt-in flag pattern proven 3× cumulative (Mig 29 F1+F3 + Mig 30 F4 + Mig 31 F2) — pattern ALSO applies cho refactor existing scope, KHÔNG chỉ greenfield. Memory feedback_per_nv_permission_scope.md reinforced S23 t1. State final: 31 mig (+1 Mig 31) · 59 tables · ~145 endpoints (-1 backout) · 34 FE pages · 104 test PASS unchanged · 47 gotcha · 20 memory · 6 skills · 4 sub-agents (Investigator 1 spawn + Implementer 4 spawn + Reviewer 1 spawn + CICD pending K9). CHƯA push remote — chờ bro confirm K9 spawn CICD Monitor verify.)
S22 chốt cuối: 2026-05-13 2300 (Session 22 CHỐT cuối cùng — bro chốt directive Thứ 9 BẮT BUỘC delegate sub-agent. 14 commits pushed remote 3d725c4..2b9788d + (this final). CICD Monitor Run #193 PASS verified all live endpoints + Mig 30 prod + bundle hash rotated. State final: 30 mig · 104 test · 47 gotcha (#47 revised informational) · 19 memory · 6 skills · 4 sub-agents · 33 active users prod. Retrospective S22: em main solo 6/10 task lẽ ra delegate được — vi phạm directive Thứ 9. Forward S23+ rule: BẮT BUỘC spawn sub-agent khi ACCEPT criteria match (Implementer Case 1/2/3/5, Investigator pre-flight, Reviewer pre-commit, CICD Monitor post-deploy). Em main solo CHỈ khi schema/UX/architecture decision + cross-stack tight coupling + bug fix reasoning chain.)
S22 chốt v1: 2026-05-13 2200 (Session 22 CHỐT — bro confirm sub-agent solution OK. 11 commits pushed remote 3d725c4..b04a11a. CICD Monitor Run #188 PASS verified. State final: 30 mig · 104 test · 47 gotcha (+1 #47) · 19 memory · 6 skills · 4 sub-agents · 33 active users prod. KHÔNG còn pending push. Plan G S21-S22 evidence: Trial Week 1 → Week 2 sub-agent ROI confirmed. Bro test UAT 4 flag pattern + view file PDF + Section Điều chỉnh ngân sách.)
S22 prev: 2026-05-13 1800 (Session 22 — Plan C + D + E done, Plan F ABORTED pre-flight fail. 5 commits local 60efeed..HEAD chưa push. Plan D F2 toggle UI (BE+FE Admin). Plan C task 4 + 1-3 — 19 unit test mới (+5 reg #44 + 7 ReturnMode + 7 Guard). Plan E strict V2 scope List + Detail (remove UAT loose || ApprovalWorkflowId != null). Plan F ABORTED — pre-flight prod sqlcmd reveal Contract entity HOÀN TOÀN V1 chưa wire V2 + 4 PE V1-only + 23 PE V1+V2 mix. Defer F sau Plan B Contract V2 wire. State: 29 mig · 103 test (+19) · 46 gotcha · 19 memory · 6 skills · 4 sub-agents (em main solo S22). 5 commits pending push 3d725c4..HEAD.)
S21 CHỐT CUỐI: 2026-05-13 1530 (Session 21 CHỐT CUỐI — 5 turn cumulative 3a34831..c0af9e0 12 commits pushed remote, CICD Monitor verify 2/2 run PASS. Gotcha #46 mới (Gitea API path/cache stale). 2 memory user-level mới: feedback_ef_migration_backfill_reorder + feedback_per_nv_permission_scope. 3 agent Inv/Imp/Rev cập nhật MEMORY ghi recent activity S21 t3-t5 em main solo. State final: 29 mig · 84 test · 46 gotcha · 19 memory · 6 skills · 4 sub-agents (3 seeds + 1 cicd 2-run). KHÔNG còn pending push. Plan G Trial Week 1 evidence: CICD spawn 2/2 PASS (green), cost ~110-120K under 150K budget, CI time 3-3.5min stable. Pending Plan C test-after bundle defer sau UAT 2-3 lần ổn.)
S21 turn 5: 2026-05-13 1400 (Session 21 turn 5 — 🎯 Refactor Allow sang PER-NV (Mig 29). 4 chunk per-commit 0366946 (A BE+Mig 29) → 63234b2 (B FE Admin Designer per-Level 5 checkbox) → 5ccb2a7 (C FE eOffice mirror 2 app rename) → this Chunk D Docs. F1+F3 5 flag MOVED xuống ApprovalWorkflowLevels (per slot Approver). F2 MOVED xuống Users (per-Drafter). Mig 29 4-stage: ADD 5 Levels + 1 Users + BACKFILL bulk SQL preserve admin config S21 t4 + DROP 6 workflow column. Service refactor đọc currentLevel.Allow* + drafterUser.AllowDrafterSkipToFinal. DTO AwLevelDto +5, PeDetailBundle.workflowOptions → currentLevelOptions + drafterAllowSkipToFinal. FE Admin Designer 5 checkbox per Level slot inline (drop section workflow-level). 84 test PASS. CHƯA push remote — chờ bro confirm.*)
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 (A schema) → c56024b (B BE) → a508564 (C FE Admin) → d27caaf (D FE eOffice) → this (E Docs). F1 4 mode Trả lại admin tick stick (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 clear pointer (S17 backward compat). F2 Drafter skip thẳng Cấp cuối — workflow tick + Workspace checkbox dynamic. F3 Approver edit Section 2 (Hạng mục/NCC/Báo giá) khi workflow tick + actor match CurrentLevel.ApproverUserId + audit ghi PurchaseEvaluationChangelog. Mig 28 thêm 6 bit column lên ApprovalWorkflows (DEFAULT 1 cho AllowReturnToDrafter backward compat, 5 còn lại 0). BE Service extend signature 3 optional param (returnMode/returnTargetUserId/skipToFinal). Helper EnsureEditableForDetailsAsync mới gating Detail/Quote/Supplier CRUD theo Drafter scope OR F3 Approver scope + audit changelog Update/Delete (trước đây silent). FE Admin Designer "Cấu hình nâng cao" section 6 checkbox 3 group. FE eOffice 3 changes mirror 2 app. UAT mode skip dotnet test mỗi chunk, npm build × 2 app pass mỗi chunk. CHƯA push remote — chờ bro confirm.)
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 mới). 3 chunk per-commit: de00887 (BE Chunk A guard + 3 test) + 4b29d00 (FE Chunk B fix 2 app mirror) + this Chunk C Docs. 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 skip Reject branch → enter APPROVE STEP → ApproveV2Async UPSERT opinion "đã duyệt" + advance Cấp tiếp theo. Inconsistency phụ: dialog isSendBack (L247-248) cùng pattern thiếu TraLai → dialog title sai '✓ Duyệt → Trả lại' + KHÔNG amber warning. Severity CRITICAL — data integrity issue khó rollback (BE đã SaveChangesAsync). Test-before §7 BẮT BUỘC: viết test reproduce → confirm FAIL (BE đi sâu vào ApproveV2Async throw "Phiếu chưa pin workflow") → thêm BE guard early throw ConflictException khi target ∈ {TraLai, TuChoi} && decision != Reject → confirm PASS. 3 regression test (Throws TraLai+Approve, Throws TuChoi+Approve consistency, happy path Reject+TraLai). Tổng dotnet test SolutionErp.slnx 84 PASS (58 Domain + 26 Infra = +3 from 81 baseline). npm run build × 2 app pass. Stats: 27 mig (no change) · 59 tables · ~142 endpoints · 34 FE pages · 84 test (+3) · 45 gotcha (+1 #45) · 17 memory · 6 skills · 4 sub-agents seeds-only. Em main solo S21 t3 — bug fix reasoning chain cross BE/FE Implementer REFUSE per multi-agent rule (decision tree: tightly coupled BE+FE+test). CHƯA push remote — chờ bro confirm sau Chunk C wrap.)
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). KHÔNG implement, plan only — defer chờ bro confirm 5 dự án future. Decision chốt: Cách A defensive (giữ blanket 120K em main + RAG retrieve) over Cách B aggressive (cắt 60-70% blanket). Industry-validated cross 4 Anthropic blog + 5 community tools (Cursor/Continue/Cline/Aider). Stack: Voyage-3-large + Qdrant + FastMCP + Streamlit dashboard. Multi-agent cost reality: 4 agents → ~520K cumulative blanket → heavy session ~560K (Cách A) vs ~700K (lazy). 3-layer pattern Phase 1-3 rollout (embeddings + BM25 + reranking, ~70% → ~92% recall). Stats: +1 memory entry (feedback_rag_hybrid_pattern) +1 plan file (rag-setup-plan.md 1500 LOC). Sub-agents vẫn 4 seeds-only, em main solo session.)
S21 turn 1: 2026-05-12 0030 (Session 21 turn 1 — 🎯 Add con thứ 4 cicd-monitor (Path A — post-deploy verifier). 1 commit f1c61c9 pushed 36e21c8..f1c61c9 main -> main. CI skipped per path filter (3 file .md). Cost reality update: ~750K spawn (3 → 4 agents) · ~1.35M heavy / ~700K optimized. Stats: 4 sub-agents seeds-only · 16 memory · 27 mig · 59 tables · ~142 endpoints · 81 test · 44 gotcha · 6 skills unchanged. KHÔNG flush 3 agent MEMORY.md (chưa spawn work — em main solo). Trial Week 1 kick-off S21 turn 2+ Plan B Contract V2 wire mirror PE pattern.)
🎯 Plan B Contract V2 wire — sub-agent role pre-allocated (S23+ kick off priority)
Per directive Thứ 9 BẮT BUỘC delegate. Workflow forward:
| Chunk | Owner | Justification |
|---|---|---|
| Pre-flight audit Contract entity V1 state | 🟦 Investigator | Read-only scan Contract.cs + ContractDeptApproval.cs + sqlcmd 7 V1 contract — Case "audit > 5 files" |
| Chunk A Mig 31 schema (Contract +ApprovalWorkflowId +CurrentApprovalLevelOrder) | 🟨 Implementer Case 2 | Cookie-cutter mirror PE Mig 23 pattern proven 1× S17. Spec deterministic |
| Chunk B Service ApproveV2Async branch | 👤 Chủ trì Solo | Cross-stack reasoning chain — Service branch theo pin field + state machine integration |
| Chunk C Mig 32 ContractLevelOpinions + Service UPSERT hook | 🟨 Implementer Case 2 | Mirror PE Mig 26 + service hook pattern (feedback_service_hook_vs_endpoint) |
| Chunk D FE ContractCreatePage Workspace Select V2 | 🟨 Implementer Case 2 | Mirror PeCreate Workspace pattern × 2 app (rule §3.9) |
| Chunk E FE ContractDetailContent Section 5 V2 | 🟨 Implementer Case 2 | Mirror PE Section 5 LevelOpinionsSectionV2 × 2 app |
| Pre-commit each chunk review | 🟥 Reviewer | Heavy diff + cross-stack — Smart Friend guard active |
| Post-deploy verify Run | 🟩 CICD Monitor | Bundle hash + Mig 31+32 prod + smoke endpoint mới |
Estimated: 5-6 chunk, ~700+ LOC. Em main lead Chunk B (cross-stack reasoning) + coordinate. 4 chunk delegate Implementer (Case 2 cookie-cutter mirror).
🎯 Session 21 chốt cuối — 5 turn timeline (2026-05-12 → 2026-05-13)
| Turn | Date/Time | Topic | Commits | CICD verify |
|---|---|---|---|---|
| t1 | 2026-05-12 0030 | Add cicd-monitor sub-agent (4th, Path A) | f1c61c9 3a34831 (2) |
n/a (docs+config only) |
| t2 | 2026-05-12 1800 | RAG Hybrid setup planning Cách A | 1f8e9af 0a3b747 (2) |
n/a (docs only) |
| t3 | 2026-05-12 2100 | 🔴 Fix gotcha #45 PE button "Trả lại" mismatch + 3 regression test | de00887 4b29d00 6d30ba4 (3) |
bundled with t4 push |
| t4 | 2026-05-13 1200 | F1+F2+F3 PE Workflow advanced options Mig 28 (workflow-level) | 0294693 c56024b a508564 d27caaf eea86fd (5) |
🟩 Run #186 PASS 3m32s |
| t5 | 2026-05-13 1400 | Refactor Allow* sang per-NV Mig 29 (UAT feedback "cấu hình cho từng người") | 0366946 63234b2 5ccb2a7 c0af9e0 (4) |
🟩 Run #187 PASS 3m18s |
Cumulative 12 commits S21 pushed remote 3a34831..c0af9e0. No pending push.
Major schema evolution
- S21 t4 Mig 28 +6 column
ApprovalWorkflows(workflow-level Allow*) - S21 t5 Mig 29 → REFACTOR: +5 column
ApprovalWorkflowLevels(per-Approver slot) + 1 columnUsers(per-Drafter F2) + BACKFILL bulk SQL + DROP 6 columnApprovalWorkflows
Pattern reusable saved memory (2 entry mới user-level)
feedback_ef_migration_backfill_reorder— EF auto drop-then-add WRONG cho data preservation, manual reorder ADD→BACKFILL SQL→DROP discipline. Cross-project pattern.feedback_per_nv_permission_scope— Multi-role workflow flag split scope theo role context (Approver per-slot Level vs Drafter per-User global), KHÔNG gắn parent workflow-level. Decision tree role → table mapping.
Gotcha mới (gotchas.md count +1: 45 → 46)
#46 Gitea Actions API— path/actions/tasksnot/actions/runs(Gitea v1 naming khác GitHub), cache stale ~2 min → cross-check VPS file mtime. Discovery từ CICD Monitor Run #186 + #187.
Plan G Trial Week 1 evidence (multi-agent ROI)
| Spawn | Run | Cost | Verdict | Catch | Notes |
|---|---|---|---|---|---|
| 🟩 CICD Monitor #1 | Run #186 (S21 t4 deploy) | ~12K | PASS | 0 fail (green) | Gitea API discovery |
| 🟩 CICD Monitor #2 | Run #187 (S21 t5 deploy) | ~110K | PASS | 0 fail (green) | Schema refactor verify all 5 stage |
| 🟦 Investigator | seeds-only | 0 | n/a | n/a | Em main solo S21 t3-t5 (cross-stack) |
| 🟨 Implementer | seeds-only | 0 | n/a | n/a | REFUSE 3× per criteria #3/#4 — correct |
| 🟥 Reviewer | seeds-only | 0 | n/a | n/a | Em main self-review build+test |
CICD Monitor ROI: 100% green = 0 catch but 100% peace of mind, automated verify thay em nhớ verify thủ công (recurring blind spot pattern S20). 2 bonus gotcha learnings saved cross-session.
Pending S22+ (cumulative carry)
- Plan C test-after bundle — defer commit riêng sau UAT 2-3 lần ổn:
- Service
ApplyReturnModeAsync4 mode read level.Allow* (per-NV) - Service skipToFinal read user.AllowDrafterSkipToFinal (per-Drafter)
- Helper
EnsureEditableForDetailsAsync3 scenario read level (Drafter scope / Approver match / Approver mismatch → Forbidden) - Regression #44 silent 403 còn nợ (S18 — vi phạm test-before bug fix)
- Service
- User Management F2 toggle UI — BE column
Users.AllowDrafterSkipToFinalsẵn, FE chỉ cần thêm 1 checkbox vào UserEdit dialog (~30 phút). Defer khi admin UAT request. - Plan B Contract V2 wire Mig 30+31 — chưa kick off (PE V2 + per-NV pattern đã proven, ready mirror sang Contract).
- Plan D-F-H-I unchanged (carry from S21 t2 HANDOFF tree).
Audit định kỳ unchanged
- Lần gần: 2026-05-04 manual
- Lần kế: 2026-06-01 combined audit (skill + doc drift)
- Drift sau S21: Mig 27→29 (+2), gotcha 44→46 (+2), memory 16→19 (+3), test 81→84 (+3). Threshold KHÔNG kích hoạt audit sớm.
TL;DR Session 21 turn 5 — Refactor Allow* sang PER-NV (Mig 29 drop Mig 28)
User feedback sau UAT S21 t4 deploy: "Cấu hình cho từng người nhé (chứ ko phải là cho toàn bộ quy trình duyệt), thêm table vào SQL luôn để cấu hình cho dễ."
→ Refactor 6 Allow* options từ workflow-level (Mig 28) sang per-NV scope:
- F1 (4 mode Trả lại) + F3 (Edit Section 2) = 5 flag MOVE xuống
ApprovalWorkflowLevels(per slot Approver — cùng table với ApproverUserId). - F2 (AllowDrafterSkipToFinal) MOVE xuống
Users(per-Drafter user, admin config ở User Management page).
Q&A clarify (2 lượt)
| Câu | User chốt |
|---|---|
| Scope "từng người" | Per-Level: 5 flag (4 F1 + 1 F3) gắn slot Designer. F2 per-Drafter user. |
| Mig 28 cũ xử lý sao | Migrate bốc → per-NV bulk + drop: copy workflow → all Levels của workflow, set TRUE cho Drafter user từng dùng workflow F2, rồi drop 6 column workflow. |
Chunk A — BE schema + Service refactor (0366946)
Mig 29 RefactorAdvancedOptionsToPerLevelAndDrafterUser — 4-stage (EF
auto-generated drop-then-add đã REORDER manual):
- ADD 5 column trên
ApprovalWorkflowLevels(AllowReturnOneLevel/OneStep/ ToAssignee/ToDrafter[default true]/AllowApproverEditDetails) - ADD 1 column trên
Users(AllowDrafterSkipToFinal default false) - BACKFILL bulk SQL (preserve admin config Mig 28):
-- Levels: copy workflow.Allow* → all Levels của workflow UPDATE l SET l.AllowReturnOneLevel = w.AllowReturnOneLevel, ... FROM ApprovalWorkflowLevels l INNER JOIN ApprovalWorkflowSteps s ON s.Id = l.ApprovalWorkflowStepId INNER JOIN ApprovalWorkflows w ON w.Id = s.ApprovalWorkflowId; -- Users: SET TRUE cho user từng Drafter PE link workflow Allow=true UPDATE u SET u.AllowDrafterSkipToFinal = 1 FROM Users u WHERE EXISTS ( SELECT 1 FROM PurchaseEvaluations pe INNER JOIN ApprovalWorkflows w ON w.Id = pe.ApprovalWorkflowId WHERE pe.DrafterUserId = u.Id AND w.AllowDrafterSkipToFinal = 1 ); - DROP 6 column workflow-level (Mig 28 cleanup)
Domain entity refactor:
ApprovalWorkflow.cs— REMOVE 6 Allow* (S21 t4 Mig 28 cũ)ApprovalWorkflowLevel.cs— ADD 5 Allow* (F1 + F3)User.cs— ADD 1 Allow* (F2 AllowDrafterSkipToFinal)
Service refactor ApplyReturnModeAsync:
- Resolve currentLevel slot (CurrentWorkflowStepIndex + CurrentApprovalLevelOrder)
- Read 5 Allow* từ
currentLevel.AllowXxxthay vì workflow - Drafter mode validate AllowReturnToDrafter của currentLevel
- V1 legacy phiếu → fallback Drafter tự động
DRAFTER trình refactor:
- Permission check moved →
drafterUser.AllowDrafterSkipToFinal userManager.FindByIdAsync(actorUserId)get Drafter user entity- Admin bypass unchanged
Helper EnsureEditableForDetailsAsync:
- Read
level.AllowApproverEditDetailsthay vì workflow - Error message rõ "Cấp Approver hiện tại (Bước X / Cấp Y)"
DTO refactor:
AwLevelDto +5 Allow*(admin Designer GET per-Level)AwDefinitionDto -6 Allow*(no longer workflow-level)CreateAwLevelInput +5 Allow*(admin Designer POST per-Level)ApprovalWorkflowOptionsDtochỉ còn 5 flag (F2 separate field)PurchaseEvaluationDetailBundleDto:- RENAME
WorkflowOptions→CurrentLevelOptions - ADD
DrafterAllowSkipToFinal bool
- RENAME
GetPe handler populate:
- currentLevelOptions = 5 Allow* của Cấp hiện tại (null nếu V1 / no pointer)
- drafterAllowSkipToFinal = User.AllowDrafterSkipToFinal từ DrafterUserId
Chunk B — FE Admin Designer (63234b2)
ApprovalWorkflowsV2Page.tsx:
- Types:
LevelDto +5 Allow*,DefinitionDto -6 Allow*,EditLevelEntry +5 Allow* - Helper
makeDefaultLevelEntry(order, userId)factory với 4 false + AllowReturnToDrafter=true copyFromDefinitionpropagate 5 Allow* từ Levels cũ- REMOVE section "Cấu hình nâng cao" workflow-level (amber bg 6 checkbox)
- REPLACE với info banner violet ngắn "ⓘ Cấu hình quyền duyệt riêng cho từng NV..."
- Mỗi Level entry (NV row) ADD inline panel amber-50/30 5 checkbox grid-cols-2
fe-user KHÔNG mirror (Designer admin-only).
F2 cần UX riêng ở User Management page → defer commit sau (BE field đã sẵn, FE chỉ thêm 1 toggle UserEdit dialog khi admin UAT request).
Chunk C — FE eOffice (5ccb2a7) mirror 2 app
Types:
ApprovalWorkflowOptionsREMOVE allowDrafterSkipToFinal (5 flag)PeDetailBundle:- RENAME
workflowOptions → currentLevelOptions - ADD
drafterAllowSkipToFinal: boolean
- RENAME
PeWorkflowPanel.tsx:
- RENAME local
wfOptions → levelOptions, sourceevaluation.currentLevelOptions
PeDetailTabs.tsx:
- F3 approverEditMode: read
currentLevelOptions?.allowApproverEditDetails - F2 allowSkipToFinal: read
drafterAllowSkipToFinal(per-user)
Backward compat:
- Backfill Mig 29 preserve admin config S21 t4
- Phiếu V1 legacy → currentLevelOptions=null → fallback chỉ Drafter mode
- drafterAllowSkipToFinal TRUE chỉ cho user từng Drafter PE link workflow Allow=true
Chunk D — Docs (this commit)
docs/database/schema-diagram.md §14: title "Mig 22-29, S17-21" + add 5 column Level inline comment + add 1 column User Mig 29 blockdocs/STATUS.mdLast updated S21 t5 + 28→29 migdocs/HANDOFF.mdTL;DR S21 t5 đầy đủ (file này)- Session log riêng
State chốt S21 turn 5
| Metric | Trước (S21 t4) | Sau (S21 t5) | Δ |
|---|---|---|---|
| DB tables | 59 | 59 | 0 |
| Migrations | 28 | 29 | +1 (Mig 29 refactor per-NV) |
| Endpoints | ~143 | ~143 | 0 (same body, different schema) |
| FE pages | 34 | 34 | 0 |
| Unit tests | 84 | 84 | 0 (UAT defer test-after §7) |
| Gotchas | 45 | 45 | 0 |
| Memory | 17 | 17 | 0 |
| Skills | 6 | 6 | 0 |
| Sub-agents | 4 seeds-only | 4 seeds-only | 0 |
| Commits S21 t5 | — | 4 | 0366946 → 63234b2 → 5ccb2a7 → this |
Pending — defer / next session
- User Management page F2 toggle: thêm checkbox "Cho phép gửi PE thẳng Cấp cuối" vào UserEdit dialog (BE column sẵn, FE 1 toggle nhỏ — defer khi admin UAT request)
- Test-after carry (Plan C bundle): Service ApplyReturnModeAsync 4 mode + EnsureEditableForDetailsAsync 3 scenario read-from-level + skipToFinal read-from-user
TL;DR Session 21 turn 4 — F1+F2+F3 PE Workflow advanced options (Mig 28)
User request 3 tính năng mới trong PE V2 Workflow:
- F1 4 mode Trả lại admin stick: 1 Cấp / 1 Bước / Người chỉ định / Người soạn thảo
- F2 Drafter gửi thẳng Cấp cuối (skip mọi Bước/Cấp trung gian)
- F3 Approver chỉnh sửa Section 2 (Hạng mục + NCC + Báo giá) khi đang duyệt
Q&A clarify chốt scope (2 lượt AskUserQuestion)
- F1 "1 bậc" = cả 2 mode (admin chọn 1 Cấp HOẶC 1 Bước HOẶC cả 2 stick)
- F1 "Người chỉ định" = Approver pick runtime từ list NV đã ký (PE.LevelOpinions)
- F1 behavior = 3 mode đầu giữ Phase=ChoDuyet lùi pointer (peer review chain). Mode Drafter giữ Phase=TraLai S17 fallback.
- F2 skip = chỉ skip tới Level cuối (CEO) — Dropdown 2 option "Gửi tuần tự" vs "Gửi thẳng Cấp cuối"
- F2+F3 admin enable = cả 2 cần admin tick per workflow (audit nghiêm)
- F3 approver perm = mọi approver Cấp đang active (currentLevel match)
- F3 scope = Section 2 only (Hạng mục + NCC + Báo giá), KHÔNG đụng PE Header, KHÔNG reset workflow
- Test = test-after UAT default Phase 9 (skip dotnet test mỗi chunk, npm build × 2 app pass)
Chunk A — Mig 28 + Domain (0294693)
ApprovalWorkflow.cs thêm 6 bool field:
AllowReturnOneLevel/AllowReturnOneStep/AllowReturnToAssignee(default false)AllowReturnToDrafter(default TRUE — backward compat S17)AllowDrafterSkipToFinal/AllowApproverEditDetails(default false)
EF config ApprovalWorkflowConfiguration thêm 6 HasDefaultValue match Mig 28 DEFAULT.
Mig 28 AddAdvancedOptionsToApprovalWorkflows:
- 6 AddColumn bit NOT NULL DEFAULT 0/1
- 3-file rule complete (mig.cs + Designer.cs + Snapshot.cs)
- Apply LocalDB Dev + Design
Chunk B — BE Service + handlers + DTOs (c56024b)
Service interface + impl TransitionAsync thêm 3 optional param (backward compat):
WorkflowReturnMode? returnMode(enum {OneLevel=1, OneStep=2, Assignee=3, Drafter=4})Guid? returnTargetUserId(required khi mode=Assignee)bool skipToFinal
REJECT branch extend với helper ApplyReturnModeAsync switch 4 mode:
- OneLevel: lùi 1 Cấp cùng Step. Bước 1 Cấp 1 → fallback Drafter.
- OneStep: lùi sang Bước trước Cấp cuối. Bước 1 → fallback Drafter.
- Assignee: tìm Step+Level match
ApproverUserId == returnTargetUserId. - Drafter: Phase=TraLai clear pointer (S17 behavior).
- 3 mode đầu giữ ChoDuyet + reset SLA 7d.
- Admin bypass workflow.Allow* flag check.
- Non-admin → throw ConflictException nếu flag disabled.
DRAFTER trình branch extend với F2 skipToFinal:
- Workflow.AllowDrafterSkipToFinal required (non-admin)
- Set CurrentWorkflowStepIndex = Steps.Count-1 + CurrentApprovalLevelOrder = max Level
- Audit comment append "[Drafter gửi thẳng Cấp cuối]"
Helper edit guard EnsureEditableForDetailsAsync mới (PurchaseEvaluationDraftGuard class):
- Drafter scope: DangSoanThao OR TraLai
- F3 Approver scope: ChoDuyet + workflow.AllowApproverEditDetails + actor match CurrentLevel.ApproverUserId
- Admin bypass workflow flag check
8 handler switch sang helper mới + inject ICurrentUser khi cần:
- Detail Add/Update/Delete + Quote Upsert/Delete (5 handler — replace EnsureDraftAsync)
- Supplier Add/Update/Remove (3 handler — bonus security fix, trước đây hoàn toàn KHÔNG có phase guard!)
- Update/Delete handler trước đây silent → thêm changelog
PhaseAtChange + UserId + Summary(append[Approver edit khi đang duyệt]khi phase=ChoDuyet)
Command DTO + DTOs:
TransitionPurchaseEvaluationCommand+3 optional fieldApprovalWorkflowOptionsDtoNEW sub-record (6 Allow* flag)PurchaseEvaluationDetailBundleDto+WorkflowOptions fieldAwDefinitionDto+6 Allow* (admin Designer GET)CreateAwDefinitionCommand+6 Allow* param (admin Designer POST)
Chunk C — FE Admin Designer (a508564)
ApprovalWorkflowsV2Page.tsx Designer modal thêm section "Cấu hình nâng cao" 3 sub-group:
-
Mode Trả lại 4 checkbox:
- Trả về 1 Cấp trước (peer review chain trong cùng Bước)
- Trả về 1 Bước trước (Cấp cuối Bước trước nhận lại)
- Trả về Người chỉ định (pick runtime từ NV đã ký)
- Trả về Người soạn thảo (default checked = backward compat S17)
-
Drafter skip: 1 checkbox "Cho phép Drafter gửi thẳng Cấp cuối"
-
Approver edit: 1 checkbox "Cho phép Approver chỉnh sửa Section 2"
Styling: container amber-50/30 border distinct với Steps section. Helper text [10px] dưới label. Headers uppercase tracking.
DTO types + state defaults từ cloneFrom (giữ config version trước) hoặc S17 fallback (chỉ AllowReturnToDrafter=true). POST body propagate 6 flag → BE Create handler set entity.
fe-user KHÔNG mirror (Designer admin-only).
Chunk D — FE eOffice (d27caaf) mirror 2 app
Types purchaseEvaluation.ts:
ApprovalWorkflowOptionstypeWorkflowReturnModeconst-objectPeDetailBundle+workflowOptions field
PeWorkflowPanel.tsx F1 Trả lại radio picker:
- State
returnMode(default Drafter) +returnTargetUserId - Dialog Trả lại render 1-4 radio mode enabled theo wfOptions.Allow*
- Assignee mode → submodal Select pick từ levelOpinions (NV đã ký), dedupe by userId
- Banner amber rounded dưới mô tả hành vi mode chọn
- Mutation payload +returnMode +returnTargetUserId khi isTraLaiAction
PeDetailTabs.tsx F2 Drafter skip:
- State
skipToFinal+allowSkipToFinaltừ workflowOptions - submitForApproval mutationFn accept opts.skipToFinal
- Workspace action bar: checkbox violet "Gửi thẳng Cấp cuối (skip trung gian)" conditional
- Confirm dialog message + button label dynamic theo skipToFinal
PeDetailTabs.tsx F3 Approver edit Section 2:
- useAuth import + compute
approverEditMode(phase=ChoDuyet + workflowOptions.allowApproverEditDetails + actor match) itemsReadOnly = readOnly && !approverEditMode→ ItemsTab nhận- Banner violet "ⓘ Bạn được phép chỉnh sửa..." khi approverEditMode + readOnly (Duyệt menu)
- InfoTab / NccSelectorRow / BudgetFieldRow GIỮ strict isEditablePhase (Header + Section 3, KHÔNG trong F3 scope)
Chunk E — Docs (this commit)
docs/database/schema-diagram.md §14cập nhật title "Mig 22-28, S17-21" + thêm 6 column Allow* trong Core block với inline comment F1/F2/F3docs/STATUS.mdLast updated S21 t4 + count 27→28 mig + UAT defer test count unchanged 84docs/HANDOFF.mdTL;DR S21 t4 đầy đủ (file này)docs/changelog/sessions/2026-05-13-1200-s21-turn4-pe-workflow-advanced-options.mdsession log
State chốt S21 turn 4
| Metric | Trước (S21 t3) | Sau (S21 t4) | Δ |
|---|---|---|---|
| DB tables | 59 | 59 | 0 |
| Migrations | 27 | 28 | +1 (Mig 28 6 column Allow*) |
| Endpoints | ~142 | ~143 | +1 (extend transitions body) |
| FE pages | 34 | 34 | 0 (Designer extend section) |
| Unit tests | 84 | 84 | 0 (UAT defer test-after §7) |
| Gotchas | 45 | 45 | 0 |
| Memory entries | 17 | 17 | 0 |
| Skills | 6 | 6 | 0 |
| Sub-agents | 4 seeds-only | 4 seeds-only | 0 |
| Commits S21 t4 | — | 5 | (0294693 → c56024b → a508564 → d27caaf → this) |
Pending — Test-after (Plan C carry)
Per feedback_uat_skip_verify Phase 9 default: viết test sau UAT 2-3 lần ổn.
Test scope candidate (test-after-uat commit riêng):
- Service
ApplyReturnModeAsync4 mode happy path (OneLevel/OneStep/Assignee/Drafter) - Service skipToFinal happy path + AllowDrafterSkipToFinal=false → ConflictException
EnsureEditableForDetailsAsync3 scenario: Drafter scope / Approver match / Approver mismatch → Forbidden
Bundle với Plan C existing (test #44 silent 403 + test V2 ApproveV2Async + Mig 25/27 PATCH).
TL;DR Session 21 turn 3 — Bug fix CRITICAL "Trả về nhưng hệ thống vẫn duyệt" (gotcha #45)
User UAT 2026-05-12 21:00 screenshot button labeled ← Trả lại trong PE Workflow Panel (menu "Duyệt"), mô tả hành vi: nhấn vào nhưng phiếu KHÔNG về phase TraLai — ngược lại tiến qua Cấp tiếp theo. User mô tả: "Trả về nhưng hệ thống vẫn duyệt".
Diagnose (em main solo, no agent spawn)
3 chỗ inconsistency cùng pattern trong PeWorkflowPanel.tsx (× 2 app fe-admin + fe-user):
| # | Location | Logic | Bug? |
|---|---|---|---|
| 1 | L205-207 button isSendBack |
include TraLai → label ← Trả lại ĐÚNG |
✅ no bug |
| 2 | L64-66 payload isReject |
thiếu nhánh TraLai → gửi decision: 1 (Approve) |
🔴 BUG ROOT |
| 3 | L247-248 dialog isSendBack |
thiếu nhánh TraLai → dialog title fallback '✓ Duyệt → Trả lại' + no amber warning |
🔴 BUG phụ |
BE PurchaseEvaluationWorkflowService.TransitionAsync:
- L51
if (decision == Reject)branch → đúng cho decision=Reject. - L97
APPROVE STEPbranch khi decision=Approve + fromPhase=ChoDuyet → ApproveV2Async UPSERT opinion + advance Cấp. - → FE gửi
decision=1(do bugisReject) → BE đi vào nhánh APPROVE thay vì REJECT → phiếu approve mặc dù user định trả lại.
Chunk A — BE defense-in-depth + 3 regression test (de00887)
Test-before §7 BẮT BUỘC: Viết test reproduce bug TRƯỚC fix.
// Sau line 48 (set isAdmin/isSystem), trước REJECT branch (L51)
if ((targetPhase == PurchaseEvaluationPhase.TraLai
|| targetPhase == PurchaseEvaluationPhase.TuChoi)
&& decision != ApprovalDecision.Reject)
{
throw new ConflictException(
$"Transition tới {targetPhase} BẮT BUỘC decision=Reject (nhận {decision}). " +
"Báo lỗi caller — payload mismatch giữa target phase và decision.");
}
Boundary protection cho mọi caller tương lai (API client / mobile / cron retry). Guard KHÔNG xoá khi FE fix — defense-in-depth.
3 test file tests/SolutionErp.Infrastructure.Tests/Services/PurchaseEvaluationWorkflowServiceGuardTests.cs:
TransitionAsync_TargetTraLai_WithApproveDecision_Throws_AndDoesNotMutateState— reproduce bug, expectConflictException"TraLaiReject*"TransitionAsync_TargetTuChoi_WithApproveDecision_Throws_AndDoesNotMutateState— consistency cover TuChoiTransitionAsync_TargetTraLai_WithRejectDecision_SetsPhaseTraLai— happy path control (Reject branch vẫn đúng)
NoOpNotificationServicestub reusable cho future PE service tests (avoidINotificationServicereal DI complexity).
Run test → 2 FAIL (reproduce bug, BE đi sâu vào ApproveV2Async throw "Phiếu chưa pin workflow") + 1 PASS (happy path). Thêm BE guard → 3 PASS. Tổng dotnet test SolutionErp.slnx 84 PASS (+3 from 81 baseline).
Chunk B — FE fix mirror 2 app (4b29d00)
3 chỗ × 2 app = 6 edits:
// Chỗ 1: isReject payload (L64-66)
const isReject = target === PurchaseEvaluationPhase.TuChoi
|| (target === PurchaseEvaluationPhase.DangSoanThao
&& evaluation.phase !== PurchaseEvaluationPhase.DangSoanThao)
|| (target === PurchaseEvaluationPhase.TraLai // ← THÊM
&& evaluation.phase !== PurchaseEvaluationPhase.TraLai)
// Chỗ 3: dialog isSendBack (L247-248)
const isSendBack = (target === PurchaseEvaluationPhase.DangSoanThao
|| target === PurchaseEvaluationPhase.TraLai) // ← THÊM
&& evaluation.phase !== PurchaseEvaluationPhase.DangSoanThao
&& evaluation.phase !== PurchaseEvaluationPhase.TraLai // ← THÊM
Chỗ 2 (button label isSendBack L205-207) đã đúng từ S17, KHÔNG đụng.
Verify: npm run build × 2 app pass (fe-user 17.91s + fe-admin 6.71s, 0 TS6 err).
Chunk C — Docs (this commit)
docs/gotchas.md+#45 PE button label vs decision payload mismatch (~120 dòng narrative + 2 commit cross-ref + pattern reusable + phòng tránh tương lai)docs/gotchas.mdchecklist debug +entry 22 quick lookupdocs/STATUS.mdLast updated S21 t3 + count 81→84 test + 44→45 gotchadocs/HANDOFF.mdTL;DR S21 t3 narrative đầy đủ (file này)docs/changelog/sessions/2026-05-12-2100-s21-turn3-fix-tra-lai-bug45.mdsession log mới
Pending (carry from S21 turn 2)
Plans A-I unchanged. Plan C1 (test regression gotcha #44 silent 403 S18) vẫn còn nợ — không bundle với S21 t3 fix (scope khác, ưu tiên unblock UAT bug critical trước).
Audit cadence
- Lần gần nhất: 2026-05-04 manual trễ 4 ngày
- Lần kế: 2026-06-01 combined audit
- Drift sau S21 t3: 44→45 gotcha (+1) + 81→84 test (+3) + 17→17 memory (no new) + 6 skills unchanged
State chốt S21 turn 3
| Metric | Trước (S21 t2) | Sau (S21 t3) | Δ |
|---|---|---|---|
| DB tables | 59 | 59 | 0 |
| Migrations | 27 | 27 | 0 |
| Endpoints | ~142 | ~142 | 0 |
| FE pages | 34 | 34 | 0 |
| Unit tests | 81 | 84 | +3 (PE guard) |
| Gotchas | 44 | 45 | +1 (#45) |
| Memory entries | 17 | 17 | 0 |
| Skills | 6 | 6 | 0 |
| Sub-agents | 4 seeds-only | 4 seeds-only | 0 |
| Commits S21 t3 | — | 3 | (de00887 + 4b29d00 + this) |
TL;DR Session 21 turn 2 — RAG Hybrid setup planning (Cách A chốt + 3-layer pattern)
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 2 lần claude-code-guide agent research Anthropic + community practice.
Q&A deep dive 10 topics
- RAG fundamentals + Vector DB role (Qdrant)
- Embedding "AI nhúng" + Voyage AI cost mechanics ($0.18/M tokens)
- Multi-project shared architecture (5 projects → single Qdrant + per-collection)
- Audit procedure 3-tier (weekly auto + monthly deep + quarterly major)
- UI/UX Streamlit dashboard 7 pages design
- Cách A defensive (giữ blanket 120K) vs Cách B aggressive (cắt 60-70%)
- Reasoning depth comparison: lazy 60% → A 90% → B 75-80%
- Industry validation: Anthropic + Cursor + Continue + Cline + Aider all hybrid
- Multi-agent cost reality: 8-10× multiplier, ~520K cumulative blanket 5 entities
- 3-layer hybrid pattern (Anthropic Contextual Retrieval Sept 2024)
Quyết định chốt — Cách A vs Cách B
Chọn Cách A (defensive hybrid):
- Blanket: GIỮ NGUYÊN 120K em main + RAG retrieve supplement
- Sub-agent spawn baseline: ~80-100K each (4 agents = ~400K cumulative)
- Heavy session billed: ~560K (saving -20% vs lazy 700K)
- Quality recall: ~85% (vs Cách B 75-80% do fragmentation)
Why Cách A (bro priority chốt):
- ✅ Em main control flow strong (state ownership direct, response fast)
- ✅ Decision quality 90% (multi-source cohesive reasoning)
- ✅ Wall-clock per task -20% (12 phút vs Cách B 16 phút)
- ✅ Risk-averse (graceful fallback blanket nếu RAG fail)
- ✅ Multi-agent leverage cache 70-90% hit common queries
- ✅ Industry-validated (Anthropic + Cursor + Continue + Cline + Aider)
3-layer hybrid Phase rollout (Anthropic Contextual Retrieval)
| Phase | Layers | Recall | Cost/mo |
|---|---|---|---|
| Phase 1 (Week 1-4) | Vector embedding only (Voyage-3-large) | ~70% | ~$1.50 |
| Phase 2 (Month 2) | + BM25 hybrid (bm25s free local) | ~78% | ~$1.50 |
| Phase 3 (Month 3) | + Voyage rerank-2 + Contextual prefix | ~92% | ~$4-5 |
Stack validated cross-industry
- Voyage AI embedding (Anthropic partner, multilingual 26 lang)
- Qdrant local (Rust binary, "leading agent memory backend 2026")
- FastMCP Python (official Anthropic SDK)
- SQLite event log + Streamlit dashboard 7 pages
- Pre-commit hook re-index delta
Multi-agent cost reality (Anthropic warn 8-10× multiplier)
Per entity blanket Cách A:
Em main: ~120K
4 sub-agents × ~100K spawn = 400K cumulative
Total: ~520K cumulative billed (not single context window)
Heavy session 4-agent spawn:
Lazy: ~700K effective billed
Cách A: ~560K (-20% from multi-agent shared cache)
Plan I NEW — RAG Setup Implementation (defer)
Trigger: Bro confirm 5 dự án path + stack + pilot choice + Voyage API key + disk cleanup 5-8GB.
Schedule: Dedicated session 10-14h weekend (per feedback_drastic_refactor_scope).
Phase rollout:
- Phase 1 single project pilot 4-week trial
- Phase 2-3 upgrade incremental conditional on Phase 1 success
- Cost realistic: ~$2-5/month total cho 5 projects
Deliverables
- ✅
docs/rag-setup-plan.md(commit1f8e9af1223 LOC + extend S21 t2 ~300 LOC = ~1500 LOC final) - ✅ Memory
feedback_rag_hybrid_pattern.md(NEW cross-project reusable) - ✅ MEMORY.md index +1 entry
- ✅ Session log this chốt
- ⏭ Implementation defer chờ trigger
Em main solo S21 turn 2 (no SOLUTION_ERP sub-agent spawn)
3 spawn này session — KHÔNG phải 4 SOLUTION_ERP sub-agents:
- claude-code-guide × 2 (generic agent for Anthropic + industry research)
- 4 SOLUTION_ERP sub-agents (Inv/Imp/Rev/CICD) vẫn seeds-only
State chốt S21 turn 2
| Metric | Trước | Sau | Δ |
|---|---|---|---|
| DB tables | 59 | 59 | 0 |
| Migrations | 27 | 27 | 0 |
| Endpoints | ~142 | ~142 | 0 |
| FE pages | 34 | 34 | 0 |
| Unit tests | 81 | 81 | 0 |
| Gotchas | 44 | 44 | 0 |
| Memory entries | 16 | 17 | +1 (RAG hybrid pattern) |
| Skills | 6 | 6 | 0 |
| Sub-agents | 4 seeds-only | 4 seeds-only | 0 |
| Commits S21 cumulative | 2 | 4 | +2 |
| Plan files | 0 | 1 (rag-setup-plan.md) |
+1 |
TL;DR Session 21 turn 1 — Add cicd-monitor (4th sub-agent, Path A chốt)
User chốt Path A sau pre-flight Plan G Trial Week 1 (S20 wrap đã setup 3 agents Inv/Imp/Rev): thêm sub-agent thứ 4 chuyên post-deploy verify (Gitea Actions poll + bundle hash × 2 app + sqlcmd mig prod + endpoint smoke). Trade-off chốt: +~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.
Em main solo S21 turn 1 (no agent spawn)
1 turn từ S20 wrap chiều/đêm 2026-05-11 22:00 sang sáng/đêm 2026-05-12. Em main solo qua context paste + Write file. 3 agents (Inv/Imp/Rev) KHÔNG spawn, vẫn seeds-only state. cicd-monitor mới setup file (Write), chưa spawn work.
Deliverables (1 commit f1c61c9, 456 +/-23 LOC)
| File | Loại | LOC |
|---|---|---|
.claude/agents/cicd-monitor.md |
NEW system prompt (~7KB) | ~200 |
.claude/agent-memory/cicd-monitor/MEMORY.md |
NEW seed (~5KB) | ~150 |
.claude/agents/README.md |
UPDATE 4-agent architecture | +~80 / -23 |
feedback_multi_agent_setup.md (user-level memory) |
UPDATE 3 → 4 agents | +12 / -6 |
Q&A chốt scope (Path A vs B)
- Path A (chọn): cicd-monitor READ tier (~150K/spawn) — catch fail tự động
- Path B (bỏ): Em main thêm checklist verify manual (~0 cost) — phụ thuộc memory + recurring miss
- Decision rationale: Recurring blind spot S20 ~30% push quên verify → tự động hóa worth +150K trade-off
CI skipped (gotcha #41 path filter)
3 file .md → match paths-ignore: '**/*.md' → CI không trigger → prod IIS không thay đổi. Đây là expected behavior — agent infrastructure là local Claude Code only, KHÔNG cần deploy lên prod IIS. Khi spawn agent đọc file .md từ local filesystem.
State chốt S21 turn 1
| Metric | Trước S21 | Sau S21 t1 | Δ |
|---|---|---|---|
| DB tables | 59 | 59 | 0 |
| Migrations | 27 | 27 | 0 |
| Endpoints | ~142 | ~142 | 0 |
| FE pages | 34 | 34 | 0 |
| Unit tests | 81 | 81 | 0 (no test added) |
| Gotchas | 44 | 44 | 0 |
| Memory entries | 16 | 16 | 0 (update existing entry only) |
| Skills | 6 | 6 | 0 |
| Sub-agents | 3 seeds-only | 4 seeds-only | +1 cicd-monitor green |
| Commits S21 | — | 1 | (f1c61c9) |
Plan G (Multi-agent Trial 4-week) update post-S21 t1
- ✅ Setup phase complete: 3 agents S20 t12 + 1 agent S21 t1 → 4 sub-agents seeds-only ready
- ⏳ Week 1 pending: Plan B Contract V2 wire Mig 28+29 — kick-off Session 21 turn 2+
- ⏳ Pass criteria updated: Rev catch ≥ 2 wire bugs + CI/CD Monitor catch ≥ 1 deploy ship fail (bundle hash unchanged / mig drift) + saving ≥ 25% Case 1+2 + Max 20× quota comfortable
Next session priority unchanged (carry from S20 wrap)
- Plan B (HIGH) Contract V2 wire — Trial Week 1 kick-off với 4 agents
- Plan C (HIGH) Test gap fill (B4 silent 403 + Mig 25/26/27 PATCH) — bundle Chunk E Plan B
- Plan D Hard blockers ops (UAT/SMTP/creds/backup)
- Plan E Phân quyền strict V2 + drop legacy V1
- Plan F Audit định kỳ 2026-06-01 (chưa đến — KHÔNG tự chạy)
Cost reality update
| Metric | Trước (3 agents) | Sau (4 agents) |
|---|---|---|
| Spawn setup total | ~564K | ~750K (+150K cicd-monitor) |
| Heavy session | ~1.2M (~6× solo) | ~1.35M (~6.5× solo) |
| Optimized cached | ~600K (~3× solo) | ~700K (~3.5× solo) |
Max 20× plan absorbs ~3.5× solo cost comfortable.
TL;DR Session 20 WRAP (turns 1-12 chốt 2026-05-11)
User UAT live iteration liên tục — 12 turns trong 1 ngày (sáng-trưa-chiều-tối-đêm). 14 commit cumulative.
3 chủ đề lớn
- PE Detail UI restructure (turns 1-5 + 6 + 8-10): User yêu cầu 3 polish UX core + 4 polish nhỏ.
- Turn 1-5 wrap commit
9dee00d→f2f01f4→f8e5675: Section reorder (Hạng mục lên #2 + auto-seed 1 row từ gói thầu) → Nested grid HangMucCard NCC expand (drop SuppliersTab dead code) → Section Ý kiến gộp đồng cấp 1 box / Step - Turn 6
f568945: Manual budget "Nhập tay" drop tên field, chỉ giữ số tiền + VND format - Turn 8-10: NCC palette 5-màu cycle + Winner icon ✓ đậm + hover transition + AddSupplier auto-fill master data 4 field
- Turn 1-5 wrap commit
- Admin menu eOffice management (turn 7): Mig 27
IsVisible + DisplayLabelcột MenuItem + PATCH/api/menus/{key}+ NEWMenuVisibilityPage~210 LOC + fe-user Layout filter !isVisible + renderdisplayLabel || label. Admin sidebar luôn dùng Label gốc (Q2=b). - Infrastructure (turns 11-12):
- Turn 11 responsive 4-tầng pattern cho laptop nhỏ → memory
feedback_responsive_laptop_breakpoint.md - Turn 12 SETUP 3 sub-agents (Investigator + Implementer + Reviewer) + em main coordinator → memory
feedback_multi_agent_setup.md
- Turn 11 responsive 4-tầng pattern cho laptop nhỏ → memory
Stats cumulative
| Metric | Trước S20 | Sau S20 | Δ |
|---|---|---|---|
| DB tables | 59 | 59 | 0 |
| Migrations | 26 | 27 | +1 (Mig 27 menu visibility) |
| Endpoints | ~141 | ~142 | +1 (PATCH /menus/{key}) |
| FE pages | 33 | 34 | +1 (MenuVisibilityPage) |
| Menu keys | ~60 | ~61 | +1 (MenuVisibility) |
| Unit tests | 81 | 81 | 0 (Phase 9 UAT defer §7) |
| Gotchas | 44 | 44 | 0 |
| Memory entries | 14 | 16 | +2 (responsive t11 + multi-agent t12) |
| Skills | 6 | 6 | 0 |
| Sub-agents | 0 | 3 | +3 (Inv + Imp + Rev seeds) |
| Commits S20 | — | 14 | (9dee00d → ae1814c) |
Multi-agent state chốt session
3 sub-agents vừa setup turn 12 → seeds-only state, chưa spawn work. KHÔNG có findings để flush cross-agent learnings ở session này.
Trial Week 1 sẽ kick off Session 21:
- Investigator pre-flight: audit PE V2 schema patterns (Mig 22-27) + Permission flow → spec Contract V2
- Implementer Chunk A-E (Mig 28 ALTER Contract + Mig 29 ContractLevelOpinions + Service ApproveV2Async + Controller + FE mirror)
- Reviewer pre-commit verify gotcha #42 (V1/V2 dual schema branch)
- Em main: architecture decisions + scope refusals + final synthesize
Memory entries mới capture S20
feedback_responsive_laptop_breakpoint.md(t11) — 4-tầng pattern: sidebar w-60 xl:w-72 + workspace 2-panel lg:260 xl:320 + Section padding xs/sm responsive + Card flex-wrap. Phân biệtlgvsxlbreakpoint quan trọng cho laptop nhỏ.feedback_multi_agent_setup.md(t12) — Decision gate 6-criteria. Anthropic + Cognition hybrid. Implementer ACCEPT/REFUSE strict rules. Windows MAX_PATH pitfall (drop isolation worktree). NAMGROUP s41-s43 ROI curve.
Pending Session 21+ (cumulative carry over)
Plan cha B (HIGH priority) — Contract V2 wire Mig 28+29: mirror PE pattern S17-S19 + S20 turn 7. Audit-reuse memory feedback_audit_reuse_before_clone áp dụng — discriminator ApplicableType.Contract=3 đã chung 80% với PE schema V2. 6-task plan:
- Task 1: Mig 28 ALTER
Contract.ApprovalWorkflowId? + CurrentApprovalLevelOrder? - Task 2: Mig 29 CREATE
ContractLevelOpinions(mirror PE Mig 26 UNIQUE + FK Cascade/Restrict) - Task 3:
ContractWorkflowService.ApproveV2Asyncbranch + UPSERT opinion - Task 4:
ContractCreatePageWorkspace Select V2 (validate ApplicableType=3) - Task 5: Pin V2 mặc định cho ContractType qua Designer (admin)
- Task 6:
ContractDetailContentSection "Ý kiến cấp duyệt" V2 dynamic mirror S20 Chunk C
Plan cha C (HIGH priority) — Test coverage gap fill (§7):
- Test regression B4 silent 403 S18 (HIGH — vi phạm rule §7 test-before bug fix)
- Test V2 Service wire
ApproveV2AsyncUPSERT opinion (Mig 26) + Section gộp render (S20 t1-5 Chunk C) - Test Mig 25 PATCH
/user-selectableendpoint - Test PATCH
/api/menus/{key}Mig 27 (mới)
Plan cha D — Hard blockers ops (chờ user/ops): UAT thật 1 tuần / SMTP / Rotate creds / SQL backup schedule / win-acme fix / remove .huypham.vn binding
Plan cha E — Phân quyền strict V2 + drop legacy V1:
- List/Inbox/Detail filter actor scope (V2 đã đúng —
ResolveV2InboxIdsAsync) - Drop tables V1 sau UAT confirm: WorkflowDefinitions/Steps/Approvers + column RejectedAtStepIndex/RejectedFromPhase
- Mig 30 drop Mig 15 PurchaseEvaluationDepartmentOpinions cleanup
Plan cha F — Audit định kỳ 2026-06-01: skill stale (ef-core-migration "21" → 27 / dependency-audit-erp 41 → 44) + schema-diagram §16-21 + memory consolidate xem có duplicate
Plan cha G NEW — Multi-agent trial 4 tuần (Week 1-4): evaluate ROI keep/tune/archive 3 sub-agents
Audit cadence
- Lần gần nhất: 2026-05-04 manual trễ 4 ngày
- Lần kế: 2026-06-01 combined audit
- Drift sau S20: Mig 27 + 1 menu key + +2 memory entries + 3 sub-agents NEW + (no gotcha new).
ef-core-migrationskill "21 migration" stale → thực 27 sau S20 t12
TL;DR Session 20 turn 7 — Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27)
User UAT live yêu cầu thêm tính năng admin quản lý menu eOffice (fe-user) — Ẩn/Hiện + Đổi tên hiển thị. Hỏi xác nhận "chưa có?" → đúng, chưa có. User clarify Q2=b "edit hiển thị bên ngoài, chỉ của eOffice thôi" → admin sidebar luôn dùng Label gốc, DisplayLabel CHỈ áp render fe-user.
Q&A chốt scope
- Q1=a Global (không per-role — permission matrix đã handle)
- Q2=b DisplayLabel chỉ fe-user, admin sidebar giữ Label gốc
- Q3=a Giữ USER_HIDDEN_KEYS hardcode + tầng IsVisible dynamic combine
- Q4=ok UAT iteration skip test, vẫn npm build mỗi chunk
Chunk A (2ea2d27) — Schema + Migration 27
Domain MenuItem +IsVisible bool=true +DisplayLabel string?(200). EF config HasDefaultValue(true) + HasMaxLength(200). Migration 27 AddVisibilityAndDisplayLabelToMenuItems (2 AddColumn) — 3-file rule. Apply LocalDB Dev + Design qua --connection override.
Chunk B (ef394f8) — BE API
DTO MenuNodeDto + MenuItemDto +isVisible +displayLabel. GetMyMenuTreeQueryHandler pass through (KHÔNG filter server-side — 2 FE app tự quyết render). NEW UpdateMenuItemCommand + Validator (Key required + DisplayLabel max 200) + Handler (whitespace → null). MenusController +PATCH /api/menus/{key} [Authorize Policy="Permissions.Update"].
Chunk C (059bfcb) — FE Admin MenuVisibilityPage
Domain MenuKeys +MenuVisibility. DbInitializer +leaf System/MenuVisibility (Eye, Order=94). FE Admin: types/menu.ts mirror, lib/menuKeys.ts +const, Layout resolver +/system/menu-visibility, App.tsx +Route. NEW pages/system/MenuVisibilityPage.tsx ~210 LOC:
- PageHeader + description nhắc admin sidebar dùng Tên gốc
- 4 StatCard (Tổng / Hiển thị / Đã ẩn / Đã đổi tên)
- Search box (key | label | displayLabel)
- 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)
- onSuccess invalidate
['menus','all']+['my-menu']→ live update
Chunk D (1ed6530) — fe-user Layout filter + render
types/menu.ts mirror. Layout.tsx:
function filterForUser(nodes: MenuNode[]) {
// 2 tầng: hardcode USER_HIDDEN_KEYS + dynamic !isVisible
return nodes.filter(n => !USER_HIDDEN_KEYS.has(n.key) && n.isVisible !== false)
.map(n => ({ ...n, children: filterForUser(n.children) }))
}
function effectiveLabel(n) { return (n.displayLabel?.trim()) || n.label }
Replace 3 callsite {node.label} → {effectiveLabel(node)}. fe-admin Layout KHÔNG đụng — render Label gốc + show hết menu (Q2=b).
Pending S21+ (cumulative carry over)
- Test V2 Service wire + Section gộp + B4 silent 403 + Mig 25 PATCH user-selectable
- Contract V2 wire (Mig 28+29 mirror PE) — biggest pending
- Phân quyền strict V2 + drop legacy V1 + Mig 15 cleanup
- Test PATCH /api/menus/{key} validate (Mig 27)
- Skill
permission-matrixthêm 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 chỉ check
!n.isVisibleper-node)
Hard blockers ops (carry)
UAT thật / SMTP / Rotate creds / SQL backup schedule / win-acme fix / remove .huypham.vn
TL;DR Session 20 — PE Detail UI restructure 3 yêu cầu UX user (previous)
Note: TL;DR Session 20 PE Detail UI giữ nguyên dưới (rule §6.5 KEEP narrative). Session 20 turn 7 này thêm trên cùng (admin menu visibility — chủ đề khác).
(4 chunk 9dee00d→2bba851→f2f01f4→Docs). FE-only restructure (1 hook BE nhẹ auto-seed Detail). Q1=a giữ Section "Chọn NCC TP" riêng / Q2=a NCC shared + 1 hạng mục demo / Q3=a chỉ hiện NV đã ký / Q4 public luôn (skip dotnet test, vẫn npm build × 2 app mỗi chunk vì rename/remove function). Chunk A: BE CreatePE handler thêm 1 PurchaseEvaluationDetail mặc định (NoiDung=TenGoiThau, ThanhTienNganSach=Budget.TongNganSach||BudgetManualAmount||0) + FE reorder section Hạng mục lên #2. Chunk B: ItemsTab restructure list HangMucCard 1 card / hạng mục với expand panel chứa 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. Drop SuppliersTab function ~134 LOC dead code, giữ 2 dialog + SupplierAttachmentsCell. Section 4 NCC tham gia gộp vào Section 2 → 4 section final (Thông tin/Hạng mục nested/Chọn NCC TP thắng thầu/Ý kiến). Chunk C: LevelOpinionsSectionV2 forEach step → 1 StepOpinionsBox (replace grid-cols-2 N approvers). Header "Bước N — Tên" + dept badge + "X/Y đã duyệt" counter. Body filter signed opinions sort levelOrder asc + signedAt asc → StepOpinionEntry per signed (tên + Cấp badge + admin override badge + timestamp + comment). NV chưa duyệt KHÔNG hiển thị. KHÔNG đụng Mig 26 schema. Drop LevelOpinionBox function. 81 test pass unchanged (UAT defer).**)
TL;DR Session 20 — PE Detail UI restructure 3 yêu cầu user UX
User UAT live feedback: "Logic khá OK rồi, điều chỉnh giao diện chỗ Duyệt NCC 1 tý". 3 yêu cầu cụ thể:
- Hạng mục đưa lên phía trên + auto-tạo 1 hạng mục từ gói thầu (tên = TenGoiThau, giá trị = ngân sách)
- NCC expand dưới hạng mục (tầng 1 = hạng mục, tầng 2 = NCC, thông tin nhập trên grid)
- Section Ý kiến: gộp comment đồng cấp cùng Phòng → 1 ô / bước (chỉ hiện NV đã duyệt)
Q&A clarify trước code (chốt scope)
- Q1=a: Giữ Section "Chọn NCC TP thắng thầu" riêng (rõ UX, không gộp dropdown winner vào nested grid)
- Q2=a: NCC shared cross-hạng mục (như schema PE.Suppliers hiện tại) — "nhưng hiện chỉ cần 1 hạng mục trước tiên" → đơn giản scope Chunk B
- Q3=a: CHỈ hiển thị NV đã ký (KHÔNG show placeholder "— chưa duyệt")
- Q4 public luôn demo: Phase 9 UAT iteration skip
dotnet testmỗi chunk, vẫn chạynpm run build× 2 app mỗi chunk (rule UAT skip verify exception cho rename/remove function — đã catch TS6133 SuppliersTab + SupplierAttachmentsCell)
Chunk A (9dee00d) — BE auto-seed Hạng mục + FE reorder section
BE — PurchaseEvaluationFeatures.cs CreatePurchaseEvaluationCommandHandler:
var defaultBudgetValue = linkedBudgetTotal ?? request.BudgetManualAmount ?? 0m;
var defaultDetail = new PurchaseEvaluationDetail
{
PurchaseEvaluationId = entity.Id,
GroupCode = "01",
GroupName = "Hạng mục chính",
NoiDung = request.TenGoiThau,
DonViTinh = "gói",
KhoiLuongNganSach = 1m,
KhoiLuongThiCong = 1m,
DonGiaNganSach = defaultBudgetValue,
ThanhTienNganSach = defaultBudgetValue,
Order = 1,
};
db.PurchaseEvaluationDetails.Add(defaultDetail);
// + Changelog Insert audit
linkedBudgetTotal mới: nếu PE link Budget, fetch Budget.TongNganSach (computed sum BudgetDetails). Nếu không link, fall back BudgetManualAmount. Nếu cả 2 null → 0.
FE — Reorder section (mirror fe-admin + fe-user, Chunk A intermediate state): 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 SolutionErp.slnx 0 warn / 0 err.
Chunk B (2bba851) — Nested grid Hạng mục → NCC expand
Restructure ItemsTab thành list HangMucCard (1 card / 1 hạng mục, expanded=true mặc định cho 1 hạng mục demo).
HangMucCard structure:
┌──────────────────────────────────────────┐
│ ▼ 01 · Tên hạng mục KL ĐG TT NS │ ← Header row
│ ──────────────────────────────────────── │
│ NCC tham gia (3) [+ Thêm NCC] │ ← Sub-header
│ ┌──────────────────────────────────────┐ │
│ │ NCC │ Liên hệ │ ĐK │ File │ giá ... │ │ ← Inline NCC table
│ ├──────────────────────────────────────┤ │
│ │ NCC X │ ... │ ... │ ... │ ... │ │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────┘
Header card: GroupCode + NoiDung + 3 stat (KL/ĐG ngân sách/TT ngân sách) + NS link Δ (nếu có Budget link) + Pencil/Trash actions + ▼/▶ toggle expand. 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).
Tương tác:
- Click cell quote (chưa VAT / có VAT / Thành tiền) → mở
QuoteDialogcũ (reuse) + Thêm NCCbutton trong expand panel →AddSupplierDialogcũ (reuse)✏icon mỗi NCC row →EditSupplierDialogcũ✓icon →setWinnermutation, row + cell ăn theo màu emerald🗑icon disabled khi NCC là winner hoặc đã có quote (giữ logic cũ)SupplierAttachmentsCellnhúng vào cell "File báo giá" — full CRUD upload/download/delete file
Drop dead code:
- Function
SuppliersTabxóa hoàn toàn (~134 LOC) — replace bằngHangMucCardexpand panel - Bỏ Section 4 "NCC tham gia" cũ trong main render PeDetailTabs (gộp vào Section 2)
Section layout cuối (4 section):
- Thông tin gói thầu
- Hạng mục + Báo giá NCC (nested expand)
- Chọn NCC / TP thắng thầu
- Ý kiến cấp duyệt
Verify: npm run build × 2 app pass (sau khi catch TS6133 SuppliersTab unused → drop + SupplierAttachmentsCell unused → restore vào cột "File báo giá").
Chunk C (f2f01f4) — Section Ý kiến gộp đồng cấp cùng Phòng
FE-only mirror 2 app. KHÔNG đụng Mig 26 schema (vẫn UPSERT 1 row / Level trong PurchaseEvaluationLevelOpinions qua ApproveV2Async Service). Chỉ thay đổi render layer.
Trước (S19 LevelOpinionsSectionV2):
forEach step:
div.grid-cols-2:
forEach level:
forEach approver:
<LevelOpinionBox /> (1 box / NV)
- Cấp N — Tên NV
- "Đã duyệt" emerald badge or "— chưa duyệt" italic gray
- comment text
- admin override badge nếu signedBy !== approver
Sau (Chunk C):
forEach step:
<StepOpinionsBox stepOrder stepName departmentName totalApprovers opinions={stepOpinions} />
- Header: "Bước N — Tên" + dept badge emerald + "X/Y đã duyệt" counter
- Body:
- empty → "— Chưa có ý kiến duyệt." italic gray
- else → list <StepOpinionEntry opinion /> per signed opinion
(sort levelOrder asc, signedAt asc)
StepOpinionEntry:
- Header trái: ApproverFullName + "Cấp N" badge slate + admin override badge amber nếu có
- Header phải: emerald rounded-full timestamp "✓ DD/MM/YYYY HH:mm"
- Body: comment text whitespace-pre-wrap
NV chưa duyệt KHÔNG hiển thị (Q3=a) — chỉ 1 box / Step thay vì N box / NV như cũ.
Drop dead code:
- Function
LevelOpinionBoxxóa (~50 LOC) — replace bằngStepOpinionsBox+StepOpinionEntry
Verify: npm run build × 2 app pass.
Pending Session 21+ (carry over từ HANDOFF S19 + còn nguyên)
- Test V2 Service wire (Chunk B Service hook S19 + Section gộp Chunk C S20) — defer chờ UAT user confirm + có sample data Production. Domain test ApproveV2 + UPSERT opinion match logic + Admin override match firstLevel + comment empty placeholder + render gộp Step.
- 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-selectableendpoint (admin scope hẹp, MED). - 🎯 Contract V2 wire (Mig 27/28 mirror PE pattern) — biggest pending Plan. Audit-reuse memory áp dụng:
- Mig 27:
Contract.ApprovalWorkflowIdGuid? +CurrentApprovalLevelOrderint? - Mig 28:
ContractLevelOpinionsmirror PE Mig 26 (UNIQUE composite, FK Cascade/Restrict) ContractWorkflowService.ApproveV2Asyncmirror PE branchContractCreatePageWorkspace Select V2 (validate ApplicableType=Contract=3)- Pin V2 mặc định cho ContractType (admin Designer)
ContractDetailContentSection "Ý kiến cấp duyệt" V2 dynamic (mirror S20 Chunk C — 1 box / Step)
- Mig 27:
- Phân quyền strict V2 — vẫn loose UAT (mọi authenticated user thấy mọi phiếu V2). Sau confirm flow:
- List = Drafter + approver any-Step + Admin
- Inbox = chỉ approver Cấp hiện tại (V2 đã đúng —
ResolveV2InboxIdsAsync) - Detail = same as List
- Cũng giải quyết được bug "/inbox loose trả phiếu Nháp" → sau khi strict, B1 FE filter S18 có thể relax
- Drop legacy V1 cleanup sau khi không còn phiếu pin
WorkflowDefinitionId:- Drop tables
WorkflowDefinitions+WorkflowSteps+WorkflowStepApprovers+ PE versions - Mig 29+ cleanup drop column
RejectedAtStepIndex+RejectedFromPhasedeprecated S17 - Drop
ApproveV1LegacyAsyncbranch trong Service
- Drop tables
- Drop Mig 15 cho V2 phiếu sau UAT confirm — Mig 30 cleanup drop bảng
PurchaseEvaluationDepartmentOpinions+ entity. Hoặc giữ cả 2 backward compat (Q3 user chốt phiếu MỚI dùng V2, V1 cũ giữ legacy không migrate). - schema-diagram §16 PE Level Opinions V2 + §17-21 Mig 18-21 — defer cron audit 2026-06-01.
- Skill
ef-core-migrationfrontmatter "21 migration" stale (thực 26) — defer cron audit 2026-06-01.
Hard blockers ops (carry over từ Session 19)
- UAT thật 1 tuần với 2-3 user (Drafter/CCM/BOD)
- 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.ps1chưa schedule Task Scheduler - Remove binding cũ
.huypham.vnsau verify stable - win-acme scheduled task "unhealthy" — auto-renew fix trước 2026-06-18
Audit định kỳ
- Lần gần nhất: 2026-05-04 (manual trễ 4 ngày) — log
docs/changelog/skill-audit-2026-05.md - Lần kế: 2026-06-01 combined audit (skill + doc drift). Drift hiện tại unchanged từ S19 (S20 không thêm migration / gotcha mới):
ef-core-migration"21 migration" stale (thực 26)dependency-audit-erpcount gotcha 41 stale (thực 44)schema-diagram§16 PE Level Opinions V2 + §17-21 Mig 18-21 pending
- Cron Claude SDK KHÔNG fit monthly (auto-expire 7d, memory
feedback_cron_monthly_limitation) — manual trigger khi đến ngày hoặc user nói "audit MD" / "kiểm tra docs"
TL;DR Session 19 — PE Section 5 V2 dynamic theo Workflow + Mig 26
User feedback Section 5 hiện CỨNG 4 box (Mig 15 PheDuyet/CCM/MuaHàng/SmPm từ Phase 8) → cần ĐỘNG theo Workflow V2 đã pin với phiếu. Spec rõ: forEach Step (Phòng) → forEach Level (Cấp) → forEach NV → 1 OpinionBox với ý kiến + tên người. "Bước 1 phòng A có 2 NV → 2 box ngang hàng".
User chốt 5 câu Q&A trước code (capture trong session log):
- Q1=1B: Comment khi NV nhấn Duyệt trong Workflow Panel auto sync sang OpinionBox của NV đó (Section 5 read-only summary). KHÔNG có form input rời.
- Q2=2A+Admin: Chỉ NV chính chủ duyệt được. Admin override → lưu SignedByUserId=Admin.Id, FE banner "Admin duyệt thay" khi SignedByUserId !== Level.ApproverUserId.
- Q3=V2 hết: Phiếu V1 legacy → fallback render Mig 15 4 box CỨNG readOnly cho data history (KHÔNG drop Mig 15 — giữ data cũ).
- Q4=4C + bonus: Phase=DaDuyet/TuChoi → khoá hoàn toàn. Admin có quyền duyệt thay. Comment empty/whitespace → ghi "(duyệt — không ý kiến)" placeholder.
- Q5=5A: Layout group theo Step (header "Bước N — Phòng X" badge emerald + hint số người duyệt) + grid-cols-2 cho N approvers (wrap nếu N>2).
Polish 3 button Hành động (873e7a1) — đầu session
Session 18 turn cuối user đã review screenshot "Hành động: Trả lại / Hủy/Từ chối / Duyệt → Chờ CCM" — yêu cầu rút gọn label + 3 màu khác nhau + bold. PeWorkflowPanel.tsx (mirror 2 app):
- Label: "← Trả lại (về Drafter sửa)" → "← Trả lại" | "✗ Hủy / Từ chối" → "✗ Từ chối" | "✓ Duyệt → Chờ CCM" → "✓ Duyệt"
- Phase đích vẫn hiện qua tooltip title khi hover
- 3 màu: Duyệt = emerald (positive) · Trả lại = amber (request changes) · Từ chối = red (terminal)
- font-medium → font-bold
Chunk A (77a3058) — Domain entity + EF + Mig 26
PurchaseEvaluationLevelOpinion : AuditableEntity:
- (PEId, ApprovalWorkflowLevelId) UNIQUE composite
- Comment nvarchar(2000)
- SignedAt datetime2 (luôn có khi UPSERT)
- SignedByUserId Guid (NV chính chủ HOẶC Admin override)
- SignedByFullName nvarchar(200) — denorm tránh user xóa/đổi tên
EF: FK Cascade Pe + Restrict Level. SignedByUserId KHÔNG nav (denorm only).
Migration 26 AddPeLevelOpinionsForV2: 1 CREATE TABLE + 2 FK + 2 index. 3-file rule commit đủ. Apply LocalDB SolutionErp_Dev OK (Mig 25 + 26 catchup).
Verify: dotnet build pass + dotnet test 81 pass.
Chunk B (90baa8e) — Service V2 hook + DTO + GET include
Service ApproveV2Async sau khi log approval → UPSERT row LevelOpinion cho Cấp hiện tại:
var matchingLevel = pendingLevelGroup
.FirstOrDefault(l => actorUserId.HasValue && l.ApproverUserId == actorUserId.Value)
?? pendingLevelGroup.First(); // Admin override fallback first
var actorFullName = await ResolveActorFullNameAsync(actorUserId, isSystem, ct);
var existingOpinion = await db.PurchaseEvaluationLevelOpinions
.FirstOrDefaultAsync(o => o.PurchaseEvaluationId == evaluation.Id
&& o.ApprovalWorkflowLevelId == matchingLevel.Id, ct);
var normalizedComment = string.IsNullOrWhiteSpace(comment)
? "(duyệt — không ý kiến)"
: comment.Trim();
// UPSERT: if existing → update; else → Add new
Reject (Trả lại / Từ chối) KHÔNG sync. Multi-NV cùng Cấp OR-of-N: match level theo ApproverUserId (NV chính chủ). Admin = fallback first; FE banner "Admin duyệt thay".
Helper ResolveActorFullNameAsync 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 Include LevelOpinions + BuildLevelOpinionsAsync JOIN ApprovalWorkflows.Steps.Levels + Departments + Users → denorm DTO. Empty list cho phiếu V1 / V2 chưa có cấp duyệt → FE fallback message.
Verify: dotnet build pass + dotnet test 81 pass.
Chunk C (6e913b3) — FE Section 5 V2 dynamic mirror 2 app
Type PeLevelOpinion (15 field) + PeDetailBundle.levelOpinions[].
Section 5 PeDetailTabs conditional:
{evaluation.approvalWorkflowId
? <LevelOpinionsSectionV2 ev={evaluation} />
: <DepartmentOpinionsSection ev={evaluation} readOnly={opinionsReadOnly} />}
LevelOpinionsSectionV2:
- Empty state khi
flow null/0 steps→ message "Workflow chưa cấu hình hoặc chưa có cấp duyệt nào" - forEach
step→ header "Bước N — <step.name>" + dept badge emerald + hint "(N người duyệt)" nếu totalApprovers > 1 - Body grid-cols-2 cho
step.levels.flatMap(level => level.approvers.map(approver => <LevelOpinionBox/>)) - Lookup opinion theo (stepOrder, levelOrder, approverUserId) match levelOpinions[]
LevelOpinionBox read-only:
- Title "Cấp N — "
- Badge amber "⚠ Admin duyệt thay" khi
signedByUserId !== approverUserId - Badge emerald "✓ Đã duyệt" khi opinion tồn tại
- Empty: "— chưa duyệt" italic gray
- Footer: timestamp signedAt format vi-VN
Workspace mode hint amber giữ "Ý kiến + chữ ký auto đồng bộ khi NV duyệt phiếu — vào menu Duyệt để ký."
Mirror fe-admin + fe-user (rule §3.9).
Verify: npm run build × 2 pass · 0 TS error.
Stats Δ Session 19
| Trước S19 | Sau S19 | |
|---|---|---|
| Migrations | 25 | 26 (+1 Mig 26) |
| DB tables | 58 | 59 (+1 PeLevelOpinions) |
| API endpoints | ~141 | ~141 (no new — UPSERT auto qua Service hook) |
| FE pages | 33 | 33 (modify existing only) |
| Test pass | 81 | 81 (no change — UAT defer test §7) |
| Gotchas | 44 | 44 |
| Memory entries | 14 | 14 |
| Skills | 6 | 6 |
| Commits | (after S18) | +4 (873e7a1 + 77a3058 + 90baa8e + 6e913b3 + Chunk D Docs) |
⚠️ Điều quan trọng cho Session 20+
-
Test V2 Service wire mới (Chunk B Service hook) — defer khi UAT user UAT 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(tất cả phiếu V2 chỉ dùng Mig 26 LevelOpinions). Mig 27 cleanup drop bảng + entity. Phiếu V1 legacy giữ Mig 15. Hoặc giữ cả 2 để backward compat. -
Migrate phiếu V1 cũ sang V2 (data migration) — admin tool chuyển
ApprovalWorkflowIdtừ null → V2 workflow phù hợp + clearWorkflowDefinitionId. Hiện chưa làm (Q3 user nói chuyển V2 hết = phiếu MỚI dùng V2, phiếu V1 cũ giữ legacy không migrate — đơn giản hơn). -
Contract V2 wire (Mig 27 hoặc 28) + Section 5 dynamic Contract — mirror PE Mig 26 pattern: thêm
Contract.ApprovalWorkflowId+CurrentApprovalLevelOrder(Mig 27) +ContractLevelOpinions(Mig 28) + ServiceApproveV2Asyncmirror PE + ContractDetailContent Section 5 V2. Audit-reuse pattern memoryfeedback_audit_reuse_before_cloneáp dụng. -
Phân quyền strict V2 — vẫn loose UAT. Sau confirm V2 flow (S19 Section 5 + S18 polish OK):
- List = Drafter + approver any-Step + Admin
- Inbox = chỉ approver Cấp hiện tại (V2 đã đúng)
- Detail = same as List
-
schema-diagram §16 PE Level Opinions V2 — thêm khi Chunk D update. Mig 22-25 V2 schema vẫn defer cron audit 2026-06-01.
-
Skill
ef-core-migrationfrontmatter "21 migration" stale (thực 26). Defer cron audit 2026-06-01.
TL;DR Session 18 — PE V2 polish + Clone B + 4 bug fix UAT
User UAT live tiếp Session 17, 7 batch nhỏ + 1 feature lớn (Mig 25). Áp memory feedback_uat_skip_verify (skip dotnet test mỗi chunk, push ngay) + lesson 0ae3fe2: rename/remove → BẮT BUỘC npm run build.
B1 (aaa1c6c) — Pe Duyệt filter cứng "Đã gửi duyệt"
User: "Duyệt bỏ cái trạng thái đi, chỉ load những trạng thái 'Đã gửi duyệt' là đc."
- Bỏ dropdown "Tất cả trạng thái" khỏi UI khi
pendingMe=true, thay bằng hint amber "Lọc cố định: Đã gửi duyệt (phiếu đang chờ duyệt)" - Filter cứng client-side:
getPeDisplayStatus(p.phase) === DaGuiDuyet— loại Nháp/Trả lại/Đã duyệt/Từ chối - Header count dùng
rows.lengthkhipendingMe(inbox không paged) - Workaround BE
/inboxloose UAT có thể trả phiếu Nháp (phân quyền strict V2 pending Session 19+) - Mirror fe-admin + fe-user
PurchaseEvaluationsListPage.tsx
B2 (917446d) — HistoryTab filter Trả lại / Gửi duyệt lại
User: "Lịch sử thay đổi: chỉ bắt các dòng thay đổi khi trả lại và gửi duyệt lại thôi nhé, không cần bắt trạng thái duyệt và các thay đổi trước khi trả lại."
- FE filter trong
PeDetailTabs.HistoryTab, BE giữ audit data đầy đủ (reversible nếu user đổi ý / cần audit trail compliance) - Logic giữ:
- Workflow transition về TraLai (
phaseAtChange === 98) - Workflow transition từ TraLai (summary chứa
"TraLai →") - Mọi thay đổi nội dung (Header/Detail/Supplier/Quote/Attachment) khi
phaseAtChange === 98
- Workflow transition về TraLai (
- Bỏ: workflow Approve cùng cấp (Cấp 1→2→DaDuyet), sửa khi phase=Nháp/ChoDuyet ban đầu
- Empty state: "Chưa có lịch sử trả lại / gửi duyệt lại"
B3 (937eb24) — Clone V2 cho B (DuyetNccPhuongAn)
User: "Quy trình chọn thầu phụ - NCC → Duyệt NCC đúng. Plan kế hoạch clone toàn bộ updates sang Duyệt NCC và Giải pháp."
Audit reuse trước thay vì duplicate. Phát hiện 80% đã chung:
- Schema V2 (Mig 22-24) qua
ApplicableTypeenum - BE Service
ApproveV2Asynckhông hardcode type - App CQRS / API
/approval-workflows-v2?applicableType=Ndynamic - FE Designer
ApprovalWorkflowsV2PagecóTYPE_CODE_TO_INTcả 3 type - Layout regex
^AwV2_(.+)$match dynamic typeCode - App.tsx route
/system/approval-workflows-v2/:typeCodedynamic
Chỉ thiếu cho B: menu key + sample seed (3 file ~60 LOC).
MenuKeys.cs+constApprovalWorkflowDuyetNccPhuongAnV2 = "AwV2_DuyetNccPhuongAn"+ add vàoAll[]DbInitializer.SeedMenusAsync+leaf "Duyệt NCC và Giải pháp (Mới)" dưới root ApprovalWorkflowsV2 (Order=2 cạnh leaf A Order=1)DbInitializer +SeedSampleApprovalWorkflowsV2Async(idempotent — skip nếu admin đã tạo workflow B nào, hoặc thiếu test usernv.test/Phòng CCM): seedQT-DN-PA-V2-001 v011 Bước Phòng CCM × 1 Cấp NV testfe-admin/lib/menuKeys.ts+AwV2_DuyetNccPhuongAn
KHÔNG migration / Service / Designer page mới. Memory feedback_audit_reuse_before_clone.md capture pattern.
User feedback "OK khá tốt, 1 phát chạy luôn :))" sau verify → confirm approach.
B4 (f77ea38) — Fix silent 403 ApprovalWorkflowsV2Controller
Triệu chứng: Drafter nv.test Workspace tạo phiếu B → dropdown "Quy trình duyệt" empty mặc dù Admin Designer thấy 2 version (v01 sample + v02 admin clone).
Root cause: Class-level [Authorize(Policy = "Workflows.Read")] → non-admin role 403 Forbidden khi GET /api/approval-workflows-v2. TanStack Query catch error không hiện UI → dropdown rỗng silent.
Fix:
- Class-level đổi
[Authorize]only (any authenticated user) - GET endpoint inherit class policy — Drafter list workflow để pick read-only, không nhạy cảm
- POST + DELETE giữ
[Authorize(Policy = "Workflows.Create")]admin-only Designer
Pattern reusable cho Contract V2 Mig 26 sau.
B5 (a9c0857) — Fix sidebar highlight queryMatches transient keys
Triệu chứng: Ở leaf "Danh sách" /purchase-evaluations?type=1, click chọn 1 phiếu → URL thành ?type=1&id=abc → leaf bị mất highlight box (gotcha #34 cũ tái phát theo cách khác).
Root cause: queryMatches exact-set equality — target {type} (1 key) vs current {type, id} (2 keys) length mismatch → no match → leaf unhighlight.
Fix: TRANSIENT_QUERY_KEYS = {id, q, editHeader, page, phase, awId} — strip trước khi compare. Mọi key navigation identity (type, pendingMe, mode) check exact-set như cũ.
Edge cases verified:
| URL hiện tại | Target leaf | Match |
|---|---|---|
?type=1&id=abc |
Danh sách ?type=1 |
✓ giữ highlight |
?type=1&pendingMe=1 |
Danh sách ?type=1 |
✗ distinct (không cross-highlight Pending) |
?type=1&phase=10 |
Danh sách ?type=1 |
✓ giữ highlight (filter dropdown) |
?type=1&pendingMe=1&awId=xyz |
Duyệt ?type=1&pendingMe=1 |
✓ giữ highlight |
Mirror fe-admin + fe-user Layout.tsx.
B6 (2a53107) — Mig 25 IsUserSelectable + Designer pin toggle + bỏ "(clone)"
User feedback xem Admin Designer: "Bỏ chữ Clone đi nhé, ghi v02, v03... là đủ rồi. Thêm cho tao nút stick để chọn các quy trình nào mà User đc select bên ngoài khi tạo phiếu."
Bỏ "(clone)": Designer auto-fill name = cloneFrom.name (bỏ (clone) suffix). Version số đã đủ phân biệt.
Pin toggle "Cho user chọn":
- Migration 25
AddIsUserSelectableToApprovalWorkflows:ALTER TABLE ApprovalWorkflows ADD IsUserSelectable bit NOT NULL DEFAULT 0; -- Backfill (giữ behavior cũ — active workflows vẫn pickable): UPDATE ApprovalWorkflows SET IsUserSelectable = 1 WHERE IsActive = 1; - Domain
ApprovalWorkflow.IsUserSelectable— independent vớiIsActive, multiple versions có thể cùng selectable (admin có thể "ghim" nhiều version cho user pick). - App CQRS:
AwDefinitionDto+fieldIsUserSelectableCreateAwDefinitionCommandHandler set defaulttruecho version mới (mirror IsActive default)- New
SetAwUserSelectableCommand(Guid Id, bool IsUserSelectable)+ Handler — toggle
- API
PATCH /api/approval-workflows-v2/{id}/user-selectablepolicyWorkflows.Create(admin only) - DbInitializer
SeedSampleApprovalWorkflowsV2Async+IsUserSelectable = true - FE Designer (
fe-admin/ApprovalWorkflowsV2Page.tsx):DefinitionDto+isUserSelectable- Badge amber "📌 Cho user chọn" cạnh badge IsActive/Archived khi
isUserSelectable === true - Button "📌 Ghim cho user / 🚫 Bỏ ghim" trong action group + mutation
toggleSelectable(call PATCH endpoint, invalidate query)
- FE Workspace (cả fe-admin + fe-user
PeWorkspaceCreateView.tsx):- approvalWorkflows query
.filter(w => w.isUserSelectable)— chỉ workflows admin đã ghim hiện trong dropdown user
- approvalWorkflows query
B7 (32a8d4d) — Cleanup orphan zip files
.claude.zip + docs.zip từ harness session start lỡ tay vào git add -A ở B6 commit. Untrack + add *.zip rule .gitignore.
Stats Δ Session 18
| Trước S18 | Sau S18 | |
|---|---|---|
| Migrations | 24 | 25 (+1) |
| DB tables | 58 | 58 (Mig 25 chỉ ALTER cột) |
| API endpoints | ~140 | ~141 (+1 PATCH user-selectable) |
| FE pages | 33 | 33 (modify existing only) |
| Test pass | 81 | 81 (no change — UAT feature defer test §7) |
| Gotchas | 43 | 44 (+1 silent 403) |
| Memory entries | 13 | 14 (+1 audit reuse pattern) |
| Skills | 6 | 6 (no add) |
| Commits | (after S17) | +7 |
⚠️ Điều quan trọng cho Session 19+
-
Contract V2 wire (Mig 26) — pending dedicated session. Pattern audit-reuse áp dụng: phần lớn đã chung. Mirror PE pattern:
- Thêm
Contract.ApprovalWorkflowId+CurrentApprovalLevelOrder(Mig 26) ContractWorkflowService.ApproveV2Asyncmirror PE patternContractCreatePageWorkspace Select V2- Pin V2 mặc định cho ContractType
- Permission GET endpoint đã permissive (Session 18 fix), không cần đụng
- Thêm
-
Phân quyền strict V2 V2 — hiện loose UAT (mọi authenticated thấy mọi phiếu V2). Sau confirm flow:
- List = Drafter + approver any-Step + Admin
- Inbox = chỉ approver Cấp hiện tại (V2 đã đúng —
ResolveV2InboxIdsAsync) - Detail = same as List
- Cũng giải quyết được bug "/inbox loose trả phiếu Nháp" — sau khi strict, B1 FE filter có thể relax nếu BE đã filter đúng
-
Drop legacy V1 cleanup sau khi không còn phiếu pin
WorkflowDefinitionId(V1):- Drop tables
WorkflowDefinitions+WorkflowSteps+WorkflowStepApprovers+ PE versions - Mig 27 cleanup drop column
RejectedAtStepIndex+RejectedFromPhasedeprecated S17 - Drop
ApproveV1LegacyAsyncbranch trong Service
- Drop tables
-
Test V2 wire (defer khi UAT confirm + có sample data) — Domain test
ApproveV2Asyncmatch logic + transient TraLai entry → Cấp 1 reset. -
feedback_audit_reuse_before_clonememory — áp dụng cho mọi "clone X sang Y" / "thêm type Z mới" sau này. List "đã chung" vs "còn thiếu" trước khi propose plan. -
Sample seed B sample chạy với check
hasAnyB— sau UAT có thể remove sample seed (admin đã tạo workflow thật). Hoặc giữ làm fallback. Idempotent skip nếu admin có workflow B → không clobber.
TL;DR Session 17 — PE V2 schema end-to-end
TL;DR Session 17 — PE V2 schema end-to-end
User chốt sau Session 16 (drastic refactor flat Mig 21 vẫn sai intent): viết lại schema riêng + thêm Menu "Duyệt NCC (Mới)" với cấu trúc explicit:
Mã Quy trình - Tên Quy trình
* Bước 1 - Phòng A
* Cấp 1 - NV X ← 1 user CỤ THỂ qua ApproverUserId
* Cấp 2 - NV Y
* Bước 2 - Phòng B
* Cấp 1 - NV Z
Khác Mig 21: mỗi Cấp = 1 NV chính xác, KHÔNG OR-of-many group Dept+PositionLevel/Role/User.
4 commit (3 chunk per-commit + docs):
Chunk A (c847dc0) — Domain + EF + Mig 22 + Menu
- Domain
ApprovalWorkflowsV2/ApprovalWorkflow.cs— 3 entity (ApprovalWorkflow + Step + Level) + enumApprovalWorkflowApplicableType(DuyetNcc=1 / DuyetNccPhuongAn=2 / Contract=3) - EF
ApprovalWorkflowConfiguration.cs— UNIQUE (Code, Version), FK Cascade Step→Workflow + Level→Step, FK Restrict Department + ApproverUserId - ApplicationDbContext +3 DbSet
- Migration 22
AddApprovalWorkflowsV2— 3 CREATE TABLE + 1 UNIQUE + 4 INDEX. Applied cả_Design+_DevLocalDB - DbInitializer SeedMenusAsync: +menu
ApprovalWorkflowsV2root dưới System (icon Workflow) + leafAwV2_DuyetNcc(icon FileCheck, label "Duyệt NCC (Mới)") - MenuKeys.cs +2 const trong All array
Chunk B (f6047d5) — Application CQRS + API
Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs:GetAwAdminOverviewQuery(ApplicableType?)— load 3-level Include + dept/user names mapCreateAwDefinitionCommand+ Validator — auto-increment Version theo Code, deactivate active version cùng ApplicableTypeDeleteAwDefinitionCommand— UAT helper unconditional (chưa pin)- DTO AwDefinition/AwStep/AwLevel + AwTypeSummary
- IApplicationDbContext +3 DbSet
Api/Controllers/ApprovalWorkflowsV2Controller— route/api/approval-workflows-v2, GET ?applicableType=N | POST | DELETE/{id}, reuse policyWorkflows.Read+Workflows.Create
Chunk C (2781c7e) — FE Designer
fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx(~480 LOC)- Overview cards Active+History per ApplicableType
- DefinitionCard read-only: Bước (badge phòng emerald) → Cấp (badge violet C1/C2 + tên NV + email)
- Designer dialog: Mã/Tên/Mô tả + Add/Remove Step + reorder (chevron up/down) + Add/Remove Level + Select Phòng + Select NV duyệt
- Validate: mỗi Step ≥1 Level, mỗi Level phải có ApproverUserId
- Auto-assign code mặc định theo type:
QT-DN-V2-001/QT-DN-PA-V2-001/QT-HD-V2-001
- Layout.tsx resolver +ApprovalWorkflowsV2 root →
/system/approval-workflows-v2, +AwV2_ leaf →/system/approval-workflows-v2/<code> - App.tsx +2 route
- menuKeys.ts +2 const sync với BE
Chunk D — Docs
STATUS + HANDOFF + project_solution_erp.md memory.
Chunk E (UAT iteration, 9 commit) — Designer fix + State machine + Service wire + UX
User UAT iter Designer V2 phát hiện multiple issues + chốt spec dần qua state diagram. Per memory feedback_uat_skip_verify.md UAT mode iterate nhanh:
| Commit | Tóm tắt |
|---|---|
9712778 |
Designer iter 1 lock 3 cấp/bước (sai intent) |
f3bea3c |
Designer iter 2 đúng intent: max 3 cấp × N NV/cấp + sequential gating C2/C3 disabled khi prev empty + filter NV theo Phòng + no-dup same level. Validator BE strict |
ff21120 |
State machine 5 trạng thái Nháp/ĐãGửiDuyệt/TrảLại/TừChối/ĐãDuyệt. TraLai = Phase RIÊNG (không revert DangSoanThao + không jump-back). PE/Contract/Budget Phase enum + Policy + Service Reject branch → TraLai. 4 test mới TraLai entry point |
0a40c65 |
Mig 23 AddApprovalWorkflowIdToPurchaseEvaluation — pin V2 vào PE entity. Workspace Select bắt buộc workflow lúc create. Validate ApplicableType match PE.Type |
b41484b |
Mig 24 AddCurrentApprovalLevelOrderToPe + Service V2 wire — ApproveV2Async iterate Steps/Levels group by Order = Cấp (OR-of-N approvers) match actor.Id ∈ ApproverUserId. Synthetic Policy ForV2Schema() cho FE nextPhases |
d814429 |
DTO CurrentApproval + banner "Đến lượt bạn" / "Không phải lượt bạn" + button Duyệt forward disabled khi V2 + actor không trong cấp + tooltip "chỉ {NV X / Y} duyệt được". Trả lại + Từ chối vẫn enabled |
9e63e2d d250ae4 74745a7 |
List/Inbox V2-aware (ResolveV2InboxIdsAsync precompute IDs). 2 dropdown filter Quy trình + Trạng thái (chỉ ở Duyệt). Inbox endpoint nhận approvalWorkflowId |
ac41d5e |
SQL clean-transactional-uat.sql — clean prod (9 PE + 11 HĐ + 19 Notif xóa) giữ master. Run qua SSH VPS .\SQLEXPRESS |
de0f38d |
Panel 3 thay 4 phase cards bằng flow workflow thực tế: Bước (icon ✓/●/○) → Cấp (label "đang chờ"/"đã duyệt" + tên NV). DTO ApprovalFlow full snapshot với Status Done/Current/Pending |
Stats final Session 17: 24 migration (+3), 58 DB tables (+3), ~140 endpoints (+5), 81 test pass (+4).
Test user UAT (tạo qua API admin):
- Email:
nv.test@solutions.com.vn/ Pass:TestUser@123456/ Role: Drafter / Phòng: CCM
⚠️ Điều quan trọng cho Session 18+
-
Contract V2 wire CHƯA làm — chỉ PE wire xong (Mig 23-24). Session sau mirror pattern PE → Contract:
- Thêm
Contract.ApprovalWorkflowId+CurrentApprovalLevelOrder(Mig 25) - Update
ContractWorkflowServicethêmApproveV2Asyncbranch - Update Workspace Select V2 trong
ContractCreatePage - Pin V2 thành mặc định cho Contract types
- Thêm
-
Phân quyền strict V2 — hiện loose UAT (mọi authenticated user thấy phiếu V2). Sau confirm flow OK:
- List: Drafter + bất kỳ approver any-Step + Admin
- Inbox: chỉ approver Cấp hiện tại (V2 đã đúng —
ResolveV2InboxIdsAsync) - Detail: same as List
-
Drop legacy V1 sau UAT — khi không còn phiếu nào pin
WorkflowDefinitionId:- Drop tables
WorkflowDefinitions+WorkflowSteps+WorkflowStepApprovers+ PE versions - Cleanup migration: drop column
RejectedAtStepIndex+RejectedFromPhase(deprecated từ Session 17) - Drop
ApproveV1LegacyAsyncbranch trong Service
- Drop tables
-
Admin role bypass — hiện code
if (!isAdmin && !isSystem)skip approver check. By design cho UAT + emergency override. Nếu prod cần audit override → option C trong Session 17 thảo luận: thêm flagIsAdminOverride=truetrong approval row + banner đỏ trên detail. -
81 test pass — Domain WorkflowPolicyTests + PurchaseEvaluationPolicyTests + BudgetPolicyTests đã update cho TraLai entry point. KHÔNG có test cho V2 Service wire (defer khi UAT confirm + có sample data).
TL;DR Session 16 (08/05 — Drastic refactor flat workflow EXECUTE)
Resume từ Session 15 defer plan. Per memory feedback_drastic_refactor_scope.md: dedicated session, fresh context, conservative buffer.
Spec: Workflow flat list (Phòng × Cấp × Approvers). Mỗi step = 1 (Phòng × Cấp). Service iterate steps OrderBy Order, advance pointer. Phase enum simplify ChoDuyet=10. Pin WorkflowDefinitionId.
2 chunk per-commit (5-6 chunk plan rút gọn vì BE tightly coupled):
Chunk A (dbb0089) — All BE: Domain + Mig 21 + Service + Tests
Domain entities:
- Phase enum (PE + Contract): + ChoDuyet=10 generic intermediate. Legacy 2-6 + 98 deprecated (giữ enum cho data cũ).
- WorkflowStep + DepartmentId Guid? FK Restrict + PositionLevel int?
- PurchaseEvaluation/Contract + CurrentWorkflowStepIndex int? + RejectedAtStepIndex int?
- DROP class WorkflowStepInnerStep + nav (PE + Contract)
- DROP *DepartmentApproval.InnerStepId column
EF Configurations:
- DROP InnerStep config (PE + Contract) → table dropped
- WorkflowStep config + DeptId/PositionLevel + FK Restrict
- DepartmentApprovals: restore simple unique non-filtered (Mig 19/20 filtered split reverse)
ApplicationDbContext: DROP DbSet<*WorkflowStepInnerStep> × 2
Migration 21 RefactorWorkflowToFlatModel GỘP:
- 4 ALTER (PE/Contract +CurrentStepIndex +RejectedAtStepIndex)
- 2 ALTER (WorkflowStep +DepartmentId +PositionLevel) PE + Contract
- DROP TABLE × 2 (PEWorkflowStepInnerSteps + WorkflowStepInnerSteps Mig 18+20)
- DROP COLUMN × 2 (*DeptApproval.InnerStepId)
- DROP filtered indexes Mig 19/20
- RESTORE simple UNIQUE (TargetId, Phase, Dept, Stage) non-filtered × 2
Service rewrite (PE + Contract WorkflowService.TransitionAsync):
- DangSoanThao → ChoDuyet (Drafter trình, init idx=0)
- ChoDuyet → ChoDuyet (advance idx per approve)
- ChoDuyet → DaDuyet/DaPhatHanh (idx ≥ steps.Count → terminal, gen mã HĐ Contract)
- ChoDuyet → DangSoanThao (Trả lại — save RejectedAtStepIndex)
- ChoDuyet → TuChoi (Từ chối — khoá vĩnh viễn)
- Resume Drafter (DangSoanThao + RejectedAtStepIndex≠null) → ChoDuyet jump-back
- Match approver: actor.Dept == step.Dept AND actor.PositionLevel >= step.PositionLevel (OR-of-many cùng cấp/dept) OR Approvers.Kind=User|Role match
- Admin role bypass policy
App CQRS: WorkflowStepDto + WorkflowStepInput drop InnerStep, add DepartmentId/DepartmentName/PositionLevel (PE + Contract mirror).
Tests:
- DROP
PeNStageApprovalTests.cs(6) +ContractNStageApprovalTests.cs(6) +PeTwoStageApprovalTests.cs(7) — legacy - UPDATE
PeWorkflowAdminTestssignature for new flat input - 96 → 77 test pass (-19 legacy)
3-file rule Mig 21 commit đủ (.cs + Designer + Snapshot).
Chunk B (88a5be1) — FE Designer + types
PeWorkflowsPage + WorkflowsPage rewrite (~210 LOC each):
- Drop InnerStepDto + EditInnerStep types
- Drop PHASE_OPTIONS (auto-assign ChoDuyet=10 behind scenes)
- StepDto + EditStep + departmentId, departmentName, positionLevel
- Designer step UI rewrite: Tên + Phòng Select + Cấp Select + SLA + Approvers (Role/User optional fallback). Drop InnerSteps sub-section.
- DefinitionCard view: badge Phòng emerald + Cấp NV/PP/TP violet
- Save payload: phase=10 (ChoDuyet)
- Hint amber: "User cùng Phòng + Cấp ≥ step → duyệt được (OR-of-many)"
types/purchaseEvaluation.ts (fe-admin + fe-user mirror): + ChoDuyet=10 enum + label "Đang duyệt" + color amber. Legacy 2-6 + 98 keep.
Chunk C (FE PeWorkflowPanel) SKIPPED — existing UI compatible (workflow.nextPhases BE-driven, 3-button Trả lại/Từ chối Session 14 reuse với target=DangSoanThao/TuChoi pattern).
Verify
- ✅ dotnet build SolutionErp.slnx 0 error
- ✅ dotnet ef database update Mig 21 LocalDB applied OK
- ✅ dotnet test 77 pass (54 Domain + 23 Infra)
- ✅ npm build fe-admin + fe-user pass
Cumulative sau Session 16
| Trước S16 | Sau S16 | |
|---|---|---|
| BE LOC | ~15800 | ~15500 (-300 service simplified) |
| Migrations | 20 | 21 |
| DB tables | 57 | 55 (-2 InnerStep tables) |
| Tests | 96 | 77 (-19 legacy N-stage/2-stage) |
| FE pages | 32 | 32 (rewrite existing 2 designer) |
⚠️ CẢNH BÁO Session 17+
-
UAT live test — workflow flat ready. Tạo new workflow definition qua
/system/pe-workflows/:typeCodevới 3 phòng × N cấp setup. Verify Drafter trình → cấp 1 phòng A → cấp 2 phòng A → cấp 1 phòng B → ... → DaDuyet flow. -
Old PE/HĐ pinned legacy workflow definitions (phase=ChoPurchasing/ChoCCM/etc) — service rewrite chỉ handle ChoDuyet=10 + DangSoanThao/DaDuyet. Old data ở phase 2-6 sẽ stuck (admin manual transition required). Recommend: data migration script convert old workflow → new flat model (defer).
-
Approver explicit (Role/User Approvers list) — fallback nếu user không match Dept+PositionLevel của step. Cho phép user external (không thuộc dept) duyệt qua Role match (vd Admin) hoặc User explicit.
-
Bypass cấp dưới cùng dept — User TP với CanBypassReview=true cùng dept và PositionLevel cao hơn step.PositionLevel → duyệt qua. KHÔNG batch upsert NV+PP rows như Mig 18 N-stage trước (đơn giản hóa: 1 step approve = 1 row).
-
N-stage tests dropped — 19 test legacy (Mig 18, 20 N-stage + Mig 16 2-stage). Có thể viết test mới cho flat workflow flow nếu UAT phát sinh bug. Defer.
-
Sample data N-stage seed task vẫn pending (Session 14). Block trên DesignTime vs Runtime DB gotcha + DbInitializer seed flow.
-
Budget N-stage vẫn defer (cần versioned WF migration trước).
-
schema-diagram §17-21 update defer cron audit 2026-06-01.
TL;DR Session 15 (07/05 — Tooltip diagnose + drastic refactor DEFER)
User UAT live screenshot báo button "Lưu & Gửi Duyệt" KHÔNG hoạt động + suy đoán "trùng ID" giữa các phiếu.
Diagnose (commit 835cc7f):
- Root cause: button silent disabled khi
evaluation.workflow.nextPhaseskhông có forward phase (chỉ TuChoi/TraLai). Cause khả năng: workflow definition pinned thiếu adjacent step →policy.NextPhasesFrom(DangSoanThao)return empty. - Improvement: tooltip + dialog hiển thị reason rõ ràng:
submitDisabledReasontext: "Phiếu đã ở phase X — chỉ Bản nháp/Trả lại mới sửa+gửi" / "Workflow không có phase tiếp theo từ X. Liên hệ admin kiểm tra cấu hình"- Button title attribute → hover show reason hoặc forward phase label
- Dialog confirm show forward phase explicit ("Sẽ chuyển sang Chờ Purchasing")
- Mirror fe-admin + fe-user. Build pass cả 2. KHÔNG đụng BE — chỉ FE diagnostic UX.
- "Trùng ID" KHÔNG phải bug FE —
PurchaseEvaluationWorkspacePageURL state đúng, mỗi PE row unique GUID + MaPhieu. Suy đoán user do button silent.
Plan drastic refactor → DEFER:
User confirm "bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking" — refactor workflow từ phase-based + InnerStep nested model sang flat WorkflowStep model (mỗi step = Phòng × Cấp + Approvers users).
Edit working tree 12 files (Domain entities + EF Configurations + DbContext):
- Phase enum +ChoDuyet=10, legacy values 2-6 deprecated
- WorkflowStep +DepartmentId, +PositionLevel
- Drop class WorkflowStepInnerStep + nav (PE + Contract)
- PE/Contract +CurrentWorkflowStepIndex int?, +RejectedAtStepIndex int?
- *DepartmentApproval drop InnerStepId column
- EF Configurations: drop InnerStep config + nav, restore simple unique non-filtered
- DbContext: drop DbSet × 2
Reality check scope realistic ~8-10h:
- Domain + EF + DbContext (~50min) ✓ done in working tree
- PolicyRegistry rewrite PE+Contract (~45min)
- App CQRS DTOs rewrite (~45min)
- Service rewrite PE+Contract (~2-3h)
- Tests rewrite — drop 12 N-stage tests + update remaining (~1.5h)
- Migration 21 + LocalDB apply + verify (~30min)
- FE Designer rewrite (~1.5h)
- FE PeWorkflowPanel + workflow timeline (~1h)
- Docs/Skill update (~45min)
Vượt session boundary + risk session deep ~30 commits → REVERT working tree về 835cc7f clean state. Test 96 pass intact.
Decision memorized: add memory feedback_drastic_refactor_scope.md — drastic refactor cần dedicated session, scope estimation conservative (2x buffer), tránh mid-session big refactor.
⚠️ CẢNH BÁO Session 16+
- Drastic refactor flat workflow chưa làm — DEFER với plan chi tiết. Khi resume:
- Plan kỹ 6 chunk per-commit
- Buffer 2x estimate (~16h thực tế)
- Tests rewrite biggest risk
- Hoặc fall back Approach Y (FE Designer flat UI giới hạn 5 phòng) ROI 1-2h nếu user OK trade-off
- Task 2 sample data seed N-stage vẫn pending (block trên DesignTime vs Runtime DB gotcha + DbInitializer seed flow)
- schema-diagram §17-19 Mig 18-20 vẫn defer cron audit 2026-06-01
- Hard blockers Ops giữ nguyên 6 task
TL;DR Session 14 (07/05 — PE 3-button approval workflow)
User chỉ thị thay 2-button approval (Duyệt + Reject mơ hồ) bằng 3 hành động rõ ràng cho approver:
- Duyệt = forward phase tiếp theo (decision=Approve)
- Trả lại = về DangSoanThao + Drafter sửa (decision=Reject + target=DangSoanThao). Smart reject pattern Mig 16 + clear N-stage rows + Drafter resume jump-back tới phase đã reject.
- Từ chối = phase=TuChoi (decision=Reject + target=TuChoi). Phiếu khoá vĩnh viễn (17 handler Mig 16 lock edit). Drafter phải tạo phiếu mới.
1 commit (0d77698):
- Domain
PurchaseEvaluationPolicy.cs: NccOnly + NccWithPlan thêm(X → TuChoi)transition cho mọi phase trung gian (ChoPurchasing/ChoDuAn/ChoCCM/ChoCEODuyetPA/ChoCEODuyetNCC) với roles của phase đó. FromDefinition expand: mỗi step (trừ DangSoanThao) thêm (step.Phase → TuChoi) với roles step. - Service
PurchaseEvaluationWorkflowService.TransitionAsync— Reject branch tách 2 case:if (decision == Reject) { if (target != TuChoi) { // Trả lại RejectedFromPhase = fromPhase target = DangSoanThao // force clear N-stage rows tại fromPhase } // else target=TuChoi: giữ nguyên, KHÔNG set RejectedFromPhase, KHÔNG clear } - FE PeWorkflowPanel (admin + user mirror): render 3 button rõ:
- "✓ Duyệt → " brand
- "← Trả lại (về Drafter sửa)" red khi
target=DangSoanThao && fromPhase != DangSoanThao - "✗ Hủy / Từ chối" red khi
target=TuChoi - Decision logic FE:
isReject = target=TuChoi || isSendBack
- Dialog confirm: title rõ theo loại, Cancel case warning đỏ "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 update + add 1:
- rename
Reject_Sets_RejectedFromPhase_And_Forces_DangSoanThao→Reject_To_DangSoanThao_Sets_RejectedFromPhase_TraLai(change target TuChoi→DangSoanThao) - NEW
Reject_To_TuChoi_Locks_Permanently_No_RejectedFromPhase(Phase=TuChoi + RejectedFromPhase null) - update
NStage_Reject_Clears_InnerStep_Rows_At_Phasetarget → DangSoanThao
- rename
- 95 → 96 test pass (+1 Từ chối).
Task 2 sample seed in-progress (defer Session 15+):
- Phát hiện gotcha:
DesignTimeDbContextFactoryhardcodedSolutionErp_Designconnection —dotnet ef database updatetừ session 12-13 thực ra apply lên_DesignDB, KHÔNG phải_Devruntime DB. Khi user run API → DbInitializer auto-MigrateAsync apply lên_Dev. - Đã apply Mig 9-20 lên
SolutionErp_Devquadotnet ef database update --connection. - Start API để DbInitializer auto-seed 30 demo user. API exit 255 sớm khi log buffer full (~100 lines) — seeding dở dang (chỉ seed Roles, chưa Users).
- Defer Task 2 cho session sau. Cần: user manual
dotnet runAPI 1 lần hoàn thành seed, hoặc Claude resume với output redirect to file.
⚠️ CẢNH BÁO Session 15+
-
Phase TraLai = 98 (orphan) — Domain enum đã có từ S11+++++++ nhưng KHÔNG dùng (user chốt Session 14 không cần phase trung gian, chỉ DangSoanThao + TuChoi). Để giữ enum không phá nhưng ko wire vào policy → orphan. Có thể remove migration sau (không gây hại).
-
Task 2 sample seed pending: 2 việc:
- Update
Users.PositionLevel(1=NV, 2=PP, 3=TP) cho 30 demo user đã seed (mapping email pattern:tpb./ccm./pro./fin./act./equ./hra.→ 3 (TP),pp.*→ 2 (PP),nv./qs./else → 1 (NV),bod./pm./admin./huy.duong/chau.le→ null) - INSERT N-stage WorkflowDefinition v2 cho DuyetNcc với InnerSteps (PRO + CCM × 3 cấp NV/PP/TP)
- Update
-
DesignTime vs Runtime DB distinction: dev có 2 DB:
SolutionErp_Design—dotnet ef migrations add/update(DesignTimeDbContextFactory hardcode)SolutionErp_Dev— runtime API (appsettings.Development.json) Khi muốn apply migrations lên Dev:dotnet ef database update --connection "...SolutionErp_Dev..."hoặcdotnet runAPI (DbInitializer auto-MigrateAsync).
-
Budget N-stage defer — chưa wire vì Budget chưa có versioned WorkflowDefinition. Cần migration
AddBudgetVersionedWorkflow+AddBudgetWorkflowInnerSteps. User quyết riêng (feature mở rộng module lớn). -
schema-diagram §17-19 Mig 18-20 chưa update — defer cron audit 2026-06-01.
TL;DR Session 13 (07/05 — Mirror N-stage workflow sang Contract)
User chỉ thị mirror N-stage từ PE (Mig 18+19) sang Contract. Budget defer (cần migration AddBudgetVersionedWorkflow trước — hardcoded BudgetPolicy.Default chưa có WorkflowDefinition entity).
5 chunk per-commit (Chunk E skipped — WorkflowsController auto-bind record qua [FromBody]):
- Chunk A (
951ffa3) DomainWorkflowStepInnerStep+ nav WorkflowStep.InnerSteps + ALTER ContractDeptApproval.InnerStepId + EF config + Migration 20 GỘP 1 (CREATE TABLE + ALTER + DropIndex old + Recreate filtered legacy/N-stage) - Chunk B (
04cf2a0) Application CQRS DTO/Input/Validator/Handler mirror PE Chunk B (default null backward compat) - Chunk C (
e247b67) ContractWorkflowService refactor — load InnerSteps eager + reject clear N-stage rows + dept block split hasInnerSteps→N-stage / else→legacy 2-stage - Chunk D (
7c0772a) ContractNStageApprovalTests 6 test mirror PE + helper SeedWorkflowDefinitionAsync 2 step adjacent + FakeChangelogService + FakeContractCodeGenerator stubs. 89→95 test pass - Chunk E SKIP — auto-bind no code change
- Chunk F (current) FE WorkflowsPage Designer extend InnerSteps sub-section mirror PeWorkflowsPage + Docs
Verify: dotnet build pass + dotnet ef database update Mig 20 LocalDB applied + dotnet test 95 pass + npm build fe-admin pass.
Cumulative sau Session 13:
| Trước S13 | Sau S13 | |
|---|---|---|
| BE LOC | ~15300 | ~15700 (+400) |
| Migrations | 19 | 20 (+Mig 20) |
| DB tables | 56 | 57 (+1 WorkflowStepInnerSteps) |
| DB columns mới | — | +1 (ContractDeptApproval.InnerStepId) |
| API endpoints | ~134 | ~134 (no change — auto-bind) |
| FE pages | 32 | 32 (extend existing WorkflowsPage) |
| Tests | 89 | 95 (+6 Contract N-stage) |
| Commits | (after S12) | +5 (A→F per-chunk, E skipped) |
⚠️ CẢNH BÁO Session 14+
-
Budget N-stage defer — Budget chưa có versioned WorkflowDefinition entity (hardcoded
BudgetPolicy.Default). Để mirror N-stage Budget cần migrationAddBudgetVersionedWorkflowtrước (4 bảngBudgetWorkflowDefinitions/Steps/StepApprovers/InnerSteps+Budget.WorkflowDefinitionId?). Defer cho user quyết riêng — feature mở rộng module Budget lớn. -
PE-only deploy first — Contract N-stage giờ đã có nhưng PE đã được test trước. UAT cả 2 module song song để verify pattern reusable.
-
Sample data N-stage — pending (Task 2). Khi user yêu cầu sẽ seed PositionLevel cho 30 demo user + 1 N-stage workflow definition active cho DuyetNcc + 1 cho ContractType (ví dụ HopDongThauPhu).
-
TraLai BE wire — pending (Task 4). Phase TraLai = 98 enum đã có ở Session S11+++++++ FE side, nhưng BE workflow service chưa wire button "Trả lại" cho approver. Khi user yêu cầu sẽ thêm decision
SendBack(or reuse Reject với target=TraLai) + Service logic + FE button trong Workflow Panel. -
schema-diagram §17 Mig 20 chưa update — defer cron audit 2026-06-01.
-
Skill ef-core-migration Mig 20 row chưa add — defer cron audit.
-
Skill contract-workflow N-stage cross-ref chưa add — defer cron audit.
TL;DR Session 12 (07/05 — N-stage workflow approval per phase × dept × cấp)
User yêu cầu mở rộng từ 2-stage Mig 16 sang N-stage cấu hình động: 1 phase (WorkflowStep cha) có thể cấu hình chuỗi InnerSteps con theo Department × PositionLevel sequential. Mỗi phòng có NV → PP → TP duyệt thứ tự, có thể bypass cùng dept khi role cao + CanBypassReview.
6 câu spec defaults chốt:
- Q1 enum
PositionLevel { NhanVien=1, PhoPhong=2, TruongPhong=3 }+User.PositionLevel int? - Q2 Sequential pure (Order asc, mỗi inner step = 1 cấp duyệt)
- Q3 TP có CanBypassReview → skip NV+PP cùng dept (audit IsBypassed=true cho cấp dưới)
- Q4 Smart reject về DangSoanThao + RejectedFromPhase + clear N-stage rows tại phase reject
- Q5 PE first (Contract+Budget mirror sau khi PE stable UAT)
- Q6(a) Designer UI 1 sub-section "Cấp duyệt nhỏ trong phòng" drag-list per step
6 chunk per-commit:
- Chunk A (
13ab533) Domain + Migration 18 — enum PositionLevel + entity InnerStep + ALTER User.PositionLevel + ALTER PEDeptApproval.InnerStepId + EF config Cascade/Restrict + 3-file rule - Chunk B (
0e56bd0) Application CQRS DTO — extend PeWorkflowStepDto + InnerStepDto + Validator + Handler atomic batch + UserDto +PositionLevel + SetUserPositionLevelCommand (default null backward compat existing PeWorkflowAdminTests) - Chunk C (
0c62e24) Service N-stage logic + Migration 19 filtered unique (drop UNIQUE Mig 16 → recreateWHERE InnerStepId IS NULLlegacy + newWHERE InnerStepId IS NOT NULLN-stage). Refactor TransitionAsync: load InnerSteps eager + reject clear rows + hasInnerSteps→N-stage / else→legacy 2-stage. Match firstPending Order asc, exact match upsert 1 row, bypass batch upsert NV+PP+TP audit IsBypassed - Chunk D (
3d76c6b) 6 test PE N-stage (FirstInner blocks / 3-level sequential pass / TP bypass skips / wrong dept 403 / reject clears rows / legacy fallback no inner). IdentityFixture extend +positionLevel arg. Helper SeedWorkflowDefinitionAsync 2 step adjacent. 83→89 test pass - Chunk E (
83ffabd) APIPATCH /users/{id}/position-levelmirror SetBypassReview - Chunk F (current) FE-Admin types/users.ts +positionLevel + PositionLevel const + Label/Short. PeWorkflowsPage Designer + sub-section InnerSteps drag-list { Phòng × Cấp + required } + button "+ Thêm cấp duyệt" emerald + departmentsList query + payload include. UsersPage column "Cấp" badge + cycle button. KHÔNG đụng fe-user (admin-only). Docs/Skill update.
Verify: dotnet build pass + dotnet ef database update Mig 18+19 LocalDB applied + dotnet test 89 pass + npm build fe-admin pass.
Cumulative sau Session 12:
| Trước S12 | Sau S12 | |
|---|---|---|
| BE LOC | ~14850 | ~15300 (+450 — Domain enum + entity + EF config + Service N-stage + App CQRS) |
| Migrations | 17 | 19 (+Mig 18 + Mig 19) |
| DB tables | 55 | 56 (+1 PEWorkflowStepInnerSteps) |
| DB columns mới | — | +2 (User.PositionLevel + PEDeptApproval.InnerStepId) |
| API endpoints | ~133 | ~134 (+1 PATCH /users/{id}/position-level) |
| FE pages | 32 | 32 (no change — extend existing 2 page) |
| Tests | 83 | 89 (+6 N-stage PE) |
| Commits | (after S11+++) | +6 (A→F per-chunk) |
⚠️ CẢNH BÁO Session 13+
-
N-stage chỉ áp PE first — Contract + Budget vẫn 2-stage Mig 16 cũ. Mirror sau khi UAT PE 2-3 tuần ổn (giữ pattern, lặp lại Domain entity + Service logic + Tests).
-
User.PositionLevel chưa seed cho 30 demo user hiện có — admin phải set tay qua UsersPage cycle button. Hoặc seed migration sau (mapping Position text → PositionLevel int).
-
Designer InnerSteps optional — workflow definition KHÔNG có InnerSteps fallback logic 2-stage Mig 16 cũ. Backward compat 100%. Khi muốn enable N-stage cho 1 phase: clone version + add inner steps + Save.
-
Bypass kế thừa cùng dept only — TP với CanBypassReview chỉ skip NV+PP CÙNG dept của TP. KHÔNG cross-dept (NV.A bypass không skip NV.B).
-
Reset N-stage rows khi reject — clear chỉ rows tại phase reject. Phase trung gian khác vẫn giữ approvals nếu có. Resume sẽ jump tới RejectedFromPhase + cần re-approve N-stage tại phase đó.
-
Wrong-level error message — đã localize "phòng X cấp Y không khớp". UAT user cần feedback nếu confused.
-
schema-diagram §15 Mig 18 + §16 Mig 19 chưa update — defer cho audit cron 2026-06-01 (small drift, không major).
-
Skill
ef-core-migrationMig 18+19 row chưa add — defer audit 2026-06-01.
TL;DR Session phase 2 (08/05 — B12-B14 polish iterate sau wrap-up 6e7a6db)
User UAT live tiếp tục, áp rule strict verify khi rename/remove (lesson hotfix CI 0ae3fe2).
- B12 (
378c993) PE detail polish 5 changes:- "Lưu" no-close (chỉ toast + invalidate sync, KHÔNG đóng workspace)
- "Xóa phiếu" red bottom button CHỈ Bản nháp (soft-delete IsDeleted=true)
- Bỏ header bar workspace mode "Sửa header"/"Xóa"/"Đóng" (chuyển hết xuống bottom action bar)
- Section 4 column header:
s.supplierName(master NCC name) thaydisplayName - Section 3 row chặn xóa NCC khi đã có quotes + tooltip "xóa báo giá trước"
- B13 (
e320027) InfoTab auto re-edit + pencil active visual:- useEffect watch
[autoEdit, canEdit, ev.id, ...]→ re-trigger edit khi pencil click phiếu khác - Sync values từ ev mới (tránh stale state)
- Pencil "sáng lên" active state (
bg-brand-100 + ring) khieditingRowId === p.id
- useEffect watch
- B14 (
d2306b8) QuoteDialog + winner column highlight + loading feedback:- Bỏ checkbox "Chọn NCC này cho hạng mục" (consolidate winner ở Section 2.a)
- Winner column Section 4 matrix LUÔN xanh emerald (header
✓prefix + cells full column) - QuoteDialog full overlay loading + spinner khi save có delay
- NccSelectorRow inline spinner "Đang chọn NCC + sync cột giá Section 4…"
Verify: npm run build × 2 app pass · dotnet test 83 pass (KHÔNG regression). Push gitea OK.
⚠️ CẢNH BÁO Session 12+ (cập nhật)
Bổ sung từ wrap-up trước (xem TL;DR S10-11+++++++ phía dưới):
-
isSelectedper-quote BE field — vẫn còn trong API payload nhưng UI ẩn. Nếu sau này muốn cho user chọn winner per-item (khác với winner per-phiếu) → re-enable checkbox QuoteDialog. Hiện tại chỉ gửiexisting?.isSelected ?? falseđể giữ legacy data nếu có. -
Section 4 cell winner highlight dùng
ev.selectedSupplierId === s.supplierId(per-supplier check). Nếu BE workflow có multi-winner per-item (ít khả năng), cần điều chỉnh logic. -
Loading overlay QuoteDialog chỉ áp QuoteDialog. Các dialog khác (AddSupplierDialog/EditSupplierDialog/DetailDialog) chỉ có button text feedback. Nếu UAT thấy delay → mở rộng pattern overlay sang các dialog khác.
-
InfoTab re-edit useEffect deps include 4 ev fields (tenGoiThau/diaDiem/moTa/paymentTerms). Có thể trigger re-set values nếu BE cập nhật ev (vd auto-save từ chỗ khác). Trade-off: chấp nhận để sync state. Nếu UAT thấy "values mất" → debug deps.
TL;DR Session S10-11+++++++ (07/05 — PE Workspace UX overhaul đầy đủ)
User UAT live mode iterate liên tục. Áp rule feedback_uat_skip_verify skip dotnet test sau mỗi chunk, push ngay. Lesson hotfix CI 0ae3fe2: rename/remove → BẮT BUỘC npm run build trước commit.
11 batch deliverable (23 commit total — xem STATUS Recently Done row đầu tiên cho narrative đầy đủ):
- B1 PE Thao tác 2-panel workspace (S10)
- B2 Migration 17 manual budget fields PE + HĐ (S11)
- B3 BudgetFieldRow inline editor Section 2.b
- B4 InfoTab inline edit Section 1 + PeListPanel pencil hover
- B5 Workspace "new" sectioned create view 5 sections
- B6 Danh sách view disable toàn bộ tương tác
- B7 Workspace "new" lock Loại quy trình + payment preset Select
- B8 PE display status meta (Bản nháp / Đã gửi duyệt / Trả lại / Đã duyệt / Từ chối)
- B9 Phase TraLai = 98 + pencil always visible + edit gating
- B10 Hotfix CI TS errors (forcedPhase rename + unused import)
- B11 PE detail polish — NCC selector dropdown + Section 3 winner protect + Bottom action bar Lưu/Gửi Duyệt
Stats sau wrap-up
| Trước S10 | Sau S11+++++++ | |
|---|---|---|
| BE LOC | ~14400 | ~14850 (+450 — Mig 17 + Domain TraLai + App CQRS) |
| Migrations | 16 | 17 (+1 Mig 17) |
| DB columns mới | — | +4 (PE+HĐ × Name+Amount manual budget) |
| FE pages | 31 | 32 (+1 Workspace 2-panel) |
| FE components mới | — | +5 (PeListPanel, PeHeaderForm, PeWorkspaceCreateView, BudgetFieldRow, NccSelectorRow inline) |
| PE phase enum | 8 | 9 (+TraLai = 98) |
| PE display status | — | 5 (gom phase chi tiết) |
| Tests | 83 | 83 (no test added — UAT iteration, áp rule §7 test-after defer) |
| Commits S10-S11+++++++ | — | +23 |
⚠️ CẢNH BÁO Session 12+
-
Workflow transition vào TraLai — Domain enum đã có (98), FE label/color/badge sẵn. NHƯNG chưa wire button "← Trả lại" trong PeWorkflowPanel approver view + workflow service BE chưa transition logic vào TraLai. Khi UAT cần → thêm: button "Trả lại" trong Chuyển tiếp (hoặc Reject Dialog), workflow rule
Decision=3 (TraLai)set phase=TraLai + ghiRejectedFromPhase. FE đã ready accept hiển thị TraLai phase. -
"Đã gửi duyệt" multi-phase filter — display status "Đã gửi duyệt" gom 5 phase trung gian (ChoPurchasing/ChoDuAn/ChoCCM/ChoCEODuyetPA/ChoCEODuyetNCC). BE chưa hỗ trợ multi-phase param trong list endpoint → FE filter dropdown ẩn option này (chỉ exact-match Bản nháp/Đã duyệt/Từ chối). TODO: BE thêm
?phases=2,3,4,5,6query param hoặc derivedisplayStatusfield. -
feedback_uat_skip_verifyexception — khi commit có rename/remove, BẮT BUỘCnpm run build1 lần trước push (lesson hotfix0ae3fe2). Chỉ skip verify khi pure ADD (props, JSX, new file). -
Workspace + Danh sách + Duyệt matrix:
- Workspace (Pe_*_Create): 2-panel, Section 5 disabled, edit only Bản nháp+Trả lại, pencil bright/disabled, bottom bar 2 nút Lưu/Gửi Duyệt
- Danh sách (Pe_*_List): 3-panel, readOnly=true toàn bộ (no edit/transition)
- Duyệt (Pe_*_Pending): 3-panel, PeDetailTabs readOnly + PeWorkflowPanel có Chuyển tiếp + Section 5 nhập opinion (chỗ này hiện vẫn disabled do code path cũ — verify nếu UAT user cần sign opinions ở đây)
-
Mig 17 columns nullable — backward compat OK. Cả 2 (BudgetId + manual fields) cùng null acceptable. Validation BE chưa XOR.
-
CreateContractFromEvaluation carry-forward manual fields PE→HĐ — đã wire. Verify UAT.
-
CI deploy status — sau hotfix
0ae3fe2các run từ commit4c0625ctrở đi sẽ xanh + deploy. User UAT live trên prod ngay sau push.
TL;DR Session 11++ housekeeping (07/05 — InfoTab inline edit + pencil hover)
User feedback after BudgetFieldRow: muốn thêm nút edit kế bên row trong Panel 1 list, click sáng nội dung Section 1 lên cho sửa header inline.
- ✅ InfoTab inline edit — display mode + button "✎ Sửa" góc phải Section 1. Editing mode: card border brand-200 + 4 input (Tên / Dự án locked / Địa điểm / Mô tả / Payment) + Save (PUT /pe/:id) / Hủy.
- ✅ PeListPanel pencil hover — icon absolute right-2 top-2 mỗi row, group-hover opacity-100. Click → callback onEditClick(id).
- ✅ URL flag
?editHeader=1— set bởi pencil click → autoEditHeader prop chain xuống PeDetailTabs → InfoTab tự open editing mode mount-time. - ✅ fe-admin Chunk 1 (
5a89dd2) · fe-user mirror Chunk 2 (this).
Defer (chưa làm): Refactor workspace "new" mode wrap PeHeaderForm trong sectioned layout giống detail view (5 sections visible). PeHeaderForm hiện tại single-card đủ dùng — chỉ làm thêm khi user feedback rõ.
TL;DR Session 11+ housekeeping (07/05 — inline budget editor)
User feedback after Session 11: muốn toggle + 2 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. Empty values cứ empty.
- ✅ BudgetFieldRow component (~125 LOC) thay FormRow tĩnh cũ ở
b. Ngân sách. canEdit=!readOnly && isDraft. Save dùng existing PUT endpoint (KHÔNG cần BE thay đổi). - ✅ fe-admin Chunk 1 (commit
19712d8) · fe-user mirror Chunk 2 (commitd5c6f12). - ✅ Verify: 2 build pass + 0 TS error.
UX:
- Workspace + Danh sách + isDraft → editable inline: toggle + Select OR 2 input + nút Lưu khi dirty
- Duyệt + !isDraft → display only (link card / manual values / "—" empty)
- Empty state: hiển thị "—" thay vì "(chưa link)" verbose (per user)
TL;DR Session 11 (07/05 — Migration 17 manual budget fields)
Output session 11 — UX improvement cho user nhập ngân sách không cần Budget entity approved:
- ✅ Migration 17
AddManualBudgetFieldsToPeAndContract— 4 ALTER (PE + HĐ × BudgetManualName nvarchar(200) + BudgetManualAmount decimal(18,2)). Applied LocalDB OK. Mirror logic PE ↔ HĐ (Q3 user chốt). - ✅ BE Domain 2 entity (PE + Contract) +2 property + EF config HasMaxLength/HasPrecision. App CQRS Create/Update commands + DTO + Validator + diff log + carry-forward CreateContractFromEvaluation pe→contract.
- ✅ FE Toggle checkbox "Nhập tay (không link)" cạnh Label Ngân sách trong 4 chỗ: PeHeaderForm (workspace + /new page wrap), ContractCreatePage NewForm + EditForm. Khi ON: hide Select, show 2 input field grid 2-col (Tên + Số tiền formatted VND). Khi OFF (default): Select Budget approved cũ. Auto-detect manual mode khi load existing có manual data + !budgetId. Display fallback ở PeDetailTabs Section 1 "b. Ngân sách" + Contract EditForm read-only branch.
- ✅ 5 chunk per-commit (build + 83 test pass mỗi chunk): C1 Domain+Infra Migration 17 (
ecd5f7e) · C2 App CQRS (0f7901c) · C3 FE-Admin (bab5031) · C4 FE-User mirror (14f8d9d) · C5 Docs (current).
Validation Q2 chốt: cả BudgetId + manual fields có thể cùng null (PE/HĐ chưa có ngân sách gì). KHÔNG XOR enforce — BE prefer link nếu có (Phase=DaDuyet guarantee), manual fallback only.
KHÔNG đụng: Budget entity / Phase=DaDuyet validation (giữ invariant "PE/HĐ link Budget approved only"). Manual fields chỉ là note/display, KHÔNG join với Budget.Details cho per-row comparison ở PE matrix Section 4 (cột "So với ngân sách" vẫn require ev.budgetId — không có detail rows để compare).
⚠️ CẢNH BÁO session tiếp (Session 12+)
- UAT manual budget flow — toggle ON/OFF + save + reload + verify auto-detect mode. Đặc biệt edit existing PE/HĐ có manual data → toggle phải auto-checked. Edit existing có Budget link → toggle auto-unchecked.
- Section 4 PE matrix "So với ngân sách" vẫn require Budget link — manual amount KHÔNG render comparison column. Document giới hạn này nếu UAT thắc mắc.
docs/database/schema-diagram.mdchưa cập nhật count 16→17 migration + 4 column mới. Defer cho audit định kỳ 2026-06-01 (per §6.4 cron) — nhỏ enough không phải selective rewrite.- CreateContractFromEvaluation đã carry forward manual fields PE→Contract. Verify khi UAT: PE có manual budget → tạo HĐ từ phiếu → HĐ inherit luôn.
- No new test added (rule §7 — feature mới = test-after, soak UAT 2-3 tuần ổn → viết happy path).
- Schema fields nullable — không phá HĐ/PE cũ (legacy null OK). Backward compatible.
TL;DR Session 10 (07/05 — PE workspace 2-panel)
Output session 10 — restructure leaf "Thao tác" PE từ page Create header riêng sang workspace 2-panel mirror HĐ Thầu phụ pattern:
- ✅ Spec chốt 5 câu trước code — Q1 Panel 2 chỉ data entry (KHÔNG Workflow/Approvals/History); Panel 1 pure picker (no inline edit/delete); Q2 mirror HĐ Thầu phụ (sticky "+ Thêm mới" + new→edit Panel 2); Q3 leaf "Danh sách" + "Duyệt" giữ 3-panel hiện tại; Q4 route mới
/workspace; Q5 Section 5 Ý kiến 4PB disable trong workspace (nhập khi duyệt). - ✅ Chunk 1 fe-admin (commit
ee0d360) — 3 file mới:PeListPanel.tsx(~180 LOC pure picker reuse-able + sticky "+ Thêm mới"),PeHeaderForm.tsx(~210 LOC extract header form từ CreatePage),PurchaseEvaluationWorkspacePage.tsx(~120 LOC 2-panel[320px_1fr]). 3 file sửa:PeDetailTabs.tsxthêm propmode?: 'detail' \| 'workspace'+ Section 5 hint amber khi workspace + forceopinionsReadOnly.Layout.tsxresolverPe_*_Create→/workspace?type=N.App.tsxroute mới. - ✅ Chunk 2 fe-user mirror (commit
ecf3c59) — 6 file y hệt content (rule §3.9 duplicate có chủ đích). - ✅ Verify: 2 build pass + dotnet test 83 vẫn pass mỗi chunk.
KHÔNG đụng BE / migration / schema / endpoint. Route /new cũ giữ tồn tại cho deep-link "Sửa header" button.
⚠️ CẢNH BÁO session tiếp (Session 11+)
- UAT live workspace với anh Kiệt + 2-3 user — feature mới UX khác (page Create cũ thành Dialog implicit qua sticky button + transition new→edit auto).
- Section 5 Ý kiến 4PB hint banner amber ở workspace mode chỉ là text gợi ý — KHÔNG block. User vẫn thấy existing opinions read-only. Nếu UAT thấy confused → có thể đổi thành collapsible section.
- Mobile: workspace KHÔNG có fallback — màn hình
<lg(< 1024px) Panel 2 ẩn (lg:block). Cần test mobile redirect →/purchase-evaluations/:idnếu UAT phát hiện. Hiện chỉ admin desktop dùng workspace. - Pe_*_Pending vẫn /purchase-evaluations?pendingMe=1 — leaf "Duyệt" 3-panel (giữ Panel 3 Workflow + readOnly Section 5 cho phép sign). UAT verify Drafter trình → TPB duyệt thấy Section 5 enable đúng.
- PE WorkflowPanel duplicate ở 2 chỗ — leaf "Danh sách" + "Duyệt" Panel 3 (3-panel). KHÔNG có ở "Thao tác" workspace. Verify route resolver active state highlight đúng (queryMatches helper từ session 3).
- Sửa header trong workspace — vẫn navigate sang
/new?id=(button "Sửa header" trong PeDetailTabs). Chưa wire inline edit qua Dialog. Nếu UAT yêu cầu → thêm proponEditHeadercho PeDetailTabs trigger Dialog reusePeHeaderForm.
Housekeeping today (sau Session 9)
- ✅ Audit định kỳ 2026-05 — combined skill + doc drift (commit
7dc0233). 5 drift patch (count 77→83 + 52→55 bảng), 1 skill content patch (contract-workflow Phase 9 cross-ref). KHÔNG tạo skill mới. Logdocs/changelog/skill-audit-2026-05.md. - ✅ Optional polish — fe-user Inbox PE section (commit
332a90f). useQuery/purchase-evaluations/inbox+ Panel 1 chia 2 section sticky header (HĐ + PE). PE click → navigate page riêng. - ✅ User Manual 7 file rewrite compact (commit
16c2c9c). User feedback "ko cần quá đầy đủ chi tiết, cho end-user họ làm". Bỏ field validation table + error troubleshoot table + FAQ chi tiết. Giữ tổng quan ngắn + numbered steps đơn giản. ~86 KB / 7 file (cũ ~123 KB ↓30%). Setuppackage.json+npm install docx@9.5.0+npm run gen:all. Lần sau sửa: edit_gen-*.js→ run gen:all.
Cron audit định kỳ: Claude SDK CronCreate auto-expire 7 days → KHÔNG fit monthly cadence. User cần setup OS Task Scheduler .bat script ping API hoặc manual trigger mỗi đầu tháng. Lần kế: 2026-06-01.
User Manual style rule (mới chốt session này): end-user docs BỎ field/error tables, FAQ chi tiết, phím tắt — GIỮ tổng quan ngắn + numbered steps + note/warn/tip critical. Phân biệt với rule §6.5 (KHÔNG cắt narrative) — đây là rule cho audience end-user, không phải agent dev đọc rationale.
TL;DR Session 9 (04/05 — Chunk E-bis sau Session 8)
Output session 9 — đóng tất cả Chunk E-bis defer từ session 8:
- ✅ FE PE WorkflowPanel — Section "Tiến trình duyệt 2-cấp phòng ban" group by phase × dept, highlight amber chờ TPB confirm, badge fuchsia bypass (cả fe-admin + fe-user).
- ✅ FE UsersPage UserManager — Column "Bypass" + button ShieldCheck toggle CanBypassReview, badge fuchsia khi enabled. UserDto thêm field.
- ✅ HĐ 2-stage logic —
ContractWorkflowServicethêm UserManager DI + mirror logic từ PE service.ContractDepartmentApprovalFeatures.csList query. EndpointGET /contracts/{id}/department-approvals. FEWorkflowHistoryPanelsection mới. - ✅ Budget 2-stage logic —
TransitionBudgetCommandHandlerthêm INotificationService + IDateTime DI + 2-stage.BudgetDepartmentApprovalFeatures.cs+ endpoint + FEBudgetWorkflowPanelsection. - ✅ 6 test PE 2-stage —
IdentityFixturesetup full Identity stack (DbContext SQLite + AddIdentityCore + AddRoles) reusable. 6 scenario: NV_Review_Blocks / TPB_Confirm_Allows / NV_Bypass / Admin_Skip / Reject_Sets / Resume_Jumps_Back. - ✅ Verify: Build pass + 83 test pass mỗi commit (54 Domain + 29 Infra: 17 codegen + 6 PE WF Application + 6 PE 2-stage).
- ✅ 5 commit pushed Gitea (E2 → E6).
⚠️ CẢNH BÁO session tiếp (Session 10+)
- UAT live ngay với anh Kiệt + 2-3 user — feature 2-stage đầy đủ cả 3 module + UX.
- Tests Contract + Budget 2-stage skipped — logic identical PE (cùng pattern, cùng entity shape). Pattern
PeTwoStageApprovalTestsreusable nếu UAT phát hiện regression riêng. - Bypass toggle audit — chưa log Changelog khi admin toggle CanBypassReview. Audit qua Identity standard column UpdatedAt only. Có thể cần thêm audit row riêng nếu UAT yêu cầu.
- Notify TPB cùng dept dùng
UserManager.GetRolesAsyncfilterDeptManager— verify production có user role DeptManager đúng (data already seeded). - fe-user KHÔNG có UsersPage — admin-only function. Bypass toggle chỉ ở fe-admin.
- 3 endpoint mới List dept-approvals PE/HĐ/Budget cùng pattern, reuse policy authz
*Read. - Cron audit định kỳ 2026-05-01 vẫn EMPTY (
No scheduled jobs). Có thể recreate khi user yêu cầu.
TL;DR Session 8 (04/05 — code lớn, 5 commit per-chunk)
TL;DR Session 8 (04/05 — code lớn, 5 commit per-chunk)
Output session 8 — đóng bug anh Kiệt + thêm 3 ràng buộc workflow:
- ✅ Migration 16
AddTwoStageDeptApprovalAndSmartReject— 4 ALTER (3 RejectedFromPhase int + Users.CanBypassReview bit) + 3 CREATE TABLE (Contract/PE/Budget DepartmentApprovalsUNIQUE (TargetId, Phase, Dept, Stage)) + 12 indexes. - ✅ Lock edit 17 handler thêm guard Phase != DangSoanThao (Contract Detail × 15 qua helper, PE Detail × 5 qua helper mới, Budget Detail × 3 inline).
- ✅ Smart reject + Resume 3 module — Reject = lưu phase nguồn + force về DangSoanThao. Resume = jump straight tới phase đã reject (skip phase trung gian, bypass policy guard).
- ✅ PE 2-stage logic trong
PurchaseEvaluationWorkflowService— TPB/CanBypass → Confirm; NV → Review only, BLOCK transition cho đến khi TPB confirm. - ✅ 3 endpoint mới:
GET /pe/{id}/department-approvals(List),PATCH /users/{id}/bypass-review(toggle), Notify TPB cùng dept khi NV review. - ✅ Verify: Build + 77 test pass mỗi commit. Migration applied LocalDB OK. Schema verified.
- ✅ 6 commit pushed (2 docs S7 + 5 code S8).
⚠️ CẢNH BÁO session tiếp (Session 9+)
- Bug fix anh Kiệt chỉ áp PE workflow. HĐ + Budget 2-stage scope DEFER cho khi UAT PE OK.
- FE Workflow Panel chưa update — workflow vẫn block đúng (BE), nhưng UX chưa hiển thị 2-stage progress. User test sẽ thấy phase không đổi mà không hiểu tại sao "stuck". Phải UAT với hint cho user trước khi code FE.
- FE UserManager toggle CanBypassReview chưa làm — tạm thời SET qua HTTP PATCH:
PATCH /api/users/{userId}/bypass-review Authorization: Bearer <admin> { "canBypassReview": true } - Test thực tế bug fix flow:
- Login
phuong.nguyen(NV.PRO, role=Procurement, DeptId=PRO) tạo phiếu PE type A - Trình DangSoanThao → ChoPurchasing
- phuong.nguyen click Duyệt phase ChoPurchasing → expect: phase KHÔNG đổi, có row Stage=Review
tra.bui(TPB.PRO, role=DeptManager) click Duyệt → expect: phase chuyển ChoCCM
- Login
- Notify TPB cùng dept dùng
UserManager.GetRolesAsyncfilterDeptManager. Best effort, fail OK. - Cron audit định kỳ 2026-05-01 đã quá hạn 3 ngày, vẫn EMPTY runtime. Cần manual trigger.
- Smart reject test: Reject phase ChoCCM → DangSoanThao + RejectedFromPhase=ChoCCM. Drafter sửa Detail + trình lại → jump straight tới ChoCCM (skip ChoPurchasing).
- Lock edit test: HĐ ở Phase=DangGopY → cố sửa Detail → expect 409 Conflict "đã trình duyệt, không thể chỉnh sửa".
TL;DR Session 6 (30/04 — không code, chỉ docs)
Output session 6 — pure docs work, không thay đổi code/test:
- ✅ Compact 3 file core: STATUS -27%, HANDOFF -32%, migration-todos -35% (-288 dòng tổng). 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),permission-matrix(12 menu → ~60 + inheritance roots),ef-core-migration(24 DbSet → 52 bảng). - ✅ Rule mới §7 Timing test: 1 bảng 5-row compact — feature mới = test-after, bug = test-before, critical algorithm = test-before, spec change = update test cũ, skip 5 loại.
- ✅ Rule mới §6.4 Audit + compact MD định kỳ: cadence + checklist + anti-pattern. Cross-ref §9.4 skill audit. KHÔNG rewrite toàn bộ.
- ✅ 77 test vẫn pass (Domain 54 + Infra 23). 0 thay đổi code.
- ✅ Cron 2026-05-01 fire mai — combined audit (skill + doc drift) theo §6.4 + §9.4.
Session 5 (29/04) đóng gần hết feature gap + bật test gate:
🎯 Headline outcomes
- ✅ Budget feature-complete — FE 3-panel pages cả 2 app, BE đã sẵn từ session 4
- ✅ PE/HD ↔ Budget integration — form select Budget filter Phase=DaDuyet + Project, cột "So với ngân sách" ở PE matrix với delta indicator
- ✅ PE Detail UI restructure match form chính thức PHIẾU TRÌNH KÝ (4 section đánh số)
- ✅ PE Workflow Designer admin UI
/system/pe-workflows/:typeCodeversioned với clone/edit/+Role/+User - ✅ Ý kiến 4 phòng ban — migration 15 + section sign-off 2x2 grid (Phê duyệt/CCM/MuaHàng/SM-PM)
- ✅ Tests Phase 1-2 — 71 unit test pass + CI gate fail-fast (Domain policy + Infra code generator)
Session 4 (28/04) đã có:
A. Module Ngân sách (Budget) BE — migration 14, +4 bảng, +11 endpoint
- 4 entity:
Budget(Header) +BudgetDetail(flat row) +BudgetApproval(history) +BudgetChangelog(audit log). - Enum
BudgetPhase5 state (DangSoanThao→ChoCCM→ChoCEO→DaDuyet + TuChoi). BudgetPolicy.Defaulthardcoded simple 3-step (Drafter→CCM→CEO) — chưa versioned (TODO khi user cần admin config UI).- Mã
NS-YYYYMM-XXXXRandom.Shared (chưa atomic — TODO khi format chính thức). - Link nullable:
Contract.BudgetId?+PE.BudgetId?đã có FK + index, FE chưa wire form. - Menu seed
Budgetsroot + 3 leaf (Bg_List/Bg_Create/Bg_Pending) order=27 icon Wallet. - Application: 11 CQRS handler ~340 LOC (Create/UpdateDraft/Transition/List/GetDetail/Delete + Detail CRUD auto-recompute TongNganSach + ListChangelogs).
- Api:
BudgetsController11 endpoint REST. - FE chưa làm — Priority 0 session 5.
B. 14 demo user Solutions thật
- PRO 5 (TPB tra.bui + 4 NV) + CCM 7 (TPB ngocanh.huynh + 6 NV) + ISO 1 (chau.le) + CEO 1 (huy.duong).
- Pwd
User@123456. Reconcile pattern (gotcha #38 4-field rename). - Tổng 30 user (16 sample cũ giữ + 14 Solutions thật mới).
Tổng cumulative: 52 DB tables, ~128 endpoints, 15 migrations, 41 gotchas (+3 CI fixes), 77 unit test (+6 PE WF Application), 11+ commit session 5 push lên Gitea.
🆕 CI/CD optimize (29/04 tối)
- ✅ Manual checkout bypass github.com — fix #108/#109 transient TCP timeout 21s. Run #110 pass.
- ✅ Path filter docs-only skip —
paths-ignoretrong on:push, commit MD-only KHÔNG trigger CI (verify512880c→ no run #113). - ⏸️ npm junction cache — thử ở #111 fail
tsc not found, rollback. Cần debug session sau (gotcha #40 doc rồi). - ✅ Tests Phase 3 mini (PE Workflow Designer) — 6 test versioning logic. Total 77.
⚠️ CẢNH BÁO session tiếp (Session 7+)
- CI test gate active — code →
dotnet test SolutionErp.slnxlocal → commit → push. Test fail = NO deploy. Workflow user §7 rules.md. - Timing test rule live (§7): feature mới = test-after, bug = test-before BẮT BUỘC, critical algorithm = test-before merge, spec change = update test cũ + code chung commit.
- Doc audit cadence live (§6.4): cuối phase compact, đầu tháng cron audit, KHÔNG rewrite toàn bộ. Cron fire mai (2026-05-01) — combined skill + doc drift theo checklist.
- Login email
admin@solutionerp.local/Admin@123456— domain default chưa đổi sang@solutions.com.vn(chỉ demo user rebrand). - Chưa xóa binding cũ
.huypham.vn— vẫn fallback. Sau verify stable →.\migrate-domains.ps1 -RemoveOld -SkipCerttrên VPS. - win-acme scheduled task "unhealthy" — fix trước cert expire 2026-06-18.
- Export phiếu PDF/Excel PE — pending vô thời hạn (user nói không quan trọng).
- 3 feature mới chưa test (PE Opinion Upsert / Budget validate / Contract BudgetId carry) — đợi UAT phát sinh bug → áp rule §7 (regression test before fix). Hoặc soak 2-3 tuần ổn → viết happy path.
- G-084: VPS shared VietReport — mọi reverse proxy mới phải
127.0.0.1+ bind loopback IPv4 explicit.
⭐ Skills (.claude/skills/) — PHẢI dùng khi task khớp
| Domain (3) | Ops (3) |
|---|---|
contract-workflow — state machine + versioned WF |
dependency-audit-erp — npm/dotnet CVE scan |
form-engine — render docx/xlsx + PDF |
ef-core-migration — EF migration + 3-file rule |
permission-matrix — role × menu × CRUD |
iis-deploy-runbook — 3 site IIS + win-acme + runner |
Audit cron: 0 9 1 * * (9:00 AM ngày 1 mỗi tháng). Workflow: docs/rules.md §9.4. Lần kế: 2026-05-01.
Quy tắc: KHÔNG bulk-clone repo skill 3rd party. Chỉ skill PROJECT-SPECIFIC. Đầy đủ: docs/rules.md §9.
Ở đâu rồi?
| Phase | Trạng thái |
|---|---|
| 0 Draft | ✅ Done |
| 1 Alpha Core (foundation + đợt 2 CRUD + Permission) | ✅ Done |
| 2 Form Engine MVP + iter 2 (upload UI + .doc auto-convert + PDF export) | ✅ Done |
| 3 Workflow MVP (9 phase + code gen) + iter 2 (SLA job + attachment + notify) | ✅ Done |
| 4 Report MVP (Dashboard + Excel) + user-specific dashboard | ✅ Done |
| 5 Prep + 5.1 Security + Users Mgmt | ✅ Done |
| 5 Deploy prod (3 domain HTTPS live) | ✅ Done |
| Tier 3 (Attach + Realtime + Form builder + PDF + Versioned WF + Nested menu + Permission 3-panel) | ✅ Done |
| Skill governance (6 skill project-level + audit cron 1/tháng) | ✅ Done |
| 3-panel List/Inbox/Thao tác + sidebar accordion + UserDashboard | ✅ Done |
| 4-bảng data model (Header + 7 Details + Workflow + Changelog audit) | ✅ Done |
| Mã HĐ gen tại Create + backfill legacy | ✅ Done |
| 4 master catalogs (Units/Materials/Services/WorkItems) + datalist autocomplete | ✅ Done |
| Roles VN labels (ShortName) + Users dept/position + 13 demo users | ✅ Done |
RolesPage CRUD /system/roles + custom role admin |
✅ Done |
| 7 demo HĐ varied phases + details + comments + approvals workflow | ✅ Done |
| User-kind approver runtime guard + Warning 20% SLA | ✅ Done |
| Edit detail row inline (7 typed Update commands + EditRowDialog) | ✅ Done |
| Master expand 15 NCC + 8 Project + backfill demo HĐ diverse | ✅ Done |
Deps audit script (scripts/deps-audit.ps1) |
✅ Done |
| Module Duyệt NCC (tiền-HĐ) E2E — 10 bảng + 2 workflow + Kế thừa HĐ | ✅ Done |
| PE polish iter 2 — flat layout + per-NCC attachments + readOnly Duyệt + email rebrand | ✅ Done |
| Domain rebrand huypham.vn → solutions.com.vn — 3 subdomain + cert + CORS + FE bundle | ✅ Done |
| Module Ngân sách BE + 30 user — 4 bảng + 11 endpoint + workflow simple | ✅ Done (S4) |
| Module Ngân sách FE — 3-panel pages + Detail tabs + Workflow Panel cả 2 app | ✅ Done (S5) |
| PE/Contract → Budget integration — form Select + cột "So với ngân sách" PE matrix | ✅ Done (S5) |
PE Workflow designer admin UI /system/pe-workflows/:typeCode |
✅ Done (S5) |
| Ý kiến 4 phòng ban PE — migration 15 + section sign-off 2x2 grid | ✅ Done (S5) |
| Tests Phase 1-2 + CI gate — 71 test (Domain policy + Infra code generator) | ✅ Done (S5) |
| CI manual checkout bypass github.com + Path filter docs-only skip | ✅ Done (S5) |
| Tests Phase 3 mini — 6 test PE Workflow Designer versioning (total 77) | ✅ Done (S5) |
Export phiếu PDF/Excel PE — IDocumentConverter + template |
📝 Pending (không quan trọng) |
| Tests Phase 3 full — Application handlers (UpsertOpinion + Budget link validation, cần UserManager setup) | 📝 Pending |
| Tests Phase 4-5 — API smoke + FE Vitest | 📝 Pending (làm khi cần) |
| npm cache CI optimize (debug junction Move-Item issue #40) | 📝 Pending |
| 9+ Post-launch (E-signature, Bravo/SAP, Mobile, AI) | 📝 Future |
Run nhanh
# Terminal 1 — API (auto seed 12 role + 9 dept + 5 supplier + 3 project + 8 template + 7 workflow definition + 28 ContractType menu + 7 workflow menu)
dotnet run --project src\Backend\SolutionErp.Api
# Terminal 2 — Admin FE
cd fe-admin && npm run dev # → http://localhost:8082
# Terminal 3 — User FE
cd fe-user && npm run dev # → http://localhost:8080
Login: admin@solutionerp.local / Admin@123456
Quick sanity-check
Admin (:8082):
/dashboard→ "Của tôi" row 4 card + KPI cards + charts/contracts→ list toàn bộ, filter phase/supplier/project/contracts/new?type=5→ tạo HĐ Mua bán, pre-select type từ URL/contracts/{id}→ timeline + action dialog + attachments drag-drop + WorkflowSummaryCard/system/workflows→ 7-card landing (Thầu phụ/Giao khoán/NCC/Dịch vụ/Mua bán/NguyenTacNcc/NguyenTacDv)/system/workflows/MuaBan→ DefinitionCard active + history + "Tạo phiên bản mới" modal với Steps + Approvers (+Role / +User)/system/permissions→ 3-panel layout (Role list | Menu×CRUD matrix | Granted stats)/system/users→ Users CRUD + assign roles/forms→ upload .docx/.xlsx + render dialog Form↔JSON + Tải PDF
User (:8080):
/inbox?type=5→ HĐ Mua bán chờ role mình/my-contracts?type=2→ HĐ Thầu phụ của tôi/contracts/new?type=3→ tạo HĐ NCC- Sidebar nested: 📄 Hợp đồng → expand 7 type → expand "HĐ Mua bán" → Danh sách / Thao tác / Duyệt
Realtime check:
- Login 2 tab (admin + user) → user tạo comment / transition → admin nhận toast + bell +1
Cần làm kế tiếp
🔥 Priority 0 — Session 6 (Ops + UAT focus)
Đa số feature gap đã đóng. Còn lại chủ yếu Ops/UAT + optional polish.
A. Hard blockers (chờ user / ops):
- UAT thật 1 tuần với 2-3 user — Drafter (QS/NV.PB) + CCM + BOD ghi bug/friction
- SMTP config → bật Email outbox (BLOCKED chờ user cấp host/user/pass)
- Rotate credentials — admin + 30 demo + SA + vrapp + JWT secret
- Schedule SQL backup daily —
scripts/backup-sql.ps1chưa schedule Task Scheduler
B. PE feature gap còn lại:
- Export phiếu PDF/Excel PE — pending (user nói không quan trọng lắm). Khi cần: tái dùng
IDocumentConverter+ templatePE-TrinhDuyet.docx.
C. Optional polish (làm khi UAT phát sinh):
- Budget MaNganSach atomic sequence — chốt format → migration
AddBudgetCodeSequencesmirror Contract/PE - 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):
- 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
Đã xong session 5 (check STATUS Recently Done):
Module Ngân sách FE — 3-panel pages cả 2 app✅PE/Contract → Budget integration + cột "So với ngân sách"✅PE Detail UI restructure 4 section đánh số✅PE Workflow Designer admin UI✅Ý kiến 4 phòng ban (migration 15)✅Tests Phase 1-2 + CI gate (71 test)✅
A. Hard blockers (chờ user / ops)
- UAT thật 1 tuần với 2-3 user — hard requirement từ roadmap. Kiến nghị:
- User A: Drafter (QS/NV.PB) — tạo 3 HĐ mỗi type, đi hết 9 phase
- User B: CCM — duyệt phase 6
- User C: BOD — duyệt phase 7
- Ghi bug / friction / đề xuất → backlog iter 2
- SMTP config để bật Email outbox:
Khi có → thêm
"Email": { "Host": "smtp.gmail.com", "Port": 587, "Username": "...", "Password": "...", "From": "noreply@solutionerp.local" }MailKit,IEmailSender, hook vàoNotificationService.CreateAsyncngay trước khi enqueue realtime push. - Rotate credentials — SA SQL password, vrapp password, JWT secret prod, Gitea runner registration token
- Schedule SQL backup —
schtasks /create /tn "SolutionErp Backup" /tr "powershell -File C:\...\scripts\backup-sql.ps1" /sc DAILY /st 03:00
A1. Định kỳ — Skill audit
Cadence: Mỗi đầu tháng (4 tuần). Lần audit kế tiếp: 2026-05-01.
Workflow xem docs/rules.md §9.4. Tóm tắt:
- Cross-check 6 skill hiện có với STATUS / gotchas / migration-todos
- Check repo nguồn 3rd party (alirezarezvani/claude-skills) có gì mới
- Update / archive / add skill nếu cần
- Log vào
docs/changelog/skill-audit-2026-05.md
Trigger: user nói "audit skill" hoặc tự chạy đầu Phase mới.
B. Polish iterations (optional — khi UAT phát sinh)
- Roles CRUD — admin tạo custom role ngoài 12 hardcoded (
Domain.Identity.AppRoles) - User-kind approver runtime — data model
WorkflowStepApprover.Kind=User+AssignmentValue=userIdđã có, chỉ cần:// ContractWorkflowService.TransitionAsync (bổ sung): var userApprovers = step.Approvers.Where(a => a.Kind == ApproverKind.User) .Select(a => Guid.Parse(a.AssignmentValue)); if (userApprovers.Any() && !userApprovers.Contains(actorUserId)) throw new ForbiddenException(); - Grant
Workflows.Readcho non-admin role trong PermissionsPage → menu Wf_* auto-visible (inheritance đã có) - Warning notification 20% SLA — job emit khi
SlaDeadline - now < sla * 0.2 && !SlaWarningSent, set flag - Reject → DangSoanThao E2E test với 3 role khác nhau
- Deps scan CI —
dotnet list package --vulnerable+npm audit --audit-level=high
C. Non-goals / parked
- E-signature (VNPT CA / FPT CA) — Phase 6
- Bravo/SAP import NCC — Phase 6
- Mobile app — Phase 6
- AI OCR scan HĐ — Phase 6+
Lưu ý kỹ thuật quan trọng
Đọc gotchas.md (41 bẫy) trước khi:
- Thêm package mới → .NET 10 compat (MediatR 14 fail → dùng 12.4.1)
- Debug TS enum error → dùng const-object pattern (
erasableSyntaxOnly) - Expression tree lỗi → tách switch ra ngoài LINQ
- Deploy Windows Feature (WebSockets, etc.) → unlock section ở applicationHost (gotcha #25)
- Workflow transition 403 → check
Contract.WorkflowDefinitionIdpin đúng không - Migration lỗi → 3 file đầy đủ (Designer + Migration + Snapshot)
Versioned workflow — quick ref
Contract.WorkflowDefinitionId pin tại Create. ContractWorkflowService.LoadAsync resolve order: pinned def → admin override → hardcoded Registry.For(type). Invariant: HĐ cũ giữ policy cũ qua FK Restrict. Detail: workflow-contract.md §7bis.
File đang active
Cấu trúc thư mục + file map: PROJECT-MAP.md. Git state: git log --oneline -10.
Credentials + URLs
admin@solutionerp.local / Admin@123456 ← Admin (QTV)
Demo users — 30 user (User@123456):
── 16 sample @solutionerp.local (giữ cho test legacy) ──
bod.huynh, bod.le Tổng GĐ + Phó GĐ NĐUQ
pm.nguyen GĐ Dự án FLOCK 01 (PM)
ccm.tran, pro.pham, fin.do TPB CCM/PRO/FIN
act.vu, equ.bui, hra.dang Kế toán trưởng / TPB EQU / TPB HRA
qs.hoang, qs.ngo QS công trường (NV.PB)
nv.cao, nv.dinh, nv.truong NV Cung ứng/Tài chính/CCM (NV.PB)
bod.tran, pm.le Bonus NĐUQ + PM thứ 2
── 14 Solutions thật @solutions.com.vn (session 4) ──
PRO 5: tra.bui (TPB) + phuong.nguyen, binh.lethanh, danh.huynh, dat.tran (NV)
CCM 7: ngocanh.huynh (TPB) + ha.dao, cuong.do, long.le, ha.nguyen,
dung.nguyen, anh.nguyen (NV)
ISO 1: chau.le
CEO 1: huy.duong
⚠ Rotate ALL passwords trước UAT thật
- API prod: https://api.solutions.com.vn —
/health/live,/health/ready - Admin FE prod: https://admin.solutions.com.vn
- User FE prod: https://eoffice.solutions.com.vn
- API dev: http://localhost:5443 —
/swagger(Dev only) - Admin FE dev: http://localhost:8082
- User FE dev: http://localhost:8080
- SQL dev:
(localdb)\MSSQLLocalDB/SolutionErp_Dev - SQL prod:
.\SQLEXPRESS/SolutionErp/vrapp(⚠️ rotate)
Đánh giá nhanh
Tốt:
- 3 domain HTTPS prod live, CI/CD xanh
- Tier 3 feature-complete: attachment, realtime, form builder (upload + DynamicForm + PDF), versioned workflow (admin-configurable per ContractType, pin per contract), nested menu per type, 3-panel permissions
- Clean-arch 3-project split đúng cho 2 cross-cutting service (realtime + document-converter)
- 26 gotchas tích lũy, 8 session log, 40 docs — agent onboard nhanh
- Invariant critical: "HĐ cũ giữ quy trình cũ" guaranteed by pinning (reference-based immutability, không snapshot copy)
Rủi ro còn:
- UAT thật chưa chạy → có thể phát hiện edge case missing
- SMTP chưa có → notification chỉ in-app (toast + bell), không email
- User-kind approver chưa enable guard runtime (designer cho pick, nhưng transition dùng Role fall-back)
- Credentials chưa rotate
- SQL backup chưa schedule Task Scheduler