From c97b5795c61a3ff2f7001947fa0ff814c7736bd3 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Wed, 5 Aug 2026 16:27:07 +0700 Subject: [PATCH] wal: flush 20260805T1627 --- .../spec-khkk-style-dong-bo-05-08-2026.md | 98 ++++++ .../sub-frontend-designer-L1.md | 126 +++++++- .../sub-frontend-designer-L2.md | 151 ++++++++- .../sub-frontend-designer-L3.md | 81 +++-- .../sub-frontend-designer-L4.md | 291 ++++++++++++++++-- .../sub-frontend-designer-L5.md | 195 ++++++++++-- 6 files changed, 862 insertions(+), 80 deletions(-) create mode 100644 .claude/workflows/runs/2026-08-05-S174-khkk-style-audit/spec-khkk-style-dong-bo-05-08-2026.md diff --git a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/spec-khkk-style-dong-bo-05-08-2026.md b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/spec-khkk-style-dong-bo-05-08-2026.md new file mode 100644 index 0000000..2ac8c27 --- /dev/null +++ b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/spec-khkk-style-dong-bo-05-08-2026.md @@ -0,0 +1,98 @@ +# SPEC — Đồng bộ style KHKK theo Duyệt NCC + 8 owner-item (05-08-2026) + +> Nguồn: `/fable-clone frontend-designer` ensemble **5 lane / 5 xong / 0 chết** (`wf_016b1bed-b85`, 755K tok, 147 tool-use, ~17′). +> Engine **propose-only**; file này do **LEAD ghi sau khi verify** (H21 §Spec-file). Lane-MD gốc: `sub-frontend-designer-L1..L5.md`. +> 🔴 **O7/O8 tới SAU khi lane đã phóng** ⇒ lane KHÔNG thấy; lead tự phủ. "Lane không nhắc" ≠ "không có việc". + +--- + +## ① TÍNH-NĂNG / ĐỀ-BÀI + +Module **KHKK (GĐ2)** phải nhìn và cư xử **cùng một hệ** với **Duyệt NCC (GĐ1 — golive, owner coi là CHUẨN)**. +Owner: *"cho style nó giống với duyệt NCC nhé, style hiển thị 2 bên khác nhau ko đc"* · *"cho nó quét lại hết luôn, design lại"*. + +Phạm vi = 8 owner-item (O1–O8) + **3 phát hiện lane** owner chưa nêu nhưng nặng hơn phần lớn danh sách: + +| id | Phát hiện thêm | Vì sao vào spec | +|---|---|---| +| **X1** | `KhkkWorkflowPanel:501` tải file bằng `` trần ⟂ PE `:331` `api.get(responseType:'blob')` | JWT chỉ gắn qua axios interceptor ⇒ **401, nút không chạy**. Đây là LỖI, không phải style. LEAD ĐÃ VERIFY | +| **X2** | `KhkkWorkflowPanel:247` bọc `card-accent` (viền + rail 3px + bóng + hover) ⟂ PE `:345` panel TRẦN | Lệch **đập vào mắt trước mọi mục owner liệt kê**: PE phẳng, KHKK là thẻ-trong-thẻ | +| **X3** | Màn Thao tác `` là `hidden … lg:block` ⇒ **mobile không tạo được phiếu** | 🔴 **Do chính lead gây ra khi dựng host 2-panel trong phiên này.** Regression, phải vá | + +🔴 **Hai giả-định của LEAD bị lane BÁC — đã tự verify, xác nhận lane đúng:** +- **O3 KHÔNG cần migration.** `ContractSigningPlanLineConfiguration.cs:37-38` unique index **3 cột** `(PlanId, SupplierId, CatalogEntryId)` ⇒ 1 NCC **đã được phép** N dòng × N hạng mục. Máy rộng sẵn từ Mig 71; chỉ UI hẹp. Lead từng báo owner "nặng nhất, cần migration" — **SAI, đã đính chính**. +- **O4 FE-only, 0 BE.** Rail `dossierItemId` đã land đủ 6 tầng từ Mig 69. + +--- + +## ② CÁCH IMPLEMENT — 4 đợt, xếp theo thứ tự land an toàn + +### ĐỢT A — SỬA LỖI (không phải style; land trước, không chờ ai) + +| # | File | Đổi | Vì sao | +|---|---|---|---| +| A1 | `components/khkk/KhkkWorkflowPanel.tsx:500-505` | `` → `api.get(url,{responseType:'blob'})` + `URL.createObjectURL`, mirror `PeWorkflowPanel.tsx:327-342` | **X1** — nút tải hiện trả 401 | +| A2 | `pages/khkk/KhkkCreatePage.tsx` `
` | Bỏ ngõ cụt `` hiện ở mọi breakpoint, panel-1 chuyển `hidden lg:flex` (hoặc accordion) | **X3** — regression lead vừa gây | +| A3 | `pages/khkk/KhkkDetailPage.tsx` | Bọc `KhkkWorkflowPanel` trong `
` | Nợ đã khai với owner: mobile mất nút Gửi duyệt/Xóa | + +### ĐỢT B — BÊ THẲNG TỪ PE (FE-only, 0 quyết định, rủi ro thấp) + +**B1 — panel Quy trình duyệt (O1)** · `KhkkWorkflowPanel.tsx` +- **X2** gỡ `card-accent` + `p-5` → panel trần `space-y-4` (PE `:345`). 🔴 **GỠ CLASS, KHÔNG đè `border-0`** — `.card-accent` (`index.css:112`) ngoài `@layer` nên thắng utility Tailwind v4 (**gotcha #66, đã cắn thật phiên này**). +- Tiêu đề → `

