[CLAUDE] PurchaseEvaluation: nut XOA phieu o man duyet + menu 3 muc + man "Da xoa" (dot 2)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m47s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m47s
Dong ca UAT goc (Tra Sol): "lo bam sai cai goi thau, quay lai khong duoc, phai
xoa thi no moi ko co luy ke len". Dot 1 (b1bae77) da dung NEN (co F6 + lenh sua
quy trinh tai cho); dot 2 la phan nguoi dung cham vao.
- App: PeSoftDeleteFeatures.cs MOI
- DELETE /api/purchase-evaluations/{id}/by-approver — 3 rao: phase ChoDuyet
^ dung luot actor ^ matchingLevel.AllowApproverDelete (PER-NGUOI, khong g.Any)
- GET /api/purchase-evaluations/deleted — IgnoreQueryFilters() KEM .Where(IsDeleted)
(IgnoreQueryFilters GO filter chu khong DAO — thieu Where la liet ke ca phieu song)
+ tai lap khoi IDOR cua list hien hanh
- ghi vet ChangelogAction.Delete + ly do
- Api: 2 endpoint moi. 🔴 by-approver CO Y KHONG co [Authorize(Policy)] — do tren DB:
moi vai dang lam nguoi duyet (Procurement/CostControl...) deu CanDelete=0 va khong
seeder nao nang co do => gan policy la 403 voi dung nguoi can dung, ma FE gate cung
quyen nen khong thay nut khong thay loi (gotcha #44). Quyen = 3 rao handler; co F6
la admin tick dich danh tung nguoi => chat hon quyen theo vai.
- Dto: ApprovalWorkflowOptionsDto + AllowApproverDelete. Thieu field nay thi FE gate
luon sai => nut KHONG BAO GIO hien, ma BE lan FE deu build sach (reviewer bat).
- Domain/Infra: 2 menu key Pe_{code}_Approved / _Deleted (KHONG vao MenuKeys.All =>
giu 54/216) + seed tree + labelBackfill "Duyet" -> "Dang duyet" + 2 danh sach permission
- FE x2 app: nut Xoa trong PeWorkflowPanel (md5 mirror khop) + dialog ly do + Layout
regex/route cho 2 muc moi + man "Da xoa" chi-xem
- Tests: +11 (562 tong, 0 fail). T27 khoa CA LOP loi entity-co-co-ma-DTO-khong-co
(fault-inject xac nhan: go field ra => T27 do ngay)
Review: 12 FLAG, chot chan H1 (DTO thieu field) da va. Deploy KHONG co migration
nhung CO seed menu => phai restart API.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@ -143,6 +143,11 @@ public class PurchaseEvaluationsController(IMediator mediator) : ControllerBase
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// 🔴 [S155 đợt 2 — 7a] ĐƯỜNG XÓA NHÁP: GIỮ NGUYÊN, KHÔNG gắn policy.
|
||||
// Gắn `[Authorize(Policy="PurchaseEvaluations.Delete")]` vào đây = role `Drafter` MẤT
|
||||
// quyền xóa nháp của chính mình (đo thật DB Dev: 11/13 role `CanDelete=0`;
|
||||
// `DbInitializer.cs:2515` chỉ cấp DeptManager) ⇒ regression trên chức năng đang sống.
|
||||
// Rào của đường này vẫn là PHASE (`{DangSoanThao, TuChoi}`).
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
@ -150,6 +155,45 @@ public class PurchaseEvaluationsController(IMediator mediator) : ControllerBase
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// [S155 đợt 2 — 7b] ĐƯỜNG XÓA KHI ĐANG DUYỆT (mới). Endpoint RIÊNG để giữ đường xóa
|
||||
// nháp ở trên nguyên vẹn.
|
||||
//
|
||||
// 🔴 CỐ Ý KHÔNG có `[Authorize(Policy=...)]` — owner chốt 2026-07-27 sau khi reviewer
|
||||
// ĐO trên DB: mọi vai đang thật sự làm người duyệt (`Procurement`, `CostControl`, …)
|
||||
// đều `CanDelete = 0`, và 2 seeder KHÔNG BAO GIỜ nâng cờ đó (`DbInitializer.cs:2139-2140`
|
||||
// chỉ nâng Read/Create; `:2538` bỏ qua hàng đã tồn tại) ⇒ gắn policy vào là **403 với
|
||||
// đúng người cần dùng**, mà FE gate cùng quyền nên **không thấy nút, không thấy lỗi**
|
||||
// (gotcha #44 — 403 im lặng).
|
||||
//
|
||||
// Nguồn quyền THẬT của đường này = **3 rào nghiệp vụ trong handler** (`PeSoftDeleteFeatures.cs`):
|
||||
// (i) phase == ChoDuyet · (ii) ĐÚNG LƯỢT actor · (iii) `matchingLevel.AllowApproverDelete`
|
||||
// Rào (iii) là cờ F6 **per-NGƯỜI** — admin tick đích danh từng NV trong Designer quy trình.
|
||||
// Đó CHẶT HƠN permission theo vai (vai gộp nhiều người; cờ chỉ đúng một người, đúng một Cấp).
|
||||
// Đây KHÔNG phải "bỏ authz": handler có `ICurrentUser` và ném Forbidden — đúng khuôn cả
|
||||
// controller này vẫn dùng ("class [Authorize] any-auth; handler fine-grained Forbidden",
|
||||
// xem chú thích `:69-71`, `:88-90`, `:120`).
|
||||
// ⚠️ Hệ quả phải nhớ: FE **KHÔNG** được gate nút này bằng `can(..., 'Delete')` nữa —
|
||||
// gate đó sẽ ẩn nút với đúng người có quyền. Đã gỡ ở `PeWorkflowPanel.tsx`.
|
||||
[HttpDelete("{id:guid}/by-approver")]
|
||||
public async Task<IActionResult> DeleteByApprover(Guid id, [FromBody] DeleteByApproverBody? body, CancellationToken ct)
|
||||
{
|
||||
await mediator.Send(new DeletePurchaseEvaluationByApproverCommand(id, body?.Reason), ct);
|
||||
return NoContent();
|
||||
}
|
||||
public record DeleteByApproverBody(string? Reason);
|
||||
|
||||
// [S155 đợt 2 — mục 8] List phiếu ĐÃ XÓA (màn "Đã xóa", chỉ-xem). Handler RIÊNG là chỗ
|
||||
// DUY NHẤT dùng `IgnoreQueryFilters()` trong toàn `src/Backend` (D3).
|
||||
[HttpGet("deleted")]
|
||||
[Authorize(Policy = "PurchaseEvaluations.Read")]
|
||||
public async Task<ActionResult<PagedResult<PurchaseEvaluationListItemDto>>> ListDeleted(
|
||||
[FromQuery] PurchaseEvaluationType? type = null,
|
||||
[FromQuery] int page = 1, [FromQuery] int pageSize = 20,
|
||||
[FromQuery] string? search = null, [FromQuery] bool sortDesc = true,
|
||||
CancellationToken ct = default)
|
||||
=> Ok(await mediator.Send(new ListDeletedPurchaseEvaluationsQuery(type)
|
||||
{ Page = page, PageSize = pageSize, Search = search, SortDesc = sortDesc }, ct));
|
||||
|
||||
// ========== Suppliers (N:M) ==========
|
||||
|
||||
[HttpPost("{id:guid}/suppliers")]
|
||||
|
||||
Reference in New Issue
Block a user