# S74 (2026-06-18) — PE ô "Ghi chú từ CCM" ngân sách gói thầu (Mig 55)

> **Nguồn:** anh forward 2 luồng chat Zalo (chị Trà Sol + anh Kiệt FDC) về panel "TỔNG HỢP NGÂN SÁCH TRÌNH KÝ" → "Chỗ CCM cũng giống Pro cũng cho tất cả nhập mới lần đầu và ghi chú lại nhé, hiện đang hiển thị 0 hết." Tiếp nối S73 (Mig 54 giá đề xuất) — cùng module PE, go-live thứ Hai 22/06.

## Bối cảnh — yêu cầu gộp từ 3 nguồn
- **Chị Trà Sol:** lần đầu trình phải cho nhập ngân sách full; chỉ khóa khi trình lần 2+ đúng hạng mục đã có; lần 2 có hiệu chỉnh thì vẫn mở ô NS hiệu chỉnh. "Khóa cả 2 ô ngay lần đầu thì khi nào mới nhập được."
- **Anh Kiệt FDC:** PRO tự nhập ngân sách + ghi lý do (nguồn gốc số) vào ghi chú; khi qua CCM thì CCM nhập số của CCM vào, **tương tự PRO**.
- **Anh (chốt):** CCM giống PRO — cho nhập mới lần đầu + **ghi chú lại**; hiện đang hiển thị 0 hết.

## Chẩn đoán (em-main scout, read-only)
- Panel = `PeBudgetSummaryTable` trong `PeDetailTabs.tsx` (cả 2 app). Ô nhập PRO/CCM gate bằng **capability flag** `bs.canEditPro` / `bs.canEditCcm` (KHÔNG phải phase — phase chỉ gate row3/row8 "B. Thực hiện").
- BE: `canEditPro = isAdmin || Procurement` · `canEditCcm = isAdmin || CostControl` (thuần role, KHÔNG chặn phase). 2 handler `UpdatePeBudgetPro/Ccm` cũng role-gate fail-closed, KHÔNG ràng phase (bảng ngân sách = "tài liệu sống" per cặp Dự án × Hạng mục).
- → "0 hết không nhập được" trên CCM = **tài khoản xem không có vai trò CostControl** (không phải bug). Bất đối xứng THẬT: PRO có ô ghi chú (`ProNote`), **CCM thiếu ô ghi chú** (entity không có `CcmNote`).

## 2 quyết định anh chốt (AskUserQuestion)
1. **Thêm ô "Ghi chú từ CCM"** (cần Mig 55 — cột `CcmNote`). ✅ CÓ.
2. **Quyền nhập CCM:** giữ phân vai (CostControl/Admin) — đúng phân vai anh Kiệt chốt S61. ✅ GIỮ.

## Cách chạy (hybrid có chủ đích — báo trước theo cam kết S73)
Mode HMW ON nhưng task = migration + ràng buộc DTO BE↔FE chặt + go-live-critical → theo `feedback_workflow_fanout_reliability`: **em-main tự làm BE (migration + DTO = chốt hợp đồng), song song giao implementer-frontend mirror FE 2 app** (contract pin), test-specialist test-after, em-main self-gate. KHÔNG full Workflow fan-out (reviewer-stage không tin được + tránh #53 trên migration).

## Done — commit `8655ebf` (14 file)
**BE (em-main):**
- Entity `PeWorkItemBudget +CcmNote` (string?, mirror `ProNote`) + config `HasMaxLength(1000)`.
- **Mig 55 `AddCcmNoteToPeWorkItemBudget`** — pure additive `AddColumn CcmNote nvarchar(1000) nullable`, Down drop (3-file: migration + Designer + snapshot). Không gotcha #63 (RenameColumn) — sạch.
- `UpdatePeBudgetCcmCommand` +param `CcmNote` (absolute-set, null=clear) + validator MaxLength(1000) + handler set `rec.CcmNote` + changelog "ghi chú CCM cập nhật".
- DTO `PeBudgetSummaryDto +CcmNote` + mapping `pairRec?.CcmNote` + controller `BudgetCcmBody +CcmNote` + `new UpdatePeBudgetCcmCommand(..., body.CcmNote)`.
- **Build-fail lần đầu:** quên call-site controller (record +param → mọi `new` thiếu arg). Grep `new UpdatePeBudgetCcmCommand|new PeBudgetSummaryDto` trong src/Backend → chỉ 2 (mapping + controller) → vá → build PASS. (Lesson: thêm positional param vào command record → grep mọi call-site; full slnx build bắt — gotcha #65.)

**FE 2 app (implementer-frontend, background):** dòng "Ghi chú từ CCM" chèn sau "V0/hiệu chỉnh" (gom nhóm CCM), gate `bs.canEditCcm`, lưu qua `ccmMut` kèm `initialAmount + adjustmentAmount + ccmNote` (absolute-set đủ 3 field — 2 call-site số tiền cũ cũng echo `ccmNote: bs.ccmNote`); type `+ccmNote: string|null`. SHA-mirror identical 2 app, cả 2 npm build PASS (local `index-CCPIU9Wr.js` / `index-j5Zh9w96.js`).

**Test (test-specialist — chết rate-limit, recover-disk):** agent died trên 529 NHƯNG work landed (15 tool_uses); đọc file thẳng (per `feedback_agent_kill_recovery`): existing CCM tests đã update 4-arg + section "4b. CcmNote" 5 test mới (set CCM/Admin, null-clear absolute-set, non-priv Forbidden+no-mutate, all-3-persist). KHÔNG re-spawn (anti-retry-loop). Em-main self-gate `dotnet test SolutionErp.slnx` → **45 Domain + 294 Infra = 339 PASS** (baseline 334 +5), 0 fail/skip. test-specialist tự curate L1 27.2→24.6KB (dưới cap) trong lúc chạy.

## State sau S74
- Mig **55** · tables **88** (additive col) · test **339** (45D + 294I) · menu 54.
- Bundle prod: **Run #315 PASS** (~4m54s, id=429) — admin `BYF5vIMJ` / user `CB-tiRxd`; Mig 55 applied prod (CcmNote nvarchar(1000) nullable, before/after DB snapshot xác nhận), sys.tables 88 (no new table), smoke api/admin/eoffice 4×200, 0 regression, 0 prod-data mutation.

## NEXT
- **Anh/anh Kiệt UAT:** đăng nhập tài khoản **CostControl/Admin** để thấy + test ô nhập CCM (Ban hành lần đầu / Hiệu chỉnh / **Ghi chú từ CCM** mới). Tài khoản PRO chỉ thấy ô PRO (đúng phân vai).
- **Carry S73:** cấu hình "Ngưỡng giá CEO" Designer + test 3 luồng giá Mig 54; "C" chuyển phiếu→dự án chờ spec form.
- **Em (carry):** curate L1 over-cap — reviewer **38.8KB** + investigator-codebase **31.5KB** (cả 2 chronic, budget.json đã re-measure S74) · monthly audit 2026-07-01 (doc-flush ef-core SKILL Mig 53→55 + root CLAUDE counts; STATUS/HANDOFF re-tier).
- **Ops giữ S58/S59:** tzutil VPS · anh Chương email typo · 5 real-staff pw `User@1234567` · gán CNTT lock nv.cao/nv.truong.