` trần, bỏ `icon-chip` (PE `:346-354`). +- Số hiệu WF `v${n}` → `padStart(2,'0')` ⇒ `v01` (PE `:350`). +- Banner kết-thúc: violet → **emerald** + câu chữ PE (`:360-377`). Dữ-liệu ĐỦ (`khkk.ts:385-390` `endedByLevelFinalize` 3-ngả) ⇒ bê thẳng. *Violet đang bị trộn 2 nghĩa — PE dành violet riêng cho "Duyệt thay CEO".* +- Chip PHÒNG: `border-slate-300 bg-white` → `bg-emerald-50 text-emerald-700` (PE `:406-410`). +- Nút hành động: lucide `//` → glyph chuỗi `'✓ Duyệt'/'← Trả lại'` (PE `:540`); nút **Xóa** xám → đỏ `border-red-300 text-red-700` (PE `:574`). +- "File đính kèm khi duyệt": thêm `border-t border-slate-200 pt-4` + `

` + **nút `` xem trước** — route BE **đã có** `ContractSigningPlansController.cs:231-232`; cần thêm prop `basePath` cho `AttachmentPreviewDialog` (đang hardcode `evaluationId`). +- "Lịch sử duyệt (n)": bỏ pill đếm → `(n)` trong ngoặc; thẻ `bg-slate-50/60 text-[11px]` → `rounded border bg-white p-3 text-sm`; mốc-giờ **đẩy phải** bằng `justify-between`; badge **2-ngả → 3-ngả** (Từ chối rose / Trả lại amber / Duyệt emerald) — `KhkkApprovalDto.toPhase` đã có (`khkk.ts:307`). +- "Lịch sử thay đổi": bỏ gập, render ngay (PE `PeDetailTabs.tsx:787-794`). +- Micro-copy: "Huỷ"→"Hủy", "Ý kiến (tuỳ chọn)"→"Ghi chú (tùy chọn)". + +**❌ KHÔNG bê:** chip **"NS PRO"/"NS CCM"** (L1 H3). Hai cờ `canEditProBudget/canEditCcmBudget` mở quyền sửa **ngân sách per-gói-thầu** (`PeWorkItemBudgets`, Mig 50) — **khái niệm KHKK không có**. Bê sang = vẽ nhãn quyền không tồn tại. +**✅ GIỮ chỗ KHKK TỐT HƠN PE:** mốc chữ-ký `✓ dd/mm/yyyy hh:mm` + in đậm người đã ký (`:338-355`). + +**B2 — nội dung phiếu (L5: 6 P0)** · `KhkkDetailContent.tsx` +`table-fixed` + width cột + `min-w-[880px]` trong `overflow-x-auto` · ô rỗng `—` → `text-slate-300` (đang cùng màu chữ thật ⇒ đọc nhầm là dữ liệu) · empty-block `text-muted-foreground` → `text-sm italic text-slate-400` · bỏ emoji `🔒` → `` lucide · thống nhất `text-xs` cả hàng bảng · `hover:bg-slate-50/60` cho ``. + +**B3 — panel danh sách (L2)** · `KhkkListPanel.tsx` + `KhkkWorkListPanel.tsx` +Chế-độ cây thiếu tiêu-đề/số đếm ⇒ cột cụt đầu · **gộp 2 ô tìm chồng nhau** · thang chữ 4 nấc (13/11/10/**9**px) → về 3 nấc chuẩn · 🔴 **`slate-400` = 2.56:1 cho 6 dòng chữ mang thông tin** — dưới ngưỡng WCAG, phải nâng · nút phân trang ký tự trần → icon · chip nhóm lệch baseline. + +**B4 — host (L3)** · `KhkkCreatePage.tsx` +Grid `300px` → **`260px`** khớp PE (`lg:grid-cols-[260px_1fr] xl:grid-cols-[320px_1fr]`) · sửa comment sai sự thật (L3 H-5: `grep -c Button` = 0, đối chứng dương `PeListPanel` = 8). +**CHỐT KHÔNG SỬA (L3):** `h-calc` toàn hệ · violet 2-hệ toàn hệ · nút tạo ở header List · `truncate` (KHKK tốt hơn chuẩn — **đừng hạ xuống**). + +### ĐỢT C — CẦN QUYẾT ĐỊNH TRƯỚC KHI CODE + +| # | Việc | Lane khuyến nghị | Cần owner | +|---|---|---|---| +| C1 | **O5** flatMode đổi Cây→Phẳng khi lọc | **GIỮ MÁY R-11, SỬA HÌNH**: bỏ `term` khỏi `flatMode` + gộp ô tìm + **công tắc Cây/Danh-sách** + chip lọc | Gật/bác | +| C2 | **O2** badge "SẮP" → 0 | Lane đo: **không GĐ nào còn đúng nghĩa "sắp"** (cả 4 đều fetch API thật); `SoonChip` **không export**, 8 hit nội bộ, **0 host phụ thuộc** ⇒ bỏ sạch, an toàn | Xác nhận "bỏ hết" = mọi GĐ | +| C3 | **P2-1** `Field` tô brand cho MỌI value ⟂ PE giữ slate | label giữ `text-brand-600`, **value → `text-slate-900`**, chừa màu cho winner/cảnh báo | Nhìn 1 lần | +| C4 | Nút **"Từ chối"**: PE có code nhưng **thực tế không hiện** (BE gỡ khỏi `nextPhases`, UAT S60) ⟂ KHKK VẪN hiện | Đừng tự cắt | Quyết | + +### ĐỢT D — VIỆC LỚN + +- **O6** (`usePipelineStages.ts:219` → `/purchase-evaluations?id=…&expand=1`): thêm callback **`onOpenPe`** mirror khuôn `stage2Options.onOpenKhkk` (ternary `:233`). **Không truyền ⇒ giữ navigate** ⇒ 5 host kia không đổi hành vi. +- **O3** hạng mục N-dòng + SUM tổng — **Model-A N-ROW, 0 migration**. Tổng giá duyệt ghi phía trên. 🔴 Người land phải mở đủ `Features.cs:1310-1360` + `CreateContractFromSigningPlanFeatures.cs:100-240` (lane chỉ grep, chưa soi hết) và soi `KhkkDetailPage.tsx:312` — **tách N dòng sẽ in N lần cho 1 NCC**, có thể phải gộp. +- **O4** shopdrawing đính kèm — **FE-only**, rail `dossierItemId` đã đủ 6 tầng từ Mig 69. +- **O7** nút edit trên card KHKK — mirror `PeListPanel` `onEditClick`. +- **O8** tầng trên ra cây thư mục **Dự án › Hạng mục** 2 cấp (hiện `byProject` mới 1 cấp). + +--- + +## ③ CHECKLIST — acceptance ĐO ĐƯỢC + +| # | Deliverable | Acceptance (đo được, không phải "trông ổn") | +|---|---|---| +| 1 | Đợt A land | `grep -c 'href={`/api/contract-signing-plans' KhkkWorkflowPanel.tsx` = **0**; control-dương `grep -c "responseType: 'blob'"` ≥ **1** | +| 2 | A2 mobile | DevTools 375px màn Thao tác: **bấm được "Tạo kế hoạch"** (hiện `
` bị `hidden`) | +| 3 | B1 gỡ card-accent | `grep -n 'card-accent' KhkkWorkflowPanel.tsx` = **0 hit**. 🔴 KHÔNG chấp nhận `border-0`/`shadow-none` (gotcha #66) | +| 4 | Mirror 2 app | `sha256sum` **từng cặp** file đụng tới — **khớp 100%**. O2+O6 chạm **5 cặp = 10 file**, nghiệm thu **5/5** | +| 5 | Build | `npm run build` **cả 2 app**, 0 `error TS`. 🔴 `cd` đúng thư mục mỗi lần — thời gian bất thường (`~500ms`) = dấu hiệu build nhầm app | +| 6 | Bundle prod | `curl` bundle + `grep` chuỗi mới **có** ∧ chuỗi cũ **vắng**, cả 2 host (gotcha #77). Mọi 0-hit phải kèm **control-dương** | +| 7 | 6 host cây | O2/O6 land xong: mở PE-list · GĐ3 ×2 · GĐ4 ×2 · KHKK-list — **5 host không truyền callback giữ nguyên hành vi navigate** | +| 8 | 🔴 Visual loop | **CẢ 5 LANE ĐỀU KHÔNG CÓ SCREENSHOT** (lane audit + dev-rig chặn: chỉ `:8080` LISTENING, API `:5443` không có, trang sau `ProtectedRoute` — gotcha #3). Mọi kết luận là **đọc mã, không phải đã nhìn**. ⇒ Người land **BẮT BUỘC** soi thật **375 / 1366 / 1440** trên **prod sau deploy** trước khi tuyên PASS | +| 9 | O3 tiền | Trước khi viết BE: **xác nhận 1 dòng với owner** cách đọc "sum tổng" — đây là tiền | + +**Thứ tự land:** A → B → (chờ owner) C → D. A độc lập, land ngay. B không phụ thuộc C/D. D2 (`O3`) land cuối vì chạm dữ-liệu tiền. diff --git a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L1.md b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L1.md index f14fd64..b6f8f1c 100644 --- a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L1.md +++ b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L1.md @@ -11,13 +11,19 @@ | # | Việc | Trạng thái | Ghi chú | |---|---|---|---| -| 0.1 | Ghi khung rỗng | ⬜ | | -| 0.2 | Đọc PE panel (chuẩn) | ⬜ | LOC / cấu trúc | -| 0.3 | Đọc KHKK panel (đối tượng) | ⬜ | LOC / cấu trúc | -| 0.4 | Đọc DTO 2 phía (field gap) | ⬜ | types/*.ts | -| 0.5 | Kê bảng đối chiếu 8 hạng mục | ⬜ | | -| 0.6 | Phân loại bê-thẳng ⟂ thiếu-data | ⬜ | | -| 0.7 | Viết spec thi-hành-được | ⬜ | | +| 0.1 | Ghi khung rỗng | ✅ | lượt 1, TRƯỚC mọi Read | +| 0.2 | Đọc PE panel (chuẩn) | ✅ | 1062 dòng, đọc HẾT (2 trang) | +| 0.3 | Đọc KHKK panel (đối tượng) | ✅ | 726 dòng, đọc HẾT | +| 0.4 | Đọc DTO 2 phía (field gap) | ✅ | `types/khkk.ts` (527 dòng, đọc hết) + grep `types/purchaseEvaluation.ts` | +| 0.4b | Đọc phần PE ủy-quyền sang file khác | ✅ | `PeDetailTabs.tsx:778-794` (2 section) + `:3056-3063` (badge) + `:3109-3288` (2 tab) | +| 0.4c | Đọc vỏ host 2 bên (loại nhiễu) | ✅ | `PurchaseEvaluationsListPage.tsx:668` ⟂ `KhkkListPage.tsx:186` — **giống hệt** | +| 0.4d | Đọc CSS `.card-accent`/`.icon-chip` | ✅ | `fe-admin/src/index.css:112-137` | +| 0.4e | Đo xác thực đường tải file | ✅ | `ContractSigningPlansController.cs:222-232` + `lib/api.ts:18` → §6.1 | +| 0.4f | Đo bất-biến mirror 2 app | ✅ | sha256 khớp `fb563b25…` | +| 0.5 | Kê bảng đối chiếu 8 hạng mục | ✅ | §2 (H0-H8, H0 là mục tao thêm) | +| 0.6 | Phân loại bê-thẳng ⟂ thiếu-data | ✅ | §4.A (15) / §4.B (1) / §4.C (8) | +| 0.7 | Viết spec thi-hành-được | ✅ | §5 (7 đợt + 7 bất-biến + nghiệm thu) | +| 0.8 | Soi-bằng-mắt FD2 | ❌ | **KHÔNG chạy được** — không dev server/API; khai thẳng §6.4, KHÔNG bịa ảnh | --- @@ -96,7 +102,7 @@ - PE `PeWorkflowPanel.tsx:360-377` — gate `evaluation.endsBeforeCeo`, khung **emerald** `border-emerald-300 bg-emerald-50 text-emerald-800`, 2 nhánh theo phase: `DaDuyet` → "✅ Phiếu đã kết thúc tại **{finalizeStepName}** · {finalizeLevelName} — không qua CEO." / đang chạy → "⚑ Quy trình rút gọn: duyệt đến **{…}** là KẾT THÚC, không trình CEO. Các cấp trước vẫn duyệt như thường." - KHKK `KhkkWorkflowPanel.tsx:269-281` — gate `plan.endedByLevelFinalize`, khung **violet** `border-violet-300 bg-violet-50 text-violet-800`, 2 nhánh theo `isApproved`. - ≠ **màu lệch emerald ⟂ violet** (KHKK dùng violet = màu PE dành riêng cho badge "Duyệt thay CEO" `:441-448` ⇒ KHKK trộn 2 nghĩa vào 1 màu). -- ∅/⊘ **nhánh "báo trước"**: xem §4.B — phụ thuộc ngữ nghĩa cờ BE (đo ở dưới). +- ✅ **ĐÃ ĐO — bác giả-thuyết ban đầu của chính tao**: tao định kê "KHKK thiếu nhánh báo-trước vì cờ chỉ bật sau khi duyệt". SAI. `khkk.ts:385-390` khai rõ `endedByLevelFinalize` là **3-NGẢ ở BE**: phiếu `DaDuyet` → cờ runtime thật (Mig 71); phiếu **chưa xong → suy từ cấu hình, POINTER-AWARE** (chỉ tính cấp `allowApproverFinalize` ở-hoặc-SAU con-trỏ); chưa pin quy trình → false. Và KHKK **đã** rẽ nhánh sẵn (`:276-278`). ⇒ H1 **chỉ lệch MÀU + CÂU CHỮ**, dữ-liệu đủ, **bê thẳng được** (§4.A-4). ### H2. Bước/Cấp + icon TRÒN check xanh — ≡ (gần như khớp tuyệt đối) - PE `:381-478` (`stepIcon` `:384`, dot `:397-404`, cấp `:423-434`) ⟂ KHKK `:284-366` (`STATUS_DOT/BOX/ICON` `:64-78`, dot `:293-295`, cấp `:318-320`). @@ -191,25 +197,111 @@ ## 4. Phân loại hành động -### 4.A BÊ THẲNG được (data đã có 2 phía) -_(chờ)_ +### 4.A BÊ THẲNG được — data đã có đủ 2 phía (13 + 2 mục) +> Mọi mục 1-13 chỉ đụng **class / JSX / chuỗi**, KHÔNG đụng query, mutation, guard quyền. -### 4.B THIẾU DỮ-LIỆU (nêu field DTO thiếu + nguồn BE) -_(chờ)_ +| # | Mục | Sửa gì | Bằng chứng đủ-data | +|---|---|---|---| +| 1 | **Vỏ panel** | bỏ `
` → `
`; bỏ `
` + `.icon-chip`; bỏ `p-5` (aside đã `p-4`) | thuần trình bày | +| 2 | **Tiêu đề panel** | `

