# sub-implementer-frontend-1 — W3 ĐỢT-1b: FE panel duyệt KHKK ×2 app (S161, 2026-07-29)

> Ghi TỪNG MỤC trong lúc làm (chống #53). Task: panel `KhkkWorkflowPanel.tsx` + gắn DetailPage + types transitions, SHA-pair ×2 app, build ×2.
> BE logic W3 CHƯA có (task 1 skeleton song song) ⇒ bấm nút sẽ 404/500 — CHẤP NHẬN đợt này.

## MỤC 0 — Baseline SHA (trước khi sửa)

```
340e64d0…  fe-user/src/types/khkk.ts            = fe-admin  (PAIR OK)
9b91606e…  fe-user/src/pages/khkk/KhkkDetailPage.tsx = fe-admin  (PAIR OK)
```

## MỤC 1 — RECON: field THẬT của detail DTO (BE đĩa, KHÔNG suy)

Nguồn: `src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs`

| Cái | Bằng chứng | Ghi chú |
|---|---|---|
| `ContractSigningPlanDetailDto` | `:119-147` | field CUỐI = `List<ContractSigningPlanWorkflowStepDto> WorkflowSteps` `:147` |
| `ContractSigningPlanWorkflowStepDto(Id, Order, Name, DepartmentId, Levels)` | `:112-117` | KHÔNG có `DepartmentName`, KHÔNG có `Status` |
| `ContractSigningPlanWorkflowLevelDto(Id, Order, Name, ApproverUserId, ApproverFullName)` | `:105-110` | KHÔNG có `Status`, KHÔNG có `allowApproverFinalize` (W1 rào type-10) |
| `ContractSigningPlanLevelOpinionDto` | `:93-103` | có `ApproverUserId` (`:99`) — FE types W2 **THIẾU** field này ⇒ bổ sung |
| con-trỏ đôi | `:136-137` `CurrentWorkflowStepIndex` + `CurrentApprovalLevelOrder` | |
| Tree build | `:537-566` | Steps sort `Order`, Levels sort `Order`, `ApproverFullName` join Users `:549-552` |

🔴 **KHÁC PE CĂN BẢN — đây là điểm dễ đứt nhất của wave này:** PE trả `approvalFlow` đã có sẵn
`status: 'Done'|'Current'|'Pending'` per Bước/Cấp (BE precompute). **KHKK trả CÂY THÔ, KHÔNG status**
⇒ FE **phải tự suy** trạng thái từ con-trỏ đôi. Không có field `status` nào để đọc.

🔴 **Ngữ nghĩa con-trỏ (BE tự khai, `:224-226`):** `CurrentWorkflowStepIndex` là **INDEX 0-based** vào
list Steps đã sort theo Order — **KHÔNG phải Order**. `CurrentApprovalLevelOrder` là **GIÁ TRỊ** `Level.Order`.
Luật so khớp OR-of-N canonical BE `:259-260`:
`steps[idx].Levels.Any(l => l.Order == CurrentApprovalLevelOrder && l.ApproverUserId == userId)`
⇒ FE mirror ĐÚNG vị ngữ này (nhiều Level **cùng Order** = OR-of-N; lấy `.some()`, KHÔNG `.find()` 1 người).

Rào XEM BE `:184-195` (`EnsureCanViewAsync`): Admin ∨ drafter ∨ (phase ≠ DangSoanThao ∧ approver-của-workflow-đã-pin).
⇒ phiếu **Nháp** approver KHÔNG đọc được; **TraLai** thì đọc được.

Endpoint transitions: **CHƯA có trong controller** (`ContractSigningPlansController.cs` grep `HttpPost` → chỉ
`/`, `/{id}/dossier-items`, `/{id}/attachments`). Đúng như đề bài: đợt-1 FE đi trước BE.

## MỤC 2 — Khuôn PE panel: cái GIỮ / cái BỎ

Nguồn `fe-user/src/components/pe/PeWorkflowPanel.tsx` (1063 dòng).

GIỮ (mirror): sơ đồ Bước→Cấp ✓/●/○ `:381-478` · banner đến-lượt `:489-508` (`actorInV2Level`/`blockedByV2Level`
`:109-114`) · nút 3 màu emerald/amber/red `:546-562` · Dialog + ô ý kiến `:869-870` · `useAuth()` lấy actor `:66-67`
· `qc.invalidateQueries` + `toast.error(getErrorMessage(e))` `:288`.

BỎ (đơn-giản-hoá theo lệnh): price-picker `:794-820` · budget/pendingPriors `:170-180` · file-đính-kèm-khi-duyệt
`:823-868` · 4 return-mode radio `:635-713` (W3 = TraLai toàn phần, spec §②-3(c)) · `skipToFinal`/`finalizeByCcm`/
`applyLevelFinalize` `:57-63` (spec §②-3(b): **CẤM** port 2 đường kết thúc sớm) · xóa-mềm-bởi-approver `:295-312`.
