# Session 23 turn 5 — 2026-05-15 — Plan O HOTFIX 4 lookup sites cascade **Dev:** Claude Opus 4.7 1M (em main solo — bug fix reasoning chain cross 4 sites) **Duration:** ~1.5h **Base commit:** `fb3c22c` (S23 t4 Plan N wrap) **Final HEAD:** `` (Plan O atomic + docs) **Total commits Plan O:** **2** (atomic O1-O5 + docs) ## 🎯 Trigger session Bro UAT 2026-05-15 sau Plan N deploy phát hiện 2 bug mới: **Bug 1 — Trả lại Người chỉ định fail:** - Phiếu PE/2026/A/029 workflow QT-DN-V2-001 v14 - Actor NV Test (UAT V2) trong OR-of-N slot Bước 2 Cấp 1 (4 NV) - Click ← Trả lại → Dialog mở (Plan N hotfix wire OK) - Chọn "Trả lại Người chỉ định" + Bùi Lê Thủy Trà (Bước 1 Cấp 2) - Click Xác nhận → **Toast: "Không phải lượt bạn — chỉ NV Cấp duyệt hiện tại mới được Trả lại / Từ chối phiếu"** **Bug 2 — F2 Duyệt thẳng KHÔNG đến Cấp cuối:** - Click ✓ Duyệt + checkbox skipToFinal → pointer trỏ đến **Phan Văn Chương Bước 2 Cấp 2** thay vì **Nguyễn Văn Trường Bước 3 Cấp 1 (BOD)** - F2 logic [Service.cs:483-524](src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs:483) đã đúng (`lastStepIdx = steps.Count - 1` + `lastLevelMaxOrder`) - **Defer follow-up** — cần verify workflow v14 DB structure (có thể workflow chưa setup Bước 3 đầy đủ) ## 🔍 Em main audit grep — Phát hiện 5 lookup sites cùng bug Plan N ```bash grep -n "FirstOrDefault.*Order ==" src/Backend/**/*.cs ``` | # | File | Line | Purpose | Pre-Plan O | |---|---|---|---|---| | 1 | `PurchaseEvaluationWorkflowService.cs` | 201 | `EnsureCanRejectV2Async` actor match guard | ❌ Bug bro UAT | | 2 | `PurchaseEvaluationWorkflowService.cs` | 248 | `ApplyReturnModeAsync` read Allow flag | ❌ Bug | | 3 | `PurchaseEvaluationDetailFeatures.cs` | 72 | F3 `EnsureEditableForDetailsAsync` | ❌ Bug | | 4 | `PurchaseEvaluationFeatures.cs` | 311 | F4 `AdjustBudgetCommand` ChoDuyet branch | ❌ Bug | | 5 | `PurchaseEvaluationFeatures.cs` | 765 | GetPe handler currentLevelOptions | ✅ Plan N S23 t4 fixed | → **Plan N S23 t4 chỉ catch 1/5 sites**. Plan O cần fix 4 sites còn lại cùng pattern. ## 🌳 Plan O 2 chunk execution ### Chunk O1-O5 — Atomic BE fix + regression tests (commit `ae01ca5`) 👤 Chủ trì Solo (bug fix reasoning chain cross 4 sites — Implementer auto-refuse criteria #4). **O1 fix `EnsureCanRejectV2Async:201` (5 LOC):** ```diff - var currentLevel = step.Levels.FirstOrDefault(l => l.Order == curLvl); - if (currentLevel?.ApproverUserId != actorId) + var currentLevel = step.Levels.FirstOrDefault(l => + l.Order == curLvl && l.ApproverUserId == actorId); + if (currentLevel is null) throw new ForbiddenException("Không phải lượt bạn — ..."); ``` **O2 fix `ApplyReturnModeAsync:248` (signature change + caller cascade):** ```diff private async Task ApplyReturnModeAsync( PurchaseEvaluation evaluation, WorkflowReturnMode mode, Guid? returnTargetUserId, + Guid? actorUserId, bool isAdmin, CancellationToken ct) - currentLevel = step.Levels.FirstOrDefault(l => l.Order == evaluation.CurrentApprovalLevelOrder); + currentLevel = step.Levels.FirstOrDefault(l => + l.Order == evaluation.CurrentApprovalLevelOrder + && l.ApproverUserId == actorUserId); ``` Caller `TransitionAsync:94` cũng update: `actorUserId` thêm vào call: ```diff - ApplyReturnModeAsync(evaluation, effectiveMode, returnTargetUserId, isAdmin, ct); + ApplyReturnModeAsync(evaluation, effectiveMode, returnTargetUserId, actorUserId, isAdmin, ct); ``` Non-admin actor không match slot → currentLevel=null → validation block line 252 skip → mode logic switch proceed (KHÔNG dùng currentLevel object, chỉ dùng curStepIdx + curLevel int values). Admin bypass existing line 252. **O3 fix `EnsureEditableForDetailsAsync:72` (F3 guard, merge filter + collapse 3 throw → 2 throw):** ```diff - var level = step?.Levels.FirstOrDefault(lv => lv.Order == levelOrder); - if (level is null) throw ConflictException("schema lỗi"); - if (!level.AllowApproverEditDetails) throw ConflictException(...); - if (level.ApproverUserId != actorUserId) throw ForbiddenException(...); + var level = step?.Levels.FirstOrDefault(lv => + lv.Order == levelOrder && lv.ApproverUserId == actorUserId); + if (level is null) throw ForbiddenException("Chỉ NV phụ trách..."); + if (!level.AllowApproverEditDetails) throw ConflictException(...); ``` **O4 fix `AdjustPurchaseEvaluationBudgetCommandHandler:311` (F4 ChoDuyet branch, cùng pattern):** ```diff - var level = step.Levels.FirstOrDefault(l => l.Order == curLvl); - if (level is null) throw ConflictException("schema lỗi"); - if (!level.AllowApproverEditBudget) throw ConflictException(...); - if (level.ApproverUserId != actorId) throw ForbiddenException(...); + var level = step.Levels.FirstOrDefault(l => + l.Order == curLvl && l.ApproverUserId == actorId); + if (level is null) throw ForbiddenException("Chỉ NV phụ trách..."); + if (!level.AllowApproverEditBudget) throw ConflictException(...); ``` **O5 Regression test** (`PurchaseEvaluationPerNvLookupRegressionTests.cs` new file, 3 test): 1. `TransitionReject_ActorD_LastInSlot_AllowsRejectViaDrafterMode` — Actor D (non-first-row in OR-of-N) trả lại Drafter mode → no throw. Pre-fix: throw "Không phải lượt bạn" vì handler chỉ check row đầu DB (Actor A). 2. `TransitionReject_Outsider_NotInSlot_ThrowsForbidden` — Outsider không trong slot → throw đúng intent (verify fix KHÔNG over-permissive). 3. `TransitionRejectOneLevel_ActorC_HasFlagWhileOthersDont_AllowsMode` — Actor C only tick AllowReturnOneLevel, 3 NV khác KHÔNG. Actor C click "Trả lại 1 Cấp" → mode allowed. Pre-fix: read flag từ row A (false) → throw ConflictException "không bật mode OneLevel". **Verify:** - `dotnet build SolutionErp.slnx` clean (0 err, 2 warn pre-existing DocxRenderer) - `dotnet test SolutionErp.slnx` **111/111 PASS** (+3 từ 108: 58 Domain + 53 Infra) - 3 Plan O test individual PASS ### Chunk O7 — Docs + memory + push (this commit) - `docs/STATUS.md` Last updated S23 t5 + stats 111 test (+3) - `docs/HANDOFF.md` TL;DR S23 t5 với 5 sites enumerate - Session log file này - Memory user-level `feedback_per_nv_permission_scope.md` CRITICAL HOTFIX S23 t5 section: - 5 sites checklist documented - 4× cumulative gap analysis updated (3× refactor wire SAME bug toàn 5 sites — 2 sessions hotfix Plan N + Plan O) - Audit checklist grep `FirstOrDefault.*Order ==` - Reviewer pre-commit guideline grep enum lookup sites ## 📊 Stats Plan O chốt | Metric | Trước (S23 t4) | Sau (S23 t5) | Δ | |---|---|---|---| | Migrations | 31 | 31 | 0 | | Endpoints | ~145 | ~145 | 0 | | FE pages | 34 | 34 | 0 | | **Unit tests** | 108 | **111** | **+3** (3 site regression: OR-of-N actor + outsider + flag-only-1) | | Gotchas | 47 | 47 | 0 | | Memory entries | 20 | 20 | 0 (1 entry CRITICAL HOTFIX section reinforced 2 lần: S23 t4 + S23 t5) | | Skills | 6 | 6 | 0 | | Sub-agents | 4 | 4 (em main solo Plan O) | active | | Commits Plan O | — | **2** (atomic O1-O5 + docs O7) | pending push | ## 🎯 Multi-agent ROI evidence Plan O | Spawn | Agent | Cost | Output | Catch | |---|---|---|---|---| | Audit grep | 👤 Chủ trì | <1K | 5 lookup sites enum file:line | em main solo grep nhanh hơn spawn Investigator | | O1-O4 fix | 👤 Chủ trì | ~self | 4 site fix surgical ~30 LOC BE | Pattern uniform sau Plan N | | O5 tests | 👤 Chủ trì | ~self | 3 regression test new file ~210 LOC | First iter compile error (using missing) — fix add `SolutionErp.Application.PurchaseEvaluations.Services` namespace | | O7 docs | 👤 Chủ trì | ~self | docs + memory update | — | | CICD verify | 🟩 (pending spawn) | ~150K | post-deploy verify Plan O | TBD | **Total Plan O spawn cost (excl CICD):** ~0 sub-agent — em main solo cross 4 sites bug fix reasoning chain. Per directive Thứ 9: bug fix tightly coupled → em solo. Investigator catch root cause Plan N đã enough — Plan O = follow-up surgical fix. **Anti-pattern caught Plan N → Plan O:** Plan N S23 t4 fix 1 site (Plan N Investigator brief mention 9 surface points). Em main + Investigator + Reviewer ALL MISS enum 5 lookup sites — chỉ catch 1 GetPe handler. Bug regression 4 sites lại ở prod 1 ngày sau Plan N deploy → 4 sites bug present 2 ngày prod cumulative. **Pattern reinforced cross-project:** Khi refactor schema 1-row-per-role (OR-of-N) + bug fix lookup site → BẮT BUỘC grep enum tất cả lookup sites cùng pattern, KHÔNG fix theo bug report mỗi lần. Investigator pre-flight cho bug fix tightly coupled phải `grep -rn "FirstOrDefault.*Order =="` enumerate trước. ## ⚠️ Pending S23 t6+ - 🟩 **CICD Monitor post-deploy verify Plan O** — spawn sau push - 🔍 **Bug 2 F2 follow-up** — verify workflow v14 DB structure thực tế (BOD Bước 3 setup đúng?). Nếu DB OK + F2 logic line 483-524 đúng → có thể là FE timeline render issue - 🟦 **Investigator follow-up gotcha #41/#48 candidate** controlled test paths-ignore (carry từ Plan M) - 🟡 **Plan B Contract V2 wire (Mig 32+33)** — HIGH priority next - ⛔ **Plan F drop V1** — defer - 🟢 **Plan H PE PDF Export** — LOW priority - 🟡 **Plan I RAG Hybrid setup** — defer - 🔧 **Gotcha #47 paths-ignore agent-memory** — pending ## 📋 Pattern reinforced Plan O 1. **5 lookup sites enum SOLUTION_ERP PE module** sau Mig 29 OR-of-N — documented trong memory user-level `feedback_per_nv_permission_scope.md` CRITICAL HOTFIX S23 t5 section 2. **Grep `FirstOrDefault.*Order ==` audit checklist** cho future flag F5+ HOẶC migration mới có OR-of-N pattern 3. **Atomic commit BE fix + regression test** (per `feedback_uat_skip_verify` rule Plan L lesson) — Service refactor semantic BẮT BUỘC test cùng commit 4. **Em main solo cross-stack bug fix reasoning chain** — Implementer auto-refuse criteria #4 (bug fix involving reasoning chain). Investigator pre-flight Plan N đã enough — Plan O = follow-up surgical fix 5. **Method signature cascade** — Khi thêm param `Guid? actorUserId` vào helper internal, phải update ALL callers (1 caller trong này — TransitionAsync:94) ## References - Memory user-level updated: `feedback_per_nv_permission_scope.md` CRITICAL HOTFIX S23 t5 section (5 sites enum + audit grep checklist) - Files: - [PurchaseEvaluationWorkflowService.cs:94,201,212-248](src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs:201) (O1+O2) - [PurchaseEvaluationDetailFeatures.cs:72](src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationDetailFeatures.cs:72) (O3) - [PurchaseEvaluationFeatures.cs:311](src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs:311) (O4) - [PurchaseEvaluationPerNvLookupRegressionTests.cs](tests/SolutionErp.Infrastructure.Tests/Services/PurchaseEvaluationPerNvLookupRegressionTests.cs) (O5) - Rules: §3.9 mirror 2 FE, §7 test timing test-before (regression bug), `feedback_per_nv_permission_scope` (S23 t5 5 sites enum), `feedback_uat_skip_verify` (Service refactor + test atomic)