# sub-investigator-codebase-3 — lens PRE-FILL (chuỗi kế thừa 3 tầng PE → KHKK → HĐ)

- **run-id:** `2026-08-13-S194-yc032-hd-thauphu-2form` · **lane:** 3 / 5 · **vai:** `investigator-codebase` (READ-only)
- **câu hỏi:** hôm nay chuỗi kế thừa đã CHẢY được bao nhiêu field, còn THIẾU bao nhiêu ô phải gõ tay
- **VERDICT:** `LENS-PREFILL: 17 ô có nguồn / 5 phải gõ tay / 2 chưa xác định` — nhưng **chỉ 3/17 thực sự đã chảy tới hàng `Contracts` hôm nay**; 14 ô còn lại nguồn NẰM SẴN trong DB mà **đường chưa nối**.

## 0. Khung

3 nhịp của chuỗi, đo bằng **write-site thật** (không suy):

| nhịp | file:line | hạt dữ liệu | số field ghi sang |
|---|---|---|---|
| PE → KHKK | `ContractSigningPlanFeatures.cs:526-575` | phiếu → phiếu; dòng = **1 NCC** (gộp tiền) | **3 header + 3 line** |
| KHKK → HĐ | `CreateContractFromSigningPlanFeatures.cs:215-243` (+`:269`,`:277`) | N dòng KHKK → **1 hàng `Contracts`** | **15 cột + 2 con-trỏ ngược** |
| HĐ → thân HĐ (7 bảng Details) | — | — | **0** (xem §C-D4) |

🔴 Hình dạng hạt LỆCH ở nhịp 1: PE giữ **N hạng mục × M NCC** (`PurchaseEvaluationDetail` + `PurchaseEvaluationQuote`), KHKK **gộp về 1 dòng/NCC** (`ContractSigningPlanLine.PeReferenceAmount` = Σ quote đã chọn, `ContractSigningPlanFeatures.cs:552-554`) ⇒ **toàn bộ dữ liệu dòng (nội dung / ĐVT / khối lượng / đơn giá) rụng ngay tại GĐ2**, HĐ có muốn kế thừa cũng không còn chỗ mà lấy — trừ khi đọc **vượt tầng** thẳng về PE.

Tiền lệ đọc vượt tầng ĐÃ TỒN TẠI: bridge K7 nạp thẳng PE qua `plan.PurchaseEvaluationId` (`CreateContractFromSigningPlanFeatures.cs:199-200`) chỉ để lấy `pe.MoTa` + `pe.TenGoiThau`. ⇒ đường về PE là đường **đã mở**, chỉ chưa dùng hết.

## A. KHKK→HĐ đã chảy

Field-map ĐÍCH DANH (`CreateContractFromSigningPlanFeatures.cs`):

| # | cột `Contract` | nguồn | line |
|---|---|---|---|
| 1 | `Type` | người bấm chọn trong dialog (**không suy được**) | `:217` |
| 2 | `Phase` | hằng `DangSoanThao` | `:218` |
| 3 | `SupplierId` | `line.SupplierId` (rào 1-NCC `:122-125`) | `:219` |
| 4 | `ProjectId` | `plan.ProjectId` | `:220` |
| 5 | `DepartmentId` | `plan.DepartmentId` | `:221` |
| 6 | `DrafterUserId` | actor đang bấm | `:222` |
| 7 | `GiaTri` | **Σ `line.ApprovedAmount`** (giá CHỐT, không lấy `ProposedAmount` — `:132-137`) | `:223`,`:202` |
| 8 | `TenHopDong` | request ?? `"{pe.TenGoiThau ?? plan.MaKeHoach} — {Σ TenHangMuc}"` clamp 500 | `:224`,`:204-213` |
| 9 | `NoiDung` | **`pe.MoTa`** ← ĐỌC VƯỢT TẦNG về PE | `:225`,`:199-200` |
| 10 | `BudgetManualName` | `"NS tham chiếu KHKK {plan.MaKeHoach}"` | `:228` |
| 11 | `BudgetManualAmount` | **Σ `line.PeReferenceAmount`** (số PE, KHÔNG phải số chốt — cố ý `:226-227`) | `:229` |
| 12 | `WorkflowDefinitionId` | V1 active theo `ContractType` | `:230`,`:192-195` |
| 13 | `ApprovalWorkflowId` | pin V2: người chọn ∨ auto theo họ `HD-*` (PA-A) | `:233`,`:161-177` |
| 14 | `SlaDeadline` | `now + GetPhaseSla(DangSoanThao) ?? 7d` | `:236-237` |
| 15 | `MaHopDong` | codegen RG-001 (`project.Code` + `supplier.Code`) | `:243` |
| 16 | *(ngược)* `Line.ContractId` | write-site DUY NHẤT của đường GĐ2→GĐ3 | `:269` |
| 17 | *(ngược)* `pe.ContractId ??=` | fallback hiển thị, `??=` có chiều | `:277` |

