10 KiB
run — PE: nút XÓA phiếu ở màn DUYỆT (approver-side)
- run-id:
2026-07-27-S155-pe-delete-approver - phiên: S155 (phiên-LOGIC L7, window 2)
- pipeline anh lệnh: 2 Invest → file-chi-tiết + checklist → review spec → hmw Opus 5 MAX
- mode: RUN-TRACE (≥3 task) — sub ghi full-detail vào CHỈ
sub-<role>-<i>.mdcủa mình
Nguồn yêu cầu (ảnh chat anh gửi — UAT thật, không phải giả định)
Phiếu mẫu: PE/2026/A/046 · Duyệt NCC · dự án FLOCK03 · hạng mục MAT-16 "16 Mat Khác" · gói thầu "16 Mat - Băng cản nước" · trạng thái Đã gửi duyệt · có cờ GẤP (PRO).
Workflow đang chạy: Bước 1 (Phòng Cung ứng) — Cấp 2, NV duyệt Bùi Lê Thủy Trà, "Đến lượt bạn duyệt". Bước 2 có Cấp 2 "Duyệt thay CEO". Bước 3 Ban Giám đốc, kết thúc tại Cấp 2. Lịch sử duyệt (1).
Khối HÀNH ĐỘNG hiện chỉ có: ✓ Duyệt · ← Trả lại — thiếu Xóa.
Banner đang hiện: "Bạn được phép chỉnh sửa Hạng mục / NCC / Báo giá (workflow bật mode Approver edit)".
Lời user verbatim (Tra Sol):
- "@Kenny Chỗ duyệt - tạo giúp em nút xóa với"
- "Vì mấy bạn bắt sai cái gói thầu" → "nên phải cho quyền xóa cái phiếu đó luôn"
- "chứ lỡ bấm sai cái gói thầu / quay lại không được / phải xóa thì nó mới ko có lũy kế lên"
Lời anh (owner) verbatim: "Hic hôm trước có cái chỗ cho huy / mọi người nói là ko cần cái đó / giờ lại cần" ⇒ 🔴 giả-thuyết cần KIỂM: đường xóa/thu-hồi đã từng tồn tại rồi bị gỡ. Nếu đúng → khôi phục rẻ hơn dựng mới, và phải tìm ra lý do gỡ để không tái phạm.
Vì sao đây là lỗi SỐ LIỆU, không phải UX
CLAUDE.md S134: ComputePendingAsync complementary — phiếu ChoDuyet có winner được tính vào lũy kế TẠM TÍNH. Phiếu bấm sai gói thầu đang treo ở bước duyệt ⇒ đang ăn ngân sách hạng mục sai. "Trả lại" đẩy về TraLai=98 chứ không triệt tiêu ⇒ số tạm tính vẫn lệch. Đó chính là điều Tra Sol mô tả bằng chữ "lũy kế lên".
Stages
- S1 — 2 Invest song song (BE-slice + FE/authz-slice) →
sub-invest-be-1.md·sub-invest-fe-2.md - S2 — file chi tiết + checklist (lead tổng hợp) →
spec-pe-delete-approver.md - S3 — review spec (reviewer, adversarial)
- S4 — hmw Opus 5 MAX thực thi
Ràng buộc mang theo mọi stage
- Governance L7 vẫn treo 22 FLAG chờ (42)(43)(44) — việc này song song, KHÔNG đóng thay.
- Soft delete là quy ước sẵn có (
AuditableEntity:IsDeleted/DeletedAt/DeletedBy) — spec phải nói rõ dùng cái này hay thu-hồi-về-Nháp. - Quyền = 2 tầng độc lập (bài học S118 / gotcha #82): display-layer menu
CanRead⟂ API-authz[Authorize(Policy)]. Ẩn nút ≠ đóng API. - Xóa phải có vết (changelog/audit) — phiếu đã có
Lịch sử duyệt (1).
taskList snapshot
- invest BE: entity/state-machine/endpoint-xóa/authz/lũy-kế/changelog + git-khảo-cổ "chỗ cho huy"
- invest FE: màn duyệt (component khối HÀNH ĐỘNG) + permission matrix
Pe_*+ nơi PE đang xóa được (nếu có) + dấu vết UI đã gỡ
🔴 OWNER CHỐT (2026-07-27, anh trả lời 3 câu + ảnh menu)
(1) AI được xóa: "Người đứng đầu phòng đc xóa khi đến lượt họ." → 2 điều kiện AND: là người đứng đầu phòng ∧ đang tới lượt duyệt của họ. CHẶT HƠN "ai tới lượt cũng xóa được".
(2) Kiểu xóa: "Xóa mềm." → dùng đúng cơ chế AuditableEntity.IsDeleted sẵn có (qua AuditingInterceptor), KHÔNG dựng mới.
(3) Ngữ nghĩa + phiếu đã có lượt duyệt: "Tính năng xóa mềm này sinh ra để phục vụ chuyện đó mà, vì nó đang trong quá trình duyệt và đang ăn lũy kế nên phải có nút xóa -> nhưng xóa là chuyển trạng thái đưa xuống mục đã xóa và ko ăn lũy kế nữa."
⇒ KHÔNG thêm rào cho phiếu đã có Lịch sử duyệt (n) — đang-duyệt CHÍNH LÀ ca cần cứu. Câu hỏi "đã duyệt rồi có xóa được không" = ĐÃ TRẢ LỜI: CÓ.
⇒ Xóa = chuyển trạng thái + xuất hiện ở mục "Đã xóa" + rớt khỏi lũy kế.
(4) 🆕 RESTRUCTURE MENU (ảnh, ghi chú đỏ chỉ vào mục menu "Duyệt"):
Chỗ này:
Duyệt -> Đang duyệt.
Thêm :
Đã duyệt.
Đã xóa.
Menu hiện tại dưới 1. Duyệt Nhà Cung Cấp - Thầu phụ (NCC-TP): Luồng duyệt · Danh sách · Thao tác · Duyệt.
Menu sau khi sửa: Luồng duyệt · Danh sách · Thao tác · Đang duyệt (đổi tên từ Duyệt) · Đã duyệt (MỚI) · Đã xóa (MỚI).
URL màn hiện tại: /purchase-evaluations?type=1&pendingMe=1 · tiêu đề "Duyệt NCC — Chờ duyệt (8)" · lọc cố định Đã gửi duyệt.
🔴 Nghịch lý phải xử ở (4) — nối thẳng vào F9/F10 của sub BE
Global filter HasQueryFilter(x => !x.IsDeleted) (PurchaseEvaluationConfiguration.cs:84) + IgnoreQueryFilters 0 hit toàn backend là thứ khiến phiếu xóa tự rớt khỏi 4 phép cộng ⇒ cho không ý (3).
NHƯNG cùng filter đó làm phiếu xóa vô hình với MỌI query ⇒ mục "Đã xóa" không thể dựng nếu không mở IgnoreQueryFilters().
⇒ Ràng buộc spec: mở IgnoreQueryFilters() cho ĐÚNG 1 endpoint list "Đã xóa", KHÔNG đụng accumulator, KHÔNG mở diện rộng. Đây sẽ là chỗ đầu tiên trong toàn backend dùng IgnoreQueryFilters ⇒ đáng 1 dòng checklist riêng + test.
❓ Chưa giải: "người đứng đầu phòng" map vào đâu trong schema?
Ảnh cho thấy 1 Cấp có NHIỀU người (Phòng Cung ứng — Cấp 2: Bùi Lê Thủy Trà, Trần Xuân Lưu, Lê Trần Đăng Trường). 3 cách hiểu khả dĩ, ra 3 đoạn code KHÁC nhau:
(a) Cấp cao nhất trong Bước (Level order) · (b) field trưởng phòng trên Department (Mig 51 có ParentId org-tree) · (c) role/permission riêng.
⇒ giao sub invest #3 tìm, CẤM đoán.
Stages (cập nhật)
- [~] S1 — 2 Invest BE/FE: đã cứu từ đĩa Q1-Q3 (BE) + Q1,Q2,Q4 (FE); cả 2 dính #53, đã SendMessage-resume làm nốt
- S1-bis — Invest #3 lát cắt MỚI: menu restructure + "Đã xóa" list + "người đứng đầu phòng"
- S2 — spec + checklist · [ ] S3 — reviewer · [ ] S4 — hmw Opus 5 MAX
🔴 OWNER CHỐT — BỔ SUNG (2026-07-27, lượt 2)
(5) Quyền xóa = cờ AllowApproverDelete per-Cấp-duyệt (AskUser). Owner bác phương án Department.ManagerUserId.
🔴 KHAI: đổi nghĩa so với chữ đầu "người đứng đầu phòng" → thành "cấp nào admin tick". Owner chọn với trade-off ghi rõ trên bàn. KHÔNG phải hiểu nhầm.
(6) Màn "Đã xóa" = CHỈ XEM, chưa làm khôi phục (AskUser).
(7) 🆕 QUY TRÌNH DUYỆT — sửa TẠI CHỖ vs bắt buộc TẠO MỚI (anh, verbatim):
"Chỗ quy trình duyệt -> Cho thêm tính năng thêm người/điều chỉnh quyền -> Chứ ko cần phải tạo mới. Khi nào cần thay đổi quy trình duyệt thì mới bắt buộc tạo mới."
⇒ Bài toán = PHÂN LOẠI THAY ĐỔI thành 2 lớp:
- Lớp AN TOÀN (sửa tại chỗ, giữ nguyên Version): thêm/bớt NGƯỜI trong một Cấp · điều chỉnh QUYỀN (các cờ F1-F6)
- Lớp PHÁ VỠ (bắt buộc Version mới): đổi CẤU TRÚC quy trình — thêm/bớt/đổi thứ tự Bước hoặc Cấp
🔗 Đụng thẳng spec đang viết: tick cờ F6 AllowApproverDelete = "điều chỉnh quyền" ⇒ thuộc lớp AN TOÀN ⇒ phải sửa được tại chỗ trên workflow đang có phiếu chạy. Nếu không thì mọi lần bật quyền xóa lại đẻ 1 version mới.
Nền đã có (lead peek): ApprovalWorkflow.Version (int, monotonic per Code) · IsActive · ActivatedAt · IsUserSelectable; note ApprovalWorkflowV2AdminFeatures.cs:402 "Sau UAT khi link với PE/Contract thật cần check usage trước khi delete" ⇒ usage-check là việc còn treo.
- S1-ter — Invest #4 XONG (25 phát hiện) —
sub-invest-wfver-4.md - S2 — spec XONG 37.490 B —
spec-pe-delete-approver.md(2 phần, ~50 mục checklist) - S3 — reviewer XONG
PASS-WITH-FLAGS 17 FLAG (4H/8M/5L)—reviewer-spec-review.md; F.0 KHÔNG bác được sau 6 đường tấn công; lead đã vá 4H + 8M vào spec
🔴 OWNER CHỐT — LƯỢT 3 (sau review)
| # | Câu | Anh chốt | Vá FLAG |
|---|---|---|---|
| 8 | Quyền xóa nháp vs xóa-khi-duyệt | TÁCH 2 quyền riêng | H1 |
| 9 | Cờ F6 per-người hay per-cấp | PER-NGƯỜI (matchingLevel, 1 người) |
H2 |
| 10 | Cờ F5 AllowApproverFinalize |
PHÁ VỠ (nhất quán ngưỡng CEO) | M1 |
| 11 | CeoApprovalThreshold |
PHÁ VỠ | W3#12 |
| 12 | Step.DepartmentId |
AN TOÀN | W3#13 |
| 13 | Cách chạy hmw | CHIA 2 ĐỢT — F trước, A-E sau | — |
🎁 Lời giải gọn cho (8): đường xóa nháp GIỮ NGUYÊN không policy (Drafter không mất gì) · đường xóa-khi-duyệt = endpoint RIÊNG mang policy root có sẵn PurchaseEvaluations.Delete ⇒ 0 key mới ⇒ Menu keys=54 / Policies=216 không đổi (khớp M6).
ĐỢT 1 (hmw lượt này) — hạng mục F + schema F6
Phạm vi: checklist mục 1-2 (schema F6 + migration) → F-1…F-4 + F-1a/b/c (lệnh Update + validator 2 phép thử + authz) → F-5/F-5a (hạng NỬA) → F-6 (message 403) → F-10 (vá comment sai) → F-11 (5 dây tick F6) → test F-12…F-16. KHÔNG làm đợt này: nút xóa FE · menu restructure · màn "Đã xóa" (mục 3-28) — đợt 2. Lý do phân đợt: F.0 chứng minh A-E không chạy nếu thiếu F ⇒ đợt 1 xong là UAT được ngay, biết chắc nền đúng trước khi xây tiếp; blast radius ≥6 module nên tách để truy lỗi dễ.
- S4-đợt1 — hmw fan-out Opus 5 MAX