Files
solution-erp/docs/changelog/sessions/2026-07-14-S117-pe-luyke-display-minmax-range.md
pqhuy1987 fc1b8d9642 [CLAUDE] Docs: S117 closeout — PE lũy-kế display-fix + duyệt theo khoảng Min–Max
STATUS + HANDOFF mega-line: prepend S117 (Mig 66 · 89 bảng · 501 test · bundle
cFI7ih4a/MxgooVZw · prod 37 NCC/17 user), demote S116 → Prev. ef-core-migration
SKILL +row Mig 66 AddPeApprovedPriceRange + range 51-66. Session log
2026-07-14-S117-pe-luyke-display-minmax-range.md (2 feature + /fable-real process
+ #53-garble ×2 recover + lessons).

Docs-only → CI-skip (path-filter #41). Feature code đã ship `316a82f` Run #387.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:39:08 +07:00

45 lines
7.0 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.

# S117 — PE lũy-kế display-fix + duyệt theo khoảng MinMax (2026-07-14)
**Commit:** `316a82f` · **CI Run #387** PASS 5m36s · **State:** Mig 66 · 89 bảng · 501 test · gotcha 81 · menu 54 · bundle admin `cFI7ih4a`/user `MxgooVZw` · prod 37 NCC/17 user.
**Mode:** HMW-ON · lead Opus 4.8 · em-main-led · 2× `/fable-real investigator-codebase` engine-đắt (Fable) + 4 spawn (test-specialist · implementer-frontend · cicd-monitor) · 1 commit.
Buổi UAT anh Kiệt FDC (Zalo, ảnh) đưa 2 việc PE — anh chỉ-định `/fable-real` điều-tra rồi fix.
## (1) Fix hiển thị lũy-kế "duyệt rồi không bắt" — root cause = DISPLAY-LIE, KHÔNG phải lũy-kế bug
**Triệu chứng (anh Kiệt/Kenny/Bích Phượng):** phiếu A/018 "8 Mat Gạch xây…" (MAT-8) anh Chương duyệt rồi nhưng phiếu mới A/028 cùng hạng mục hiện Row "2. Ký trước đã chọn thầu = Chưa chọn" → "duyệt rồi mà không bắt lũy kế", xóa/tạo lại vô ích. Nguyên tắc Kenny: "duyệt rồi mới bắc lũy kế".
**Điều tra (`/fable-real invest`, wf `ab2657ee`):** ⚠️ #53-garble lần 1 → **SendMessage-resume** recover (verdict đầy đủ lần 2) + em-main verify prod-DB độc-lập. Kết quả **lật tiền-đề**: A/018 **CHƯA DaDuyet** — prod `Phase=ChoDuyet, EndedByLevelFinalize=0, pointer Step2/Lvl1 = đang chờ CEO (Bước 3)`. Anh Chương duyệt cấp finalize (AllowApproverFinalize=1) NHƯNG **không tick "Cấp này KẾT THÚC quy trình"** (S97 đảo default = bỏ-tick/opt-in) → phiếu **advance lên CEO** thay vì finalize. `CeoApprovalThreshold=NULL` → nhánh ccm-delegation không dùng được. Lũy-kế filter Row 2 (`PurchaseEvaluationFeatures.cs:906` `Phase==DaDuyet && Any(IsWinner)`) loại A/018 **ĐÚNG** (chưa duyệt xong). IsWinner A/018 = có (winner Thảo Anh). Tính LIVE → xóa/tạo lại A/028 vô ích (lỗi ở phiếu-nguồn).
**Bug thật = display-lie (gotcha #78 nửa-còn-lại):** `endsBeforeCeo` cho phiếu non-DaDuyet trả config `true` mù (`PurchaseEvaluationFeatures.cs:1152` literal `: true`; list/inbox `.Any()`) → sơ đồ mờ Bước 3 "Kết thúc tại Cấp 2" NGAY KHI phiếu đang nằm inbox CEO → user tưởng đã xong. S97 mới sửa cho phiếu ĐÃ DaDuyet (runtime `EndedByLevelFinalize`), bỏ sót nhánh ChoDuyet.
**Owner AskUser → "Sửa hiển thị + giữ opt-in".** Fix **POINTER-AWARE 3 site**: non-DaDuyet chỉ `endsBeforeCeo=true` khi CÒN cấp finalize ở-hoặc-SAU con-trỏ hiện tại (phiếu còn có thể finalize). Đã đi QUA → false. Detail `:1152` (in-memory, robust index `steps.IndexOf` vs currentIdx) + list `:647` + inbox `:767` (EF-subquery, rank cấp = `Step.Order-1` — Order tuần-tự s+1 seed/Designer). **BE-only** (FE là consumer thuần: `PeFinalizeChip` + `PeWorkflowPanel:124` gate cờ). +1 test repro `PeFinalizeProjectionTests.ListPe_EndsBeforeCeo_ChoDuyet_PointerAware_PastFinalizeIsFalse` (RED trước → GREEN sau). A/018 lên lũy-kế = việc vận-hành (CEO duyệt / re-approve tick).
## (2) Feature "duyệt theo khoảng MinMax"
**Yêu cầu (anh Kiệt):** ô "CHỌN GIÁ CHỐT" khi duyệt cuối chọn 1 radio (Ncc/ProMin/ProMax/Ccm). Khi PRO có cả Min & Max → cho **"chọn cả 2 min và max"** = duyệt theo khoảng [Min,Max].
**Điều tra (`/fable-real invest`, wf `abbbb86d`, 0-garble clean verdict) → Model A:** +cột `ApprovedPriceMaxAmount` + source token `"ProMinMax"` radio thứ 5 (chỉ khi cả 2 giá PRO ≠ null). Amount=Min, MaxAmount=Max. Bất-biến `MaxAmount != null ⟺ Source=="ProMinMax"` + Min≤Max. **Linchpin verify độc-lập:** `CreateContractFromEvaluationFeatures.cs:88` GiaTri = SUM báo giá NCC được chọn, `grep ApprovedPrice` = 0-match → **HĐ KHÔNG đọc giá chốt** → range thuần record-of-decision, **0 tác động HĐ**.
**Implement (anh "làm theo hướng dẫn nó luôn"):**
- BE **em-main-solo** (financial + signature ripple): entity + EF config + Mig 66 (AddColumn nullable no-backfill, reversible) + service (`ValidApprovedPriceSources` +ProMinMax · `ApplyApprovedPriceOnFinalize` +param maxAmount + validate · 3 call-site :858/:896/:928 + 2 method sig) + command/validator/handler + controller body + DTO + projection + interface.
- FE **implementer-frontend** 2-app SHA-mirror `011ee8cf`/`617b0aa0` (Panel radio thứ 5 + submit `approvedPriceMaxAmount` chỉ khi ProMinMax → axios omit key với source đơn = khớp BE reject · DetailTabs "min max") + types ×2 (không mirror).
- Test **test-specialist** +5 (happy/min==max-boundary/thiếu-Max-Conflict/min>max-Conflict/single+Max-wire-bug + regression). Harness fix: `InvokeApply` reflection 4→5 args (optional param không auto-bind `MethodInfo.Invoke` → tránh `TargetParameterCountException` phá 3 test cũ).
- 3 product-decision investigator flag → resolve theo khuyến nghị (HĐ giữ nguyên · range dùng chung 3 finalize-path · 1 radio khớp "chọn cả 2").
## Verify + deploy
- BE build 0-err · full suite **495→501** (Domain 45 + Infra 456) · Mig 66 applied Dev (+ caught up Mig 65 unapplied-local drift) · FE 2-app build sạch · SHA-mirror khớp · submit logic review đúng contract · diff 18 file (pre-existing "M" = CRLF, verify all-mine).
- Commit `316a82f` push main → **cicd Run #387 PASS**: test gate 501 · Mig 66 prod (`ApprovedPriceMaxAmount` decimal(18,2) + history top + 89 bảng) · bundle rotate ×2 (CSS frozen=logic-only) · byte-level `ProMinMax`/`duyệt theo khoảng`/`cả Min và Max`/`MinMax` PRESENT · smoke 200×3 · PE 401.
- ⚠️ **#53-garble ×2** (invest-lane-1 + cicd-monitor) → SendMessage-resume + **em-main curl độc-lập cross-verify** (`cFI7ih4a`/`MxgooVZw` khớp verdict agent) = verdict tin-cậy dù agent-return hỏng.
## Lessons
- **premise-flip → surface-before-code:** báo-lỗi "lũy kế không bắt" hóa ra là ĐÚNG-behavior (phiếu chưa duyệt xong); điều-tra lật tiền-đề, hỏi owner TRƯỚC khi code (tránh sửa nhầm filter/đảo S97). Real bug = display-lie.
- **HĐ-independent-verify-before-model:** grep ApprovedPrice=0 ở HĐ-gen = bằng chứng range an-toàn → chọn Model A (chỉ record-of-decision) không lo tác động tài-chính.
- **#53-garble WRITE-lane ≠ mất-code** (code trên disk) + **cicd cross-verify em-main-curl** (2 nguồn độc-lập khớp) = pattern verify đáng-tin.
- **signature-ripple financial = em-main-solo** (3 call-site + 2 method), delegate mechanical FE-mirror + test.
- reflection-test `MethodInfo.Invoke` không auto-bind optional param → đổi arity helper phải cập-nhật (test-specialist bắt).
## NEXT
**Anh (UAT prod INCOGNITO — #77):** (1) phiếu chờ CEO → sơ đồ hết mờ "Kết thúc tại Cấp 2"; (2) đưa A/018 lên Đã duyệt → A/028 hiện lũy kế 749.564.000đ; (3) phiếu PRO MinMax → radio "PRO — cả Min và Max" → chi tiết "min max"; (4) carry BVAAU.
**Em:** commit closeout · watch AI_INFRA 3 email · inv-cb L1 curate · monthly re-tier 2026-08-01.