# SUB investigator-codebase-0 — S134 PE lũy-kế TẠM TÍNH (ground-truth + propose spec)

> Run: 2026-07-17-S134-pe-luyke-tam-tinh · engine fable-real single-deep-pass · ghi-đĩa-trong-lúc-làm (chống #53)
> Status: IN-PROGRESS — append từng phần

## PHẦN 1 — Predicate lũy kế hiện tại (Claim 1) ✅

### Kết luận Claim 1: PARTIAL — quan trọng: "chỉ bắt DaDuyet" KHÔNG đúng cho cả 4 số

**Helper `PeBudgetAccumulator.cs` (extracted S133, behavior-preserving):**
- Peers = cùng `(ProjectId, WorkItemId)`, `Id != this`, `CreatedAt < this` (`PeBudgetAccumulator.cs:38-40`) + HasQueryFilter !IsDeleted tự áp.
- **PrevSubmitted (Total/Count)** = peers `Phase ∈ {ChoDuyet, DaDuyet}` → Count + SUM(`BudgetPeriodAmount ?? 0`) — `:42-48`. **⟵ ĐÃ bao gồm ChoDuyet (phiếu đang trình chưa duyệt)!**
- **PrevSelected (Total/Count)** = peers `Phase == DaDuyet` AND `Suppliers.Any(IsWinner)` (existence) — `:50-52`; total = SUM `ThanhTien` quote `IsSelected` của các peer đó — `:53-60`. **⟵ Đây mới là chỗ DaDuyet-only ("duyệt rồi mới bắc" Kenny S117).**
- **CurrentProposalTotal** = own quotes IsSelected — `:62-65`.

**Số bản predicate phải khớp — thực tế HIỆN TẠI là 2 bản sống + 1 bản one-time:**
- (a) Helper `PeBudgetAccumulator.ComputeAsync` — bản RUNTIME duy nhất.
- (b) Live-path `PurchaseEvaluationFeatures.cs:962-963` — **GỌI helper, KHÔNG duplicate predicate** (comment :959-961: "Row 1 = đã trình (ChoDuyet + DaDuyet); Row 2 = đã chọn thầu (DaDuyet + IsWinner). TraLai/DangSoanThao KHÔNG tính").
- (c) Snapshot-path `PurchaseEvaluationWorkflowService.cs:998` (`ApplyBudgetSnapshotOnFinalizeAsync`) — `:1018` **GỌI helper, KHÔNG duplicate**. Helper được gọi từ 5 site: `:311` (submit-bypass?), `:865/:904/:937` (finalize branches), `:1112` (admin-override) — cần verify chi tiết từng site.
- (d) Backfill SQL Mig 67 `20260717032812_AddPeApprovedBudgetSnapshot.cs:99-150` — bản DỊCH SQL one-time (đã chạy, không chạy lại — idempotent `WHERE SnapshotAt IS NULL`). Xác nhận enum số: `Phase IN (10, 7)` = ChoDuyet=10, DaDuyet=7 (`:128`), selected = `Phase = 7 + EXISTS IsWinner` (`:135-137`).

**TẠM TÍNH có đụng các bản này không?** → KHÔNG bắt buộc đụng semantics cũ. Cách sạch nhất: THÊM computation mới (nhánh/method mới trong helper hoặc method riêng) tính "phần góp từ phiếu chưa-terminal". Snapshot-path (c) + backfill (d) KHÔNG đụng — tạm-tính KHÔNG snapshot (đề bài chốt). Live-path (b) chỉ THÊM field vào DTO.

### Frozen gate S133 (Claim 3 partial — evidence)
- Gate: `PurchaseEvaluationFeatures.cs:929` — `frozen = Phase == DaDuyet && ApprovedBudgetSnapshotAt != null`.
- Frozen branch `:930-955`: serve 11 cột `ApprovedBudget*` từ PE row; row 4 "Giá trị kỳ này" VẪN live từ own quotes (`:932-936` — SPEC S133); `BudgetFrozen: true` (`:955`); canEditPro/Ccm = false-false (`:950`).
- Live branch `:957-984`: pairRec `PeWorkItemBudgets` + accumulator; `BudgetFrozen: false` (`:984`).
- DTO: `PurchaseEvaluationDtos.cs:353` — `bool BudgetFrozen = false` (cần đọc full record để map field position).
- Entity: `PurchaseEvaluation.cs:116-132` — 11 cột ApprovedBudget* + SnapshotAt.
- Seeder demo cũng set snapshot: `DbInitializer.cs:1327-1340`.

## PHẦN 2 — Cụm 4 số lũy kế + FE matrix (Claim 2) ✅

**BE DTO `PeBudgetSummaryDto` (`PurchaseEvaluationDtos.cs:331-353`, 18 field):**
- `PreviousSubmittedTotal/Count` (:342-343) = SUM `BudgetPeriodAmount` peers Phase ∈ {ChoDuyet, DaDuyet} — "Ngân sách trình duyệt trước"
- `PreviousSelectedTotal/Count` (:344-345) = SUM quote ThanhTien IsSelected của peers DaDuyet+IsWinner — "Kỳ trước đã chọn thầu"
- `CurrentProposalTotal` (:346) = own quotes IsSelected
- `BudgetFrozen` (:353) — append CUỐI default false (pattern append-an-toàn construction site)

**FE type `PeBudgetSummary` (`fe-user/src/types/purchaseEvaluation.ts:314-334`; block lũy kế :327-333).** ⚠️ types 2-app KHÔNG SHA-mirror (fe-user `173529b` vs fe-admin `5631542` — S117 confirmed lại): sửa type = edit CẢ 2 file riêng.

**FE matrix `PeDetailTabs.tsx` (2-app SHA IDENTICAL `47c97df1`):**
- Công thức: `row1 = bs.previousSubmittedTotal` (:1315) · `row2 = bs.previousSelectedTotal` (:1316) · `row4 = bs.currentProposalTotal` (:1317) · **`row6 = row2 + row4`** (:1318 — "6. Lũy kế thực hiện = 2 + 4", render :1579-1586) · **`r5 = row1 + r3`** per-cột trong `colCalc` (:1323-1335, :1325 — "5. Lũy kế ngân sách đã sử dụng = 1 + 3", render :1569-1578). r7 = colFull − r5 (:1326), r9 = row4 + r8 (:1328), cmp56 = r5 − row6 (:1332).
- Row 1/2 CHUNG 3 cột (`BudgetSharedNumCell colSpan=3`, :1498/:1503) — hiển thị null khi Count==0.
- Row 3 PER-cột: PRO = `ev.budgetPeriodAmount ?? bs.proInitialAmount` (:1516) / CCM = `ev.ccmBudgetPeriodAmount ?? bs.initialAmount` (:1531 — Mig 59).
- Frozen badge "🔒 Ngân sách chốt tại thời điểm duyệt" (:1352-1356). WorkItemId null → `bs` null → banner nhắc gắn hạng mục (:1297-1304).
- ⚠️ Nuance sẵn có: row1 (accumulator) SUM `BudgetPeriodAmount ?? 0` — peer chưa nhập row3 góp 0, trong khi FE row3 phiếu đó hiển thị fallback proInitialAmount (drift-vector S133 đã ghi nhận, TẠM TÍNH kế thừa nguyên trạng — không sửa trong scope này).

## PHẦN 3 — Phase taxonomy (Claim 4) + snapshot helper (Claim 3 evidence)

**Enum `PurchaseEvaluationPhase.cs:15-27`:**
- `DangSoanThao = 1` (Nháp) · `ChoPurchasing=2/ChoDuAn=3/ChoCCM=4/ChoCEODuyetPA=5/ChoCEODuyetNCC=6` [LEGACY deprecated] · **`DaDuyet = 7`** (terminal thành công) · **`ChoDuyet = 10`** (đã gửi duyệt) · **`TraLai = 98`** (trả lại — Drafter sửa gửi lại) · **`TuChoi = 99`** (terminal khoá).

**Snapshot helper `ApplyBudgetSnapshotOnFinalizeAsync` (`PurchaseEvaluationWorkflowService.cs:998-1041`):**
- WorkItemId có → load pair `PeWorkItemBudgets` 6 cột (:1004-1011) + accumulator 4 cột lũy kế (:1018-1023); WorkItemId null → 10 cột null (:1027-1037); LUÔN set `ApprovedBudgetSnapshotAt` (:1040).
- 5 call-site: `:311` + `:865/:904/:937` + `:1112` (RULE comment :997 "MỌI nhánh set Phase=DaDuyet PHẢI gọi helper NGAY TRƯỚC assignment").
- Display-gate frozen: `PurchaseEvaluationFeatures.cs:929` (`Phase==DaDuyet && SnapshotAt != null`); frozen branch :930-955 serve snapshot + `BudgetFrozen:true`; row 4 "giá trị kỳ này" VẪN live (:932-936 spec S133).

## PHẦN 4 — Màn duyệt (Claim 5) — PeWorkflowPanel (2-app SHA IDENTICAL `9d88e671`)

- Component: `fe-user/src/components/pe/PeWorkflowPanel.tsx` (+ fe-admin mirror same SHA). Panel 3 = timeline + transition buttons + history.
- Final-approver detect ĐÃ CÓ SẴN: `currentIsFinalApprover = lastFlowLevel?.status === 'Current'` (:112-115) · CCM-done eligible `ccmDelegationEligible` (:109-111) · per-level finalize `approverFinalizeEligible` (:119) · `shouldPickPrice = isApproveAction && (currentIsFinalApprover || finalizeByCcm || (approverFinalizeEligible && applyLevelFinalize))` (:510) — ĐÂY chính là predicate "đang duyệt CUỐI" tái dụng được cho cảnh báo.
- V2 banner "Đến lượt bạn" :418-437 · action buttons :439-487 · Dialog transition :494-530 (footer Xác nhận :522, `priceMissing` disable :514) · mutation POST `/purchase-evaluations/{id}/transitions` :161-216.
- Chỗ chèn cảnh báo "phiếu trước chưa duyệt" khả thi: (i) TRONG Dialog duyệt khi `isApproveAction` (cạnh :509-514, trước bộ chọn giá) — approver PHẢI nhìn thấy trước Xác nhận; (ii) banner Panel cạnh V2 banner :437; (iii) badge/note trong matrix PeDetailTabs rows 1/5/6.
- Data: bundle DTO (`evaluation`) hiện KHÔNG có danh sách phiếu-trước-chưa-duyệt (chỉ có count gộp ChoDuyet+DaDuyet trong PreviousSubmittedCount — không tách). ⇒ cần field DTO MỚI; endpoint MỚI: KHÔNG cần — bơm vào GetPurchaseEvaluationQuery (đã query peers sẵn).

## PHẦN 5 — 5 call-site snapshot định danh + DB ground + tests

**5 site `ApplyBudgetSnapshotOnFinalizeAsync` (đọc context thật):**
- `:311` — Admin manual override (site 5, reviewer-catch S133; assignment qua biến targetPhase)
- `:865` — level-finalize (AllowApproverFinalize + applyLevelFinalize opt-out S96; set `EndedByLevelFinalize=true`)
- `:904` — CCM duyệt-done miễn CEO (threshold guard winnerQuoteTotal < ceoThreshold)
- `:937` — all-steps-done terminal (CEO/cấp cuối V2)
- `:1112` — V1 legacy final (KHÔNG ApplyApprovedPriceOnFinalize)

**Entity fields:** `PurchaseEvaluation.cs:10` MaPhieu · `:16` WorkItemId loose-Guid · `:40` BudgetPeriodAmount · `:49` CcmBudgetPeriodAmount. Bundle root DTO `BudgetSummary` field `PurchaseEvaluationDtos.cs:260`; construction site duy nhất `PurchaseEvaluationFeatures.cs:1253`. FE bundle type `budgetSummary: PeBudgetSummary | null` (`fe-user/src/types/purchaseEvaluation.ts:470`) ⇒ PeWorkflowPanel đọc được qua `evaluation.budgetSummary` — KHÔNG cần prop/endpoint mới.

**DB ground:** Dev LocalDB `SolutionErp_Dev`: Phase distribution = {1:1, 3:1, 6:1, 7:1} — **phase LEGACY 3/6 CÒN ROW SỐNG** (seed cũ); multi-pair (cùng ProjectId+WorkItemId ≥2 phiếu) = 0 trong Dev. Prod: UNVERIFIED run này (SSH password extraction fail ×2 — không chặn spec; predicate viết dạng loại-trừ tự cover).

**Tests `PeBudgetFreezeTests.cs`:** peer-seed helper `:311-332` — CreatedAt override THỦ CÔNG (`peer.CreatedAt = createdAt` :332; TestApplicationDbContext KHÔNG wire audit interceptor); test lũy kế `:435-451` seed 2 peer CreatedAt sớm hơn `baseT`, phiếu xét `pe.CreatedAt = baseT` :451. Own-quote seed helper `:277-306` (IsWinner :287 + IsSelected :306).

## PHẦN 6 — Phase-set TẠM TÍNH trade-offs (Claim 4)

| Phase | Vào tạm-tính? | Trade-off |
|---|---|---|
| ChoDuyet=10 | ✅ CORE | "cung đặc thù" — đã trình, sẽ duyệt. Với row2-delta là gap chính; row1 ĐÃ gồm sẵn |
| TraLai=98 | ✅ khuyến nghị | đang sửa để gửi lại — sẽ quay lại luồng duyệt; badge trạng thái để người duyệt tự cân |
| DangSoanThao=1 | ⚠️ FLAG ANH | "tất cả lun" (lời em chat) NHƯNG: (i) nháp = RIÊNG TƯ S89 (`PurchaseEvaluationFeatures.cs:893-898` chặn approver mở nháp) — cộng tiền nháp vào matrix phiếu khác = lộ thông tin nháp; (ii) số nháp chưa chốt → nhiễu. Khuyến nghị: list-only (hiện MÃ + badge "nháp" trong danh sách cảnh báo, KHÔNG cộng delta tiền) |
| TuChoi=99 | ❌ | terminal — không bao giờ thành "chính xác"; nghịch mục đích "số sẽ thành chính xác khi phiếu trước duyệt" |
| DaDuyet=7 | — | đã nằm trong CHÍNH XÁC |
| Legacy 2-6 | ✅ gộp | Dev DB CÓ row (3, 6) — predicate loại-trừ `Phase NOT IN (DaDuyet, TuChoi)` tự cover, an toàn hơn liệt kê dương |

**Khuyến nghị tập mặc định:** pendingPriorPes list = peers `Phase NOT IN (7, 99)` (mọi non-terminal, kể cả nháp — có badge); **delta-TIỀN row2** = non-terminal TRỪ nháp (`NOT IN (1, 7, 99)`) + IsWinner-existence + SUM IsSelected; **delta-TIỀN row1** = phases chưa nằm trong row1 hiện tại TRỪ nháp (= TraLai + legacy 2-6). Lead quyết + flag anh điểm nháp.

## PHẦN 7 — Edge cases (Claim 7) — từng edge tạm-tính xử ra sao

1. **WorkItemId null**: `budgetSummary` null toàn cục (gate `Features.cs:912`) → tạm-tính + warning KHÔNG TỒN TẠI (đúng — không định nghĩa được "cùng cặp"); banner "gắn hạng mục" cũ giữ (`PeDetailTabs.tsx:1297-1304`).
2. **Multi-winner D4/IsSelected (S114)**: delta dùng ĐÚNG predicate hiện hành (IsWinner existence + SUM IsSelected — mirror accumulator :50-60) → nhất quán cardinality, 0 quy ước mới.
3. **Giá âm (S116)**: SUM signed nguyên trạng → delta có thể ÂM; FE render dấu như mọi số tiền; KHÔNG guard thêm.
4. **Min–Max (S117)**: `ApprovedPrice*` KHÔNG feed lũy kế (lũy kế = BudgetPeriodAmount + quote-sums; HĐ GiaTri cũng không đọc giá chốt) → orthogonal, 0 tác động.
5. **PRO-split Mig 56**: `proInitialAmount` chỉ là FALLBACK DISPLAY row3 (`PeDetailTabs:1516`), accumulator dùng `BudgetPeriodAmount ?? 0` — tạm-tính giữ nguyên quy ước (không sửa drift-vector sẵn có trong scope này).
6. **CcmBudgetPeriodAmount Mig 59**: row1 CHỈ SUM `BudgetPeriodAmount` (PRO-side); CCM-kỳ-này per-phiếu KHÔNG vào lũy kế hiện tại → delta CŨNG KHÔNG (thêm = đổi semantics CHÍNH XÁC, ngoài scope).
7. **Mốc CreatedAt**: tạm-tính GIỮ NGUYÊN peer-window `CreatedAt < this.CreatedAt` (khác window = 2 tập peer khác nhau — số chính xác và tạm tính hết so sánh được). Phiếu bị đè CreatedAt (seed/import) nằm ngoài/trong window y hệt semantics hiện tại. Test seed mirror `PeBudgetFreezeTests:311-332/:435-451`.
8. **Own-phase TuChoi/TraLai**: live branch vẫn compute; FE ẩn NUDGE màn duyệt khi phiếu terminal (duyệt không còn); matrix vẫn hiển thị (thông tin).
9. **Audit gap (phiếu sau duyệt trước phiếu trước)**: snapshot CHỈ chốt 4 số CHÍNH XÁC; tạm-tính không snapshot → sau này không tái dựng "lúc duyệt có cảnh báo N phiếu". Optional-recommended: nối `ContextNote` LogTransitionAsync tại finalize "Duyệt khi N phiếu trước chưa duyệt: [mã…]" — 0 cột mới, Changelogs pattern Mig 61 đã có.

## PHẦN 8 — PROPOSED SPEC 3-mục

### ① Tính năng / đề bài
UAT anh Kiệt FDC 15/07 (chốt 16:10): vì "cung đặc thù" (phiếu sau trình khi phiếu trước cùng gói thầu CHƯA duyệt), lũy kế PE phải: (a) "bắt lên và tính toán" cả phần góp từ phiếu chưa duyệt = **Lũy kế TẠM TÍNH**, phần góp NHẬN DIỆN ĐƯỢC (note màu); (b) **Lũy kế CHÍNH XÁC** = semantics hiện tại GIỮ NGUYÊN 100%; (c) người duyệt cuối (CEO/duyệt thay) NHÌN THẤY danh sách "phiếu trước chưa duyệt [mã — trạng thái]" lúc duyệt — informational nudge, KHÔNG hard-block.
**Ground-truth thu hẹp đề:** row1 "NS trình duyệt trước" ĐÃ gồm ChoDuyet sẵn (accumulator :43-44). Gap thật = **row2 "Kỳ trước đã chọn thầu" (DaDuyet-only) + row6 dẫn xuất**, và (tuỳ tập chốt) row1 thiếu TraLai/nháp/legacy.

### ② Cách implement
**BE (0 migration, 0 endpoint mới, 0 đụng snapshot-path):**
1. `PeBudgetAccumulator.cs` — THÊM record `PePendingAccumulation(PendingPrevSubmittedTotal, PendingPrevSubmittedCount, PendingPrevSelectedTotal, PendingPrevSelectedCount, PendingPriorPes)` + method MỚI `ComputePendingAsync(db, projectId, workItemId, peId, peCreatedAt, ct)`. Predicate: peers cùng cặp + `CreatedAt <` + `Phase NOT IN (DaDuyet, TuChoi)`; delta-selected mirror :50-60 (IsWinner-existence + SUM IsSelected); delta-submitted = phases ngoài {ChoDuyet, DaDuyet} theo tập chốt. `PePendingPriorPeDto(Id, MaPhieu, Phase)` cho danh sách. **KHÔNG sửa `ComputeAsync`** (snapshot-path + Mig 67 bất động).
2. `PeBudgetSummaryDto` (`PurchaseEvaluationDtos.cs:331-353`) — append CUỐI sau `BudgetFrozen`: 4 số pending (default 0) + `IReadOnlyList<PePendingPriorPeDto>? PendingPriorPes = null` (pattern append-an-toàn như BudgetFrozen S133).
3. `GetPurchaseEvaluationQuery` — nhánh LIVE (`Features.cs:957-984`): gọi `ComputePendingAsync`, fill fields. Nhánh FROZEN (`:930-955`): pending = 0/null/empty — **R1-S133 giữ nguyên: phiếu DaDuyet là bảng snapshot bất biến, KHÔNG thêm số động** (tạm-tính trên phiếu đã duyệt vô nghĩa + vi phạm freeze).
4. *(Optional-recommended, đóng audit-gap)*: tại 4 finalize-site + admin-site, nếu pending priors tồn tại → nối vào comment/ContextNote của `LogTransitionAsync` "Duyệt khi N phiếu trước cùng gói chưa duyệt: [mã…]" — 0 cột, Changelogs pattern sẵn.

**FE (2 app):**
5. `types/purchaseEvaluation.ts` ×2 app (KHÔNG SHA-mirror — edit block `PeBudgetSummary` TỪNG app): +5 field mới.
6. `PeDetailTabs.tsx` (SHA-mirror 2 app `47c97df1` — sửa 1 copy 2): matrix gate `!bs.budgetFrozen`:
   - Row 2 + Row 6: sub-dòng amber khi `pendingPrevSelectedCount > 0`: "Tạm tính: {row2+delta2} (+{delta2} từ {n} phiếu chưa duyệt)"; row6_tạm = row6 + delta2.
   - Row 1 + Row 5 (nếu delta1 bật): tương tự với delta1; r5_tạm per-cột = r5 + delta1.
   - Danh sách mã pending (MaPhieu + PhaseLabel badge màu per-phase) — render nhỏ dưới matrix hoặc tooltip.
7. `PeWorkflowPanel.tsx` (SHA-mirror `9d88e671`): (a) banner amber cạnh V2-banner (:437) khi `evaluation.budgetSummary?.pendingPriorPes?.length > 0 && !readOnly`: "⚠ N phiếu trước cùng gói thầu CHƯA duyệt: A/012 (Đã gửi duyệt), … — cần duyệt phiếu trước trước phiếu này"; (b) NHẮC LẠI block đó TRONG Dialog duyệt khi `isApproveAction` (vùng :509-514, trên bộ chọn giá) — người duyệt cuối (`shouldPickPrice` :510 = predicate CEO/duyệt-thay sẵn có) chắc chắn thấy trước "Xác nhận". KHÔNG disable nút.

### ③ Checklist per-vai + acceptance ĐO ĐƯỢC
**implementer-backend:**
- [ ] `ComputePendingAsync` mới; acceptance: `git diff` vùng `ComputeAsync` = 0 dòng; build xanh.
- [ ] DTO append-cuối; acceptance: 2 construction site (:946/:975) set explicit; không site nào khác gãy.
- [ ] Frozen → pending 0/empty; acceptance: GET phiếu DaDuyet+SnapshotAt → `pendingPriorPes=[]`, 4 số 0.
- [ ] Live → pending đúng; acceptance: curl GET phiếu B (peer A ChoDuyet+winner) → `pendingPrevSelectedTotal` = SUM quote IsSelected của A, list chứa `{maPhieu A, phase 10}`.
- [ ] KHÔNG migration: snapshot `ApplicationDbContextModelSnapshot.cs` diff = 0.
- [ ] (opt) Changelog note finalize; acceptance: transition finalize khi có pending → Changelogs row ContextNote chứa mã A.
**implementer-frontend (×2 app):**
- [ ] types 2 app; acceptance: `grep pendingPriorPes fe-{user,admin}/src/types/purchaseEvaluation.ts` = 2 hit.
- [ ] PeDetailTabs sub-rows + list; acceptance: SHA 2 app IDENTICAL sau sửa; phiếu frozen KHÔNG render tạm-tính.
- [ ] PeWorkflowPanel banner + dialog-block; acceptance: SHA 2 app IDENTICAL; nút Xác nhận KHÔNG bị disable bởi pending.
- [ ] UAT flow đo được: A ChoDuyet → mở B: matrix row2 hiện tạm-tính + banner liệt kê A; duyệt A → DaDuyet → refetch B: tạm-tính biến mất, row2 chính-xác tăng ĐÚNG delta cũ.
**test-specialist (pattern `PeBudgetFreezeTests` seed :277-306/:311-332):**
- [ ] ComputePendingAsync: peer ChoDuyet+IsWinner+IsSelected → delta2 = tổng; peer TuChoi → 0; peer DaDuyet → 0 (bên chính xác); TraLai/nháp theo tập chốt.
- [ ] Peer-window: peer CreatedAt > this → loại (mirror :435-451).
- [ ] Quote âm trong delta (signed).
- [ ] Frozen phiếu → pending rỗng.
- [ ] (opt) finalize ghi Changelog note.

### Phân rã hmw staged (kiểu S133): INV done (file này) → **IMPL-A** backend (items 1-4) → **IMPL-B** frontend 2-app (5-7, sau A merge DTO shape) → **TEST-C** (sau A; FE acceptance UAT tay). A/B song song được nếu chốt DTO-shape trước; C sau A.

## DECISION-POINTS (lead quyết / flag anh)
1. **DangSoanThao (nháp) vào delta-TIỀN?** A=include-all (đúng verbatim "tất cả lun") / B=list-only mã+badge, KHÔNG cộng tiền (S89 nháp-riêng-tư + số chưa chốt) — **tao khuyến nghị B, flag anh Kiệt**.
2. **Delta row1** (TraLai+legacy BudgetPeriodAmount) làm luôn hay chỉ row2? Khuyến nghị: LÀM (nhất quán "tất cả") — chi phí ~0 (cùng query).
3. **Nudge mức nào:** plain-info (khuyến nghị) / +confirm-checkbox FE "tôi biết N phiếu trước chưa duyệt" (rẻ, không BE) / hard-block BE (KHÔNG khuyến nghị — nghịch "cung đặc thù"; nếu anh muốn thì guard ở 4 finalize-site + admin bypass).
4. **Changelog audit note** lúc finalize (optional-recommended — đóng gap "về sau không chứng minh được lúc duyệt đã cảnh báo").
5. **TuChoi exclude** — khuyến nghị chắc, nêu để anh confirm.

## STATUS: COMPLETE — return digest gửi kèm.
