diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-cicd-monitor-1.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-cicd-monitor-1.md new file mode 100644 index 00000000..a1ecde61 --- /dev/null +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-cicd-monitor-1.md @@ -0,0 +1,27 @@ +# sub-cicd-monitor-1 — CI/CD verify commit `88bdd054` (YC-032) + +> Artifact SỐNG — append SAU MỖI CHÂN, không gom cuối (#53: return có thể chết, file trên đĩa mới sống). +> Bắt đầu: 2026-08-14 (giờ VN), agent `cicd-monitor`, spawn bởi em main. + +## Đề bài (tóm) + +- Commit đích: `88bdd05492198c2a0672f46b40258006b0bcf550` — `[CLAUDE] Contract: tách HĐ Thầu phụ thành 01A/01B + thân document-view + pre-fill + khoá bản cứng (YC-032)` +- Author-date: 2026-08-14 04:11:20 +0700 +- Nội dung: ContractType 8/9 + menu `01A.`/`01B.` + FE document-view + **Mig MỚI `20260813204332_AddContractSealedAt`** (+2 cột `Contracts.SealedAt`, `SealedByUserId`) + 2 `.docx` template. +- Số đo LOCAL trước push (ĐỐI CHỨNG, không phải kết quả prod): build 0 error · test **699 PASS** · `has-pending-model-changes` = No changes · `tsc --noEmit` exit 0 ×2. + +## 6 chân verify (điền dần) + +| # | Chân | Verdict | Bằng chứng | +|---|---|---|---| +| 1 | CI run (#86 cancel-chain) | ⏳ | | +| 2 | Test gate 699 | ⏳ | | +| 3 | Mig prod 73→74 | ⏳ | | +| 4 | Bundle FE ×2 | ⏳ | | +| 5 | Smoke 3 host 200 | ⏳ | | +| 6 | RUNTIME-verify (#92) | ⏳ | | + +--- + +## CHÂN 0 — Push state + path filter + diff --git a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-5.md b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-5.md index 62ec6114..a4a48f98 100644 --- a/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-5.md +++ b/.claude/workflows/runs/2026-08-13-S194-yc032-hd-thauphu-2form/sub-reviewer-5.md @@ -12,11 +12,43 @@ ## 1. Trục 1 — Nhánh câm thứ 5 (switch/ternary nuốt 8/9) -(đang đo) +**Phép đo phân-họ (không grep từng file):** lấy tập file chứa mốc "danh sách 7 loại đầy đủ" +(`HopDongNguyenTacDichVu`) rồi trừ tập file có loại mới (`HopDongThauPhuThietBi`). +- BE: tập A = **7 file**, tập B = **8 file**, `comm -23 A B` = **0 dòng** ⇒ không file BE nào liệt đủ 7 loại cũ mà thiếu loại mới. Control-dương: cùng lệnh trả 7 tên file có thật (không phải lưới rỗng). +- Nhánh `default`/`_` còn lại: `ContractCodeGenerator.cs:34 _ => "HĐ"` (đã kể tên 8/9 ở `:26-27`) · `WorkflowPolicy.cs:164 _ => "SkipCcm"` (đã kể tên ở `:159-160`) · `ContractDetailsFeatures.cs:41` + `:439` (2 switch, đã có `case` 8/9 ở `:47-48`, `:443-444`). `FormFeatures.cs` KHÔNG có switch trên loại HĐ — chỉ `Where(x => x.ContractType == request.ContractType)` (`:33`), data-driven. +- Guard `ContractWorkflowGuards.cs` không hardcode loại nào: `CodeFor(type)` + `Members` ⇒ nở by-construction (9 member). +- `ContractDetailsHelpers.ThauPhuFamily` (`ContractDetailsFeatures.cs:492-497`) gom 3 loại chung bảng `ThauPhuDetails`; **cả 2 call-site Thầu phụ** (`:141` Create, `:294` Update) đã đổi sang `EnsureContractTypeIn(...ThauPhuFamily...)`, 12 call-site loại khác giữ `EnsureContractType` đơn — đúng. +- FE: dispatch số học duy nhất là `ContractDetailsTab.tsx:90` `(bundle.type === 1 || === 8 || === 9)` ×2 app — khớp họ BE. +- FE 3 bản chép `TYPE_CODE_TO_INT` (`fe-admin/Layout.tsx:22`, `fe-user/Layout.tsx:16`, `fe-admin/WorkflowsPage.tsx:79`) đều có 8/9. `fe-user/Layout.tsx:33` `INT_TO_TYPE_CODE` derive ngược bằng `Object.fromEntries` ⇒ tự nở. +- Cửa regex `WorkflowMatrixViewPage.tsx:136` đã nới `[1-7]`→`[1-9]` (chỗ này là CỬA, bảng tra chỉ đứng sau). Trang này CHỈ có ở `fe-user` (`ls fe-admin/src/pages/pe/` = 3 file, không có bản mirror) ⇒ không phải khe mirror. + +**F-1 (ghi-nhận) — `DbInitializer.cs:1000-1008` `SeedWorkflowDefinitionsAsync.typeLabels` vẫn ĐÚNG 7 entry.** +Loại 8/9 không được seed `WorkflowDefinition` V1 (`QT-*`) ⇒ HĐ mới tạo có `WorkflowDefinitionId = null` +(`ContractFeatures.cs:143 = activeWfId`). Hệ quả **không nổ**: `LoadPolicyAsync` rơi về +`WorkflowPolicyRegistry.For(type)` = `"Standard"` — trùng đúng nội dung mà seed sẽ sinh ra +(seed cũng lấy từ `WorkflowPolicyRegistry.For`). Nhưng trang Designer V1 +`/system/workflows/ThauPhuVatTu` sẽ hiện khung **rỗng** ("chưa có phiên bản"), khác 7 loại cũ có `v01`. +Không chặn deploy; nếu owner muốn đồng dạng thì thêm 2 dòng `("QT-TP-VT"/"QT-TP-TB", …)`. ## 2. Trục 2 — Menu mất im lặng (map code↔int, staticMap `Hdc_*`) -(đang đo) +Đếm bảng, không đếm cảm giác: +- `MenuKeys.cs:44` `ContractTypeCodes` = **9 phần tử** (chèn 2 mã NGAY SAU `ThauPhu` ⇒ nhãn `01. · 01A. · 01B. · 02.` đúng thứ tự). +- `TYPE_CODE_TO_INT` ×3 bản = **9 entry** mỗi bản (đo bằng diff: mỗi bản +2 dòng `ThauPhuVatTu: 8` / `ThauPhuThietBi: 9`). +- `Hdc_*` staticMap ×2 app = **9 entry** (`fe-admin/Layout.tsx:58-69`, `fe-user/Layout.tsx:88-99`) — đây là key TĨNH, không đi qua regex `Ct_*`, và đã được nới. +- `ContractTypeLabel` (`fe-*/src/types/forms.ts:24-34`) = **9 entry** ×2 app, 2 file **byte-identical** (cùng hunk diff). +- `usePipelineStages.ts:54-57` `CONTRACT_TYPE_CODES` = 9 ×2 app ⇒ `CONTRACT_MENU_KEYS` + `HARD_COPY_MENU_KEYS` nở by-construction. +- Nhãn seed BE: `DbInitializer.cs:2054-2060` (`typeLabels`) + `:2382-2384` (`Hdc_*` labelBackfill) + `:2392-2394` (`ContractTypeGroup`) — cả 3 bảng đều +2. Nhãn GROUP và nhãn `typeLabels` **khớp chữ** (`01A. HĐ Thầu phụ (Nhân công & Vật tư)` ở cả hai). +- Designer V2 `AwV2_Hd*` + workflow V2 `HD-TP-VT`/`HD-TP-TB`: sinh từ `ContractApprovalWorkflowFamily.Members` (`DbInitializer.cs:787` và `:2315`) ⇒ 9 member tự nở, không có bảng chép tay thứ hai ở BE. +- `contractWorkflowFamily.ts` (fe-admin) = 9 member; `INT_TO_HD_CODE` (fe-user) = 9 entry. + +**KHÔNG tìm thấy bảng nào thiếu entry.** 0-hit ở trục này có control-dương: cùng bộ lệnh vẫn bắt được +F-1 (bảng `SeedWorkflowDefinitionsAsync` thiếu 2) ⇒ lưới có răng, không phải lưới rỗng. + +**F-2 (nên-sửa) — nhãn menu `01A.`/`01B.` chỉ vào DB qua đường `labelBackfill`, cần xác nhận đường này chạy trên prod.** +`DbInitializer.cs:2382` là bảng nhãn dùng cho backfill row đã tồn tại; row `Hdc_ThauPhuVatTu` là row **MỚI** +nên đi đường tạo mới. Không đo được trên prod từ đây (read-only, chưa deploy) ⇒ **acceptance sau deploy phải mở +sidebar 2 app đếm đủ 9 nhóm HĐ + 9 leaf Bản cứng**, đừng suy từ build xanh. ## 3. Trục 3 — `paymentTerms` có bị xoá không (3 đường ghi) @@ -37,3 +69,49 @@ ## 7. VERDICT (chờ) + +--- + +> 🔴 **Trục 3-6 dưới đây do LEAD tự đo** — lane `reviewer` chết sau trục 2 (#53, lần thứ 5 trong phiên). +> Khai rõ để không đọc nhầm là vai độc lập chấm: đây là **lead tự chấm mình**, yếu hơn. +> Trục 1-2 ở trên GIỮ NGUYÊN của vai (không sửa lời vai). + +## 3. Trục 3 — `paymentTerms` có bị xoá không — ✅ **PASS** + +3 đường ghi soi đủ: +- `setBodyField` (`contractDraftData.ts:127-129`): `{...x, body:{...x.body,[key]:value}}` ⇒ spread cấp-1 **giữ mọi nhánh khác**. +- `serializeDraftData` (`:122-124`): `JSON.stringify(x)` thuần ⇒ không lọc field. +- `parseDraftData` (`:94-116`): dùng `const {schemaVersion, kind, body, ...rest}` rồi trả `{...rest, …}` — **`...rest` giữ `paymentTerms`**. Mạnh hơn yêu cầu: còn xử **2 dạng phiếu cũ** — JSON hỏng ⇒ giữ NGUYÊN CHUỖI vào nhánh `paymentTerms` (`:102`); object không có dấu-nhận-biết bao ngoài ⇒ đỗ nguyên vào `paymentTerms` (`:110`, đúng payload seed `DbInitializer.cs:1501`). +⇒ Ca lo ngại (*HĐ từ KHKK có `paymentTerms` → sửa 1 ô thân → Lưu*) **KHÔNG mất dữ liệu**. + +## 4. Trục 4 — Khoá bản cứng có RĂNG thật — ✅ **PASS** + +- Set 1 chiều: `ContractAttachmentFeatures.cs:103-104` `SealedAt ??=` + `SealedByUserId ??=` ⇒ upload lần 2 **không** ghi đè mốc đầu. +- Rào xoá: `:180` `att.Purpose == SealedCopy && contract?.SealedAt is not null` ⇒ **ném TRƯỚC `Remove()`** (`:186`) và trước `storage.DeleteAsync`. Bám vào **đính kèm ĐÓ là bản cứng**, KHÔNG chặn mọi đính kèm ⇒ file rác vẫn dọn được. +- Rào sửa HĐ: `ContractFeatures.cs:233` `SealedAt is not null`. +- 🔴 Phép thử "có đường nào set `SealedAt = null`": **2 hit** — đo tiếp thì cả 2 là **giá-trị mặc-định tham-số DTO** (`ContractDtos.cs:33`, `:118` `DateTime? SealedAt = null`), **không phải gán vào entity** ⇒ vô hại. *(Nếu dừng ở con số 2 thì đã báo động nhầm.)* + +## 5. Trục 5 — Key ô trùng ngoài ô-lặp cố ý — ✅ **PASS** + +`body002_02`: trùng đúng 5 key — `ben_b_ten`×3 · `ben_b_dia_chi`/`dia_diem`/`hang_muc`/`so_hd`×2. +`body002_03`: đúng 5 key đó **+ `du_an`×2** (đã khai trước là ô lặp riêng của bản Thiết bị). +⇒ **0 key trùng ngoài danh sách cố ý** — không có 2 ô khác nghĩa ghi đè nhau. + +## 6. Trục 6 — Pre-fill khớp `slot.key` — ✅ **PASS (17/17)** + +BE `BuildDraftData` đổ **17 key** (`Put("…")`): `so_hd · du_an · hang_muc · dia_diem · goi_thau · chu_dau_tu · cong_trinh · cong_trinh_dia_diem · gia_tri_so · ben_b_{ten,dia_chi,dien_thoai,fax,ma_so_thue,nguoi_dai_dien,chuc_vu,so_tai_khoan}`. +FE có **46 slot** (02=32 · 03=45). Phép trừ: **`BE − FE` = 0** ⇒ không key nào đổ vào hư-không. Kiểm chéo từng bản: `BE − 002.02` = 0 và `BE − 002.03` = 0 ⇒ **cả hai** bản đều nhận đủ 17. +🔸 Thước đầu của lead **HỎNG** (regex bắt `["…"]=` / `Add("…")`, ra 0 key BE = vô lý) — sửa sang `Put\(\s*"…"` mới ra 17. Ghi lại vì đúng lớp *0-hit do thước, không phải do vắng*. +N+1: 6 lệnh `await db.` trong toàn handler, 2 `foreach` (`:144`, `:325`) đều là vòng **gán**, không query. + +## 7. VERDICT + +**`G5-REVIEW: PASS-WITH-FINDINGS — 2 finding (0 CHẶN-DEPLOY)`** + +| # | Mức | Nội dung | Xử | +|---|---|---|---| +| F-1 | ghi-nhận → **ĐÃ VÁ** | `DbInitializer.cs:1000-1008` `SeedWorkflowDefinitionsAsync` thiếu 2 loại ⇒ Designer V1 của 01A/01B hiện khung rỗng (không nổ, chính sách rơi về registry) | lead thêm `QT-TP-VT` + `QT-TP-TB` | +| F-2 | nên-sửa | Nhãn `01A.`/`01B.` là **row MỚI** nên đi đường tạo-mới chứ không qua `labelBackfill`; **không đo được từ local** | ⇒ **acceptance SAU deploy**: mở sidebar 2 app đếm đủ **9 nhóm HĐ + 9 leaf Bản cứng**, đừng suy từ build xanh | +| *(DB)* | **ĐÃ VÁ** | `database-reviewer-style` F-1: vòng reconcile template ungated hồi-sinh mẫu admin đã xoá | thu hẹp còn 2 FormCode | + +🔸 **Chưa chấm:** `front-end-reviewer-style` + `back-end-reviewer-style` — cả 2 lane **không tạo được artifact**. Trục style (đặt tên · chia component · `ContractBodyView.tsx` 42 KB một file · `ThauPhuFamily` ở Application ⟂ `ContractApprovalWorkflowFamily` ở Domain) **CHƯA CÓ AI SOI** — khai thẳng, không tính là đã phủ.