# sub-d2-fe-2 — ĐỢT 2 FRONTEND (nút Xóa màn duyệt + menu + màn "Đã xóa")

- **role:** implementer-frontend · **run:** `2026-07-27-S155-pe-delete-approver` · **đợt:** 2
- **scope:** `fe-admin/src/**` + `fe-user/src/**` ONLY. KHÔNG chạm `src/Backend/**`, KHÔNG chạm `tests/`.
- **ghi TRONG LÚC LÀM** (flush từng phần, tự `wc -c` sau mỗi flush).

---

## 0. Nền đã ĐO (trước khi sửa) — số thật, không phải trí nhớ

| Đo | Kết quả |
|---|---|
| `PeWorkflowPanel.tsx` md5 ×2 app | `02671be6334110028c55fe82f5e70dee` (IDENTICAL, 942 dòng) |
| `PurchaseEvaluationsListPage.tsx` md5 ×2 app | `99e38e167d2f745ea7628174f1d2d6bf` (IDENTICAL — **bonus**, spec không nói) |
| `usePermission.ts` md5 ×2 app | `2b210b753e8fe2704407c787cb82779d` (IDENTICAL) |
| `Layout.tsx` | **DIVERGE** admin `ca59f56c…` / user `cab674f4…` (đúng như thiết kế — 2 app khác staticMap) |
| `lib/menuKeys.ts` | DIVERGE đúng 2 dòng (`Off_AttendanceReport` admin-only) |
| `git status` lúc bắt đầu | sạch (chỉ `.session-counter.json`) ⇒ **lane BE chưa land gì** |

## 1. 🔴 PHỤ THUỘC LIÊN-LANE — FE gọi gì, BE phải có gì

Hợp-đồng `run.md:139-154` chốt 3 route + 2 menu-key. FE bám ĐÚNG. **NHƯNG** hợp-đồng
**không** đặt tên cho 2 thứ FE bắt buộc phải có — khai ở đây để lead verify, KHÔNG tự chế lung tung:

### (D1) Cờ F6 per-NGƯỜI trong bundle chi tiết phiếu
- FE đọc: `evaluation.currentLevelOptions.allowApproverDelete`
- BE PHẢI thêm: `bool AllowApproverDelete` làm **member positional thứ 9** của
  `ApprovalWorkflowOptionsDto` (`src/Backend/…/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs:123-131`)
  + gán từ `curLevel.AllowApproverDelete` tại `PurchaseEvaluationFeatures.cs:1088-1096`.
- Vì sao chỗ này mà không phải chỗ khác: `currentLevelOptions` là kênh **DUY NHẤT** per-NGƯỜI —
  nó resolve bằng `l.ApproverUserId == currentUser.UserId` (`PurchaseEvaluationFeatures.cs:1083-1085`).
  Kênh còn lại `approvalFlow.steps[].levels[].allowApproverFinalize` là **OR-of-N per-CẤP**
  (`:1182 g.Any(...)`) — dùng nó là vi phạm B2 (`run.md:159` per-ROW).
- **Nếu BE quên:** field về `undefined` → gate `=== true` false → **nút KHÔNG BAO GIỜ HIỆN**,
  build 2 bên vẫn xanh. Đúng dạng silent-drop đợt 1. ⇒ lead phải grep xác nhận.

### (D2) Chỗ chứa "lý do xóa" trong request
- Hợp-đồng chỉ ghi `DELETE /api/purchase-evaluations/{id}/by-approver`, **không** nói payload.
- FE gửi **CẢ HAI** đường cho tương thích mọi kiểu binding BE:
  `api.delete(url, { params: { reason }, data: { reason } })`
  → BE bind `[FromQuery] string? reason` ✅ · bind `[FromBody] {Reason}` ✅ · không nhận gì ✅ (bị bỏ qua).
- Chủ ý: thà thừa 1 query-param còn hơn mất vết audit vì lệch tên. Ghi rõ để reviewer không tưởng là cẩu thả.

### (D3) DTO list "Đã xóa"
- FE khai `PeDeletedListItem = PeListItem + { deletedAt?, deletedBy?, deletedByName? }` — **tất cả optional**,
  render phòng thủ (`—` khi thiếu). BE trả thêm/thiếu đều không vỡ. Tên bám `AuditableEntity`
  (`src/Backend/SolutionErp.Domain/Common/AuditableEntity.cs:5-7`: `IsDeleted`/`DeletedAt`/`DeletedBy`).

## 2. 🚩 FLAG — mục "vá `WfView` cho fe-admin" TÔI KHÔNG LÀM (tiền-đề của lệnh sai)

Lệnh: *"vá lệch sẵn có: `fe-admin:107` THIẾU `WfView` so với `fe-user:120`"*.
Đo trước khi làm thì tiền-đề **không đứng**:

| Bằng chứng | fe-user | fe-admin |
|---|---|---|
| Page `pages/pe/WorkflowMatrixViewPage.tsx` | CÓ (323 dòng) | **KHÔNG CÓ** |
| Route `/purchase-evaluations/workflow-matrix` | `App.tsx:60` | **KHÔNG CÓ** (`App.tsx:78-81` chỉ 4 route PE) |
| Types `types/approvalWorkflowV2.ts` | CÓ | **KHÔNG CÓ** (admin để type inline trong `ApprovalWorkflowsV2Page.tsx`) |
| Catch-all | — | `App.tsx:107-114` render *"Trang này chưa được build"* |

⇒ Thêm `WfView` vào regex admin **một mình** = đẻ link chết dẫn tới "Trang này chưa được build".
Đó là **regression nhìn thấy được**, tệ hơn trạng-thái-hiện-tại (leaf bị drop im lặng).
Sửa cho đúng cần **3 file mới bên admin** (page + types + route) = ngoài phạm vi đợt 2, và là
lựa-chọn UX (admin nên thấy matrix read-only hay Designer sửa được?) ⇒ **thuộc quyền owner/lead**.

**3 đường cho lead chọn (đều ≤ 1 lượt):**
- **(a)** mirror hẳn: copy `WorkflowMatrixViewPage.tsx` + `types/approvalWorkflowV2.ts` sang admin + route + regex.
- **(b)** rẻ nhất: admin map `Pe_{code}_WfView` → `/system/approval-workflows-v2/{code}` (Designer đã sống, `Layout.tsx:130-136`).
- **(c)** giữ nguyên: coi lệch này là CỐ Ý (admin có Designer rồi, không cần bản read-only) → ghi 1 dòng comment cho người sau khỏi "vá" lại.

Tôi để nguyên trạng (đường **c** *de facto*) + không chèn comment tránh đụng byte thừa. Chờ lead.
