Files
solution-erp/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/sub-implementer-frontend-2.md
2026-07-27 10:50:36 +07:00

9.1 KiB
Raw Blame History

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 · vai implementer-frontend Spec 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-v2allowApproverDelete: 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(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):

  1. 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ới any param của api.post ⇒ build vẫn ✓ built.
  2. BE không bắtAwLevelInput là positional record với default = false (ApprovalWorkflowV2AdminFeatures.cs:216-230) ⇒ JSON thiếu key bind thẳng false, không 400, không validator nào kêu.
  3. 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: AllowApproverDelete trong src/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ƯNG SolutionErp.Application/ vẫn 0 hit4 neo DTO chưa nối.

⇒ Đú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 đúng allowApproverDeleteLevelDto:58 chờ.
  • Ghi lên: AwLevelInput (:230) phải có bool AllowApproverDelete = false + map entity :368. 🔴 Nếu BE đặt tên khác (vd AllowDeleteWhilePending) 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)