1.**MenuKeys VÀO `All`** (lead) vs **NGOÀI `All`** (invest-spec K1.d:57) → theo lead, khai drift bên dưới.
2.**Controller riêng**`ContractCatalogController` (lead) vs **extend `CatalogsController`** (invest-spec K1.c:51) → theo lead về FILE, giữ PATH `api/catalogs/contract-catalog` của spec để FE khỏi đổi.
3.**TenVi B2-07..B2-14 bỏ chú-thích `(ảnh: …)`** — chú-thích của người chép, không phải tên hạng mục; chuyển thành comment cuối dòng. Row vẫn đủ 14.
4.**Test K1 k=5 (invest-spec K1.e) KHÔNG viết** — lead chỉ định "KHÔNG viết test mới, chỉ KHÔNG VỠ"; test là việc `test-specialist`.
| Tests | 590 | **590** | không đổi (0 test mới, 0 vỡ) |
## 🔴 Bàn giao / còn hở (lead quyết)
1.**Mig 70 CHƯA áp Dev-DB** — `sqlcmd (localdb)\MSSQLLocalDB -d SolutionErp_Dev` → top `__EFMigrationsHistory` = `20260729122015_AddContractSigningPlans` (Mig 69). Tôi KHÔNG tự áp (ngoài 7 bước lệnh + lệnh cấm sửa schema). Tự lành khi chạy API: `DbInitializer:64 MigrateAsync()` áp Mig 70 rồi seed 86 row ngay cùng lần boot. Hoặc tay: `dotnet ef database update --project src/Backend/SolutionErp.Infrastructure --startup-project src/Backend/SolutionErp.Api` (+ `--connection "…Database=SolutionErp_Dev…"` cho Dev).
2.**Chưa chạy verify RUNTIME** (restart API ×2 → COUNT=86 idempotent, K1.f:70-71) vì chưa áp DB — checklist đó còn TRỐNG, đừng tick.
3.**Leaf menu hiện chỉ Admin thấy** — 3 seeder grant khác dùng list-key-cứng (chi tiết P2-B2).
4.**[OG-6] chưa gật** — data 86 row đã land trong code; nếu owner soát ra sai ô nào thì sửa tuple + restart (per-Code idempotent nên row đã seed KHÔNG tự cập nhật, cần UPDATE tay hoặc thêm nhánh upgrade-if-exists).
5. Test K1 k=5 để `test-specialist`.
END impl-be-k1 — 4 file lượt này (10 file scope K1-BE) · build PASS 0W/0E · test 590 (45D+545I) 0 fail
## PHẦN 2 (relaunch)
Lượt 1 chết sau schema-layer. Schema ĐÃ TRÊN ĐĨA (verify @relaunch):
- LỆNH lead PHẦN 2 mục 2 ghi **VÀO `All`** + drift DELTA **(+1 key / +4 policy)**.
→ Làm theo LỆNH lead (mới hơn + có khai drift). Hệ quả: `docs/STATUS.md` row `Menu keys` +1 và row `Policies` +4 (DERIVED |All|×|Actions|) — em main cập nhật STATUS, tôi KHÔNG đụng doc.
### P2-B1 Seeder 86 row — XONG
-`DbInitializer.cs` +`SeedContractCatalogAsync` (đặt ngay TRƯỚC `SeedContractTemplatesAsync`) + call-site trong `InitializeAsync` ngay SAU `SeedRealMasterDataAsync` (khớp K1.b "cạnh SeedRealMasterDataAsync, trước sample workflows").
- SignerRole/InitialsChain: lookup `signByGroup` 8 nhóm → **ghi denorm vào TỪNG row** (đủ 8 field/row như lệnh; không chép tay chuỗi 86 lần để khỏi lệch ký tự). Ô "N/A" đã bỏ đúng spec (N3 còn 3 ô, N4/N8 còn 2 ô, N7 còn 3 ô).
- **ĐO tại chỗ (grep count trên file vừa ghi):** tổng **86** row; theo section `A1=5 A2=1 A3=9 A4=5 B1=23 B2=14 B3=15 C1=6 C2=3 C3=4 CEO=1`; theo `ApprovalGroup` = **5/10/5/23/14/15/13/1** → khớp ĐÚNG K1.b:41.
-`DbInitializer.cs``SeedMenuTreeAsync` +row `(MenuKeys.ContractCatalog, "Danh mục Hợp đồng", MenuKeys.Catalogs, 5, "FileSpreadsheet")` — leaf thứ 5 dưới "Danh mục chi tiết"; icon `FileSpreadsheet` đã dùng sẵn (`MenuKeys.Forms` :1788) nên FE có map.
- Grant Admin: TỰ ĐỘNG qua loop `SeedAdminPermissionsAsync``MenuKeys.All` (`:2058`) — 0 code thêm.
- ⚠️ **Flag cho lead (KHÔNG tự làm — ngoài lệnh):** 3 seeder grant khác dùng DANH SÁCH KEY CỨNG chứ không prefix-match → key mới KHÔNG tự vào: `SeedCatalogManagerPermissionsAsync` (`:2626-2629`), `SeedAllRolesReviewReadPermissionsAsync` (`:2181-2182`), `SeedProcurementMasterAccessAsync` (`:2360-2363`). ⇒ **hiện chỉ Admin thấy leaf "Danh mục Hợp đồng"**; muốn PRO/CatalogManager thấy thì thêm key vào 3 list đó (mỗi chỗ 1 dòng).
### P2-B3+B4 CQRS + Controller — XONG
- MỚI `SolutionErp.Application/Master/Catalogs/ContractCatalogFeatures.cs`: `ListContractCatalogQuery(GroupCode?, ApprovalGroup?, IsActive?, Q?)` (sort GroupCode→SortOrder→Code, KHÔNG paging) · `GetContractCatalogEntryQuery` · `Create/Update/Delete` + 2 validator.
- Validator MaxLength **lấy từ EF config**`ContractCatalogEntryConfiguration.cs:18-24` (Code 20 · TenVi/TenEn/NduqNote 500 · GroupCode 10 · SignerRole 100 · InitialsChain 300) — không trust spec; `ApprovalGroup``InclusiveBetween(1,8)`.
- Dup-Code: `AnyAsync(x => x.Code == req.Code)` chạy trên ACTIVE-set (query filter `!IsDeleted`) → khớp UNIQUE filtered `[IsDeleted]=0`, Code của row xoá-mềm tái dùng được. Delete = `Remove` → soft qua AuditingInterceptor.
- MỚI `SolutionErp.Api/Controllers/ContractCatalogController.cs` — `[Route("api/catalogs/contract-catalog")]` + class `[Authorize]`, 3 verb ghi `[Authorize(Roles = "Admin")]`. **0 policy key mới**.
- Ghi chú hòa 2 lệnh: lead nói "controller ContractCatalogController", invest-spec K1.c:51 nói "extend CatalogsController" với path `api/catalogs/contract-catalog` → tôi lấy **file controller riêng (theo lead)** + **giữ nguyên path (theo spec)** ⇒ FE KIND_CONFIG `contract-catalog` chạy được không cần đổi.
- ⚠️ **1 quyết-định data khai rõ:** transcribe B2-07..B2-14 có chú-thích truy-vết `(ảnh: số 7 thứ nhất)`, `(ảnh: 9)`… — đó là **ghi-chú của người chép, KHÔNG phải tên hạng mục**. Tôi seed TenVi **sạch** ("Công tác kim loại", "Cảnh quan"…) và chuyển chú-thích thành comment cuối dòng code. Số dòng giữ nguyên 14 (AS-IS về row). Nếu owner muốn giữ nguyên văn trong tên → sửa 8 chuỗi, 1 phút.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.