Quy trình duyệt

` (bỏ `text-emerald-700`), meta xuống dòng riêng `

` — khuôn PE `:347-352` | — | +| 3 | **Số hiệu quy trình** | `v${String(plan.workflowVersion ?? 0).padStart(2,'0')}` → hiện `v01` như PE `:350` | `workflowVersion` có (`khkk.ts:384`) | +| 4 | **Banner kết-thúc-sớm** | violet → **emerald** `border-emerald-300 bg-emerald-50 text-emerald-800`; nhánh đã-duyệt mở bằng `✅`, nhánh đang-chạy mở bằng `⚑ Quy trình rút gọn:` + câu "Các cấp trước vẫn duyệt như thường." | `endedByLevelFinalize` BE **3-ngả, pointer-aware, CÓ cả khi chưa duyệt** (`khkk.ts:385-390`) ⇒ nhánh báo-trước có dữ-liệu thật | +| 5 | **Chip phòng của Bước** | `border border-slate-300 bg-white text-slate-600` → `bg-emerald-50 text-emerald-700`, bỏ viền | `departmentName` có (`khkk.ts:289`) | +| 6 | **3 nút hành động** | thay `` bằng glyph trong chuỗi `'✓ Duyệt'` · `'← Trả lại'` · `'✗ Từ chối'`; bỏ `inline-flex items-center gap-1` để khớp `rounded border px-2 py-1 text-[11px] font-bold` (PE `:553`) | thuần trình bày | +| 7 | **Nút Xóa phiếu** | `border-slate-300 text-slate-600` → `border-red-300 bg-white text-red-700 hover:bg-red-50`; giữ `` (PE cũng dùng icon ở đúng nút này `:576`) | — | +| 8 | **3 khối cuối** | bọc mỗi khối `