**KHÔNG chảy** (cột `Contract` tồn tại mà bridge để trống): `TemplateId` · `DraftData` · `BypassProcurementAndCCM` · `NoiDung` chỉ là `MoTa` 1 dòng · **0 dòng `ThauPhuDetails`**.

🔴 **REGRESSION so với cầu PE→HĐ cũ** — cầu cũ CÓ 2 field mà cầu KHKK **đánh rơi**:
- `DraftData = pe.PaymentTerms` (`CreateContractFromEvaluationFeatures.cs:104`, "carry forward payment terms")
- `BypassProcurementAndCCM = request...` (`:103`)
⇒ HĐ đi đường **KHKK** mất sạch điều khoản thanh toán mà HĐ đi đường **PE trực tiếp** vẫn có. Đây là **nguồn có sẵn, đường bị đứt**, không phải "chưa có dữ liệu".

## B. PE→KHKK đã chảy

`CreateContractSigningPlanCommandHandler` (`ContractSigningPlanFeatures.cs:451-593`):

**Header (3 field):** `PurchaseEvaluationId = pe.Id` `:529` · `ProjectId = pe.ProjectId` `:530` · `DepartmentId = pe.DepartmentId` `:531`.
`DrafterUserId` = **actor, CỐ Ý không lấy `pe.DrafterUserId`** (3 lý do khai ở `:532-537`). Còn lại (`MaKeHoach` codegen `:524`, `Phase`, `ApprovalGroup`, `ApprovalWorkflowId`, `GhiChu`) = máy/request.

**Line per winner (3 field, `:550-575`):** `SupplierId = w.SupplierId` `:559` · `PeReferenceAmount` = Σ `Quote.ThanhTien` WHERE `IsSelected` ∧ `q.PurchaseEvaluationSupplierId == w.Id` `:552-554` · `ProposedAmount` = mồi bằng chính số đó `:573`.
`CatalogEntryId`/`TenHangMuc`/`MaHangMuc` = **NULL CÓ CHỦ ĐÍCH** `:565-567` (gán tay sau, submit-guard chặn).

**KHÔNG chảy từ PE** (PE có, KHKK không nhận): `TenGoiThau` · `MoTa` · `DiaDiem` · `HoSoLink` · `PaymentTerms` · `WorkItemId` · `ApprovedPriceAmount/MaxAmount/Source` · `BudgetPeriodAmount` · **toàn bộ `PurchaseEvaluationDetail`** (`GroupName`/`NoiDung`/`DonViTinh`/`KhoiLuongThiCong`/`DonGiaNganSach`/`ThanhTien…`) và **`PurchaseEvaluationQuote`** (`ChuaVat` = đơn giá NCC, `ThanhTien`).

⇒ **6 field chảy / ~40 cột PE + 2 bảng con**. KHKK là **tầng hẹp nhất** của chuỗi.

## C. BẢNG ô × nguồn

Danh sách ô đo THẬT từ `SOL-CCM-FO-002.02.v01` bằng `antiword -m UTF-8.txt` (346 dòng; số dòng dưới = dòng trong bản trích).

