[CLAUDE] App+FE-User+FE-Admin: Plan AG4 — bổ sung Drafter + Department vào PE List card
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m27s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m27s
Anh UAT 2026-05-21: PE card danh sách thiếu người tạo + phòng ban tạo. Bổ sung 4 field qua BE JOIN Users + Departments LEFT (cả 2 nullable theo PE entity). BE — 4 file: - PurchaseEvaluationDtos.cs: +4 fields DrafterUserId/DrafterName/DepartmentId/DepartmentName - PurchaseEvaluationFeatures.cs ListHandler: JOIN Users + Departments LEFT, projection +4 - PurchaseEvaluationFeatures.cs InboxHandler: mirror JOIN + projection +4 - CreateContractFromEvaluationFeatures.cs ListApproved: mirror JOIN + projection +4 FE — 4 file × 2 app mirror: - types/purchaseEvaluation.ts: PeListItem +4 fields - pages/pe/PurchaseEvaluationsListPage.tsx: PE card render thêm dòng "👤 {drafterName} · {departmentName}" giữa Mã phiếu và Supplier. Conditional: chỉ render khi có ít nhất 1 field. Verify: - dotnet build clean 0 err - dotnet test SolutionErp.slnx 111/111 PASS (58 Domain + 53 Infra) — no regression - npm build fe-user PASS 0 TS err 1290.31 KB (gzip 336.79 KB) 1907 modules - npm build fe-admin PASS 0 TS err 1401.66 KB (gzip 357.30 KB) 1926 modules - 2 FE PE List file SHA256 IDENTICAL C6996194... (mirror §3.9) - KHÔNG Mig (chỉ DTO + projection extend) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -129,17 +129,24 @@ public class ListApprovedPurchaseEvaluationsQueryHandler(IApplicationDbContext d
|
||||
public async Task<List<PurchaseEvaluationListItemDto>> Handle(
|
||||
ListApprovedPurchaseEvaluationsQuery request, CancellationToken ct)
|
||||
{
|
||||
// Plan AG4: JOIN Users + Departments LEFT (mirror ListPurchaseEvaluations).
|
||||
return await (
|
||||
from e in db.PurchaseEvaluations.AsNoTracking()
|
||||
join p in db.Projects.AsNoTracking() on e.ProjectId equals p.Id
|
||||
join s in db.Suppliers.AsNoTracking() on e.SelectedSupplierId equals s.Id into sj
|
||||
from s in sj.DefaultIfEmpty()
|
||||
join u in db.Users.AsNoTracking() on e.DrafterUserId equals u.Id into uj
|
||||
from u in uj.DefaultIfEmpty()
|
||||
join d in db.Departments.AsNoTracking() on e.DepartmentId equals d.Id into dj
|
||||
from d in dj.DefaultIfEmpty()
|
||||
where e.Phase == PurchaseEvaluationPhase.DaDuyet && e.ContractId == null
|
||||
orderby e.CreatedAt descending
|
||||
select new PurchaseEvaluationListItemDto(
|
||||
e.Id, e.MaPhieu, e.TenGoiThau, e.Type, e.Phase,
|
||||
e.ProjectId, p.Name,
|
||||
e.SelectedSupplierId, s != null ? s.Name : null,
|
||||
e.ContractId, e.SlaDeadline, e.CreatedAt, e.UpdatedAt)).ToListAsync(ct);
|
||||
e.ContractId, e.SlaDeadline, e.CreatedAt, e.UpdatedAt,
|
||||
e.DrafterUserId, u != null ? u.FullName : null,
|
||||
e.DepartmentId, d != null ? d.Name : null)).ToListAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,14 @@ public record PurchaseEvaluationListItemDto(
|
||||
// S23 t2 UAT: bro UI polish PE list — display "Ngày giờ tạo" thay SLA countdown,
|
||||
// sort theo "phiếu vừa update" (Tạo / Gửi duyệt / Trả lại). UpdatedAt được
|
||||
// AuditingInterceptor auto-set mỗi SaveChanges → covers cả 3 event tự nhiên.
|
||||
DateTime? UpdatedAt);
|
||||
DateTime? UpdatedAt,
|
||||
// Plan AG4 (2026-05-21): bro UAT yêu cầu bổ sung Người tạo + Phòng ban tạo
|
||||
// vào PE card list. JOIN Users + Departments (LEFT join — cả 2 nullable theo
|
||||
// PE entity). FullName resolve từ Users.FullName, Department.Name từ Departments.
|
||||
Guid? DrafterUserId,
|
||||
string? DrafterName,
|
||||
Guid? DepartmentId,
|
||||
string? DepartmentName);
|
||||
|
||||
public record PurchaseEvaluationSupplierDto(
|
||||
Guid Id,
|
||||
|
||||
@ -468,11 +468,16 @@ public class ListPurchaseEvaluationsQueryHandler(
|
||||
public async Task<PagedResult<PurchaseEvaluationListItemDto>> Handle(
|
||||
ListPurchaseEvaluationsQuery request, CancellationToken ct)
|
||||
{
|
||||
// Plan AG4: JOIN Users + Departments LEFT (cả 2 nullable theo PE entity).
|
||||
var q = from e in db.PurchaseEvaluations.AsNoTracking()
|
||||
join p in db.Projects.AsNoTracking() on e.ProjectId equals p.Id
|
||||
join s in db.Suppliers.AsNoTracking() on e.SelectedSupplierId equals s.Id into sj
|
||||
from s in sj.DefaultIfEmpty()
|
||||
select new { e, p, s };
|
||||
join u in db.Users.AsNoTracking() on e.DrafterUserId equals u.Id into uj
|
||||
from u in uj.DefaultIfEmpty()
|
||||
join d in db.Departments.AsNoTracking() on e.DepartmentId equals d.Id into dj
|
||||
from d in dj.DefaultIfEmpty()
|
||||
select new { e, p, s, u, d };
|
||||
|
||||
// IDOR strict (Plan E S22 — Session 21 +1): non-admin chỉ thấy phiếu khi:
|
||||
// 1. là Drafter (mình tạo)
|
||||
@ -527,7 +532,9 @@ public class ListPurchaseEvaluationsQueryHandler(
|
||||
x.e.Id, x.e.MaPhieu, x.e.TenGoiThau, x.e.Type, x.e.Phase,
|
||||
x.e.ProjectId, x.p.Name,
|
||||
x.e.SelectedSupplierId, x.s != null ? x.s.Name : null,
|
||||
x.e.ContractId, x.e.SlaDeadline, x.e.CreatedAt, x.e.UpdatedAt))
|
||||
x.e.ContractId, x.e.SlaDeadline, x.e.CreatedAt, x.e.UpdatedAt,
|
||||
x.e.DrafterUserId, x.u != null ? x.u.FullName : null,
|
||||
x.e.DepartmentId, x.d != null ? x.d.Name : null))
|
||||
.ToListAsync(ct);
|
||||
|
||||
return new PagedResult<PurchaseEvaluationListItemDto>(items, total, request.Page, request.PageSize);
|
||||
@ -587,12 +594,17 @@ public class GetMyPurchaseEvaluationInboxQueryHandler(
|
||||
? new HashSet<Guid>()
|
||||
: await ResolveV2InboxIdsAsync(userId, ct);
|
||||
|
||||
// Plan AG4: JOIN Users + Departments LEFT (mirror ListPurchaseEvaluations).
|
||||
var q = from e in db.PurchaseEvaluations.AsNoTracking()
|
||||
join p in db.Projects.AsNoTracking() on e.ProjectId equals p.Id
|
||||
join s in db.Suppliers.AsNoTracking() on e.SelectedSupplierId equals s.Id into sj
|
||||
from s in sj.DefaultIfEmpty()
|
||||
join u in db.Users.AsNoTracking() on e.DrafterUserId equals u.Id into uj
|
||||
from u in uj.DefaultIfEmpty()
|
||||
join d in db.Departments.AsNoTracking() on e.DepartmentId equals d.Id into dj
|
||||
from d in dj.DefaultIfEmpty()
|
||||
where eligiblePhases.Contains(e.Phase) || v2InboxIds.Contains(e.Id)
|
||||
select new { e, p, s };
|
||||
select new { e, p, s, u, d };
|
||||
|
||||
if (request.Type is not null) q = q.Where(x => x.e.Type == request.Type);
|
||||
if (request.ApprovalWorkflowId is not null)
|
||||
@ -605,7 +617,9 @@ public class GetMyPurchaseEvaluationInboxQueryHandler(
|
||||
x.e.Id, x.e.MaPhieu, x.e.TenGoiThau, x.e.Type, x.e.Phase,
|
||||
x.e.ProjectId, x.p.Name,
|
||||
x.e.SelectedSupplierId, x.s != null ? x.s.Name : null,
|
||||
x.e.ContractId, x.e.SlaDeadline, x.e.CreatedAt, x.e.UpdatedAt))
|
||||
x.e.ContractId, x.e.SlaDeadline, x.e.CreatedAt, x.e.UpdatedAt,
|
||||
x.e.DrafterUserId, x.u != null ? x.u.FullName : null,
|
||||
x.e.DepartmentId, x.d != null ? x.d.Name : null))
|
||||
.Take(100)
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user