wal: flush 20260813T2242
This commit is contained in:
@ -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ế
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
@ -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<int>();`
|
||||
(+ `WorkflowDefinitionConfiguration.cs`, `WorkflowTypeAssignmentConfiguration.cs`, `ContractTemplateConfiguration.cs` — 2 hit mỗi file).
|
||||
Migration gốc: `20260421051248_AddContractsWorkflow.cs:33` → `Type = table.Column<int>(type: "int", nullable: false)`.
|
||||
Snapshot: `ApplicationDbContextModelSnapshot.cs:882-883` `b.Property<int>("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<int>` 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
|
||||
|
||||
|
||||
@ -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\|<Stepper" fe-user/src/pages/contracts/ContractCreatePage.tsx` → 0 (control dương cùng thước: `grep -c "<Input"` → 13).
|
||||
|
||||
### A2. Đếm ô nhập (lệnh + số)
|
||||
|
||||
`grep -c "<Input" …ContractCreatePage.tsx` → **13**
|
||||
`grep -c "<Select" …` → **6**
|
||||
`grep -c "<Textarea" …` → **2**
|
||||
`grep -c 'type="checkbox"' …` → **2**
|
||||
`grep -c "useState" …` → **18**
|
||||
|
||||
Vì file chứa **2 form** (create + edit) + 1 preview, con số trên là tổng cả 2. Bóc theo form:
|
||||
|
||||
| Form | Field NGƯỜI DÙNG nhập được | Nguồn |
|
||||
|---|---|---|
|
||||
| **Create** (`ContractHeaderForm :294-473`) | **11** = 8 trong `FormFields` (Loại HĐ · Template · NCC · Dự án · Tên HĐ · Giá trị · checkbox bypass · Nội dung — `:807-866`) + Quy trình duyệt V2 (`:414`) + Tên ngân sách (`:439`) + Số tiền NS (`:448`) | `:395-461` |
|
||||
| **Edit** (`ContractEditForm :539-772`) | **6 sửa được** (Template `:674` · Tên HĐ `:695` · Giá trị `:699` · Nội dung `:712` · NS tên `:722` · NS số tiền `:731`) + **4 chỉ-đọc** (Loại HĐ `:670` · NCC `:687` · Dự án `:691` · checkbox bypass `:702` disabled) | `:667-759` |
|
||||
| **Chi tiết (bảng dòng)** | bảng `ContractDetailsTab`, cột theo `HEADERS_BY_TYPE :529-537`; **type 1 (Thầu phụ) = 7 cột** `['Hạng mục','ĐVT','Khối lượng','Đơn giá','Thành tiền','Hoàn thành','Ghi chú']` | `:530` |
|
||||
|
||||
⇒ **Trả lời (A): 11 field ở form tạo, 10 ô ở form sửa (6 sửa + 4 khoá), + 1 bảng chi tiết 7 cột.** Tổ chức = **1 khung `<form>` 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<Paragraph>` (`: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<string,string?>)` (`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<IDocumentConverter, LibreOfficeDocumentConverter>()`. 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<fieldKey, {label,type,required,placeholder,hint,options}>`, 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).
|
||||
|
||||
@ -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.**
|
||||
|
||||
@ -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<string,string?>** 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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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
|
||||
Reference in New Issue
Block a user