[CLAUDE] Contract+FE-User: dot-5 menu 6-leaf y-chang-Duyet-NCC + GET /contracts/deleted + bo hero + "Tong quan quy trinh"
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m38s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m38s
- BE: ListDeletedContractsQuery (IgnoreQueryFilters + Where(IsDeleted) + IDOR mirror; LUAT thay so-dem theo reviewer FLAG-10) + GET /contracts/deleted [Contracts.Read] - Seeder: Ct x7 +3 leaf (WfView/Approved/Deleted) + labelBackfill "Dang duyet" x7 + Khkk 6 leaf + Dashboard -> "Tong quan quy trinh" + reviewKeys +23 key - FE user: matrix +type=3 (HD chung 1 bo V2) + route /contracts/workflow-matrix + Ct regex +3 action + MyContractsPage (phase/deleted + banner + an nut Tao + FLAG-2 guard selectContract khi deleted) + MenuLeaf depth-0 UPPERCASE + UserDashboardPage BO HERO (owner: "bo cai tren di, cai duoi OK") + title "Tong quan quy trinh" - fe-admin: staticMap +2 Khkk (FLAG-1); leaf Ct_ moi van an theo isAdminHidden (da kiem CLEAN) - reviewer PASS_WITH_FLAGS 10 FLAG (0H/4M/6L): fix 4M, park 6L co ghi (FLAG-4 dinh nghia "Da duyet" HD -> cho owner) - build fe x2 + BE PASS; test 562 PASS Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@ -351,6 +351,64 @@ public class ListContractsQueryHandler(IApplicationDbContext db, ICurrentUser cu
|
||||
}
|
||||
}
|
||||
|
||||
// ========== LIST DELETED contracts — màn "Đã xóa" (chỉ-xem) ==========
|
||||
// [S159-đợt5 — owner "y chang Duyệt NCC"] Cookie-cutter `ListDeletedPurchaseEvaluationsQuery`
|
||||
// (PeSoftDeleteFeatures.cs). 🔴 LUẬT (không đếm số — reviewer FLAG-10: "chỗ THỨ HAI" là
|
||||
// con số tự-lan, ApprovalWorkflowV2AdminFeatures cũng dùng Ignore ở lớp KHÁC): mọi nơi
|
||||
// dùng `IgnoreQueryFilters` để LIỆT KÊ bản ghi đã xóa BẮT BUỘC kèm `.Where(x => x.IsDeleted)`
|
||||
// vì Ignore GỠ filter chứ KHÔNG ĐẢO (bài 8a S155). Tự kiểm:
|
||||
// grep -rn "IgnoreQueryFilters" src/Backend --include=*.cs
|
||||
// IDOR scope MIRROR ListContractsQueryHandler (Drafter ∪ eligible-phase; phase ở đây =
|
||||
// phase tại thời điểm xóa).
|
||||
|
||||
public record ListDeletedContractsQuery(ContractType? Type = null)
|
||||
: PagedRequest, IRequest<PagedResult<ContractListItemDto>>;
|
||||
|
||||
public class ListDeletedContractsQueryHandler(IApplicationDbContext db, ICurrentUser currentUser)
|
||||
: IRequestHandler<ListDeletedContractsQuery, PagedResult<ContractListItemDto>>
|
||||
{
|
||||
public async Task<PagedResult<ContractListItemDto>> Handle(ListDeletedContractsQuery request, CancellationToken ct)
|
||||
{
|
||||
var q = from c in db.Contracts.AsNoTracking().IgnoreQueryFilters()
|
||||
.Where(x => x.IsDeleted)
|
||||
join s in db.Suppliers.AsNoTracking() on c.SupplierId equals s.Id
|
||||
join p in db.Projects.AsNoTracking() on c.ProjectId equals p.Id
|
||||
select new { c, s, p };
|
||||
|
||||
if (!currentUser.Roles.Contains(AppRoles.Admin))
|
||||
{
|
||||
var userId = currentUser.UserId;
|
||||
var eligiblePhases = ListContractsQueryHandler.GetEligiblePhases(currentUser.Roles);
|
||||
q = q.Where(x => x.c.DrafterUserId == userId || eligiblePhases.Contains(x.c.Phase));
|
||||
}
|
||||
|
||||
if (request.Type is not null) q = q.Where(x => x.c.Type == request.Type);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Search))
|
||||
{
|
||||
var kw = request.Search.Trim();
|
||||
q = q.Where(x =>
|
||||
(x.c.MaHopDong != null && x.c.MaHopDong.Contains(kw)) ||
|
||||
(x.c.TenHopDong != null && x.c.TenHopDong.Contains(kw)) ||
|
||||
x.s.Name.Contains(kw) || x.p.Name.Contains(kw));
|
||||
}
|
||||
|
||||
q = request.SortDesc ? q.OrderByDescending(x => x.c.DeletedAt) : q.OrderBy(x => x.c.DeletedAt);
|
||||
|
||||
var total = await q.CountAsync(ct);
|
||||
var items = await q
|
||||
.Skip((request.Page - 1) * request.PageSize).Take(request.PageSize)
|
||||
.Select(x => new ContractListItemDto(
|
||||
x.c.Id, x.c.MaHopDong, x.c.TenHopDong, x.c.Type, x.c.Phase,
|
||||
x.c.SupplierId, x.s.Name,
|
||||
x.c.ProjectId, x.p.Name,
|
||||
x.c.GiaTri, x.c.SlaDeadline, x.c.CreatedAt))
|
||||
.ToListAsync(ct);
|
||||
|
||||
return new PagedResult<ContractListItemDto>(items, total, request.Page, request.PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== INBOX — HĐ chờ role/tôi xử lý ==========
|
||||
|
||||
public record GetMyInboxQuery : IRequest<List<ContractListItemDto>>;
|
||||
|
||||
@ -181,7 +181,14 @@ public class ListDeletedPurchaseEvaluationsQueryHandler(
|
||||
{
|
||||
// 🔴 8a — `IgnoreQueryFilters()` **GỠ** filter chứ KHÔNG ĐẢO nó. Thiếu
|
||||
// `.Where(x => x.IsDeleted)` ⇒ màn "Đã xóa" liệt kê TOÀN BỘ phiếu sống lẫn chết.
|
||||
// 🔴 D3 — đây là chỗ DUY NHẤT trong `src/Backend` dùng `IgnoreQueryFilters`:
|
||||
// 🔴 D3 (S159-đợt5, sửa lần 2 theo reviewer FLAG-10 — BỎ CON SỐ ĐẾM, nêu LUẬT):
|
||||
// MỌI nơi dùng `IgnoreQueryFilters` để LIỆT KÊ bản ghi đã xóa BẮT BUỘC kèm
|
||||
// `.Where(x => x.IsDeleted)`. Lớp list-deleted hiện: đây + `ListDeletedContractsQueryHandler`
|
||||
// (ContractFeatures.cs). `ApprovalWorkflowV2AdminFeatures.cs` cũng dùng Ignore ở nhiều
|
||||
// vị trí nhưng thuộc LỚP KHÁC (đọc *LevelOpinions + dò phiếu-cha-mồ-côi, không list-deleted).
|
||||
// Tự kiểm: grep -rn "IgnoreQueryFilters" src/Backend --include=*.cs
|
||||
// (Bài: "chỗ DUY NHẤT/THỨ HAI" là con số tự-lan — S155 ghi DUY NHẤT đã sai từ khi
|
||||
// ApprovalWorkflowV2AdminFeatures dùng; đừng thay số sai cũ bằng số sai mới.)
|
||||
// CẤM đưa vào helper dùng chung trả `IQueryable`, CẤM sửa
|
||||
// `PurchaseEvaluationConfiguration.cs:84`, CẤM thêm cờ `includeDeleted` vào query list
|
||||
// hiện có (cờ sẽ lan tới `PeBudgetAccumulator` = phá đúng thứ owner muốn).
|
||||
|
||||
Reference in New Issue
Block a user