9.1 KiB
sub-implementer-frontend-2 — [F-11] nối cờ F6 allowApproverDelete vào Designer V2
run
2026-07-27-S155-pe-delete-approver· ĐỢT 1 · vaiimplementer-frontendSpec canonical:spec-pe-delete-approver.md§F.4 mục F-11 (dòng 303) + §F.2 bảng 13 thao tác hạng 2a ✅ AN TOÀN. Ghi TRONG LÚC LÀM (5/5 sub run này mất return vì #53) — file này luôn hợp lệ giữa chừng.
0. Phạm vi tự chốt trước khi viết dòng nào
| Làm | Không làm (đợt 2) |
|---|---|
| 5 dây Designer fe-admin + ô tick F6 | nút Xóa ở màn duyệt (PeWorkflowPanel.tsx) |
1 dây hiển thị chỉ-xem fe-user (WorkflowMatrixViewPage:282) |
menu restructure (Layout.tsx regex, menuKeys) |
| type/DTO FE tương ứng (2 chỗ) | màn "Đã xóa" |
Ý nghĩa cờ (viết đúng, không suy diễn): PER-NGƯỜI, KHÔNG per-Cấp. 1 Level row = 1 người
(ApproverUserId là field của row); "Cấp" chỉ là tập row cùng Order. Owner chốt per-ROW để giữ
sát ý "người đứng đầu phòng" = 1 người, admin chỉ đích danh (spec §B2 dòng 60-66).
Khối UI tick nằm trong ô Quyền duyệt {tên NV} (:928-930) ⇒ ngữ cảnh per-người đã hiện sẵn trên màn.
1. Đối chứng: F5 allowApproverFinalize đi qua ĐÚNG 5 chỗ nào (đọc trước, không tin spec suông)
Grep allowApproverFinalize trên fe-admin/ → 5 hit trong ApprovalWorkflowsV2Page.tsx ngoài ô tick:
| Dây | Line (trước sửa) | Vai trò | Hỏng gì nếu thiếu |
|---|---|---|---|
| 1 | :54 |
LevelDto — mirror BE AwAdminOverviewDto (đọc VỀ) |
TS2339 khi đọc l.allowApproverDelete ở dây 3 |
| 2 | :110 |
EditLevelEntry — state form đang soạn |
TS2339 ở updateField + dây 4/5 |
| 3 | :159 |
copyFromDefinition() — Nhân bản version cũ |
tick cũ mất trắng khi admin bấm "Nhân bản" |
| 4 | :177 |
makeDefaultLevelEntry() — "+ Thêm NV" |
entry mới thiếu key ⇒ TS2739 (object literal thiếu field) |
| 5 | :605 |
payload POST /approval-workflows-v2 |
🔴 BẪY: tick xong KHÔNG gửi lên BE |
🔴 Vì sao dây :605 là bẫy thật (không phải cảnh báo lễ nghi): payload POST là object literal
liệt kê từng field bằng tay (:594-606), KHÔNG spread ...e. TypeScript không báo lỗi khi
thiếu field (thừa field mới lỗi, thiếu thì chỉ là object hẹp hơn) ⇒ build vẫn PASS, UI tick vẫn
hiện dấu tick, onChange vẫn chạy, state vẫn đổi — chỉ có BE không bao giờ nhận. Không có
compiler/test nào bắt; chỉ lộ khi UAT tick → lưu → mở lại thấy trống, hoặc tệ hơn: quyền xóa im
lặng không tới người được chỉ định.
2. Thay đổi đã áp — 3 file, 10 neo
2.1 fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx (+28 dòng) — 5 dây + ô tick
| # | Line MỚI (cũ) | Nội dung |
|---|---|---|
| Dây 1 | :58 (54) |
LevelDto + allowApproverDelete: boolean ngay dưới allowApproverFinalize |
| Dây 2 | :116 (110) |
EditLevelEntry + allowApproverDelete: boolean |
| Dây 3 | :168 (159) |
copyFromDefinition() → allowApproverDelete: l.allowApproverDelete ?? false |
| Dây 4 | :188 (177) |
makeDefaultLevelEntry() → allowApproverDelete: false |
| Dây 5 | :620 (605) |
payload POST /approval-workflows-v2 → allowApproverDelete: e.allowApproverDelete |
| Ô tick | :1025-1034 |
<label class="col-span-2 … text-rose-700"> ngay DƯỚI ô F5 (kết ở :1024) |
Nhãn ô tick (tiếng Việt, per-người): "Cho phép NV này XÓA phiếu khi đến lượt duyệt của mình".
Màu text-rose-700 phân biệt với F5 text-emerald-700 — hành vi phá huỷ, cố ý cho khác màu.
updateField là (field: keyof EditLevelEntry, value: boolean) (:940) ⇒ chuỗi 'allowApproverDelete'
được TS kiểm, không phải magic string.
2.2 fe-user/src/types/approvalWorkflowV2.ts (+7/−1)
AwLevelDto + allowApproverDelete: boolean (:27); header đổi "8 Allow flag"* → "9 Allow flag"* (:5-6).
2.3 fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx (+2) — dây chỉ-xem
:284 (F5 ở :282) — <FlagRow active={r.level.allowApproverDelete} … colSpan2 /> cùng nhãn với Designer.
2.4 🔴 Dây :605/:620 — KHAI RIÊNG THEO YÊU CẦU: ĐÃ NỐI, và đây là bằng chứng nó thật sự là bẫy
Kiểm cuối cùng, sau khi đã build xong. Grep allowApproverDelete toàn repo *.ts,*.tsx = 10 hit / 3 file,
trong đó hit ApprovalWorkflowsV2Page.tsx:620 nằm bên trong s.levelEntries.map(e => ({…})) của
await api.post('/approval-workflows-v2', …).
Ba tầng cùng im lặng nếu thiếu dòng này (đo, không đoán):
- TS không bắt — payload là object literal liệt kê tay từng field (
:609-621), không...e. Thiếu key = object hẹp hơn, hợp lệ vớianyparam củaapi.post⇒ build vẫn✓ built. - BE không bắt —
AwLevelInputlà positional record với default= false(ApprovalWorkflowV2AdminFeatures.cs:216-230) ⇒ JSON thiếu key bind thẳngfalse, không 400, không validator nào kêu. - UI không bắt — ô tick là controlled input trên state FE, tick vẫn hiện dấu, vẫn đổi màu.
⇒ Chuỗi "tick → lưu → toast xanh → DB ghi false" hoàn toàn không có tín hiệu lỗi ở bất kỳ tầng nào.
Chỉ lộ khi UAT mở lại thấy ô trống, hoặc tệ hơn: người được chỉ định bấm xóa thì 403 mà không ai hiểu vì sao.
3. Verify
| Phép đo | Kết quả |
|---|---|
npm run build fe-admin |
✅ PASS — ✓ 1950 modules transformed · ✓ built in 21.05s · bundle index-_gosUXpu.js 1,630.28 kB · 0 TS error |
npm run build fe-user |
✅ PASS — ✓ 1939 modules transformed · ✓ built in 8.32s · bundle index-B-nir85X.js 1,536.32 kB · 0 TS error |
| Warning | 3 cái, pre-existing 100%: @import phải đứng trước rule · chunk >500 kB · realtime.ts INEFFECTIVE_DYNAMIC_IMPORT |
| Dây đã nối | 5/5 + ô tick + 2 neo fe-user (grep 10 hit / 3 file) |
git status |
đúng 3 file code + sub-MD này. (.session-counter.json = harness, không phải tôi; sub-implementer-backend-1.md = lane BE song song) |
Mirror / SHA: slice này KHÔNG có cặp mirror nào — Designer chỉ tồn tại ở fe-admin
(fe-user không có ApprovalWorkflowsV2Page.tsx), matrix chỉ-xem chỉ tồn tại ở fe-user
(fe-admin không có WorkflowMatrixViewPage.tsx, cũng không có types/approvalWorkflowV2.ts).
⇒ 3 file, 0 cặp SHA. Không phải bỏ sót mirror.
Chứng minh đợt-2 chưa bị đụng: PeWorkflowPanel.tsx md5 02671be6334110028c55fe82f5e70dee ×2 app
— khớp đúng con số spec §E3 ⇒ file đặt nút Xóa còn nguyên vẹn và vẫn byte-identical.
4. 🔴 Phụ thuộc chéo lane — cần lane BE xác nhận (chưa xong lúc tôi return)
Đo 2 lần, lane BE land giữa chừng:
- Lúc bắt đầu:
AllowApproverDeletetrongsrc/Backend= 0 hit. - Lúc return (sau build): đã có entity
ApprovalWorkflow.cs:169 public bool AllowApproverDelete- migration
20260727033522_AddPeAllowApproverDelete(3-file). ✅ Tên khớp chính xác cái FE chờ. NHƯNGSolutionErp.Application/vẫn 0 hit ⇒ 4 neo DTO chưa nối.
- migration
⇒ Đúng lúc này, tick trên Designer vẫn chưa tới được DB: cột đã có, ô tick đã có, nhưng không có đường DTO nối 2 đầu. Cần lane BE đóng nốt 4 neo dưới đây (đây là số dòng của khuôn F5, đo trực tiếp trên file):
- Đọc về:
AwLevelDto(…AdminFeatures.cs:41) phải cóbool AllowApproverDelete+ projection:188⇒ camelCase mặc định của System.Text.Json cho ra đúngallowApproverDeletemàLevelDto:58chờ. - Ghi lên:
AwLevelInput(:230) phải cóbool AllowApproverDelete = false+ map entity:368. 🔴 Nếu BE đặt tên khác (vdAllowDeleteWhilePending) thì FE gửi key lạ → bị bỏ qua im lặng, đúng cùng một class lỗi với bẫy:620. Đây là chỗ duy nhất 2 lane có thể lệch mà cả 2 build đều xanh.
5. Đã CỐ Ý không làm (ranh giới đợt 1/đợt 2)
| Bỏ qua | Lý do |
|---|---|
Nút Xóa ở màn duyệt (PeWorkflowPanel.tsx ×2) |
đợt 2, spec mục 14/14a — cần prop + mutation mới |
types/purchaseEvaluation.ts (fe-admin:274/427 · fe-user:276/429) |
đó là bundle PE detail (levelOptions + approvalFlow), BE đợt 1 không trả cờ này ⇒ khai boolean non-optional bây giờ = type nói dối. Thuộc gate E4 đợt 2 |
Layout.tsx regex, menuKeys.ts, màn "Đã xóa" |
checklist mục 3-28, đợt 2 |
| Luật 4-place mirror | không kích: 0 route mới, 0 menu key mới, 0 page mới (spec §C xác nhận thêm nút/mục không kích 4-place) |