` (PE `:929`,`:970`,`:974`) — đây là thứ tạo "nhịp" dọc của panel PE | — | +| 9 | **Tiêu đề 3 khối cuối** | `

` (+`mb-2` cho 2 khối lịch sử); **bỏ icon `//` + bỏ pill đếm**, viết `(n)` ngay trong tiêu đề: `Lịch sử duyệt ({approvals.length})` — khuôn `PeDetailTabs.tsx:781`,`:790` + `PeWorkflowPanel.tsx:930` | — | +| 10 | **Thẻ lịch sử duyệt** | `bg-slate-50/60 px-2.5 py-1.5 text-[11px]` → `rounded border border-slate-200 bg-white p-3 text-sm`; dòng 1 bọc `flex items-center justify-between` để **đẩy mốc-giờ sang phải** (`text-xs text-slate-500 shrink-0`); dòng 2 `mt-1 text-xs text-slate-500` | — | +| 11 | **Badge quyết định 3-ngả** | bỏ nhãn gộp *"Trả lại / Từ chối"* (`:83`), theo `decisionBadge` (`PeDetailTabs.tsx:3056-3063`): `toPhase===99` → **"Từ chối"** `bg-rose-100 text-rose-700 border border-rose-200`; reject khác → **"Trả lại"** `bg-amber-100 text-amber-700 border border-amber-200`; còn lại → **"Duyệt"** `bg-emerald-100 text-emerald-700 border border-emerald-200`. Cỡ `text-[11px] font-medium` (không phải `text-[9px] font-semibold`) | `KhkkApprovalDto.toPhase` **có** (`khkk.ts:307`) ⇒ tách được Từ-chối ⟂ Trả-lại | +| 12 | **Lịch sử thay đổi mở sẵn** | bỏ nút gập + bỏ `enabled: showChangelog` (`:186`) → render thẳng như PE `:973-975`; giữ `take:200` | xem §6.2 (chi phí 1 request) | +| 13 | **Chi-tiết cấp-chữ** | "Huỷ"→"Hủy"; `variant="outline"`→`"ghost"`; hộp chọn file dựng theo khuôn brand gạch-đứt + liệt-kê từng file có cỡ + nút `` (PE `:841-866`) | — | -### 4.C KHÔNG áp dụng (domain khác thật) -_(chờ)_ +**Bán-bê-thẳng (cần thêm 1 component nhỏ — route BE ĐÃ CÓ SẴN):** + +| # | Mục | Ghi chú | +|---|---|---| +| 14 | **Xem trước `` + tải xuống có xác thực** | PE `:938-953` + `AttachmentPreviewDialog.tsx:24-32`. KHKK **đủ route**: `GET {id}/attachments/{attId}/view` (`ContractSigningPlansController.cs:231-232`) + `/download` (`:222-223`). Cần (a) đổi `` `:500-505` sang `api.get(…, {responseType:'blob'})` khuôn PE `:327-342`; (b) thêm prop `basePath` cho `AttachmentPreviewDialog` (đang hardcode `evaluationId`, `:18`) hoặc bản KHKK riêng. 🔴 **Đồng thời là sửa LỖI THẬT — §6.1** | +| 15 | **Mờ nhánh sau cấp KẾT THÚC theo CẤU HÌNH** | PE mờ ngay khi phiếu **đang chạy** (`:165-169`) + badge "Kết thúc tại {X}" ở **Bước** (`:411-418`). KHKK chỉ mờ **sau khi đã duyệt** (`skipped` `:313`). Data đủ (`allowApproverFinalize` `khkk.ts:277` + `finalizeStepName/LevelName` `:392-393`). **Cần owner gật** — đổi *nghĩa hiển thị*, không chỉ màu | + +### 4.B THIẾU DỮ-LIỆU — nêu đúng field +| Mục | Field DTO thiếu | Nguồn BE | +|---|---|---| +| **H3 chip "✎ NS PRO" / "✎ NS CCM"** | `KhkkWorkflowLevelDto` (`fe-admin/src/types/khkk.ts:269-278`) chỉ có `{id, order, name, approverUserId, approverFullName, allowApproverFinalize}` — **thiếu `canEditProBudget` + `canEditCcmBudget`**, thứ PE có trên `PeCurrentApprovalLevelApprover` (`fe-admin/src/types/purchaseEvaluation.ts:252-253`) | `ContractSigningPlanWorkflowLevelDto` (BE `ContractSigningPlanFeatures.cs:105-110`, theo chú thích `khkk.ts:265-268`) không trả 2 cờ này | + +🔴 **Nhưng KHÔNG đề nghị thêm field.** 2 cờ đó của PE suy từ role để mở quyền sửa ô **ngân sách per-gói-thầu** (`PeWorkItemBudgets`, Mig 50) — khái niệm KHKK không có (KHKK chốt giá per-Line tại choke-point BE `ApplyApprovedValuesOnFinalize`, đã khai `KhkkWorkflowPanel.tsx:29`). Bê chip sang = **vẽ nhãn quyền không tồn tại**, tệ hơn thiếu ⇒ xếp §4.C. + +### 4.C KHÔNG áp dụng (khác domain thật — ĐỪNG bê) +`✎ NS PRO / NS CCM` (H3) · "Tiến trình duyệt 2-cấp phòng ban" (`:983-1052`, Mig 16) · bộ chọn giá chốt (`:794-820`) · 4 mode Trả lại (`:635-713`) · "Duyệt thẳng Cấp cuối" (`:725-748`) · "Duyệt done miễn CEO" (`:751-768`) · banner phiếu-trước-chưa-duyệt (`:512-516`) · fallback phiếu V1 (`:481-485`, đã khai `KhkkWorkflowPanel.tsx:27`). --- ## 5. SPEC thi-hành-được cho Opus 5 MAX -_(chờ)_ + +### 5.0 Phạm vi đụng file (KHÔNG mở rộng) +``` +fe-admin/src/components/khkk/KhkkWorkflowPanel.tsx ← sửa +fe-user/src/components/khkk/KhkkWorkflowPanel.tsx ← MIRROR y hệt +``` +🔴 **2 file đang SHA256 identical** — đo lúc audit: cả hai = `fb563b25cac09af9ccf00fe6145972244d25012a22bd8ffdd9faac2e04dd6913` (`wc -l` = 726 mỗi bên). Sửa 1 bên mà quên bên kia = vỡ bất-biến file tự khai ở `:4`. Cách an toàn: sửa fe-admin xong **copy nguyên file** sang fe-user, rồi `sha256sum` lại 2 bên. +🔴 **TUYỆT ĐỐI KHÔNG đụng** `PeWorkflowPanel.tsx` / `PeDetailTabs.tsx` — PE là **CHUẨN đang golive**, kéo PE về phía KHKK là làm hỏng thứ owner đang lấy làm mẫu. + +### 5.1 Thứ tự thi công (theo mức đập-vào-mắt giảm dần) +| Đợt | Việc | Mục §4.A | +|---|---|---| +| **Đ1 — vỏ** | bỏ card-accent + header icon-chip + `p-5`; dựng lại tiêu đề + meta kiểu PE | 1, 2, 3 | +| **Đ2 — 3 khối cuối** | thêm `border-t pt-4`, đổi tiêu đề `

