# S76 (2026-06-19) — PE ngân sách MA TRẬN 3 cột + bảng lưới `<table>` + badge quyền-NS theo role

> **Anh Kiệt FDC + chị Trà Sol — go-live.** anh giao `/ultra-on` "làm hết, step-by-step có workflow review kiểm tra đàng hoàng, hoàn chỉnh rồi deploy báo tao".

## Bối cảnh (chat Zalo anh forward)
- Chị Trà Sol + anh Kiệt: panel "TỔNG HỢP NGÂN SÁCH TRÌNH KÝ" — **mỗi phòng (PRO/CCM) nhập + điều chỉnh ngân sách của CHÍNH phòng mình**; ảnh Excel = ma trận cột **DỰ ÁN | PRO | CCM**, mỗi cột có Ban hành lần đầu + V0/hiệu chỉnh → full = tổng.
- Thêm: cột tích "quyền nhập/điều chỉnh NS" bên flow (nhìn biết ai sửa được) + hiển thị ra fe-user mục Duyệt NCC.

## 2 fork anh chốt (AskUserQuestion)
1. **Cột DỰ ÁN** = FE hiển-thị-only (`—`), sau mới có người bên dự án nhập (đơn giản nhất, FE-only).
2. **Ô tích quyền NS ở flow** = chỉ-hiển-thị, GIỮ quyền theo role (PRO→cột PRO, CCM→cột CCM) — làm trước cho nhanh, KHÔNG configurable.

## Step 1 — ĐIỀU TRA (verify-workflow: "phần này hôm qua làm chưa?")
3 lens độc-lập (code-gate + git-history + adversarial) → **CHƯA**. Hôm qua S74 chỉ thêm CcmNote. Ngân sách edit thuần ROLE (`canEditPro`/`canEditCcm`, `PurchaseEvaluationFeatures.cs:800-801`), KHÔNG có submission-count lock. Phát hiện: "Ban hành/hiệu chỉnh" cũ gán CCM, nhưng ảnh Excel cho thấy PRO mới điền → design mới phải TÁCH mỗi phòng cột riêng.

## Done — 3 commit prod-verified

