# spec-duyet-hd-10-08-2026 — YC-023 module Duyệt Hợp đồng (GĐ3) từ KHKK

> H21① — 3 heading. Engine propose (4 lượt /fable-real) → lead verify → LEAD ghi bản này → worker hạng-hai (Opus 5 MAX) thi công theo spec.
> 🔴 Chân lý chi tiết = [`sub-plan-review-fable.md`](sub-plan-review-fable.md) (29.647B, `== HẾT ==`) — spec này là bản chưng-cất + AC, KHÔNG chép lại lập luận (B1). Lead đã spot-check độc lập claim wire đắt nhất: `Contract.CurrentWorkflowStepIndex` TỒN TẠI (`Contract.cs:40`) ⇒ W2 đúng 0-mig.

## 1. Đề-bài

- YC-023 (nguyên văn → sổ yêu-cầu): module Duyệt HĐ GĐ3 mirror khuôn PE/KHKK cả FE+BE+menu; quy trình duyệt V2 **riêng per-LOẠI-HĐ**; phiếu GĐ3 = **CHÍNH Contract** (0 entity mới); kế thừa từ KHKK ĐÃ DUYỆT qua bridge K7 (`CreateContractFromSigningPlanFeatures`, 251 dòng).
- Trục treo lớn nhất = Q1/(80): 7-type ⟂ 8-nhóm — mọi wave mang nhánh điều-kiện theo bảng `sub-plan-review §4`, CẤM hardcode trước khi anh chốt.
- 3 quyết mức-lead ĐÃ CHỐT (T1-T3 `§8b`, anh bác thì 1 lệnh): F3-inbox = verify-only (code V2-aware sẵn `ContractFeatures.cs:530-593`) · DTO màn duyệt theo **khuôn cây-thô KHKK** không phải cặp precompute PE (`§1b-①`) · CÓ sinh 7 key `AwV2_Hd*` trong nhánh PA-A.

## 2. Cách làm — 11 wave W0-W10 (bảng đầy đủ + phụ thuộc = `sub-plan-review §7`)

- **Đợt-1, 0-chặn-owner, bất biến mọi nhánh Q — thi công theo thứ tự, TỪNG wave qua gate rồi ship:**
  - **W1 — gỡ 2 bom per-type** (BE 0-mig): deactivate per-Code cho type-3 tập-type tường-minh (R-6 CẤM regex) + sửa comment `:363` CÙNG edit + overview family type-3. File: `ApprovalWorkflowV2AdminFeatures.cs:205-215/:356-377`. **Test-before RED→GREEN** (khuôn W3-KHKK).
  - **W2 — BE-DTO nền hiển thị** (BE 0-mig, đóng FLAG-2): (a) `ContractSourceDto?` reverse-join `ContractSigningPlanLines WHERE ContractId=id` → plan (`MaKeHoach`/`PlanId`/`ApprovalGroup`) + hạng mục + Σ`ApprovedAmount` + `PurchaseEvaluationId`; (b) +`CurrentWorkflowStepIndex` + `List<ContractWorkflowStepDto>? WorkflowSteps` cây thô mirror `ContractSigningPlanFeatures.cs:194/:205`. Positional record: append field default-null CUỐI. `types/contracts.ts` ×2 app cùng wave. KHÔNG sinh cặp DTO PE-precompute.
  - **W7 — per-type server-filter** (FE): `MyContractsPage.tsx:42-48` + `InboxPage.tsx:91` ×2 app gửi `?type=` lên BE (param sẵn `ContractsController.cs:23-27`); đổi queryKey.
