[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>
This commit is contained in:
pqhuy1987
2026-07-14 11:39:08 +07:00
parent b128f30a35
commit fc1b8d9642
4 changed files with 48 additions and 3 deletions

View File

@ -85,8 +85,9 @@ when-to-use:
| **63** | **`AddSupplierImportSourceFields`** | **🎯 S112 Supplier +`SourceUpdatedAt datetime2?` + `SourceUpdatedBy nvarchar(200)?` (import-provenance cột 29/30 file Excel "Database NCC" Phase B Excel-import Phase B). 2 AddColumn nullable + 2 DropColumn Down (reversible). **No new table (89 giữ)**. Prod-verified Run #496 PASS `__EFMigrationsHistory` top + 2/2-col present; commit `e100ef0`.** |
| **64** | **`AddSupplierPublishState`** | **🎯 S113 Supplier +`IsPublic` bit NOT NULL default 0 (cổng công-bố "thiếu NCC nháp/ẩn" R4): AddColumn + `Sql("UPDATE Suppliers SET IsPublic=1")` backfill 37/37 prodpublic + DropIndexCreateIndex đổi Code filtered-unique `WHERE [IsDeleted]=0 AND [Code]<>''` (cho phép nhiều nháp Code=''). **No new table (89 giữ)**. Reversible Down (⚠ edge: rollback sau khi 2 nháp Code='' sẽ kẹt CreateIndex filter ). Prod-verified Run #497; commit `5fa11b5`. Kèm gotcha #80 (null-Code NRE NewSupplier).** |
| **65** | **`BackfillPeQuoteIsSelectedAndWinnerInvariant`** | **🎯 S114 DATA-ONLY no-schema (multi-NCC per hạng mục anh Kiệt): backfill **2-CHIỀU** thiết lập bất-biến `Supplier.IsWinner == Any(Quote.IsSelected)`. CHIỀU-1 `IsWinner→IsSelected` (phiếu whole-supplier winner Mig58 set IsSelected mọi quote NCC, **preserve tổng** winnerQuoteTotal) + CHIỀU-2 `IsSelected→IsWinner` (dormant/seed EXISTS). Idempotent (`WHERE ...=0`), SQL-Server-only (SQLite EnsureCreated bỏ qua migration), Down **no-op** (data backfill không reversible, mirror Mig 61). **No new table (89 giữ)** snapshot UNCHANGED (0 CreateColumn). Prod-verified bundle rotate ×2 `D51OYyGV`/`BVdssm5S`; commit `d436af2`. Kèm **gotcha #81** (delete-writer sót re-derive IsWinner phantom giaTri=0 verify-bắt).** |
| **66** | **`AddPeApprovedPriceRange`** | **🎯 S117 PE +`ApprovedPriceMaxAmount decimal(18,2)?` (feature "duyệt theo khoảng MinMax" anh Kiệt FDC "chọn cả 2 min max"): source `"ProMinMax"` = Amount(Min)+MaxAmount(Max), bất-biến `MaxAmount != null ⟺ Source=="ProMinMax"` + MinMax (guard service `ApplyApprovedPriceOnFinalize` + FluentValidation). 1 AddColumn nullable, **no backfill** (phiếu Source-đơn Max=null nguyên vẹn), **no new table (89 giữ)**, reversible Down (DropColumn). .GiaTri KHÔNG đọc giá chốt (=SUM báo giá NCC được chọn) 0 tác động (range = record-of-decision hiển thị). Prod-verified Run #387 (`ApprovedPriceMaxAmount` col decimal(18,2) + `__EFMigrationsHistory` top); commit `316a82f`.** |
Total: **89 bảng** `sys.tables` (= 88 dbo-app + `__EFMigrationsHistory`; re-ground S97 cicd Run #482 LIVE số canonical `docs/STATUS.md`; last schema-changing Mig 50 net 9388-app, Mig 51-64 đều AddColumn/index/data-only/schema-expand không đổi số bảng). Xem `docs/database/schema-diagram.md` migration table + §11-15 module ERD 16+ Mig 27-64 chi tiết pending).
Total: **89 bảng** `sys.tables` (= 88 dbo-app + `__EFMigrationsHistory`; re-ground S97 cicd Run #482 LIVE số canonical `docs/STATUS.md`; last schema-changing Mig 50 net 9388-app, Mig 51-66 đều AddColumn/index/data-only/schema-expand không đổi số bảng). Xem `docs/database/schema-diagram.md` migration table + §11-15 module ERD 16+ Mig 27-64 chi tiết pending).
## N-stage workflow pattern (Mig 18-20 — Session 12-13)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,44 @@
# 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.