K3-BE: IN-PROGRESS — bước-0 XONG (lead), §1-8 p2 đang chạy

# sub-implbe-k3 — K3 BACKEND (S165, wave 3/8: 8 workflow nhóm + finalize + sweep)

> 🔴 **KHAI VẾT:** p1 wf_a04124a7-0fe = class *engine-process-exit* (35 tool-use/458s nhưng **0 delta đĩa
> + 0 artifact + return rỗng** — lượt chạy coi như chưa từng tồn tại; #53 tally cả wave). Bước-0 dưới đây
> = **LEAD tự chạy** (ssh + sqlcmd trực tiếp), p2 seed từ bảng ĐÃ VERIFY này, không cần ssh lại.

## §0 [LEAD] FAIL-CLOSED verify đội-hình QT-DN-V2-001 LIVE — PASS

- Instance prod = `localhost\SQLEXPRESS` (named instance — `-S localhost`/`tcp:1433` đều fail; ghi lại cho
  mọi lane sau đỡ dò). Windows-auth `-E` qua `ssh vietreport-vps` chạy được.
- `ApprovalWorkflows WHERE Code='QT-DN-V2-001'` = **3 row: 2 archived (IsActive=0) + 1 LIVE (IsActive=1)**,
  ApplicableType=**1** (Duyệt NCC/PE — nguồn đội-hình, đúng OG-2 + note owner slot-54 "lấy đúng bộ người
  duyệt đang cấu hình ở workflow Duyệt NCC prod"). TotalWf active toàn hệ = 7.
- **ROSTER bản LIVE (nguyên văn sqlcmd, mojibake console đã chuẩn hoá dấu theo email đã biết):**

| Bước | Dept | Cấp | Email | Tên |
|---|---|---|---|---|
| 1 «Phòng 1» | Phòng Cung Ứng (PRO) | 1 (OR-4) | phuong.nguyen@solutions.com.vn | Nguyễn Thị Bích Phượng |
| 1 | Phòng Cung Ứng | 1 | long.chau@solutions.com.vn | Châu Tạ Kim Long |
| 1 | Phòng Cung Ứng | 1 | duy.nguyen@solutions.com.vn | Nguyễn Văn Duy |
| 1 | Phòng Cung Ứng | 1 | binh.lethanh@solutions.com.vn | Bình Lê Thanh |
| 1 | Phòng Cung Ứng | 2 | tra.bui@solutions.com.vn | Bùi Lê Thủy Trà |
| 2 «Phòng 2» | P. Kiểm soát Chi phí (CCM) | 1 (OR-4) | luu.tran@solutions.com.vn | Trần Xuân Lưu |
| 2 | P. Kiểm soát Chi phí | 1 | truong.letrandang@solutions.com.vn | Lê Trần Đăng Trường |
| 2 | P. Kiểm soát Chi phí | 1 | binh.le@solutions.com.vn | Lê Văn Bình |
| 2 | P. Kiểm soát Chi phí | 1 | nguyen.ho@solutions.com.vn | Hồ Thị Nữ Nguyên |
| 2 | P. Kiểm soát Chi phí | 2 | chuong.phan@solutions.com.vn | **Phan Văn Chương** (TP.CCM — giữ cờ finalize OG-3/OG-9) |
| 3 «Phòng 3» | Ban Giám đốc (CEO) | 1 | truong.nguyen@solutions.com.vn | Nguyễn Văn Trường |

- **Vá-11 danh-tính (phân giải bằng dept trên bản LIVE):** `truong.letrandang` (CCM C1) ⟂ `truong.nguyen`
  (BGĐ C1 = CEO) — 2 người khác dept, không nhầm; `binh.lethanh` (PRO C1) ⟂ `binh.le` (CCM C1) — khác dept,
  không nhầm. Cặp "tring.le"/"thanh.lethanh" trong câu-đố spec KHÔNG xuất hiện trên roster LIVE ⇒ là bẫy
  chính-tả từ ảnh, roster email là chân lý.
- ⇒ **Mô-hình 3 trạm PRO → CCM → CEO** với OR-of-N cấp 1 mỗi trạm + cấp 2 = trưởng phòng. Seed 8 workflow
  KHKK-N1..N8 mirror ĐÚNG cấu trúc này (3 Steps × Levels như bảng), resolve user theo EMAIL.

(§1-8 p2 ghi tiếp bên dưới)

---

# §1 [p2] KẾ HOẠCH (ghi TRƯỚC khi code — 3 dòng)

1. **A** — `DbInitializer.cs`: thêm `SeedKhkkGroupWorkflowsAsync(db, userManager, logger)` (UNGATED, đặt sau
   cụm 4 seeder WorkflowApps V2 ở `InitializeAsync`), mirror khuôn `SeedSampleContractWorkflowV2Async`; resolve
   11 email → dict 1 lượt, rồi loop N=1..8 tạo `KHKK-N{n}` (type-10) 3 Steps × Levels theo roster §0.
2. **B** — `MenuKeys.cs`: +8 const `AwV2_KhkkN1..N8` + vào `All` (ràng buộc T6: key ∈ All BẮT BUỘC có menu-row)
   ⇒ `DbInitializer.SeedMenuTreeAsync`: +8 row con dưới `ApprovalWorkflowsV2` (order 3..10), idempotent per-Key.
3. **C** — `dotnet build SolutionErp.slnx` (0W/0E) + `dotnet test SolutionErp.slnx` full (baseline 609/0) + đếm
   `MenuKeys.All` bằng lệnh máy → ghi DELTA.

**Đo TRƯỚC khi sửa (baseline, lệnh máy chạy ở §4):** `MenuKeys.All` hiện = ? (đo ở §4, không đoán).

🔴 **2 lệch phát hiện lúc khảo sát (khai ngay, KHÔNG tự quyết):**
- **L1 — "vùng seed workflow type-10 hiện có (W3 S161)" KHÔNG TỒN TẠI.** `grep ContractSigningPlan
  DbInitializer.cs` = **0 hit** (chỉ có `SeedKeHoachKyKetAccessAsync` = seed *quyền*, không phải workflow).
  ⇒ Không có khuôn type-10 để mirror; tôi mirror khuôn gần nhất **cùng lớp** = `SeedSampleContractWorkflowV2Async`
  (`:226`) / `SeedSampleProposalWorkflowV2Async` (`:276`) — cùng cách dựng `ApprovalWorkflow + Steps + Levels`.
- **L2 — `AllowApproverFinalize` bị ÉP `false` server-side cho type-10 ở đường Designer.**
  `ApprovalWorkflowV2AdminFeatures.cs:380-381`: `AllowApproverFinalize = typeEnum != ...ContractSigningPlan && l.…`
  (W1 S161 F-8 containment). Seeder ghi entity TRỰC TIẾP nên **không bị chặn**, nhưng nếu admin bấm "tạo version
  mới" qua Designer thì cờ rơi về false. Tôi vẫn set `true` theo lệnh (OG-3/OG-9) + ghi comment đúng câu lead
  giao; **nhịp sau cùng wave** port nhánh service + (nếu owner chốt) gỡ ép-false ở `:381`.

---

# §2 VIỆC A — `SeedKhkkGroupWorkflowsAsync` — DONE

**File:** `src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs` (2 chỗ)
- `:160-171` — gọi trong `InitializeAsync`, **UNGATED** (ngoài `if (!demoSeedDisabled)`), đặt ngay sau cụm
  4 seeder WorkflowApps V2 và trước `WarnDefaultAdminPasswordAsync`. Comment nêu gotcha #51.
- `:520-…` — thân method, ngay sau `SeedSampleVehicleBookingWorkflowV2Async`.

**Đã làm đúng lệnh:**
- 8 workflow `KHKK-N1..KHKK-N8`, `ApplicableType = ContractSigningPlan` (=10), `Version=1`,
  `IsActive=true`, `IsUserSelectable=true`, `Name = $"Duyệt KHKK — Nhóm {n}"` (em-dash + khoảng trắng
  y như lệnh), `ActivatedAt=UtcNow`.
- Idempotent per-Code literal theo lệnh: `if (await db.ApprovalWorkflows.AnyAsync(w => w.Code == code)) continue;`
- 3 Steps Order 1/2/3, Name `"Phòng Cung Ứng"` / `"P. Kiểm soát Chi phí"` / `"Ban Giám đốc"`,
  `DepartmentId` = hint theo mã phòng `PRO` / `CCM` / `BOD` (`SeedDepartmentsAsync:2744`; nullable ⇒ phòng
  vắng cũng không chặn).
- Levels đúng roster §0: Step1 C1 OR-4 + C2 `tra.bui`; Step2 C1 OR-4 + C2 `chuong.phan`; Step3 C1 `truong.nguyen`.
- `AllowApproverFinalize = true` **CHỈ** ở Step2/Cấp2 (`chuong.phan`) — ghi comment nguyên câu lead giao
  (OG-3/OG-9 + "nhánh service port ở nhịp sau CÙNG WAVE trước push nên không phải config-lie #78").
  Đặt PER-ROW (khuôn F5/F6) nên chỉ 1 người mang cờ, không phát cho cả Cấp.
- Resolve email→user 1 lượt (11 email `Distinct` OrdinalIgnoreCase) qua `userManager.FindByEmailAsync`;
  thiếu ⇒ log warning gộp + **bỏ slot**, KHÔNG throw. Cấp 0 người ⇒ bỏ Cấp; Bước 0 Cấp ⇒ bỏ Bước + warn;
  workflow 0 Bước ⇒ VẪN tạo + warn (đúng lệnh).
- Log: `logger.LogInformation("Seeded {Count} KHKK group workflows (K3 SP-002)", seeded)` — chỉ khi `seeded > 0`.

**2 quyết định nhỏ tôi tự chốt (khai để lead soi):**
- ⚠️ **KHÔNG dùng khuôn `AnyAsync(w.ApplicableType == …)`** của 6 seeder mẫu: 8 workflow DÙNG CHUNG type-10
  ⇒ check-theo-type sẽ chặn 7 nhóm còn lại ngay sau khi N1 vừa tạo. Đây là chỗ khuôn cũ **không** copy được.
- **Order của Cấp đánh LẠI liên tục** (`levelOrder++` chỉ khi cấp có người) thay vì cứng 1/2 — nếu dev-DB thiếu
  đúng người Cấp 1 thì workflow còn mỗi Cấp Order=2, vi phạm ràng buộc sequential của Designer
  (`ApprovalWorkflowV2AdminFeatures.cs` `HaveSequentialOrders`). Trên prod (đủ 11 người) kết quả **y hệt** 1/2.

**Build chunk A:** `dotnet build src/Backend/SolutionErp.Infrastructure/SolutionErp.Infrastructure.csproj`
→ **0 Warning / 0 Error**.
