Files
solution-erp/.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/run.md
2026-07-27 10:30:24 +07:00

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>.md củ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ạithiế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

  1. invest BE: entity/state-machine/endpoint-xóa/authz/lũy-kế/changelog + git-khảo-cổ "chỗ cho huy"
  2. 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.Delete0 key mớiMenu 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