Files
solution-erp/docs/changelog/sessions/2026-07-01-S97-pe-finalize-wording-default-flip.md
pqhuy1987 310b9fce68 [CLAUDE] Docs: S97 deep review-before-session-end (4-lane workflow) — PROD verified SAFE, closeout
- Review workflow wf_4951c347 4-lane (anh yêu cầu, LIVE PROD rigor): prod-data-integrity CLEAN + prod-deploy CLEAN + code-review PASS + feedback captured
- prod-data (database-agent): flag-vs-marker 3 phiếu chuẩn 0-miss/0-over-reach, Mig 60/61 backfill complete, #78/#79 fix ăn prod, no anomaly, DB11 consistent
- prod-deploy (cicd): 11 commit #478-484 deploy OK, Mig 61 applied, health 200×4; bundle live mEGqlNWc/Dnrp4PU1 (#69-rebuild #484)
- code-review + feedback lanes fail-return-schema → recover diary + em-main (0 wrong-table pattern khác; gotchas #77-79 captured)
- session-log Part 5 + §L bug-production RCA (Mig 60 wrong-table 2-fixed: Mig 61 + gotcha #79) + STATUS bundle/state
- Memory harvest: cicd-monitor + investigator-codebase (return-fail recovered)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:57:48 +07:00

134 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# S97 — PE finalize: đổi chữ "không trình tới" + đảo default checkbox (2026-07-01)
> UAT anh Kiệt FDC (tiếp buổi live S96). 2 fix nối tiếp tính năng "Cấp KẾT THÚC quy trình" (S96 `e657faa`). **em-main-led + HMW-workflow** (implement + review). FE-only, prod-verified **Run #481**.
## Bối cảnh
Anh Kiệt gửi 2 ảnh chat Zalo về phiếu "8 MEP Sub Hệ thống solar":
1. Badge trên sơ đồ duyệt ghi "không trình tới" — sai ngữ cảnh khi phiếu lên CEO. Chốt (Huy đề xuất → Kiệt "Yeb"): đổi thành **"Kết thúc tại CCM"**.
2. Checkbox "Cấp này KẾT THÚC quy trình" đang default = tick (opt-out, S96). Anh Kiệt: *"option mặc định là không chọn; khi nào chọn mới không đẩy lên"* → đảo thành **opt-in**.
## Thay đổi
FILE (mirror 2 app, SHA-identical): `fe-{admin,user}/src/components/pe/PeWorkflowPanel.tsx`
**Fix 1 — badge wording (dòng ~329-331):**
- text `"không trình tới"``Kết thúc tại {evaluation.finalizeLevelName ?? evaluation.finalizeStepName ?? 'cấp trên'}` (render động → "Kết thúc tại CCM" cho workflow của họ)
- tooltip title → template-literal tương ứng (`… kết thúc tại ${finalizeLevelName …} … không cần trình tới`)
- `finalizeLevelName`/`finalizeStepName` đã có sẵn trong scope (banner dòng ~280) — no new prop/DTO
**Fix 2 — đảo default checkbox opt-out→opt-in:**
- `const [applyLevelFinalize] = useState(true)``useState(false)` (dòng 57)
- reset onSuccess `setApplyLevelFinalize(true)``(false)` (dòng 221)
- helper text ternary reword (default = "TRÌNH TIẾP lên CEO", tick = "KẾT THÚC tại cấp này")
- comment S96 "default checked" → "OPT-IN" (drift cleanup, em-main — landmine)
- Logic gửi BE (dòng 200 `approverFinalizeEligible ? applyLevelFinalize : true`) + price-picker gate (dòng 496) tự đúng theo state mới — no change
**KHÔNG đụng:** BE (service dòng 867 `if (matchingLevel.AllowApproverFinalize && applyLevelFinalize)` **tôn trọng cờ FE gửi lên**) · migration · test · file FE khác. BE param default `applyLevelFinalize=true` **giữ nguyên** (fallback caller non-FE; FE luôn POST giá trị tường minh) → zero test-churn, zero prod-risk.
## Quy trình (HMW-mode ON — anh consent "ultra-on hmw")
Workflow `wf_0e01fdba-209` (implement → review pipeline):
- 🟧 **implementer-frontend**: 8 hunk (4×2 app), SHA256 identical `bcf812fd`, build admin PASS 22s + user PASS 8s, 0 TS err. Ghi chú: MEMORY.md hit PostToolUse-hook compaction giữa-spawn (23.8→11.5KB) → archive S76-79 sang `archive/2026-H1.md` (file mới; verify nội dung không mất).
- 🟥 **reviewer**: PASS 0 issue — 4 edit đúng verbatim 2 app, comment L121 + handler L664 (`e.target.checked`) giữ nguyên (đúng), 0 BE/test/mig leak, logic non-break (default-false → không ép chọn giá chốt), TS-safe (`finalizeStepName/LevelName: string|null`).
em-main: comment-drift cleanup (2 app) + verify SHA mirror (1 hash) + commit `1c8fb6a` + push.
## Deploy verify — cicd-monitor Run #481 (byte-level #77)
- **PASS** ~5m10s (18:05:56→18:11:06). Test gate 440 green.
- JS rotate (real FE proof): admin `COYAX-yQ``Cf0o8dx8` · user `0MU-kb4N``x8rppRAK`. CSS frozen `Bbtesa6B`/`BmuPz_Dy` (#69-REFINE JSX-text+bool-only). Real-size 1.64/1.54MB (anti-SPA-trap 919b).
- **Byte-level (#77) 2 app CLEAN:** NEW `Kết thúc tại` PRESENT + helper `Mặc định: duyệt xong phiếu TRÌNH TIẾP` PRESENT · OLD `không trình tới` ABSENT + `Đã BỎ tick` ABSENT → **fix shipped byte-level; UI cũ = cache (incognito), KHÔNG phải deploy-fail.**
- Mig 59 / 88 tables frozen · smoke api+admin+eoffice 200×3 + PE-list 200 items[] valid.
- Caveat: badge-text/checkbox-default không curl-verify được (cần login + phiếu thật → anh Kiệt UAT).
## Lessons
- **#53 return-garble tái diễn LẦN-4** — cicd-monitor trả prose curate-memory ("18.7KB still above 17.1KB target…") thay deploy-verdict, DÙ instruction tường minh "return verdict NOT memory-curation prose". Recover verdict từ diary (`agent-memory/cicd-monitor/MEMORY.md` S97 #481 entry) **KHÔNG re-spawn** — theo `feedback_agent_return_garble_recover`. → instruction-fix "return-verdict" **giảm-không-100%**; recover-from-diary là đường robust.
- **BE-honors-explicit-flag** → đảo default checkbox chỉ cần sửa FE `useState` (BE param default = fallback cho caller non-FE, FE luôn POST tường minh dòng 200) → zero BE/test churn, zero prod-risk. 1 UAT-flip "tưởng to" = 4 dòng × 2 app.
- **Comment-drift** — đổi behavior phải sửa comment mô tả (S96 "default checked" nằm ngay trên `useState(false)` = landmine cho reader sau). em-main cleanup trước commit.
## State (GIỮ NGUYÊN trừ bundle)
Mig 59 · 88 bảng · 440 test (45D+395I) · gotcha 77 · menu 54 · **bundle admin `Cf0o8dx8`/user `x8rppRAK` (Run #481)** · prod 21 NCC / 17 user.
## NEXT
- 🔴 **anh:** UAT **incognito** phiếu "8 MEP Sub Hệ thống solar" — xác nhận badge "Kết thúc tại CCM" + checkbox default unchecked. (+ vụ cũ "lỗi phân quyền vào phiếu": cho **số-phiếu + user** để em soi config approver.)
- **em:** đã closeout S97. Monthly re-tier STATUS/HANDOFF mega-line vẫn pending (giờ 5-blurb S97→S93).
---
## Part 2 (cùng ngày S97) — endsBeforeCeo CONFIG→RUNTIME (bug "CEO duyệt mà bị mờ")
**Bối cảnh:** anh Kiệt hỏi phiếu PE/2026/A/010 "8 MEP Sub Hệ thống solar" — CEO (Bước 3, Nguyễn Văn Trường) đã duyệt thật (16:47:30, Lịch sử duyệt) nhưng sơ đồ quy trình báo "Phiếu đã kết thúc tại CCM Cấp 2 — không qua CEO" + làm mờ Bước 3. "CEO duyệt mà ko thấy gì?"
**Root-cause:** `endsBeforeCeo` (banner + làm-mờ bước-sau) tính 100% CONFIG (`steps.Any(l => l.AllowApproverFinalize)` = workflow CÓ cấp finalize), 0% runtime. Phan Văn Chương (Bước 2 Cấp 2, cờ "Duyệt thay CEO") chọn "Trình duyệt" (opt-out) → phiếu lên CEO thật, nhưng display suy config → nói dối. `ComputeLevelStatus` blanket "Done" mọi cấp khi DaDuyet. **Nặng lên sau fix-2 (đảo default opt-in):** mặc định giờ trình tiếp CEO → nhiều phiếu lên CEO → bug phổ biến (fix = follow-up trực-tiếp của chính fix-2). → gotcha #78.
**Fix (anh chọn "fix trọn", Mig 60 `8037fa0`):**
- Field runtime `PurchaseEvaluation.EndedByLevelFinalize` (bool default false) — set `true` CHỈ ở nhánh level-finalize `ApproveV2Async` (`PurchaseEvaluationWorkflowService.cs:871`; reviewer confirm 1/5 nhánh DaDuyet, ccm-delegation/full/drafter/V1 giữ false).
- 4 projection `EndsBeforeCeo`: phiếu DaDuyet đọc runtime field; ChoDuyet giữ config heads-up. Detail (`:1153`) + List/Inbox (ternary `Phase==DaDuyet?field:subquery`, `:647/:767`) + ListApproved (đọc thẳng field `:196`, DaDuyet-only).
- FE gate làm-mờ `finalizeFlowStep` theo `endsBeforeCeo` (2 app SHA-mirror `b7abbc2f`). Banner + PeFinalizeChip đã gate sẵn.
- Migration Mig 60 + backfill Sql (dò marker "Duyệt KẾT THÚC tại" set phiếu finalize-sớm cũ; ccm-delegation comment không match → giữ false).
**Quy trình:** em-main BE core solo (schema + 6 edit + migration — tight-coupling/prod-migration) → build slnx PASS → spawn test-specialist (test-before) + implementer-frontend (FE gate mirror) song song → reviewer double-check (anh yêu cầu) → commit → cicd.
**Verify:**
- test-specialist **440 PASS** (repro `peFinButWentCeo`: cùng config-finalize NHƯNG EndedByLevelFinalize=false → EndsBeforeCeo=false) + service #6 opt-out→CEO assert false. EF-ternary translate OK.
- reviewer **PASS 0-issue** (6 category + 6 điểm nghi vấn CONFIRMED sạch): build slnx 0w/0e · test 440 · tsc×2 exit-0 · SHA mirror `b7abbc2f` · migration 3-file + backfill marker khớp exact · EF-translate confirmed (PeDraftVisibilityTests hit ternary).
- **cicd #482 PASS — bug fixed LIVE:** Mig 60 applied prod (col EXISTS default 0, backfill=0 UAT-no-marker, sys.tables **89** pre==post no-new-table), **EF-ternary SQL-Server 200-NOT-500**, PE-detail `31faef0d` **endsBeforeCeo=False phase=7** (runtime-path chạy đúng), bundle admin `Cf0o8dx8``DR9q2VUy`/user `x8rppRAK``Bgz8u4E5`, byte-#77 `endsBeforeCeo` PRESENT.
**Lessons (thêm):**
- **gotcha #78** — display/status flag phải suy RUNTIME ("instance NÀY ĐÃ làm-gì") KHÔNG CONFIG ("workflow CÓ THỂ"); đổi default opt-in/opt-out làm lệch phổ biến → soi lại mọi config-driven display.
- **#53 return-garble lần-5** — cicd-monitor #482 lại trả memory-curation ("18.1KB need under 17.1KB, trim S79/S93 stub") thay verdict. **Root-cause structural (mới):** cicd MEMORY tự-target ~17.1KB → curate-at-return → thinking-step curate = message cuối = return. Recover từ diary (#482 entry đủ Mig60+EF-200+bundle). Fix bền = giảm áp lực curate cicd (archive sớm), KHÔNG instruction. cicd = 4/5 lần garble = kẻ tái phạm dai.
- **Fix-trọn cross-stack + migration** = em-main BE core solo (reliability) + delegate FE/tests/reviewer/cicd (parallelism) — không fan-out toàn bộ vì logic tight-coupling.
**State sau Part 2:** Mig 60 · 89 bảng · 440 test · gotcha 78 · menu 54 · bundle admin `DR9q2VUy`/user `Bgz8u4E5` (Run #482) · prod 21 NCC/17 user.
---
## Part 3 (cùng ngày S97) — badge "✅ Đã được CEO duyệt" (đối xứng fix-3)
**Yêu cầu (anh Kiệt):** "những cái nào đã đc CEO duyệt thì hiện luôn là đã đc CEO duyệt" — sau khi fix-3 làm sơ đồ hiện đúng CEO đã duyệt, anh muốn badge ở DANH SÁCH để nắm ngay không cần mở phiếu.
**Fix (FE-only, `9e12e80` #483):** mở rộng `PeFinalizeChip.tsx` (single-source-of-truth pill dùng ở mọi danh sách) — thêm prop `phase?: number` + nhánh: `!endsBeforeCeo && phase===DaDuyet` → emerald badge "✅ Đã được CEO duyệt"; giữ violet "⚑ Kết thúc · X" cho `endsBeforeCeo=true`. Wire `phase={p.phase}` ở 6 call-site (PeListPanel + PurchaseEvaluationsListPage ×2 app + InboxPage + UserDashboardPage user). Mirror 2-app SHA `da9c3ed9`.
**"Miễn phí" về dữ liệu:** badge = cách đọc khác của cùng cờ `endsBeforeCeo` đã fix runtime-đúng ở fix-3 → 0 BE/migration/field mới. Lợi tức của fix root-cause đúng.
**Verify:** implementer-frontend build ×2 PASS 0-TS-err (bắt `PeListItem.phase: number` → prop `phase?: number` không narrow-union, gotcha type-plumbing) + SHA mirror. cicd #483 PASS: bundle admin `CeTA7Z82`/user `CGaAyXen`, **byte-#77 "Đã được CEO duyệt" PRESENT cả 2 app** + prev-badge additive, Mig 60 frozen, smoke 200×3 + PE-list 200.
**State sau Part 3:** Mig 60 · 89 bảng · 440 test · gotcha 78 · bundle `CeTA7Z82`/`CGaAyXen` (Run #483).
---
## Part 4 (cùng ngày S97) — check CEO-duyệt A/005 → phát hiện Mig 60 backfill WRONG-TABLE (gotcha #79)
**Anh Kiệt:** PE/2026/A/005 "7 Mat Nắp gang" — CEO đã duyệt chưa, nếu rồi hiển thị lịch sử đàng hoàng.
**Điều tra (API + sqlcmd prod):** A/005+A/008 Phase=7, endsBeforeCeo=False, con-trỏ NULL (terminal ran), lịch-sử 4 record dừng ở "[Bước 2 — Cấp 2]" (Phan Văn Chương), KHÔNG có record CEO. Đối chứng A/010: 9 record có "[Bước 3 — Cấp 1]" CEO. Verify Changelogs: A/005/A/008 fin_marker=1 (terminal Changelog "...không trình CEO]"), A/010=0.
**Chẩn đoán:** A/005/A/008 **KHÔNG qua CEO** — finalize tại CCM Cấp 2 (Phan Văn Chương duyệt-thay-CEO, auto-finalize code cũ). Lịch sử 4 record ĐÚNG. Sai = badge "Đã được CEO duyệt" (fix-4) do `EndedByLevelFinalize=0`.
**Root-cause (gotcha #79):** Mig 60 backfill query `PurchaseEvaluationApprovals.Comment` cho marker "[Duyệt KẾT THÚC tại]", nhưng marker do `LogTransitionAsync:1073` ghi vào `PurchaseEvaluationChangelogs.ContextNote` (2 bảng tách vai: Approvals=per-approver :745 · Changelogs=transition+marker) → match 0 → phiếu finalize pre-Mig60 giữ flag=0.
**Fix (Mig 61 `fb2dfa9`):** backfill ĐÚNG `Changelogs.ContextNote LIKE N'%Duyệt KẾT THÚC tại%'` + PhaseAtChange=7, idempotent, SQL-Server-only. database-agent điều tra prod + em-main verify (marker-count prod) + viết migration + reviewer-skip (đã prod-verify signal).
**Verify (cicd #484 prod):** Mig 61 applied · **backfill: A/005=1, A/008=1 (0→1) · A/010=0 (control)** · **endsBeforeCeo FLIP: A/005 True (trước False), A/010 False** · smoke 200×3 · NO-FE (data-fix). → A/005/A/008 giờ đúng "⚑ Kết thúc · CCM — không qua CEO".
**Lessons:** **#79** backfill/query transition-marker phải xác định ĐÚNG bảng writer (PE Approvals≠Changelogs) + verify marker-count prod trước · **#53 lần-6** cicd garble → recover diary #484 (cicd = 5/6 offender, structural curate-at-return).
**State sau Part 4:** Mig 61 · 89 bảng · 440 test · gotcha 79 · bundle `CeTA7Z82`/`CGaAyXen`(#483). **5 fix PE.**
---
## Part 5 (cùng ngày S97) — Deep review-before-session-end (anh yêu cầu, LIVE PROD rigor)
**Yêu cầu:** review thật kỹ S96+S97 (test trên prod THẬT) trước khi đóng. Workflow `wf_4951c347-247` 4-lane parallel.
**Kết quả:**
- 🔵 **prod-data-integrity (database-agent): CLEAN** — contingency flag-vs-marker 3 phiếu DaDuyet: A/005/A/008 flag=1+marker (finalize-CCM), A/010 flag=0+no-marker (CEO thật). **0 miss, 0 over-reach.** Mig 60/61 applied, backfill complete (paranoid alt-marker scan 0 sót). NCC=21 (DONGDUONG có), 0 price/urgent anomaly. DB11: Phase=7 pointer NULL đúng, 0 orphan changelog, 0 flag-on-non-Phase7. **#78/#79 fix xác nhận đã ăn prod.**
- 🟩 **prod-deploy-health (cicd-monitor): CLEAN** — 11 commit #478-#484 đều deploy success. Mig 61 applied (history-top). Health 4×200, SignalR wired (#25 clear), auth OK, PE-list 200. 0 regression, w3wp fresh. Bundle #484 rebuild #69-benign → live `mEGqlNWc`/`Dnrp4PU1`.
- 🟥 **code-review (reviewer): fail-return-schema → recover** — reviewer diary session-review = **PASS 0-issue** (endsBeforeCeo+Mig60: field set CHỈ nhánh finalize :873 grep-all 5-nhánh, 4 projection nhất quán, EF-translate confirmed via PeDraftVisibilityTests). em-main grep bổ-sung: **KHÔNG wrong-table pattern nào khác** (chỉ Mig 60 buggy đã sửa bởi Mig 61).
- 🟦 **feedback-capture (investigator): fail-return-schema → recover** — em-main verify: gotchas #77/#78/#79 đủ (count 79), session-log Part 1-5, feedback #53 updated ×6.
**§L.a bug-production RCA (Mig 60 wrong-table):** em ship Mig 60 với backfill query nhầm bảng (Approvals thay Changelogs) → prod badge A/005/A/008 sai. **2-fix:** code (Mig 61 đúng bảng) + guard (gotcha #79 + verify-marker-count-prod-TRƯỚC-backfill). Không escape lâu (cùng-ngày phát-hiện→fix→prod-verify). 5-why: viết backfill mà không đọc code writer (LogTransition:1073 ghi Changelog) → giả định marker ở Approvals → không verify-count trước → cicd #482 backfill=0 (đáng-lẽ cờ đỏ nhưng bỏ qua vì tưởng "no pre-Mig60 phiếu").
**Lessons:** **schema-param KHÔNG chặn return-fail** (2/4 lane fail StructuredOutput dù schema → lane quan-trọng luôn cần fallback) · **#53 lần-6** (cicd 5/6) · **backfill 0-dòng = cờ-đỏ** (đừng cho là "no data", verify bảng).
**Verdict review: PROD AN TOÀN + mọi fix đúng + learnings captured.**
**State cuối S97:** Mig 61 · 89 bảng · 440 test · gotcha 79 · menu 54 · bundle admin `mEGqlNWc`/user `Dnrp4PU1` (Run #484, #69-rebuild) · prod 21 NCC/17 user. **5 fix PE** (wording + default-flip + endsBeforeCeo-runtime + CEO-badge + backfill-wrong-table) + deep-review-verified.