| # | ô trong thân HĐ (002.02) | dòng .doc | nguồn | trạng thái hôm nay |
|---|---|---|---|---|
| A1 | Bên A: tên/địa chỉ/ĐT/MST `0313891900` | 89-96 | **in cứng trong form** | ✅ không cần nối |
| A2 | Bên A: Số TK + Ngân hàng | 95 | **0 bảng CompanyProfile** | ✍ gõ tay |
| A3 | Bên A: Người đại diện + chức vụ | 97 | 0 bảng | ✍ gõ tay |
| B1 | Bên B: tên công ty | 98 | `Supplier.Name` | ◻ có nguồn, chưa nối |
| B2 | Bên B: địa chỉ | 99 | `Supplier.Address` / `OfficeAddress` | ◻ chưa nối |
| B3 | Bên B: điện thoại / Fax | 100 | `Supplier.Phone`·`ContactPhone` / `Fax` | ◻ chưa nối |
| B4 | Bên B: số tài khoản | 101 | `Supplier.BankAccount` | ◻ chưa nối |
| B5 | Bên B: mã số thuế | 102 | `Supplier.TaxCode` | ◻ chưa nối |
| B6 | Bên B: người đại diện + chức vụ | 103 | `Supplier.LegalRepresentative` + `LegalRepTitle` | ◻ chưa nối |
| C1 | Ngày/tháng/năm ký | 87 | **0 cột** (`NgayKy`/`SignedDate`/`NgayHieuLuc` = 0-hit Domain) | ✍ gõ tay |
| C2 | Gói thầu: …… | 107 | `pe.TenGoiThau` → `Contract.TenHopDong` | ✅ **ĐÃ CHẢY** |
| C3 | Chủ đầu tư là ……… | 159 | `Project.Investor` (Mig 48) | ◻ chưa nối |
| C4 | Công trình/công trường … – tại … | 179 | `Project.Name` + `Project.Location` (∨ `pe.DiaDiem`) | ◻ chưa nối |
| C5 | HĐ giao thầu chính số … ngày … | 182-183 | **0 cột, 0 bảng** | ❓ chưa xác định |
| D1 | Giá trị HĐ **(bao gồm VAT)** | 210 | `Contract.GiaTri` | ✅ **ĐÃ CHẢY** ⚠ xem cảnh báo |
| D2 | Bằng chữ: ……… đồng | 211 | derive từ D1 — **0 helper** (`bangChu`/`numberToWords` 0-hit; control-dương `giaTri` = 10 hit cùng file FE) | ◻ phải viết mới |
| D3 | Tỷ giá …. | 226 | 0 cột | ✍ gõ tay |
| D4 | **Bảng tổng hợp giá trị HĐ** — 7 cột `STT · NỘI DUNG CÔNG VIỆC · ĐƠN VỊ · KHỐI LƯỢNG · ĐƠN GIÁ · THÀNH TIỀN · GHI CHÚ` | 243-251 | **PE**: `Detail.NoiDung`·`DonViTinh`·`KhoiLuongThiCong` + `Quote.ChuaVat`·`Quote.ThanhTien`; đích `ThauPhuDetail.HangMuc/DonViTinh/KhoiLuong/DonGia/ThanhTien/GhiChu` khớp **6/7 cột 1-1** | ◻ **nguồn có, đứt 2 nhịp** |
| D5 | TỔNG CỘNG / VAT 10% / TỔNG SAU THUẾ | 251-253 | derive từ D4 | ◻ derive |
| E1 | Ngày hoàn thành **toàn bộ** | 257-258 | chỉ có per-dòng `ThauPhuDetail.ThoiGianHoanThanh`; **mốc tổng 0 cột** | ✍ gõ tay |
| E2 | Tạm ứng / thanh toán (ĐIỀU 8) | PHẦN 2 | `pe.PaymentTerms` JSON (`tamUng`,`thanhToanTam`,`quyetToan`,`hanMucCongNo`) | ◻ **đường đứt tại cầu KHKK** (§A) |
| E3 | Bảo hành (ĐIỀU 12) | PHẦN 2 | cùng JSON, khoá `baoHanh` | ◻ đường đứt |
| E4 | PHẦN 2 "ĐIỀU KIỆN CHUNG" 17 điều (phạt · nghiệm thu · bất khả kháng…) | mục lục 44-62 | — | ❓ **antiword không trích ra thân** (xem §E) |
| F1 | Số hợp đồng | header | codegen RG-001 | ✅ **ĐÃ CHẢY** |

**Tổng: 24 ô — 17 có nguồn / 5 phải gõ tay / 2 chưa xác định.** Trong 17 ô có nguồn, **chỉ 3 ô (C2·D1·F1) đã thực sự nằm sẵn trên hàng `Contracts`**; 14 ô còn lại là *nguồn nằm trong DB, đường chưa nối* — đúng chỗ owner muốn "điền nốt".

🔴 **Cảnh báo ngữ nghĩa D1:** `.doc` viết "**bao gồm thuế VAT**" (dòng 210), còn `Contract.GiaTri` = Σ `ApprovedAmount` mà `ApprovedAmount` bắt nguồn từ `Quote.ThanhTien` = KL × **`ChuaVat`** (`PurchaseEvaluationQuote.cs`). Bảng .doc lại có riêng dòng `VAT 10%` + `TỔNG SAU THUẾ` ⇒ **số hiện tại là CHƯA VAT**. Đổ thẳng vào ô "bao gồm VAT" là sai tiền — cần owner phán (hoặc thêm cột VAT).

## D. Pre-fill có sẵn

