Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k2.md
2026-07-31 16:47:07 +07:00

180 lines
14 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

K2-FE: DONE — build ×2 PASS · SHA-pair 5/5
# sub-implfe-k2 — K2 FRONTEND (S164, wave 2/8, SP-002 phiếu mang hạng-mục + nhóm)
> Luật đan-xen (bài K1 + #53): làm mục nào GHI NGAY mục đó. Dòng-1 = verdict, cập nhật CUỐI.
> Nguồn: `sub-implbe-k2.md` (BE đã land) + spec-cum1 vá-4 + spec-tổng-quát QĐ-1/OG-1/OG-7 + nền `sub-invest-fable-b2-cum1.md` §K2.f.
## §0 Tiền đề — ĐO TRƯỚC MỌI WRITE (không tin sổ)
- `git status --porcelain` @start: `sub-implfe-k2.md` **KHÔNG tồn tại** trong run-folder (có k1/implbe-k2/test/reviewer/cicd) ⇒ Write mới AN TOÀN, không đè lane khác.
- **0 file `fe-*/src` nào đang M** trong git status @start ⇒ mọi thay đổi FE dưới đây là của lane này, git-diff sẽ quy trách nhiệm đúng.
- BE lane đã land (đọc `sub-implbe-k2.md`): Mig 71 applied 2-DB · rào (PeId,Group) · submit-guard · list `?group=` · Σ-Approved · build 0W/0E · suite 598/0.
- 🔴 CẤM theo task: đụng BE/.cs · chạy dotnet (lane test song song) · route/menu mới (K4a) · sửa spec.
### §0.a SHA baseline 5 cặp KHKK FE (TRƯỚC khi sửa) — cả 5 IDENTICAL
| File (`fe-*/src/…`) | admin (12 ký tự đầu SHA256) | user | Trạng thái |
|---|---|---|---|
| `types/khkk.ts` | `8b757f5b1422` | `8b757f5b1422` | IDENTICAL |
| `pages/khkk/KhkkCreatePage.tsx` | `ea51e7626eb1` | `ea51e7626eb1` | IDENTICAL |
| `pages/khkk/KhkkDetailPage.tsx` | `c8ab2eadd3fd` | `c8ab2eadd3fd` | IDENTICAL |
| `pages/khkk/KhkkListPage.tsx` | `40a650bf6964` | `40a650bf6964` | IDENTICAL |
| `pages/khkk/KhkkWorkflowPanel.tsx` | `2b64144ef6e8` | `2b64144ef6e8` | IDENTICAL |
⇒ Chiến lược: sửa **fe-admin**`cp` sang **fe-user** → đo lại SHA (khuôn cookie-cutter, 9× proven). Không có UX riêng 2 app trong 5 file này (đối chứng: baseline identical).
### §0.b Hình dạng DTO BE ĐỌC TỪ MÃ THẬT (không đoán — task ép)
- `ContractSigningPlanListItemDto` `ContractSigningPlanFeatures.cs:58-64`: append-CUỐI **`int ApprovalGroup`** + **`decimal TotalApprovedAmount`** (vá-8 — Σ thứ 3).
- `ContractSigningPlanLineDto` `:66-79`: append-CUỐI **`Guid? CatalogEntryId`** + **`string? TenHangMuc`** (denorm).
- `ContractSigningPlanDetailDto` `:130-166`: append-CUỐI (SAU cả các List) **`int ApprovalGroup`** + **`bool BudgetFrozen`** (display-gate, KHÔNG phải cột DB).
- `ApprovedPeAwaitingPlanDto` `:169-191`: append-CUỐI **`List<int> ExistingGroups`** (nhóm của các plan `Phase != TuChoi`; rỗng = PE chưa có phiếu). Picker **KHÔNG còn loại PE-đã-có-phiếu** (`:1291-1295` gỡ vế `!Any(...)`).
- `CreateContractSigningPlanCommand` `:333-337`: `PeId, ApprovalWorkflowId, GhiChu?, **int? ApprovalGroup = null**`**null ⇒ nhóm 1** (`:393`); validator `InclusiveBetween(1,8)` CHỈ khi có giá trị (`:349-353`).
- `ListContractSigningPlansQuery` `:700-705` `+int? ApprovalGroup`; controller expose **`?group=`** (BE lane §f).
- 409 message create (`:400-402`): *"Phiếu Duyệt NCC này đã có kế hoạch ký kết cho nhóm duyệt N{g}. Chọn nhóm duyệt khác, hoặc xoá/để bị từ chối kế hoạch cũ của nhóm này trước khi lập lại."*
> 🔴 **[lead @S165] p1 CHẾT ngay sau §0.b** — 4 dòng markup tool-call rò vào đuôi file (sub-class #53 MỚI:
> content-Write lẫn cú pháp invoke), lead đã cắt. Disk-truth p1: fe-admin 4 file M (types +69 · CreatePage +177
> mid-wire [unused: select-nhóm + nút-phụ + badge chưa vào JSX] · Detail +33 · List +1); fe-user CHƯA cp.
> p2 tiếp từ đây — append §1 trở xuống.
---
# p2 (relaunch @S165) — tiếp từ §0.b
## §1 Wire CreatePage — ĐO ĐĨA TRƯỚC, KHÔNG TIN SỔ
🔴 **Tiền đề của lead SAI MỘT PHẦN (đã đo lại bằng `git diff`, không đọc lại sổ):**
lead khai CreatePage "GIỮA CHỪNG WIRE, các khai báo chưa vào JSX". Đo thật trên đĩa
(`git diff -- fe-admin/src/pages/khkk/KhkkCreatePage.tsx`, 177 dòng +) thì **8/8 khai báo
lead liệt là unused đều ĐÃ có call-site trong JSX**:
(số dòng dưới = đo bằng `grep -n` trên file SAU khi p2 xong, không phải trí nhớ)
| Khai báo lead nói "chưa vào JSX" | Call-site THẬT trên đĩa (fe-admin) |
|---|---|
| `KHKK_APPROVAL_GROUPS` | `firstFreeGroup()` `:55` + `.map` dựng 8 `<option>` `:281` |
| `KHKK_APPROVAL_GROUP_DEFAULT` | `useState<number>(...)` `:62` |
| `KHKK_APPROVAL_GROUP_LABELS` | `title=` badge nhóm-đã-dùng `:232` + text `<option>` `:285` |
| `approvalGroup` / `setApprovalGroup` | body POST `:99` · `value=` select `:277` · `onChange` `:278` |
| `firstFreeGroup` | `selectPe()` `:131` + `const free` mỗi dòng PE `:180` |
| `groupTaken` | cảnh báo đỏ `:291-293` · `disabled` nút Lưu `:360` |
| `selectPe` | `onClick` dòng PE `:185` + `onClick` nút "Tạo thêm cho nhóm khác" `:240` |
| icon `Layers` / `Plus` | `<Layers>` label nhóm `:272` · `<Plus>` nút phụ `:241` |
**Suy đoán về nguyên nhân lệch:** p1 chết ở lượt Write CUỐI (rò markup tool-call), nên
lead nhìn thấy transcript dở-dang và kết luận theo Ý ĐỊNH đang thực thi, không theo đĩa.
Đây đúng khuôn "WRITE-lane garble ≠ mất code" (MEMORY S114) — **code đã LAND, chỉ báo cáo
chết**. p2 KHÔNG viết lại CreatePage (viết lại = phá thứ đang đúng).
**Nội dung K2 mà CreatePage đã có (đọc để xác nhận đúng spec, không phải đọc để chép):**
- vá-4: `<select id="approvalGroup">` N1N8, default 1 (`KHKK_APPROVAL_GROUP_DEFAULT`),
option nhóm đã dùng bị `disabled` + hậu tố "(đã có kế hoạch)"; header section đổi
"Quy trình duyệt" → "Nhóm duyệt & quy trình", lưới 1→2 cột `md:grid-cols-2`.
- body POST `approvalGroup` gửi TƯỜNG MINH (không dựa default ngầm `CreateCommand:337`).
- OG-7 UI-hướng-1: dòng PE **không còn bị ẩn** khi đã có kế hoạch → hiện hàng chip
`N{g}` (nhóm đã dùng) + nút phụ **"Tạo thêm cho nhóm khác"**; hết 8 nhóm → chữ
"Đã dùng hết 8 nhóm duyệt" (không mời bấm vào thứ chắc chắn 409).
- `<button>` PE bọc trong `<div key>` phụ vì nút phụ **không được lồng trong `<button>`**
(HTML không hợp lệ) — p1 đã xử đúng.
- Rào cứng vẫn ở BE (`Features.cs:395-402` 409); FE chỉ `disabled` nút Lưu khi `groupTaken`.
**§1 = 0 dòng sửa mới.** Việc thật của p2 dồn vào §2 (ListPage) + §3 (cp) + §4 (build/SHA).
## §2 Detail / List
**KhkkDetailPage — cũng ĐÃ LAND ĐỦ (0 dòng sửa mới, +33 của p1):**
- badge `N{approvalGroup}``PageHeader actions`, ĐỨNG TRƯỚC badge phase, `title` = nhãn
nghiệp vụ (`:298-304`), guard `!= null` cho payload cache cũ.
- cột **"Hạng mục"** trong bảng Lines (`<th>` `:460`, `<td>` `:472``tenHangMuc` rỗng ⇒
`—` xám) + 1 câu chú thích "ô gán thuộc bước Lines-editor" (K4b) ở cuối bảng `:485`.
- badge `🔒 Giá đã chốt tại thời điểm duyệt` khi `budgetFrozen` `:443` — gắn qua prop `actions`
của `SectionCard`. ✅ đã kiểm: `actions?: ReactNode` là prop **CÓ SẴN** `:63` (không
phải p1 tự bịa), render `{actions && <span className="ml-auto">…}` `:82`.
**KhkkListPage — ĐÂY LÀ CHỖ THẬT SỰ DỞ (p1 chỉ kịp thêm dòng import).** p2 sửa:
1. `:240-246` badge `N{g}` **trong ô "Mã kế hoạch"**, không đẻ cột riêng — bảng này đã
cuộn ngang ở 1366 khi có cây GĐ 19rem bên trái (ghi chú S162 ngay trong file); guard
`!= null` + `title` = `KHKK_APPROVAL_GROUP_LABELS[k.approvalGroup]` `:243` ⇒ **import
treo đã có người dùng** (đây chính là dòng làm build đỏ TS6133 nếu bỏ mặc).
2. `:190` +cột **"Tổng duyệt"** (Σ `totalApprovedAmount`) và `:271-273` ô giá trị
(emerald, `tabular-nums`). Phiếu chưa duyệt BE trả **0** ⇒ ép hiện `—` (`k.totalApprovedAmount ? … : '—'`),
không in "0 đ" gây đọc nhầm "đã duyệt 0 đ".
3. `colSpan={7}`**`{8}`** hai chỗ (hàng "Đang tải..." `:198` + hàng rỗng `:205`) —
quên chỗ này thì 2 hàng đó thụt 1 ô, lỗi câm không ai thấy tới lúc bảng rỗng.
*(Cột này KHÔNG có trong gạch đầu dòng của lead, nhưng là **vá-8 spec-cum1** — "list
thêm Σ Approved sau freeze để list/detail không lệch số" — và BE đã trả field
`ListItemDto:64`; khai lệch ở phần cuối.)*
-**KHÔNG** thêm ô lọc `?group=` ở list: K2 là CHỦ tham số nhưng **chỗ lọc thuộc K4b/K5**
(leaf nhóm + cây 8 folder) — đẻ chỗ lọc thứ hai bây giờ là giẫm chân wave sau.
## §3 cp sang fe-user (khuôn cookie-cutter)
`cp` 4 file admin → user, KHÔNG gõ tay lần hai (gõ tay = đẻ lệch không ai thấy):
`types/khkk.ts` · `pages/khkk/{KhkkCreatePage,KhkkDetailPage,KhkkListPage}.tsx`.
- **`KhkkWorkflowPanel.tsx` KHÔNG cp** vì K2 không sửa nó — và đó là **đối chứng dương**
của phép đo §4: hash panel sau lượt này phải BẰNG ĐÚNG baseline §0.a `2b64144ef6e8`.
Nếu nó cũng "identical" mà lệch baseline thì phép đo của tôi vô nghĩa.
- Không import mới nào ngoài `lucide-react` (`Layers`,`Plus`) + const trong chính
`types/khkk.ts` đã cp ⇒ không có rủi ro "fe-user thiếu component" (bẫy Card/Badge quen).
- `git diff --stat` **ĐỐI XỨNG 2 app**: `269 insertions(+), 36 deletions(-)`, từng file
khớp số dòng (177 / 33 / 26 / 69). Lệch số ở đây = cp sót, không cần đợi tới SHA.
## §4 Build ×2 + SHA-pair — TRỌNG TÀI
`npm run build` = **`tsc -b && vite build`** (`package.json:11` cả 2 app) và
`tsconfig.app.json:19 noUnusedLocals: true`**build LÀ phép thử TS6133**: import treo
`KHKK_APPROVAL_GROUP_LABELS` ở ListPage chắc chắn làm đỏ, không phải "IDE dọa" (#68).
| App | Kết quả | Bundle | Thời gian |
|---|---|---|---|
| fe-admin | ✅ PASS 0 TS error | `dist/assets/index-DORfkFnc.js` 1,722.60 kB | 23.10s |
| fe-user | ✅ PASS 0 TS error | `dist/assets/index-qYI2yszp.js` 1,638.80 kB | 8.80s |
Warning cả 2 app = **pre-existing**, không phải do lượt này: `@import` phải đứng trước
rule khác (font Google trong CSS) · chunk > 500 kB · `realtime.ts` vừa dynamic vừa
static import từ `NotificationBell`.
**SHA256 (`Get-FileHash`, 12 ký tự đầu) — 5/5 cặp IDENTICAL:**
| File (`fe-*/src/…`) | admin | user | Cặp | So baseline §0.a |
|---|---|---|---|---|
| `types/khkk.ts` | `72A71451B98F` | `72A71451B98F` | ✅ | ĐỔI (từ `8b757f5b1422`) |
| `pages/khkk/KhkkCreatePage.tsx` | `677F4AB803B0` | `677F4AB803B0` | ✅ | ĐỔI (từ `ea51e7626eb1`) |
| `pages/khkk/KhkkDetailPage.tsx` | `1863153B4F99` | `1863153B4F99` | ✅ | ĐỔI (từ `c8ab2eadd3fd`) |
| `pages/khkk/KhkkListPage.tsx` | `E892310434F2` | `E892310434F2` | ✅ | ĐỔI (từ `40a650bf6964`) |
| `pages/khkk/KhkkWorkflowPanel.tsx` | `2B64144EF6E8` | `2B64144EF6E8` | ✅ | **BẰNG baseline** (chứng: đo đúng thứ, không đụng file ngoài phạm vi) |
## §5 Ranh giới đã giữ + khoảng hở khai thật
**Giữ đúng 5 điều cấm của task:** 0 lệnh `dotnet` · 0 file `.cs` bị ghi (13 `.cs` đang M/??
trong `git status`**lane BE** — Mig `20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime`
+ `ContractSigningPlanFeatures.cs` + 3 test file; tôi chỉ ĐỌC DTO, không Edit) · 0 route
mới (`App.tsx` không nằm trong diff) · 0 menu key mới (`lib/menuKeys.ts`,
`components/Layout.tsx` không nằm trong diff — K4a giữ chỗ đó) · 0 dòng spec bị sửa.
**Khoảng hở / lệch — khai để lead phán, không tự nuốt:**
1. 🔴 **Tiền đề task sai một phần** (§1): CreatePage + DetailPage đã LAND ĐỦ trên đĩa từ
p1; chỉ ListPage dở. Việc thật của p2 ≈ 1/4 mô tả. Tôi **không viết lại** phần đã đúng.
*(Bài học tái xác nhận: báo cáo chết ≠ code chết — đo `git diff` trước khi gõ.)*
2. ⚠️ **Cột "Tổng duyệt" (Σ Approved) là việc TÔI THÊM ngoài gạch đầu dòng của lead.**
Căn cứ: **vá-8 spec-cum1** (*"list thêm Σ Approved sau freeze để list/detail không lệch
số"*) + BE đã trả `ListItemDto:64` + p1 đã khai field trong types kèm đúng lý do đó.
Không thêm thì field nằm chết. **Nếu lead thấy quá tay: gỡ = xoá 1 `<th>` + 1 `<td>` +
trả `colSpan` 8→7, không ảnh hưởng gì khác.**
3. ⚠️ **`existingGroups` chỉ đọc `Phase != TuChoi`** (BE). Phiếu **xóa mềm** có rơi khỏi tập
này không thì tôi KHÔNG kiểm được từ FE. Nếu BE không loại phiếu deleted thì user xoá
phiếu nhóm 2 xong vẫn thấy N2 bị khoá trong ô chọn (rào 409 BE mới là lời cuối). Câu này
thuộc lane BE — nêu để có người soi, không tự sửa.
4. ⚠️ **Nhãn 8 nhóm còn treo [OG-6]** (owner soát sau trên UI). Đang nằm 1 chỗ
`KHKK_APPROVAL_GROUP_LABELS` × 2 app ⇒ K4a đổi nhãn menu thì sửa đúng 1 const, đừng vá rải.
5. **Không tự bấm được UI** (acceptance vá-4 "tạo phiếu nhóm 2 TỪ UI ×2 app" cần người
chạy app + login). Tôi dừng ở mức build-PASS + đọc mã; **acceptance UI vẫn ĐANG MỞ**,
đừng đọc "build PASS" thành "đã UAT".
6. Chỗ lọc `?group=` **cố ý chưa làm** (thuộc K4b/K5) — nêu lại ở đây để wave sau không
tưởng là sót.
END p2 — 4 file × 2 app · build ×2 PASS · SHA-pair 5/5.