[CLAUDE] Docs: S114 PE multi-NCC per hạng mục closeout (STATUS/HANDOFF/gotcha #81/Mig 65/session-log)
Closeout feature multi-NCC per hạng mục (ship d436af2):
- STATUS/HANDOFF: S114 blurb + CURRENT STATE canonical Mig 64→65 · test 477→486 · gotcha 80→81 · bundle D51OYyGV/BVdssm5S.
- gotcha #81: derived-invariant re-derive sót DELETE writer (DeleteQuote/DeleteDetail) → IsWinner kẹt stale → HĐ giaTri=0 phantom (Bước 4 fable-clone verify bắt; fix helper PeWinnerInvariant test-before).
- ef-core skill: +Mig 65 BackfillPeQuoteIsSelectedAndWinnerInvariant row.
- session log 2026-07-12-S114 + 2 reviewer diary harvest (verify run) + run-trace fold.
- cicd on-behalf record (garble #53 ×5 → em-main-solo deploy verify).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1340,6 +1340,14 @@ for h in resp.points: # ← .points không phải iterable trực tiếp
|
||||
|
||||
**Fix:** `Code = (r.Code ?? string.Empty).Trim()` → null + "" đồng nhất → Code="" + IsPublic=false (nháp, ẩn khỏi filtered-unique `[Code]<>''`). **Quy tắc:** `!` null-forgiving trên dữ-liệu-parse-từ-ngoài (Excel/JSON/form-input) = MÌN — dùng `?? default` chứ KHÔNG `!`. Build-0/0 KHÔNG bảo-đảm null-safety khi có `!` che. Chỉ **test-before-critical** bắt được (test-specialist viết characterization-test reproduce NRE → em-main fix prod → flip test sang NotThrow+draft). Liên-quan #73 (absolute-set null-clobber).
|
||||
|
||||
### 81. Derived-flag invariant re-derive ở write/select NHƯNG SÓT DELETE writer → giá-trị-phái-sinh kẹt stale → phantom (Session 114)
|
||||
|
||||
**Triệu chứng:** S114 đổi winner-truth single→multi per-hạng-mục — `Supplier.IsWinner` thành **DERIVED** (== `Any(Quote.IsSelected)`), maintain ở SelectWinner + UpsertQuote(insert+update) + seed + backfill. NHƯNG **DeleteQuote + DeleteDetail (cascade) xóa quote-selected KHÔNG re-derive IsWinner** → xóa quote-selected CUỐI của 1 NCC để `IsWinner` kẹt=true trong khi `Any(IsSelected)=false`. Hậu quả TÀI CHÍNH: `CreateContractFromEvaluation Where(s.IsWinner)` gom NCC phantom → sinh **HĐ giaTri=0** cho NCC không-winner + winner-names phantom + FE row kẹt (`canDelete=!isWinner`). Build+test PASS, silent (0 test gọi Delete command).
|
||||
|
||||
**Fix:** helper `PeWinnerInvariant.ReSyncAfterQuoteRemovalAsync` (re-derive IsWinner toàn phiếu + re-sync SelectedSupplierId từ DB **đã-flush** — gọi SAU SaveChanges nên không cần loại-trừ quote-đã-xóa) gọi ở CẢ DeleteQuote + DeleteDetail. Test-before: 2 test RED→GREEN + guard-test (xóa hạng mục khác KHÔNG over-clear).
|
||||
|
||||
**Bài học:** derived-invariant phải re-establish ở **MỌI mutation đổi tập-nguồn — kể cả DELETE + cascade-delete**, không chỉ write/select. **Enumeration writer trong spec KHÔNG đáng tin** — grep độc-lập MỌI `.Remove(` chạm entity chứa field-nguồn (S87/S88 cardinality class mở rộng lên tầng mutation-path: writer-miss ≡ read-site-miss). Chỉ **verify adversarial-refute** bắt (invest/review/implement đều sót vì cùng theo enumeration). Liên-quan #72/#73 (cardinality) + `feedback_cardinality_change_grep_consumers`.
|
||||
|
||||
---
|
||||
|
||||
## Checklist debug bug mới
|
||||
|
||||
Reference in New Issue
Block a user