180 lines
14 KiB
Markdown
180 lines
14 KiB
Markdown
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">` N1–N8, 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` là **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.
|