- **✅ ĐÁP ÁN ANH @S188 (YC-024, nguyên văn → sổ yêu-cầu):** (80) **7-LOẠI** · (81) **PA-A** (+xác nhận chuỗi PE→KHKK→HĐ) · (82) **mở-tất-cả-trước ⇒ W8 HOÃN** chờ anh nói "ổn rồi" *(đội hiểu bậc-2 — guard đúng-trạm luồng V2 vẫn chạy, đó là logic quy trình)* · (83) **roster sao khuôn KHKK**, anh tự chỉnh Admin Designer sau · (84) **2 nút** · (85) ẩn ⇒ **W9 HỦY** · (86) **khoá cứng** không đổi chéo · (87) không cột gợi-ý — user tự chọn loại khi tạo từ KHKK · (88) **ruột HĐ pre-fill** (xem W5 dưới).
- **Sau đợt-1:** **W3** seed 7 workflow `HD-{TYPE}` **sao cấu trúc trạm/người từ seeder KHKK** (`SeedKhkkGroupWorkflowsAsync` — implementer đo DB thật: 8 nhóm chung khuôn thì dùng khuôn chung, khác nhau thì lấy N1 + khai) + 7 key `AwV2_Hd*` (T3) → **W4** guard 4-cửa nhà-chung `public static` + cửa re-pin UpdateDraft; **khoá cứng theo (86)** — auto-pin theo loại, KHÔNG cho đổi chéo → **W5** card "Nguồn gốc KHKK" (sau W2a) **+ (88) ruột pre-fill**: load field từ dữ-liệu đã nhập (bridge K7 map + KHKK/PE upstream), khuôn hiển thị tham khảo **form HĐ Word có sẵn** (`docs/forms-spec.md` 8 form), field thiếu (vd điều khoản) cho điền bổ sung — ĐO bridge-map + màn edit Nháp hiện có TRƯỚC khi thêm field mới → **W6** màn duyệt **2-nút** mirror `KhkkWorkflowPanel` tự-suy từ con-trỏ + cây thô (Đ2 = guard đúng-trạm CÓ SẴN đường V2 — KHÔNG wave authz riêng) → ~~W8~~ **HOÃN theo (82)** → ~~W9~~ **HỦY theo (85)** → **W10** dry-run 2 nhánh 2 loại HĐ toàn trình + **verify F3 tại đây** + sổ ID/seq-cháy (khuôn K8).
- 🔴 **F-3 (reviewer đợt-1) — quyết TREO CHO W6:** DTO overview type-3 có `ActiveOthers` (đợt-1) nhưng **0 consumer FE** ⇒ màn Designer với type-3 nhiều-active VẪN giấu N-1 bản. W6/W3-Designer PHẢI chọn MỘT: (i) FE đọc `activeOthers`, hoặc (ii) lát-cắt-theo-Code từ `history` (khuôn KHKK `ApprovalWorkflowsV2Page.tsx:330-341`) rồi GỠ field (test W1b re-check khi gỡ). CẤM tuyên "bom-2 đã chữa UI" trước khi một trong hai land.
- **Đường tới hạn:** W0(thu đáp án) → W3 → W4 → W6 → W10. Nhịp: {đợt nhỏ → ship → anh nhìn prod → vẽ tiếp} (khuôn S159-⑦). Fan-out = hmw Opus MAX, `args.run` BẮT BUỘC (run này ≥3 task).

## 3. Checklist AC đo được

- **Gate CHUẨN mọi wave code:** `dotnet build` 0-err + `npm run build` ×2 app 0-err (trọng tài chạy SAU agent dừng tay — #68) + `dotnet test` baseline `docs/STATUS.md` (668 @S185) GIỮ-hoặc-TĂNG + reviewer diff-review + cicd 3-chân (deploy xanh · marker-dương · control-âm ĐỘC-QUYỀN) + SHA-pair từng cặp file mirror FE + wave 0-mig ⇒ `git status` 0 file `Migrations/` đổi.
- **W1:** (i) POST version Code loại X ⇒ `IsActive` Code loại khác GIỮ (đếm SQL trước/sau); (ii) overview type-3 trả ĐỦ N active cùng type; (iii) comment `:363` đã sửa cùng commit (grep).
- **W2:** HĐ dry-run K8 (`ZZTEST-K8/...`) GET detail trả `source.maKeHoach="KHKK/2026/00x"` đúng + HĐ tạo tay trả `source=null`; DTO có `currentWorkflowStepIndex` + `workflowSteps`; wire cũ không vỡ (test cũ xanh nguyên); types ×2 app SHA-pair.
- **W7 (AC SỬA @S188 — reviewer F-4, tiền-đề spec cũ SAI):** "×2 app" thật = `ContractsListPage` (admin — kèm 3 fix badge-total/pagination do client-filter-sau-cắt-trang) + `MyContractsPage` (user): request mang `?type=`, client-filter gỡ, queryKey có type — **ĐẠT**. **Inbox OUT-OF-SCOPE đợt-1** (đo: `GET /contracts/inbox` → `GetMyInboxQuery` KHÔNG tham số + `ContractFeatures.cs:550` `Take(100)`) ⇒ nợ có mã **[carry:w7b-inbox-param]**: BE thêm `type` + bỏ trần 100 → FE mới gỡ client-filter `InboxPage` (blocker-comment tại chỗ). Cùng-lớp còn sót (F-5, không chặn): `ContractCreatePage` ×2 app client-filter-sau-cắt-trang — gộp vào w7b.
- **W3+:** đáp án đã đủ @S188 (YC-024) — AC chi tiết bổ sung CÙNG LƯỢT thi công từng wave, khung sẵn ở `sub-plan-review §7`; W3 kèm AC "7 workflow seed khớp cấu-trúc trạm/người nguồn KHKK (đếm SQL đối chiếu)" + 2 row canonical STATUS đổi cùng commit.