` + `(n)`, bỏ pill/icon, mở sẵn changelog | 8, 9, 12 | +| **Đ3 — lịch sử duyệt** | thẻ trắng `p-3 text-sm`, mốc-giờ canh phải, badge 3-ngả có viền | 10, 11 | +| **Đ4 — nút + chip** | glyph thay lucide ở 3 nút, nút Xóa đỏ, chip phòng emerald | 5, 6, 7 | +| **Đ5 — banner + chữ** | banner finalize violet→emerald + đúng câu chữ PE; "Hủy"/ghost; hộp chọn file brand | 4, 13 | +| **Đ6 — file đính kèm** | `` preview + tải-xuống-qua-axios (**kèm sửa lỗi 401**) | 14 | +| **Đ7 — CHỜ OWNER** | mờ nhánh sau cấp KẾT THÚC theo cấu hình | 15 | + +### 5.2 Bất-biến phải giữ (đụng vào = hỏng nghiệp vụ, KHÔNG phải style) +1. `stepStatus` / `levelStatus` / `currentLevels` / `signedByLevelId` (`:122-155`) — KHKK **tự suy** trạng thái vì BE trả cây THÔ (`khkk.ts:265-268`). PE có `status` BE precompute. **Đừng "đơn giản hoá" theo PE.** +2. Gom Cấp cùng `order` (`:289`, `:307`) = OR-of-N khớp `ContractSigningPlanFeatures.cs:259-260`. +3. `actingLevel` + `approverFinalizeEligible` (`:165-167`) — mirror `ResolveActingLevel` BE; soi cờ của level khác = ô-tích nói dối. +4. `applyLevelFinalize` là **opt-IN, reset `false` mỗi lần mở dialog** (`:102`, `:431-432`) — F-1 S166 đã RATIFIED @S167. +5. `canSubmit` **cố ý không ẩn theo vai** (`:169-172`, bug-class gotcha #44). +6. 5 `invalidateQueries` sau transition (`:216-219`) + 2 sau xóa (`:230-231`) — có `pipeline-khkk-index` nuôi cây 4-folder S162. +7. `commentRequired` cho Return/Reject (`:242-244`). + +### 5.3 Nghiệm thu (acceptance) +- `cd fe-admin && npm run build` **0 lỗi TS**; `cd fe-user && npm run build` **0 lỗi TS**. +- `sha256sum` 2 file KHKK panel → **bằng nhau** sau khi sửa. +- `git diff --stat` chỉ hiện **đúng 2 file** đó. +- Soi mắt cạnh nhau: mở `/purchase-evaluations` chọn 1 phiếu ⟂ mở `/contract-signing-plans` chọn 1 phiếu → panel phải **cùng một hệ**: không thẻ-trong-thẻ, cùng nhịp `border-t`, cùng cỡ chữ khối lịch sử, cùng cách canh mốc-giờ. +- Bấm "Tải xuống" ở "File đính kèm khi duyệt" → **file tải được thật** (nay đang 401, §6.1). --- ## 6. Rủi ro / caveat / cái tao KHÔNG verify được -_(chờ)_ + +### 6.1 🔴 LỖI THẬT bắt được khi soi style (không chỉ style) +`KhkkWorkflowPanel.tsx:500-505` tải file bằng **thẻ `` trần**. +- JWT chỉ được gắn bằng **interceptor axios** (`fe-admin/src/lib/api.ts:18` `config.headers.Authorization = 'Bearer …'`); trình duyệt điều-hướng thẻ `` **không đi qua axios** ⇒ không có header. +- Endpoint đích **bắt buộc xác thực**: `ContractSigningPlansController.cs:222-223` `[Authorize(Policy = "KeHoachKyKet.Read")]`, cộng dồn với `[Authorize]` cấp class `:29`. +- ⇒ Suy luận: bấm "Tải xuống" **ăn 401**. PE không dính vì đi `api.get(..., {responseType:'blob'})` (`PeWorkflowPanel.tsx:327-342`). +- ⚠️ **Tao CHƯA chạy thử** (spawn này không có API + dev server) — đây là suy luận từ 2 mã nguồn, **cần bấm thật 1 lần** để chốt. Nếu đúng thì §4.A-14 đổi hạng từ "style" thành **vá lỗi**. + +### 6.2 Bỏ gập "Lịch sử thay đổi" = thêm 1 request mỗi lần mở phiếu +KHKK đang `enabled: showChangelog` (`:186`) ⇒ 0 request khi chưa bấm. PE fetch sẵn nhưng **dùng chung `queryKey ['pe-changelog', id]` giữa `ApprovalsTab` (`:3115`) và `HistoryTab` (`:3224`)** nên vẫn chỉ 1 request. KHKK không có nhu cầu dùng-chung đó ⇒ bỏ gập = **+1 request `take:200` / phiếu**. Chấp nhận được (giống PE), nhưng khai thẳng chứ không giấu. + +### 6.3 Nút "Từ chối" — cần owner quyết, ĐỪNG tự bỏ +PE **vẫn còn code** nút Từ chối nhưng thực tế không hiện: BE gỡ `TuChoi` khỏi `nextPhases` + FE filter thêm (`PeWorkflowPanel.tsx:314-317`, UAT S60 "bỏ luôn nút Từ chối — Duyệt hoặc Trả về thôi"). KHKK **vẫn hiện** (`:456-467`). Muốn "giống PE" tuyệt đối thì phải bỏ — nhưng đó là **bỏ chức năng**, không phải đổi style, và KHKK có `KhkkPhase.TuChoi` + banner riêng (`:397-401`) + `commentRequired`. ⇒ **Hỏi owner**, đừng tự cắt. + +### 6.4 KHÔNG có ảnh chụp màn hình lượt này (khai thẳng, khuôn NO-SCREENSHOT S159) +Vòng soi-bằng-mắt FD2 **không chạy được** trong spawn này: không có dev server, không có API/SQL, và panel duyệt nằm sau `ProtectedRoute` + cần phiếu KHKK có thật ở trạng thái `ChoDuyet` (bẫy rig #3 trong ký-ức: proxy Vite HTTP ⟂ API HTTPS :5443). Thay bằng **đọc mã nguồn đối chiếu theo cặp file:line** — mọi khẳng định trên đều truy được về dòng cụ thể. **Không bịa screenshot.** Khi lane thi công xong, chốt bằng cách mở prod cạnh nhau (ký-ức: authed pages nghiệm thu ở prod, đừng vật lộn dev-rig). + +### 6.5 Ngoài lăng-kính này (nêu để lane khác không sót) +- Thứ tự upload file khác nhau: PE upload **TRƯỚC** transition (`:234-244`, "file lỗi = không duyệt"), KHKK upload **SAU** (`:198-207`, "transition hỏng thì không đẻ file mồ côi"). Hai lựa chọn đều có lý do viết trong mã ⇒ **không đụng ở đợt style**. +- `AttachmentPreviewDialog` xuất sẵn `isPreviewable` (`:11-14`) nhưng `PeWorkflowPanel.tsx:326` **tự viết lại regex** — trùng lặp sẵn có bên PE, không phải việc của đợt này. +- Panel 2 KHKK (`KhkkDetailContent.tsx`) có "Ý kiến cấp duyệt" + có `signedByUserId`/`approverUserId` (`khkk.ts:256-262`) ⇒ badge "Admin duyệt thay" (khuôn `PeDetailTabs.tsx:756-760`) **dựng được** — nhưng thuộc lăng-kính panel 2, không phải lane này. --- -END sub-frontend-designer-L1 — VERDICT= +## 7. Kết luận 1 dòng cho owner +Sơ đồ Bước/Cấp của 2 bên **đã giống nhau gần như tuyệt đối** (H2, H4) — cái làm owner thấy "khác nhau" nằm ở **vỏ panel (thẻ-trong-thẻ có viền xanh + tiêu đề có icon)** và ở **3 khối cuối (mất đường kẻ ngăn, tiêu đề nhỏ hơn, thẻ lịch sử xám và đặc chữ, mốc-giờ không canh phải, lịch sử thay đổi bị gập)**. Toàn bộ đều **bê thẳng được**, trừ chip "NS PRO/NS CCM" (KHKK không có khái niệm ngân sách đó — **đừng bê**). + +--- + +END sub-frontend-designer-L1 — VERDICT=15 lệch có-thể-sửa (13 bê-thẳng + 1 bán-bê kèm vá lỗi 401 + 1 chờ-owner) · 1 KHÔNG-BÊ vì thiếu khái niệm (H3 NS PRO/CCM) · H2+H4 đã khớp · lệch nặng nhất = vỏ card-accent (H0) không nằm trong danh sách owner nêu diff --git a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L2.md b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L2.md index a2dda51..7c1bea8 100644 --- a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L2.md +++ b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L2.md @@ -103,7 +103,7 @@ Shell `