From 3d01333461395c3a17b5be38feb60de996775ba6 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 13 Aug 2026 22:42:59 +0700 Subject: [PATCH] wal: flush 20260813T2242 --- .claude/WAL.md | 8 +- .../run.md | 31 ++- .../spec-hd-thauphu-2form-13-08-2026.md | 147 +++++++++++++ .../sub-investigator-codebase-1.md | 153 ++++++++++++- .../sub-investigator-codebase-2.md | 116 +++++++++- .../sub-investigator-codebase-3.md | 105 ++++++++- .../sub-investigator-codebase-4.md | 97 +++++++- .../sub-investigator-codebase-5.md | 208 +++++++++++++++++- .../sub-reviewer-1.md | 25 +++ .../sub-reviewer-2.md | 25 +++ .../sub-reviewer-3.md | 22 ++ .../sub-reviewer-4.md | 23 ++ 12 files changed, 921 insertions(+), 39 deletions(-) create mode 100644 .claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/spec-hd-thauphu-2form-13-08-2026.md create mode 100644 .claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-1.md create mode 100644 .claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-2.md create mode 100644 .claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-3.md create mode 100644 .claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-4.md diff --git a/.claude/WAL.md b/.claude/WAL.md index b96235b2..b131d4b7 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -15,8 +15,12 @@ chain: [x] ctx-verifier #53 garble → resume-in-session lấy lại TRỌN 13 cờ (KHÔNG respawn); datum mới: lần đầu garble ở cửa /tiep [x] cờ-4 VÁ NGAY: HANDOFF (94) ghi "4 vai" over-byte → thật 5 (thiếu ctx-verifier 26.013 do seed @PAUSE-2 đẻ) — 3 nguồn khớp byte-exact; +khắc bẫy wc -m==wc -c (hoà-giải-giả) [x] YC-032 GHI SỔ NGAY trong lượt anh nói (tách HĐ Thầu phụ ×2 + form document-view + pre-fill 3 tầng + bản cứng khóa) — so-yeu-cau Khối 1 -[!] wf: wf_5f43c10f-506 /fable-clone invest 5 lane investigator-codebase — run .claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/ (run.md có taskList snapshot + 5 label); chết giữa chừng ⇒ /tiep §4 cắt-gọt theo sub-*.md đã có trên đĩa -[ ] sau invest: /fable-clone review (anh chỉ đích danh) → lead synthesize → spec 3 heading → fan-out Opus 5 MAX +[x] wf: wf_5f43c10f-506 invest 5/5 lane XONG — 4/5 mất return (empty_result) nhưng 5/5 GHI ĐĨA 85KB ⇒ 0 việc mất, thu hoạch bằng đọc đĩa (bằng chứng #2 cùng phiên: chống-#53 = thiết-kế ARTIFACT) +[x] lead refute P3 giải 2 mâu thuẫn bằng ĐO: M-1 lane5⟂lane2 → 66% chung/f03 +245 dòng riêng ⇒ component chung + 2 lắp ráp · M-2 PHẦN 2 "17 điều" → 002.04 chỉ có 5 ⇒ THIẾU 12 điều (CHẶN vế 2) +[x] spec 3 heading ghi: runs/2026-08-13-S194-yc032-hd-thauphu-2form/spec-hd-thauphu-2form-13-08-2026.md (W0-W6 + acceptance đo-được + §④ 6 điều chưa đo) +[!] wf: wf_71a58432-f90 /fable-clone review 4 lane reviewer (r1-taxonomy r2-formshape r3-prefill r4-plan) — chấm spec, chết giữa ⇒ /tiep §4 theo sub-reviewer-*.md +[!] 5 câu CHỜ ANH cho YC-032 (12 điều thiếu · in bằng gì · khoá 0-mig hay 1-mig · VAT · đánh số menu) — hỏi ở báo cáo lượt này +[ ] sau review: lead tổng hợp → fan-out Opus 5 MAX thi công theo wave [!] 7 câu (90)-(96) CHỜ CHỦ DỰ ÁN — 0 đáp; nhà CANONICAL = docs/HANDOFF.md khối (90)-(96) [!] residual-write 2 vai/2 phiên (score-counter S192 · score-count-auditor S189+S192) — GIỮ+KHAI theo LT3 bậc-2, chờ owner lật [!] NHIP-NO OVERDUE:deep — deep_every=1 từ YC-019(5); cặp H24 deep chạy ở bookend kế diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/run.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/run.md index 27c35c1b..eec689e5 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/run.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/run.md @@ -43,10 +43,27 @@ ## Verdict lane -| lane | verdict | ghi chú | -|---|---|---| -| 1 lens-taxonomy | *(đang chạy)* | | -| 2 lens-formshape | *(đang chạy)* | | -| 3 lens-prefill | *(đang chạy)* | | -| 4 lens-hardcopy | *(đang chạy)* | | -| 5 lens-formdoc | *(đang chạy)* | | +| lane | verdict | artifact | ghi chú | +|---|---|---|---| +| 1 lens-taxonomy | **A1 rẻ hơn — 13 site mã sống, ~6/13 by-construction, 0 migration nếu dùng chung `ThauPhuDetails`** | 13.738 B | 🔸 **mục C + D còn `(đang đo)`** — cháy trần lượt; lead đo bù (xem `synthesis §W1`) | +| 2 lens-formshape | **(i) HTML dựng tay làm TRỤC + (ii) docx làm đường XUẤT BẢN IN — 11 field create / 6+4 edit** | 17.570 B | ✅ trọn | +| 3 lens-prefill | **17 ô có nguồn / 5 gõ tay / 2 chưa xác định — nhưng CHỈ 3/17 đã thực chảy tới `Contracts`** | 13.398 B | ✅ trọn | +| 4 lens-hardcopy | **bản-cứng 0 migration (đã có `SealedCopy=3`) · KHÔNG khoá · đường IN đứt 3 chỗ** | 13.713 B | ✅ trọn | +| 5 lens-formdoc | **002.02 = 5 điều / 002.03 = 6 điều / chung 5 — "1 xương chung + 1 lớp delta"** | 26.555 B | ✅ trọn — lane DUY NHẤT return được | + +## 🔴 Sự-cố tầng RETURN (ghi làm vết, KHÔNG phải mất việc) + +`4/5 lane` báo `agents_empty_result` — *"subagent completed without calling StructuredOutput"*. **Việc KHÔNG mất**: 5/5 lane đã ghi file (85 KB tổng) nhờ luật **ép-ghi-khung-rỗng-lượt-1-2**. Lead thu hoạch bằng **đọc đĩa**, không bằng return. ⇒ đây là bằng chứng vận-hành thứ 2 (sau `ctx-verifier` cùng phiên) rằng **chống-#53 phải thiết-kế ARTIFACT, không phải viết prompt khéo hơn**. +🔸 Lane-5 tự khai 1 lệch: TASK body bảo ghi `-5.md`, footer RUN-TRACE bảo `-4.md` ⇒ trùng tên với lane-4. Lane theo TASK, không đẻ file thứ 2. Đây là `[carry:hmw-subfile-index-collision]` **tái hiện lần nữa** — footer đánh số theo index 0-based. + +## Lead refute @P3 — 2 mâu thuẫn phải giải bằng đo, không bằng chọn phe + +**M-1 — lane 5 ⟂ lane 2 về "2 form có phải sinh đôi không":** lane 5 nói *1 xương chung + delta*; lane 2 nói *KHÔNG phải sinh đôi, dựng 1-thân-1-cờ là **sai ngay từ giả thiết*** (căn cứ `diff | grep -c '^[<>]'` = 400). +**Thước thứ 3 của lead (so tập DÒNG, không so diff):** `f02` 200 dòng không-rỗng → **133 dòng (66%) có mặt nguyên văn trong `f03`**; `f03` 378 dòng không-rỗng → **245 dòng RIÊNG**. +⇒ **CẢ HAI ĐÚNG MỘT NỬA.** Xương chung có thật và lớn (133 dòng) — nhưng `f03` gấp ~1,9× nên "1 thân + 1 cờ boolean" không chứa nổi. **Chốt: component thân dùng chung + 2 bản LẮP RÁP riêng**, không phải 1-cờ, cũng không phải 2 bản chép tay độc lập. +🔸 Bài: `diff` đếm dòng **không phân biệt** *"khác cấu trúc"* với *"cùng cấu trúc, thêm phần"* ⇒ lane 2 kết luận vượt quá bằng chứng của chính nó. + +**M-2 — PHẦN 2 "17 ĐIỀU" đi đâu (lane 3 + lane 5 cùng nêu, không lane nào truy được vì ngoài file-pin):** +Mục lục CẢ 2 file khai `PHẦN 2 — ĐIỀU KIỆN CHUNG` gồm **ĐIỀU 1→17**, thân 2 file **không chứa**. Ứng viên duy nhất = `SOL-CCM-FO-002.04.v01 Dieu kien chung hop dong tron goi.docx`. +**Lead đo (gộp `run` như `DocxRenderer` làm, vì tách run ra chỉ thấy 1 hit — đúng bẫy repo đã tự khai):** bản repo và bản gốc FORM **byte-identical 192.608 B**; `62 w:p · 0 bảng · 40 đoạn có chữ`; đúng **5 ĐIỀU**: 1 Am hiểu · 2 Nội dung công việc · 3 Tiến độ-chất lượng · **4 = "Xử lý khi có tranh chấp" (mục lục gọi là ĐIỀU 16)** · **5 = "Sửa đổi, bổ sung" (mục lục gọi là ĐIỀU 17)**. +⇒ 🔴 **THIẾU 12 điều: 4 Đơn giá · 5 Tiện ích công trường · 6 Phát sinh · 7 Nghiệm thu · 8 TẠM ỨNG & THANH TOÁN · 9 Quyền-nghĩa vụ · 10 Phạt · 11 Chấm dứt · 12 BẢO HÀNH · 13 Bất khả kháng · 14 Thông tin-thông báo · 15 Quy tắc ứng xử.** Bảng tóm tắt PHẦN 1 lại trỏ sang ĐIỀU 2/3/8/10/12 của PHẦN 2 ⇒ **trỏ vào chỗ trống**. Đây là câu **CHẶN vế (2)**: "toàn bộ cái hợp đồng" hiện **không tồn tại đủ** trong tài liệu đã cấp. diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/spec-hd-thauphu-2form-13-08-2026.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/spec-hd-thauphu-2form-13-08-2026.md new file mode 100644 index 00000000..48392217 --- /dev/null +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/spec-hd-thauphu-2form-13-08-2026.md @@ -0,0 +1,147 @@ +# spec — HĐ Thầu phụ tách 2 + form document-view + pre-fill 3 tầng (YC-032) + +> Nguồn: `/fable-clone invest` 5 lane `investigator-codebase` (`wf_5f43c10f-506`) + lead refute/synthesize @P3 + 3 phép đo bù của lead. +> Ngày 2026-08-13 · phiên S194 · run `2026-08-13-S194-yc032-hd-thauphu-2form`. +> 🔴 File này do **LEAD ghi sau khi verify**, engine KHÔNG tự ghi. + +--- + +## ① Tính-năng / đề-bài + +Nguyên văn owner → `docs/governance/so-yeu-cau-chu-du-an.md` mã **`YC-032`**. Bốn vế: + +1. **Tách** `01. HĐ Thầu phụ` thành 2 menu ruột riêng — *Thầu phụ (Nhân công & Vật tư)* ↔ `SOL-CCM-FO-002.02.v01` · *Thầu phụ (Nhân công & Thiết bị)* ↔ `SOL-CCM-FO-002.03.v01`. +2. **Đổi khuôn nhập** sang **document-view cuộn từ trên xuống** đúng thân hợp đồng — *"người nhập giống như đang view từ trên xuống toàn bộ cái hợp đồng"*. +3. **Pre-fill 3 tầng**: Duyệt NCC (đầy) → KHKK (đầy) → người nhập điền **nốt phần thiếu** ⇒ hợp đồng trọn, **chỉ để in trình ký**. +4. **Chốt bằng bản cứng**: upload bản đã ký → **KHÓA**. + +**Phạm vi lượt này:** chỉ 2 HĐ đó. KHÔNG đụng 5 loại còn lại. + +--- + +## ② Cách implement + +### ②.0 — Nền đã có sẵn (đo được, đừng dựng lại) + +| Thứ | Trạng thái | Bằng chứng | +|---|---|---| +| Chỗ chứa "văn bản trọn" | ✅ `Contract.DraftData nvarchar(max)` từ **Mig 5**, BE round-trip đủ | `Contract.cs:25` · `ContractFeatures.cs:105,146,207,851` | +| Bản cứng | ✅ `AttachmentPurpose.SealedCopy=3` + `HasSealedCopy` derived + endpoint + trang FE + **3 test** | `ContractAttachment.cs:9` · `ContractFeatures.cs:384-391` · `ContractsController.cs:105` · `HardCopyTests` | +| Khoá khi rời Nháp | ✅ sửa HĐ chỉ mở ở `DangSoanThao` | `ContractFeatures.cs:167-168` | +| Enum persist | ✅ `int` trần, **0 CHECK constraint** (control-dương `HasMaxLength`=54 file) | `ContractConfiguration.cs:15` · sqlcmd Dev | +| Guard họ workflow | ✅ **fail-closed**: loại ngoài họ ⇒ **409 có câu tiếng Việt** | `ContractWorkflowGuards.cs:147-152` | +| Tiền lệ số-hoá form giấy | ✅ 2 lần: `PeDetailTabs.tsx` 3.613 dòng · `KhkkDetailContent.tsx` 1.140 dòng | đếm thật | +| Render docx + PDF | ✅ `DocxRenderer` 81 dòng + LibreOffice converter + endpoint | `DocxRenderer.cs` · `LibreOfficePdfConverter.cs` | + +### ②.1 — 🔴 Bốn cái BẪY phải né (mỗi cái đã cháy thật ở dự án này) + +1. **`WorkflowPolicy.cs:155` hỏng CÂM.** `switch` liệt 3 loại rồi `_ => "SkipCcm"`. Thêm enum mà quên thêm vào vế `or` ⇒ **rơi im lặng, mất phase CCM, không nổ**. *(Đối chiếu: `ContractWorkflowGuards.cs:149` cùng tình huống lại **409 to tiếng** — hai switch cạnh nhau, hai kiểu hỏng ngược nhau. Chỉ cái CÂM mới nguy.)* +2. **`TYPE_CODE_TO_INT` có 4 bản chép tay trong FE.** `Layout.tsx:165` `if (!typeInt) return null` ⇒ thiếu 1 entry = **leaf biến mất khỏi sidebar, im lặng** (gotcha #50, đã cháy @S190). +3. **`draftData: null` hardcode 4/4 site FE** (`ContractCreatePage.tsx:366,:582` ×2 app) ⇒ **mỗi lần bấm Lưu là xoá trắng thân HĐ**. Phải gỡ TRƯỚC khi dựng document-view, nếu không mọi ô điền đều bay. +4. **Menu-key vào `All` thì BẮT BUỘC có menu-row** — `MenuKeys.cs:147-148`: thiếu row ⇒ **Permission FK nổ ngay lần boot đầu**. Thêm 2 member họ = +2 key vào `All` = +8 policy ⇒ phải seed row **cùng lượt**. + +### ②.2 — Ba quyết định kiến trúc (lead chốt, có số đỡ lưng) + +**(a) Tách bằng A1 — thêm 2 `ContractType` (=8, =9), KHÔNG dùng cờ biến-thể.** +A1 ≈ 13 site mã sống, **~6/13 by-construction** (sửa 1 mảng nguồn là nở hết: `ContractTypeCodes` → menu/seed-loop; `Family.Members` → menu-key/policy/guard/message). A2 ít sửa chỗ cũ hơn **nhưng đẻ một trục mới (`?variant=`) mà 0 site nào đang canh** ⇒ drift cao, và menu/route/codegen/template đều đang khoá bằng `ContractType` nên A2 phải nới 8-10 chỗ để dựng trục song song. **0 migration** nếu 2 loại mới dùng chung `ThauPhuDetails` (enum int trần + 0 CHECK ⇒ `ef migrations add` sinh migration RỖNG, đúng tiền lệ Mig 62 `SupplierType +CaHai=6`). + +**(b) Document-view = HTML dựng tay làm TRỤC; docx-render chỉ làm đường XUẤT BẢN IN.** +Thứ nghe như "tái dùng" — render docx rồi overlay ô nhập — thực tế **thiếu 3 mảnh**: `DocxRenderer.cs:10` tự khai **không support vòng lặp bảng** (mà thân HĐ có bảng DANH MỤC N dòng) · 2 file gốc **chưa placeholder-hoá** (dạng `"Số: ………"`) · repo **0 viewer docx trong trình duyệt** (`contenteditable|tiptap|slate|quill` = 0 file, control-dương `useMemo` sống). ⇒ đi đường đó vẫn phải dựng UI nhập riêng = HTML **cộng thêm** một lớp. + +**(c) Thân HĐ = component dùng chung + 2 bản LẮP RÁP — không phải 1 cờ, cũng không phải 2 bản chép tay.** +Đo của lead (so **tập dòng**, không so `diff`): `f02` 200 dòng không-rỗng → **133 dòng (66%) nằm nguyên trong `f03`**; `f03` 378 dòng → **245 dòng riêng**. ⇒ xương chung có thật và lớn, nhưng `f03` gấp ~1,9× nên một `bool` không chứa nổi. +🔸 *Đây là chỗ 2 lane mâu thuẫn: lane 5 "1 xương chung + delta" ⟂ lane 2 "KHÔNG phải sinh đôi, 1-thân-1-cờ là sai từ giả thiết" (căn cứ `diff` = 400 dòng). Cả hai đúng một nửa — `diff` không phân biệt "khác cấu trúc" với "cùng cấu trúc, thêm phần".* + +**(d) Làm 1 app — `fe-user`.** 2 file `ContractCreatePage.tsx` hiện **giống nhau từng byte**, nhưng `fe-admin/Layout.tsx:234-249` đã ẩn trọn nhánh `Contracts` **có chủ đích** ⇒ mirror sang admin là công vô ích (màn sống-không-menu). + +### ②.3 — 🔴 CHẶN: "toàn bộ cái hợp đồng" hiện KHÔNG tồn tại đủ + +Mục lục **cả 2 file** khai `PHẦN 2 — ĐIỀU KIỆN CHUNG` gồm **ĐIỀU 1→17**; thân 2 file **không chứa**. Ứng viên duy nhất là `SOL-CCM-FO-002.04.v01 Dieu kien chung hop dong tron goi.docx`. +Lead đo (gộp `run` như `DocxRenderer` làm — tách run ra chỉ thấy 1 hit, đúng bẫy repo tự khai): bản repo ≡ bản gốc FORM **byte-identical 192.608 B** · `62 w:p · 0 bảng · 40 đoạn` · đúng **5 ĐIỀU**: 1 Am hiểu · 2 Nội dung công việc · 3 Tiến độ-chất lượng · **4 = "Xử lý tranh chấp" (mục lục gọi ĐIỀU 16)** · **5 = "Sửa đổi bổ sung" (mục lục gọi ĐIỀU 17)**. + +⇒ **THIẾU 12 điều:** 4 Đơn giá · 5 Tiện ích công trường · 6 Phát sinh · 7 Nghiệm thu · **8 Tạm ứng & thanh toán** · 9 Quyền-nghĩa vụ · 10 Phạt · 11 Chấm dứt · **12 Bảo hành** · 13 Bất khả kháng · 14 Thông tin-thông báo · 15 Quy tắc ứng xử. +Nặng thêm: **bảng tóm tắt PHẦN 1 trỏ sang ĐIỀU 2/3/8/10/12 của PHẦN 2** ⇒ trỏ vào chỗ trống. + +### ②.4 — Bản đồ pre-fill (nền của vế 3) + +**24 ô đo được ở `002.02`: 17 có nguồn / 5 gõ tay / 2 chưa xác định — nhưng CHỈ 3 ô đã thực sự chảy tới hàng `Contracts` hôm nay** (`TenHopDong` · `GiaTri` · `MaHopDong`). 14 ô còn lại là **nguồn nằm sẵn trong DB, đường chưa nối** — đúng chỗ owner muốn "điền nốt". + +| Cụm | Ô | Nguồn có sẵn | Trạng thái | +|---|---|---|---| +| Bên B | tên · địa chỉ · ĐT/Fax · số TK · MST · người đại diện+chức vụ | `Supplier.*` (6 cột) | ◻ chưa nối | +| Dự án | Chủ đầu tư · Công trình–tại | `Project.Investor` (Mig 48) · `Project.Name`+`Location` | ◻ chưa nối | +| Bảng giá | 7 cột `STT·Nội dung·ĐVT·Khối lượng·Đơn giá·Thành tiền·Ghi chú` | PE `Detail.*` + `Quote.ChuaVat/ThanhTien` → khớp `ThauPhuDetail` **6/7 cột 1-1** | ◻ nguồn có, **đứt 2 nhịp** | +| Thanh toán / bảo hành | tạm ứng · thanh toán · quyết toán · bảo hành | `pe.PaymentTerms` (JSON) | ◻ **regression**, xem dưới | +| Bên A | số TK · người đại diện | **0 bảng `CompanyProfile`** | ✍ gõ tay | +| Khác | ngày ký · tỷ giá · ngày hoàn thành tổng | 0 cột | ✍ gõ tay | +| Bằng chữ | "…… đồng" | **0 helper** (`numberToWords` 0-hit, control-dương `giaTri` 10 hit) | ◻ viết mới | + +🔴 **Hai lỗ nghiêm trọng phát hiện kèm:** +- **REGRESSION cầu KHKK:** cầu PE→HĐ **cũ** có `DraftData = pe.PaymentTerms` (`CreateContractFromEvaluationFeatures.cs:104`) và `BypassProcurementAndCCM` (`:103`); cầu **KHKK→HĐ mới đánh rơi cả hai** ⇒ HĐ đi đường KHKK **mất sạch điều khoản thanh toán** mà HĐ đi đường PE vẫn có. +- **Sai tiền tiềm tàng:** `.doc` ghi giá trị **"bao gồm thuế VAT"**, còn `Contract.GiaTri` = Σ`ApprovedAmount` ← `Quote.ThanhTien` = KL × **`ChuaVat`** ⇒ **số hiện tại CHƯA VAT**. Bảng `.doc` lại có riêng dòng `VAT 10%` + `TỔNG SAU THUẾ`. Đổ thẳng vào ô "bao gồm VAT" là **sai tiền** — cần owner phán. +- **Quyết định cũ bị đảo:** `CreateContractFromEvaluationFeatures.cs:18-20` khai chủ đích *"KHÔNG copy Details per-type automatically — tránh mapping sai"*. YC-032 chính là lệnh **đảo** nó (ít nhất cho 2 loại TP) — phải khai rõ khi thi công. + +### ②.5 — Bản cứng + khoá + in (vế 4) + +- **Lưu bản cứng: 0 migration** — máy đã đủ. +- 🔴 **Nhưng KHÔNG khoá gì cả**: upload không đổi phase, không set cờ; xoá đính kèm là **HARD delete** ⇒ `hasSealedCopy` là **cờ 2 chiều**, xoá file là "mở khoá" lại. Owner nói *"upload rồi khóa"* = mốc **1 chiều** ⇒ lệch. +- 🔴 **Cửa GĐ4 hẹp hơn tưởng**: trang bản cứng chỉ nạp HĐ **phase 9 `DaPhatHanh`** và chỉ 4 role (`Director·AuthorizedSigner·HrAdmin·CostControl`) — **người soạn HĐ không có trong đó**. +- **Đường IN: 0 print CSS** cả 2 app (`window.print|@media print` = 0 hit, control-dương 56 file có `useState`). Render docx thì **đứt 3 chỗ**: 0 map `Contract → Dictionary` · 2 file `.docx` chưa có trên đĩa (seed `DbInitializer.cs:3833-3838` **đã khai** 002.02/002.03 nhưng `IsActive = fileExists` ⇒ **row tồn tại, cờ chết**) · `DocxRenderer` 0 vòng lặp bảng. +- ⚠️ Thả file vào sau **không tự bật cờ**: `:3861 if (existingCodes.Contains(formCode)) continue` ⇒ wave nào thả file phải kèm **UPDATE `IsActive=1`**. + +--- + +## ③ Checklist theo WAVE + +> Acceptance mỗi wave phải **ĐO ĐƯỢC**. Vai gợi ý; lead điều phối. + +### W0 — CHỜ OWNER (chặn một phần, không chặn hết) +- [ ] 5 câu ở §"Chờ anh" của báo cáo. **W1/W2 chạy song song được** với W0 (không phụ thuộc đáp án); **W3/W4 phụ thuộc**. + +### W1 — Tách 2 loại (BE + menu + FE dict) · `implementer-backend` + `implementer-frontend` +- [ ] +2 member `ContractType` (=8, =9) + **+2 vào vế `or` của `WorkflowPolicy.cs:155`** ← bẫy CÂM +- [ ] +2 tuple `ContractApprovalWorkflowFamily.Members` ⇒ họ 7→9 (menu-key + policy tự nở by-construction) +- [ ] +2 `MenuKeys.ContractTypeCodes` · +2 nhánh `ContractCodeGenerator.cs:27` · +2 nhãn `ContractExcelExporter.cs:36` + `WorkflowAdminFeatures.cs:72` +- [ ] Seed: menu-row `Ct_*` ×2 + `Hdc_*` ×2 + nhãn tiếng Việt 2 chỗ (`DbInitializer.cs:2054` + `:2375`) + **seed 2 quy trình `HD-*` mới** +- [ ] FE: **cả 4 bản** `TYPE_CODE_TO_INT` (2 `Layout.tsx` + `ApprovalWorkflowsV2Page.tsx:172` + `WorkflowsPage.tsx:77`) +- [ ] **Acceptance:** `dotnet ef migrations add` sinh migration **RỖNG** (chứng enum không đụng schema) · boot Dev **không nổ Permission FK** · 2 leaf mới **hiện trên sidebar** (chống gotcha #50) · pin chéo quy trình ⇒ **409** · test hiện có vẫn **697 PASS** + +### W2 — Khung document-view (2 thân) · `frontend-designer` + `implementer-frontend` +- [ ] **Gỡ `draftData: null` 4/4 site TRƯỚC** ← nếu không mọi ô đều bay khi Lưu +- [ ] Component thân dùng chung (bìa · mục lục · căn cứ · Bên A/B · ĐIỀU 2 định-nghĩa · ĐIỀU 4 · điều khoản chung · bảng tóm tắt 8 dòng đầu) +- [ ] 2 bản lắp ráp: `002.02` (5 điều, bảng giá nhúng ĐIỀU 3, 14 dòng tóm tắt) · `002.03` (6 điều +Thuế, bảng ĐIỀU 3 rỗng→PL02, 16 dòng tóm tắt, **4 phụ lục**) +- [ ] Ô lặp **bind 1 nguồn**: Số ×2 · Hạng mục ×2 · Địa điểm ×2 · **tên Bên B ×3** · Dự án ×2 (f03) +- [ ] Lưu vào `Contract.DraftData` (JSON) — **0 migration** +- [ ] **Acceptance:** nhập → Lưu → F5 → **đủ ô còn nguyên** (chống bẫy #3) · ô máy-điền ⟂ ô phải-gõ **phân biệt được bằng mắt** · `npm run build` ×1 app sạch + +### W3 — Pre-fill nối 14 ô · `implementer-backend` (phụ thuộc W0) +- [ ] Nối `Supplier.*` 6 ô + `Project.Investor/Name/Location` 2 ô +- [ ] Bảng giá 7 cột: PE `Detail`+`Quote` → `ThauPhuDetail` (đọc **vượt tầng** về PE qua `plan.PurchaseEvaluationId` — đường **đã mở sẵn** ở `CreateContractFromSigningPlanFeatures.cs:199-200`) +- [ ] **Vá regression**: cầu KHKK gán lại `DraftData = pe.PaymentTerms` + `BypassProcurementAndCCM` +- [ ] Helper `numberToWords` (tiền → chữ) +- [ ] **Acceptance:** tạo HĐ từ KHKK thật trên Dev → đếm ô tự điền **≥14** · so từng ô với nguồn **byte-exact** · **ĐO DỮ LIỆU TRƯỚC**: `COUNT(PE có PaymentTerms != null)` — cơ-chế đúng mà dữ-liệu rỗng đã lặp nhiều lần ở dự án này + +### W4 — Đường IN · `implementer-frontend` (phụ thuộc W0 câu 2) +- [ ] Print CSS cho document-view (`@media print`: ẩn chrome, ngắt trang theo ĐIỀU, giữ bảng không vỡ) +- [ ] **Acceptance:** Ctrl+P ra PDF **khớp bố cục bản Word** — đối chiếu bằng mắt 2 trang đầu + trang bảng giá + +### W5 — Khoá bản cứng · `implementer-backend` + `database-agent` (phụ thuộc W0 câu 3) +- [ ] Theo đáp án owner: **(0-mig)** guard mềm dựa `HasSealedCopy` · **(1-mig)** `Contracts.SealedAt` + `SealedByUserId` = mốc 1 chiều, audit được +- [ ] Nới cửa GĐ4 nếu owner muốn người soạn thấy (nay 4 role đóng + phase 9) +- [ ] Mirror endpoint `view` inline từ KHKK (`ContractSigningPlansController.cs:254`) — HĐ hiện thiếu +- [ ] **Acceptance:** upload bản cứng → sửa HĐ ⇒ **409** · xoá đính kèm ⇒ **không mở khoá lại** · test-before cho cả 2 ca + +### W6 — Verify + deploy · `reviewer` + `cicd-monitor` +- [ ] `reviewer` adversarial toàn diff · `front-end-reviewer-style` + `back-end-reviewer-style` +- [ ] **Acceptance:** test ≥697 PASS · cicd 6/6 · bundle hash đổi ×2 app · **runtime-verify console 0 lỗi** (bài gotcha #92: mọi phép đo chỉ đo FILE thì 2 lần đều lọt) + +--- + +## ④ Điều CHƯA đo được (khai thật, đừng đọc thành đã phủ) + +1. **Mục C+D của lane-1** (`sub-investigator-codebase-1.md`) còn `(đang đo)` — cháy trần lượt. Lead **đã đo bù mục C** (guard fail-closed) nhưng **mục D "rủi ro" thì chưa ai làm**. +2. **Prod có HĐ Thầu phụ sống không** — chưa đo lượt này. Memory S160 ghi "prod Contracts = 0 active" nhưng đã **cũ**; phải đo lại nếu dùng để quyết migration/backfill. +3. **Chất lượng convert `.doc → .docx`** của LibreOffice với 2 file 516KB/591KB (bảng phức tạp, header 3 tầng merge) — **chưa chạy thử**. +4. **`soffice` trên prod**: lane-2 báo `Test-Path` = True; **máy dev không có** ở đường mặc định ⇒ nhánh IN server-side sẽ chết câm ở local. `Pdf:SofficePath` **0-hit trong mọi `*.json`** = ghost-wire. +5. **`antiword` cắt UTF-8 ở biên wrap dòng** (f02 3 dòng, f03 5 dòng; `U+FFFD` = 0 nên **trông như sạch**, chỉ decode mới lộ) ⇒ 🔴 **CẤM chép thẳng text antiword vào code**; câu chữ chuẩn phải lấy qua Word/LibreOffice. +6. **Số ô THẬT** có thể > 24 — bảng §②.4 là **cận dưới**, chưa gộp ô nằm trong 61/113 dòng bảng. diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-1.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-1.md index 7c485f37..fe43f383 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-1.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-1.md @@ -12,11 +12,160 @@ ## A. Hai phương án tách + đếm consumer -(đang đo) +### A.0 — Mẫu số: `ContractType` có bao nhiêu consumer (đo, kèm lệnh) + +``` +rg -l "ContractType" src fe-admin/src fe-user/src --glob '!**/node_modules/**' => 133 file +rg -l "ContractType" ... --glob '!**/Persistence/Migrations/**' => 59 file (mã SỐNG) +rg -l "ContractType" src/Backend/SolutionErp.Infrastructure/Persistence/Migrations => 74 file (Designer/Snapshot auto-gen, KHÔNG sửa tay) +rg -c "ContractType" tests => 14 file +rg -c "HopDongThauPhu" --glob '!**/node_modules/**' => 28 file (13 mã sống + 13 test/doc/runs) +``` +⇒ Mẫu số THẬT phải sửa tay = **59 file mã sống**, KHÔNG phải 133. 74 file Designer là snapshot EF sinh lại. + +### A.0-bis — 3 hằng-số CỐT nằm ở đâu (file PIN) + +| Trục | File:line | Ruột | +|---|---|---| +| Enum loại HĐ | `src/Backend/SolutionErp.Domain/Contracts/ContractType.cs:3-12` | 7 member, `HopDongThauPhu = 1` | +| Họ quy trình duyệt V2 | `src/Backend/SolutionErp.Domain/Contracts/ContractApprovalWorkflowFamily.cs:33-42` | mảng 7 tuple `(Type, Code, ShortName)`; `CodeFor` `:48-53`; `KeyToken` `:59-62` | +| Policy 9-phase V1 | `src/Backend/SolutionErp.Domain/Contracts/WorkflowPolicy.cs:153-157` | `DefaultPolicyNameFor` — TP/GK/NCC → `"Standard"`, **`_ => "SkipCcm"`** (default-catch-all) | + +🔴 **Phát hiện quan trọng #1 — `WorkflowPolicy.cs:153-157` KHÔNG cần sửa nếu A1 tách đúng:** +switch dùng `or` liệt 3 loại rồi `_ => SkipCcm`. Enum mới =8/=9 mà KHÔNG thêm vào vế `or` sẽ **rơi im lặng +vào SkipCcm** (mất phase CCM) — đây là *ghost-default*, không nổ, không cảnh báo. ⇒ A1 **BẮT BUỘC** thêm +2 member vào dòng `:155`, nếu quên thì lỗi câm. + +### A.1 — PHƯƠNG ÁN A1: thêm 2 `ContractType` member (=8, =9) + +Danh sách site liệt-đủ-7-loại (đo bằng `rg -n "HopDongNguyenTacDichVu" src fe-admin/src fe-user/src --glob '!**/Persistence/Migrations/**'` = 16 hit / 10 file — member cuối làm proxy "chỗ nào liệt đủ 7"): + +| # | file:line | Việc phải làm khi +2 type | +|---|---|---| +| 1 | `Domain/Contracts/ContractType.cs:12` | +2 member (=8, =9) | +| 2 | `Domain/Contracts/ContractApprovalWorkflowFamily.cs:33-42` | +2 tuple ⇒ họ 7→**9** mã (kéo theo C) | +| 3 | `Domain/Contracts/WorkflowPolicy.cs:155` | +2 vào vế `or` (nếu không: ghost-default SkipCcm) | +| 4 | `Infrastructure/Services/ContractCodeGenerator.cs:27` | +2 nhánh prefix mã HĐ (RG-001) | +| 5 | `Infrastructure/Reports/ContractExcelExporter.cs:36` | +2 nhãn | +| 6 | `Application/Contracts/WorkflowAdminFeatures.cs:72` | +2 nhãn (Designer V1) | +| 7 | `Application/Contracts/ContractDetailsFeatures.cs:109,460` (+ `:263,:403` mẫu) | 🔴 dispatch **bảng chi tiết per-type** — xem A.1-bis | +| 8 | `Infrastructure/Persistence/DbInitializer.cs:1008,1157,1169,1247,1390` | seed WF V1 + demo data + menu `Ct_*` | +| 9 | `Domain/Identity/MenuKeys.cs` | sinh key `Ct_*` ×4 + `AwV2_Hd*` — xem A.1-ter | +| 10 | `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx:59-65` | map `ct → HD-*` (7 entry) | +| 11 | `fe-admin/src/lib/contractWorkflowFamily.ts:46` | `CONTRACT_WF_FAMILY` 7 member | +| 12 | `fe-{user,admin}/src/components/Layout.tsx` | accordion 7 nhóm HĐ | +| 13 | `fe-{user,admin}/src/pages/contracts/ContractCreatePage.tsx` (7 hit mỗi bên) | dropdown loại | + +### A.1-bis — 🔴 PHÁT HIỆN LỚN NHẤT: 7 BẢNG CHI TIẾT VẬT LÝ per-type (không phải TPH) + +`ls src/Backend/SolutionErp.Domain/Contracts/Details/` = **8 file** (`ContractDetailBase.cs` + 7 subclass). +`ContractDetailBase.cs:6-11` khai nguyên văn: *"Mỗi loại HĐ có 1 bảng riêng (Option B theo user decision)… +**Không dùng EF TPH/TPT inheritance** — mỗi subclass tự khai báo entity ToTable riêng."* +`ContractDetailsConfiguration.cs` = 7 class config, 7 `ToTable(...)`: `ThauPhuDetails` `:14` · `GiaoKhoanDetails` `:38` +· `NhaCungCapDetails` `:64` · `DichVuDetails` `:92` · `MuaBanDetails` `:118` · `NguyenTacNccDetails` `:147` · `NguyenTacDvDetails` `:172`. + +Dispatch 7-nhánh CỨNG (đo `rg -n "case ContractType" ContractDetailsFeatures.cs`): +- `ContractDetailsFeatures.cs:43,54,65,76,87,98,109` (nhánh đọc/GetDetails) +- `ContractDetailsFeatures.cs:436,440,444,448,452,456,460` (nhánh thứ 2) +- **21 handler** `EnsureContractType(…, ContractType.X, …)` — `:136,157,178,200,221,242,263` (Create) + `:289,307,326,345,364,384,403` (Update) + mẫu ở `:263/:403`. + +⇒ **A1 sinh câu hỏi schema:** 2 loại Thầu phụ mới **dùng chung bảng `ThauPhuDetails`** (rẻ, 0 migration, nhưng +phá bất-biến "1 type = 1 bảng" đang có) **hay đẻ 2 bảng mới** (`ThauPhuVatTuDetails` / `ThauPhuThietBiDetails` +⇒ **+2 CreateTable, migration 74, bảng 97→99**, + ~6 handler CRUD ×2 + 2 DbSet + 2 nav-collection trên `Contract`). +🔴 Câu này **owner/lead phải chốt**, không phải suy được từ mã. + +### A.1-ter — Menu/policy: `Ct_*` sinh động (RẺ) ⟂ `AwV2_Hd*` vào `All` (ĐẮT) + +- `MenuKeys.cs:40-41` `ContractTypeCodes = ["ThauPhu","GiaoKhoan","NhaCungCap","DichVu","MuaBan","NguyenTacNcc","NguyenTacDv"]` + + factory `:88-91` `Ct_{code}` / `_List` / `_Create` / `_Pending`. + `MenuKeys.cs:69` khai rõ: *"`Ct_*` / `Pe_*` leaf **CỐ Ý NGOÀI `All`** — sinh động qua factory"* ⇒ **+2 typeCode + = +8 menu-row, 0 policy mới**. Rẻ. +- `MenuKeys.cs:151` `ApprovalWorkflowContractLeaf(token) => "AwV2_Hd{token}"`, `:155-157` `ContractWorkflowMenuKeys` + **sinh TỪ `ContractApprovalWorkflowFamily.Members`**, và `:275` `.. ContractWorkflowMenuKeys` **NẰM TRONG `All`**. + ⇒ +2 member họ = **+2 menu-key vào `All` = +8 policy** (4 action × 2) và `MenuKeys.cs:147-148` cảnh báo cứng: + *"Vào `All` thì **BẮT BUỘC** có menu-row trong `SeedMenuTreeAsync` — thiếu row = Permission FK **nổ ngay lần + boot đầu** (invariant T6; test `ContractCatalogTests:445` bắt đúng ca này)."* + ⇒ **by-construction**: sửa `ContractApprovalWorkflowFamily.Members` là menu-key + policy tự nở — KHÔNG mirror tay, + nhưng PHẢI seed menu-row cùng lượt nếu không **boot nổ**. + +### A.1-quater — Vòng seed menu + FE route: chuỗi phái sinh, **thêm 1 typeCode là chạy hết** + +- Seed menu-row: `DbInitializer.cs:2192` `foreach (var code in MenuKeys.ContractTypeCodes)` ⇒ `:2198-2206` + đẻ **1 group + 6 leaf** (`WfView/List/Create/Pending/Approved/Deleted`) + `:2197` thêm 1 leaf `Hdc_*` (Bảng cứng). + Vòng thứ 2 `:2212`, vòng grant `:2578-2580`. +- Nhãn tiếng Việt **PHẢI thêm tay** (2 chỗ): `DbInitializer.cs:2054` (`["ThauPhu"]="01. HĐ Thầu phụ"`) và + `:2375` (`Hdc_ThauPhu`) + `:2382-2388` (dict reconcile 7 nhãn `01.`→`07.`). + 🔴 Thêm 2 loại ⇒ **đánh số lại 01..09** hoặc chèn `01a/01b` — nhãn có SỐ THỨ TỰ là quyết định của owner + (số này đã UPDATE prod theo UAT anh Kiệt @S159, `:2048-2049`). +- FE route: `fe-user/src/components/Layout.tsx:16-24` + `fe-admin/src/components/Layout.tsx:19` — dict + `TYPE_CODE_TO_INT` **chép tay 7 entry mỗi bên** (+2 bản nữa ở `ApprovalWorkflowsV2Page.tsx:172` và + `WorkflowsPage.tsx:77` ⇒ **4 bản sao trong FE**). `Layout.tsx:161-181` regex + `^Ct_([^_]+)_(List|Create|Pending|WfView|Approved|Deleted)$` → `TYPE_CODE_TO_INT[code]`; **`:165` `if (!typeInt) return null`** + ⇒ thiếu 1 entry trong dict = **leaf DROP IM LẶNG khỏi sidebar (gotcha #50)** — đúng ca đã cháy @S190. + +### A.2 — PHƯƠNG ÁN A2: giữ 1 `ContractType=1` + cờ/biến-thể (sub-type) + +**Trạng thái hiện tại:** `Contract.cs` **KHÔNG có** trường biến-thể nào (đo `rg -n "public " Contract.cs` — 11 field +nghiệp vụ, gần nhất là `TemplateId` `:17` "Template dùng để render" và `BypassProcurementAndCCM` `:21`). +⇒ A2 phải **đẻ mới** 1 trong 2: +- (a) **cột enum mới** `ContractVariant?` trên `Contracts` ⇒ **1 AddColumn, Mig 74, 0 bảng mới** (rẻ hơn A1-có-bảng), hoặc +- (b) **tái dụng `Contract.TemplateId`** → `ContractTemplates` (`ContractTemplateConfiguration.cs:11,16,24`: + `ContractType` nullable + có index; `FormFeatures.cs:33` filter `x.ContractType == request.ContractType`) + ⇒ **0 cột mới, 0 migration** — 2 form = 2 row `ContractTemplates` cùng `ContractType=1`. + +**Nhưng chi phí A2 KHÔNG rẻ hơn ở tầng TAXONOMY**, vì mọi trục phái sinh đang khoá bằng `ContractType`: + +| Trục | Hôm nay khoá bằng | A2 phải làm gì | +|---|---|---| +| Menu key + route | `MenuKeys.ContractTypeCodes` → `TYPE_CODE_TO_INT[code]` → `?type=N` | thêm 2 pseudo-code **không có** int ⇒ `Layout.tsx:165` trả `null` ⇒ **mất leaf**; phải nới regex + đẻ param mới (`?variant=`) ở **2 Layout + trang List + trang Create + Hdc** | +| Quy trình duyệt | `ContractApprovalWorkflowFamily.CodeFor(ContractType)` | 2 biến-thể **buộc dùng chung `HD-TP`** — muốn khác lộ trình duyệt thì phải đổi **chữ ký** `CodeFor`/guard (xem C) | +| Bảng chi tiết | 7 bảng vật lý per-type | dùng chung `ThauPhuDetails` (hợp lý) | +| Mã HĐ RG-001 | `ContractCodeGenerator.cs:27` switch theo type | 2 biến-thể chung prefix `HĐTP` — **có thể là điều owner muốn**, hoặc không | +| Form template | `ContractTemplate.ContractType` (nullable int) | **KHÔNG phân biệt được 2 biến-thể** nếu chỉ có `ContractType=1` ⇒ chọn form phải đi qua `TemplateId`/`FormCode`, không qua type | + +**Đếm thô:** A1 ≈ **13 site mã sống** (bảng A.1) + 4 bản `TYPE_CODE_TO_INT` — nhưng **~6/13 là by-construction** +(sửa 1 mảng nguồn là nở hết: `ContractTypeCodes`, `ContractApprovalWorkflowFamily.Members` → menu-key/policy/seed-loop). +A2 ≈ **1 cột (hoặc 0)** + **~8-10 site ĐẺ TRỤC MỚI** (`?variant=` xuyên 2 FE app + BE filter + guard) — +tức A2 **ít sửa chỗ cũ hơn nhưng đẻ một khái niệm mới chưa từng có trong hệ**, và trục mới đó +**0 site nào đang canh** ⇒ rủi ro drift cao hơn. ## B. Enum persist + migration -(đang đo) +### B.1 — CÓ persist, dạng `int` thuần, **4 cột** trên 4 bảng + +| Nơi khai | file:line | Cột DB | +|---|---|---| +| `Contract.Type` | `Domain/Contracts/Contract.cs:11` | `Contracts.Type` | +| `WorkflowDefinition.ContractType` | `Domain/Contracts/WorkflowDefinition.cs:17` | `WorkflowDefinitions.ContractType` (V1) | +| `WorkflowTypeAssignment.ContractType` | `Domain/Contracts/WorkflowTypeAssignment.cs:11` | `WorkflowTypeAssignments.ContractType` (V1 override) | +| `ContractTemplate.ContractType` **nullable** | `Domain/Forms/ContractTemplate.cs:11` | `ContractTemplates.ContractType` (null = phụ lục) | + +Cấu hình EF: `Configurations/ContractConfiguration.cs:15` `b.Property(x => x.Type).HasConversion();` +(+ `WorkflowDefinitionConfiguration.cs`, `WorkflowTypeAssignmentConfiguration.cs`, `ContractTemplateConfiguration.cs` — 2 hit mỗi file). +Migration gốc: `20260421051248_AddContractsWorkflow.cs:33` → `Type = table.Column(type: "int", nullable: false)`. +Snapshot: `ApplicationDbContextModelSnapshot.cs:882-883` `b.Property("Type").HasColumnType("int")`. + +### B.2 — 🟢 TRẢ LỜI: thêm giá-trị enum mới **KHÔNG cần migration**, giá-trị cũ `=1` **KHÔNG bị đụng** + +Bằng chứng đo: +``` +rg -c "HasCheckConstraint" src/Backend --glob '!**/Migrations/**' => 0-hit (exit=1) + CONTROL DƯƠNG cùng cây: rg -c "HasMaxLength" …/Configurations => 54 file ⇒ thước SỐNG, 0-hit là vắng thật + +sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Dev -Q " + SELECT c.name,t.name FROM sys.columns c JOIN sys.types t … WHERE object_id=OBJECT_ID('Contracts') AND c.name='Type'" + => Type | int | is_nullable=0 + "SELECT COUNT(*) FROM sys.check_constraints WHERE parent_object_id=OBJECT_ID('Contracts')" => 0 + "SELECT Type, COUNT(*) FROM Contracts GROUP BY Type" => 1:2 · 2:1 · 3:1 · 4:1 · 5:1 · 6:1 · 7:1 (9 HĐ Dev) +``` +⇒ Cột là `int` trần, **0 CHECK constraint**, EF `HasConversion` chỉ đổi CLR↔int lúc đọc/ghi. +Thêm `= 8`/`= 9` vào enum C# ⇒ **snapshot EF KHÔNG đổi** ⇒ `dotnet ef migrations add` sinh migration **RỖNG** +(đúng khuôn Mig 62 `SupplierType +CaHai=6` append-only đã làm 1 lần rồi). +2 HĐ đang có `Type=1` giữ nguyên nghĩa "Thầu phụ (cũ)" — **không cần backfill** trừ khi owner muốn ép +9 HĐ cũ phải chọn 1 trong 2 biến-thể mới (đó là quyết định NGHIỆP VỤ, không phải ràng buộc kỹ thuật). + +🔴 **Migration CHỈ phát sinh nếu** chọn A1 + đẻ bảng chi tiết riêng cho 2 loại mới (A.1-bis) — khi đó ++2 `CreateTable` ⇒ Mig 74, bảng 97→99. Nếu 2 loại mới **dùng lại `ThauPhuDetails`** ⇒ **0 migration**. ## C. 7 quy trình HD-* + guard diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-2.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-2.md index 12674307..faa71a97 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-2.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-2.md @@ -6,24 +6,128 @@ ## 0. Khung -(đang đo — append dần, KHÔNG gom cuối) +Kết luận sớm (chi tiết §A-§D): +1. Form tạo HĐ hiện tại = **form field rời rạc 2 cột** (grid `grid-cols-2`), KHÔNG tab / KHÔNG accordion, chia **3 khối** (Header · Ngân sách · bảng Chi tiết) — cách "document-view" xa **toàn bộ tầng render**, KHÔNG có gì tái dụng ở tầng UI. +2. Repo **CÓ form-engine thật** (`{{placeholder}}` docx + LibreOffice convert) nhưng nó là engine **XUẤT RA FILE**, không phải engine **NHẬP LIỆU trong trình duyệt**. Chỗ gần "ô nhập theo spec" nhất = `fe-admin/src/components/DynamicForm.tsx` (150 dòng) — nhưng nó cũng vẫn là **lưới 2 cột**, không phải thân văn bản. +3. Ống dữ liệu để chứa "văn bản trọn" **ĐÃ CÓ SẴN**: `Contract.DraftData nvarchar(max)` (JSON) sống từ Mig 5, BE round-trip đủ, **FE hardcode `null` 4/4 site** ⇒ lớp "cơ-chế đúng, dữ-liệu không có" lần nữa. +4. fe-admin có màn **y hệt** (file byte-identical) nhưng đã bị **ẩn khỏi menu admin có chủ đích** ⇒ mirror 2 app là **KHÔNG cần**, làm 1 app (fe-user). ## A. Form hiện tại -(pending) +### A1. Cấu trúc màn (fe-user/src/pages/contracts/ContractCreatePage.tsx, 868 dòng) + +`wc -l fe-user/src/pages/contracts/ContractCreatePage.tsx` → **868** + +Layout = **2 panel** (`:132` `grid ... lg:grid-cols-[320px_1fr]`): +- Panel 1 (`:134-248`) aside: ô tìm kiếm + list HĐ theo `?type=` + nút "Thêm mới". +- Panel 2 (`:251-286`) main: 3 trạng thái — empty (`:252`) · `ContractHeaderForm` create (`:261`) · `ContractEditForm` edit (`:274`). + +**KHÔNG có tab, KHÔNG có accordion, KHÔNG có stepper** trong màn nhập. Kiểm: `grep -c "Tabs\|accordion\|` cuộn dọc, các ô xếp lưới 2 cột trong 2 `card` trắng** (`:393` Header card, `:483` Chi tiết card). Không tab/accordion. + +### A3. Khoảng cách tới "document-view" + +Form hiện tại KHÔNG mang bất kỳ mảnh nào của **thân hợp đồng**: 0 hit "ĐIỀU" trong toàn bộ 3 file pin. +`grep -c "ĐIỀU \|Điều [0-9]" fe-user/src/pages/contracts/ContractCreatePage.tsx fe-user/src/components/contracts/ContractDetailContent.tsx fe-user/src/pages/contracts/MyContractsPage.tsx` → **0 / 0 / 0**. +Control dương cùng thước (chứng regex sống): `git grep -c "Điều [0-9]" -- 'docs/*.md'` → `docs/_archive/forms-spec-raw.md:1` ⇒ 0-hit ở FE là **vắng thật**, không phải thước hỏng. + +### A4. 🔴 Ống chứa "văn bản trọn" ĐÃ CÓ, FE bỏ trống + +`Contract.DraftData` = `nvarchar(max)` JSON, khai từ `src/Backend/SolutionErp.Domain/Contracts/Contract.cs:25` (`// JSON field values (render template)`), cấu hình `ContractConfiguration.cs:21`, sinh từ **Mig 5** `20260421051248_AddContractsWorkflow.cs:45`. +BE **round-trip đủ**: Create nhận (`ContractFeatures.cs:27` + gán `:105`) · Update nhận + gán (`:146`, `:207`) · trả ra DTO (`ContractDtos.cs:46`) · list/detail select (`ContractFeatures.cs:851`). +FE **hardcode null 4/4 site**: `fe-user/…/ContractCreatePage.tsx:366` + `:582` và `fe-admin/…:366` + `:582` (`git grep -n "draftData"` — 4 hit FE, đều `draftData: null`). +Ca **duy nhất** có ghi thật: `CreateContractFromEvaluationFeatures.cs:104` `DraftData = pe.PaymentTerms` (carry-forward điều khoản thanh toán từ PE). +⇒ Dựng document-view **không cần migration** cho chỗ chứa dữ liệu: `DraftData` là chỗ đặt JSON các ô điền trong thân HĐ. ## B. Có sẵn gì gần document-view -(pending) +### B1. form-engine = engine XUẤT FILE, không phải engine NHẬP + +Đo: `wc -l` các file +- `src/Backend/SolutionErp.Infrastructure/Forms/DocxRenderer.cs` → **81** +- `…/Forms/XlsxRenderer.cs` → 47 · `…/Forms/FormRenderer.cs` → 24 · `…/Forms/LibreOfficePdfConverter.cs` → **96** +- `src/Backend/SolutionErp.Application/Forms/FormFeatures.cs` → **310** · `…/Forms/Services/IFormRenderer.cs` → 15 + +Làm được tới đâu: +- **Thay `{{fieldName}}`** trong docx: regex `\{\{([a-zA-Z0-9_\.]+)\}\}` (`DocxRenderer.cs:14`), gộp run trước khi replace nên bắt được placeholder bị Word cắt (`:56-77`), quét cả header/footer (`:39-42`), bảng ăn theo vì `Descendants` (`:79`). +- **KHÔNG có vòng lặp bảng**: `// Không support {{#loop}}...{{/loop}} (table lặp)` — `DocxRenderer.cs:10`. ⇒ **danh mục hạng mục N dòng của HĐ Thầu phụ KHÔNG render được** bằng engine hiện tại nếu đi đường docx-render. +- Endpoint: `POST /api/forms/templates/{id}/render` (`FormsController.cs:25-28`) → `RenderTemplateCommand(TemplateId, Dictionary)` (`FormFeatures.cs:59`). Nhận **dictionary phẳng string→string**, không nhận mảng. +- **PDF**: `IPdfConverter` = LibreOffice headless `soffice --headless --convert-to` (`IPdfConverter.cs:4-9`), impl `LibreOfficePdfConverter.cs` (96 dòng); DI `Infrastructure/DependencyInjection.cs:36` `AddSingleton()`. Upload `.doc/.xls` cũ **tự convert** sang `.docx/.xlsx` (`FormFeatures.cs:188-239`) ⇒ **2 file `.doc` của owner nạp vào được** mà không cần Word. + +### B2. `DynamicForm` — thứ gần "ô nhập theo spec" nhất, nhưng vẫn là lưới + +`git grep -l "DynamicForm"` → **2 file, cả 2 đều fe-admin**: `fe-admin/src/components/DynamicForm.tsx`, `fe-admin/src/pages/forms/FormsPage.tsx`. +- Spec = JSON `Record`, 6 kiểu ô (`DynamicForm.tsx:23-30`). +- Render = `div className="grid grid-cols-1 gap-3 md:grid-cols-2"` (`:68`) ⇒ **lưới 2 cột**, mỗi ô có nhãn + mã field mono góc phải (`:85`). **Không có chỗ cho văn xuôi giữa các ô.** +- Chỗ dùng duy nhất = trang quản trị mẫu (preview spec), `FormsPage.tsx` (510 dòng) — **KHÔNG** nối vào màn nhập HĐ. +- 0-hit ở fe-user: `git grep -rn "DynamicForm" -- 'fe-user/'` → 0. Control dương cùng thước: `git grep -c "ContractDetailsTab" -- 'fe-user/'` → 4 file có hit ⇒ thước sống. + +### B3. Kho mẫu trên đĩa — 2 file owner cần thì CHƯA có trong repo + +`ls src/Backend/SolutionErp.Api/wwwroot/templates/` → **5 file**: +`SOL-CCM-FO-002.01.v01 Bang kiem tra hop dong.docx` · `002.04.v01 Dieu kien chung hop dong tron goi.docx` · `002.05.v01 Hop dong Giao khoan.docx` · `002.07.V01 Don dat hang.xlsx` · `SOL-CCM-RG-001.v02 QD ma so hop dong.docx` +⇒ **002.02 và 002.03 (2 file owner chỉ định) 0-hit trong repo** (`git grep -n "002\.02\|002\.03" -- '*.cs' '*.tsx'` → 0 hit mã nguồn; chỉ hit trong `.claude/auto-memory/project_sol_ccm_sp002_signing_matrix.md:41` = ghi chú). Control dương: `002.05`/`002.07` **có** trên đĩa cùng thư mục ⇒ thư mục đúng, file thiếu thật. +⚠️ Kèm bằng chứng cũ đáng tin: memory `office-document` S107 đã đo **2 template hiện có = 0 placeholder `{{}}`** (mẫu gốc dạng "Số: ………"), tức kể cả file có sẵn cũng **chưa placeholder-hoá**. + +### B4. Nghề "render 1 văn bản dài trong trình duyệt" — 0 tiền lệ + +`git grep -rln "contenteditable\|dangerouslySetInnerHTML\|tiptap\|slate\|quill\|ProseMirror\|react-quill" -- 'fe-user/' 'fe-admin/'` → **0 file**. +Control dương cùng thước: `git grep -rln "useMemo" -- 'fe-user/src'` → nhiều chục file ⇒ thước sống, kết luận **repo chưa từng có rich-text/editor nào**. +`package.json` 2 app: 0 gói editor/docx (`git grep -n "\"docx\"\|editor\|quill\|tiptap" -- 'fe-user/package.json' 'fe-admin/package.json'` → 0 hit; control dương: `"@tanstack/react-query"` có hit). ## C. 1 app hay 2 app -(pending) +- **File màn tạo HĐ 2 app hiện GIỐNG NHAU TỪNG BYTE**: `diff fe-admin/src/pages/contracts/ContractCreatePage.tsx fe-user/…` → **0 dòng khác** (cả 2 đều 868 dòng). +- Route **cả 2 app đều có**: `fe-admin/src/App.tsx:82-84` (`/contracts`, `/contracts/new`, `/contracts/:id`) · `fe-user/src/App.tsx:64-65` (+ `:73` `/contracts/workflow-matrix` chỉ fe-user). +- 🔴 **Nhưng menu admin đã ẩn trọn nhánh HĐ có chủ đích**: `fe-admin/src/components/Layout.tsx:234-249` `ADMIN_HIDDEN_KEYS` chứa `'Contracts'` (+ `PurchaseEvaluations`, `KeHoachKyKet`, `HopDongCung`), và `:255` `isAdminHidden = ADMIN_HIDDEN_KEYS.has(key) || key.startsWith('Ct_')`. Chú thích ngay trên đó khai rõ: route + quyền **còn nguyên**, chỉ bớt lối đi trên menu. +- fe-admin **thiếu** `MyContractsPage.tsx` và `ContractListPanel.tsx` (`git ls-files 'fe-admin/src/pages/contracts/*'` → 4 file: ContractCreate/ContractDetail/ContractsList/HardCopies; fe-user → 4 file khác gồm MyContracts). + +⇒ **Trả lời (C): làm 1 app — fe-user.** Mirror sang fe-admin là **công vô ích** (màn sẽ sống-không-menu, đúng tiền lệ KHKK admin ghi ở memory S187). Nếu vẫn muốn admin xem được thì chỉ cần **1 dòng**: bỏ `'Contracts'` khỏi `ADMIN_HIDDEN_KEYS` — nhưng đó là quyết định của owner, **KHÔNG** phải hệ quả kỹ thuật của YC-032. ## D. 3 cách dựng + đánh đổi -(pending) +### D0. Đo THÂN 2 file .doc (số để cân 3 phương án) + +`antiword -m UTF-8.txt '…002.02….doc' > /tmp/f02.txt` → **346 dòng** · `grep -c "ĐIỀU\|Điều [0-9]"` → **45** · `grep -c "^|"` (dòng bảng) → **61** · `grep -c "\.\.\.\.\|………"` (chỗ trống chấm) → **10** +Cấu trúc: mục lục (`:33-40`) → **PHẦN 1 ĐIỀU KIỆN RIÊNG** + **PHẦN 2 ĐIỀU KIỆN CHUNG** với **ĐIỀU 1→17** (`:44-62`) → `DANH MỤC HỢP ĐỒNG` (`:30`) → bảng có `TỔNG CỘNG` + `TỔNG CỘNG (SAU THUẾ)` (`:249`,`:251`) → `a) BẢNG TÓM TẮT CÁC ĐIỀU KIỆN CƠ BẢN` (`:312`) → `d) BẢNG TỔNG HỢP GIÁ TRỊ HỢP ĐỒNG (theo file đính kèm)` (`:340`). +002.03: **538 dòng**, 46 hit ĐIỀU. `diff f02 f03 | grep -c "^[<>]"` → **400 dòng khác nhau**. +🔴 ⇒ **2 form KHÔNG phải sinh đôi**: 002.03 dài hơn 55%, khác 400 dòng ⇒ dựng "1 thân + cờ nhân-công-vật-tư / nhân-công-thiết-bị" là **sai ngay từ giả thiết**; phải là **2 thân riêng** (dùng chung khung + chung máy ô-điền). + +### D1. Ba cách, cân theo ĐÚNG thứ repo đang có + +| | (i) HTML dựng tay | (ii) docx template render + overlay ô nhập | (iii) rich-text editor | +|---|---|---|---| +| **Repo đã có gì** | Tiền lệ số-hoá form giấy: `PeDetailTabs.tsx` **3.613 dòng** (Section 1→5 + mục `a. / b. / c.`, `grep -no "Section [0-9]"` 20+ hit) · `KhkkDetailContent.tsx` **1.140 dòng**. Chỗ chứa: `Contract.DraftData` sẵn | `DocxRenderer.cs` 81 dòng (`{{field}}`, gộp run, header/footer) · `LibreOfficePdfConverter.cs` 96 · endpoint `/forms/templates/{id}/render` · **prod CÓ soffice**: `ssh vietreport-vps … Test-Path 'C:\Program Files\LibreOffice\program\soffice.exe'` → **True** | **0 gói** — `git grep -rln "contenteditable\|tiptap\|slate-react\|quill\|ProseMirror"` trên `fe-user/src`+`fe-admin/src` → **0 file** (control dương `useMemo` → 18 file); `package.json` 0 hit editor/docx (control dương `@tanstack/react-query` `fe-user/package.json:18`) | +| **Cái ĐƯỢC** | Đúng thứ owner tả (cuộn từ trên xuống, ô điền NẰM TRONG thân); ô điền = React state ⇒ pre-fill 3 tầng (PE→KHKK→người nhập) là **gán giá trị mặc định**, hiển thị được "chỗ nào máy điền / chỗ nào anh phải điền"; validate từng ô; 0 migration (ghi `DraftData`) | Bản in **giống hệt bản Word gốc 100%** (đúng font/dấu/bảng ISO); tái dùng máy có sẵn; upload `.doc` cũ tự convert (`FormFeatures.cs:188-239`) | Sửa được câu chữ HĐ | +| **Cái MẤT / rủi ro thật** | Phải gõ tay thân 2 HĐ (346 + 538 dòng, 61 dòng bảng) thành TSX — khối lượng ngang `KhkkDetailContent`; **bản in ≠ bản Word** trừ khi làm thêm bước xuất; `fe-user` **chưa có hạ tầng in**: `git grep -rn "window.print\|@media print"` trên `fe-user/src` → **0 hit** (control dương: 80 file có `className`) | 🔴 **DocxRenderer KHÔNG có vòng lặp bảng** — `DocxRenderer.cs:10` `// Không support {{#loop}}...{{/loop}} (table lặp)` ⇒ `DANH MỤC HỢP ĐỒNG` N dòng **không render được**, phải viết thêm engine. 🔴 **2 file gốc chưa placeholder-hoá** (mẫu dạng "Số: …………", 10 chỗ chấm) và **chưa nằm trong repo** (`wwwroot/templates/` chỉ 5 file, không có 002.02/.03). Overlay ô-nhập lên docx trong trình duyệt = **không có đường nào trong repo** (0 viewer docx) ⇒ thực chất vẫn phải dựng UI nhập riêng ⇒ **thành (i) + thêm 1 lớp** | Sai bài: HĐ mẫu là văn bản **CỐ ĐỊNH**, chỉ có chỗ trống được điền; cho sửa tự do = mất khoá bản-cứng vế (4) + mất pre-fill có kiểm soát + đẻ phụ thuộc mới; 0 tiền lệ trong repo | + +### D2. Đề xuất (lens hình-dạng form) + +**(i) HTML dựng tay làm TRỤC — mượn (ii) làm đường XUẤT BẢN IN.** +Lý do đứng trên số, không trên lý thuyết: chỗ chứa dữ liệu **đã có** (`DraftData`, 0 migration); tiền lệ số-hoá form giấy **đã có 2 lần** (PE 3.613 dòng · KHKK 1.140 dòng) nên đội biết làm; còn (ii) — thứ nghe như "tái dùng" — thực tế **thiếu 3 mảnh** (vòng lặp bảng · placeholder trong file · viewer/overlay trong trình duyệt) nên chỉ là (i) cộng thêm việc. (ii) **vẫn dùng**, nhưng ở **đúng chỗ nó mạnh**: khi người nhập bấm "In", đẩy `DraftData` qua `/forms/templates/{id}/render` → docx → (`LibreOfficePdfConverter`) → PDF **giống bản Word gốc**, prod đã có soffice. +⇒ Việc dựng phải tách 2 nhánh song song: **nhánh NHẬP** (TSX 2 thân) ⟂ **nhánh IN** (placeholder-hoá 2 file .doc + thêm loop cho bảng danh mục). Nhánh IN có thể chậm 1 nhịp mà owner vẫn xem được hình. ## E. Chưa đo được -(pending) +1. **Số ô điền THẬT của mỗi thân HĐ** — antiword làm phẳng bảng nên đếm "chỗ trống" chỉ ra **10** dòng có dấu chấm; con số ô thật (kể cả ô trong 61 dòng bảng + ô ẩn trong bảng tóm tắt `:312`) phải mở bằng công cụ đọc .doc có cấu trúc (agent `office-document`) — **KHÔNG suy từ số 10**. +2. **Bao nhiêu ô trong 2 thân đó đã có sẵn dữ liệu từ PE/KHKK** (vế (3) pre-fill 3 tầng) — cần map từng ô ↔ field; lane này chỉ đo được **ống**: bridge KHKK→HĐ đã gán sẵn 15 field (`CreateContractFromSigningPlanFeatures.cs:186-207`, dẫn theo chú thích `ContractCreatePage.tsx:547-551`), chưa đối chiếu với thân .doc. +3. **`Pdf:SofficePath` không khai trong bất kỳ `*.json`** (`git grep -n -i "soffice\|LibreOffice" -- '*.json'` → 0 hit) ⇒ chạy bằng đường mặc định `C:\Program Files\LibreOffice\program\soffice.exe`; prod Test-Path=True nhưng **máy dev của người thi công có thể không có** ⇒ nhánh IN sẽ chết câm ở local, không phải lỗi mã. +4. **Prod có HĐ Thầu phụ nào đang sống không** — chưa đo lượt này (memory S160: prod Contracts = 0 active/0 deleted, **cũ**, phải đo lại nếu dùng để quyết migration). +5. **Menu**: tách `01. HĐ Thầu phụ` thành 2 ruột (vế 1) chạm seed menu + `ContractType` — **ngoài lăng-kính lane này** (lane menu/BE đo). diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-3.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-3.md index aa2457b9..b2d12b6e 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-3.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-3.md @@ -2,28 +2,119 @@ - **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 -- **trạng thái:** `KHUNG RỖNG — đang đo` (append dần, không gom cuối) +- **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 -*(đang đo)* +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 -*(đang đo)* +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 -*(đang đo)* +`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 -*(đang đo)* +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 -*(đang đo)* +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 -*(đang đo)* +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.** diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-4.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-4.md index 3f930d91..2b5acc33 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-4.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-4.md @@ -1,23 +1,106 @@ # sub-investigator-codebase-4 — LENS: BẢN CỨNG + KHOÁ + IN (YC-032) > Run: 2026-08-13-S194-yc032-hd-thauphu-2form · Agent: investigator-codebase (lane 4) -> Trạng thái: KHUNG RỖNG (lượt 1) — append dần, chống #53. > Vùng pin: `src/Backend/SolutionErp.Domain/ContractSigningPlans/**` + `src/Backend/SolutionErp.Application/ContractSigningPlans/**` +> Trạng thái: ĐANG ĐỔ RUỘT (append liên tục, chống #53). Mọi số = từ lệnh vừa chạy. ## 0. Khung -(đang đo) + +VERDICT sớm: **bản-cứng + khoá = 0 migration (đã có sẵn trên Contract)**; **đường IN = có máy nhưng ĐỨT ở 3 chỗ**. +- Cơ chế "bản cứng 1-MỐC" KHÔNG nằm ở KHKK — nó land thẳng trên **Contract** (`AttachmentPurpose.SealedCopy = 3`). +- KHÔNG có khoá nào bật khi upload bản cứng (đo ở §B). +- Repo CÓ form-engine thật (OpenXml + ClosedXML + LibreOffice) nhưng **0 đường nối Contract → template data**. ## A. Bản-cứng 1-MỐC của KHKK -(đang đo) + +**Kết luận: "1-MỐC" của W7 hiện thực trên CONTRACT, không phải trên KHKK. KHKK 0 field bản-cứng.** + +| Vế | Đo được | Evidence | +|---|---|---| +| Entity/field | KHÔNG có cột mới. Mốc = **tồn tại ≥1 attachment purpose 3** | `Domain/Contracts/ContractAttachment.cs:9` `SealedCopy = 3, // Scan HĐ đã đóng dấu ở phase DangDongDau` | +| Cờ đọc ra | `HasSealedCopy` DERIVED, append CUỐI record + có default | `Application/Contracts/Dtos/ContractDtos.cs:18-25` (`bool HasSealedCopy = false`) | +| Nơi tính | EXISTS trong LIST query | `Application/Contracts/ContractFeatures.cs:384-391` — `x.c.Attachments.Any(a => a.Purpose == AttachmentPurpose.SealedCopy)` | +| Endpoint upload | tái dùng upload đính kèm HĐ sẵn có | `Api/Controllers/ContractsController.cs:105` `[HttpPost("{id:guid}/attachments")]` | +| Menu GĐ4 | 7 leaf `Hdc_*` dưới root `HopDongCung` | `Infrastructure/Persistence/DbInitializer.cs:2197`, nhãn `:2375-2381` ("01. HĐ Thầu phụ - Bảng cứng") | +| FE trang | có ×2 app | `fe-admin/src/pages/contracts/HardCopiesPage.tsx:204` + `fe-user/...:204` (`c.hasSealedCopy ? …`), panel upload `:259 HardCopyPanel` | +| Test | 3 test PIN tên theo spec | `tests/…/Application/ContractHardCopyTests.cs:22` (T3 `HardCopyBadge_DerivesFromSealedCopyAttachment`), `:316`, `:344` | + +**KHKK 0-hit bản-cứng/khoá** — `grep -n "SealedCopy|Sealed|Khoa|Lock|readonly|khoá|khóa"` trên `Domain/ContractSigningPlans/` ⇒ **No matches found**. Control dương cùng thước: cùng regex chạy trên `src/Backend` ⇒ 13 hit (`ContractAttachment.cs:9`, `ContractDtos.cs:18-25`, `ContractFeatures.cs:384-391`, `DbInitializer.cs:2197/2375-2381/3658`) ⇒ thước sống, vắng ở KHKK là **vắng thật**. + +**Trạng thái sau upload = KHÔNG ĐỔI GÌ. KHÔNG KHOÁ.** +- `UploadContractAttachmentCommandHandler` chỉ check contract tồn tại rồi Add + log changelog — **0 guard phase, 0 set cờ, 0 đổi Phase**: `Application/Contracts/ContractAttachmentFeatures.cs:64` (`?? throw new NotFoundException`), `:80` (`Purpose = request.Purpose`), `:84` (log), `:89` (return DTO). Không có dòng nào đụng `contract.Phase`. +- Xoá đính kèm = **HARD delete, không soft** (`ContractAttachmentFeatures.cs:139-144`; `ContractAttachment : BaseEntity` không có `IsDeleted` — `ContractAttachment.cs:13`) ⇒ **cờ `hasSealedCopy` ĐẢO NGƯỢC ĐƯỢC**: xoá file là badge tắt, HĐ "mở khoá" lại. Mốc 1-chiều mà owner mô tả ("upload = xong") hiện là **cờ 2-chiều**. + +`ContractSigningPlanAttachment` (KHKK) có 3 purpose: `DossierScan=1 / PlanDocument=2 / ApprovalAttachment=3 / Other=99` — `Domain/ContractSigningPlans/ContractSigningPlanAttachment.cs:9-15`. **Không** có purpose "bản cứng đã ký". + +**Cửa vào GĐ4 hẹp hơn tưởng — 2 rào:** +1. Trang bản cứng CHỈ nạp HĐ **phase 9 DaPhatHanh**: `fe-admin/src/pages/contracts/HardCopiesPage.tsx:80-81` (`phase: ContractPhase.DaPhatHanh`) + test T1 `ListHardCopies_ReturnsOnlyContracts_InDaPhatHanhPhase`. ⇒ HĐ vừa nhập xong ở document-view (phase 2) **không xuất hiện** ở GĐ4 cho tới khi chạy hết 9 phase. +2. Vai được thấy = danh sách ĐÓNG 4 role: `ContractFeatures.cs:427-428` `HardCopyActorRoles = [Director, AuthorizedSigner, HrAdmin, CostControl]` (test `:518` khai rõ "danh sách ĐÓNG"). Người soạn HĐ (Drafter) **không** nằm trong đó. + +**ĐO DỮ LIỆU (chống 'cơ-chế đúng, dữ-liệu không có'):** LocalDB `SolutionErp_Dev` ⇒ `SELECT COUNT(*) FROM ContractAttachments` = **0** (tổng, mọi purpose) ⇒ chưa từng có bản cứng nào được upload ở Dev; `SUM(Purpose=3)` trả **NULL** (tập rỗng, không phải 0-do-lọc). Máy đủ, đường đi chưa ai bước. ## B. Mirror sang HĐ + migration -(đang đo) + +**Không cần mirror — đã ở sẵn trên HĐ. 0 cột mới, 0 migration cho vế "lưu bản cứng".** +Chỉ cần thêm nếu owner muốn **KHOÁ THẬT** (xem 3 lựa chọn dưới). + +Khoá hiện có trên Contract (phase-based, KHÔNG liên quan bản cứng): +- `Application/Contracts/ContractFeatures.cs:167-168` — `if (entity.Phase != ContractPhase.DangSoanThao) throw new ConflictException("Chỉ được sửa HĐ khi ở phase Đang soạn thảo.")` ⇒ HĐ rời Nháp là header đã cấm sửa. Với YC-032, **document-view nhập liệu chỉ mở ở DangSoanThao** là hành vi sẵn có, không phải dựng mới. +- Xoá HĐ chặn từ `DangInKy` trở lên (memory S160 `delete-guard-numeric`, `Phase >=`). + +3 đường cho "KHOÁ sau upload bản cứng", đắt dần: +1. **0-mig, 0 cột** — dựa `HasSealedCopy` đã derive: thêm guard `if (contract.Attachments.Any(a => a.Purpose == SealedCopy)) throw Conflict` vào `UpdateContractCommand` + `DeleteAttachment` (chặn gỡ chính bản cứng). Điểm yếu: cờ vẫn 2-chiều, ai xoá attachment là mở lại; và EXISTS phải load thêm. +2. **1 cột, 1 mig** — `Contracts.SealedAt datetime2?` (+ optional `SealedByUserId`), set 1 lần ở upload purpose=3, guard `SealedAt != null`. Mốc thành **1 chiều thật**, audit được ai/khi nào, không phụ thuộc file còn/mất. Đây là đường khớp chữ owner nhất ("upload bản cứng = xong"). +3. **1 cột + 1 phase** — extend enum `ContractPhase`. 🔴 **KHÔNG khuyến nghị**: owner đã chốt 1-MỐC = **0 enum-extend** (skill `contract-workflow` §KHKK GĐ4); và `DeleteContract` guard so SỐ `Phase >=` (S160) ⇒ chèn số mới là đụng guard số học. + +⇒ Nếu lead cần "khoá thật + audit": **1 migration, 1-2 cột** (`SealedAt`, `SealedByUserId`). Nếu chấp nhận khoá mềm: **0 migration**. ## C. Đường IN -(đang đo) + +**Có máy render, nhưng ĐỨT 3 chỗ. Không có đường "in HĐ này" hôm nay.** + +CÓ sẵn (không phải dựng mới): +- `Infrastructure/Forms/DocxRenderer.cs:12-51` — OpenXml 3.5.1, thay placeholder `{{fieldName}}` (regex `:14`), phủ cả header/footer (`:39-42`). +- `Infrastructure/Forms/XlsxRenderer.cs` (ClosedXML 0.105) · `Infrastructure/SolutionErp.Infrastructure.csproj:8-9` (2 package đã cài). +- `Infrastructure/Forms/LibreOfficePdfConverter.cs:11-87` — `LibreOfficeDocumentConverter` shell `soffice --headless --convert-to`; DI singleton `Infrastructure/DependencyInjection.cs:36`. +- Endpoint: `Api/Controllers/FormsController.cs:25` `POST templates/{id}/render` · `:32` `POST templates/{id}/export-pdf` (→ `FormFeatures.cs:103-125`, `converter.ConvertAsync(rendered.Content, tpl.Format, "pdf")`). +- Upload template CÒN tự convert `.doc → .docx`: `FormFeatures.cs:175-240` (`converter.ConvertAsync(srcBytes, srcExt, targetExt)`). + +3 chỗ ĐỨT (mỗi cái là 1 wave): +1. 🔴 **Không ai nạp dữ liệu HĐ vào render.** `grep -rn "RenderTemplateCommand(|ExportTemplatePdfCommand("` trên `src/Backend` ⇒ **4 hit, đều là khai báo + FormsController**: `FormsController.cs:28`, `:35`, `FormFeatures.cs:59`, `:103`. **0 hit từ `Contracts/`**. Data dictionary hiện do người dùng gõ tay ở `fe-admin/src/pages/forms/FormsPage.tsx:55` (`DynamicFormValues`) rồi POST thẳng (`:65`). ⇒ Muốn in HĐ phải viết **map Contract → Dictionary** mới. +2. 🔴 **2 file form của YC-032 CHƯA có trên đĩa.** Seed `DbInitializer.cs:3833-3838` ĐÃ khai `SOL-CCM-FO-002.02` + `002.03` → `ContractType.HopDongThauPhu`, nhưng `IsActive = fileExists` (`:3865-3876`) và `ls src/Backend/SolutionErp.Api/wwwroot/templates` ⇒ **5 file** (002.01, 002.04, 002.05, 002.07 xlsx, RG-001) — **002.02/002.03/002.06 VẮNG**. Comment tự khai `:3826-3827`: *"3 file .doc (FO-002.02/03/06) chưa convert sẽ IsActive=false"*. ⇒ 2 row template tồn tại nhưng **chết cờ**, đúng lớp "cơ-chế đúng, dữ-liệu không có". +3. 🔴 **DocxRenderer không lặp bảng.** `DocxRenderer.cs:9-11` tự khai: *"Không support `{{#loop}}…{{/loop}}` (table lặp) — Phase 2 iteration 2"*. Mà thân 2 form có bảng **DANH MỤC HỢP ĐỒNG** N dòng (lead đã đọc bằng antiword) ⇒ render docx hiện **không đổ được N dòng hạng mục**. + +Rủi ro thứ 4 (ghost-wire): `Pdf:SofficePath` **0-hit trong `appsettings*.json`** (control dương: `appsettings.json` 37 dòng, không rỗng) ⇒ chạy fallback hard-code `C:\Program Files\LibreOffice\program\soffice.exe` (`LibreOfficePdfConverter.cs:15-18`). Lead đã đo: máy dev **không có soffice ở đường đó**. Prod chưa đo (§E). + +**Print CSS = 0.** `grep -rn "window.print|@media print|print:"` trên `fe-admin/src` + `fe-user/src` (`*.tsx`,`*.css`) ⇒ **0 hit**. Control dương cùng cây: 56 file fe-admin có `useState` ⇒ cây có được quét. + +**Đánh đổi (dựa trên cái repo ĐÃ có):** + +| | (a) Print CSS trang document-view | (b) Render .docx từ template | +|---|---|---| +| Vốn sẵn | 0 (phải dựng từ đầu, nhưng chỉ là CSS) | ~70% (renderer + converter + endpoint + seed row đã có) | +| Chặn cứng | không | cần đủ CẢ 3: file .docx thật + map data + loop bảng | +| Khớp YC-032(2) | **Rất khớp** — document-view CHÍNH LÀ thứ đem in; WYSIWYG tuyệt đối, người nhập thấy sao in vậy | phải giữ 2 bản trình bày (HTML + docx) đồng bộ → drift | +| Font/dấu tiếng Việt | trình duyệt lo | phụ thuộc font trên máy chủ IIS/LibreOffice | +| Ra PDF | Ctrl+P → "Save as PDF" (client), 0 hạ tầng | cần soffice sống trên prod (chưa chứng) | +| Nợ về sau | muốn file .docx sửa tay thì vẫn phải làm (b) | đã có file để lưu trữ/ký | + +⇒ Cho phạm-vi-lượt-này (2 HĐ, "chỉ cần in ra trình ký"): **(a) print CSS** là đường ngắn nhất và khớp lời owner nhất; (b) để wave sau, và khi làm thì việc TỐN NHẤT không phải renderer mà là **3 chỗ đứt trên**. ## D. Dossier tái dụng -(đang đo) + +**Dossier KHÔNG phải kho file — nó là CHECKLIST. Cái tái dụng được là bộ endpoint attachment của KHKK, không phải dossier.** + +- `Domain/ContractSigningPlans/ContractSigningPlanDossierItem.cs:27-40` — `Kind` (MauVatLieu/Shopdrawing/Khac `:7-12`), `Status` (ChuaNop/DaNop/TvgsDuyet/TvgsBac `:17-23`), `TvgsName`, `TvgsResultAt`, `Note`. **0 cột file** (không FileName/StoragePath/ContentType). CRUD chỉ mở ở `{DangSoanThao, TraLai}` (comment `:26`). +- File nối vào dossier bằng **loose-Guid trên bảng attachment**: `ContractSigningPlanAttachment.ContractSigningPlanDossierItemId` (`:28`), CỐ Ý không FK vật lý vì **multiple cascade paths** (`:19-24`). +- Bộ endpoint attachment KHKK (đây mới là thứ mirror được): `ContractSigningPlansController.cs:198` upload · `:226` upload-approval (BE **ép cứng** Purpose=ApprovalAttachment, `:241`) · `:245` download · `:254` **view** (inline) · `:268` delete. +- 🔴 **HĐ hiện THIẾU cửa `view` inline** — `ContractsController.cs` chỉ có `:105` upload + `:130` delete (download nằm ở chỗ khác/không cùng khuôn). Muốn xem bản cứng ngay trên trang mà không tải về ⇒ mirror `:254` của KHKK. +- Authz: KHKK per-action thật (`Read/Create/Update/Delete` — `ContractSigningPlansController.cs:29,37,100,109,154,…`, ≥23 dòng `[Authorize(Policy = "KeHoachKyKet.*"]`). Contract thì **class `[Authorize]` trần** + chỉ `:32` `Contracts.Read` cho 1 action; `:87` có comment tự khai *"CỐ Ý CHƯA gắn policy per-action cho verb này"* ⇒ nếu mirror upload bản cứng sang HĐ thì **không thừa hưởng authz per-action** (gotcha #82/#44 còn treo — W8 owner HOÃN). ## E. Chưa đo được -(đang đo) + +- **soffice trên PROD**: chưa đo (SSH + đo file trên VPS ngoài lượt này; memory S134/S148: SSH session chết khi SQL-client load — nhưng chỉ với sqlcmd, `Test-Path` thì chưa thử). ⇒ đừng hứa export PDF server-side trước khi đo. +- **Prod có row ContractTemplate 002.02/003 chưa** (seed idempotent theo FormCode; nếu prod seed từ lâu thì row đã có với `IsActive=0`, và **seed sẽ KHÔNG tự bật lại khi ta thả file .docx vào** — `:3861 if (existingCodes.Contains(formCode)) continue`). ⇒ Wave nào thả file phải kèm **UPDATE IsActive=1** (hoặc sửa qua `PUT /forms/templates/{id}`), nếu không file có mà cờ vẫn tắt. +- Chất lượng convert `.doc → .docx` của LibreOffice với 2 file 516KB/591KB (bảng phức tạp): chưa chạy thử. +- `FieldSpec` JSON của template (`ContractTemplate.cs:16`) — chưa đọc file nào có FieldSpec thật; chưa rõ có bộ field chuẩn nào để tái dụng. diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-5.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-5.md index 7f3d136e..bacdcc89 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-5.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-investigator-codebase-5.md @@ -1,25 +1,217 @@ # sub-investigator-codebase-5 — LENS: RUỘT 2 FILE FORM (.doc) > Lane 5 / S194 / YC-032. Nguồn sự thật = 2 file `.doc` gốc ở `D:/Dropbox/CONG_VIEC/SOLUTION/FORM/`. -> CHỈ ĐỌC (owner đang mở 002.02 trong Word — có `~$` lock). Trích bằng `antiword -m UTF-8.txt`. +> CHỈ ĐỌC (owner đang mở 002.02 trong Word — có `~$` lock, 162 B). Trích bằng `antiword -m UTF-8.txt`, 0 stderr. ## 0. Khung -(đang điền) + +| | 002.02 "…trọn gói nhân công, **vật tư**" | 002.03 "…trọn gói nhân công, **thiết bị**" | +|---|---|---| +| Bytes | 516.608 | 591.360 | +| mtime | 2026-03-03 16:16 | 2026-03-19 09:28 | +| Dòng text trích | **346** | **538** | +| ĐIỀU trong THÂN | **5** | **6** | +| Dòng bảng (`^\|`) | 61 | 113 | +| Ảnh (`[pic]`) | 1 (logo bìa) | 1 (logo bìa) | +| Phụ lục rời | **0** | **4** (PL01→PL04) | + +Lệnh tái lập (đổ ra scratchpad, KHÔNG ghi đè gốc): +``` +antiword -m UTF-8.txt "D:/…/SOL-CCM-FO-002.02.v01 Hop dong tron goi nhan cong, vat tu.doc" > f02.txt # 346 dòng +antiword -m UTF-8.txt "D:/…/SOL-CCM-FO-002.03.v01 Hop dong trong goi nhan cong, thiet bi.doc" > f03.txt # 538 dòng +``` +Mọi `Lnn` dưới đây = số dòng trong file trích tương ứng (f02.txt / f03.txt). + +🔴 **Phát hiện nền, chi phối cả 4 câu hỏi:** cả 2 file có **MỤC LỤC khai 22 dòng giống hệt nhau** (f02 L33-62 ≡ f03 L32-61), trong đó có `PHẦN 2 | ĐIỀU KIỆN CHUNG` + `ĐIỀU 1..17`. Nhưng **THÂN cả 2 file KHÔNG chứa PHẦN 2** — chỉ có câu tham chiếu *"Những nội dung không đề cập trong bảng tóm tắt các điều kiện cơ bản tham chiếu theo PHẦN 2: ĐIỀU KIỆN CHUNG của Hợp đồng"* (f02 L336-337, f03 L399-400). ⇒ **17 ĐIỀU kia là TÀI LIỆU RIÊNG ngoài 2 file này**, không có ở đây. Document-view dựng từ 2 file này chỉ phủ được phần "Hợp đồng chính + PHẦN 1 Điều kiện riêng (+ Phụ lục ở 002.03)". Nếu spec muốn "toàn bộ thân hợp đồng" thì **thiếu nguồn** cho PHẦN 2. + +--- ## A. Cấu trúc ĐIỀU — 002.02 (Nhân công + VẬT TƯ) -(đang điền) + +Theo thứ tự vật lý trong file. 🔴 antiword **mất số thứ tự ĐIỀU** ở thân (heading dùng auto-numbering của Word) — số dưới đây map theo MỤC LỤC + thứ tự xuất hiện, tiêu đề là chữ trích nguyên. + +| # | Khối | Dòng | Tiêu đề (nguyên văn trích) | +|---|---|---|---| +| — | Trang bìa | L6-29 | `HỢP ĐỒNG THI CÔNG XÂY DỰNG` / `Số:` / `DỰ ÁN :` / `HẠNG MỤC :` / `ĐỊA ĐIỂM :` / khối Bên A cứng / `CÔNG TY TNHH ………` / `Địa chỉ ….` / `Tháng ...... năm 20.....` | +| — | `DANH MỤC HỢP ĐỒNG` | L30-62 | bảng mục lục 22 dòng (ĐIỀU 1-5 + PHẦN 1 + PHẦN 2 + ĐIỀU 1-17) | +| — | Header thân lặp lại | L74-78 | tiêu đề + `Số:` + bảng 2 dòng `\|Hạng mục\|:\|` `\|Địa điểm\|:\|` (⚠️ **KHÔNG có dòng "Dự án"** — 002.03 thì có) | +| — | Căn cứ pháp lý | L81-86 | 4 gạch đầu dòng CỨNG (BLDS 91/2015/QH13 · Luật XD 50/2014 + 62/2020 · NĐ 06/2021 · nhu cầu 2 Bên) | +| — | Khối chủ thể | L87-107 | `Hôm nay, ngày ..… tháng …... năm 20….` + BÊN A (cứng, trừ STK/người đại diện) + BÊN B (trống) + `Hợp đồng Gói thầu: …………….` | +| **1** | L109 | L109-146 | `HỒ SƠ HỢP ĐỒNG VÀ THỨ TỰ ƯU TIÊN` — 3 mục đánh số + 6 mục con (bảng tóm tắt, bản vẽ/shopdrawing, phạm vi công việc, tiêu chuẩn KT, bảng tổng hợp đơn giá, tài liệu khác) + 2 đoạn quy tắc ưu tiên | +| **2** | L149 | L149-203 | `ĐỊNH NGHĨA VÀ DIỄN GIẢI` — **12 định nghĩa**: Hợp đồng · Chủ đầu tư · Nhà thầu chính · Nhà thầu-Bên A · Nhà thầu-Bên B · Ngày · Công trình/công trường · Hợp đồng giao thầu chính · Thiết bị của Bên B · Công việc · Luật · Thay đổi | +| **3** | L206 | L206-251 | `GIÁ TRỊ HỢP ĐỒNG` — 3.1 tổng giá trị (gồm VAT) + tham chiếu bảng · 3.2 trọn gói, không điều chỉnh trượt giá · 3.3 phát sinh bằng Phụ lục · +2 khoản (không phát sinh nếu CĐT không chấp thuận) · **Bảng tổng hợp giá trị hợp đồng 7 cột** (L244-251) | +| **4** | L254 | L254-258 | `thời hạn hoàn thành công việc` (chữ thường trong file) — 1 câu: `Ngày hoàn thành toàn bộ các công việc trong hợp đồng là : ……./…../20……` | +| **5** | L261 | L261-271 | `ĐIỀU KHOẢN CHUNG` — 3 khoản CỨNG (cam kết tuân thủ · hiệu lực từ ngày ký · lập 02 bản) | +| — | Chữ ký | L276 | `ĐẠI DIỆN BÊN A ĐẠI DIỆN BÊN B` | +| — | `PHẦN 1 — ĐIỀU KIỆN RIÊNG CỦA HỢP ĐỒNG` | L310-341 | `a)` **BẢNG TÓM TẮT CÁC ĐIỀU KIỆN CƠ BẢN 14 dòng** (L313-333) · `b)` bản vẽ/shopdrawing *(theo file đính kèm)* · `c)` tiêu chuẩn KT/quy trình thi công *(đính kèm)* · `d)` bảng tổng hợp giá trị HĐ *(đính kèm)* · `e)` các tài liệu khác | + +**Bảng tóm tắt 14 dòng (002.02, L313-333)** — cột `NỘI DUNG` TRỐNG 100%: +`1 Nội dung công việc→ĐIỀU 2` · `2 Tiến độ thi công→ĐIỀU 3` · `3 Bảo lãnh thực hiện hợp đồng→ĐIỀU 8` · `4 Bảo lãnh tạm ứng→ĐIỀU 8` · `5 Tạm ứng→ĐIỀU 8` · `6 Thanh toán tạm→ĐIỀU 8` · `7 Thời gian làm hồ sơ thanh toán→ĐIỀU 8` · `8 Thời gian thanh toán kể từ ngày nhận đủ hồ sơ thanh toán hợp lệ→ĐIỀU 8` · `9 Tiền giữ lại→ĐIỀU 8` · `10 Thu hồi tạm ứng→ĐIỀU 8` · `11 Thanh toán quyết toán→ĐIỀU 8` · `12 Tỷ lệ giữ lại bảo hành→ĐIỀU 8` · `13 Thời gian bảo hành→ĐIỀU 12` · `14 Các điều khoản phạt, làm lại, khắc phục và sửa chữa sai sót/vi phạm→ĐIỀU 10` +⚠️ Cột `ĐIỀU` ở đây trỏ sang **PHẦN 2 (ĐIỀU 2/3/8/10/12)** — tức trỏ ra ngoài file. Đây là **liên kết chéo giữa bảng nhập và tài liệu không có trong file**. + +--- ## B. Cấu trúc ĐIỀU — 002.03 (Nhân công + THIẾT BỊ) -(đang điền) + +| # | Khối | Dòng | Tiêu đề / ghi chú | +|---|---|---|---| +| — | Trang bìa | L6-29 | **Y HỆT 002.02** (so từng dòng: L6-29 trùng khớp, kể cả `Tháng ...... năm 20.....`) | +| — | `DANH MỤC HỢP ĐỒNG` | L30-61 | **Y HỆT** mục lục 22 dòng của 002.02 | +| — | Header thân | L66-70 | tiêu đề + `Số:` + bảng **3 dòng**: `\|Dự án\|` `\|Hạng mục\|:\|` `\|Địa điểm\|:\|` ← **KHÁC** (002.02 chỉ 2 dòng) | +| — | Căn cứ pháp lý | L73-78 | y hệt 4 gạch đầu dòng | +| — | Khối chủ thể | L80-100 | y hệt (khác vặt: `chúng tôi gồm có:` vs `chúng tôi gồm:`) | +| **1** | L102 | L102-154 | `HỒ SƠ HỢP ĐỒNG VÀ THỨ TỰ ƯU TIÊN` — **PHÌNH TO**: 6 mục, trong đó mục 1 liệt kê **4 PHỤ LỤC đích danh** (PL01 Phạm vi công việc công tác chính · PL02 Phạm vi thi công/cung cấp vật tư thiết bị + đơn giá khoán · PL03 Quy định xử lý vi phạm ATLĐ · PL04 Quy định tập kết/bảo quản/lưu kho thiết bị + sử dụng sắt hộp); mục 2 hồ sơ thiết kế; 3 LOA; 4 Điều kiện riêng (5 mục con); 5 Điều kiện chung; **6 Hợp đồng chính giữa Bên A và Chủ Đầu Tư của Bên A** | +| **2** | L157 | L157-210 | `ĐỊNH NGHĨA VÀ DIỄN GIẢI` — **cùng 12 định nghĩa, cùng thứ tự, câu chữ gần như y hệt**; khác duy nhất ở độ dài dấu chấm lửng + `Công trình/công trường **là dự án** ……. – tại …….` (002.02: không có chữ "là dự án") | +| **3** | L213 | L213-295 | `GIÁ TRỊ HỢP ĐỒNG` — **PHÌNH TO GẤP ~2**: 4 khoản đầu ≈ 002.02 (khoản 4 thêm câu *"phải chốt phát sinh 1 tháng 1 lần…"*) + **khoản 5 `Bảng tổng hợp giá trị hợp đồng:` NHƯNG KHÔNG CÓ BẢNG** + **khoản 6 `Đơn giá trên đã bao gồm:`** (6 gạch đầu dòng: dọn vệ sinh · máy móc/dụng cụ cầm tay + nhiên liệu · vật tư phụ · chi phí m³/ca bơm bê tông · nhân công hoàn thành nghiệm thu kết cấu · máy móc thiết bị dụng cụ) + **khoản 7 `Bảo hộ lao động`** (Bên A cấp, khấu trừ 100% — 9 món: áo kỹ sư, giày joger kỹ sư, nón kỹ sư 3M, dây nón 3M, kính KY2221, áo công nhân, giày công nhân, nón công nhân, ủng mũi thép) + **khoản 8 `Tuân thủ Quy định về hao hụt`** (thép · bê tông · gạch lát · vữa xây-tô · tham chiếu định mức) | +| **4** | L297 | L297-301 | `thời hạn hoàn thành công việc` — y hệt (`…../……/20…..`) | +| **5** | L304 | L304-344 | 🔴 **`TRÁCH NHIỆM RỦI RO VỀ THUẾ` — CHỈ CÓ Ở 002.03**. 2 nhánh: *Trách nhiệm của Bên B* (3 khoản: cam kết chịu trách nhiệm pháp lý hoá đơn/GTGT khi có tên trong DS rủi ro thuế · giải quyết dứt điểm sai phạm + thông báo tiến trình · chịu mọi rủi ro bồi thường/truy thu GTGT-TNDN) + *Quyền hạn của Bên A* (2 khoản: giữ lại công nợ tương ứng số thuế rủi ro · thanh toán lại sau khi Bên B chứng minh đã xử lý) | +| **6** | L347 | L347-357 | `ĐIỀU KHOẢN CHUNG` — 3 khoản CỨNG, **y hệt** 002.02 | +| — | Chữ ký | L362 | `ĐẠI DIỆN BÊN A ĐẠI DIỆN BÊN B` | +| — | `PHẦN 1 — ĐIỀU KIỆN RIÊNG` | L368-404 | `a)` **BẢNG TÓM TẮT 16 dòng** (L371-396) · b/c/d/e y hệt 002.02 | +| — | **PHỤ LỤC 01** | L406-423 | `PHẠM VI CÔNG VIỆC CÔNG TÁC CHÍNH` — bảng header **2 tầng** (`STT` \| `Diễn giải` \| `TÊN CÔNG TY`{`Vật tư`\|`Nhân công`} \| `Ghi chú`), **13 dòng trống** + cặp ký | +| — | **PHỤ LỤC 02** | L431-449 | `PHẠM VI THI CÔNG, PHẠM VI CUNG CẤP VẬT TƯ THIẾT BỊ VÀ ĐƠN GIÁ KHOÁN THI CÔNG` — bảng header **3 tầng** (`STT`\|`CÔNG VIỆC`\|`ĐVT`\|`TRÁCH NHIỆM THỰC HIỆN`{`BÊN A (SOLUTIONS)`\|`BÊN B (Đội thi công)`}\|`ĐƠN GIÁ KHOÁN`\|`GHI CHÚ`), **11 dòng trống** + cặp ký | +| — | **PHỤ LỤC 03** | L452-469 | `QUY ĐỊNH XỬ LÝ VI PHẠM AN TOÀN LAO ĐỘNG` — `I. CÁC HÀNH VI VI PHẠM NHẸ` (a/b/c: cảnh cáo → thẻ vàng → thẻ đỏ) · `II. CÁC HÀNH VI VI PHẠM NẶNG` (1. biện pháp xử lý = cấm làm việc tại công trường; **2. "Hành vi vi phạm và mức xử lý" — trống, không có bảng theo sau**) + cặp ký | +| — | **PHỤ LỤC 04** | L471-531 | `QUY ĐỊNH VIỆC TẬP KẾT, BẢO QUẢN, LƯU KHO THIẾT BỊ VÀ SỬ DỤNG SẮT HỘP` — `I` bảng quy cách tập kết (4 cột, **5 dòng CÓ SẴN nội dung**: thép hộp/tuýp-chống-chéo-ty-gông · giàn giáo-mâm · ván phủ film · kích U-kích chân · cùm-bát chuồn; cột `HÌNH ẢNH MINH HỌA` trống) · `II` bảng bốc dỡ/bảo quản/lưu kho (5 cột, 4 dòng: Sắt thép · Thiết bị điện · Máy móc thiết bị · Vật liệu nhựa — **STT nhảy 1,2,4,5 thiếu 3**, lỗi trong file gốc) · `III` quy định sắt hộp (bảng 7 ô module: 1,0/1,5/2,0/3,0/4,0/5,0/6,0 m + cấm cắt ngắn) · `IV` xử lý vi phạm (4 khoản) + cặp ký | + +**Bảng tóm tắt 16 dòng (002.03, L371-396)** — cột `NỘI DUNG` TRỐNG 100%. Dòng 1-8 **trùng khít 002.02**. Từ dòng 9 lệch: +`9 Tạm ứng vật tư tập kết đến công trường→ĐIỀU 8` **(chỉ 002.03)** · `10 Thu hồi tạm ứng→ĐIỀU 8` · `11 Tiền giữ lại→ĐIỀU 8` · `12 Thanh toán quyết toán→ĐIỀU 8` · `13 Tỷ lệ giữ lại bảo hành→ĐIỀU 8` · `14 Thời hạn thông báo sai sót (defect)→(cột ĐIỀU TRỐNG)` **(chỉ 002.03)** · `15 Thời gian bảo hành→ĐIỀU 12` · `16 Các điều khoản phạt…→ĐIỀU 10` + +--- ## C. Ô cần điền -(đang điền) + +Ký hiệu chỗ trống trong 2 file: `:` bỏ trắng · `…` (ellipsis) · `....` · `20....` · ô bảng rỗng. Đo được **15 dòng** chứa `…`/`....` ở 002.02, **18 dòng** ở 002.03 (`grep -cE "…|\.\.\.\."`). + +### C1 — Ô CHUNG cả 2 file (ứng viên pre-fill 3 tầng) + +| # | Ô (nguyên văn) | Ở ĐIỀU / khối | f02 | f03 | Ghi chú | +|---|---|---|---|---|---| +| 1 | `Số:` | Bìa **+ lặp lại** đầu thân | L7, L75 | L7, L67 | mã HĐ — xuất hiện **2 lần**, phải bind cùng 1 nguồn | +| 2 | `DỰ ÁN :` | Bìa | L9 | L9 | | +| 3 | `HẠNG MỤC :` | Bìa | L10 | L10 | | +| 4 | `ĐỊA ĐIỂM :` | Bìa | L11 | L11 | | +| 5 | `CÔNG TY TNHH ………` | Bìa (Bên B) | L22 | L22 | tên NCC/thầu phụ | +| 6 | `Địa chỉ ….` | Bìa (Bên B) | L23 | L23 | | +| 7 | `Tháng ...... năm 20.....` | Bìa | L29 | L29 | tháng/năm ký | +| 8 | `\|Hạng mục\|:` `\|Địa điểm\|:` | Header thân | L77-78 | L69-70 | **lặp** ô 3+4 | +| 8b | `\|Dự án\|` | Header thân | — | **L68** | ⚠️ chỉ 002.03 | +| 9 | `Hôm nay, ngày ..… tháng …... năm 20….` | Mở đầu | L87 | L80 | ngày ký | +| 10 | `Số tài khoản : … tại Ngân hàng :` (Bên A) | Mở đầu | L95 | L88 | Bên A còn trống STK | +| 11 | `Người đại diện : Ông/Bà … Chức vụ:` (Bên A) | Mở đầu | L97 | L90 | | +| 12 | `BÊN NHẬN THẦU (Bên B): CÔNG TY ..........` | Mở đầu | L98 | L91 | | +| 13 | `Địa chỉ :` (Bên B) | Mở đầu | L99 | L92 | | +| 14 | `Điện thoại : … Fax :` (Bên B) | Mở đầu | L100 | L93 | 2 ô/1 dòng | +| 15 | `Số tài khoản :` (Bên B) | Mở đầu | L101 | L94 | | +| 16 | `Mã số thuế :` (Bên B) | Mở đầu | L102 | L95 | | +| 17 | `Người đại diện : Ông/Bà … Chức vụ :` (Bên B) | Mở đầu | L103 | L96 | | +| 18 | `Hợp đồng Gói thầu: …………….` | Mở đầu | L107 | L100 | tên gói thầu | +| 19 | `Chủ đầu tư là ……………………………….` | **ĐIỀU 2** | L159 | L167 | | +| 20 | `Bên B là CÔNG TY ................` | **ĐIỀU 2** | L173 | L181 | **lặp** ô 12 | +| 21 | `Công trình/công trường ……… – tại ………` | **ĐIỀU 2** | L179 | L187 | 2 ô/1 dòng; f03 thêm chữ "là dự án" | +| 22 | `Hợp đồng giao thầu chính … ký số ………… … ngày ….../….../20…….` | **ĐIỀU 2** | L182-183 | L190-191 | 2 ô (số HĐ chính + ngày); f03 chỉ `ngày ………` | +| 23 | `Tổng giá trị Hợp đồng … là ………….. VNĐ (Bằng chữ: …………... đồng)` | **ĐIỀU 3** | L209-211 | L217-218 | **2 ô: số + bằng chữ** | +| 24 | `Ngày hoàn thành toàn bộ … là : ……./…../20……` | **ĐIỀU 4** | L257-258 | L300-301 | | +| 25 | `ĐẠI DIỆN BÊN A / ĐẠI DIỆN BÊN B` (tên người ký) | Cuối thân | L276 | L362 | ô ký, trống dưới | +| 26 | **Bảng tóm tắt điều kiện cơ bản — cột `NỘI DUNG`** | PHẦN 1 a) | L313-333 (**14 ô**) | L371-396 (**16 ô**) | ⚠️ nội dung dòng lệch nhau từ #9 | + +### C2 — Ô CHỈ CÓ ở 002.02 (Vật tư) + +| # | Ô | ĐIỀU | Dòng | +|---|---|---|---| +| 27 | **Bảng tổng hợp giá trị hợp đồng** 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Ú` + 3 dòng tổng `TỔNG CỘNG` / `VAT 10%` / `TỔNG CỘNG (SAU THUẾ)` | **ĐIỀU 3** | L244-251 (4 dòng dữ liệu trống) | + +### C3 — Ô CHỈ CÓ ở 002.03 (Thiết bị) + +| # | Ô | ĐIỀU / Phụ lục | Dòng | +|---|---|---|---| +| 28 | `Bảng tổng hợp giá trị hợp đồng:` — **có tiêu đề, KHÔNG có bảng** | ĐIỀU 3 kh.5 | L247 | +| 29 | Đơn giá 9 món bảo hộ lao động — câu ghi *"theo đơn giá sau (đơn giá chưa bao gồm VAT)"* nhưng **9 gạch đầu dòng KHÔNG kèm số** | ĐIỀU 3 kh.7 | L274-286 | +| 30 | Bảng PL01 `Diễn giải / TÊN CÔNG TY (Vật tư \| Nhân công) / Ghi chú` | PL01 | L408-420 (13 dòng trống) | +| 31 | Bảng PL02 `CÔNG VIỆC / ĐVT / TRÁCH NHIỆM (Bên A \| Bên B) / ĐƠN GIÁ KHOÁN / GHI CHÚ` | PL02 | L434-445 (11 dòng trống) | +| 32 | `2. Hành vi vi phạm và mức xử lý` — heading trống, không có bảng | PL03 II | L466 | +| 33 | Cột `HÌNH ẢNH MINH HỌA` bảng quy cách tập kết (5 dòng) | PL04 I | L475-489 | +| 34 | Bảng PL04 II: cột `BỐC DỠ` / `BẢO QUẢN` / `LƯU KHO` × 4 loại thiết bị | PL04 II | L493-497 | +| 35 | 4 cặp `ĐẠI DIỆN BÊN A \| ĐẠI DIỆN BÊN B` cuối mỗi phụ lục | PL01-04 | L423, L449, L469, L531 | + +**Tổng nhóm-ô:** 002.02 ≈ **27 nhóm** · 002.03 ≈ **34 nhóm** (26 chung + 8 riêng, trong đó 6 là bảng/phụ lục). + +**Ô LẶP (bind 1 nguồn → nhiều vị trí render):** `Số:` ×2 · `Hạng mục` ×2 · `Địa điểm` ×2 · tên Bên B ×3 (bìa L22 + mở đầu L98 + ĐIỀU 2 L173) · `Dự án` ×2 ở f03 (bìa L9 + header L68). ⇒ document-view **phải** có cơ chế field-lặp, không thể 1-ô-1-chỗ. + +--- ## D. So sánh 2 file -(đang điền) + +**VERDICT: 1 khuôn XƯƠNG CHUNG + 1 lớp DELTA — KHÔNG phải 2 khuôn rời, cũng KHÔNG phải 1 khuôn y hệt.** + +Bằng chứng ĐỒNG NHẤT (chép nguyên, khác nhau ở mức dấu cách): +- Trang bìa L6-29 ≡ L6-29 (7/7 ô giống) +- Mục lục 22 dòng ≡ (f02 L33-62 ≡ f03 L32-61) — kể cả cùng lỗi chính tả `THÔNG TIN VÀ THÔNG BÁOXỬ LÝ KHI CÓ TRANH CHẤP` (thiếu space, ĐIỀU 14) và `DƯỢC DUYỆT` (b, PHẦN 1) +- 4 căn cứ pháp lý ≡ +- Khối BÊN A / BÊN B ≡ (khác 1 chữ: `gồm:` vs `gồm có:`) +- ĐIỀU 2 — 12 định nghĩa, cùng thứ tự, cùng câu chữ +- ĐIỀU 4 (thời hạn) ≡ · ĐIỀU KHOẢN CHUNG 3 khoản ≡ · PHẦN 1 mục b/c/d/e ≡ +- Bảng tóm tắt điều kiện cơ bản: **dòng 1-8 trùng khít** + +Bằng chứng KHÁC BIỆT: + +| Trục | 002.02 (Vật tư) | 002.03 (Thiết bị) | +|---|---|---| +| Header thân | 2 dòng (Hạng mục, Địa điểm) | **3 dòng (+ Dự án)** | +| **ĐIỀU 1** Hồ sơ | 3 mục + 6 mục con, **0 phụ lục** | 6 mục, **liệt kê đích danh 4 Phụ lục** + "Hợp đồng chính giữa Bên A và CĐT" | +| **ĐIỀU 3** Giá trị | 3.1/3.2/3.3 + 2 khoản + **bảng 7 cột tại chỗ** | 8 khoản: 1-4 tương đương (+"chốt phát sinh 1 tháng/lần") **+ kh.5 tiêu đề bảng rỗng + kh.6 "Đơn giá đã bao gồm" 6 mục + kh.7 Bảo hộ lao động 9 món + kh.8 Hao hụt 5 mục** | +| **ĐIỀU thuế** | **KHÔNG CÓ** | 🔴 **`TRÁCH NHIỆM RỦI RO VỀ THUẾ`** (L304-344, 7 khoản) | +| Số ĐIỀU trong thân | **5** | **6** | +| Bảng tóm tắt PHẦN 1 | **14 dòng** | **16 dòng** (+`Tạm ứng vật tư tập kết đến công trường`, +`Thời hạn thông báo sai sót (defect)`; thứ tự Tiền giữ lại ⟂ Thu hồi tạm ứng **đảo nhau**) | +| Phụ lục | **0** | **4** (PL01 phạm vi công việc · PL02 phạm vi thi công + đơn giá khoán · PL03 ATLĐ · PL04 tập kết/lưu kho/sắt hộp) | + +🔴 **Hệ quả cho lane-1 (tách menu):** hai bản dùng **cùng bộ ô định danh** (26/26 ô chung đều là ô nhận diện dự án/bên/giá trị/thời hạn) ⇒ **1 entity + 1 bộ trường lõi**, tách bằng **biến thể khuôn (variant) chứ không phải 2 module rời**. Nhưng delta **KHÔNG phải cosmetic**: 002.03 có thêm 1 ĐIỀU pháp lý, +2 dòng bảng điều kiện, +4 phụ lục có bảng ⇒ chọn "1 khuôn chung + phần khác biệt" thì phần khác biệt vẫn chiếm **~35% khối lượng render** của bản Thiết bị (192/538 dòng = riêng phụ lục + điều thuế). + +⚠️ **Cảnh báo bất đối xứng đáng ngờ (KHÔNG kết luận, cần owner xác nhận):** bản Vật tư có bảng giá trị hợp đồng **nhúng trong ĐIỀU 3**, bản Thiết bị chỉ có **tiêu đề rỗng** ở đúng chỗ đó rồi đẩy bảng giá sang **PL02 (Đơn giá khoán)**. Đây có thể là chủ đích (khoán theo đơn giá) HOẶC là template thiếu bảng. Lane-3 lập bảng ô×nguồn cần chốt điểm này trước khi map nguồn giá. + +--- ## E. Bảng/biểu -(đang điền) + +**002.02 — 3 bảng** (61 dòng `^|`): +1. `DANH MỤC HỢP ĐỒNG` L33-62 — 2 cột, 22 dòng, **nội dung tĩnh** (mục lục) → render dễ, read-only. +2. Header thân L77-78 — 2 cột × 2 dòng, ô nhập → thực chất là 2 field, không cần table-editor. +3. **`Bảng tổng hợp giá trị hợp đồng` L244-251 — 7 cột × N dòng + 3 dòng tổng có merge ngang** (`|TỔNG CỘNG` chiếm 4 cột đầu). → **BẢNG NHẬP THẬT, khó nhất của 002.02**: cần thêm/xoá dòng, tính `THÀNH TIỀN = KL × ĐƠN GIÁ`, `TỔNG CỘNG`, `VAT 10%`, `TỔNG SAU THUẾ`. +4. (bán-bảng) Bảng tóm tắt PHẦN 1 L313-333 — 4 cột × 14 dòng, **3 cột đầu tĩnh, chỉ cột `NỘI DUNG` nhập** → render như list 14 textarea, không cần grid động. + +**002.03 — 8 bảng** (113 dòng `^|`): 1-2 giống trên (mục lục + header 3 dòng); bảng tóm tắt PHẦN 1 16 dòng; **PL01** (header **merge 2 tầng** `TÊN CÔNG TY`→{Vật tư, Nhân công}, 13 dòng trống); **PL02** (header **merge 3 tầng** `TRÁCH NHIỆM THỰC HIỆN`→{BÊN A (SOLUTIONS), BÊN B (Đội thi công)}, 11 dòng trống); **PL04-I** (4 cột, 5 dòng nội dung tĩnh + cột ảnh); **PL04-II** (5 cột, 4 dòng nhập); **PL04-III** (bảng module sắt hộp 4×2 = 7 giá trị tĩnh). + +**Xếp hạng khó render document-view:** +| Mức | Vật thể | Vì sao | +|---|---|---| +| 🔴 Khó | PL02 (header 3 tầng), PL01 (header 2 tầng) | colspan/rowspan lồng, thêm-xoá dòng, cột "trách nhiệm" kiểu tick A/B | +| 🔴 Khó | Bảng tổng hợp giá trị 002.02 | có công thức + dòng tổng merge + VAT | +| 🟡 Vừa | Bảng tóm tắt PHẦN 1 (14/16 dòng) | cột trái tĩnh, chỉ 1 cột nhập, nhưng dòng **khác nhau giữa 2 bản** ⇒ phải seed theo variant | +| 🟡 Vừa | PL04-I/II | ô ảnh (PL04-I cột `HÌNH ẢNH MINH HỌA`) — cần upload ảnh hoặc để trống | +| 🟢 Dễ | Mục lục, header thân, PL04-III | tĩnh/read-only | + +**Ảnh:** chỉ **1 `[pic]`** mỗi file (logo bìa L2). Cột `HÌNH ẢNH MINH HỌA` của PL04-I **rỗng trong template** (không phải antiword nuốt — nếu có ảnh antiword đã in `[pic]` như logo). + +--- ## F. Chỗ trích lỗi (khai thật) -(đang điền) + +1. 🔴 **antiword cắt ký tự UTF-8 giữa chừng ở cuối dòng wrap** — đo bằng python decode: **f02 có đúng 3 dòng hỏng, f03 có đúng 5 dòng**. Tất cả đều là **byte dẫn đầu mồ côi**: + - `f02 L21 tail=b'V\xc3'` → phải là **`Và`** (bìa, giữa 2 pháp nhân) + - `f02 L222 tail=b'...Nh\xc3'` → **`Nhà`** (…"được Nhà thầu chính xác nhận") + - `f02 L311 tail=b'...\xc4\x90\xe1\xbb\x92NG\xc2'` → `ĐIỀU KIỆN RIÊNG CỦA HỢP ĐỒNG` + 1 ký tự đuôi mất (byte `\xc2`, nghi nbsp/ký tự ẩn) + - `f03 L21`, `f03 L225` (`Nhà`), `f03 L252` (`và`), `f03 L369` (như f02 L311), `f03 L464` (`nhà thầu`) + ⇒ **KHÔNG dùng text antiword làm nguồn chép thẳng vào code**. Muốn lấy câu chữ chuẩn phải convert lại bằng Word/LibreOffice hoặc chép tay từng đoạn. Lỗi này **xác định được và ít** (8 dòng tổng), không lan rộng. +2. 🔴 **Mất số thứ tự ĐIỀU**: heading trong thân dùng auto-numbering của Word ⇒ antiword in ra **chỉ có tiêu đề, không có "ĐIỀU n"** (vd L109 `HỒ SƠ HỢP ĐỒNG VÀ THỨ TỰ ƯU TIÊN`, L206 `GIÁ TRỊ HỢP ĐỒNG`). Số ĐIỀU trong mục A/B là **suy từ mục lục + thứ tự vật lý**, KHÔNG phải trích trực tiếp. Riêng ĐIỀU 3 của 002.02 có sẵn `3.1/3.2/3.3` trong text nên khẳng định được. +3. 🔴 **Số khoản trong list bị auto-numbering đè sai**: f02 L235 `5 Hợp đồng không phát sinh…` và L242 `6 Bảng tổng hợp giá trị hợp đồng:` nằm trong ĐIỀU 3 nhưng đánh số 5/6 (tiếp nối list trước), trong khi 3 khoản đầu là 3.1/3.2/3.3. Tương tự f02 L119-134 (`2 Bảng tóm tắt…` → `7 Các tài liệu khác`) và f03 L110-140. ⇒ **con số đầu dòng trong text trích KHÔNG đáng tin**; cấu trúc phân cấp phải dựng lại tay khi làm document-view. +4. ⚠️ **`TRÁCH NHIỆM RỦI RO VỀ THUẾ` (f03 L304) KHÔNG có trong mục lục của chính f03** (mục lục chỉ khai ĐIỀU 1-5). ⇒ mục lục file gốc **stale so với thân của nó**. Không phải lỗi trích — đối chiếu được vì mục lục 2 file giống hệt nhau còn thân thì khác. +5. ⚠️ **Cả 2 file cùng in footer `Trang 5 | 6`** (f02 L343, f03 L535) dù độ dài text chênh 192 dòng ⇒ antiword chỉ in **một** footer (của section cuối), **KHÔNG suy ra được số trang thật**. Không dùng con số này. +6. ⚠️ **f03 PL04-II STT nhảy `1,2,4,5` (thiếu 3)** (L494-497) — nghi lỗi trong file gốc, nhưng cũng có thể antiword nuốt 1 dòng bảng. **Chưa xác minh được** bằng công cụ hiện có. +7. ⚠️ **f03 kh.7 Bảo hộ lao động nói "theo đơn giá sau" nhưng 9 món không kèm số** (L274-286) và **kh.5 "Bảng tổng hợp giá trị hợp đồng:" không có bảng** (L247) — chưa phân biệt được "template cố ý để trống" vs "antiword mất bảng". Control: antiword **có** in được bảng ở chỗ khác trong cùng file (PL01/PL02/PL04) ⇒ thước không hỏng ⇒ **nghiêng về template trống thật**, nhưng vẫn khai là chưa chắc. +8. ✅ **Không** thấy mojibake diện rộng, không có ký tự thay thế `U+FFFD` nào (`grep -c` = 0 cả 2 file). Tiếng Việt trích ra sạch ngoài 8 dòng ở mục 1. + +--- + +## Ghi chú vận hành + +- File này ghi ở đường dẫn theo TASK (`sub-investigator-codebase-**5**.md`); footer RUN-TRACE của khung spawn ghi `-4.md` — **lệch tên giữa 2 chỉ thị trong cùng lượt**, tôi theo TASK body (lane-5). Không tạo file `-4.md` để tránh 2 bản. +- 2 file `.doc` gốc **KHÔNG bị chạm**; text trích đổ ra scratchpad ngoài repo, không commit. diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-1.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-1.md new file mode 100644 index 00000000..ada9a6ee --- /dev/null +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-1.md @@ -0,0 +1,25 @@ +# sub-reviewer-1 — chấm quyết định ②.2(a) A1-vs-A2 + wave W1 (YC-032, S194) + +> Vai: `reviewer` (adversarial, read-only). Vật chấm: `spec-hd-thauphu-2form-13-08-2026.md` §②.1 · §②.2(a) · W1. +> Kỷ luật: mọi số phải từ lệnh vừa chạy (kèm lệnh). 0-hit BẮT BUỘC kèm control-dương. + +## 0. Khung +(đang ghi) + +## 1. Đếm lại site +(đang ghi) + +## 2. Migration rỗng? +(đang ghi) + +## 3. Nhánh nuốt im lặng +(đang ghi) + +## 4. Acceptance W1 +(đang ghi) + +## 5. Test canh menu-row +(đang ghi) + +## 6. VERDICT + finding đánh số +(đang ghi) diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-2.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-2.md new file mode 100644 index 00000000..5086d1d7 --- /dev/null +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-2.md @@ -0,0 +1,25 @@ +# sub-reviewer-2 — R2-FORMSHAPE (YC-032, S194) + +> Vai: reviewer (adversarial). Vật chấm: `spec-hd-thauphu-2form-13-08-2026.md` §②.2(b) HTML dựng tay · §②.2(c) component chung + 2 bản lắp ráp · §②.2(d) chỉ fe-user · §②.1 bẫy 3 · W2. +> Kỷ luật: mọi số từ lệnh vừa chạy, kèm lệnh. 0-hit kèm control-dương. + +## 0. Khung +(đang ghi) + +## 1. Đo lại độ chung 2 file +(đang ghi) + +## 2. draftData:null +(đang ghi) + +## 3. DraftData làm kho +(đang ghi) + +## 4. 3 mảnh thiếu +(đang ghi) + +## 5. Acceptance W2 +(đang ghi) + +## 6. VERDICT + finding +(đang ghi) diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-3.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-3.md new file mode 100644 index 00000000..cd4033b2 --- /dev/null +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-3.md @@ -0,0 +1,22 @@ +# sub-reviewer-3 — YC-032 chấm ②.4 bản đồ pre-fill + 2 lỗ nghiêm trọng + wave W3 + +## 0. Khung +(đang đo) + +## 1. Verify 24/17/3 +(đang đo) + +## 2. Regression cầu KHKK +(đang đo) + +## 3. Chuỗi tiền + VAT +(đang đo) + +## 4. Đọc vượt tầng +(đang đo) + +## 5. Acceptance W3 +(đang đo) + +## 6. VERDICT + finding +(đang đo) diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-4.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-4.md new file mode 100644 index 00000000..e93ad14f --- /dev/null +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-4.md @@ -0,0 +1,23 @@ +# sub-reviewer-4 — CHẤM KẾ HOẠCH (lăng-kính "cái gì THIẾU") + +> Vật chấm: `spec-hd-thauphu-2form-13-08-2026.md` (LEAD tổng hợp 5 lane invest). +> Lăng-kính: kế hoạch — thứ tự wave, phụ thuộc, vế 4 (bản cứng/khoá/in), §②.3 phát hiện CHẶN. +> ⚠️ Xung-đột đường-dẫn trong spec giao việc: khối TASK ghi `sub-reviewer-4.md`, khối RUN-TRACE footer ghi `sub-reviewer-3.md`. Chọn `-4` (khối TASK cụ-thể hơn + khớp tên lane R4); khai ở finding. + +## 0. Khung + +(đang chạy — append dần) + +## 1. Verify 12 điều thiếu + +## 2. Wave thiếu/chồng + +## 3. Phụ thuộc + +## 4. Khoá chết vế 4 + +## 5. Rủi ro đánh giá thấp + +## 6. Câu hỏi owner + +## 7. VERDICT + finding