Chỗ DUY NHẤT trong repo tự gọi mình là pre-fill: `fe-user/src/pages/contracts/ContractCreatePage.tsx:547` (+ bản song sinh `fe-admin/.../ContractCreatePage.tsx:547`) —
> `// ===== [W5 S187 vế (88)] Ruột pre-fill: HĐ sinh từ cầu KHKK→HĐ mang sẵn 15 field do BE gán`

Đo ruột thật của nó (`ContractEditForm`, `:539-629`):
- **6 ô NHẬP**: `templateId` `:555` · `giaTri` `:556` · `tenHopDong` `:557` · `noiDung` `:558` · `budgetManualName` `:560` · `budgetManualAmount` `:561`.
- **1 dải CHỈ-ĐỌC** thêm @W5 (`:624-629`): Phòng ban · Người soạn · SLA · nguồn KHKK (`contract.source`, reverse-join `ContractFeatures.cs:748-785`).
- 🔴 `draftData: null` **hard-code** ở PUT `:582` (và POST `:366`) ⇒ **mỗi lần lưu Nháp là XOÁ TRẮNG** `DraftData` — kể cả HĐ đi đường PE cũ vốn có `PaymentTerms` trong đó (`CreateContractFromEvaluationFeatures.cs:104`). Cột `DraftData` hiện **0 consumer đọc** (grep BE: chỉ Create/Update/DTO; FE: 2 chỗ gán `null` + 1 khai type).

**Tái dụng được không:** *một phần*. Cái tái dụng được là **đường ống** (BE gán sẵn → FE hiển thị + cho sửa) và card `Source` reverse-join. Cái **KHÔNG** tái dụng được là **hình dạng**: đây là form 6-ô-header phẳng, không có khái niệm "thân văn bản"; `Contract.DraftData nvarchar(max)` là chỗ chứa JSON hợp lý nhất cho document-view (đã có sẵn cột, 0 migration) **nhưng phải gỡ 2 dòng `draftData: null` trước**, nếu không ô nào cũng bay khi bấm Lưu.

Nhập **thân HĐ** hôm nay = `ContractDetailsFeatures.cs:138-144` (`new ThauPhuDetail` → `db.ThauPhuDetails.Add`), **100% gõ tay**: không write-site nào tạo `ThauPhuDetail` từ PE/KHKK (grep `new ThauPhuDetail` = 2 hit: 1 CRUD tay + 1 `DbInitializer.cs:1283` seed demo). Bridge PE cũ khai thẳng lý do ở `CreateContractFromEvaluationFeatures.cs:18-20`: *"KHÔNG copy Details per-type automatically — user điền riêng sau khi HĐ gen, tránh mapping sai (PE detail schema ≠ 7 Contract detail schemas)"* ⇒ đây là **quyết định cũ có chủ đích**, YC-032 chính là lệnh đảo nó (ít nhất cho 2 loại TP).

## E. Chưa đo được

1. **PHẦN 2 "ĐIỀU KIỆN CHUNG" (17 ĐIỀU)** — mục lục có (`antiword` dòng 44-62) nhưng **thân không ra** trong 346 dòng trích (bản trích kết ở `ĐIỀU 5 ĐIỀU KHOẢN CHUNG` + chữ ký, dòng ~258-346). Chỉ đếm được **15 dòng chứa placeholder `…`/`....`** trong toàn bản trích ⇒ số ô THẬT của 002.02 **≥ 24, chưa chốt**. Lane 5 (`sub-investigator-codebase-5.md`) là người chốt con số này — bảng §C của tôi **là cận dưới**, không phải danh sách đủ.
2. **File 002.03 (Nhân công & Thiết bị, 591.360 B)** — tôi **không mở** (ngoài file-pin lane 3). Delta ô giữa 02 và 03 = ẩn số.
3. **Dữ liệu thật trên Dev/prod**: bao nhiêu HĐ đang đi đường KHKK (`Line.ContractId != null`), bao nhiêu PE có `PaymentTerms != null` — **chưa chạy sqlcmd** lượt này (lens là code-flow). Nếu wave định kế thừa `PaymentTerms` thì phải đo trước: cơ-chế đúng mà dữ-liệu rỗng là lớp đã lặp 7 lần trong dự án này.
4. **Bảng `ContractTemplate`/`form-engine`** — `templateId` có trong form nhập (`:555`, GET `/forms/templates?type=`) nhưng tôi chưa đo template hiện chứa gì; nếu 002.02/002.03 đã được nạp làm template thì đường document-view có thể đi qua đó thay vì dựng mới. **Cần lane khác / lượt sau xác nhận.**
