[CLAUDE] Workflow: sua quy trinh duyet TAI CHO + co F6 AllowApproverDelete (dot 1)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m31s

Nen cho tinh nang "xoa phieu PE o man duyet" (UAT: phieu bam sai goi thau dang
an luy ke). Dot 1 lam NEN, dot 2 moi lam nut xoa.

Vi sao phai lam nen truoc: phieu dang chay pin ApprovalWorkflowId va doc workflow
SONG theo Id do. Moi POST tao workflow moi = Id MOI, nen tick co tren ban moi
KHONG BAO GIO toi duoc phieu dang treo. Phai co lenh sua-tai-cho giu nguyen Id.

- Domain: ApprovalWorkflowLevel + AllowApproverDelete (F6, per-NGUOI) + Mig
  20260727033522_AddPeAllowApproverDelete (AddColumn bit default false, no new table)
- Application: UpdateAwDefinitionCommand + Validator + Handler
  - 2 phep thu: (T1) khung khong doi - Order tung row bat bien
                (T2) luat choi khong doi - cam CeoApprovalThreshold, AllowApproverFinalize,
                     Code, ApplicableType => 409 bao tao ban moi
  - diff theo Level.Id (CAM delete-then-insert: no FK Restrict + mo coi LevelOpinion)
  - guard 2 tang chu ky: tang 1 chu ky tren ho so CON SONG; tang 2 chu ky CON LUU
    (cha xoa mem hoac opinion xoa mem) - ca hai deu CHAN, khong xoa cung
  - purge chi khi ho so cha KHONG CON ROW; boc transaction (ExecuteDelete tu commit)
  - KHONG tang Version, KHONG dung IsActive, KHONG doi Id
- Api: PUT /api/approval-workflows-v2/{id} + [Authorize(Policy="Workflows.Create")]
- Infra: message 403 in TEN NGUOI thay GUID tran
- FE: Designer tach "Sua tai cho" (PUT) vs "Tao phien ban moi" (POST) + o tick F6
- Tests: +21 (551 tong, 0 fail) - gom F12 chung minh phieu dang chay nhan thay doi ngay

Review: 14 FLAG, 3 HIGH da va (ExecuteDelete ngoai transaction / purge xoa cung
chu ky phieu xoa mem / endpoint khong co day FE goi toi).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-27 12:36:59 +07:00
parent d745fec445
commit b1bae77c9c
13 changed files with 8604 additions and 41 deletions

View File

@ -78,8 +78,20 @@ public class ApprovalWorkflowStep : BaseEntity
public List<ApprovalWorkflowLevel> Levels { get; set; } = new();
}
// Cấp = 1 NV cụ thể. 1 bước có nhiều cấp theo Order. Approver = ApproverUserId
// chính xác (KHÔNG OR-of-many). Sequential trong cùng bước: cấp 1 → cấp 2 → ...
// Cấp = tập các row CÙNG Order trong 1 Bước. MỖI ROW = 1 NV cụ thể
// (ApproverUserId), nhưng 1 Cấp có thể có N row ⇒ N người duyệt **OR-of-N**:
// bất kỳ ai trong Cấp duyệt là Cấp đó xong. Sequential giữa các Cấp trong cùng
// bước: cấp 1 → cấp 2 → ...
//
// [F-10 S155 2026-07-27] Comment cũ ghi "Cấp = 1 NV cụ thể ... (KHÔNG OR-of-many)"
// — SAI, ngược với mã lẫn tài liệu. Bằng chứng: service group Levels theo Order
// rồi cho phép BẤT KỲ ai trong nhóm duyệt
// (`PurchaseEvaluationWorkflowService.cs:689` `GroupBy(l => l.Order)` + `:702`
// `allowedUserIds.Contains(actorUserId)`); validator chỉ cấm TRÙNG cặp
// {Order, ApproverUserId} chứ không cấm nhiều row cùng Order
// (`ApprovalWorkflowV2AdminFeatures.cs:309-314`); CLAUDE.md + skill
// contract-workflow đều ghi "N NV cụ thể (OR-of-N cùng cấp)". Ảnh UAT prod:
// Cấp 2 Phòng Cung ứng có 3 người.
public class ApprovalWorkflowLevel : BaseEntity
{
public Guid ApprovalWorkflowStepId { get; set; }
@ -141,5 +153,20 @@ public class ApprovalWorkflowLevel : BaseEntity
/// nhất, KHÔNG threshold/role check). Mirror F2/F3/F4 admin opt-in per-slot pattern.
public bool AllowApproverFinalize { get; set; }
/// F6 (Mig 68 2026-07-27 — S155, UAT Tra Sol/anh Kiệt) — Cho phép NV slot này,
/// khi đang tới lượt duyệt của mình, XÓA MỀM phiếu đang treo ở ChoDuyet. Sinh ra
/// vì phiếu bấm sai gói thầu đang **ăn lũy kế** ngân sách hạng mục sai mà không
/// có cửa nào dừng: "Trả lại" chỉ DỜI số sang dòng khác chứ không triệt tiêu
/// (`PeBudgetAccumulator.cs:104-126`), còn xóa thì allow-list chỉ nhận
/// {DangSoanThao, TuChoi} (`PurchaseEvaluationFeatures.cs:1404-1406`).
/// Default false (admin opt-in per slot). Đọc **PER-ROW** `matchingLevel.
/// AllowApproverDelete` (khuôn F5 `PurchaseEvaluationWorkflowService.cs:859`),
/// KHÔNG đọc per-Cấp `g.Any(...)` — 1 Cấp có N người (OR-of-N), đọc `Any` sẽ
/// phát quyền huỷ chứng-từ cho CẢ Cấp thay vì đúng 1 người admin chỉ đích danh
/// (owner chốt 2026-07-27). Mirror F2/F3/F4/F5 admin opt-in per-slot pattern.
/// ⚠️ Cờ này chỉ là 1 trong 3 rào: policy `PurchaseEvaluations.Delete` ∧ đúng
/// lượt ∧ F6 — xem hạng mục B spec S155 (đợt 2).
public bool AllowApproverDelete { get; set; }
public ApprovalWorkflowStep? Step { get; set; }
}