### `e33481e` (feature, cicd Run #318 PASS, bundle admin `BhFDF9IJ` / user `BAkuRl3C`)
- **Part 1 — form ma trận 3 cột.** Data-model (em-main solo, additive-an-toàn):
  - Entity `PeWorkItemBudget` +`ProInitialAmount`+`ProAdjustmentAmount` (decimal 18,2 nullable) — cột PRO; tái dùng `InitialAmount`/`AdjustmentAmount` làm cột CCM (đã canEditCcm-gate). `ProEstimateAmount` = LEGACY (FE bỏ).
  - **Mig 56 `AddProBudgetSplitToPeWorkItemBudget`** — 2 AddColumn additive + `Sql()` data-migrate `UPDATE PeWorkItemBudgets SET ProInitialAmount=ProEstimateAmount WHERE ProEstimateAmount IS NOT NULL AND ProInitialAmount IS NULL` (giữ số PRO cũ; chạy SAU AddColumn; idempotent; **4 rows backfill prod** gotcha #64). Additive-only → tránh gotcha #63. ASCII-only comment (gotcha #30).
  - Handler `UpdatePeBudgetProCommand` đổi `(PeId, ProInitialAmount, ProAdjustmentAmount, ProNote)` absolute-set, role-gate PRO/Admin Forbidden TRƯỚC side-effect + validator (ProInitial≥0, ProAdjust cho-âm) + controller `BudgetProBody` + DTO `PeBudgetSummaryDto` +2 field + capability `full` = CCM nếu hasCcm else proFull, `FullIsEstimate = !hasCcm && hasPro`.
  - FE 2 app SHA-mirror: `PeBudgetSummaryTable` Block A ma trận (DỰ ÁN hiển-thị-only / PRO canEditPro / CCM canEditCcm) + `BudgetCell` compact + `BudgetNoteRow`. proFull/ccmFull = ban-hành + hiệu-chỉnh mỗi cột.
- **Part 2+3 — badge quyền-NS theo role (display-only).**
  - BE: +2 cờ `CanEditProBudget`/`CanEditCcmBudget` per approver — tính qua 3 `GetUsersInRoleAsync` set-lookup (Procurement∪Admin / CostControl∪Admin, **no N+1**, khớp gate `canEditPro`/`canEditCcm` bit-for-bit) → vào `AwLevelDto` (designer, `ApprovalWorkflowV2AdminFeatures.cs`) + `PurchaseEvaluationApprovalLevelApproverDto` (PE flow, 2 build-site: approvalFlow + currentApproval).
  - FE: badge "✎ NS PRO"(amber)/"✎ NS CCM"(sky) cạnh approver ở `ApprovalWorkflowsV2Page` (fe-admin designer) + `PeWorkflowPanel` (2 app, `.join`→map từng approver). Types +2 cờ CẢ 2 app (purchaseEvaluation.ts types DIFFER giữa 2 app).
- **Test 339→344** (+5 PRO split: set cả ProInitial+ProAdjust gồm âm · validator · full=proFull khi CCM empty · full=CCM khi CCM present). test-specialist.

### `21d1f4e` (bảng lưới, cicd Run #319 PASS, bundle admin `jOqxW4-p` / user `DbsznVvR`)
- Anh phản hồi "giao diện vẫn chưa chia cột giống Excel" — Block A cũ dùng flex+gap (KHÔNG viền dọc) → chuyển **`<table border-collapse>` viền ô đầy đủ** (header Khoản mục|Dự án|PRO|CCM + 5 hàng: full / ban hành / hiệu chỉnh / ghi chú PRO / ghi chú CCM).
- `BudgetCell` xếp DỌC (input full ô + nút Lưu dưới — vừa cột hẹp). `BudgetNoteRow`→`BudgetNoteCell` (td colSpan=3, ghi chú trải hết).
- FE-only, 2 app SHA-mirror. **LESSON: flex+gap KHÔNG ra "bảng" — phải `<table>` viền ô mới giống spreadsheet.**

### Race fix (gotcha #70 NEW)
Reviewer workflow Part 2/3 escalate MAJOR data-loss: 2 ô PRO (ban hành + hiệu chỉnh) lưu qua CÙNG `proMut`, mỗi save echo field anh-em từ `bs` (server snapshot). Lưu ô A → `invalidate()` refetch BẤT-ĐỒNG-BỘ; trước khi refetch về, `bs.proInitial` còn CŨ → lưu ô B đè mất A. Vá: gate nút Lưu = `mut.isPending || useIsFetching({queryKey:['pe-detail',ev.id]}) > 0` (khoá tới khi refetch land). 2 app. Pattern absolute-set-echo có từ CCM Mig 50/55 prod chưa-báo-lỗi nhưng PRO nhân-đôi bề-mặt → vá trước go-live tài-chính.

## Workflow / harness
- HMW-mode ON (`/ultra-on`). 1 verify-workflow (Step-1) + 2 review-workflow (Part 1 · Part 2/3). Mỗi step build + review trước khi sang step kế (đúng anh giao).
- **Workflow-review-flaky:** 2/3 lane Part 1 + 1/3 lane Part 2/3 return RỖNG (#53). Lane trả về thì đầy-đủ + chính xác. → em-main self-gate lane rỗng (BE đã test-spec verify + build/test pass; FE lane cross-check). Khớp `feedback_workflow_fanout_reliability`: verify-heavy → em-main self-gate ≈ spawn lẻ.
- cwd-misland (gotcha tái diễn): impl-FE `cd fe-admin` npm build → MEMORY rơi `fe-user/.claude` stray → em-main harvest về canonical + thêm `.gitignore` guard `fe-*/.claude/`.

## State THẬT (verified)
Mig **56** · 88 bảng · **344** test (45D+299I) · gotcha **70** · menu 54 · bundle admin **`jOqxW4-p`** / user **`DbsznVvR`** (Run #319) · RAG 2428 · user-memory 29.

## 🔴 NEXT
- **Em (carry ưu-tiên):** curate L1 over-cap — reviewer **45KB** + inv-codebase **35KB** (keep-floor-hit, entries newest lớn → manual SPLIT/condense) + cicd 29KB + test-spec 28KB (strike-1). archive-gate A7 PASS 186/186.
- **Anh/anh Kiệt:** UAT bảng lưới (Ctrl+F5) bằng PRO/CCM; xem badge ✎NS trong Designer + flow.
- **Ops giữ:** tzutil VPS · anh Chương email typo · 5 real-staff pw · gán CNTT. Monthly audit 2026-07-01.
