REVIEWER-GATE-K2: IN-PROGRESS — chưa chốt # sub-reviewer-gate-k2 — GATE K2 (adversarial, trước commit-push) — S164/S165 > Luật ghi-đĩa-trong-lúc-làm (#53): mỗi mục đo xong ghi NGAY. Dòng-1 = verdict, cập nhật cuối cùng. > Vai: reviewer READ-only. KHÔNG sửa prod-code. Được chạy dotnet/npm build. ## §0 Tiền đề đo được (git, trước mọi phán) - Branch `main`. `git status --porcelain`: 31 file M + 5 file untracked (2 mig K2 + 1 test file K2 + 2 auto-memory). - Diffstat vùng code: 31 files changed, +1005/-126 (gồm cả .claude/docs). - File K2 untracked: `20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime.cs` + `.Designer.cs` + `tests/.../Application/ContractSigningPlanGroupAndCatalogTests.cs`. ## §1 Trọng tài BUILD/TEST — reviewer tự chạy tươi (nguyên văn) - `dotnet build SolutionErp.slnx` → `Build succeeded. 0 Warning(s) 0 Error(s)` · `Time Elapsed 00:00:18.24` ⇒ **KHỚP claim 0W/0E**. - `dotnet test SolutionErp.slnx`: - Domain: `Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, Duration: 69 ms` - Infrastructure: `Passed! - Failed: 0, Passed: 562, Skipped: 0, Total: 562, Duration: 1 m 11 s` - Tổng **607/0 = 45D + 562I** ⇒ **KHỚP claim `sub-test-k2.md:1` từng con số.** ## §2 Ranh giới (a)-(d) — đo trên diff - (a) `ContractSigningPlanWorkflowService.cs`: diff CHỈ 1 hunk `@@ -164,6 +164,23 @@` (+17/-0) nằm trọn trong `SubmitAsync`. `ReturnOrRejectAsync` (`:359 plan.Phase = targetPhase`) + `ApplyApprovedValuesOnFinalize` + `:295 = DaDuyet` **0 dòng đổi** ⇒ ranh B3-B4 GIỮ theo NỘI DUNG. - (b) K1 `ContractCatalog*`: `git status --porcelain` trên 3 path K1 = **0 dòng** (đã commit `50e6d8c`) ⇒ không bị đụng. - (c) route/menu: `App.tsx` · `lib/menuKeys.ts` · `components/Layout.tsx` **KHÔNG có trong diff** ⇒ 0 route/menu mới (K4a turf giữ). (`MenuKeys.cs` BE cũng không trong diff của wave K2 — chỉ `.claude/skills/permission-matrix/SKILL.md` là doc.) - (d) 3 test lead vá: `ContractSigningPlanSchemaTests.cs` (+56/-…) + `ContractSigningPlanApprovalTests.cs` (+19) vẫn còn trong diff, file mới `ContractSigningPlanGroupAndCatalogTests.cs` tách riêng ⇒ không bị lane đè. ## §3 `npm run build` ×2 — reviewer tự chạy tươi | App | Kết quả | Bundle (nguyên văn) | So claim lane | |---|---|---|---| | fe-admin | ✅ PASS, `✓ 1961 modules transformed` / `✓ built in 647ms` | `dist/assets/index-DORfkFnc.js 1,722.60 kB │ gzip: 428.08 kB` | KHỚP TỪNG KÝ TỰ | | fe-user | ✅ PASS, `✓ built in 575ms` | `dist/assets/index-qYI2yszp.js 1,638.80 kB │ gzip: 413.92 kB` | KHỚP TỪNG KÝ TỰ | Warning cả 2 app = pre-existing (chunk > 500 kB · `INEFFECTIVE_DYNAMIC_IMPORT` `realtime.ts`), 0 TS error. `noUnusedLocals: true` ⇒ build XANH = phép thử TS6133 có răng (import `KHKK_APPROVAL_GROUP_LABELS` ở ListPage đã có người dùng thật). ## §4 Code-vs-spec K2 **Mig 71 — 6 item ĐỦ, 3-file ĐỦ.** `20260731085624_…FinalizeRuntime.cs` + `.Designer.cs` (2 file `??` mới) + `ApplicationDbContextModelSnapshot.cs` (M) — snapshot có đủ `ApprovalGroup` · `EndedByLevelFinalize` · `CatalogEntryId` · `TenHangMuc(500)` · `HasIndex("ApprovalGroup","Phase")` · `HasIndex("CatalogEntryId")`. Backfill `:61` `UPDATE [ContractSigningPlans] SET [ApprovalGroup] = 1;` **vô điều kiện** đúng chữ vá. Unique `:93-98` 3 cột + `filter: "[IsDeleted] = 0"` đúng #57. **Đo DB (sqlcmd, `SolutionErp_Dev`) — reviewer tự chạy:** `tables=97` · top mig `20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime` · 2 cột header + 2 cột line **có mặt** · `sys.indexes is_unique=1` chỉ còn `PK_…` và `IX_…_ContractSigningPlanId_SupplierId_CatalogEntryId` filter `([IsDeleted]=(0))` ⇒ **unique cũ 2-cột ĐÃ CHẾT THẬT** (index swap, không phải thêm chồng). `plans=0 · lines_cnt=0` ⇒ Σ tiền local là **control RỖNG** — lane khai đúng độ mạnh, KHÔNG đòi thêm phép đo (xem §6). **Submit-guard 2 vế — PHÁN vụ "vá-5 THẮNG nền `:126`": lead ĐỌC ĐÚNG ưu tiên.** Chứng bằng chữ: - `spec-cum1-chi-tiet:4` nguyên văn: *"**12 VÁ dưới đây THẮNG khi xung đột**"* ⇒ luật ưu tiên do CHÍNH spec đặt, không phải lead tự phong. - `spec-cum1-chi-tiet:12` (vá-5): *"auto-gen line lúc tạo phiếu được mồi null NHƯNG **submit-guard CHẶN null**"*. - Nền `sub-invest-fable-b2-cum1.md:126`: *"Line `CatalogEntryId == null` (legacy nhóm-1 backfill) → CHO QUA"*. ⇒ Xung đột THẬT, vá thắng, code chọn CHẶN (`WorkflowService.cs:174-176`). Đúng. Kèm điểm cộng: cái GIÁ của lựa chọn (phiếu cũ bị Trả-lại sẽ **kẹt trình** tới khi K4b land) được khai THẲNG trong comment tại chỗ, không giấu. Vế (ii) khác-nhóm 409 `:178-182` theo đúng code mẫu nền K2.d. **List `?group=` end-to-end — 5/5 mắt xích có thật:** controller `ContractSigningPlansController.cs:42` `[FromQuery(Name="group")] int? group` → truyền vị trí thứ 5 `:49` → record `ListContractSigningPlansQuery:705 int? ApprovalGroup` → filter `:735-736` (đặt SAU vế IDOR, chỉ thu hẹp) → DTO `ListItemDto:63 ApprovalGroup` → FE `types/khkk.ts:158 approvalGroup: number`. FE **cố ý chưa đẻ ô lọc** (K4b/K5) — khai rõ ở `sub-implfe-k2.md §2` + comment `types/khkk.ts:28-32`. *(bẫy tên tham số → F-6.)* **Σ×3 + display-gate:** `Features.cs:793-804` — PeReference · Proposed · **+Approved** (`.Sum(l => l.ApprovedAmount) ?? 0m`). Detail `:689` `BudgetFrozen = plan.Phase == DaDuyet`. FE: `KhkkListPage` +cột "Tổng duyệt" (8 `` == 8 ``, `colSpan` 7→**8** ở CẢ 2 chỗ ×2 app — đếm tay khớp) · `KhkkDetailPage:439-446` badge 🔒 qua prop `actions` (`SectionCard` có sẵn prop này, không bịa). **Phase-guard + T-f:** `EnsureDraftEditable` (`Features.cs:229-237`) có **3 call-site thật** `:518 :928 :982`; K2 KHÔNG dựng guard mồ côi. T-f (`…GroupAndCatalogTests.cs:570-641`) **nối đường ghi THẬT**: dựng `UpdateContractSigningPlanDraftCommandHandler` thật, `ThrowAsync`, rồi đọc lại DB khẳng định `GhiChu == null` **và** `Changelogs == 0` ⇒ không phải reflection suông, có cả vế "ném rồi thì không ghi gì". **FE vá-4 + OG-7 — mở file thật, đối chiếu 4 điểm (≥3 yêu cầu):** 1. select nhóm default 1 — `KhkkCreatePage.tsx:62` `useState(KHKK_APPROVAL_GROUP_DEFAULT)`, const `= 1` (`types/khkk.ts:121`). ✅ 2. POST tường minh — `:99` `approvalGroup,` trong body (không dựa default ngầm BE). ✅ 3. nút "Tạo thêm cho nhóm khác" — `:240-243`, đặt NGOÀI `