wal: flush 20260810 S188 dot-2 run-trace W3-W6
This commit is contained in:
@ -21,18 +21,20 @@ chain:
|
|||||||
[!] wf: hmw đợt-1 wf_58d6f783-395 XONG — t0 CLEAN (4 test W1a/W1b, 3 ĐỎ-hành-vi + 1 control,
|
[!] wf: hmw đợt-1 wf_58d6f783-395 XONG — t0 CLEAN (4 test W1a/W1b, 3 ĐỎ-hành-vi + 1 control,
|
||||||
+295/-0) · t1 code LAND (+89 Features +76 Dtos, 0-mig ✓, return rỗng + KHÔNG sub-file) ·
|
+295/-0) · t1 code LAND (+89 Features +76 Dtos, 0-mig ✓, return rỗng + KHÔNG sub-file) ·
|
||||||
t2 PARTIAL (types ×2 + fe-user MyContracts xong; fe-admin mirror + InboxPage ×2 THIẾU).
|
t2 PARTIAL (types ×2 + fe-user MyContracts xong; fe-admin mirror + InboxPage ×2 THIẾU).
|
||||||
W7-FINISH VỀ PASS (17:5x): admin=ContractsListPage áp logic + vá 3 lỗi client-filter-sau-
|
[x] ĐỢT-1 SHIPPED + VERIFIED: gate 672/672 · reviewer PASS_WITH_FLAGS-8 (F-1/F-2 vá ngay ×2app;
|
||||||
pagination; InboxPage BLOCKED ĐÚNG (BE GetMyInboxQuery 0 param + Take(100)) ⇒ NỢ MỚI
|
F-4→spec + [carry:w7b-inbox-param] (+F-5 ContractCreatePage); F-3 ActiveOthers treo W6) ·
|
||||||
[carry:w7b-inbox-param] (BE thêm ?type= inbox rồi FE gỡ client-filter — xếp đợt sau, hiện
|
push bc309c55..0149f106 (5 commit) · POST 4/4: run #481 success · bundle rotate 2 app
|
||||||
client-filter vẫn chạy); types-pair lệch-baseline-có-chủ-đích, khối THÊM identical ×2 ✓;
|
(DeBPqp_c/CSIiQrhk) · smoke 200 · /api/contracts?type=1 → 401. cicd-trace đứt giữa (PRE+dự-
|
||||||
build ×2 EXIT 0. CÒN BAY: W1-GREEN. Sau đó: build+test full → reviewer → commit+push → cicd.
|
đoán đủ, sub-cicd-dot1.md) — POST lead tự đo. #53 lượt này ×5 (2 impl + w1green + reviewer +
|
||||||
|
cicd), đĩa cứu 5/5. NỢ closeout: STATUS mốc bundle lão-hoá (PRE đã ≠ S164) + baseline 672.
|
||||||
[ ] W3→W4→W5(+ruột-88)→W6→W10 — MỞ HẾT theo YC-024; W8 HOÃN · W9 HỦY; W3 roster sao khuôn
|
[ ] W3→W4→W5(+ruột-88)→W6→W10 — MỞ HẾT theo YC-024; W8 HOÃN · W9 HỦY; W3 roster sao khuôn
|
||||||
KHKK (implementer đo seeder `SeedKhkkGroupWorkflowsAsync` thật, không đoán)
|
KHKK (implementer đo seeder `SeedKhkkGroupWorkflowsAsync` thật, không đoán)
|
||||||
[ ] chờ anh: (74) rotate + (76)(77)(78)(79) + tồn (22)(67)(72) + lệnh "ổn rồi" mở W8-authz.
|
[ ] chờ anh: (74) rotate + (76)(77)(78)(79) + tồn (22)(67)(72) + lệnh "ổn rồi" mở W8-authz.
|
||||||
(73) + (80)-(88) ĐÓNG @S188
|
(73) + (80)-(88) ĐÓNG @S188
|
||||||
|
|
||||||
next: fan-out đợt-1 W1→W2→W7 (hmw Opus MAX, args.run=2026-08-10-S187-duyet-hd-tu-khkk) theo
|
next: YC-025 (anh @S188 "làm tiếp hết, review 1 lần"): W3(seed 7 HD-* + 7 key, 2 lane đang bay)
|
||||||
spec §2/§3, TỪNG wave gate; xong ship thì W3→W4→W5(+88)→W6→W10.
|
→ W4(guard 4-cửa + fault-inject) → W5(card F1 + ruột-88) → W6(panel 2-nút + Đ2 + quyết F-3)
|
||||||
|
→ gate + reviewer TOÀN CỤM → push 1 LƯỢT → cicd → W10 dry-run Dev → báo anh review.
|
||||||
|
|
||||||
verify:
|
verify:
|
||||||
run: ls .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/ # run.md + 4 sub + spec
|
run: ls .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/ # run.md + 4 sub + spec
|
||||||
|
|||||||
@ -0,0 +1,116 @@
|
|||||||
|
# sub-cicd-dot1 — Verify deploy đợt-1 YC-023 (range `bc309c55..0149f106`)
|
||||||
|
|
||||||
|
> Vai: cicd-monitor (READ-only). Ghi TRONG LÚC LÀM (chống #53). Kết bằng `== HẾT ==`.
|
||||||
|
> Spawn: 2026-08-10 ~18:21. Push: 18:20:41.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CHÂN 1 — Push + path-filter (Stage 1)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git log -1 --format='%H %ci %s'
|
||||||
|
0149f106f98dcea51669c49028201d410b0a32ed 2026-08-10 18:20:41 +0700 [CLAUDE] Docs: YC-024 — 9 đáp án (80)-(88)…
|
||||||
|
$ git log origin/main..HEAD --oneline → (RỖNG) ⇒ đã push hết
|
||||||
|
```
|
||||||
|
|
||||||
|
Range 5 commit (khớp đề bài):
|
||||||
|
|
||||||
|
| sha | subject |
|
||||||
|
|---|---|
|
||||||
|
| `5a77f4f1` | wal: flush 20260810 S188 dot-1 run-trace + governance state |
|
||||||
|
| `7bad4ae1` | Workflow: W1 gỡ 2 bom per-type type-3 — deactivate per-Code + overview family; test-before RED→GREEN **+4 test (672/672)** |
|
||||||
|
| `01a7c0b8` | Contract: W2 BE-DTO nền GĐ3 — `ContractSourceDto` reverse-join KHKK + CurrentWorkflowStepIndex + WorkflowSteps |
|
||||||
|
| `502f4d2e` | Contract: W7 server-filter per-type — ContractsListPage (+3 fix badge/pagination) + MyContractsPage |
|
||||||
|
| `0149f106` | Docs: YC-024 — 9 đáp án vào sổ + HANDOFF + spec đợt-1 |
|
||||||
|
|
||||||
|
**Path-filter (gotcha #41, 7-glob):** 74 file đổi. File **non-ignored** có mặt ⇒ CI PHẢI chạy:
|
||||||
|
- `src/Backend/SolutionErp.Application/{ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs, Contracts/ContractFeatures.cs, Contracts/Dtos/ContractDtos.cs}`
|
||||||
|
- `fe-admin/src/pages/contracts/ContractsListPage.tsx` · `fe-admin/src/types/contracts.ts`
|
||||||
|
- `fe-user/src/pages/{InboxPage.tsx, contracts/MyContractsPage.tsx}` · `fe-user/src/types/contracts.ts`
|
||||||
|
- `tests/SolutionErp.Infrastructure.Tests/Application/UpdateAwDefinitionTests.cs`
|
||||||
|
|
||||||
|
⇒ **KHÔNG docs-only.** ✅ CHÂN 1 PASS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PRE-SNAPSHOT (chụp kịp giờ — bài học S168)
|
||||||
|
|
||||||
|
Spawn 18:21 / push 18:20:41 / run `status=running` lúc 18:22 ⇒ bước `deploy` (cuối pipeline, ~5-6 phút) **CHƯA chạm đĩa**. Đo LIVE lúc **18:22:29**:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s https://admin.solutions.com.vn/ | grep -oE '/assets/index-[A-Za-z0-9_-]+\.(js|css)'
|
||||||
|
/assets/index-BNiYPCoJ.js
|
||||||
|
/assets/index-Desb-yZr.css
|
||||||
|
$ curl -s https://eoffice.solutions.com.vn/ | grep -oE '/assets/index-[A-Za-z0-9_-]+\.(js|css)'
|
||||||
|
/assets/index-BYLXK11b.js
|
||||||
|
/assets/index-BGUiinrQ.css
|
||||||
|
```
|
||||||
|
|
||||||
|
🔴 **Mốc PRE của đề bài đã LÃO HOÁ** — đề bài + `STATUS.md:9` ghi mốc **@S164**: admin `B43R6Y17` · user `NLI5umBg`. Đo live: admin **`BNiYPCoJ`** · user **`BYLXK11b`** ⇒ đã có ≥1 deploy giữa S164 và giờ mà STATUS chưa lật. **Dùng mốc ĐO (`BNiYPCoJ`/`BYLXK11b`) làm PRE, KHÔNG dùng mốc đề bài** (bài học #438: mốc PRE lão hoá sau MỖI deploy).
|
||||||
|
*(Ghi chú: `STATUS.md:479` mà đề bài trỏ hiện là row `Sub-agents`, không phải row bundle — con-trỏ-số-dòng đã trôi.)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CHÂN 2 — Gitea run (Stage 2/3)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s ".../api/v1/repos/vietreport-admin/solution-erp/actions/tasks?limit=6"
|
||||||
|
id=594 run_number=481 head_sha=0149f106f98dcea… status=running workflow=deploy.yml
|
||||||
|
```
|
||||||
|
Khớp `head_sha` = HEAD (không khớp theo run_number — gotcha cancelled-shift). Run cuối của range ⇒ đúng nếp gotcha #86.
|
||||||
|
|
||||||
|
_(đang poll — ghi tiếp bên dưới)_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CHỌN MARKER + HIỆU CHUẨN THƯỚC (làm TRƯỚC khi deploy land)
|
||||||
|
|
||||||
|
### FE — 3 ứng viên, chỉ 1 sống
|
||||||
|
|
||||||
|
Diff FE **không có literal chuỗi mới nào** (toàn comment + logic). Comment bị Vite strip, tên biến local (`parsedType`/`typeFilter`) bị minify ⇒ **0 bit** (bài học #436). Phải soi kỹ:
|
||||||
|
|
||||||
|
| ứng viên | đo trên bundle PRE | phán |
|
||||||
|
|---|---|---|
|
||||||
|
| `approvedAmountTotal` (đề bài gợi ý) | — | ❌ **LOẠI trước khi đo**: chỉ nằm trong `types/contracts.ts` dạng `export type` ⇒ **type-only, TS xoá sạch lúc compile**, không phải tree-shake |
|
||||||
|
| `Number.NaN` | admin **0** · user **0** | ❌ **DEAD MARKER** — minifier viết lại `Number.NaN` → `NaN` trần. *Nếu không hiệu chuẩn mà cứ đo, POST cũng 0 ⇒ báo FAIL OAN.* |
|
||||||
|
| `Number.isFinite` | admin **4** · user **4** | ✅ **SỐNG** — truy cập property built-in, minifier không đổi được |
|
||||||
|
|
||||||
|
**Chứng ContractsListPage nằm TRONG chunk `index-*.js`** (không bị code-split ra chunk khác — nếu bị thì count sẽ đứng im và ta kết luận sai):
|
||||||
|
```
|
||||||
|
$ grep -o -F 'Tìm mã / tên / NCC' pre-admin.js | wc -l → 1 (literal của CHÍNH ContractsListPage)
|
||||||
|
$ grep -o -F 'Tìm theo mã / tên / NCC' pre-user.js | wc -l → 3
|
||||||
|
$ grep -rn "React.lazy\|lazy(" fe-admin/src/App.tsx fe-admin/src/main.tsx → 0 hit
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vị trí marker trong source (neo SHA, không dùng `HEAD~n`):**
|
||||||
|
```
|
||||||
|
base bc309c55 → fe-admin: KhkkDetailContent.tsx:666 + HardCopiesPage.tsx:68 = 2
|
||||||
|
head 0149f106 → fe-admin: + ContractsListPage.tsx:50 = 3 (Δ+1)
|
||||||
|
base bc309c55 → fe-user : KhkkDetailContent.tsx:666 + HardCopiesPage.tsx:68 = 2
|
||||||
|
head 0149f106 → fe-user : + MyContractsPage.tsx:36 = 3 (Δ+1)
|
||||||
|
```
|
||||||
|
|
||||||
|
🎯 **DỰ ĐOÁN KHAI TRƯỚC KHI ĐO (#436):** bundle POST phải là **admin 5 · user 5** (4+1 mỗi app). Sai số nào khác = phải điều tra, không được vuốt.
|
||||||
|
|
||||||
|
### BE — probe DLL 2 khung UTF-8 ⟂ UTF-16 (nếp S177)
|
||||||
|
|
||||||
|
Script `dllprobe.ps1` scp lên VPS rồi `powershell -File` (KHÔNG nhét inline — SSH→PS nuốt `$`/`"`). Đọc `SolutionErp.Application.dll` ra byte → decode Latin1 giữ nguyên byte → đếm 2 khung.
|
||||||
|
|
||||||
|
**ĐO PRE lúc 18:25:18** (`LWT=2026-08-10 12:55:16`, `LEN=2454528` — bản deploy TRƯỚC đó):
|
||||||
|
|
||||||
|
```
|
||||||
|
MARK ActiveOthers utf8=0 utf16=0 ← control-ÂM độc quyền (W1)
|
||||||
|
MARK PerCodeDeactivationTypes utf8=0 utf16=0 ← control-ÂM độc quyền (W1)
|
||||||
|
MARK ContractSourceDto utf8=0 utf16=0 ← control-ÂM độc quyền (W2)
|
||||||
|
MARK ApprovedAmountTotal utf8=0 utf16=0 ← control-ÂM độc quyền (W2)
|
||||||
|
MARK ContractWorkflowStepDto utf8=0 utf16=0 ← control-ÂM độc quyền (W2)
|
||||||
|
MARK TenHangMucs utf8=0 utf16=0 ← control-ÂM độc quyền (W2)
|
||||||
|
MARK AwDefinitionDto utf8=1 utf16=1 ← ✅ CONTROL-DƯƠNG (có từ trước)
|
||||||
|
MARK AllowApproverFinalize utf8=3 utf16=1 ← ✅ CONTROL-DƯƠNG (có từ trước)
|
||||||
|
MARK CurrentWorkflowStepIndex utf8=5 utf16=3 ← ✅ CONTROL-DƯƠNG (tên trùng phía KHKK)
|
||||||
|
```
|
||||||
|
|
||||||
|
⇒ **Thước SỐNG** (control-dương ≥1 cả 2 khung) **và** 6 marker mới **vắng thật** trước deploy. Đây là differential ĐÚNG NGHĨA: 0 → ≥1, đo trên cùng một thước, không phải "0-hit rồi đoán".
|
||||||
|
|
||||||
|
🎯 **DỰ ĐOÁN:** POST cả 6 marker phải **≥1 ở khung utf8**; `LWT` phải rơi trong cửa-sổ deploy hôm nay ~18:2x; `LEN` phải đổi.
|
||||||
|
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
# sub-implementer-backend-w3 — W3 seed họ workflow type-3 + 7 key Designer (BE data-op, 0-mig)
|
||||||
|
|
||||||
|
> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `implementer-backend` · Opus 5 MAX · 2026-08-10.
|
||||||
|
> Đáp án owner đã chốt (YC-024): (80) trục **7-LOẠI** · (81) **PA-A** mỗi loại 1 mã · (83) **roster SAO KHUÔN KHKK**, anh tự chỉnh Admin Designer sau.
|
||||||
|
> Ghi TỪNG MỤC trong lúc làm (chống #53). File này = chân lý; return chỉ là tóm.
|
||||||
|
|
||||||
|
## §0 — Ground-truth ĐO TƯƠI (không chép từ brief)
|
||||||
|
|
||||||
|
| # | Đo gì | Lệnh / nguồn | Kết quả |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | Cấu trúc trạm/người THẬT của 8 workflow KHKK trên Dev | `sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Dev -E` — chữ ký MD5 per-Code trên tập `(StepOrder/Dept/StepName/LevelOrder/Email/Finalize)` | **8/8 GIỐNG HỆT**: cùng hash `7EB6CD219E89AFA4C2DE55DE63377107`, 9 level-row/workflow ⇒ **dùng KHUÔN CHUNG** (không phải "lấy N1 + khai") |
|
||||||
|
| 2 | Khuôn chung đó là gì | cùng truy vấn, `KHKK-N1` | Bước 1 `Phòng Cung Ứng`/PRO → Cấp 1 OR-of-3 (duy·long·phuong) + Cấp 2 (tra.bui) · Bước 2 `P. Kiểm soát Chi phí`/CCM → Cấp 1 OR-of-3 (binh.le·luu.tran·nguyen.ho) + Cấp 2 (chuong.phan, `Finalize=1`) · Bước 3 `Ban Giám đốc`/BOD → Cấp 1 (truong.nguyen) |
|
||||||
|
| 3 | Roster CODE (11 email) vs DB Dev (9 slot) | `DbInitializer.cs:554-585` vs đo (1) | Dev thiếu 2 user (`binh.lethanh@`, `truong.letrandang@`) ⇒ **nhánh "bỏ slot + warning" đã chạy THẬT trên Dev** — không phải đường lý thuyết |
|
||||||
|
| 4 | Type-3 hiện có gì | `SELECT ... WHERE ApplicableType=3` | **1** workflow `QT-HD-V2-001` v01 (sample UAT S29), 1 Bước. Dev **0** workflow type-1/2 (khớp Surprise #7 BE) |
|
||||||
|
| 5 | `\|MenuKeys.All\|` trước sửa | đếm tay `MenuKeys.cs:222-250` + đối chiếu STATUS `:474` | **113** (khớp canonical) |
|
||||||
|
| 6 | Engine type-3 có hiểu `AllowApproverFinalize` không | `grep -n "Finalize" ContractWorkflowService.cs` | **0 hit** (chỉ `skipToFinal` = cờ KHÁC). Control dương: PE `:870` CÓ · KHKK `ContractSigningPlanWorkflowService.cs:286` CÓ ⇒ **thước sống**, 0-hit là vắng THẬT |
|
||||||
|
|
||||||
|
## §1 — 1 quyết kỹ thuật CÓ KHAI: cờ `AllowApproverFinalize` KHÔNG sao nguyên xi
|
||||||
|
|
||||||
|
Roster KHKK bật `Level2Finalize=true` ở trạm CCM (a Chương được kết thúc, không lên CEO). Đo (6) cho thấy **máy duyệt type-3 KHÔNG có nhánh finalize** ⇒ seed `true` cho họ HĐ = **config-lie #78**: Designer/UI khoe "cấp này được kết thúc" mà engine vẫn đi tiếp BGĐ.
|
||||||
|
|
||||||
|
⇒ Seed HĐ giữ **NGUYÊN trạm/người/thứ tự/OR-of-N** (đúng "sao khuôn" theo (83)), riêng cờ finalize = `false` + ghi vết ngay tại chỗ. Owner bật lại 1 click trong Designer khi máy type-3 có nhánh finalize (wave sau).
|
||||||
|
|
||||||
|
## §2 — 4 mảnh land (điền dần)
|
||||||
|
|
||||||
|
_(đang thi công — mục này cập nhật sau mỗi mảnh)_
|
||||||
|
|
||||||
|
== ĐANG VIẾT ==
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
# W4 — guard 4-cửa pin/re-pin quy trình HĐ (BE, 0-migration)
|
||||||
|
|
||||||
|
> vai: implementer-backend · run `2026-08-10-S187-duyet-hd-tu-khkk` · đề bài: lead W4
|
||||||
|
> đáp án owner neo vào: (86) KHOÁ CỨNG không đổi chéo loại (mirror KHKK/Duyệt NCC) · (80) trục 7-LOẠI · (81) PA-A
|
||||||
|
|
||||||
|
## 0. Đo TRƯỚC khi sửa (không nhận tiền-đề từ đề bài)
|
||||||
|
|
||||||
|
### 0.1 Nhà 7 mã ĐÃ LAND (dùng, cấm gõ lại chuỗi)
|
||||||
|
|
||||||
|
`src/Backend/SolutionErp.Domain/Contracts/ContractApprovalWorkflowFamily.cs` — có trên đĩa (untracked, W3 đang land),
|
||||||
|
`CodePrefix = "HD-"` · `Members` 7 bộ ba (Type, Code, ShortName) · `CodeFor(type)` · `KeyToken(code)`.
|
||||||
|
Consumer hiện có: `DbInitializer.cs:756/:782` (seed 7 workflow) + `MenuKeys.cs:155-156` (7 key Designer) + test W3.
|
||||||
|
⇒ W4 là consumer thứ 3, gọi `CodeFor`, KHÔNG khai lại mã.
|
||||||
|
|
||||||
|
### 0.2 Dev DB — item 3 của đề bài ("có HĐ nào pin workflow ngoài họ không?")
|
||||||
|
|
||||||
|
```
|
||||||
|
sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Dev -E
|
||||||
|
SELECT COUNT(*) , SUM(CASE WHEN ApprovalWorkflowId IS NOT NULL THEN 1 ELSE 0 END) FROM Contracts;
|
||||||
|
→ 7 | 0 ← 7 HĐ tồn tại (control dương: bảng KHÔNG rỗng) · 0 HĐ pin V2
|
||||||
|
SELECT Code,Version,ApplicableType,IsActive,IsUserSelectable FROM ApprovalWorkflows WHERE ApplicableType=3;
|
||||||
|
→ QT-HD-V2-001 | 1 | 3 | 1 | 1 ← DUY NHẤT, và mã NGOÀI họ HD-*
|
||||||
|
SELECT ApplicableType, COUNT(*) ... GROUP BY ApplicableType;
|
||||||
|
→ 3:1 · 4:1 · 5:1 · 6:1 · 7:1 · 9:1 · 10:8 ← control dương: type-10 có 8 (họ KHKK đã seed) ⇒ thước sống
|
||||||
|
```
|
||||||
|
|
||||||
|
**Kết luận đo:** Dev **0 HĐ pin quy trình ngoài họ** (vì 0 HĐ pin V2 nào cả — 7/7 đều `NULL`).
|
||||||
|
Quy trình type-3 DUY NHẤT đang sống trên Dev lại là `QT-HD-V2-001` (mã ngoài họ) — W3 seeder `HD-*` chưa chạy trên Dev
|
||||||
|
(code có trên đĩa, chưa restart API). ⇒ Ngay hôm nay, người dùng mở màn tạo HĐ chỉ thấy đúng 1 lựa chọn: `QT-HD-V2-001`.
|
||||||
|
|
||||||
|
### 0.3 🔴 ĐỌC ASSERT TRƯỚC KHI CHỌN HÌNH RÀO (bài S187 W1-GREEN)
|
||||||
|
|
||||||
|
Đề bài viết: *"`Code` phải == `CodeFor(contractType)`; workflow mã NGOÀI họ/khác loại ⇒ lỗi"* (dạng **bằng-nhau-tuyệt-đối**).
|
||||||
|
Đo trên đĩa thì hình đó **ĐỎ HÀNG LOẠT** ở tests/** — mà tests/** tôi **CẤM sửa**:
|
||||||
|
|
||||||
|
| Test (không được sửa) | Pin mã gì | Hình bằng-nhau-tuyệt-đối cho kết quả |
|
||||||
|
|---|---|---|
|
||||||
|
| `CreateContractFromSigningPlanTests` T1-T11 (`:186` `QT-HD-V2-K7-{tag}`, `TestType=HopDongThauPhu` ⇒ họ đòi `HD-TP`) | ngoài họ | **ĐỎ** cả 11 ca, kể cả ĐỐI CHỨNG DƯƠNG `:496` |
|
||||||
|
| T8 `:543` — HĐ do cầu đẻ đem TRÌNH qua `TransitionAsync` | ngoài họ | **ĐỎ** nếu cửa TRÌNH cũng bằng-nhau-tuyệt-đối |
|
||||||
|
| `ContractV2PipelineTests` `:609` `QT-CT-T7` + `:624` submit | ngoài họ | **ĐỎ** |
|
||||||
|
| `ContractWorkflowServiceApproveV2Tests` `QT-CT-TERMINAL` / `QT-CT-F2` | ngoài họ | ĐỎ nếu siết cả đường duyệt |
|
||||||
|
|
||||||
|
Và chính chữ owner (86) là **"mirror KHKK/Duyệt NCC"** — khuôn KHKK
|
||||||
|
(`ContractSigningPlanWorkflowGuards.cs:56-64`) khai TƯỜNG MINH semantics: *mã TRONG họ phải khớp · mã NGOÀI họ CHO QUA
|
||||||
|
có chủ đích ("không mang thông tin nhóm ⇒ không có gì để đối chiếu, chặn nó là bịa ràng buộc")*.
|
||||||
|
|
||||||
|
⇒ **Hình đã chọn = KHOÁ THEO KHÔNG-GIAN-TÊN `HD-`** (chặt hơn KHKK một nấc, vẫn khớp disk + khớp chữ owner):
|
||||||
|
|
||||||
|
```
|
||||||
|
code bắt đầu bằng "HD-" ⇒ PHẢI == CodeFor(type) (sai ⇒ 409 "gắn chéo loại")
|
||||||
|
code NGOÀI "HD-" ⇒ CHO QUA (QT-HD-V2-001, QT-CT-*, quy trình admin tự đặt)
|
||||||
|
```
|
||||||
|
|
||||||
|
Nó bắt ĐÚNG cái owner sợ (pin `HD-MB` lên HĐ Thầu phụ = đổi chéo loại) **và** bắt thêm ca gõ nhầm trong chính
|
||||||
|
không-gian-tên họ (`HD-TPX`, `HD-TP-V2` — sai thì nổ, không im lặng), mà **0 test ĐỎ** (0 mã test nào bắt đầu bằng `HD-`;
|
||||||
|
đã grep toàn tests/: các mã là `QT-*`, `K7-*`, `BTBM`, `FLOCK01`).
|
||||||
|
Lệch đề bài đúng 1 nấc này khai ở §4 để lead bác nếu muốn siết tiếp (siết = phải sửa tests/, việc của test-specialist).
|
||||||
|
|
||||||
|
### 0.4 Loại exception — đề bài nói "ValidationException", đĩa nói KHÔNG
|
||||||
|
|
||||||
|
- `Common/Exceptions/ApplicationException.cs:10-25`: `ValidationException` **KHÔNG có ctor nhận message**
|
||||||
|
(message cố định "Có lỗi trong dữ liệu gửi lên.", chỉ mang `Errors` dict) ⇒ "ValidationException tiếng Việt rõ ràng"
|
||||||
|
không dựng được nếu không sửa file exception dùng chung.
|
||||||
|
- `tests/.../CreateContractCommandApplicableTypeTests.cs:88-89` assert **`ConflictException`** + `"*ApplicableType=Contract*"`,
|
||||||
|
và chú thích `:22-24` nói thẳng "spec mention ValidationException — thực tế throw ConflictException".
|
||||||
|
- Khuôn KHKK `EnsureWorkflowGroupMatchAsync` cũng `ConflictException`.
|
||||||
|
⇒ **Dùng `ConflictException` (409) + message tiếng Việt rõ ràng**; `NotFoundException` giữ cho nhánh id-không-tồn-tại
|
||||||
|
(T7-a `:470` assert đúng loại này).
|
||||||
|
|
||||||
|
### 0.5 Ràng buộc chữ MESSAGE (2 test assert 2 chuỗi KHÁC NHAU cho CÙNG một vế)
|
||||||
|
|
||||||
|
Vế "sai ApplicableType" nay do 1 nhà chung phát ngôn, mà hai test đòi hai chuỗi:
|
||||||
|
- `CreateContractCommandApplicableTypeTests.cs:89` → `*ApplicableType=Contract*`
|
||||||
|
- `CreateContractFromSigningPlanTests.cs:476` (T7-b) → `*không khớp với Hợp đồng*`
|
||||||
|
⇒ message hợp nhất PHẢI chứa CẢ HAI:
|
||||||
|
`Quy trình '{Code}' áp dụng cho {ApplicableType}, không khớp với Hợp đồng (cần ApplicableType=Contract).`
|
||||||
|
3 message còn lại giữ nguyên văn bản bridge (`*ngừng hoạt động*` `:482` · `*chưa được ghim*` `:488`).
|
||||||
|
|
||||||
|
</content>
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
# sub-implementer-frontend-w5 — W5 card "Nguồn gốc KHKK" (F1) + vế (88) ruột pre-fill
|
||||||
|
|
||||||
|
> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `implementer-frontend` (WRITE, FE 2 app) · 2026-08-10.
|
||||||
|
> Đề bài: (1) card Nguồn gốc KHKK trong màn detail HĐ ×2 app; (2) ĐO vế (88) ruột pre-fill — làm TỐI THIỂU, 0 BE/0 types/0 schema; (3) mirror SHA-pair + `npm run build` ×2.
|
||||||
|
> Ghi TỪNG MỤC trong lúc làm (chống #53). File này là chân lý, return chỉ là tóm.
|
||||||
|
|
||||||
|
## §0 — ĐO ĐĨA TRƯỚC KHI GÕ (ground-truth, không chép đề bài)
|
||||||
|
|
||||||
|
**0a. Shape `ContractSource` — ĐỌC TYPES LÀM CHUẨN (đề bài dặn, xác nhận đúng):**
|
||||||
|
`fe-admin/src/types/contracts.ts:196-208` = `maKeHoach: string|null` · `planId: string` · `approvalGroup?: number|null` ·
|
||||||
|
`tenHangMucs: string[]` · `approvedAmountTotal?: number|null` · `purchaseEvaluationId?: string|null`.
|
||||||
|
Cắm vào `ContractDetail` ở `:182` dạng `source?: ContractSource | null` — comment `:174-181` khai rõ **2 nghĩa
|
||||||
|
KHÁC NHAU**: `undefined` = payload/cache CHƯA có W2; `null` = BE nói "HĐ này không có nguồn KHKK".
|
||||||
|
⇒ Điều kiện render phải là `c.source != null` (bắt CẢ 2), KHÔNG dùng `c.source !== null`.
|
||||||
|
|
||||||
|
**0b. Vỏ trang + nơi cắm card:**
|
||||||
|
- `components/contracts/ContractDetailContent.tsx` (257 dòng) = ruột dùng chung, **5 host**: admin
|
||||||
|
`ContractDetailPage:28` + `ContractsListPage:254`; user `ContractDetailPage:28` + `MyContractsPage:231` + `InboxPage:347`.
|
||||||
|
Sửa 1 file ⇒ card hiện ở cả 5 chỗ, 0 nhân bản.
|
||||||
|
- Khối cùng trang KHÔNG dùng shadcn `Card` (2 app **đều không có** `components/ui/Card.tsx` — đo `ls ui/`):
|
||||||
|
khuôn thật = `<section className="rounded-lg border border-slate-200 bg-white p-5">` + `<h2 className="mb-3 …">`.
|
||||||
|
⇒ card mới bám ĐÚNG khuôn đó, không đẻ primitive mới.
|
||||||
|
|
||||||
|
**0c. Route đích (grep `App.tsx` ×2 app) — CÓ SẴN cả 2:**
|
||||||
|
- `/khkk/:id` (admin `:110` · user `:84`) → `KhkkDetailPage`.
|
||||||
|
- `/purchase-evaluations/:id` (admin `:91` · user `:76`) → `PurchaseEvaluationDetailPage`.
|
||||||
|
- ✅ `?group=` KHÔNG phải param chết: `KhkkDetailPage.tsx:50-52` ×2 app đọc `searchParams.get('group')`,
|
||||||
|
validate **REGEX CHUỖI** `/^[1-8]$/` (không `Number()`) rồi lấy làm `backToList` = `/khkk/list?group=n`
|
||||||
|
⇒ giữ chỗ đứng + sidebar sáng đúng leaf nhóm. Truyền `?group=` là CÓ NGƯỜI TIÊU THỤ.
|
||||||
|
|
||||||
|
**0d. Nhãn nhóm — KHÔNG đẻ map thứ 2 (bài học S166-K4a):** `KHKK_APPROVAL_GROUP_LABELS`
|
||||||
|
(`types/khkk.ts:140-149`) đã tồn tại và **SHA-pair identical ×2 app** (`29aa5633…`) ⇒ import lại, không chép chuỗi.
|
||||||
|
|
||||||
|
**0e. Palette — đo trước khi gõ class:** `index.css` SHA-pair identical (`62c26bf6…`); `@theme` chỉ định nghĩa
|
||||||
|
`violet` ở stop **50/100/500/600/700** (`:50-55`), KHÔNG có 300/800. `PipelineStageFolders.tsx:41-46` chốt
|
||||||
|
tông theo GĐ: **GĐ1 PE = brand · GĐ2 KHKK = violet · GĐ3 HĐ = amberx · GĐ4 = teal**.
|
||||||
|
⇒ card nguồn-gốc dùng tông **violet** (đúng GĐ2), link PE dùng **brand** (đúng GĐ1), và **chỉ xài stop có thật**.
|
||||||
|
🔸 Ghi nhận đi ngang (KHÔNG sửa, ngoài scope): `KhkkDetailContent.tsx:477` dùng `violet-300`/`violet-800` —
|
||||||
|
2 stop này rơi về violet MẶC ĐỊNH của Tailwind (khác hệ màu `@theme`), lệch tông nhẹ; pre-existing.
|
||||||
|
|
||||||
|
**0f. Baseline hash 2 file sẽ chạm (trước khi sửa) — cả 2 vốn đã IDENTICAL ×2 app:**
|
||||||
|
`ContractDetailContent.tsx` = `cd5ce88f211d1a801cce0626e5f6a339233cadc4` (admin ≡ user)
|
||||||
|
`pages/contracts/ContractCreatePage.tsx` = `5ec906195be1d605b16f61ada8b3113bae8ab799` (admin ≡ user)
|
||||||
|
🔸 `types/contracts.ts` KHÁC nhau ×2 app (`66b2bd54` ⟂ `3004a6e7`) — diff = **bảng màu badge phase**
|
||||||
|
(fe-user brand-tinted `-800`, fe-admin `-700`, chú thích 2026-06-16 của anh). Lệch CÓ CHỦ ĐÍCH, **không phải
|
||||||
|
việc của W5** và đề bài CẤM đụng types ⇒ không chạm.
|
||||||
|
|
||||||
|
## §1 — Card "Nguồn gốc Kế hoạch ký kết" (F1)
|
||||||
|
|
||||||
|
**Landed:** `components/contracts/ContractDetailContent.tsx` ×2 app — 1 file, 5 host (admin
|
||||||
|
`ContractDetailPage`+`ContractsListPage`; user `ContractDetailPage`+`MyContractsPage`+`InboxPage`).
|
||||||
|
Vị trí: NGAY SAU `<section>` "Thông tin HĐ", TRƯỚC "Góp ý" (người mở HĐ cần biết nó đẻ từ đâu
|
||||||
|
trước khi đọc góp ý/chi tiết). +3 import: `Link` (react-router-dom) · `GitBranch` (lucide, đã
|
||||||
|
dùng sẵn ở `WorkflowSummaryCard.tsx:1` nên chắc chắn có trong version đang cài) ·
|
||||||
|
`KHKK_APPROVAL_GROUP_LABELS` (`@/types/khkk`).
|
||||||
|
|
||||||
|
**Ruột card (5 mảnh, đúng đề bài):**
|
||||||
|
1. Mã `src.maKeHoach` → `<Link to={`/khkk/${planId}${group ? `?group=${group}` : ''}`}>`.
|
||||||
|
2. Badge nhóm `Nhóm N{g} · {LABELS[g]}` khi `approvalGroup != null` — **import map, 0 chuỗi chép**.
|
||||||
|
3. `tenHangMucs` → chip list + **đếm ngay trên nhãn** `Hạng mục đưa vào HĐ (n)`.
|
||||||
|
4. `Σ approvedAmountTotal` qua **chính `fmtMoney` của file** (`v.toLocaleString('vi-VN') + ' VND'`)
|
||||||
|
— không đẻ formatter thứ 2.
|
||||||
|
5. Link "Mở phiếu Duyệt NCC →" `/purchase-evaluations/{peId}` khi có; không có ⇒ `—` (không giấu ô).
|
||||||
|
|
||||||
|
**3 quyết định có bằng chứng (không phải khẩu vị):**
|
||||||
|
- **Điều kiện render `src != null`** (loose) — bắt CẢ `undefined`; `types/contracts.ts:174-182`
|
||||||
|
khai 2 nghĩa khác nhau, cả 2 đều không có gì để vẽ.
|
||||||
|
- 🔴 **KHÔNG link khi `maKeHoach === null`** (lệch 1 chữ so với đề bài — xem FLAG-1 §5).
|
||||||
|
Đo: BE lấy mã qua query CÓ global filter (`ContractFeatures.cs:738-740`) ⇒ `maKeHoach === null`
|
||||||
|
⇔ phiếu cha **đã xoá mềm**; mà màn `/khkk/:id` đọc bằng `GetContractSigningPlanQuery`
|
||||||
|
(`ContractSigningPlanFeatures.cs:957-962`) **KHÔNG** `IgnoreQueryFilters` (grep cả folder: hit
|
||||||
|
`IgnoreQueryFilters` DUY NHẤT ở `:1383` = màn "Đã xoá") ⇒ link đó **chắc chắn 404**. Thay bằng
|
||||||
|
chữ NHÌN THẤY ĐƯỢC "(phiếu đã xoá) — không mở được phiếu gốc", **không** giấu sau tooltip
|
||||||
|
(bài S182: thông tin PHÂN BIỆT không được nằm sau hover).
|
||||||
|
- **Tông màu:** violet = GĐ2 (KHKK), brand = GĐ1 (PE) theo `PipelineStageFolders.tsx:41-46`;
|
||||||
|
chỉ dùng stop violet CÓ THẬT trong `@theme` (50/100/500/600/700). `violet-300/800` ở
|
||||||
|
`KhkkDetailContent.tsx:477` = pre-existing, KHÔNG sửa (ngoài scope).
|
||||||
|
|
||||||
|
**Đối chứng shape (không tin types một mình):** đọc thẳng BE `ContractSourceDto`
|
||||||
|
(`Contracts/Dtos/ContractDtos.cs`) = `PlanId · MaKeHoach · ApprovalGroup · PurchaseEvaluationId ·
|
||||||
|
TenHangMucs · ApprovedAmountTotal` ⇒ camelCase khớp TỪNG TÊN với `ContractSource` FE. BE **có
|
||||||
|
điền thật** (`ContractFeatures.cs:720-748` + `Source: sourceDto` `:839`), không phải field chết.
|
||||||
|
🔸 Lệch nhỏ khai luôn: BE `ApprovedAmountTotal` là `decimal` **non-null**, FE khai `?: number|null`
|
||||||
|
(siêu-tập phòng thủ) ⇒ card vẫn null-check, hiện `—` nếu vắng.
|
||||||
|
|
||||||
|
## §2 — Vế (88): ĐO bridge-map ⟂ màn edit Nháp
|
||||||
|
|
||||||
|
(đang ghi…)
|
||||||
|
|
||||||
|
## §3 — Mirror ×2 app + SHA-pair
|
||||||
|
|
||||||
|
(đang ghi…)
|
||||||
|
|
||||||
|
## §4 — Build ×2 + control-âm
|
||||||
|
|
||||||
|
(đang ghi…)
|
||||||
|
|
||||||
|
## §5 — FLAG / nợ treo
|
||||||
|
|
||||||
|
(đang ghi…)
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
# sub-implementer-frontend-w6 — W6-FE màn duyệt V2-trạm HĐ + Designer type-3 family-slice
|
||||||
|
|
||||||
|
> Lane: implementer-frontend (WRITE, FE-only). Run S187 YC-023 GĐ3.
|
||||||
|
> Đề bài: (1) `ContractWorkflowPanel` ×2 app mirror `KhkkWorkflowPanel` owner-trim 2 nút ·
|
||||||
|
> (2) wire vào `ContractDetailContent.tsx` ×2 app dual-render V1/V2 · (3) Designer type-3
|
||||||
|
> family-slice 7 mã `HD-*` khuôn KHKK nhóm · (4) SHA-pair + `npm run build` ×2.
|
||||||
|
> Đáp án owner (84) = **2 nút Duyệt / Trả lại**, KHÔNG render Từ chối (máy BE giữ).
|
||||||
|
|
||||||
|
## §0 — ĐO ĐĨA TRƯỚC KHI GÕ (tiền-đề đề bài vs thực tế)
|
||||||
|
|
||||||
|
Baseline SHA trước khi chạm (đo lúc mở lane):
|
||||||
|
|
||||||
|
| file | SHA256 (12 đầu) | ghi chú |
|
||||||
|
|---|---|---|
|
||||||
|
| `fe-{admin,user}/src/components/contracts/ContractDetailContent.tsx` | `112ccbcae77d` | **ĐANG PAIR** (W5 vừa land, cả 2 `M` trong git status) |
|
||||||
|
| `fe-{admin,user}/src/components/contracts/WorkflowHistoryPanel.tsx` | `efd01a3936b3` | pair, KHÔNG chạm wave này |
|
||||||
|
| `fe-{admin,user}/src/components/khkk/KhkkWorkflowPanel.tsx` | `e7b294c9dbad` | khuôn nguồn, pair ⇒ primitives 2 app parity |
|
||||||
|
| `fe-admin/src/types/contracts.ts` | `3d2da3bd2993` | 🔴 **KHÔNG pair** với fe-user (`1d4d649f87ae`) |
|
||||||
|
| `fe-user/src/types/contracts.ts` | `1d4d649f87ae` | lệch PRE-EXISTING: bảng `ContractPhaseColor` khác tông/app (admin -700, user -800 + brand). Đề bài CẤM chạm file này ⇒ giữ nguyên, KHÔNG "vá" thành pair. |
|
||||||
|
|
||||||
|
### Đ0.1 — Wire THẬT của "endpoint duyệt V2" (đề bài nói "grep approve/return endpoints")
|
||||||
|
|
||||||
|
🔴 **KHÔNG có endpoint V2 riêng.** `ContractsController.cs` chỉ có **một** cửa transition:
|
||||||
|
`POST /api/contracts/{id}/transitions`, body `TransitionContractBody(TargetPhase, Decision, Comment)`
|
||||||
|
(`ContractsController.cs:64-69` + `:244`). `ApproveV2Async` là **private** trong
|
||||||
|
`ContractWorkflowService.cs:230`, tới được bằng DISPATCH: `TransitionAsync` `:105` bắt
|
||||||
|
`fromPhase == ChoDuyet && decision == Approve`, rồi `:111` `if (contract.ApprovalWorkflowId is Guid awId)`
|
||||||
|
→ V2; else → V1 legacy. ⇒ FE **không** gọi đường mới, chỉ gọi đúng cửa cũ với đúng cặp
|
||||||
|
(targetPhase, decision):
|
||||||
|
|
||||||
|
- **Duyệt** → `{ targetPhase: 10 (ChoDuyet), decision: 1 (Approve), comment }`.
|
||||||
|
Nhánh approve `:105` KHÔNG đọc `targetPhase` (BE tự tính phase kế: lên Cấp / lên Bước /
|
||||||
|
`DaPhatHanh` + gen mã). Vẫn phải gửi 1 giá trị hợp lệ vì validator `IsInEnum()`
|
||||||
|
(`ContractFeatures.cs:210`) ⇒ gửi `ChoDuyet` = trung tính, không hứa phase đích.
|
||||||
|
- **Trả lại** → `{ targetPhase: 98 (TraLai), decision: 2 (Reject), comment }`.
|
||||||
|
Nhánh reject `:49-66`: `targetPhase == TuChoi` ⇒ TuChoi, **else ⇒ TraLai**. Gửi 98 tường minh
|
||||||
|
thay vì "số nào cũng được miễn khác 99".
|
||||||
|
|
||||||
|
### Đ0.2 — 3 mảnh của khuôn KHKK KHÔNG port được (đo ra, không phải chọn cho gọn)
|
||||||
|
|
||||||
|
1. ❌ **Ô-tích "Cấp này KẾT THÚC"** — `TransitionContractBody` có ĐÚNG 3 field; `skipToFinal`
|
||||||
|
là tham số C# default (`TransitionAsync(..., bool skipToFinal = false)`) **không nằm trên
|
||||||
|
API surface** ⇒ render ô-tích = control chết, bấm xong không đổi gì.
|
||||||
|
2. ❌ **Badge "⚑ Duyệt thay CEO"** trên Cấp có `allowApproverFinalize` — 🔴 `ApproveV2Async`
|
||||||
|
của HĐ **KHÔNG ĐỌC** `AllowApproverFinalize` một lần nào (`ContractWorkflowService.cs:230-407`;
|
||||||
|
cờ duy nhất nó đọc là `AllowApproverSkipToFinal` `:335`, cho đường skip mà FE không gọi được).
|
||||||
|
DTO có trả field (`ContractDtos.cs:138`) và chú thích của chính W2 nói "dùng hay không là
|
||||||
|
quyết của W6". ⇒ Vẽ badge nói "duyệt ở đây là KẾT THÚC" là **nói dối**: duyệt xong vẫn chạy
|
||||||
|
tiếp Cấp sau. Không render.
|
||||||
|
3. ❌ **Banner kết-thúc-sớm + meta `{code} v{NN} · {name}`** — `ContractDetail` **0 field**
|
||||||
|
`endedByLevelFinalize` / `workflowCode` / `workflowVersion` / `workflowName` (đo cả type
|
||||||
|
`types/contracts.ts:141-191`). Lấy `c.workflow.policyName` thế vào là ghép nhãn của
|
||||||
|
**workflow policy V1** (`WorkflowSummary`) vào quy trình V2 — 2 vật khác nhau. Không render.
|
||||||
|
(Cây thô đã inline sẵn trong detail ⇒ cũng KHÔNG gọi thêm `/approval-workflows-v2/{id}`,
|
||||||
|
đúng lời W2: "KHÔNG đẻ đường thứ 3".)
|
||||||
|
|
||||||
|
### Đ0.3 — 2 khối KHKK cố ý bỏ vì TRÙNG chỗ đã có trên cùng trang
|
||||||
|
|
||||||
|
`ContractDetailPage` render `ContractDetailContent` **và** `WorkflowHistoryPanel`
|
||||||
|
(`WorkflowHistoryPanel.tsx:38-65` đã có "Lịch sử duyệt (N)" + `:67-73` "Lịch sử điều chỉnh").
|
||||||
|
⇒ panel mới KHÔNG đẻ bản thứ 2 của 2 khối đó. Khối "📎 File đính kèm khi duyệt" cũng bỏ:
|
||||||
|
HĐ không có đường `approval-attachments` riêng (đính kèm đi qua
|
||||||
|
`ContractAttachmentsSection` đã render sẵn trong `ContractDetailContent`).
|
||||||
|
⇒ Panel W6 = khối **1 (header) + 3 (stepper) + banner lượt-ai + 4 (2 nút + dialog)**.
|
||||||
|
|
||||||
|
### Đ0.4 — FLAG-BE (không chạm, báo lead)
|
||||||
|
|
||||||
|
🔴 **Nhánh Reject `ContractWorkflowService.cs:48-66` KHÔNG có guard đúng-trạm.** Approve V2 có
|
||||||
|
(`:268-279` match `ApproverUserId` ∈ Cấp đang chờ, Admin bypass); Reject thì **bất kỳ user đã
|
||||||
|
đăng nhập nào** POST `{decision:2}` là HĐ về `TraLai`. Panel W6 có `disabled` nút Trả lại cho
|
||||||
|
người ngoài Cấp, nhưng **đó là trang trí** (gotcha #82 — 2 tầng độc lập). Đúng vế "Đ2" mà
|
||||||
|
`sub-plan-review §7` đã treo cho W6 (phần BE `ContractWorkflowService.cs:48-66`). **Ngoài lane
|
||||||
|
FE ⇒ KHÔNG sửa, báo lead.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 — Việc đã làm
|
||||||
|
|
||||||
|
### 1.1 — Nối lượt chết: ĐO LẠI ĐĨA trước khi gõ (3/4 tiền-đề của lệnh nối SAI)
|
||||||
|
|
||||||
|
Lệnh nối nói *"import & cờ `isV2` đã khai ×2 app (unused — wire dở)"*. Đo tươi lúc mở lượt:
|
||||||
|
|
||||||
|
| đo | lệnh | kết quả |
|
||||||
|
|---|---|---|
|
||||||
|
| `ContractWorkflowPanel.tsx` ×2 | `git hash-object` | `48b72391` **=** `48b72391` — pair, đúng như brief |
|
||||||
|
| `ContractDetailContent.tsx` ×2 | `git hash-object` | admin `3148173d` ⟂ user `42009bd1` — **KHÔNG pair** |
|
||||||
|
| nội dung lệch | `git diff --no-index` | **+33/−1, đúng 3 khối W6** |
|
||||||
|
| `grep -n "ContractWorkflowPanel\|isV2"` ×2 app | ripgrep | **fe-user: 6 hit** (import + `isV2` + `!isV2 &&` header + `{isV2 && <ContractWorkflowPanel/>}`) · **fe-admin: 0 hit** |
|
||||||
|
|
||||||
|
⇒ Sự thật khác brief: **fe-user KHÔNG "dở"** — wire đã TRỌN và không có biến unused nào (`isV2` được đọc 2 chỗ,
|
||||||
|
`ContractWorkflowPanel` được render). Cái chết ở lượt trước là **fe-admin chưa hề chạm**, tức lệch **MIRROR**,
|
||||||
|
không phải lệch *unused-vars*. Vá đúng bệnh: `cp` fe-user → fe-admin (giữ trọn 33 dòng W6 + mọi thứ khác
|
||||||
|
byte-y-hệt), KHÔNG gõ lại tay — gõ lại là mời sai lệch vào đúng cặp file phải identical.
|
||||||
|
|
||||||
|
**Kết quả:** `git hash-object` ×2 = `42009bd1` **=** `42009bd1` ✅ · `git diff --no-index` **rc=0** (0 dòng lệch).
|
||||||
|
|
||||||
|
3 khối wire (nay có ở CẢ 2 app): (a) import + ghi chú V1-không-tải-nhánh-này; (b) `const isV2 =
|
||||||
|
c.approvalWorkflowId != null` + khối ghi chú "một cờ ba nơi đọc"; (c) `!isV2 &&` chặn 2 nút header V1 +
|
||||||
|
`{isV2 && <section><ContractWorkflowPanel/></section>}` đặt NGAY SAU card "Nguồn gốc KHKK".
|
||||||
|
🔴 Nhánh V1 legacy **KHÔNG đổi một byte** — chỉ bọc thêm điều kiện `!isV2` ở JSX bao ngoài; `openAction`,
|
||||||
|
`transition`, `Dialog` chọn-phase, `availableTargets` giữ nguyên hoàn toàn.
|
||||||
|
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
# sub-reviewer-cum — Review ADVERSARIAL cụm W3+W4+W5+W6 (YC-024/025) TRƯỚC push
|
||||||
|
|
||||||
|
**Run:** `2026-08-10-S187-duyet-hd-tu-khkk` · **Vai:** reviewer (adversarial, verdict-only, KHÔNG sửa code)
|
||||||
|
**Ngày:** 2026-08-10 · **Relaunch** (lượt trước chết 0-trace)
|
||||||
|
|
||||||
|
## Đề bài tóm
|
||||||
|
|
||||||
|
Soi diff `git diff` + untracked (20 file) trước push. Trọng tài đã xanh: full test 680/680, build FE ×2 OK, 0 Migrations mới.
|
||||||
|
|
||||||
|
Trục rủi ro lead giao:
|
||||||
|
1. 🔴 Seeder W3 chạy thẳng prod (#51): `SeedContractTypeWorkflowsAsync` idempotent per-Code; roster builder `applyLevel2Finalize:false` cho họ HD; KHKK sau refactor GIỮ NGUYÊN (`true`); menu-row 7 key seed được trên DB prod CŨ hay chỉ DB mới?
|
||||||
|
2. 🔴 Guard W4: 4 cửa đủ; backward-compat (V1 + mã-ngoài-họ chỉ chặn pin MỚI); message giữ 2 chuỗi test cũ; UpdateDraft field cuối; vì sao `ContractSigningPlansController.cs` đổi.
|
||||||
|
3. 🔴 Đ2 W6-BE: V2 sai-trạm bị chặn thật; V1 giữ nguyên; DaPhatHanh bất khả xâm phạm.
|
||||||
|
4. FE: card Nguồn-gốc null-safe; panel 2-nút gating OR-of-N; dual-render V1; Designer type-3 slice; `contractWorkflowFamily.ts` nguồn-đôi 7 mã vs BE; SHA-pair; unused/CS8019.
|
||||||
|
5. STATUS 2 row đúng số đếm; 0 secret; tiếng Việt.
|
||||||
|
|
||||||
|
**Diff scope đo được:** 15 file modified (+1223 / −137) + 6 file untracked.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nhật ký đo (append theo mục)
|
||||||
|
|
||||||
|
### 🔴 C-1 (BLOCKER) — 7 leaf menu `AwV2_Hd*` DROP IM LẶNG khỏi sidebar (gotcha #50, Pattern 16-bis "chỗ thứ 4")
|
||||||
|
|
||||||
|
**Đo:**
|
||||||
|
- `git status --porcelain fe-admin/src/components/Layout.tsx` → **RỖNG** (file KHÔNG đổi trong cụm này).
|
||||||
|
- `grep -n "contractWorkflowFamily|findContractWfByTypeCode|Hd" fe-admin/src/components/Layout.tsx` → **0 hit**.
|
||||||
|
- Control dương cùng lệnh: `grep -n "AwV2_"` cùng file → hit `:175` `key.match(/^AwV2_(.+)$/)` + `:187` `/^KhkkN[1-8]$/` ⇒ thước sống, 0-hit là **vắng THẬT**.
|
||||||
|
- `Layout.tsx:30` `resolvePath` nhánh `AwV2_`: chỉ nhận `DuyetNcc` · `DuyetNccPhuongAn` · `Contract` · `/^KhkkN[1-8]$/`. `AwV2_HdTP` → rơi xuống `return null`.
|
||||||
|
- `Layout.tsx:331-335` `MenuLeaf`: `const path = resolvePath(node.key); if (!path) return null` ⇒ leaf biến mất **không báo lỗi**.
|
||||||
|
|
||||||
|
**Hệ quả:** BE seed 7 menu-row + 28 policy vào DB (kể cả prod, seeder ungated); admin thấy… **không gì cả** dưới "Quy trình duyệt (Mới)". Panel Designer 7 loại (đã viết ở `ApprovalWorkflowsV2Page.tsx:310-420`) **chỉ tới được bằng gõ tay URL** `/system/approval-workflows-v2/HdTP`. Đây đúng lớp "máy dựng mà không ai gọi".
|
||||||
|
|
||||||
|
**Bằng chứng lời-hứa-chưa-land (file MỚI tự khai):** `fe-admin/src/lib/contractWorkflowFamily.ts:6-11` viết *"Có ĐÚNG HAI chỗ đọc … 2. `components/Layout.tsx` `resolvePath` — đổi menu-key `AwV2_Hd*` thành đường dẫn ("chỗ thứ 4" Pattern 16-bis; thiếu là leaf DROP IM LẶNG khỏi sidebar — gotcha #50)"*. `grep -rn "contractWorkflowFamily" fe-admin/src fe-user/src` → **chỉ 1 importer** (`ApprovalWorkflowsV2Page.tsx:39`). Consumer #2 mà chính file khai = **chưa tồn tại**.
|
||||||
|
|
||||||
|
**AC để đóng:** thêm nhánh vào `resolvePath` (khuôn `/^KhkkN[1-8]$/` `:187`) dùng `findContractWfByTypeCode(code) !== null` ⇒ `return '/system/approval-workflows-v2/' + code`; kiểm bằng đếm 7 `<NavLink>` con dưới root `ApprovalWorkflowsV2` sau khi seed (control âm: `HdXX` bịa phải vẫn null).
|
||||||
|
|
||||||
|
### C-2 (MAJOR) — mirror `menuKeys.ts` ×2 app thiếu 7 key mới
|
||||||
|
|
||||||
|
`grep -rn "AwV2_" fe-admin/src/lib/menuKeys.ts fe-user/src/lib/menuKeys.ts` → có đủ 10 key cũ (`AwV2_DuyetNcc`, `AwV2_DuyetNccPhuongAn`, `AwV2_KhkkN1..N8`) nhưng **0 key `AwV2_Hd*`**. Tiền lệ ngay trong file (`fe-admin/src/lib/menuKeys.ts:34-38`) khai rõ lý do phải chép cả key chưa có call-site: *"chép vào để bảng đối chiếu FE↔BE không khuyết, tránh lượt sau đọc chỗ này rồi kết luận 'BE chưa có key nhóm'"*. Bỏ 7 key = tái lập đúng cái bẫy mà chú thích đó dựng để chặn. Không chặn build (chỉ là bảng tra), nên MAJOR không BLOCKER.
|
||||||
|
|
||||||
|
### ✅ SHA-pair 3 cặp FE mirror — ĐẠT (đo `sha256sum`, 16 ký tự đầu)
|
||||||
|
|
||||||
|
| Cặp | sha (16) | KQ |
|
||||||
|
|---|---|---|
|
||||||
|
| `ContractWorkflowPanel.tsx` admin/user | `9cdf9ffe649dbc5d` | ✅ identical |
|
||||||
|
| `ContractDetailContent.tsx` admin/user | `cd81508f97cb79fc` | ✅ identical |
|
||||||
|
| `ContractCreatePage.tsx` admin/user | `17edc625ddc9654b` | ✅ identical |
|
||||||
|
|
||||||
|
`contractWorkflowFamily.ts` **fe-admin only** — KHÔNG-CẶP có chủ đích, khai tại `:16-18` (fe-user 0 route Designer). Đo xác nhận: `ls fe-user/src/lib/contractWorkflowFamily.ts` → không tồn tại. Hợp lệ.
|
||||||
|
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
# sub-test-specialist-w3 — khoá drift menu-key/policy cho +7 key Designer `AwV2_Hd*` (W3)
|
||||||
|
|
||||||
|
> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `test-specialist` (WRITE độc quyền `tests/**`) · 2026-08-10.
|
||||||
|
> Spec-change theo `docs/rules.md §7`: test cũ update + test mới CÙNG wave với code. Lane
|
||||||
|
> `implementer-backend` land `MenuKeys.cs` + `SeedMenuTreeAsync` SONG SONG ⇒ ĐỎ-chờ-lane là trạng
|
||||||
|
> thái HỢP LỆ, nhưng phải chứng ĐỎ vì "key chưa có" chứ không phải vì test sai.
|
||||||
|
> Ghi TỪNG MỤC trong lúc làm (chống #53). Mục chưa chạy chỉ có tiêu đề + "CHƯA ĐO".
|
||||||
|
|
||||||
|
## §0 — Ground-truth ĐO TƯƠI trước khi viết
|
||||||
|
|
||||||
|
| Thứ đo | Lệnh | Kết quả |
|
||||||
|
|---|---|---|
|
||||||
|
| Cây làm việc | `git status --porcelain` | CHỈ `M docs/governance/so-yeu-cau-chu-du-an.md` (không phải của tôi). **`tests/**` SẠCH · `src/**` SẠCH** ⇒ lane impl W3 CHƯA land lúc tôi bắt đầu |
|
||||||
|
| HEAD | `git log --oneline -1` | `0f4bc184 wal: flush 20260810T1840` |
|
||||||
|
| Baseline file đích | `dotnet test --filter "~KhkkGroupMenuSeedTests\|~ContractCatalogTests"` | **Passed: 15, Failed: 0** (3 s) — build 0 error |
|
||||||
|
| `MenuKeys.All` hôm nay | đọc mã `MenuKeys.cs:222-250` + số neo trong test | **113** (= `docs/STATUS.md` row `Menu keys`) · `Policies` = 113×4 = **452** |
|
||||||
|
| 7 mã quy trình đích | đề bài lead + `UpdateAwDefinitionTests.cs:1574` (lane t0 đã ghim làm DỮ LIỆU) | `HD-TP · HD-GK · HD-NCC · HD-DV · HD-MB · HD-NT-NCC · HD-NT-DV` |
|
||||||
|
|
||||||
|
🔴 **KHÔNG đo full-suite** ở lượt này: lane impl có thể sửa `src/**` bất cứ lúc nào ⇒ số full-suite đo
|
||||||
|
giữa chừng là snapshot dở (#68) và không phải số của tôi. Số tôi chịu trách nhiệm = filter theo file đích.
|
||||||
|
Phán quyết cuối = gate lead chạy sau khi mọi lane dừng tay.
|
||||||
|
|
||||||
|
## §1 — Tra CHỒNG LẤN trước khi viết (bài S166: bước đắt nhất)
|
||||||
|
|
||||||
|
`grep -rn "MenuKeys.All" tests/` → 4 file, 3 nhóm mệnh đề đã có:
|
||||||
|
|
||||||
|
| Đã có ở đâu | Đo cái gì | Có phủ 7 key mới không |
|
||||||
|
|---|---|---|
|
||||||
|
| `ContractCatalogTests.cs:445` (T6 lane K1) | `MenuKeys.All.Except(menuKeysInDb).BeEmpty()` sau `SeedMenuTreeAsync` — **mọi** key ∈ `All` phải có menu-row (thiếu row = Permission FK nổ lúc boot) | ✅ CÓ (tự động, by-construction) ⇒ **KHÔNG nhân bản** invariant này |
|
||||||
|
| `KhkkGroupMenuSeedTests.cs:131` (T2 lane K4a) | `MenuKeys.All.HaveCount(113)` — số neo duy nhất trong toàn `tests/` cho 2 row canonical STATUS | ⚠️ CÓ nhưng SỐ CŨ ⇒ **phải update 113→120** (đây chính là "khoá drift" của đề bài) |
|
||||||
|
| `AuthorizePolicyRegressionTests.cs:139/:432` | policy trên controller phải trỏ key ∈ `All` | Không liên quan (chưa có controller nào dùng `AwV2_Hd*`) |
|
||||||
|
|
||||||
|
**Lỗ hổng đo được:** `grep -rn "KhkkN\|AwV2" tests/` → **0 hit** ⇒ họ 10 leaf Designer `AwV2_*` (2 PE + 8
|
||||||
|
KhkkN) hôm nay **KHÔNG có test nào** về hình cây/menu-row/grant. Tức khuôn mà W3 đang mirror chưa từng
|
||||||
|
được khoá. File mới bên dưới khoá CẢ họ cũ (control dương, xanh hôm nay) LẪN họ mới (đỏ tới khi land).
|
||||||
|
|
||||||
|
## §2 — Quyết định thiết kế: vì sao KHÔNG viết tên key ra chữ
|
||||||
|
|
||||||
|
🔴 Rủi ro lớn nhất của lượt này: spec (`spec-duyet-hd-10-08-2026.md:10` + `sub-plan-review §5`) chốt
|
||||||
|
**"CÓ sinh 7 key `AwV2_Hd*`"** nhưng **KHÔNG chốt chuỗi ký tự cụ thể** (`AwV2_HdTp`? `AwV2_HdThauPhu`?
|
||||||
|
theo `ContractTypeCodes` hay theo token mã `HD-TP`?). Nếu tôi đoán tên rồi viết `MenuKeys.ApprovalWorkflowHdTp`:
|
||||||
|
- đoán trúng → xanh;
|
||||||
|
- đoán trượt → **lỗi BIÊN DỊCH** ⇒ sập cả test-project ⇒ ĐỎ vì "test sai", đúng thứ đề bài cấm.
|
||||||
|
|
||||||
|
⇒ Luật tự đặt cho file này: **0 tham chiếu tới symbol C# chưa tồn tại**. Họ key mới được PHÁT HIỆN bằng
|
||||||
|
phép trừ trên `MenuKeys.All` (đã tồn tại): `All.Where(StartsWith "AwV2_")` **trừ** 10 key cũ (tham chiếu
|
||||||
|
qua const có thật). Nhờ vậy:
|
||||||
|
- hôm nay: tập rỗng ⇒ đỏ tại vế tiền-đề với thông điệp "chưa có key nào" (= "key chưa có", đúng lý do);
|
||||||
|
- sau khi land với BẤT KỲ hậu tố nào (`AwV2_HdTp` / `AwV2_HdThauPhu` / …): xanh, không phải sửa test;
|
||||||
|
- nếu land LỆCH KHUÔN tên (vd `AwV2_HD_TP` hoa/`AwV2_ContractTp`): vẫn phát hiện được và báo đúng chỗ
|
||||||
|
bằng vế "mọi key họ mới phải bắt đầu bằng `AwV2_Hd`" — đó là mệnh đề SPEC, không phải đoán mò.
|
||||||
|
|
||||||
|
**Cố ý KHÔNG viết (khai để không ai tưởng đã phủ):**
|
||||||
|
- Ánh xạ 1-1 `AwV2_Hd{X}` ↔ mã quy trình `HD-{X}`: không dựng được phép đo không-tautology khi chưa
|
||||||
|
biết luật biến hình token (`NT-NCC` → `NtNcc`? `NguyenTacNcc`?). Ai chốt tên thì bổ vế này sau — ghi
|
||||||
|
vào §6 làm nợ.
|
||||||
|
- Số workflow `HD-*` do seeder W3 tạo: thuộc lane seed (t0 đã ghim `ContractFamilyCodes` ở
|
||||||
|
`UpdateAwDefinitionTests.cs:1574` như DỮ LIỆU). File này chỉ đo MENU/POLICY.
|
||||||
|
- `RevokeTemporarilyHiddenModulesAsync`: đọc mã `:2568-2571` — bộ lọc là `StartsWith("Hrm") ||
|
||||||
|
StartsWith("Off") || == Personal` ⇒ `AwV2_*` nằm ngoài; hơn nữa site-2 `:2413` CỐ Ý loại `AwV2_*`
|
||||||
|
(admin-only). Không có ca thật ⇒ không viết test trang trí.
|
||||||
|
|
||||||
|
## §3 — Đã sửa/thêm gì
|
||||||
|
|
||||||
|
CHƯA ĐO
|
||||||
|
|
||||||
|
## §4 — Chạy: đỏ có đúng lý-do "key chưa có" không
|
||||||
|
|
||||||
|
CHƯA ĐO
|
||||||
|
|
||||||
|
## §5 — Hồi quy + phạm vi file
|
||||||
|
|
||||||
|
CHƯA ĐO
|
||||||
|
|
||||||
|
## §6 — Nợ / bàn giao cho gate W3
|
||||||
|
|
||||||
|
CHƯA ĐO
|
||||||
Reference in New Issue
Block a user