Files
solution-erp/docs/changelog/sessions/2026-07-12-S114-multi-ncc-per-hangmuc.md
pqhuy1987 16a199f031 [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>
2026-07-12 23:45:08 +07:00

33 lines
5.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.

# S114 — PE multi-NCC per hạng mục (2026-07-12)
**Feature ship prod:** cho chọn **≥2 NCC-TP trúng thầu vào 1 hạng mục** (PurchaseEvaluationDetail), mỗi nhà 1 giá riêng. Anh Kiệt (FDC). Commit `d436af2`. em-main-led, 6-bước theo plan anh, HMW-ON, lead=Opus 4.8 owner-choice.
## Flow 6 bước (fable-clone → hmw → verify)
1. **B1 `/fable-clone invest`** — 4-lane investigator-codebase ensemble (`wf_dd49ddc0`, schema/be-consumer/fe-consumer/product-history). Phát hiện **3 trục winner**: A `SelectedSupplierId` (single legacy) · B `Supplier.IsWinner` (Mig58 liên-danh cả-gói) · **C `Quote.IsSelected`** (per Detail×Supplier, hook Mig12 DORMANT — 0 consumer tài-chính). Feature = trục C, KHÁC Mig58.
2. **B2a anh Kiệt 4-decision** (AskUserQuestion, all recommended): **D1**=winnerQuoteTotal SUM(IsSelected) · **D2**=THAY-THẾ (IsSelected nguồn-sự-thật, IsWinner DERIVED) · **D3**=giá-chào-gốc Quote.ThanhTien · **D4**=1HĐ/NCC. → **0 bảng/cột mới**.
3. **B2b `/fable-clone review`** — 3-lane reviewer ensemble (`wf_1e127f4b`, consumer-completeness/correctness/checklist-risk). 6 adjustment A1-A6: A1 giaTri bỏ isSingle→detailsSum · A2 derive-invariant MỌI writer + backfill 2-chiều · A3 re-key SPLIT (5 financial→IsSelected, winner-names/existence GIỮ IsWinner-derived) · A4 endpoint per-detail + FE per-cell grid · A5 test-before + threshold-crossing · A6 mig 3-file Down-noop. *(lane correctness-edgecase #53-garble → em-main recover, territory phủ bởi 2 lane hội-tụ.)*
4. **B3 hmw implement** — staged BE→(FE+test) (`wf_e7ee6abe` stage-1 + `wf_410896c4` stage-2). BE: command `{detailId, supplierIds}` set Quote.IsSelected + derive IsWinner (SelectWinner + UpsertQuote + seed); re-key 5 financial-SUM; giaTri bỏ detailsSum; Mig 65 backfill 2-chiều. FE: HangMucCard per-cell winner grid + toggle per-detail; NccSelectorRow hạ-cấp summary; mirror 2-app SHA `ae3788e9`. Test: 7 file update + tests mới.
5. **B4 `/fable-clone verify`** — 2-lane reviewer refute (`wf_14850736`, CLEAN 0-garble). **HỘI TỤ 1 MUST bug** invest/review/implement đều sót (gotcha #81, dưới). em-main fix test-before.
6. **B5 deploy** commit `d436af2` push → CI. **B6 session-end** (file này).
## 🎯 MUST bug Bước 4 bắt (gotcha #81)
`DeleteQuote` + `DeleteDetail` (cascade) xóa quote-selected KHÔNG re-derive `Supplier.IsWinner` → xóa quote-selected cuối của NCC để `IsWinner` kẹt stale (Any(IsSelected)=false) → `CreateContract Where(IsWinner)` gom NCC **phantom** → sinh **HĐ giaTri=0** + winner-names sai + FE row kẹt. Reachable FE nút "Xóa" quote. **0 test cover.** Fix: helper `PeWinnerInvariant.ReSyncAfterQuoteRemovalAsync` (re-derive từ DB đã-flush) gọi ở 2 delete handler. Test-before: 2 test RED→GREEN + guard (không over-clear) + UpsertQuote-update coverage. **Bài học meta:** enumeration writer trong spec KHÔNG đáng tin — verify adversarial-refute grep-MỌI-`.Remove(` bắt (S87/S88 cardinality lên tầng mutation-path).
## ⚠️ #53 return-garble ×5 phiên này (0 mất việc)
review-lane-2 + BE-lane + FE-lane + test-lane + cicd — tất cả garble RETURN (sub xong việc, không gọi StructuredOutput). **Recovery khác theo loại lane:**
- **READ-only lane** (review/verify): garble = mất verdict → recover bằng **redundancy** (lane sạch phủ territory).
- **WRITE lane** (BE/FE/test): garble = mất return NHƯNG **code trên disk** → recover bằng **disk-truth + build/test verify** (`feedback_agent_kill_recovery`).
- **cicd**: garble (trả memory-curation) → **em-main-solo verify** (curl prod bundle rotate + byte-verify + smoke).
**Lesson kỹ-thuật Workflow-tool:** stage-1 `await agent()` TRỰC-TIẾP → 1 garble throw giết cả workflow (FE+test stage-2 không chạy). `parallel()` = garble→null, workflow SỐNG → verify từ disk. **Với WRITE-lane, return chỉ "nice-to-have", disk mới ground-truth.** FE-lane garble giữa mirror → chỉ fe-admin xong, em-main mirror fe-user (SHA verify).
## State
**Mig 65 · 89 bảng · 486 test (45D+441I, +9) · gotcha 81 · menu 54 · bundle admin `D51OYyGV`/user `BVdssm5S` · prod 37 NCC/17 user.**
## Deploy verify (em-main-solo, cicd garble)
bundle admin `acprMqfB``D51OYyGV` / user `Ck3BTDdA``BVdssm5S` rotate ×2 · byte-verify `detailId`+`select-winner`+`hạng mục` PRESENT (bundle 1.65MB real) · smoke api/admin/eoffice 3/3 200 · endpoint POST select-winner bogus 401-live · mig applied (API-healthy = migrations-ran-startup).
## Carry / follow-up
- **UAT anh Kiệt (incognito):** giaTri single-winner budget→bid · UX per-cell grid · nit winner-no-quote no-op.
- **NEXT em:** follow-up nit FE winner-toggle gate quote-tồn-tại (non-blocking) · inv-cb L1 98.6% cap WATCH · watch AI_INFRA 2 email · monthly re-tier 2026-08-01.
- **Run-trace:** `.claude/workflows/runs/2026-07-12-S114-multi-ncc-per-hangmuc/` (run.md + spec-multi-ncc-per-hangmuc-12-07-2026.md).