@ -43,7 +43,7 @@ Chuỗi 4 GĐ per-gói-thầu theo SOL-CCM-SP-002: GĐ1 Duyệt NCC (PE = CHA,
| OG-1 | Gộp HĐ | ✅ **CHỐT @S164** (verbatim: *"thiết kế gộp nhưng giao diện thì cho 1-1 (Khi user đổi ý thì mình làm luôn)"*) ⇒ **schema GỘP-capable (N line chung ContractId) + UI default 1-1** (bridge = 1 hàm nhận list-line, UI truyền 1 line; bung gộp = FE sau, 0 đổi máy) | K2, K7 |
| OG-1 | Gộp HĐ | ✅ **CHỐT @S164** (verbatim: *"thiết kế gộp nhưng giao diện thì cho 1-1 (Khi user đổi ý thì mình làm luôn)"*) ⇒ **schema GỘP-capable (N line chung ContractId) + UI default 1-1** (bridge = 1 hàm nhận list-line, UI truyền 1 line; bung gộp = FE sau, 0 đổi máy) | K2, K7 |
| OG-2 | Roster 8 workflow | ✅ **CHỐT @S164** (*"Đúng đội hình cũ"*) — map đội hình `QT-DN-V2-001`, **verify bản LIVE (không archived) trước seed** | K3 |
| OG-2 | Roster 8 workflow | ✅ **CHỐT @S164** (*"Đúng đội hình cũ"*) — map đội hình `QT-DN-V2-001`, **verify bản LIVE (không archived) trước seed** | K3 |
| OG-7 | 1 PE → N phiếu | ✅ **CHỐT @S164** (*"PE cho nhiều, nhưng… rào hiển thị 1-1 lại — User cần thì bung ra"*) ⇒ **BE mở N-phiếu/PE (đổi rào 2 site :342/:1201 → (PeId, ApprovalGroup)) + UI default hướng-1**: PE có phiếu rồi → hiện phiếu + nút phụ "Tạo thêm cho nhóm khác" (thay 409 chặn hẳn) | K2 |
| OG-7 | 1 PE → N phiếu | ✅ **CHỐT @S164** (*"PE cho nhiều, nhưng… rào hiển thị 1-1 lại — User cần thì bung ra"*) ⇒ **BE mở N-phiếu/PE (đổi rào 2 site :342/:1201 → (PeId, ApprovalGroup)) + UI default hướng-1**: PE có phiếu rồi → hiện phiếu + nút phụ "Tạo thêm cho nhóm khác" (thay 409 chặn hẳn) | K2 |
| OG-3 | NĐUQ | ✅ **CHỐT @S164** (*"Đồng ý. Vẫn như trước - các quyền kiểu chuyển CEO các thứ y chang vậy luôn, thì ko cần quan tâm mức duyệt, vì a Chương sẽ quyết định là cái đó cần CEO ko hay là anh ấy đc duyệt."*) ⇒ NĐUQ = ghi chú tham khảo; **cơ chế lên-CEO = TÁI DÙNG level-finalize sẵn có của V2** (người trạm CCM — Phan Văn Chương — tự chọn "kết thúc tại cấp" hay trình tiếp, y PE đang chạy: `EndedByLevelFinalize` runtime + checkbox opt-in S97) — K3**0 code mới** cho nhánh này, CHỈ config workflow 8 nhóm có cấp CCM mang quyền finalize | — |
| OG-3 | NĐUQ | ✅ **CHỐT @S164** (*"Đồng ý. Vẫn như trước - các quyền kiểu chuyển CEO các thứ y chang vậy luôn, thì ko cần quan tâm mức duyệt, vì a Chương sẽ quyết định là cái đó cần CEO ko hay là anh ấy đc duyệt."*) ⇒ NĐUQ = ghi chú tham khảo; **cơ chế lên-CEO = TÁI DÙNG level-finalize sẵn có của V2** (người trạm CCM — Phan Văn Chương — tự chọn "kết thúc tại cấp" hay trình tiếp, y PE đang chạy: `EndedByLevelFinalize` runtime + checkbox opt-in S97) — 🔄**SỬA @B2c1 (mâu thuẫn spec↔code, evidence `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx:151-152` `KHKK_EARLY_FINALIZE_NOTE` "backend chưa xử lý đường duyệt sớm"):** cơ chế = tái dùng KHUÔN level-finalize NHƯNG BE KHKK **CHƯA port nhánh** ⇒ K3 có **3 việc code**: (a) port nhánh finalize vào `ContractSigningPlanWorkflowService` (b) cột runtime `EndedByLevelFinalize` né #78 (forward-provision vào Mig 71 ⇒ K3 0-mig) (c) gỡ khóa 2 ô Designer FE. Seed `AllowApproverFinalize=true` cấp CCM-Chương CHỈ SAU khi (a) land — kẻo config-lie #78 | — |
| OG-6 | Soát bảng transcribe 86 dòng (gõ tay từ ảnh; 4 ô ⚠️ cuối file) HOẶC anh gửi file 15-trang | ⏳ đã giải thích, chờ anh soát | K1 (seed) |
| OG-6 | Soát bảng transcribe 86 dòng (gõ tay từ ảnh; 4 ô ⚠️ cuối file) HOẶC anh gửi file 15-trang | ⏳ đã giải thích, chờ anh soát | K1 (seed) |
| OG-8 | Authz per-nhóm | ✅ **CHỐT @S164** (*"Đúng chính xác. Cho hiển thị, phân quyền tính sau."*) — đợt này 8 nhóm = hiển thị + luồng duyệt; XEM/TẠO chung bộ khóa module; authz-per-nhóm = wave sau khi anh gọi | — |
| OG-8 | Authz per-nhóm | ✅ **CHỐT @S164** (*"Đúng chính xác. Cho hiển thị, phân quyền tính sau."*) — đợt này 8 nhóm = hiển thị + luồng duyệt; XEM/TẠO chung bộ khóa module; authz-per-nhóm = wave sau khi anh gọi | — |
**PRE-gate:** [OG-6] anh soát `danh-muc-sp002-transcribe.md` (4 ô ⚠️ cuối file) TRƯỚC khi land seed. Code/schema land trước được, seed-data commit sau gật.
Config `Persistence/Configurations/ContractCatalogEntryConfiguration.cs`: ToTable + HasQueryFilter(!IsDeleted) + 2 index + MaxLength như bảng. DbSet vào `ApplicationDbContext` + `IApplicationDbContext`.
**Mig 70 `AddContractCatalogEntries`** — 1 CreateTable, bảng **96→97**. 3-file rule (Up/Down + Designer + Snapshot — gotcha #17). Down = DropTable (reversible, chưa ai tham chiếu tại K1; FK từ Lines là chuyện Mig 71).
- **Gate: UNGATED** — mirror `SeedRealMasterDataAsync` (`DbInitializer.cs:2893-2896` "INFRASTRUCTURE master data thật... NOT gated DemoSeed") — danh mục SP-002 là master data thật phải có mặt prod ngay deploy, không phải demo.
- **Né #75/#76 (resurrect):** lookup existing codes PHẢI `IgnoreQueryFilters()`:
(⚠️ khuôn `SeedRealMasterDataAsync:2970` dò `db.Projects.Select(p=>p.Code)` KHÔNG IgnoreQueryFilters ⇒ chính là lỗ #75/#76 — row soft-deleted vô hình → RE-CREATE mỗi restart. K1 làm ĐÚNG hơn khuôn, ghi comment lý do. Đây là hit `IgnoreQueryFilters` đầu tiên trong src/Backend — S155 đo 0-hit — cố ý, khai trong PR.)
- Gọi trong `InitializeAsync` cạnh `SeedRealMasterDataAsync` (trước sample workflows).
### K1.c — BE CQRS + controller (QUYẾT policy: mirror CatalogsController, KHÔNG policy mới)
**SOI MẪU:**`Api/Controllers/CatalogsController.cs:13-14` = `[Route("api/catalogs")]` + class `[Authorize]` (đăng nhập = đọc được); 12 action write đều `[Authorize(Roles = "Admin")]` (:23-:130). Grep `Authorize(Policy = "(WorkItems|MaterialItems|UnitsOfMeasure|ServiceItems)` toàn Controllers = **0 hit** ⇒ "Catalogs.*"-policy per-action KHÔNG tồn tại trong khuôn catalog.
**QUYẾT:** mirror y khuôn — GET `[Authorize]` thừa hưởng class, 4 mutation `[Authorize(Roles = "Admin")]`. Lý do: (i) luật "GIỐNG NHAU — ĐỪNG CHẾ THÊM"; (ii) picker tạo phiếu K2 cần READ rộng mọi user KHKK — nếu khoá `KeHoachKyKet.Read` thì user thiếu grant leaf sẽ 403 picker (#44/#85); (iii) `KeHoachKyKet.Update` (drafter sửa phiếu) KHÔNG được phép sửa danh mục — sai vai; (iv) OG-8 "phân quyền tính sau" — 0 policy/menu-key mới = 0 drift STATUS row Policies.
- CQRS: file MỚI `Application/Master/Catalogs/ContractCatalogFeatures.cs` (cạnh `CatalogsFeatures.cs` hiện hữu — mega-file 1 file, Pattern 12-ter):
`ListContractCatalogQuery {ApprovalGroup?, GroupCode?, Search?, IncludeInactive?}` → all-rows sort (GroupCode, SortOrder), KHÔNG paging (86 row; picker + trang admin dùng chung) · `GetContractCatalogEntryQuery {Id}` · `CreateContractCatalogEntryCommand` + `UpdateContractCatalogEntryCommand` (validator: NotEmpty Code/TenVi/GroupCode/SignerRole; `ApprovalGroup` InclusiveBetween(1,8); dup-Code active-set → ConflictException — so exact `Code ==`, filtered-unique cho phép trùng với row đã xoá mềm) · `DeleteContractCatalogEntryCommand` soft-delete (Line K2 giữ FK Restrict chặn hard-delete; soft-delete OK vì Line denorm `TenHangMuc`).
- Controller: **EXTEND `CatalogsController`** +5 action `GET/POST/PUT/DELETE api/catalogs/contract-catalog[/{id}]` — không controller mới (khuôn 4 kind sẵn trong 1 controller).
**PRE-gate:** [OG-1] + [OG-7] — cả 2 ✅ CHỐT @S164 (spec §④). Phụ thuộc K1 (bảng catalog phải có trước để trỏ).
### K2.a — Mig 71 `AddContractSigningPlanGroupAndLineCatalog` (3-file rule, no new table)
1.`ContractSigningPlans`**+`ApprovalGroup int NOT NULL, defaultValue: 1`** — AddColumn với default tự backfill mọi row hiện có = QĐ-6 backfill=1 (khuôn `EvaluationType` l3-F5; KHÔNG cần `Sql()` riêng — default áp cả Design-DB 0-row lẫn prod-DB có-row, né #64 lệch môi trường).
2.`ContractSigningPlanLines`**+`CatalogEntryId uniqueidentifier NULL`** — **loose-Guid KHÔNG FK vật lý** (QUYẾT: mirror y Mig 49 `PE.WorkItemId` "convention PE: ProjectId/SelectedSupplierId đều loose"; chính Line.cs:15 `SupplierId` đã loose cố ý — thêm FK thật là phá khuôn module) + `IX_ContractSigningPlanLines_CatalogEntryId`.
3.`ContractSigningPlanLines`**+`TenHangMuc nvarchar(500) NULL`** — denorm tên tại thời điểm gán (catalog rename sau KHÔNG viết lại lịch sử phiếu — khuôn `SignedByFullName` denorm Mig 26).
4.**Đổi UNIQUE:** DropIndex unique cũ trên (`ContractSigningPlanId`,`SupplierId`) (tên EF-sinh — lấy đúng từ `ContractSigningPlanLineConfiguration.cs` vùng :24-26 khi build) → CreateIndex **UNIQUE (`ContractSigningPlanId`,`SupplierId`,`CatalogEntryId`) filtered `WHERE [IsDeleted]=0`**.
- ⚠️ **Bẫy NULL-unique SQL Server:** trong UNIQUE index, NULL == NULL ⇒ 2 row cùng (Plan,Supplier) đều `CatalogEntryId NULL` sẽ VI PHẠM. Data cũ AN TOÀN (unique cũ bảo đảm ≤1 row/cặp ⇒ tối đa 1 NULL-row/cặp). Chặn phát sinh mới bằng validator: **line MỚI/SỬA bắt buộc có `CatalogEntryId`** (chỉ row legacy được giữ null). KHÔNG thêm `AND CatalogEntryId IS NOT NULL` vào filter — làm vậy là thả trôi vô hạn dup (Plan,Supplier,null).
5. Config update cùng file `ContractSigningPlanLineConfiguration.cs`: 2 property + index swap; `:29 WithMany(p => p.Lines)` giữ nguyên.
### K2.b — QĐ-9 budget-freeze: QUYẾT **per-line, TÁI DÙNG `ApprovedAmount` — 0 cột tiền mới, KHÔNG chép 11 cột PE Mig 67**
**Bằng chứng máy-freeze ĐÃ TỒN TẠI:**
-`ContractSigningPlanLine.cs:17-20` — `PeReferenceAmount` = SNAPSHOT chụp lúc TẠO, comment tự khai *"cùng triết lý freeze ngân sách Mig 67"*; `:23 ApprovedAmount` — *"CHỈ ghi tại choke-point finalize (W3)"*.
-`ContractSigningPlanWorkflowService.cs:304` — `line.ApprovedAmount ??= line.ProposedAmount;` foreach `:303` chạy tại finalize; `:290` comment cảnh báo đúng bài #81-EXT: *"Bỏ sót một nhánh = phiếu 'Đã duyệt' mà ApprovedAmount rỗng ⇒ GĐ3 sinh hợp đồng 0đ"*.
- Grep `PeWorkItemBudget|WinnerQuoteTotal` trong `ContractSigningPlan*` = 1 hit label (`Features.cs:162` DTO picker) — **KHKK KHÔNG đọc bảng ngân sách live nào**. PE Mig 67 cần 11 cột vì PE detail đọc LIVE `PeWorkItemBudgets`; KHKK mọi số đã là cột local của chính nó.
**⇒ Lý do QUYẾT:** freeze = có sẵn về SCHEMA; K2 chỉ thêm RĂNG + GATE (luật "ĐỪNG CHẾ THÊM"). Header KHÔNG cần cột: `TotalPeReferenceAmount` (list DTO `:53`, Σ `:711`) derive từ lines bất biến.
**Việc phải làm (răng + gate):**
- **#81-EXT sweep — bảng Phase-assignment-site KHKK** (grep `Phase\s*=[^=]` — chú ý KHÔNG grep `\.Phase` vì object-initializer `Features.cs:373` không có dấu chấm, thoát lưới hẹp):
| Site | Gán | Freeze? |
|---|---|---|
| `Features.cs:373` | `Phase = DangSoanThao` (create initializer) | không cần (không vào DaDuyet) |
| `WorkflowService.cs:174` | `= ChoDuyet` (submit) | không cần |
| `WorkflowService.cs:278` | `= DaDuyet` (finalize choke-point) | ✅ đã gọi hook `:303-304` |
| `WorkflowService.cs:342` | `= targetPhase` (gán qua BIẾN — đúng class bẫy `:39` comment) | 🔴 PHẢI XỬ: nếu nhánh này reachable với `targetPhase == DaDuyet` (admin-override/return-path) thì hook `:304` bị bypass ⇒ hoặc (i) gọi `ApplyApprovedAmounts` ngay trước `:342` khi `targetPhase == DaDuyet`, hoặc (ii) assert nhánh không nhận DaDuyet + test khoá hành vi. Mirror đúng bài PE Mig 67 site-5 admin-override. |
Acceptance: re-grep khi build, liệt ĐỦ site trong PR (seeder/DbInitializer hiện 0 site gán phase KHKK).
- **Display-gate `frozen`:** DetailDto +`budgetFrozen = phase == DaDuyet` (khuôn tên PE S133); FE DaDuyet đọc `ApprovedAmount` + badge khoá, trước đó đọc `ProposedAmount`; mọi editor line (K4b) chỉ mở ở Nháp/TraLai — sau DaDuyet cấm sửa/xoá line (xoá mềm line sau DaDuyet làm Σ `:711` trôi ÂM THẦM — đây là leak gián tiếp duy nhất, chặn bằng phase-guard).
### K2.c — Đổi rào 1-PE-1-phiếu → (PeId, ApprovalGroup) [OG-7] — 2 site
- **Site 1 create `Features.cs:342-347`:** `AnyAsync(p => p.PurchaseEvaluationId == pe.Id && p.Phase != TuChoi)` → **+`&& p.ApprovalGroup == request.ApprovalGroup`**; message mới: *"Phiếu Duyệt NCC này đã có kế hoạch cho NHÓM duyệt này. Chọn nhóm khác hoặc xoá kế hoạch cũ."*`CreateContractSigningPlanCommand` +`ApprovalGroup int` (validator `InclusiveBetween(1,8)`); create gán `plan.ApprovalGroup = request.ApprovalGroup`.
- **Site 2 picker `Features.cs:1201-1202`** (query PE-eligible): bỏ loại-trừ PE-đã-có-phiếu → trả kèm **`existingGroups: int[]`** per PE (groups của các plan sống). **UI hướng-1 (OG-7 verbatim):** PE đã có phiếu → vẫn hiện, hiển thị phiếu hiện có + nút phụ **"Tạo thêm cho nhóm khác"** (disable các nhóm ∈ existingGroups trong select nhóm) — thay 409 chặn hẳn. FE `KhkkCreatePage`×2 app + `types/khkk.ts` DTO picker.
- Auto-gen Lines `:389` giữ per-winner, mồi `CatalogEntryId = null, TenHangMuc = null` (gán hạng mục = Lines-editor K4b, KHÔNG thuộc K2 — create hiện *"FE KHÔNG gửi lines"*`KhkkCreatePage.tsx:8`).
### K2.d — Validator line-cùng-nhóm (code mẫu)
Chạy ở 2 chỗ: upsert-lines (K4b, fail-fast) + **submit-guard** (service, chống data cũ lách):
**PRE-gate:** [OG-2] ✅ "Đúng đội hình cũ" — nhưng PHẢI **verify đội hình `QT-DN-V2-001` bản LIVE** trước seed (SSH+sqlcmd prod chết khi SQL-client load — S134/S148 ⇒ đo qua API prod `GET /api/approval-workflows-v2` bằng curl + token admin; Dev làm fallback, flag rõ trong PR nếu prod bất khả).
### K3.⚠️ MÂU THUẪN SPEC↔CODE phải xử TRƯỚC (finding chính của lane)
Spec §④ OG-3 ghi *"K3 **0 code mới** cho nhánh này — CHỈ config workflow"* (tái dùng level-finalize V2). Nhưng code THẬT: `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx:151-152` — `KHKK_EARLY_FINALIZE_NOTE = 'Chưa hỗ trợ cho quy trình Kế hoạch ký kết HĐ — backend chưa xử lý đường "duyệt sớm", tick vào sẽ KHÔNG có tác dụng.'` (Designer KHOÁ 2 ô duyệt-sớm cho type-10) + `ContractSigningPlanWorkflowService.cs` chỉ có finalize thường `:278` (KHÔNG có nhánh AllowApproverFinalize; MEMORY-verified `:44` comment không port CeoApprovalThreshold). Cột `AllowApproverFinalize` trên `ApprovalWorkflowLevels` CÓ SẴN (Mig 58 — dùng chung bảng, 0 mig) — cái thiếu là NHÁNH SERVICE + mở khoá Designer.
**⇒ K3 PHẢI gồm 3 việc code (không phải 0):** (a) port nhánh duyệt-sớm vào `ContractSigningPlanWorkflowService` — approver tại level `AllowApproverFinalize=true` tick "kết thúc tại đây" → nhảy thẳng choke-point `:278` (**BẮT BUỘC đi qua `ApplyApprovedAmounts :303-304`** — nối răng K2.b, không tự gán Phase tay #81-EXT) + ghi Changelog ContextNote marker `"Duyệt KẾT THÚC tại %"` (khuôn PE Mig 61/#79); (b) +cột runtime `ContractSigningPlans.EndedByLevelFinalize bit NOT NULL DEFAULT 0` — display đọc RUNTIME không suy CONFIG (né tái diễn #78) — **gắn vào Mig 71 item 6 (K2.a) nếu K2 chưa land; nếu đã land → Mig 72 AddColumn riêng**; (c) fe-admin GỠ khoá 2 ô + note `:151-152` cho type-10 (không gỡ = config-lie NGƯỢC: BE hỗ trợ mà UI bảo không).
- **QUYẾT: convention `^KHKK-N([1-8])$` — 0 mig, 0 cột mới.** Mở rộng `ContractSigningPlanScope.EnsureWorkflowTypeAsync` (`Features.cs:350` create + `:458-462` re-pin UpdateDraft — 2 call-site cùng helper = sửa 1 chỗ) thành `EnsureWorkflowTypeAndGroupAsync(db, awId, approvalGroup, ct)`: nếu `workflow.Code` match pattern → group parse PHẢI == plan.ApprovalGroup, lệch → `ConflictException("Quy trình KHKK-N{x} không thuộc nhóm duyệt N{y} của phiếu.")` (DoD: phiếu nhóm 3 pin nhóm 5 → 409); Code KHÔNG match pattern (vd `QT-KHKK-V2-001` — DEFAULT_CODE_BY_TYPE `ApprovalWorkflowsV2Page.tsx:170` gợi ý admin tự tạo) → **pass + LogWarning** (không chặn phiếu UAT cũ/quy trình tay). Phương án cột `ApprovalWorkflows.ApprovalGroup` tường minh hơn = defer, chỉ nâng khi owner cần chặn cứng quy trình tay.
### K3.c — 8 menu-con designer + resolvePath + route per-code (fe-admin ONLY — designer không có ở fe-user, S155)
- MenuKeys.cs +8 const `AwV2_KhkkN1..AwV2_KhkkN8` — **NGOÀI `All`** (0 policy mới, tiền lệ `HopDongCung`/leaf `Pe_*`); +8 MenuItem row con node `ApprovalWorkflowsV2` (`DbInitializer.cs:1801` vùng seed menu — QĐ-7 CHỪA subtree này khi thu gọn admin), Label "Nhóm n — <tênngắn>", Order n.
- fe-admin `Layout.tsx` resolvePath vùng `:166-172` (map `AwV2_*` 3-code hiện tại) **+8 entry** `AwV2_KhkkN{n}` → `/system/approval-workflows-v2/ContractSigningPlan?code=KHKK-N{n}` — leaf lạ KHÔNG map = rớt im lặng #50, phải đủ 8.
-`ApprovalWorkflowsV2Page.tsx`: deep-link type `ContractSigningPlan` ĐÃ MỞ (`:155-163` TYPE_CODE_TO_INT slot 10 `:147`); THÊM đọc `?code=` → filter danh sách definition đúng Code + prefill ô "Mã quy trình" = code đó (thay gợi ý `:164-171`). Mở designer nhóm n load đúng `KHKK-N{n}` (DoD l3-F10).
5.`KhkkEarlyFinalize_ChuongTick_DaDuyet_FreezeApplied_RuntimeFlagSet` — nhánh (a) K3.⚠️: qua choke-point, `ApprovedAmount` không null, `EndedByLevelFinalize=1`, marker Changelog ghi.
### K3.e — Checklist acceptance K3
- [ ] Verify-LIVE đội hình: curl prod `GET /api/approval-workflows-v2` (hoặc Dev + flag) — đối chiếu 11 email bảng K3.a TRƯỚC khi commit seed; ô ⚠️ "Trưởng" chốt được thì ghi vào PR, không thì hỏi owner.
- [ ] Restart API ×2 → `sqlcmd … "SELECT Code FROM ApprovalWorkflows WHERE ApplicableType=10 AND Code LIKE 'KHKK-N%'"` → **đúng 8**, không nhân.
1.**Commit K1-code**`[CLAUDE] Domain+Infra+App: add ContractCatalogEntry + Mig 70 + CQRS + FE kind` — entity/config/DbSet ×2 interface + **Mig 70** 3-file + `ContractCatalogFeatures.cs` + extend `CatalogsController` + FE ×2 KIND_CONFIG + menu row + menuKeys mirror + test K1 {3,4,5}. Restart local verify bảng rỗng OK. ⚠️ update `SolutionErp.slnx` KHÔNG cần (không .csproj mới) — chỉ file .cs mới trong project sẵn.
2.**Commit K1-seed** (SAU [OG-6] anh gật transcribe): `SeedContractCatalogAsync` 86-row + test K1 {1,2}. Restart ×2 → 86 idempotent. **Prod:** deploy xong seed tự chạy (UNGATED) — không cần tay.
3.**Commit K2**`[CLAUDE] App+Infra: KHKK ApprovalGroup + Line catalog + freeze răng (Mig 71)` — **Mig 71 gồm cả item 6 `EndedByLevelFinalize`** (forward-provision cho K3 — QUYẾT gộp để K3 **0-mig**; nếu lead tách thì K3 tự mang Mig 72) + rào 2 site + validator cùng-nhóm + list param + DTO + FE types + test K2 ×7. **Phụ thuộc K1-code** (validator K2.d query bảng catalog — phải CÓ bảng; không cần data 86). Σ PeReferenceAmount đo TRƯỚC/SAU migrate ghi PR.
4.**Commit K3**`[CLAUDE] Infra+FE-Admin: seed 8 KHKK workflows + early-finalize + designer per-code` — seed 8 + nhánh duyệt-sớm service + validator pin-group + 8 menu-con + Layout +8 + `?code=` + gỡ note khoá `:151-152` + test K3 ×5. **Phụ thuộc K2** (cột `ApprovalGroup` + `EndedByLevelFinalize`). PRE: verify-LIVE đội hình (K3.e bước 1) TRƯỚC khi commit.
5. Mỗi commit qua Gitea CI (~3 phút, path-filter docs-only 0s); prod IIS restart → `DbInitializer.MigrateAsync` áp mig + seed tự chạy; **restart ×2 verify idempotent** (khuôn S91). Docs cuối cụm: `STATUS.md` (Mig 69→**71**, bảng 96→**97**, test +**17**), `schema-diagram.md` +2 row mig, `HANDOFF.md`, tick `migration-todos.md`.
**Phụ thuộc chéo tóm tắt:** K2.d cần bảng K1 (không cần 86 row — null-pass) · K3.a seed KHÔNG đọc catalog (label hardcode) — chỉ cần K2 cột · K3.⚠️(b) cột runtime NẰM TRONG Mig 71 = K2 phải land trước K3 · seed-86 lơ lửng độc lập chờ OG-6 · K4a (cụm-2) 0 phụ thuộc cụm-1, chạy song song được (l3-F8).
> ⚠️ Path lệch trong spec: TASK yêu cầu `sub-reviewer-c1-l3.md`, footer RUN-TRACE ghi `sub-reviewer-2.md` → theo TASK (fix-mới thắng footer-cũ, S139); khai trong report.
## Findings
(ĐANG LÀM)
## Verdict
(ĐANG LÀM)
Reference in New Issue
Block a user
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.