From 83c9f7b45d7ae1574c1006f852ce94b9910e38f1 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Fri, 15 May 2026 01:53:19 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20PurchaseEvaluation=20FE-Admin=20FE-U?= =?UTF-8?q?ser:=20Chunk=20L5=20=E2=80=94=20PE=20list=20UX:=20ng=C3=A0y=20t?= =?UTF-8?q?=E1=BA=A1o=20thay=20SLA=20countdown=20+=20sort=20UpdatedAt=20DE?= =?UTF-8?q?SC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bro UAT S23 t2 yêu cầu 2 UX changes PE list: 1. Đổi "Còn N ngày Mh" (SlaTimer countdown) → "DD/MM/YYYY HH:mm" (ngày giờ tạo phiếu). 2. Sort: phiếu vừa update (Tạo / Gửi duyệt / Trả lại) đưa lên đầu, phiếu cũ phía dưới. BE changes: - PurchaseEvaluationListItemDto +UpdatedAt: DateTime? field (auto AuditingInterceptor refresh mọi SaveChanges — covers Insert/Update/Transition events natural). - ListPurchaseEvaluationsQueryHandler sort: OrderByDescending(UpdatedAt ?? CreatedAt) (was: OrderByDescending(CreatedAt)). - GetMyPurchaseEvaluationInboxQueryHandler sort: OrderByDescending(UpdatedAt ?? CreatedAt) (was: OrderBy(SlaDeadline ?? MaxValue) — SLA priority deprecated). - CreateContractFromEvaluationFeatures.cs: +UpdatedAt arg trong DTO ctor (compile fix consumer downstream). - Select projection 3 callsites populate UpdatedAt. FE × 2 app (mirror rule §3.9): - PeListItem type +updatedAt: string | null (optional — null khi phiếu chưa Update). - PurchaseEvaluationsListPage: replace với Vietnamese date format "{DD/MM/YYYY HH:mm}" qua Intl.DateTimeFormat (vi-VN locale, full date+time options). title tooltip hiện full timestamp. - Remove SlaTimer import (unused warning). UpdatedAt sort logic insight: AuditingInterceptor (Infrastructure) auto-refresh UpdatedAt mọi SaveChanges → mọi event tự nhiên (Drafter tạo / Gửi duyệt từ Workspace / Approver duyệt Cấp tiếp / Approver trả lại / Admin override) đều bump UpdatedAt → phiếu vừa action lên đầu list. Phiếu mới Insert UpdatedAt=null → fallback CreatedAt → vẫn lên đầu (vì CreatedAt vừa now). Verify: - dotnet build production projects clean (0 err, 2 pre-existing warn) - dotnet test SolutionErp.slnx 104/104 PASS (DTO change KHÔNG impact test — tests don't construct ListItemDto) - npm run build × 2 app pass clean Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/pages/pe/PurchaseEvaluationsListPage.tsx | 11 +++++++++-- fe-admin/src/types/purchaseEvaluation.ts | 4 ++++ .../src/pages/pe/PurchaseEvaluationsListPage.tsx | 11 +++++++++-- fe-user/src/types/purchaseEvaluation.ts | 4 ++++ .../CreateContractFromEvaluationFeatures.cs | 2 +- .../Dtos/PurchaseEvaluationDtos.cs | 6 +++++- .../PurchaseEvaluationFeatures.cs | 15 +++++++++++---- 7 files changed, 43 insertions(+), 10 deletions(-) diff --git a/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx b/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx index d188848..7b941d3 100644 --- a/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx +++ b/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx @@ -7,7 +7,6 @@ import { ClipboardCheck, Search, X } from 'lucide-react' import { Input } from '@/components/ui/Input' import { Select } from '@/components/ui/Select' import { EmptyState } from '@/components/EmptyState' -import { SlaTimer } from '@/components/SlaTimer' import { api } from '@/lib/api' import { getErrorMessage } from '@/lib/apiError' import { cn } from '@/lib/cn' @@ -234,7 +233,15 @@ export function PurchaseEvaluationsListPage() { {PurchaseEvaluationTypeLabel[p.type]} - + {/* S23 t2 UAT: bro yêu cầu đổi SLA countdown → ngày giờ tạo phiếu. + BE list sort theo UpdatedAt DESC (fallback CreatedAt) — phiếu vừa + update (Tạo / Gửi duyệt / Trả lại) đưa lên đầu list. */} + + {new Date(p.createdAt).toLocaleString('vi-VN', { + day: '2-digit', month: '2-digit', year: 'numeric', + hour: '2-digit', minute: '2-digit', + })} + {p.contractId && (
✓ Đã tạo HĐ
diff --git a/fe-admin/src/types/purchaseEvaluation.ts b/fe-admin/src/types/purchaseEvaluation.ts index d54bf6e..ed2ff64 100644 --- a/fe-admin/src/types/purchaseEvaluation.ts +++ b/fe-admin/src/types/purchaseEvaluation.ts @@ -118,6 +118,10 @@ export type PeListItem = { contractId: string | null slaDeadline: string | null createdAt: string + // S23 t2 UAT: BE sort theo UpdatedAt DESC (fallback CreatedAt) — phiếu vừa + // update (Tạo / Gửi duyệt / Trả lại) đưa lên đầu. FE list item hiển thị + // "Tạo lúc createdAt" (KHÔNG còn SLA countdown "Còn N ngày"). + updatedAt: string | null } export type PeSupplier = { diff --git a/fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx b/fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx index d188848..7b941d3 100644 --- a/fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx +++ b/fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx @@ -7,7 +7,6 @@ import { ClipboardCheck, Search, X } from 'lucide-react' import { Input } from '@/components/ui/Input' import { Select } from '@/components/ui/Select' import { EmptyState } from '@/components/EmptyState' -import { SlaTimer } from '@/components/SlaTimer' import { api } from '@/lib/api' import { getErrorMessage } from '@/lib/apiError' import { cn } from '@/lib/cn' @@ -234,7 +233,15 @@ export function PurchaseEvaluationsListPage() { {PurchaseEvaluationTypeLabel[p.type]} - + {/* S23 t2 UAT: bro yêu cầu đổi SLA countdown → ngày giờ tạo phiếu. + BE list sort theo UpdatedAt DESC (fallback CreatedAt) — phiếu vừa + update (Tạo / Gửi duyệt / Trả lại) đưa lên đầu list. */} + + {new Date(p.createdAt).toLocaleString('vi-VN', { + day: '2-digit', month: '2-digit', year: 'numeric', + hour: '2-digit', minute: '2-digit', + })} + {p.contractId && (
✓ Đã tạo HĐ
diff --git a/fe-user/src/types/purchaseEvaluation.ts b/fe-user/src/types/purchaseEvaluation.ts index 8c79abc..af02e0c 100644 --- a/fe-user/src/types/purchaseEvaluation.ts +++ b/fe-user/src/types/purchaseEvaluation.ts @@ -117,6 +117,10 @@ export type PeListItem = { contractId: string | null slaDeadline: string | null createdAt: string + // S23 t2 UAT: BE sort theo UpdatedAt DESC (fallback CreatedAt) — phiếu vừa + // update (Tạo / Gửi duyệt / Trả lại) đưa lên đầu. FE list item hiển thị + // "Tạo lúc createdAt" (KHÔNG còn SLA countdown "Còn N ngày"). + updatedAt: string | null } export type PeSupplier = { diff --git a/src/Backend/SolutionErp.Application/PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs b/src/Backend/SolutionErp.Application/PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs index 9526afc..1983376 100644 --- a/src/Backend/SolutionErp.Application/PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs +++ b/src/Backend/SolutionErp.Application/PurchaseEvaluations/CreateContractFromEvaluationFeatures.cs @@ -140,6 +140,6 @@ public class ListApprovedPurchaseEvaluationsQueryHandler(IApplicationDbContext d 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)).ToListAsync(ct); + e.ContractId, e.SlaDeadline, e.CreatedAt, e.UpdatedAt)).ToListAsync(ct); } } diff --git a/src/Backend/SolutionErp.Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs b/src/Backend/SolutionErp.Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs index 0526cea..4404b42 100644 --- a/src/Backend/SolutionErp.Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs +++ b/src/Backend/SolutionErp.Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs @@ -16,7 +16,11 @@ public record PurchaseEvaluationListItemDto( string? SelectedSupplierName, Guid? ContractId, DateTime? SlaDeadline, - DateTime CreatedAt); + DateTime CreatedAt, + // 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); public record PurchaseEvaluationSupplierDto( Guid Id, diff --git a/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs b/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs index 25990be..024a317 100644 --- a/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs +++ b/src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs @@ -504,7 +504,13 @@ public class ListPurchaseEvaluationsQueryHandler( x.p.Name.Contains(s)); } - q = request.SortDesc ? q.OrderByDescending(x => x.e.CreatedAt) : q.OrderBy(x => x.e.CreatedAt); + // S23 t2 UAT: sort theo "phiếu vừa update" — UpdatedAt fallback CreatedAt + // (phiếu mới Insert UpdatedAt=null → CreatedAt làm proxy). Latest event lên + // đầu (Tạo / Gửi duyệt / Trả lại / Approve advance pointer — mọi SaveChanges + // AuditingInterceptor refresh UpdatedAt). SortDesc=true default (mới đầu). + q = request.SortDesc + ? q.OrderByDescending(x => x.e.UpdatedAt ?? x.e.CreatedAt) + : q.OrderBy(x => x.e.UpdatedAt ?? x.e.CreatedAt); var total = await q.CountAsync(ct); var items = await q @@ -513,7 +519,7 @@ 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.ContractId, x.e.SlaDeadline, x.e.CreatedAt, x.e.UpdatedAt)) .ToListAsync(ct); return new PagedResult(items, total, request.Page, request.PageSize); @@ -584,13 +590,14 @@ public class GetMyPurchaseEvaluationInboxQueryHandler( if (request.ApprovalWorkflowId is not null) q = q.Where(x => x.e.ApprovalWorkflowId == request.ApprovalWorkflowId); + // S23 t2 UAT: Inbox cũng sort theo "vừa update" lên đầu (mirror List sort). return await q - .OrderBy(x => x.e.SlaDeadline ?? DateTime.MaxValue) + .OrderByDescending(x => x.e.UpdatedAt ?? x.e.CreatedAt) .Select(x => new PurchaseEvaluationListItemDto( 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.ContractId, x.e.SlaDeadline, x.e.CreatedAt, x.e.UpdatedAt)) .Take(100) .ToListAsync(ct); }