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

7.0 KiB
Raw Blame History

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.