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>
7.0 KiB
S117 — PE lũy-kế display-fix + duyệt theo khoảng Min–Max (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 Min–Max"
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 + submitapprovedPriceMaxAmountchỉ 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:
InvokeApplyreflection 4→5 args (optional param không auto-bindMethodInfo.Invoke→ tránhTargetParameterCountExceptionphá 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
316a82fpush main → cicd Run #387 PASS: test gate 501 · Mig 66 prod (ApprovedPriceMaxAmountdecimal(18,2) + history top + 89 bảng) · bundle rotate ×2 (CSS frozen=logic-only) · byte-levelProMinMax/duyệt theo khoảng/cả Min và Max/Min–MaxPRESENT · smoke 200×3 · PE 401. - ⚠️ #53-garble ×2 (invest-lane-1 + cicd-monitor) → SendMessage-resume + em-main curl độc-lập cross-verify (
cFI7ih4a/MxgooVZwkhớ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.Invokekhô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 Min–Max → 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.