diff --git a/.claude/agent-memory/cicd-monitor/MEMORY.md b/.claude/agent-memory/cicd-monitor/MEMORY.md index 3526292..37f4cbb 100644 --- a/.claude/agent-memory/cicd-monitor/MEMORY.md +++ b/.claude/agent-memory/cicd-monitor/MEMORY.md @@ -139,6 +139,38 @@ Flag commit nếu thấy ` via useMemo at top of Tab component, expose resolveXxxName(entry) helper: 1) trust entry.userName if non-empty, 2) lookup userMap by entry.userId, 3) fallback "Hệ thống". Reusable cho Contract changelog audit + Budget changelog audit historical recovery without extra API contract change (avoid /api/users admin-only permission constraint for non-admin viewers). Mirror 2 FE app §3.9 identical logic. Token cost ~10k. **Memory size BEFORE update ~69KB, AFTER ~72KB — DEDICATED CURATION SESSION REQUIRED NEXT (archive Run #186-#210 + S22-S24 verbose entries to `archive/2026-05.md` priority MAX REINFORCED).** + +- **2026-05-19 12:33-12:36 — Run #220 id=334 sha=`9ea62be` VERDICT=PASS (S25 t6 Plan AE — Changelog UserName 9 sites populate via ICurrentUser.FullName fallback Email).** Push range cumulative S25 remote = `e23f51c..9ea62be` (6 commits Plan AB+AC+AC2+AD+AE since S24). Tip commit Plan AE: 2 BE-only files `PurchaseEvaluationFeatures.cs` (+6 LOC, 4 sites: Create×2/UpdateDraft/AdjustBudget) + `PurchaseEvaluationDetailFeatures.cs` (+7 LOC, 5 sites: 1 inside if-block 16-space indent) — uniform pattern `UserName = currentUser.FullName ?? currentUser.Email`. Duration ~3m25s (~baseline). Pre-push em main local: `dotnet test SolutionErp.slnx` PASS 111/111 (~5s). Status poll 4 iter (12:33→12:36) status `running → success`. **CRITICAL Stage 4c Plan AE wire VERIFY ✓:** Endpoint `/api/purchase-evaluations/3248f2f9-c6e9-43ff-a4ca-067ffecf9f36/changelogs` returns 20 entries len 6822 bytes. Response shape has `userName` field accessible ✓. **Pre/Post split observed live:** Recent LogTransition entries (`entityType=5`) at 02:31-02:36 have populated `userName` ("Nguyễn Văn Trường", "Bùi Lê Thủy Trà", "Phan Văn Chương") because LogTransitionAsync already sets UserName independently. **The Budget Adjust entry at 02:31:39 `entityType=1` summary="Điều chỉnh ngân sách: tên..." has `userName=""` empty** — this is the EXACT pre-fix Bug 1 evidence: entry logged by old AdjustPurchaseEvaluationBudgetCommandHandler code BEFORE Plan AE deploy (existing stale entry from Run #216 prod time pre-Plan-AE). Post-Plan AE deploy: new Budget Adjust action will populate userName correctly (FullName fallback Email). Bro test verification: trigger 1 Budget Adjust on any PE → next entry will show userName ✓. Stage 4a Auth admin: HTTP 200 token len 468 ✓. Stage 4b Smoke 5/5 endpoints 200 ✓ (`/api/purchase-evaluations`, `/api/contracts`, `/api/menus`, `/api/users`, `/api/purchase-evaluations/.../changelogs`). **Stage 4d Bundle hash 2/2 UNCHANGED ✓ as expected (BE-only commit):** admin `DR95zKWg` (= Run #219 baseline), user `BAj_Yaj5` (= Run #219 baseline). Stage 4e Health ready 200 "Healthy" + Health live 200 ✓. Mig prod TOP 1 = `20260514160124_RefactorSkipToFinalToApproverLevel` (Mig 31) unchanged ✓ (Plan AE no schema change). **Pattern saved Plan AE — Changelog audit log UserName populate batch:** When detecting systemic gap "Changelog field X not set in N similar Add() sites" (Bug ngày 19/5 bro report Budget Adjust empty user column), do **preventive batch fix across ALL Changelog.Add() sites in same domain** (PE = 9 sites: 4 Features + 5 DetailFeatures), KHÔNG chỉ fix 1 site phát hiện. Pattern reusable for Contract changelog + Budget changelog future (similar entity types with audit trail). Source pattern `UserName = currentUser.FullName ?? currentUser.Email` — assumes ICurrentUser is injected scoped DI (already standard). Side benefit: 8 preventive sites future-proof against same bug pattern. Token cost ~14k. **Memory size BEFORE update ~66KB, AFTER ~69KB — STRONG CURATE REQUIRED next session (defer dedicated curate priority MAX).** + +- **2026-05-19 10:21-10:25 — Run #216 id=330 sha=`8c05947` VERDICT=PASS (S25 t2 Plan AB Chunk A2 — fix Plan M tests filter LogTransition entry post Plan AB).** Push range cumulative Plan AB remote = `e23f51c..8c05947` (2 commits Plan AB total). Tip commit Chunk A2: 1 test file `tests/SolutionErp.Infrastructure.Tests/Services/PurchaseEvaluationWorkflowServiceReturnModeTests.cs` +7/-2 LOC — 2 Plan M edge case tests (OneLevel + OneStep AtStep1Level1) add `.Where(c => c.Summary!.Contains("Chuyển phase"))` filter trước `OrderByDescending(CreatedAt).First()` để pick LogTransition entry (chứa ContextNote) thay vì Plan AB new Changelog entry (Summary="Đã xử lý Trả lại — ..." no ContextNote, SAME CreatedAt SQLite frozen clock tie-break). Plan AB Chunk A code `cdfd542` KHÔNG bị revert — Bug 1+Bug 2 fix giữ nguyên. Duration ~3m17s (baseline 3-4 min — full pipeline tests + builds + deploy + smoke). Test gate PASS: test_domain 58/58 ✓ + test_infra 53/53 ✓ (2 Plan M tests now PASS — verified live). Build BE + FE × 2 PASS. Deploy NSSM + IIS recycle PASS. **CRITICAL Stage 4c Plan AB cumulative live wire VERIFY 2/2 ✓ (cdfd542 BE + Chunk A2 test fix DEPLOYED via Run #216):** + - Bug 1 Budget Adjust entry LIVE on PE c6e9 (`3248f2f9-c6e9-43ff-a4ca-067ffecf9f36` — task brief id `c8e9` typo, actual `c6e9`): entityType=1 (Header), summary="Điều chỉnh ngân sách: tên 'Ngân sách mới update thử ccm' → 'Ngân sách mới', số tiền 5,200,000,000đ → 5,300,000,000đ [Approver Bước 2/Cấp 2]", userId=Phan Văn Chương ✓ + - Bug 2 Return Mode entries LIVE on PE c6e9: multiple entityType=5 LogTransition entries với contextNote chứa "Trả về..." text: "Trả về Bước 1 Cấp 2", "Trả về Bước 2 Cấp 2", "Trà xem lại phần so sánh [Trả về Người chỉ định — Bước 1 (Phòng 1) Cấp 2]" (user comment + auto context append pattern Plan AB Chunk A) ✓ + - Stage 4a Auth admin: HTTP 200 token len 468 ✓ + - Stage 4b Smoke 5/5 endpoints 200 ✓ (`/api/purchase-evaluations`, `/api/contracts`, `/api/menus`, `/api/users`, `/api/purchase-evaluations/{id}/changelogs`) + - **Stage 4d Bundle hash 2/2 ROTATED ✓:** admin `CZdXQ2eo → CrHpBYFM` ✓, user `DCwhhey2 → C3bCWZ90` ✓ — Plan AB cumulative shipped 2 FE PeDetailTabs.tsx changes from Chunk A `cdfd542` (test file commit doesn't change FE bundle; rotate proxy reflects cumulative Plan AB delta from Run #214 baseline since Run #215 deploy never ran). + - Stage 4e Health live HTTP 200 + Health ready "Healthy" → Mig 31 RefactorSkipToFinalToApproverLevel applied prod unchanged ✓ (Plan AB no schema change as expected). + - **Pattern saved gotcha #48 RESOLVED (Run #215 lesson):** When introducing 2nd Changelog.Add() in same transaction as existing LogTransitionAsync chain, tests using `.OrderByDescending(CreatedAt).First()` with SQLite frozen-clock get NON-DETERMINISTIC pick. Fix verified working in Chunk A2: `.Where(c => c.Summary!.Contains("Chuyển phase"))` discriminator filter restores deterministic test pick. Pattern reusable for future multi-Changelog.Add in same SaveChangesAsync transaction. + - **Side benefit Run #215 → #216 catch loop:** CI test gate caught Plan AB tie-break bug BEFORE prod deploy (Run #215 fail = bro UAT spared broken Plan M edge cases). Chunk A2 test-only fix shipped surgically without re-touching BE service code. Demonstrates: test-after pattern UAT mode CAN tolerate Plan M edge case bug for 1 chunk if next chunk lands within minutes (10:13 fail → 10:21 fix = 8 min turnaround). Token cost ~12k. + +- **2026-05-19 10:13-10:15 — Run #215 id=329 sha=`cdfd542` VERDICT=FAIL (S25 t1 Plan AB Chunk A — Changelog visibility fix Bug 1 Budget Adjust + Bug 2 Return Mode). Push range `e23f51c..cdfd542` 1 commit 3 files (1 BE `PurchaseEvaluationWorkflowService.cs` +207/-95 LOC refactor `ApplyReturnModeAsync` + 2 FE `PeDetailTabs.tsx` mirror filter extend). Duration 1m06s (much shorter than ~3-4 min baseline = early test gate fail, deploy stage never reached). **CRITICAL — Test gate FAIL at test_infra:** 51/53 passed, 2 FAIL same root cause: + - `PurchaseEvaluationWorkflowServiceReturnModeTests.ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` (line 350) + - `PurchaseEvaluationWorkflowServiceReturnModeTests.ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` (line 308) + - **Error message:** `Expected changelog.ContextNote not to be ` + - **Root cause:** Plan AB Chunk A `ApplyReturnModeAsync` adds NEW Changelog entry at end (line 403-412) for Bug 2 visibility — `EntityType=Workflow + Action=Update + Summary` (NO ContextNote field). After refactor, BOTH ApplyReturnModeAsync (new entry, no ContextNote) AND LogTransitionAsync (line 100, existing entry with ContextNote=comment) are added in same `SaveChangesAsync` transaction. Test fetches `.OrderByDescending(c => c.CreatedAt).FirstAsync()` — with SQLite + frozen test clock both entries get SAME CreatedAt, OrderByDescending tie-break returns Plan AB's Workflow entry (without ContextNote) instead of Transition entry. Plan M edge case tests broken. + - Test gate stages: test_domain PASS 58/58 ✓ / test_infra FAIL 51/53 (-2 from baseline 53) / build_be NOT RUN / build_fe_admin NOT RUN / build_fe_user NOT RUN / deploy NOT RUN — exit status 1 + - **Deploy NOT shipped:** Bundle hashes unchanged from Run #214 Plan AA baseline (admin `CZdXQ2eo`, user `DCwhhey2`). Mig prod TOP 1 = `Mig 31 RefactorSkipToFinalToApproverLevel` unchanged ✓. Smoke 5/5 endpoints 200 OK (pre-Plan-AB code stable). PE 3248f2f9... changelogs endpoint 200 returns `[]` empty array (PE may not exist or no log entries — endpoint shape OK). + - **Plan AB Bug 1+Bug 2 fix NOT live** — bro UAT screenshot pre-deploy stale, Plan AB Changelog visibility refactor NOT shipped to prod. + - **Recommendation em main:** Plan AB Chunk B HOTFIX — choose ONE of 2 approaches: + 1. **Add `ContextNote` to new Plan AB Changelog entry** (line 403-412): set `ContextNote = summary` (the OneLevel/OneStep edge case "không lùi được" string). Plan M tests pass because either entry now has ContextNote. Caveat: ContextNote may duplicate Summary content. + 2. **Skip Plan AB Changelog entry when `comment` would propagate via LogTransitionAsync ContextNote**: only add NEW Changelog for cases NOT covered by Transition entry (vd terminal mode that doesn't trigger LogTransitionAsync). Refactor more conservative, but Plan AB scope says "single uniform log". + 3. **Alternative — fix tests to fetch specifically EntityType=Transition Changelog**: `Where(c => c.EntityType == PurchaseEvaluationEntityType.Transition)` then OrderByDescending → pick the Transition entry deterministically. Tests still verify ContextNote via LogTransitionAsync chain. + - **Pattern saved gotcha #48:** When introducing 2nd Changelog.Add() in same transaction as existing LogTransitionAsync chain, tests using `OrderByDescending(CreatedAt).First()` with SQLite frozen-clock get NON-DETERMINISTIC pick → favor `.Where(c => c.EntityType == X)` filter or use deterministic order proxy (Id is Guid not sequential — must use insertion-order via explicit ordering column or filter by discriminator). 4 Mode of Plan AB exposes the assumption. + - **Side benefit:** CI test gate caught this BEFORE prod deploy — bro UAT spared from broken Changelog audit (pre-existing Plan M edge case audit trail would have been broken in prod if test gate skipped per UAT mode). UAT mode skip test-after pattern still RISKY when refactor touches existing code paths with tests. + - Token cost ~10k. + +- **2026-05-15 ~17:35 — Run #214 id=328 sha=`ee0902a` VERDICT=PASS (S24 t1 Plan AA wrap fix sidebar)**. Last successful deploy before Plan AB. Sidebar label dài wrap về đầu hàng + text smaller. Bundle hashes post-Run-#214 = admin `CZdXQ2eo` + user `DCwhhey2` (baseline for Plan AB delta comparison — KHÔNG rotated since Plan AB failed). Duration ~3m25s. + - **2026-05-15 ~15:25 — Run #210 id=324 sha=`ac2c859` VERDICT=PASS (S24 t1 Plan AA — IsUserSelectable filter + WfView menu + sidebar widen)**. Push range `a1a910f..ac2c859` 3 commits: `ee776d5` Chunk A BE+Layout (6 files +73/-24: Controller +`isUserSelectable` param, AdminFeatures GetWorkflowsForUserAsync optional filter, MenuKeys +`Pe_DuyetNcc_WfView`, DbInitializer seed Order shift idempotent, FE × 2 app Layout.tsx widen `w-72 xl:w-80` + revert Plan U truncate) + `c667802` Chunk B FE matrix page (3 files +305 LOC, 2 NEW: WorkflowMatrixViewPage.tsx + types extend approvalWorkflowV2.ts + App.tsx route registration) + `ac2c859` Chunk C Docs (6 files +244, docs/sessions + 3 agent MEMORY drift). Run duration 3-4 min baseline. Iter 4/10 poll completion. **CRITICAL Stage 4c — Plan AA wire VERIFY 4/4 ✓:** - **Test 1 `isUserSelectable` filter live ✓:** Admin no filter `total_versions=1` (bf31f120 ver=1 active=true ghim=true) — only 1 ghim version exists prod (DemoSeed:Disabled active per Plan T post Run #207). Admin filter `?isUserSelectable=true` returns same 1 ghim version (count <= no-filter). Non-admin token filter ghim returns same payload (types[0].active id=bf31f120 ghim=true name="Quy trình Duyệt NCC") — **HTTP 200, NOT 403**. Gotcha #44 silent 403 fix confirmed: any-auth read OK (S22 t6 fix `[Authorize]` class-level + GetWorkflowsForUserAsync auth filter). - **Test 2 Menu Pe_DuyetNcc_WfView present + Order shift idempotent ✓:** Sorted listing matches exact expected sequence: Pe_DuyetNcc Order=1 / Pe_DuyetNcc_WfView Order=2 "Luồng duyệt" / Pe_DuyetNcc_List Order=3 / Pe_DuyetNcc_Create Order=4 / Pe_DuyetNcc_Pending Order=5 + parallel Pe_DuyetNccPhuongAn family Order=6-10 mirror. DbInitializer seed Order shift idempotent verified (Order numbers contiguous 1-10 no gap no overlap). @@ -210,6 +242,10 @@ Flag commit nếu thấy `0 expected ~3-5 từ pre-deploy UAT, actual 8 = generous coverage). **ContextNote keywords detected** ✓: entry 2026-05-19T02:34:38 contextNote=`Trà xem lại phần so sánh [Trả về Người chỉ định — Bước 1 (Phòng 1) Cấp 2]` matches "Trả về" keyword (Drafter/Assignee return mode). Entry 02:35:43 contextNote=`Approver skip thẳng tới Bước 3 Cấp 1 (NV cuối) — bỏ qua các Bước/Cấp trung gian` matches skipToFinal pattern. 6 entries có summary `Chuyển phase ChoDuyet → ChoDuyet` (mid-flow transitions với mutation context). **FE merge logic data source FULLY VERIFIED** ✓ — synthetic Reject rows có thể reconstruct từ 2 Workflow entries với "Trả về" ContextNote (line 02:34:38 + earlier Mig 26 events). **Bundle hash ROTATED 2/2** ✓: admin `B5iZMa7g → CDVnRDe6` + user `CHJDH3M2 → gAFN3NVx`. **Smoke 5/5 200** ✓: purchase-evaluations / contracts / menus / users / changelogs PE/A/032. **Mig 31 TOP 1 unchanged** ✓ `20260514160124_RefactorSkipToFinalToApproverLevel` Plan AC2 no schema. **S25 cumulative push range `e23f51c..25837b6` 4 commits Plan AB+AB2+AC+AC2** all deployed. **Pattern saved: FE merge synthetic rows from Changelog history — reversible historical recovery pattern reusable cho Contract V2 + Budget V2 audit recovery future when missing native audit table.** Source = Changelog generic with entityType discriminator + ContextNote keyword filter + summary regex parse. **Recommendation:** bro UAT verify PE/2026/A/032 mở Lịch sử duyệt panel — kỳ vọng thấy synthetic Reject entries trộn với 6 Approve entries existing sorted ascending bởi approvedAt. Memory size ~60KB hard over 50KB limit — **IMMEDIATE ARCHIVE archive/2026-05.md MANDATORY next curation BEFORE any next entry**. FIFO ~13 entries. Token cost ~12k. + - **2026-05-13 23:25 — Verify S22 chốt cuối cumulative (push range `3d725c4..cc8a7d3` 12 commits) VERDICT=PASS** (S22 chốt — em main spawn cumulative verify cuối S22). Latest CI run #193 id=307 sha=`b04a11a` (S22+5 Chunk B FE) success at 23:16. **Tip commit `cc8a7d3` (docs+4 agent MEMORY.md) → CI SKIPPED via `**/*.md` glob** (all 4 `.claude/agent-memory/*.md` + 4 `docs/**` files match — paths-ignore correctly fires). Spec hypothesis ("gotcha #47 — `.claude/agent-memory/**` NOT in paths-ignore → trigger CI") **disproven for this commit**: `**/*.md` glob matches `.md` files at ANY depth so `.claude/agent-memory/MEMORY.md` DOES match. Run #188 a74e671 trigger anomaly was NOT due to agent-memory path. **Gotcha #47 still useful as PREVENTIVE** for future when adding non-.md state files under `.claude/agent-memory/` (e.g. `.json` state, `.log`) — explicit `'.claude/agent-memory/**'` ignore would future-proof. **Recent runs S22 sequence (12 commits → 11 trigger + 1 skip):** #189 sha=40f64c6 ✓ (S22+1 BE guard) · #190 sha=8185070 **CANCELLED** by concurrency (seed users script superseded by next push within 3 min — normal not a fail) · #191 sha=0e70789 ✓ (rename script) · #192 sha=30d51c8 ✓ (S22+4 FE) · #193 sha=b04a11a ✓ (S22+5 FE — also covers S22+5 Chunk A BE `b079b27` since both pushed batched; Gitea trigger only on push tip). `cc8a7d3` skip = correct (no run 308). **Discovery #3:** When 2+ commits pushed in same `git push`, Gitea Actions evaluates ONLY the push tip's paths against paths-ignore — intermediate commits do NOT each get evaluated separately. So `b079b27` (BE Mig 30) + `b04a11a` (FE Mig 30) pushed together → single Run #193 on tip. Test gate inferred PASS for all 11 trigger runs (each deploy stage succeeded). **Local test verify 104/104 PASS** (58 Domain + 46 Infra = +1 vs Run #188's 103 — S22+1 added 1 BE guard test). **Mig 30 prod confirmed:** sqlcmd TOP 5 = `20260513160703_AddAllowApproverEditBudgetToLevels` TOP 1 (S22+5 wire). **Schema live verify:** PE detail `currentLevelOptions` 6 keys (5 from Mig 29 + 1 new `allowApproverEditBudget`) ✓, AwLevelDto 12 keys including `allowApproverEditBudget` ✓. **Endpoint wire LIVE:** PATCH `/api/users/{id}/allow-skip-final` admin=204 ✓ + act.nv=403 ✓ (Plan D admin-only enforce) · PATCH `/api/purchase-evaluations/{id}/budget-adjust` admin=204 ✓ (S22+4 BE) · GET `/api/purchase-evaluations/{id}/attachments/{attId}/view` 200 + `Content-Disposition: inline; filename="HD- Eoffice.pdf"` ✓ (S22+4 preview). **Plan E strict V2 scope LIVE:** admin pageSize=200 → 17 PE / act.nv pageSize=200 → 0 PE (Drafter strict filter active; act.nv fresh user no PE participation). **Bundle hash rotated 2/2:** admin `Cclc8Uwu` → `CpI5OL8n` ✓ (S22 cumulative FE deploy) / user `B6N5hq3d` → `d064StNa` ✓ (S22+4 + S22+5 touched fe-user — rotation expected). Smoke 5/5 endpoints 200: contracts, pe, users, menus, approval-workflows-v2. **33 active users prod confirmed** (20 role-based new from S22+2 seed + S22+3 rename: act/pp/tp · bod.1/2 · equ/fin/hra/pm/qs nv/pp/tp + 13 pre-existing). All role-based users `isActive=true` + login `act.nv@solutions.com.vn / TestUser@2026` OK with roles `Drafter,Accounting`. **Token caching pattern from Run #188 worked:** 1 admin login + 1 act.nv login total = 2 auth requests cached to `C:\Users\pqhuy\AppData\Local\Temp\*_token.txt` + 8 subsequent endpoint calls reuse token → no 429 rate limit encountered (vs Run #188 hit 429). **Trend:** S22 cumulative 5 turn iteration delivered Mig 30 + 3 new endpoints + scope filter + 20 seed users — 0 deploy regression. Baseline cumulative passes 81→103→104 test grow consistent with feature delivery. - **2026-05-13 21:25-21:28 — Run #188 id=302 sha=a74e671 VERDICT=PASS** (S22 — 5 commits: Plan D Users F2 toggle BE+FE Admin AllowDrafterSkipToFinal + Plan C task 1-3 14 service test ReturnMode/Guard + Plan C task 4 5 regression test #44 silent 403 + Plan E PE strict V2 scope + Docs/MEMORY 3-agent drift patch). Duration 3m28s (baseline). Path filter: the push tip `a74e671` includes `.claude/agent-memory/**` files (NOT in paths-ignore) + `docs/**` (in paths-ignore) → Gitea evaluated push as CI-eligible (some files OUTSIDE paths-ignore), trigger fired correctly. **Local test verify: 58 Domain + 45 Infra = 103/103 PASS (+19 from S21 84)** breakdown: 23 codegen + 6 PE WF + 7 ReturnMode + 7 DraftGuard + 5 AuthorizePolicy regression. CI deploy succeeded → inferred test gate PASS (deploy only runs if tests pass). Bundles deployed: admin `index-Cclc8Uwu.js` rotated from `D5l49-70` (21:27:24 PM VPS), user `index-B6N5hq3d.js` UNCHANGED (Plan C/D/E touched only fe-admin, expected). DLLs deployed 21:25-26 PM. Mig 29 `RefactorAdvancedOptionsToPerLevelAndDrafterUser` still TOP 1 (no new mig in S22, expected). **Plan D wire LIVE:** GET `/api/users` response includes `allowDrafterSkipToFinal` field (boolean), PATCH `/api/users/{id}/allow-skip-final` admin=204 ✓ + nv.test=403 ✓ (admin-only enforced). **Plan E wire LIVE:** nv.test PE list totalCount=8 < admin totalCount=17 (strict V2 scope filter ACTIVE — drafter only sees own + participant PE). Smoke 5/5 endpoints 200: `/api/contracts`, `/api/purchase-evaluations`, `/api/menus`, `/api/approval-workflows-v2`, `/api/users`. **Discovery #1:** Rate limit auth login triggers at ~5 requests/min — HTTP 429. Pattern: backoff 60s + retry. Spread login calls or cache token across endpoints in same agent run. **Discovery #2:** `.claude/agent-memory/**` files are NOT in paths-ignore (only `docs/**` + `**/*.md` + `.claude/skills/**` + `.gitignore` + `scripts/**.md`) → MEMORY.md commits DO trigger CI even when "looks like docs". Spec assumption ("docs commit `a74e671` triggers paths-ignore skip per gotcha #41") was incorrect for this case — `.claude/agent-memory/**` triggers CI. @@ -232,3 +268,9 @@ Last curate: 2026-05-15 13:18 (added S23 t5 Plan O HOTFIX Run #202 entry: PASS v Last curate: 2026-05-15 13:45 (added S23 t6 Plan P HOTFIX Run #203 entry: PASS verdict + CRITICAL wire verify Test 1 admin returnMode=4 numeric → HTTP 204 phase mutated 10→98 ✓ + Test 2 admin returnMode=3 Assignee → HTTP 409 BUSINESS rejection "Không tìm thấy người chỉ định" NOT pre-fix bug "Cấp Approver mode Drafter" = `returnMode` preserved end-to-end through Controller `TransitionPeBody` 7-field record + `mediator.Send` 7-arg call + Handler Assignee branch logic. NV Test actor variant not feasible (scanned 20 PEs — NV Test = Drafter not approver). Plan P fixes the "Caveat #1 pre-existing bug" surfaced from Plan O Run #202 14 min earlier — fast turnaround S23 t5 → t6. **Discovery #4:** ASP.NET Core 10 record-with-enum needs numeric input; no global `JsonStringEnumConverter` registered (Grep confirms 0 hits). Brief example payload `"Drafter"` string format fails 400. FE × 2 correctly uses numeric. Bundle hash measurement caught Run #204 Chunk Q FE banner fix in flight (admin `QZIPWD-g`, user `DaLTMGcx`); Plan P bundle UNCHANGED criterion satisfied (Plan P diff zero FE files). Memory size ~40KB approaching curate trigger; FIFO runs at ~9 entries — schedule archive next curation.) Last curate: 2026-05-15 15:25 (added S24 t1 Plan AA Run #210 entry: PASS verdict + 4/4 wire verify ✓ — Test 1 IsUserSelectable filter live admin/non-admin token both return same ghim payload HTTP 200 NOT 403 / Test 2 menu Pe_DuyetNcc_WfView Order=2 + parallel Pe_DuyetNccPhuongAn_WfView Order=7 + 10 menu keys contiguous Order 1-10 idempotent / Test 3 non-admin Read access /api/menus returns WfView node = permission filter pass / Test 4 sidebar widen proxy via bundle hash rotate. Bundle hash 2/2 rotated admin `QZIPWD-g → Dmk--X6w` + user `DaLTMGcx → Bd4gh3Tp`. Mig 31 unchanged. Smoke 5/5 200. Discovery #3 anomaly NOT applicable Plan AA (mixed BE+FE+docs trigger expected). Memory size ~43KB now over 25KB curate trigger but under 50KB hard limit; FIFO runs at ~10 entries — recommend ARCHIVE archive/2026-05.md next curation if next entry pushes >50KB. Token cost ~12k.) + +Last curate: 2026-05-19 10:18 (added S25 t1 Plan AB Run #215 entry: **FAIL** verdict at test_infra gate — 2 Plan M edge case tests broken `ApplyReturnMode_OneStep_AtStep1` line 350 + `ApplyReturnMode_OneLevel_AtStep1Level1` line 308 both `Expected changelog.ContextNote not to be `. Root cause: Plan AB refactor adds NEW Changelog.Add() at end of `ApplyReturnModeAsync` (EntityType=Workflow, no ContextNote) alongside existing `LogTransitionAsync` chain that writes ContextNote=comment. SQLite frozen-clock tie-break in `OrderByDescending(CreatedAt).First()` non-deterministic → tests pick wrong entry. Test gate caught BEFORE deploy → prod state preserved (bundles unchanged Run #214 baseline admin `CZdXQ2eo` + user `DCwhhey2`, Mig 31 TOP 1 unchanged, smoke 5/5 200). 3 fix approaches recommended em main: (1) ContextNote=summary on new entry, (2) skip when LogTransitionAsync covers, (3) test fix EntityType=Transition filter. NEW gotcha #48: multi-Changelog.Add() in same transaction + OrderByDescending(CreatedAt) tie risk in test SQLite frozen-clock. Memory size ~50KB at hard threshold — recommend ARCHIVE archive/2026-05.md next curation BEFORE next entry. FIFO runs at ~11 entries. Token cost ~10k. UAT mode skip test-after pattern STILL RISKY when refactor touches code paths with existing tests — Plan AB Chunk A did `npm build × 2` verify but skipped `dotnet test` → CI caught regression in test_infra. Bro UAT spared broken audit trail in prod.) + +Last curate: 2026-05-19 10:50 (added S25 t3 Plan AC Run #217 entry: **PASS** verdict — fix Bug 3 "Lịch sử duyệt" panel show Trả lại + Duyệt vượt cấp. Push `8c05947..a734bf2` 1 commit Plan AC. Duration 3m27s baseline. 3 files +105/-38 LOC: BE Service Reject branch line 75-103 ADD Approval row + capture pre-call Step/Level mutation state cho audit "from-position" + Approve branch line 472 enrich Comment với `[Duyệt vượt cấp tới Cấp cuối]` prefix khi skipToFinal=true + FE × 2 app PeDetailTabs.tsx add `decisionBadge` helper render Decision badge ApprovalsTab. Local pre-push 111/111 PASS + 2× FE build 0 TS err (TS strict caught unused `PE_DECISION_APPROVE` removed). **CRITICAL Stage 4c wire VERIFY** PE_ID=`3248f2f9-c6e9-43ff-a4ca-067ffecf9f36`: approvals count=6 all keys [approvedAt/approverName/approverUserId/comment/decision/fromPhase/id/toPhase] ✓ decision field present every row (FE decisionBadge input verified) ✓ all 6 existing decision=1 Approve backward-compat preserved ✓ all 6 comments có `[Bước X — Cấp Y]` prefix structure Plan AC enrichment uniformly applied across history ✓ no Reject/skipToFinal rows current dataset n/a until bro UAT post-deploy. **Bundle hash ROTATED 2/2** ✓: admin `CrHpBYFM → B5iZMa7g` + user `C3bCWZ90 → CHJDH3M2`. **Smoke 4/4 200** ✓. **Mig 31 TOP 1 unchanged** ✓ no schema. **Pattern saved cross-ref Plan AB:** capture pre-call mutation state cho audit row "from-position" — when Service mutates entity AND writes audit row, snapshot OLD values BEFORE mutation. Reusable Contract V2 + Budget V2 future. **Discovery #5:** sqlcmd Windows-auth via ssh requires `\\\\SQLEXPRESS` 4-backslash escape; `\\SQLEXPRESS` produces 0 output silently. Memory size ~58KB strongly over 50KB hard limit — **REQUIRED ARCHIVE archive/2026-05.md next curation BEFORE next entry.** FIFO ~12 entries. Token ~12k.) + +Last curate: 2026-05-19 11:25 (added S25 t5 Plan AD Run #219 entry: **PASS** verdict — Lịch sử duyệt redesign drop phase badges + next-target hint helper. Push `25837b6..0aaf2df` 1 commit FE-only 2 .tsx +104/-28. Duration 3m24s baseline. CI status=success → full pipeline PASS (FE-only no test/BE touch baseline preserved). **CRITICAL Stage 4c wire VERIFY** PE_ID=`3248f2f9...`: approvals shape unchanged 6 rows all keys [id/fromPhase/toPhase/approverUserId/approverName/decision/comment/approvedAt] — Plan AD strips Badge JSX only, DTO shape backward-compat preserved + Plan AC `[Bước X — Cấp Y]` prefix structure provides parse input for `extractNextTargetHint()` helper. **Bundle hash ROTATED 2/2** ✓: admin `CDVnRDe6 → DR95zKWg` + user `gAFN3NVx → BAj_Yaj5`. **Smoke 5/5 200** ✓. **Mig 31 indirect verify** ✓ (diff 0 BE/Mig touch, sqlcmd direct skipped — PROD_DB_PASSWORD not set local). **Pattern saved Plan AD: UX drop misleading dual-state fields + parse comment helper for semantic hint — reusable Contract/Budget V2 audit when from/to fields mostly self-loop noise.** **S25 cumulative 5 commits AB+AB2+AC+AC2+AD** deployed clean 1 catch + 4 pass. Memory size ~62KB STRONGLY OVER 50KB hard threshold — **DEDICATED CURATION SESSION PRIORITY NEXT** archive `archive/2026-05.md` drop entries pre-2026-05-15 + compress S22-S24 entries. FIFO ~13 entries. Token cost ~14k.) diff --git a/.claude/agent-memory/implementer/MEMORY.md b/.claude/agent-memory/implementer/MEMORY.md index 391b517..6b5a68e 100644 --- a/.claude/agent-memory/implementer/MEMORY.md +++ b/.claude/agent-memory/implementer/MEMORY.md @@ -287,6 +287,10 @@ KHÔNG `*` / `latest`. Critical pins: ## 📅 Recent activity (last 10 FIFO) +- **2026-05-19 (S25 wrap — Plan AB Chunk A Case 1 + 6 follow-up plans em main solo):** Plan AB Chunk A spawn 1× ~12K Case 1 cookie-cutter mirror. BE refactor ApplyReturnModeAsync Drafter early return → common path (line 280-287 → if/else block) + single Changelog.Add() ở cuối hàm với modeName switch enum + actorName resolve via userManager.FindByIdAsync mirror LogTransitionAsync pattern. FE × 2 app HistoryTab filter relax (PE_ENTITY_HEADER=1 + summary contains 'ngân sách' for Bug 1 + Workflow summary contains 'Trả lại' for Bug 2). KHÔNG TS test (UAT mode skip). KHÔNG migration. KHÔNG endpoint. Commit cdfd542 3 file +146/-95 LOC PASS. **Em main solo từ Plan AC** (cross-stack reasoning + UAT iteration borderline scope — Implementer would REFUSE per criteria #4 tight coupling BE+FE same plan). AC capture pre-call Step/Level + add Approval row Reject branch + skipToFinal comment + FE Decision badge × 2 app. AC2 FE merge synthetic Reject + dedupe timestamp 5s bucket. AD drop phase badges + extractNextTargetHint regex parse. AE BE batch 9 Changelog.Add sites UserName preventive fix. AF FE userMap fallback từ embedded domain data PeDetailBundle. **Pattern 16 NEW** (cumulative S25): Preventive systemic batch fix khi audit phát hiện 9 sites cùng bug pattern — replace_all=true với context-aware key (UserId line + Summary line) — 1 pass cover N sites idempotent. **Pattern 17 NEW**: FE merge synthetic rows từ Changelog cho audit historical recovery — pattern reusable cho Contract V2 + Budget V2 audit visualization without DB write. **Pattern 18 NEW**: FE userMap fallback từ embedded domain data (drafter + approvals + approvalFlow + levelOpinions + departmentOpinions) — no extra API fetch cho historical name resolve. + +- **2026-05-19 (S25, Plan AB Chunk A PASS):** Bug 1+2 fix Changelog visibility audit log UAT. Commit `cdfd542` 3 file +146/-95 LOC. **BE** `PurchaseEvaluationWorkflowService.cs` `ApplyReturnModeAsync` lines 215-378 refactor: Drafter early return (line 282-287) → if/else common path, `summary = "Trả về Người soạn thảo"` thay vì return early, SLA reset move bên trong else block 3 mode còn lại (Drafter có riêng `evaluation.SlaDeadline = null`). Single Changelog.Add() ở cuối hàm cover 4 mode uniform: `EntityType=Workflow + Action=Update + Summary=$"Trả lại ({modeName}): {summary}"` với modeName switch ("Người soạn thảo"/"1 Cấp"/"1 Bước"/"Người chỉ định"). `actorName` resolve qua `userManager.FindByIdAsync` mirror pattern existing line 660-667 (LogTransition helper). KHÔNG SaveChangesAsync mới — caller `TransitionAsync` line 100 đã có downstream save. **FE** 2 file `PeDetailTabs.tsx` × 2 app mirror exact: filter extend `if (l.summary?.includes('Trả lại')) return true` (Workflow entity) + `if (l.entityType === PE_ENTITY_HEADER && l.summary?.toLowerCase().includes('ngân sách')) return true` (Header entity new const = 1). Empty placeholder + comment 3-source rewrite (UAT 2026-05-08 + 2026-05-19 + bullet list 5 filter rule). Verify: BE build clean 0 err 2 pre-existing DocxRenderer warn (20.27s), fe-user 1907 modules 16.62s 0 TS err, fe-admin 1926 modules 6.98s 0 TS err. Test SKIP per UAT mode `feedback_uat_skip_verify` Phase 9 (111 baseline preserve). **NEW pattern observed (cumulative)**: `Changelog log common path refactor + FE filter substring summary discrimination`. Reusable cho future audit log derived state (vd Adjust*/Return*/Reset* action): refactor early return → if/else common path để single log call cover N branch, FE filter qua substring summary keyword chứ KHÔNG enum field strict (action verb tiếng Việt "Trả lại"/"ngân sách" dễ maintain hơn enum + cho FE flexibility filter mới mà không cần BE schema migrate). Cross-ref Pattern 4 `feedback_service_hook_vs_endpoint` (state X derived của action Y → log trong handler Y, KHÔNG endpoint /X riêng — Bug 2 ApplyReturnModeAsync log trong service hook KHÔNG endpoint /return-changelog rời). Pattern 5 mirror 2 app §3.9 applied 7th cumulative. Token ~12k. Diff: BE +83/-49 (refactor + new log block ~40 LOC), FE × 2 app +14/-6 each (filter + comment). KHÔNG ops git push (em main verify Reviewer rồi mới push). + - **2026-05-15 (S24, Plan AA wrap):** Cumulative learning post-Chunk B. **3 patterns NEW added** (13 read-only admin Designer mirror, 14 Tailwind JIT palette array full class strings, 15 HTML table rowSpan flat row builder helper). **REFUSE log 4/4 correct** S24 polish chunks (commit da218f1 hotfix px-2 trivial < 30min criteria #6, 4d60598 redesign v1 panel-per-NV color UX flow criteria #2, fbbd361 redesign v2 table rowSpan UX flow criteria #2, ee0902a wrap fix sidebar label CSS polish criteria #6) — em main solo executed all 4, Implementer no-spawn. **Ambiguity Chunk B**: shadcn fe-user thiếu Card/Badge (lib subset minimal — chỉ Button/Dialog/Input/Label/Select/Textarea) → fallback inline `
` + inline `` mirror admin Designer DefinitionCard. Pattern reusable cho fe-user pages requiring elevated UI surface (audit shadcn fe-user/src/components/ui/ TRƯỚC khi import). Total S24 commits: 7 (a1a910f..ee0902a — 1 spawn Chunk B accepted + 6 em main solo: Chunk A cross-stack + Chunk C docs + 4 polish). Token cost cumulative S24 Implementer: ~14k (Chunk B only). - **2026-05-15 (S24, Plan AA Chunk B PASS):** FE user read-only matrix view workflow V2 ghim (Mig 25 IsUserSelectable). 3 file: (1) CREATE `fe-user/src/types/approvalWorkflowV2.ts` (~55 LOC) — subset DTO mirror BE `AwAdminOverviewDto` (7 Allow* flag per Level, 5 record type AwLevelDto/AwStepDto/AwDefinitionDto/AwTypeSummaryDto/AwAdminOverviewDto); (2) CREATE `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx` (~215 LOC) — useQuery GET `/approval-workflows-v2?applicableType=N&isUserSelectable=true` (em main BE Chunk A đã thêm `IsUserSelectable bool?` param + Controller forward), render table 10 cột Bước (rowSpan) | Cấp | NV duyệt | 7 ✓/— flag cell, header với `title` tooltip mô tả từng cột, 3 state Loading/Error/Empty rõ ràng, badge `Đang dùng` (emerald isActive) + `Được ghim` (amber isUserSelectable Pin icon); (3) UPDATE `fe-user/src/App.tsx` — import WorkflowMatrixViewPage + route `/purchase-evaluations/workflow-matrix` đặt TRƯỚC `/workspace` (URL ordering logical matrix → workspace → new → detail). Verify: `npm run build` fe-user PASS clean 0 TS err, 1907 modules, 2.61s, 1282 KB. Surprise: shadcn fe-user KHÔNG có `Card`/`Badge` (chỉ có Button/Dialog/Input/Label/Select/Textarea) → fallback inline `
` + inline `` cho badge (mirror pattern admin Designer). Pattern reusable: **read-only mirror admin Designer page** = drop edit mutations + reuse DTO types (subset) + filter param BE-side (`IsUserSelectable=true` thay vì FE filter). Cookie-cutter 0 (lần đầu pattern). Pattern 5 mirror 2 app KHÔNG apply (fe-admin có Designer riêng — Plan AA scope fe-user only). Pattern 7 admin opt-in 7 Allow* flag wire full render trong table (10 cột total = 3 meta + 7 flag). KHÔNG ops git. Token cost ~14k. - **2026-05-15 (S23 t4-t11 cumulative REFUSE — em main solo Plan N+O+P+Q+R+S+T+U):** 8 plan consecutive em main solo, 0 Implementer spawn (REFUSE 100% per criteria #4 bug fix reasoning chain + criteria #3 cross-stack tight coupling). **Plan N+O** 5 lookup site discrimination fix cross-stack BE Service + Application + 3 regression test. **Plan P** Controller TransitionPeBody record drop fix tightly coupled FE wire audit (Investigator confirm BE-only scope ~6 LOC). **Plan Q** FE banner mx-5 layout CSS polish 2 app mirror trivial 8 LOC. **Plan R+S+T5** destructive sqlcmd cleanup prod (scripts/plan-r-*.sql + plan-s-*.sql + plan-t5-*.sql + plan-t-backup.sql, 4 files scp + sqlcmd -i, ~720 rows wiped cumulative). **Plan T** DbInitializer DemoSeed:Disabled flag config (Infrastructure + Api appsettings, ~25 LOC). **Plan U** FE sidebar truncate + tooltip 2 app mirror 25 LOC (em main solo CSS Tailwind). 7 strict-scope criteria validated cumulative S23: pattern reusable saved memory user-level — Plan O wire 9 surface points (point 9 lookup discrimination 5 sites enum), Plan P wire 10 surface points (point 10 Controller body record mirror count check), Plan T DemoSeed flag pattern. diff --git a/.claude/agent-memory/investigator/MEMORY.md b/.claude/agent-memory/investigator/MEMORY.md index 466b74d..e7944b8 100644 --- a/.claude/agent-memory/investigator/MEMORY.md +++ b/.claude/agent-memory/investigator/MEMORY.md @@ -94,9 +94,10 @@ Common queries: `sys.columns`, `sys.triggers`, `__EFMigrationsHistory`, `COUNT(* - **Gitea remote:** https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp - **Gitea Actions API:** path `/api/v1/repos/.../actions/tasks` (NOT `/actions/runs` — 404). Cache stale ~2 min (gotcha #46) — cross-check VPS file mtime - **SSH VPS:** `ssh vietreport-vps` (config `~/.ssh/config` user=Administrator key=id_ed25519) -- **Gotchas active:** 46 (reference `docs/gotchas.md`) -- **Tests baseline:** 104 PASS (+20 S22: 5 reg #44 Authorize policy + 7 ReturnMode + 7 Guard + 1 V2 actor scope reject) — Phase 9 UAT skip per chunk (memory `feedback_uat_skip_verify`) -- **Endpoints:** ~146 (+3 S22: PATCH /users/{id}/allow-skip-final + PATCH /pe/{id}/budget-adjust + GET /pe/{id}/attachments/{attId}/view) +- **Gotchas active:** 47 (+1 #48 SQLite tie-break pending docs — Plan AB Chunk A2 S25 t1; reference `docs/gotchas.md`) +- **Tests baseline:** 111 PASS (S22 baseline 104 + 7 PE WF cumulative; S25 Plan M tests SQLite tie-break re-stabilized Chunk A2) — Phase 9 UAT skip per chunk (memory `feedback_uat_skip_verify`) +- **Endpoints:** ~146 (S22 +3 stable: PATCH /users/{id}/allow-skip-final + PATCH /pe/{id}/budget-adjust + GET /pe/{id}/attachments/{attId}/view) +- **Memory user-level:** 23 entries (+2 S25: Plan AC2 FE merge synthetic recovery + Plan AF userMap fallback patterns) - **Users:** 30 demo + 33 active prod (13 cũ + 20 mới S22+2 role-based: act/bod/equ/fin/hra/pm/qs prefix `.nv/.pp/.tp` + bod.1/2). Password policy ≥12 chars (S22+2 discovery, `TestUser@2026`) - **API auth response:** `accessToken` + `refreshToken` + `user` (S22+2, NOT `token`) - **Master HEAD reference:** check via `git log -1 --format='%H'` @@ -149,3 +150,6 @@ State machine 5 trạng thái phiếu PE: Nháp / Đã gửi duyệt / **Trả l - Stale > 3 months → remove Last curate: 2026-05-11 (initial seed) +- **2026-05-19 (S25 t1 spawn audit 2 bug critical UAT — Changelog logging missing):** Em main report 2 bug UAT: (1) Budget Adjust 2×click → "Lịch sử thay đổi" KHÔNG show 2 entry. (2) Return Assignee mode F1 click → "Lịch sử thay đổi" KHÔNG show return action. Audit 5Q: Q1-DB state Prod SSH fail (auth), fallback code inspect. Q2-Budget handler: `PurchaseEvaluationFeatures.cs:379-387` **ĐÃ log changelog** EntityType=Header + Action=Update + summary diff → **code ĐÚNG, likelihood FE filter bug or UAT DB stale**. Q3-Return handler: `PurchaseEvaluationWorkflowService.cs:215-378 ApplyReturnModeAsync` **ZERO changelog log** — 4 mode branches (OneLevel/OneStep/Assignee/Drafter) mutate pointers only, return summary, caller `TransitionAsync:100 LogTransitionAsync` only logs phase transition KHÔNG log mode side-effect. Q4-Schema: `PurchaseEvaluationChangelog.cs` support EntityType=Workflow(5) enum but **ZERO code populate** — schema incomplete missing Kind/ChangeType subtype enum to disambiguate "budget adjust" vs "detail edit" vs "return mode" (all Header/Workflow entity cùng Action.Update). Q5-FE: Query handler `ListPurchaseEvaluationChangelogsQueryHandler:1050-1064` **KHÔNG filter logic** — returns ALL entities. FE query component unknown (fe-user source not easily searchable) but **likely filter EntityType == (Supplier|Detail|Quote)** omit Header+Workflow → Bug 1 Budget (Header type) hidden, Bug 2 Return (Workflow type unlogged vậy). Root cause B1: FE filter skip Header updates OR Schema gap (Header.Update collision: budget vs section 2 edit). Root cause B2: Handler **intentionally skip logging** (no companion audit table like PurchaseEvaluationApprovals for return history). Fix path B1: Option A (add Kind enum — 30 LOC schema+handlers) vs B (extend EntityType — 20 LOC) vs C (FE filter conditional show Budget — 10 LOC FE). Fix path B2: Add `db.PurchaseEvaluationChangelogs.Add()` per mode (15-25 LOC 1 file). Cross-ref pattern memory: S23 t3 lookup bug (Level.Order collision per-NV Mig 29) — schema Mig 29 OR-of-N refactor created similar subtype ambiguity. Surprise: `EntityType.Workflow=5` enum value design-only, unused 4+ mig history. Recommendation: B2 fix first (clear win, 1 file BE), then B1 design (schema + cross-module pattern audit Contracts). Token ~28k. +- **2026-05-19 (S25 wrap — Plan AB Bug 1+2 audit + 6 follow-up plans em main solo):** Pre-Plan AB audit ~28K confirm root cause: Bug 1 Budget Adjust Handler ĐÃ log Changelog (Header+Update) nhưng FE HistoryTab filter strict TraLai-only loại. Bug 2 ApplyReturnModeAsync 4 mode KHÔNG add Changelog.Add() — chỉ caller LogTransitionAsync log phase transition. Recommended fix path: BE add log return mode + FE filter relax + Decision badge differentiation. **Subsequent em main solo follow-ups** (Plan AC..AF, KHÔNG re-spawn Investigator): AC capture pre-call Step/Level state + skipToFinal comment enrich + FE Decision badge; AC2 FE merge synthetic Reject rows từ Changelog (reversible recovery KHÔNG DB touch); AD drop misleading dual-phase badges + parse semantic next-target hint via regex `Chuyển phase X → Y` + comment keyword "Trả về"/"vượt cấp"; AE BE preventive batch UserName fix 9 Changelog.Add sites (Drafter+Detail+Quote+Adjust); AF FE userMap fallback từ embedded domain data PeDetailBundle (drafter+approvals+approvalFlow+levelOpinions+departmentOpinions) — no extra API fetch. **Patterns reusable Contract V2 audit recovery + Budget changelog UI:** (a) synthetic recovery FE merge từ existing audit table, (b) userMap fallback từ embedded domain data, (c) drop misleading badges + semantic hint via regex parsing, (d) preventive batch fix systemic gap. Cross-ref `feedback_service_hook_vs_endpoint` (audit log derived state) + new pattern user-level: FE merge synthetic + userMap fallback. **Tokens cumulative em main this session ~340K (mostly solo iterations, sub-agent leverage ~62K total).** **CICD result:** 7 commits `e23f51c..506cada` push remote, runs #215 FAIL (Plan M tests SQLite tie-break re-emerge) → #216 PASS (Chunk A2 SQLite tie-break fix) → #217-#221 PASS streak. Gotcha #48 SQLite tie-break pending docs. + diff --git a/.claude/agent-memory/reviewer/MEMORY.md b/.claude/agent-memory/reviewer/MEMORY.md index 0795131..c58f7c9 100644 --- a/.claude/agent-memory/reviewer/MEMORY.md +++ b/.claude/agent-memory/reviewer/MEMORY.md @@ -115,9 +115,9 @@ Per Cognition documented research: ## 🧠 SOLUTION_ERP review essentials -- **Tests baseline:** 104/104 PASS (58 Domain + 46 Infra — +20 từ 84 S21: 5 regression gotcha #44 Authorize class-level + 7 ReturnMode + 7 Guard EnsureCanRejectV2Async + 1 V2 actor scope reject). Must increase nếu feature added per §7; UAT iteration exception per memory `feedback_uat_skip_verify` (Plan C test-after bundle defer) -- **Gotchas:** 46 active (`docs/gotchas.md` reference) — +#45 PE button TraLai payload mismatch + +#46 Gitea API path/cache stale (S21 t3-t4). +#47 paths-ignore agent-memory gap (S22 PENDING bro confirm add to docs) -- **Migrations:** 30 latest `AddAllowApproverEditBudgetToLevels` (S22+5 — per-slot F4 flag admin opt-in cho Approver scope edit budget ChoDuyet branch). Mig 29 prev `RefactorAdvancedOptionsToPerLevelAndDrafterUser` (S21 t5 per-NV split) +- **Tests baseline:** 111/111 PASS (S25 unchanged from S23 t3 +7 Plan M edge case tests; pre-Plan AB Chunk A2 SQLite tie-break regression Run #215 caught by CICD test gate → em main fix EntityType + Summary discriminator filter restored 111 PASS). Must increase nếu feature added per §7; UAT iteration exception per memory `feedback_uat_skip_verify` +- **Gotchas:** 47 active (`docs/gotchas.md` reference) — +#48 Multi-Changelog.Add() trong same SaveChangesAsync transaction → SQLite test frozen clock CreatedAt tie-break non-deterministic (S25 pending docs add). +#45 PE button TraLai payload mismatch + #46 Gitea API path/cache stale (S21 t3-t4). #47 paths-ignore agent-memory gap (S22 PENDING bro confirm add to docs) +- **Migrations:** 31 latest `AddPeLevelOpinionsForV2` (S19 — Mig 31 Plan K1 swap F2 Drafter→Approver scope per-Level slot S23 t1). Mig 30 `AddAllowApproverEditBudgetToLevels` (S22+5 per-slot F4 flag admin opt-in Approver scope edit budget ChoDuyet branch). Mig 29 prev `RefactorAdvancedOptionsToPerLevelAndDrafterUser` (S21 t5 per-NV split) - **Per-NV Allow* scope split** (Mig 29 + Mig 30) — F1+F3 5 flag + F4 `AllowApproverEditBudget` (S22) on `ApprovalWorkflowLevels` (per Approver slot), F2 1 flag on `Users.AllowDrafterSkipToFinal` (per Drafter). DTO: `currentLevelOptions` + `drafterAllowSkipToFinal` thay vì `workflowOptions` - **Endpoints:** ~146 (+3 S22: allow-skip-final / budget-adjust / attachments/view) - **Identity password policy ≥12 chars** (S22+2 enforced by ASP.NET Identity stack — reject `User@123456` 11 chars). Existing HANDOFF mention "User@123456" pattern S4 outdated, current test creds Admin@123456 + TestUser@123456 OK @@ -144,6 +144,8 @@ Flag commit nếu thấy ` terseness; consistent format 4 mode). (3) **actorName resolve N+1** — single `userManager.FindByIdAsync` per ApplyReturnModeAsync invocation, mirror exact LogTransitionAsync line 700-704 pattern; KHÔNG N+1 (single call per transition). (4) **FE filter substring case** — `.includes('Trả lại')` exact match BE format `$"Trả lại ({modeName}): ..."` — case-sensitive Vietnamese diacritics lowercase 'l' match. (5) **FE placeholder Vietnamese** — "Chưa có lịch sử trả lại / điều chỉnh ngân sách / gửi duyệt lại." chuẩn Vietnamese diacritics. (6) **Comment update 5 bullet rule** — clear narrative, KHÔNG noise, Plan AB S25 marker rõ. (7) **Gotcha #45 regression** — Plan AB Service-only refactor KHÔNG đụng PE button TraLai click logic (gotcha #45 đã fix S21 t3 BE guard `decision != Reject throw line 64-72` defense), no regression. (8) **Anti-fiddle scope** — 3 file đúng spec, KHÔNG out-of-scope (no agent-memory commit, no docs/changelog touch). **Minor observation (non-block, design choice):** V1 legacy phiếu (line 226-236 fallback) return early TRƯỚC khi reach new Changelog.Add() block → V1 phiếu KHÔNG có new detailed "Trả lại (X): ..." log. FE filter `phaseAtChange === TraLai(98)` line 2042 vẫn catch V1 generic LogTransitionAsync log (V1 fallback set Phase=TraLai) → V1 history vẫn hiện (1 generic row "Chuyển phase X → TraLai" thay vì detailed mode breakdown). Acceptable per Mig 22-24 spec V1 = read-only history, không cần backfill audit detail. Recommendation: PASS proceed push remote. Smart Friend guard active. - **2026-05-15 (S24 Plan AA post-wrap cumulative finalize, no re-spawn):** Post-Chunk C learning append — em main iterate **4 polish chunks back-to-back** (`da218f1` px-2 hotfix + `4d60598` redesign v1 panel-per-NV color + `fbbd361` redesign v2 table layout rowSpan + `ee0902a` wrap fix sidebar label) UAT visual feedback bro, cumulative ~1.5h, KHÔNG Reviewer spawn. **Anti-pattern observed S24 polish chunks**: UI/UX iteration thuần CSS/layout KHÔNG cần Reviewer spawn mỗi chunk — cost overhead too high (~25K spawn × 4 = ~100K cumulative). Pattern reusable: chỉ self-verify build pass + bro visual confirm post-deploy là đủ. **Smart Friend guard validation S24**: Plan AA cumulative ~620 LOC qua 7 commits (`a1a910f..ee0902a`) — Reviewer spawn 1 lần (Chunk C cumulative A+B BE+FE wire) cost ~25K = ROI tốt. Multi-spawn polish chunks 4× sẽ ~100K = ROI thấp. **Pattern saved: BUNDLE cumulative verify cho heavy chunk (wire BE / migration / cross-stack), SKIP cho < 30 min polish (CSS / layout / color)**. **Discovery #3 anomaly re-tested S24**: Plan AA Run #210 sha=ac2c859 push range mixed BE+FE+docs → CICD trigger normal 4/4 wire end-to-end + bundle hash 2 app rotate. KHÔNG reinforce docs-only anomaly hypothesis (S22 #47 gap pending separate). Defer Investigator follow-up. **Low note S24**: BE filter `IsUserSelectable=false` non-admin leak workflow chưa ghim — defer follow-up (severity Low, workflow data non-sensitive, NOTE only — Smart Friend guard didn't escalate to block commit). CICD Monitor Run #210 sha=ac2c859 PASS verify 4/4 wire end-to-end + bundle hash 2 app rotate. Cumulative state unchanged S24: 31 mig, 111 test, 47 gotcha. - **2026-05-15 (S24 Plan AA cumulative pre-commit verify, spawn):** Adversarial verify Chunk A (BE + Layout, em main solo) + Chunk B (FE WorkflowMatrixViewPage, Implementer Case 2 PASS). Diff scope 8 file modified + 2 file CREATE — ~76 LOC BE + ~270 LOC FE, scope khớp Plan AA. **Verdict: PASS with 0 critical / 0 major / 0 minor issues — proceed commit OK.** Wire BE: Controller line 21-26 `Overview(applicableType, isUserSelectable, ct) → new GetAwAdminOverviewQuery(applicableType, isUserSelectable)` end-to-end. Handler line 114-117 `request.IsUserSelectable is bool ius → query.Where(d => d.IsUserSelectable == ius)` correct conditional. FE WorkflowMatrixViewPage line 29-33 actual `api.get('/approval-workflows-v2', { params: { applicableType: typeInt, isUserSelectable: true } })` — real call no mock/alert. Route `/purchase-evaluations/workflow-matrix?type=N` wired App.tsx:38 + Layout.tsx resolvePath:81-86. Schema integrity: 0 migration mới (column `IsUserSelectable` from Mig 25), DbInitializer INSERT-OR-UPDATE-Order idempotent verified (MenuItemConfiguration HasKey(Key) → PK unique, ToDictionary safe; existing rows UPDATE Order only nếu mismatch; new rows INSERT). 7 Allow* flag count match BE DTO ↔ FE type. ApplicableType enum {1,2,3} — FE chỉ wire 2 type PE matrix view correct. Security: class-level `[Authorize]` bare line 18 PRESERVED (gotcha #44 protect). GET any authenticated → non-admin Drafter OK access. Non-admin pass `isUserSelectable=false` BE không block → workflow chưa ghim leak risk — Low severity (workflow data non-sensitive, NOTE only). Permission seed 7 role × WfView leaf via `SeedPurchaseEvaluationPermissionDefaultsAsync:1573` added — idempotent. Admin role qua `SeedAdminPermissionsAsync` chỉ cover `MenuKeys.All[]` static root keys (Pe_*_WfView dynamic NOT in All) — but admin uses fe-admin Designer, KHÔNG cần WfView access fe-user. Code quality: BE dotnet build PASS 0 err 2 warn (DocxRenderer pre-existing); FE fe-user npm build claim PASS (Implementer Case 2); FE fe-admin npm build VERIFY tự chạy PASS 1926 modules / 740ms / 0 TS err. Anti-fiddle 0% drift, scope khớp spec. Mirror 2 FE §3.9: Layout.tsx widen `w-72 xl:w-80` cả 2 ✓; remove truncate 3 site fe-user (MenuGroup+MenuLeaf+StaticLeaf) + 2 site fe-admin (MenuGroup+MenuLeaf, no StaticLeaf component) — structural asymmetry acceptable (fe-user only có "Hộp thư" StaticLeaf); WorkflowMatrixViewPage fe-user only correct (admin có Designer riêng). Test coverage: Phase 9 UAT exception accept — test-after default OK. Adversarial deep checks: (1) DbInitializer ToDictionary duplicate key risk → PK constraint trên Key, tree.Add chỉ thêm Pe_DuyetNcc_WfView + Pe_DuyetNccPhuongAn_WfView (2 typeCode distinct) → no in-memory dup. (2) Sidebar widen 1280px responsive verified per task spec (288 sidebar + 992 main fit). (3) Plan U revert clean — 0 remaining truncate class outside Plan AA comments. (4) Mig drift schema: column existed Mig 25, no new mig needed. 2 SaveChangesAsync non-atomic giữa menu seed + label backfill (pre-existing, OUT scope Plan AA). Comment quality: Plan AA marker rõ ràng 8+ sites, no TODO/FIXME zombie. TypeScript: `FlagCell` indexed-access type clever từ Pick 7 keys union — TS happy compile clean. Recommendations defer: (a) enforce admin/non-admin policy filter (current GET cho phép user pass isUserSelectable=false — low risk, audit follow-up); (b) MenuKeys.All[] không cover dynamic Pe_*_WfView nên Admin role không auto grant — OK vì admin dùng fe-admin Designer, NOTE để future audit khi admin cần inspect user-side matrix view. Cumulative state unchanged: 31 mig, 111 test, 47 gotcha. Smart Friend guard active — KHÔNG lower bar (3 minor risk noted defer follow-up, không block commit). - **2026-05-15 (S23 t4-t11 cumulative em main self-review — Plan N+O+P+Q+R+S+T+U, no Reviewer spawn):** 8 plan consecutive em main self-review per UAT mode (`feedback_uat_skip_verify` rule — em main verify build + test + npm build mỗi chunk). Key validations cumulative: **Plan N** GetPe handler `PurchaseEvaluationFeatures.cs:765` per-NV `ApproverUserId` discriminator + admin fallback row đầu. **Plan O** 4 lookup sites cascade fix (`EnsureCanRejectV2Async:201` + `ApplyReturnModeAsync:248` + `EnsureEditableForDetailsAsync:72` + `AdjustBudgetCommandHandler:311`) — pattern uniform Plan N, +3 regression test `PurchaseEvaluationPerNvLookupRegressionTests.cs`. **Plan P** Controller `TransitionPeBody:267` record +3 fields mirror `TransitionPurchaseEvaluationCommand` schema — root cause 2 ngày prod bug F1+F2 wire fail. **Plan Q** FE banner mx-5 inset gap fix (CSS layout polish mirror 2 app). **Plan R+S+T5** destructive sqlcmd cleanup prod ~720 rows wiped cumulative + Plan F precedent avoid được (V1 active workflow giữ nguyên → BE healthy startup). **Plan T** DbInitializer `DemoSeed:Disabled` flag config — disable 5 demo seed methods (Workflow V1 + PE V1 + Demo Contracts + Demo PE + Sample V2). **Plan U** FE sidebar truncate + tooltip 2 app mirror — handle long DisplayLabel (Mig 27 admin custom). Anti-patterns observed: (1) **Plan N point 9 chỉ catch 1/5 sites** — em main + Investigator spawn miss 4 sites khác → Plan O cascade fix. Lesson: grep ENUMERATE TẤT CẢ lookup sites cùng pattern (KHÔNG fix theo bug report mỗi lần). (2) **Plan O caveat #1 surfaced Plan P pre-existing bug** — CICD Monitor catch Controller body record drop trong Stage 4c verify wire — pattern "verify cross-stack body↔command count match". (3) **DbInitializer auto re-seed** loop — Plan R+S clean → IIS recycle → re-seed loop. Plan T flag fix root cause. Pattern reusable cross-project: refactor schema 1-row-per-role MUST grep enum lookup sites + Controller body record MUST mirror Command record fields. Cumulative state: 111 test (+7 vs 104 baseline pre-Plan M), 31 mig (no new), 47 gotcha unchanged, `feedback_per_nv_permission_scope.md` reinforced 5 lookup sites enum + 9 wire surface points + Plan P caveat → 10 surface points. Smart Friend guard still active for future spawn. diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index b3fd51e..580e15a 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -1,6 +1,8 @@ # HANDOFF — Brief 5 phút cho session tiếp theo -**Last updated:** 2026-05-15 (Session 24 chốt cuối — **🎯 Plan AA cumulative 7 commits push `a1a910f..ee0902a`** — User Workflow Matrix view + Sidebar widen + 4 polish iteration UAT feedback. Phase 1 Plan AA core (3 commit `ee776d5..ac2c859`): 🟦 Investigator Pre-A 5Q audit (~32K) + 👤 Chủ trì Chunk A BE+Layout solo (gotcha #44 đã fix permanent từ S18 → chỉ ADD `IsUserSelectable bool?` filter + DbInitializer INSERT-OR-UPDATE-Order idempotent shift existing prod rows + MenuKeys `Pe_DuyetNcc_WfView` helper + sidebar widen `w-72 xl:w-80` × 2 app + revert Plan U truncate × 5 sites) + 🟨 Implementer Case 2 Chunk B FE (~14K, WorkflowMatrixViewPage + types + App.tsx route ~270 LOC) + 🟥 Reviewer cumulative pre-commit (~25K, PASS 0 blocker + fe-admin build bonus 1926 modules) + 🟩 CICD Monitor Run #210 verify (4/4 wire end-to-end: filter live + menu Order shift + bundle hash rotate 2 app + Mig 31 unchanged). Phase 2 polish iteration UAT feedback (4 commit `da218f1..ee0902a`): 👤 Chủ trì Solo 4× CSS/UX polish — (1) `da218f1` hotfix container px-6→px-2 (content sát sidebar) + (2) `4d60598` redesign v1 panel-per-NV mirror admin Designer color coding (drop table 11 cột symbol khó hiểu) + (3) `fbbd361` redesign v2 HTML table rowSpan tận dụng full width (4 cột: Bước/Cấp/NV/Quyền duyệt grid 2-col 7 label) + (4) `ee0902a` wrap fix label dài về đầu hàng pattern hanging-indent reverse CSS (flex→block + inline-block icon + inline text + absolute ChevronDown) + text smaller text-[12px] leading-snug. Patterns reusable cross-project: (a) gotcha #44 relax pattern PROVEN cross-stack reuse, (b) DbInitializer INSERT-OR-UPDATE-Order idempotent re-deploy, (c) Tailwind JIT palette array (STEP_PALETTE 5 màu + LEVEL_PALETTE 5 màu cycle), (d) HTML table rowSpan flat row builder helper, (e) hanging-indent reverse via inline-block icon. Stats: 31 mig · 59 tables · **~146 endpoints (+1 GET filter param)** · **35 FE pages (+1 WorkflowMatrixViewPage)** · **111 test** (no change, baseline confirmed post-Plan AA) · 47 gotcha · 21 memory · 6 skills · 4 sub-agents. Multi-agent ROI S24: ~175K total (Inv 32K + Imp 14K + Rev 25K + CICD 12K + 3 flush agents ~12K + em main ~80K) = ~28% solo equiv. CICD verify Run #210 PASS. **Implementer MEMORY ~31.5KB > 25KB threshold** → recommend curate next session archive S20-S22 old entries.) +**Last updated:** 2026-05-19 (Session 25 chốt cuối — **🎯 Plan AB→AF cumulative 7 commits push `e23f51c..506cada` + 7 CICD Runs (#215 FAIL → #216-#221 PASS) — Lịch sử visibility critical fix UAT iteration**. Trigger: Bro UAT 2026-05-19 phát hiện 2 bug critical phiếu PE/2026/A/032: Bug 1 Budget Adjust không show "Lịch sử thay đổi" (BE log OK + FE filter strict TraLai-only loại) + Bug 2 Return Mode 4 mode KHÔNG log Changelog (`ApplyReturnModeAsync` miss `db.PurchaseEvaluationChangelogs.Add()`). **Plan AB Chunk A** (`cdfd542`, 🟨 Implementer Case 1 spawn ~12K): BE refactor `ApplyReturnModeAsync` Drafter early return → common path + single `Changelog.Add()` cover 4 mode uniform với `modeName` switch enum + `actorName` resolve via `userManager.FindByIdAsync` mirror `LogTransitionAsync` pattern. FE `PeDetailTabs.tsx` × 2 app `HistoryTab` filter extend (PE_ENTITY_HEADER=1 + summary 'ngân sách' + Workflow summary 'Trả lại'). 🟥 Reviewer pre-commit verify ~22K PASS 0 critical/major/minor (1 minor V1 legacy fallback non-block). Run #215 FAIL test_infra 2/53 — Plan M edge case tests SQLite frozen-clock tie-break (gotcha #48 NEW): 2 Changelog rows cùng `CreatedAt` → `OrderByDescending.FirstAsync()` pick wrong → ContextNote null. **Plan AB Chunk A2** (`8c05947`, em main solo): Test filter `Where(Summary.Contains("Chuyển phase"))` pick đúng LogTransition entry. 111/111 PASS. Run #216 PASS. **Plan AC** (`a734bf2`, em main solo): BE capture pre-call Step/Level → Reject branch add `PurchaseEvaluationApprovals.Add()` với Decision=Reject + Comment from-pos + mode summary. BE line 472 enrich Approve comment prefix `[Duyệt vượt cấp tới Cấp cuối]` khi skipToFinal=true. FE × 2 app ApprovalsTab thêm `decisionBadge(decision, toPhase)` helper (Duyệt emerald / Trả lại amber / Từ chối rose). Run #217 PASS, bundle rotate × 2 app. **Plan AC2** (`25837b6`, em main solo, Option 2A bro chốt): FE merge view ApprovalsTab fetch changelogs + reconstruct synthetic Reject rows từ Workflow+Reject Changelog (filter EntityType=5 + Summary "→ TraLai"/"→ TuChoi" OR ContextNote "Trả về"/"không lùi được") + dedupe `approverUserId + timestamp 5s bucket` cùng real Reject rows + merge sort by approvedAt. KHÔNG DB write, reversible historical recovery. Run #218 PASS, 8 Workflow entries verified, bundle rotate × 2 app. **Plan AD** (`0aaf2df`, em main solo, Option A bro chốt): Drop fromPhase→toPhase badges (visual confusion 3/4 mode Reject giữ ChoDuyet — gotcha #49 NEW) + add `extractNextTargetHint(decision, toPhase, comment)` helper regex parse `Chuyển phase X → Y` + keyword detect "Trả về"/"vượt cấp"/"Cấp cuối" → "→ Cấp Y" / "→ Trả về Người chỉ định (Bước X Cấp Y)" / "→ Vượt cấp tới Cấp cuối" / "→ Đã duyệt hoàn tất". Cleanup unused `PurchaseEvaluationPhaseColor` import. Mirror × 2 app §3.9. Run #219 PASS, bundle rotate × 2 app. **Plan AE** (`9ea62be`, em main solo, preventive batch fix): BE 9 `Changelog.Add()` sites add `UserName = currentUser.FullName ?? currentUser.Email` (Budget Adjust + Create PE + Update PE Header + Detail Insert/Update/Delete + Quote Insert/Update/Delete + Select Winner). ICurrentUser đã có FullName/Email từ JWT — KHÔNG inject userManager mới. `replace_all=true` 1 pass cover 8 sites cùng indent + 1 site manual fix indent 16 spaces inside if block. 111/111 PASS local. Run #220 PASS, bundle UNCHANGED (BE-only commit). **Plan AF** (`506cada`, em main solo, Option A bro chốt): FE ApprovalsTab + HistoryTab build `userMap useMemo` từ embedded PeDetailBundle data: `drafterUserId+drafterName + approvals[].approverUserId+approverName + approvalFlow.steps[].levels[].approvers[].userId+fullName + levelOpinions[].signedByUserId+signedByFullName + departmentOpinions[].userId+userName` — no extra `/api/users` fetch (admin permission). `resolveActorName(a) / resolveUserName(l)` helper: trust entry.userName non-empty → userMap.get(entry.userId) → 'Hệ thống' fallback. Mirror × 2 app §3.9. Run #221 PASS, bundle rotate × 2 app. **Multi-agent ROI S25:** 🟦 Investigator 1 spawn (~28K Bug 1+2 audit) + 🟨 Implementer 1 spawn (~12K Plan AB Chunk A Case 1) + 🟥 Reviewer 1 spawn (~22K Plan AB pre-commit) + 🟩 CICD Monitor 7 spawns (Run #215 catch test gate fail + Run #216-#221 PASS verify, ~70K cumulative). Em main solo 5 plans AC→AF cross-stack reasoning + UAT iteration. Total cumulative agent cost ~132K · em main ~210K. **State chốt S25:** 31 mig (no schema) · 59 tables · ~146 endpoints · 35 FE pages · **111 test pass unchanged** (UAT defer test-after per §7) · **49 gotcha (+2: #48 SQLite tie-break + #49 dual-phase UI confusion)** · **23 memory user-level (+2 NEW: feedback_fe_merge_synthetic_audit + feedback_fe_usermap_fallback)** · 6 skills · 4 sub-agents active. **Memory CRITICAL FLAG:** cicd-monitor MEMORY ~72KB strongly over 50KB hard threshold — DEDICATED CURATION SESSION REQUIRED next (archive Run #186-#210 + S22-S24 verbose entries to `archive/2026-05-runs-186-210.md`). Investigator 32KB / Implementer 35.7KB / Reviewer 32.3KB cũng over 25KB threshold — recommend curate cùng session với cicd-monitor. **8 patterns reusable saved S25:** (1) Multi-Changelog SQLite tie-break (gotcha #48) → test discriminator, (2) CICD catch UAT skip test risk → resumed local verify BE refactor > 100 LOC, (3) Test fix Option A over BE refactor preservation, (4) Capture pre-call mutation state cho audit row from-position, (5) FE merge synthetic rows từ Changelog reversible recovery (Plan AC2), (6) Drop misleading dual-phase badges + semantic next-target hint (Plan AD), (7) Changelog UserName preventive systemic batch fix 9 sites (Plan AE), (8) FE userMap fallback từ embedded domain data no extra API (Plan AF). Bro UAT verify cumulative deploy live PE/2026/A/032 — historical entries pre-Plan AE nay resolve user names qua userMap fallback.) + +**Last updated S24:** 2026-05-15 (Session 24 chốt cuối — **🎯 Plan AA cumulative 7 commits push `a1a910f..ee0902a`** — User Workflow Matrix view + Sidebar widen + 4 polish iteration UAT feedback. Phase 1 Plan AA core (3 commit `ee776d5..ac2c859`): 🟦 Investigator Pre-A 5Q audit (~32K) + 👤 Chủ trì Chunk A BE+Layout solo (gotcha #44 đã fix permanent từ S18 → chỉ ADD `IsUserSelectable bool?` filter + DbInitializer INSERT-OR-UPDATE-Order idempotent shift existing prod rows + MenuKeys `Pe_DuyetNcc_WfView` helper + sidebar widen `w-72 xl:w-80` × 2 app + revert Plan U truncate × 5 sites) + 🟨 Implementer Case 2 Chunk B FE (~14K, WorkflowMatrixViewPage + types + App.tsx route ~270 LOC) + 🟥 Reviewer cumulative pre-commit (~25K, PASS 0 blocker + fe-admin build bonus 1926 modules) + 🟩 CICD Monitor Run #210 verify (4/4 wire end-to-end: filter live + menu Order shift + bundle hash rotate 2 app + Mig 31 unchanged). Phase 2 polish iteration UAT feedback (4 commit `da218f1..ee0902a`): 👤 Chủ trì Solo 4× CSS/UX polish — (1) `da218f1` hotfix container px-6→px-2 (content sát sidebar) + (2) `4d60598` redesign v1 panel-per-NV mirror admin Designer color coding (drop table 11 cột symbol khó hiểu) + (3) `fbbd361` redesign v2 HTML table rowSpan tận dụng full width (4 cột: Bước/Cấp/NV/Quyền duyệt grid 2-col 7 label) + (4) `ee0902a` wrap fix label dài về đầu hàng pattern hanging-indent reverse CSS (flex→block + inline-block icon + inline text + absolute ChevronDown) + text smaller text-[12px] leading-snug. Patterns reusable cross-project: (a) gotcha #44 relax pattern PROVEN cross-stack reuse, (b) DbInitializer INSERT-OR-UPDATE-Order idempotent re-deploy, (c) Tailwind JIT palette array (STEP_PALETTE 5 màu + LEVEL_PALETTE 5 màu cycle), (d) HTML table rowSpan flat row builder helper, (e) hanging-indent reverse via inline-block icon. Stats: 31 mig · 59 tables · **~146 endpoints (+1 GET filter param)** · **35 FE pages (+1 WorkflowMatrixViewPage)** · **111 test** (no change, baseline confirmed post-Plan AA) · 47 gotcha · 21 memory · 6 skills · 4 sub-agents. Multi-agent ROI S24: ~175K total (Inv 32K + Imp 14K + Rev 25K + CICD 12K + 3 flush agents ~12K + em main ~80K) = ~28% solo equiv. CICD verify Run #210 PASS. **Implementer MEMORY ~31.5KB > 25KB threshold** → recommend curate next session archive S20-S22 old entries.) **Last updated S23 t12:** 2026-05-15 (Session 23 turn 12 chốt cuối — **🎯 S23 cumulative 11 plan + 32 commits**. Plan K (Mig 31 F2 refactor 9c) → L (UAT bug 5c) → M (F1 edge case 4c) → N (per-NV lookup 1 site 2c) → O (4 sites cascade 2c) → P (Controller body record 1c) → Q (FE banner 1c) → R (cleanup phiếu/wf 1c) → S (wipe ALL wf 1c) → T (DemoSeed flag 2c) → U (sidebar truncate 1c). 4 sub-agents: Investigator 5 spawn + Implementer 5 spawn + Reviewer 2 spawn + CICD Monitor 10 verify runs PASS. Memory +1 NEW `feedback_demo_seed_flag_disable.md` + `feedback_per_nv_permission_scope.md` reinforced 10 wire surface points (point 9 lookup discrimination + point 10 Controller body record mirror). Cleanup cumulative R+S+T5 ~720 rows wiped + DbInitializer flag persist. Stats: 31 mig · 59 tables · ~145 endpoints · **111 test** · 47 gotcha · **21 memory** (+1) · 6 skills · **0 PE + 0 demo workflow + flag persist** UAT permanent clean slate. 32 commits push `eb106f2..86d8806`. Plan B Contract V2 wire HIGH next.) diff --git a/docs/STATUS.md b/docs/STATUS.md index 7bbfc9a..90793b7 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -2,7 +2,9 @@ > **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`. -**Last updated:** 2026-05-15 (Session 23 turn 12 chốt cuối — **🎯 S23 cumulative wrap: 11 plan + 32 commits cumulative**. Plan K (Mig 31 F2 refactor 9 commits) → L (UAT bug 5) → M (F1 edge case 4) → N (per-NV lookup 1 site 2) → O (4 sites cascade 2) → P (Controller body record 1) → Q (FE banner CSS 1) → R (cleanup phiếu/workflow 1) → S (wipe ALL workflows 1) → T (DemoSeed flag 2) → U (sidebar truncate 1). 4 sub-agents active throughout: 🟦 Investigator 5 spawn (K0 + N + P FE wire + R audit + L2 spawn) + 🟨 Implementer 5 spawn (K1/K3/K5/K7 + M2 + M3 Case 2/3) + 🟥 Reviewer 2 spawn (K2 pre-commit + M3 cumulative review) + 🟩 CICD Monitor 5 spawn (Run #195 K10 + #199 L + #200 M + #201 N + #202 O + #203 P + #204 Q + #207 T + #208 T5+T6 + #209 U — ALL PASS). **Memory user-level update cumulative S23:** `feedback_per_nv_permission_scope.md` reinforced 5 sections (S22+5 + S23 t1 + t3 + t4 + t5 + t6) → wire 10 surface points (point 9 lookup discrimination + point 10 Controller body record mirror). `feedback_uat_skip_verify.md` Plan L lesson Service refactor semantic test atomic. **+1 memory NEW** `feedback_demo_seed_flag_disable.md` cross-project Plan T pattern. **3 destructive cleanup cumulative Plan R+S+T5** ~720 rows wiped + DbInitializer permanent disable demo seed. **Stats final S23 chốt cuối:** 31 mig · 59 tables · ~145 endpoints · 34 FE pages · **111 test unchanged** · 47 gotcha · **21 memory entries** (+1 NEW Plan T flag pattern) · 6 skills · 4 sub-agents active · **0 PE + 0 demo workflow + DemoSeed flag persist** UAT permanent clean slate. Backup rollback: `vietreport-vps:C:\Backup\SolutionErp_pre_cleanup_2026-05-15.bak` 18.5MB. **32 commits S23** pushed remote `eb106f2..86d8806`. CI verify ALL PASS. Bro Designer setup workflow mới from scratch, KHÔNG còn auto re-seed contaminate. Plan B Contract V2 wire HIGH priority next.) +**Last updated:** 2026-05-19 (Session 25 chốt cuối — **🎯 S25 cumulative wrap: 7 plan + 7 commits Plan AB→AF + 7 CICD Runs (#215 FAIL → #216-#221 PASS)**. Push remote `e23f51c..506cada`. Bro UAT 2026-05-19 phát hiện 2 bug visibility critical PE/2026/A/032 + 4 follow-up polish UAT iteration: **Bug 1 Budget Adjust** không show "Lịch sử thay đổi" (BE log Header+Update OK nhưng FE filter strict TraLai-only loại) — **Bug 2 Return Mode 4 mode** không log Changelog (ApplyReturnModeAsync miss `db.PurchaseEvaluationChangelogs.Add()`) — **Bug 3 Lịch sử duyệt** missing Trả lại + Vượt cấp entries (Reject branch không add Approval row) — **Bug 4 dual-phase badges** gây nhầm "Đã gửi duyệt → Đã gửi duyệt" cho 3 mode Reject giữ Phase=ChoDuyet — **Bug 5 Changelog UserName** show "Hệ thống" thay vì user thật (9 sites BE miss UserName) — **Bug 6 historical name resolve** entries pre-deploy vẫn show empty (forward-only fix). **Plan AB Chunk A** (`cdfd542`) BE log return mode + FE filter relax × 2 app. **Plan AB Chunk A2** (`8c05947`) fix Plan M tests SQLite frozen-clock tie-break (gotcha #48 NEW — Run #215 caught + Run #216 PASS). **Plan AC** (`a734bf2`) BE Reject add Approval row + skipToFinal comment + FE Decision badge × 2 app. **Plan AC2** (`25837b6`) FE merge synthetic Reject rows từ Changelog historical (Option 2A bro chốt, reversible no-DB-touch). **Plan AD** (`0aaf2df`) Drop misleading dual-phase badges + parse next-target hint via regex `Chuyển phase X → Y` + comment keyword Trả lại/Vượt cấp (gotcha #49 NEW). **Plan AE** (`9ea62be`) BE preventive batch fix UserName 9 Changelog.Add sites (Budget Adjust + 8 sites: Create + Update + Detail CRUD + Quote CRUD + Select Winner). **Plan AF** (`506cada`) FE userMap fallback từ embedded domain data PeDetailBundle (drafter + approvals + approvalFlow + levelOpinions + departmentOpinions) — no extra API fetch admin permission. Multi-agent execution: 🟦 **Investigator** 1 spawn Plan AB Bug 1+2 audit ~28K + 🟨 **Implementer** 1 spawn Plan AB Chunk A ~12K Case 1 + 🟥 **Reviewer** 1 spawn Plan AB pre-commit ~22K PASS 0 blocker (1 minor non-block) + 🟩 **CICD Monitor** 7 spawns Run #215-#221 verify ALL PASS post-Plan AB Chunk A2 fix. **Em main solo từ Plan AC đến AF** (5 plan, cross-stack reasoning + UAT iteration borderline, Implementer/Reviewer KHÔNG re-spawn). Stats final S25 chốt: **31 mig (no schema)** · 59 tables · ~146 endpoints · 35 FE pages · **111 test pass unchanged** (UAT defer test-after per §7) · **49 gotcha (+2 #48 SQLite tie-break + #49 dual-phase UI confusion)** · **23 memory user-level (+2 NEW: `feedback_fe_merge_synthetic_audit` Plan AC2 + `feedback_fe_usermap_fallback` Plan AF)** · 6 skills · 4 sub-agents active. Bundle hash 6× rotate × 2 app (#216-#221), Mig 31 unchanged toàn S25. Patterns reusable cross-project (8 NEW S25): (1) Multi-Changelog.Add SQLite tie-break test discriminator, (2) CICD catch UAT skip test risk for BE refactor > 100 LOC, (3) Test fix Option A over BE refactor preservation, (4) Capture pre-call mutation state cho audit row from-position, (5) FE merge synthetic rows từ Changelog reversible recovery, (6) Drop misleading dual-phase badges + parse semantic hint, (7) Changelog UserName preventive systemic batch fix, (8) FE userMap fallback từ embedded domain data no extra API. **Memory CRITICAL FLAG**: cicd-monitor MEMORY ~72KB strongly over 50KB hard threshold — DEDICATED CURATION SESSION REQUIRED next (archive Run #186-#210 + S22-S24 verbose). Bro UAT verify ALL Plan AB→AF live deploy cumulative.) + +**Last updated S23 t12:** 2026-05-15 (Session 23 turn 12 chốt cuối — **🎯 S23 cumulative wrap: 11 plan + 32 commits cumulative**. Plan K (Mig 31 F2 refactor 9 commits) → L (UAT bug 5) → M (F1 edge case 4) → N (per-NV lookup 1 site 2) → O (4 sites cascade 2) → P (Controller body record 1) → Q (FE banner CSS 1) → R (cleanup phiếu/workflow 1) → S (wipe ALL workflows 1) → T (DemoSeed flag 2) → U (sidebar truncate 1). 4 sub-agents active throughout: 🟦 Investigator 5 spawn (K0 + N + P FE wire + R audit + L2 spawn) + 🟨 Implementer 5 spawn (K1/K3/K5/K7 + M2 + M3 Case 2/3) + 🟥 Reviewer 2 spawn (K2 pre-commit + M3 cumulative review) + 🟩 CICD Monitor 5 spawn (Run #195 K10 + #199 L + #200 M + #201 N + #202 O + #203 P + #204 Q + #207 T + #208 T5+T6 + #209 U — ALL PASS). **Memory user-level update cumulative S23:** `feedback_per_nv_permission_scope.md` reinforced 5 sections (S22+5 + S23 t1 + t3 + t4 + t5 + t6) → wire 10 surface points (point 9 lookup discrimination + point 10 Controller body record mirror). `feedback_uat_skip_verify.md` Plan L lesson Service refactor semantic test atomic. **+1 memory NEW** `feedback_demo_seed_flag_disable.md` cross-project Plan T pattern. **3 destructive cleanup cumulative Plan R+S+T5** ~720 rows wiped + DbInitializer permanent disable demo seed. **Stats final S23 chốt cuối:** 31 mig · 59 tables · ~145 endpoints · 34 FE pages · **111 test unchanged** · 47 gotcha · **21 memory entries** (+1 NEW Plan T flag pattern) · 6 skills · 4 sub-agents active · **0 PE + 0 demo workflow + DemoSeed flag persist** UAT permanent clean slate. Backup rollback: `vietreport-vps:C:\Backup\SolutionErp_pre_cleanup_2026-05-15.bak` 18.5MB. **32 commits S23** pushed remote `eb106f2..86d8806`. CI verify ALL PASS. Bro Designer setup workflow mới from scratch, KHÔNG còn auto re-seed contaminate. Plan B Contract V2 wire HIGH priority next.) **Last updated S23 t10:** 2026-05-15 (Session 23 turn 10 — **🔧 Plan T: Disable auto re-seed demo data + final DELETE — UAT permanent clean slate**. Bro phát hiện sau Plan R+S: 4 phiếu `[DEMO]-A/B` + 2 V1 workflows + 1 V2 mẫu UAT TỰ ĐỘNG RE-SEED sau khi BE deploy commits Plan P+Q+R+S → IIS recycle → `DbInitializer.InitializeAsync` chạy lại 5 demo seed methods. Bro AskUserQuestion chốt Option A: disable demo seed vĩnh viễn qua flag config. Em main implement: `appsettings.json` add `"DemoSeed": { "Disabled": true }` + `appsettings.Development.json` override `false` cho dev test seed local + `DbInitializer.cs` add `using Microsoft.Extensions.Configuration` + `var demoSeedDisabled = config.GetValue("DemoSeed:Disabled")` + wrap 5 method conditional `if (!demoSeedDisabled)` (SeedWorkflowDefinitions V1 + SeedPurchaseEvaluationWorkflows V1 + SeedDemoContracts + SeedDemoPurchaseEvaluations + SeedSampleApprovalWorkflowsV2). KEEP: SeedRoles + SeedAdmin + SeedDepartments + SeedDemoUsers (30 UAT users) + SeedMenuTree + SeedAdminPermissions + SeedDemoMasterData (Supplier/Project) + SeedContractTemplates + SeedCatalogs + Backfill helpers. Note: `appsettings.Production.json` bị `.gitignore` (secrets), em set flag mặc định trong `appsettings.json` commit qua git — production inherit `true`. Run #207 sha=0b97840 PASS 3min24s → IIS deploy applied flag. T5 final DELETE: SCP `scripts/plan-t5-final-cleanup.sql` upload + sqlcmd -i 3 TRANSACTION DELETE → 4 PE + 1 V2 + 2 V1 = 7 rows + cascade child. T6 verify NO re-seed loop: `Restart-WebAppPool SolutionErp-Api` force IIS recycle → BE startup → wait healthy → sqlcmd verify: **PE=0 + V2=0 + V1=0** preserved (KHÔNG re-seed) + Users=33 + Suppliers=19 + Projects=9 + Contracts=7 (existing preserved). DemoSeed flag PROVEN active end-to-end. Total cumulative Plan R+S+T cleanup: ~677 rows wiped + DbInitializer re-seed disable vĩnh viễn. Stats final S23 t10: **31 mig** · 59 tables · ~145 endpoints · 34 FE pages · **111 test unchanged** · 47 gotcha · 20 memory · 6 skills · **0 PE + 0 workflow + flag disable demo seed** — UAT permanent clean slate. Bro Designer setup workflow mới from scratch, KHÔNG còn auto re-seed contaminate.) **Last updated S23 t9:** 2026-05-15 (Session 23 turn 9 — **🧹 Plan S: Wipe ALL workflows — UAT clean slate hoàn toàn**. Bro chốt sau Plan R: "các cái demo quy trình cũ -> xóa hết luôn đi nhé". 4 workflows còn lại (2 V2 ghim + 2 V1 active) đều seed demo cumulative (`(mẫu UAT)` / `(clone)(clone)` / `(v01)` sample seed). Bro AskUserQuestion chốt Option A (Recommended): wipe ALL 4 workflows, UAT clean slate. Em main solo execute (Investigator audit Plan R đã cover scope precedent, Plan S = continuation cùng UAT cleanup phase). Backup rollback: Plan R backup `SolutionErp_pre_cleanup_2026-05-15.bak` 18.5MB vẫn capture full state pre-cleanup → reuse cho rollback. Script `scripts/plan-s-wipe-all-workflows.sql` 2 BEGIN/COMMIT TRANSACTION + SET QUOTED_IDENTIFIER ON. Execute via scp + sqlcmd -i: DELETE ALL ApprovalWorkflows (2 rows cascade Steps+Levels) + DELETE ALL PurchaseEvaluationWorkflowDefinitions (2 rows cascade Steps+Approvers). Post-state: **0 V2 + 0 V1 + 0 Steps + 0 Levels + 0 Approvers** (ALL workflow entities wiped). BE smoke verify 5/5 endpoints 200 (auth + PE list + V2 workflows + V1 workflows + users + menus) — KHÔNG crash startup (no Contract pin to V1 — chỉ PE dùng workflow, PE đã wipe Plan R). Hậu quả expected: user KHÔNG tạo được phiếu mới (Workspace Select empty) cho đến khi admin Designer seed workflow mới from scratch — UAT mode chấp nhận. Total cleanup cumulative Plan R + S: 35 PE + 17 V2 + 4 V1 + ~600 cascade child = **~670 rows wiped**, prod database UAT clean slate hoàn toàn. Stats final S23 t9: **31 mig** · 59 tables · ~145 endpoints · 34 FE pages · 111 test · 47 gotcha · 20 memory · 6 skills · **0 PE + 0 workflow** (database state mới hoàn toàn). Pending: admin Designer seed workflow mới + bro UAT test fresh.) **Last updated S23 t8:** 2026-05-15 (Session 23 turn 8 — **🧹 Plan R: Cleanup destructive prod database — phiếu test + workflow ko ghim**. Bro UAT confirm Plan P+Q wire OK + chỉ thị "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 ~64K spawn confirm scope: 28 PE active + 7 soft-deleted + 15 V2 workflows IsUserSelectable=false + 2 V1 workflows IsActive=false. FK Restrict gotcha: PE.ApprovalWorkflowId Restrict → phải hard-DELETE PE trước (soft-delete KHÔNG release FK). Entity extend `BaseEntity` (workflow) KHÔNG hỗ trợ soft-delete. Bro chốt Option A (Recommended) qua AskUserQuestion. Em main execute 6 steps qua sqlcmd ssh vietreport-vps: (1) BACKUP `SolutionErp_pre_cleanup_2026-05-15.bak` 18.5MB qua `scripts/plan-r-backup.sql` upload scp + sqlcmd -i (workaround SQL Express no COMPRESSION + no RESTORE VERIFYONLY permission cho vrapp). (2) Script `scripts/plan-r-cleanup.sql` 3 BEGIN/COMMIT TRANSACTION + SET QUOTED_IDENTIFIER ON (filtered index Mig 29+ require). (3) DELETE 35 PE rows (28 active + 7 soft-deleted, cascade ~446 child: 42 Details + 49 Suppliers + 64 Approvals + 238 Changelogs + 10 Attachments + 43 LevelOpinions). (4) DELETE 15 V2 unghim (cascade ~140 Steps+Levels). (5) DELETE 2 V1 inactive (cascade ~37 Steps+Approvers). Total: **52 rows + ~600 cascade child deleted**. Post-cleanup state: **0 PE · 2 V2 workflows ghim** (`QT-DN-V2-001 v16` + `QT-DN-PA-V2-001 v2`) · **2 V1 workflows active** (`QT-DN-A v3` + `QT-DN-B v1`). Smoke verify BE alive post-cleanup: 3/3 endpoints 200 (auth login + PE list + V2/V1 workflow list) → KHÔNG crash startup (Plan F precedent S22 avoid được — V1 active workflow giữ nguyên). Multi-agent ROI: Investigator catch FK Restrict gotcha + recommend backup mandatory + 3 Option compare — saved em main hard-delete without backup risk. Stats final S23 t8: **31 mig** · 59 tables · ~145 endpoints · 34 FE pages · 111 test unchanged · 47 gotcha · 20 memory · 6 skills · **0 PE phiếu test + 4 workflow ghim/active** (gọn đẹp UAT clean slate). Pending: bro UAT test workflow mới fresh.) diff --git a/docs/changelog/sessions/2026-05-19-s25-pe-history-visibility.md b/docs/changelog/sessions/2026-05-19-s25-pe-history-visibility.md new file mode 100644 index 0000000..5c1888a --- /dev/null +++ b/docs/changelog/sessions/2026-05-19-s25-pe-history-visibility.md @@ -0,0 +1,326 @@ +# Session 25 — 2026-05-19 — PE History Visibility cumulative fix (Plan AB → AF) + +**Dev:** Claude Opus 4.7 1M (em main + 4 sub-agent spawns Investigator+Implementer+Reviewer+CICD) +**Duration:** ~5h +**Base commit:** `e23f51c` (Docs Session 24 wrap) +**Final HEAD:** `506cada` Plan AF FE userMap fallback +**Total commits S25:** **7** (`cdfd542`, `8c05947`, `a734bf2`, `25837b6`, `0aaf2df`, `9ea62be`, `506cada`) +**CICD Runs:** **7** (#215 FAIL test gate → #216-#221 ALL PASS post-fix) + +## 🎯 Trigger session + +Bro UAT 2026-05-19 sau S24 Plan AA deploy phát hiện 2 bug critical phiếu **PE/2026/A/032** (`3248f2f9-c6e9-43ff-a4ca-067ffecf9f36`): + +**Bug 1 — Budget Adjust không show "Lịch sử thay đổi":** +- Bro click "Điều chỉnh ngân sách" 2× (5.300.000.000 → 5.200.000.000 đ) +- Section "Lịch sử thay đổi" empty placeholder "Chưa có lịch sử trả lại / gửi duyệt lại" + +**Bug 2 — Trả lại Người chỉ định không log:** +- Bro role Approver Phan Văn Chương click "Trả lại Người chỉ định" → Bùi Lê Thủy Trà +- Section "Lịch sử thay đổi" KHÔNG show entry return assignee + +Sau Plan AB deploy bro phát hiện thêm 4 issues UAT iterative — em main solo 5 follow-up plans (AC→AF) cross-stack reasoning. + +## 🌳 Plan execution 7 commits Plan AB → AF + +### Plan AB — Bug 1 + Bug 2 root fix + +#### Investigator pre-flight audit (🟦 Investigator, ~28K) + +5Q audit confirm root cause: + +| Q | Finding | +|---|---| +| Q1 DB state Changelogs cho PE | SSH auth fail, fallback grep code BE | +| Q2 BE AdjustBudget handler | `PurchaseEvaluationFeatures.cs:379-387` ĐÃ log `EntityType=Header + Action=Update + Summary="Điều chỉnh ngân sách..."` ✓ | +| Q3 BE ApplyReturnModeAsync | line 215-378 **ZERO** `Changelog.Add()` cho 4 mode. Caller `TransitionAsync:100` chỉ log phase transition qua `LogTransitionAsync` ❌ | +| Q4 Schema PurchaseEvaluationChangelog | `EntityType` enum {Header=1, Supplier=2, Detail=3, Quote=4, Workflow=5, Attachment=6}. Missing Kind discriminator (Header+Update collision Budget vs Detail) | +| Q5 FE filter HistoryTab | Hypothesis FE filter omit Header+Workflow types (unconfirmed Investigator scope) | + +**Conclusion:** +- Bug 1: BE log OK + FE filter strict TraLai-only loại Header+ChoDuyet +- Bug 2: BE Service hook ROOT GAP — KHÔNG log Changelog cho 4 mode return + +#### Em main verify FE filter logic + +`PeDetailTabs.tsx:2037-2046` HistoryTab filter: +```ts +const PE_PHASE_TRALAI = 98 +const PE_ENTITY_WORKFLOW = 5 +const filtered = (logs.data ?? []).filter(l => { + if (l.entityType === PE_ENTITY_WORKFLOW) { + if (l.phaseAtChange === PE_PHASE_TRALAI) return true + if (l.summary?.includes('TraLai →')) return true + return false + } + return l.phaseAtChange === PE_PHASE_TRALAI // ← Header Budget Adjust ChoDuyet bị loại +}) +``` + +Filter chỉ pass khi phase=TraLai. Header Budget Adjust ChoDuyet → loại. Workflow Reject 3/4 mode giữ ChoDuyet → loại. + +#### Plan AB Chunk A — `cdfd542` (🟨 Implementer Case 1 ~12K, 🟥 Reviewer ~22K) + +**BE refactor `ApplyReturnModeAsync`:** +- Drafter early return (line 282-287) → if/else common path +- Single `Changelog.Add()` cuối hàm cover 4 mode uniform với `modeName` switch +- `actorName` resolve via `userManager.FindByIdAsync` mirror `LogTransitionAsync` pattern + +**FE filter relax × 2 app:** +- ADD `PE_ENTITY_HEADER = 1` + `summary?.toLowerCase().includes('ngân sách')` cho Bug 1 +- ADD `summary?.includes('Trả lại')` Workflow entity cho Bug 2 +- Update placeholder text + comment 5 rule + +3 files +146/-95 LOC. Reviewer PASS 0 blocker. + +#### Run #215 FAIL — gotcha #48 NEW (🟩 CICD Monitor) + +`test_infra` 51/53 PASS, 2 FAIL Plan M edge case: +- `ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` +- `ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` + +Error: `Expected changelog.ContextNote not to be ` + +**Root cause:** SQLite frozen test clock + 2 Changelog.Add() trong cùng SaveChangesAsync → identical CreatedAt → `OrderByDescending.FirstAsync()` non-deterministic → pick Plan AB row (Workflow+Update+ContextNote=null) thay LogTransition row. + +#### Plan AB Chunk A2 — `8c05947` (em main solo) + +Test fix Option A: filter `Where(c => c.Summary!.Contains("Chuyển phase"))` pick đúng LogTransition entry. Plan AB BE code stays clean. + +```diff +- .Where(c => c.PurchaseEvaluationId == pe.Id) ++ .Where(c => c.PurchaseEvaluationId == pe.Id && c.Summary!.Contains("Chuyển phase")) +``` + +111/111 PASS local. Run #216 PASS deploy success. + +### Plan AC — Bug 3 Lịch sử duyệt missing Trả lại + Vượt cấp + +Bro UAT post Plan AB: "Lịch sử duyệt" panel 6 entries TẤT CẢ "Đã gửi duyệt → Đã gửi duyệt" (Approve only). Trả lại + Vượt cấp KHÔNG hiện. + +**Root cause:** +- `PurchaseEvaluationApprovals.Add()` chỉ ở Approve branch (line 472 V2 + 660 V1) +- Reject branch line 75-103 NEVER add row → Lịch sử duyệt missing +- skipToFinal advance line 532-572 dùng existing line 472 row nhưng comment KHÔNG distinct + +#### Plan AC commit `a734bf2` (em main solo) + +**BE Reject branch add Approval row:** +```csharp +// Capture pre-call (ApplyReturnModeAsync mutates pointer) +var fromStepIdx = evaluation.CurrentWorkflowStepIndex; +var fromLevelOrder = evaluation.CurrentApprovalLevelOrder; + +// ... mutate via ApplyReturnModeAsync ... + +// Add Approval row (Decision=Reject) +db.PurchaseEvaluationApprovals.Add(new PurchaseEvaluationApproval { + PurchaseEvaluationId = evaluation.Id, + FromPhase = fromPhase, + ToPhase = evaluation.Phase, + ApproverUserId = actorUserId, + Decision = ApprovalDecision.Reject, + Comment = $"{fromPos}{comment ?? ""}".Trim(), + ApprovedAt = dateTime.UtcNow, +}); +``` + +**BE skipToFinal enrich comment** line 479 `Comment = $"{skipPrefix}[Bước X — Cấp Y] {comment}"`. + +**FE Decision badge × 2 app:** +```tsx +function decisionBadge(decision, toPhase): { label, cls } { + if (decision === PE_DECISION_REJECT) { + if (toPhase === 99) return { label: 'Từ chối', cls: 'bg-rose-100 text-rose-700 ...' } + return { label: 'Trả lại', cls: 'bg-amber-100 text-amber-700 ...' } + } + return { label: 'Duyệt', cls: 'bg-emerald-100 text-emerald-700 ...' } +} +``` + +Run #217 PASS, bundle rotate × 2 app. + +### Plan AC2 — Bug FE merge synthetic Reject from Changelog historical + +Bro: "phiếu cũ PE/2026/A/032 ko thể hiện đc lịch sử trả duyệt và gửi vượt cấp ?" + +Plan AC chỉ forward fix — entries pre-deploy chỉ có Changelog (LogTransition), Approval table missing Reject rows. + +#### Plan AC2 commit `25837b6` (em main solo, Option 2A bro chốt) + +FE merge view: ApprovalsTab fetch changelogs + reconstruct synthetic Reject rows: +- Filter: `entityType=5 Workflow + (summary "→ TraLai"/"→ TuChoi" OR contextNote "Trả về"/"không lùi được")` +- Parse phase từ Summary regex `Chuyển phase X → Y` → enum map (DangSoanThao=1, ChoDuyet=10, DaDuyet=20, TraLai=98, TuChoi=99) +- Synthetic ID prefix `syn-{changelog.id}`, decision=2 +- Dedupe: `${approverUserId}-${Math.floor(approvedAt / 5000)}` 5s bucket key +- Merge sort by `approvedAt` + +KHÔNG DB write, reversible historical recovery. Run #218 PASS, 8 Workflow entries verified. + +### Plan AD — Bug 4 dual-phase badges gây nhầm + +Bro: "các bước này chưa thể hiện đc là đã gửi duyệt cho ai ? Trả lại cũng vậy (Đã trả lại thì là trả lại chứ hiển thị đã gửi duyệt thì lại gây hiểu nhầm)" + +3/4 mode Reject (OneLevel/OneStep/Assignee) giữ Phase=ChoDuyet → badge `Đã gửi duyệt → Đã gửi duyệt` identical Approve. + +#### Plan AD commit `0aaf2df` (em main solo, Option A bro chốt) + +**Drop fromPhase→toPhase badges entirely.** + +**Add `extractNextTargetHint(decision, toPhase, comment)` helper:** +```ts +// Approve patterns: +// "sang Cấp X" → "→ Cấp X" +// "sang Bước Y" → "→ Bước Y (Cấp 1)" +// "[Duyệt vượt cấp tới Cấp cuối]" → "→ Vượt cấp tới Cấp cuối" +// toPhase=DaDuyet(20) → "→ Đã duyệt hoàn tất" +// Reject patterns: +// "Người chỉ định" + Bước/Cấp regex → "→ Trả về Người chỉ định (Bước X Cấp Y)" +// "Người soạn thảo"/"Drafter" → "→ Trả về Người soạn thảo" +// "không lùi được" → "→ Không lùi được" +// "Trả về 1 Cấp"/"Trả về Cấp X" → "→ Lùi về Cấp X" +// "Trả về 1 Bước"/"Trả về Bước X" → "→ Lùi về Bước X" +// toPhase=TuChoi(99) → "→ Từ chối hoàn toàn" +``` + +Cleanup unused `PurchaseEvaluationPhaseColor` import. Mirror × 2 app §3.9. Run #219 PASS, bundle rotate × 2 app. **Gotcha #49 NEW**. + +### Plan AE — Bug 5 Changelog UserName show "Hệ thống" + +Bro: "chỗ điều chỉnh ngân sách em bắc luôn user nào điều chỉnh luôn em nhé" + +Audit phát hiện systemic gap — 9 `Changelog.Add()` sites trong PE features MISSING UserName field. + +#### Plan AE commit `9ea62be` (em main solo, preventive batch) + +**9 sites batch fix:** +- `PurchaseEvaluationFeatures.cs` 4 sites: Create + Default detail + UpdateDraft + AdjustBudget +- `PurchaseEvaluationDetailFeatures.cs` 5 sites: Detail Insert/Update/Delete + Quote Insert/Update/Delete + Select Winner (1 inside if-block 16-space indent) + +**Pattern:** +```csharp +UserName = currentUser.FullName ?? currentUser.Email +``` + +ICurrentUser interface đã có FullName + Email từ JWT claims — KHÔNG inject userManager mới. + +`Edit replace_all=true` 1 pass cover 8 sites cùng indent + 1 site manual fix indent. + +111/111 PASS local. Run #220 PASS, bundle UNCHANGED (BE-only commit). + +### Plan AF — Bug 6 historical entries pre-Plan AE missing name + +Bro: "phiếu cũ ko thể hiện đc người edit nhỉ ?" + +Plan AE chỉ forward — entries pre-deploy có `userName=""` empty. + +#### Plan AF commit `506cada` (em main solo, Option A bro chốt) + +FE ApprovalsTab + HistoryTab build `userMap useMemo` từ embedded PeDetailBundle data: + +```tsx +const userMap = useMemo(() => { + const m = new Map() + if (ev.drafterUserId && ev.drafterName) m.set(ev.drafterUserId, ev.drafterName) + ev.approvals.forEach(a => { + if (a.approverUserId && a.approverName) m.set(a.approverUserId, a.approverName) + }) + ev.approvalFlow?.steps?.forEach(s => + s.levels?.forEach(l => + l.approvers?.forEach(ap => { + if (ap.userId && ap.fullName) m.set(ap.userId, ap.fullName) + }) + ) + ) + ev.levelOpinions?.forEach(o => { + if (o.signedByUserId && o.signedByFullName) m.set(o.signedByUserId, o.signedByFullName) + }) + ev.departmentOpinions?.forEach(o => { + if (o.userId && o.userName) m.set(o.userId, o.userName) + }) + return m +}, [ev]) + +const resolveUserName = (l: PeChangelog): string => { + if (l.userName && l.userName.trim() !== '') return l.userName + if (l.userId) { + const name = userMap.get(l.userId) + if (name) return name + } + return 'Hệ thống' +} +``` + +NO extra API fetch (`/api/users` admin permission). Cover gần hết users tham gia phiếu. Mirror × 2 app §3.9. + +Run #221 PASS, bundle rotate × 2 app (admin `DR95zKWg → C8TvDy7r`, user `BAj_Yaj5 → BvcWrq2z`). + +## 📊 Stats final S25 chốt cuối + +| Metric | Pre-S25 (S24) | Post-S25 | Δ | +|---|---:|---:|---:| +| Migrations | 31 | 31 | 0 (no schema change) | +| DB tables | 59 | 59 | 0 | +| Endpoints | ~146 | ~146 | 0 | +| FE pages | 35 | 35 | 0 | +| Unit tests | 111 | 111 | 0 (UAT defer test-after per §7) | +| Gotchas | 47 | **49** | **+2 (#48 SQLite tie-break + #49 dual-phase confusion)** | +| Memory user-level | 21 | **23** | **+2 (NEW: feedback_fe_merge_synthetic_audit + feedback_fe_usermap_fallback)** | +| Skills | 6 | 6 | 0 | +| Sub-agents active | 4 | 4 | 0 | +| **Commits S25** | — | **7** | `cdfd542`, `8c05947`, `a734bf2`, `25837b6`, `0aaf2df`, `9ea62be`, `506cada` | +| **CICD Runs** | — | **7** | #215 FAIL → #216-#221 PASS | +| Bundle rotate × 2 app | — | **6×** | (Run #216,217,218,219,221) — Run #220 BE-only unchanged | + +## 🎯 Multi-agent ROI S25 + +| Owner | Cost | Verdict | Value catch | +|---|---|---|---| +| 🟦 Investigator Plan AB Bug 1+2 | ~28K | ✅ | 5Q audit confirm BE Service hook gap (Bug 2) + FE filter strict (Bug 1). Saved em main blind cross-stack fix | +| 🟨 Implementer Plan AB Chunk A | ~12K | ✅ | 3 files +146/-95 LOC cookie-cutter mirror BE refactor + FE 2 app §3.9 | +| 🟥 Reviewer Plan AB pre-commit | ~22K | ✅ | 5-category + 8 adversarial deep checks PASS 0 blocker (1 minor V1 fallback non-block) | +| 🟩 CICD Monitor Run #215 catch FAIL | ~10K | ⚠️ | **VALUE CAUGHT** — SQLite tie-break gotcha #48, test gate blocked prod from broken state | +| 🟩 CICD Monitor Run #216-#221 PASS | ~60K (5 spawns × ~12K) | ✅ | Bundle rotate verify + wire end-to-end + Mig 31 unchanged + Plan AC2 changelogs data source confirm | +| 👤 Chủ trì Solo Plan AB Chunk A2 fix tests | ~3K | ✅ | Test filter discriminator surgical fix, 111/111 PASS | +| 👤 Chủ trì Solo Plan AC | ~12K | ✅ | Cross-stack BE Reject + FE Decision badge × 2 app | +| 👤 Chủ trì Solo Plan AC2 | ~10K | ✅ | FE merge synthetic Reject + dedupe (Option 2A historical recovery) | +| 👤 Chủ trì Solo Plan AD | ~8K | ✅ | UI redesign drop dual-phase badges + extractNextTargetHint regex parse | +| 👤 Chủ trì Solo Plan AE | ~6K | ✅ | BE preventive batch 9 sites UserName | +| 👤 Chủ trì Solo Plan AF | ~10K | ✅ | FE userMap fallback từ embedded domain data (Option A historical name resolve) | + +**Total cost cumulative S25:** Agents ~132K + Em main ~210K = ~342K (cache leverage 70-90% per session). ROI ~25% solo equiv. + +## 📋 Patterns reusable saved S25 (8 NEW) + +1. **Multi-Changelog.Add() SQLite frozen-clock tie-break (gotcha #48)** — Test query audit table cần filter EntityType/Summary discriminator thay raw OrderByDescending(CreatedAt). Cross-ref Contract V2 test setup tương lai. + +2. **CICD Monitor catch UAT skip test risk validated** — Cho BE refactor > 100 LOC + signature change, UAT mode `feedback_uat_skip_verify` skip `dotnet test` RISK. Em main resumed local test verify post Plan AB Chunk A2 fix. + +3. **Test fix Option A over BE refactor preservation** — Khi test fail post-refactor do test logic assumption broken, ưu tiên tests get more specific selector (filter discriminator) thay vì revert BE code change. Code semantic stays clean, tests adapt to new schema reality. + +4. **Capture pre-call mutation state cho audit row from-position** — Khi Service mutate entity state AND write audit row simultaneously (vd Plan AC ApplyReturnModeAsync mutate pointer + log Approval), snapshot OLD Step/Level BEFORE mutation. Pattern reusable Contract V2 audit + Budget V2 audit. + +5. **FE merge synthetic rows từ Changelog historical (Plan AC2 Option 2A)** — Reversible recovery KHÔNG DB write. ApprovalsTab fetch changelogs + reconstruct synthetic rows từ existing audit data + dedupe timestamp bucket key vs real rows. Reusable Contract V2 + Budget V2 audit visualization without DB migration. + +6. **Drop misleading dual-phase badges + parse semantic next-target hint (Plan AD, gotcha #49)** — UI audit history KHÔNG nên render dual-phase badge khi state machine self-loop (ChoDuyet → ChoDuyet là advance pointer trong cùng phase). Thay bằng Decision badge + semantic hint parse từ structured comment via regex + keyword detect. + +7. **Changelog UserName preventive systemic batch fix (Plan AE)** — Khi audit phát hiện 1 site bug pattern, grep enumerate ALL similar sites. `Edit replace_all=true` với context-aware key (UserId + Summary) cover N sites idempotent. ICurrentUser.FullName fallback Email mirror LogTransitionAsync pattern. + +8. **FE userMap fallback từ embedded domain data (Plan AF Option A)** — Build composite lookup map từ data có sẵn trong response bundle (drafter + approvals + approvalFlow + levelOpinions + departmentOpinions), KHÔNG cần extra API fetch admin permission. Trust entry.userName non-empty → userMap.get(entry.userId) → 'Hệ thống' fallback. + +## ⏭ Pending S26+ + +- 🔴 **Memory curate cicd-monitor PRIORITY MAX** — ~72KB strongly over 50KB hard threshold. DEDICATED CURATION SESSION REQUIRED — archive Run #186-#210 + S22-S24 verbose entries to `archive/2026-05-runs-186-210.md`. FIFO ~15 entries. +- 🟡 **Memory curate Investigator (32KB) + Implementer (35.7KB) + Reviewer (32.3KB)** cùng session với cicd-monitor. +- 🟡 **Plan B Contract V2 wire (Mig 32+33)** — HIGH priority pre-allocated S23 HANDOFF tree. 5-6 chunk: Investigator pre-flight + Mig 32 schema (Contract +ApprovalWorkflowId) + Service ApproveV2Async branch + Mig 33 ContractLevelOpinions + FE Workspace × 2 app + FE Detail × 2 app. +- 📝 **Memory user-level** `feedback_responsive_laptop_breakpoint` stale "3-panel" → 2-panel correction (S24 Investigator surprise note). +- 🔍 **Discovery #3 anomaly CI trigger docs-only** (4× reinforced) — defer Investigator follow-up. +- 🔍 **Discovery #4 ASP.NET enum body deserialization numeric input requirement** (Plan P S23 side effect) — LOW priority register JsonStringEnumConverter Program.cs. + +## References + +- Files: [PurchaseEvaluationWorkflowService.cs](../../../src/Backend/SolutionErp.Infrastructure/Services/PurchaseEvaluationWorkflowService.cs) · [PurchaseEvaluationFeatures.cs](../../../src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs) · [PurchaseEvaluationDetailFeatures.cs](../../../src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationDetailFeatures.cs) · [PeDetailTabs.tsx fe-user](../../../fe-user/src/components/pe/PeDetailTabs.tsx) · [PeDetailTabs.tsx fe-admin](../../../fe-admin/src/components/pe/PeDetailTabs.tsx) +- Rules: §3.9 mirror 2 FE app · §6.5 KEEP narrative · §7 test timing (UAT defer test-after) +- Cross-ref skill `contract-workflow` (Approve/Reject decision pattern) + `permission-matrix` (approverUserId per-NV slot) +- Memory cross-ref `feedback_per_nv_permission_scope.md` (per-NV wire 10 surface points) + `feedback_service_hook_vs_endpoint.md` (audit log derived state) + NEW `feedback_fe_merge_synthetic_audit.md` + NEW `feedback_fe_usermap_fallback.md` +- CICD Run URLs: https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/{215..221} diff --git a/docs/gotchas.md b/docs/gotchas.md index 617b598..f46538e 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -775,6 +775,67 @@ paths-ignore: - CICD Monitor Run #187 (S21 t5 2026-05-13 20:12) — confirmed pattern + cache stale bonus - Memory `feedback_multi_agent_setup` Plan G Trial Week 1 evidence +### 48. Multi-Changelog.Add() trong cùng SaveChangesAsync → SQLite frozen-clock tie-break → tests `OrderByDescending(CreatedAt).First()` non-deterministic (Session 25 Plan AB + Run #215 catch) + +**Triệu chứng:** Plan AB Chunk A `cdfd542` add SECOND Changelog.Add() entry vào `ApplyReturnModeAsync` (cover Bug 2 — Return mode log) end-of-function. Caller `TransitionAsync:100` đã có sẵn `LogTransitionAsync` add FIRST Changelog entry (Action=Transition + ContextNote=comment chứa "không lùi được"). 2 entries cùng `SaveChangesAsync` transaction → SQLite test fixture frozen clock → CreatedAt **identical microseconds** cho cả 2 rows. + +Plan M edge case tests (S23 t3) query `.OrderByDescending(c => c.CreatedAt).FirstAsync()` assert `ContextNote.Contains("không lùi được")` — sau Plan AB, SQLite tie-break non-deterministic, pick Plan AB row (EntityType=Workflow, Action=Update, ContextNote=null) → `Expected ContextNote not to be ` FAIL. + +**CI Run #215 sha=cdfd542** test_infra FAIL 2/53 (51 PASS, 2 FAIL): +- `ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` (line 350) +- `ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` (line 308) + +Test gate caught regression → deploy never reached → prod spared broken state. + +**Fix Option A (chốt):** Test query filter by `Summary.Contains("Chuyển phase")` để pick đúng LogTransition entry. Plan AB BE code stays clean. + +```csharp +// Trước Plan AB Chunk A2: +var changelog = await db.PurchaseEvaluationChangelogs + .Where(c => c.PurchaseEvaluationId == pe.Id) + .OrderByDescending(c => c.CreatedAt) + .FirstAsync(); + +// Sau Plan AB Chunk A2 fix (commit 8c05947): +var changelog = await db.PurchaseEvaluationChangelogs + .Where(c => c.PurchaseEvaluationId == pe.Id && c.Summary!.Contains("Chuyển phase")) + .OrderByDescending(c => c.CreatedAt) + .FirstAsync(); +``` + +**Pattern reusable:** Khi handler/service add NEW Changelog row trong existing flow đã có LogTransition row, tests query audit table MUST filter EntityType / Action / Summary keyword **discriminator** thay vì raw OrderByDescending timestamp. Cross-ref Contract V2 test setup tương lai. + +**Severity:** Major — caught by CI before prod ship, no user impact. Lesson reinforced UAT mode `feedback_uat_skip_verify` skip `dotnet test` per chunk RISK khi BE refactor > 100 LOC + signature change → em main resumed local test verify post Plan AB Chunk A2. + +**References:** +- Plan AB Chunk A commit `cdfd542` (Run #215 FAIL) +- Plan AB Chunk A2 fix commit `8c05947` (Run #216 PASS) +- Memory `feedback_uat_skip_verify` lesson reinforced S25 +- File: `tests/SolutionErp.Infrastructure.Tests/Services/PurchaseEvaluationWorkflowServiceReturnModeTests.cs:304-310, 346-352` + +### 49. UI dual-phase badge `fromPhase → toPhase` gây nhầm khi 3/4 Reject mode giữ Phase=ChoDuyet (Session 25 Plan AD) + +**Triệu chứng:** Bro UAT 2026-05-19 Plan AC deploy: panel "Lịch sử duyệt" 6 entries TẤT CẢ hiện `Đã gửi duyệt → Đã gửi duyệt` (vì 3 mode Return OneLevel/OneStep/Assignee giữ Phase=ChoDuyet sau Mig 28 — chỉ Drafter mode set TraLai). Reject entry visually IDENTICAL Approve entry → user nhầm "Đã trả lại nhưng vẫn hiện đã duyệt". + +**Fix Plan AD (commit `0aaf2df`):** + +1. **Drop fromPhase → toPhase badges entirely** trong ApprovalsTab (cả `fe-user` + `fe-admin` mirror §3.9). Visual confusion gỡ bỏ. + +2. **Thay bằng next-target hint parse từ comment** via helper `extractNextTargetHint(decision, toPhase, comment)`: + - **Approve:** Summary "sang Cấp X" → "→ Cấp X", "sang Bước Y" → "→ Bước Y (Cấp 1)", "Duyệt vượt cấp" → "→ Vượt cấp tới Cấp cuối", toPhase=DaDuyet(20) → "→ Đã duyệt hoàn tất" + - **Reject:** ContextNote "Người chỉ định" → "→ Trả về Người chỉ định (Bước X Cấp Y)" parse regex, "Người soạn thảo"/"Drafter" → "→ Trả về Người soạn thảo", "không lùi được" → "→ Không lùi được", "Trả về 1 Cấp"/"Trả về Cấp X" → "→ Lùi về Cấp X", toPhase=TuChoi(99) → "→ Từ chối hoàn toàn" + +3. **Decision badge** (Plan AC `a734bf2` đã add): Duyệt emerald / Trả lại amber / Từ chối rose — phân biệt Action level KHÔNG dựa vào phase. + +**Pattern reusable cross-project:** UI audit history KHÔNG nên render dual-phase badge khi state machine self-loop (e.g. ChoDuyet → ChoDuyet là advance pointer trong cùng phase, KHÔNG phải transition). Thay bằng Decision badge + semantic next-target hint parse từ structured comment. + +**Severity:** UX confusion (KHÔNG functional bug). Bro UAT phản hồi sau Plan AC deploy. + +**References:** +- Plan AD commit `0aaf2df` (Run #219 PASS) +- File: `fe-user/src/components/pe/PeDetailTabs.tsx:1995-2070` (ApprovalsTab + decisionBadge + extractNextTargetHint) +- Mirror: `fe-admin/src/components/pe/PeDetailTabs.tsx` + ## Checklist debug bug mới 1. Build pass không? → fail → check using + package version compat @@ -800,3 +861,5 @@ paths-ignore: 21. Nếu user phàn nàn "feature work cho admin nhưng user empty/403 silent" → check class-level Authorize policy có over-restrict cho non-admin không, split per action (#44) 22. Nếu button workflow label nói "Trả lại" nhưng phiếu vẫn tiến approve → audit FE `isReject` payload condition vs button `isSendBack` label condition vs dialog `isSendBack` warning condition — phải sync 3 chỗ với CÙNG set target phase. BE thêm guard `(target ∈ terminalSet) ⇔ (decision=Reject)` chặn caller mismatch (#45) 23. Nếu Gitea Actions API trả 404 trên `/actions/runs` → đúng path là `/actions/tasks` (Gitea naming khác GitHub). Cache `updated_at` stale ~2 min → cross-check VPS file LastWriteTime cho time-sensitive verify (#46) +24. Nếu test `OrderByDescending(CreatedAt).First()` query audit table fail sau add Changelog mới → SQLite frozen-clock tie-break, MUST filter Summary/EntityType discriminator (#48) +25. Nếu UI audit list show `Đã gửi duyệt → Đã gửi duyệt` lặp gây nhầm → drop dual-phase badge khi state machine self-loop, thay Decision badge + next-target hint parse từ comment (#49)