[CLAUDE] PurchaseEvaluation: cây Duyệt NCC node Dự án hiện mã dự án thay tên dài (anh Kiệt FDC)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m23s

Anh Kiệt FDC (Zalo + screenshot): node 📁 Dự án trong cây "Năm > Dự án >
Hạng mục > Phiếu" hiện TÊN dài ("Phân xưởng homecare 75 tấn/ngày và các
hạng mục") → user thấy vướng → "bắt mã dự án nhé".

BE: thread ProjectCode vào PurchaseEvaluationListItemDto + 3 projection
(list + inbox + approved-from-evaluation) + search khớp thêm mã dự án.
FE 2 app (SHA-mirror): PeListItem +projectCode; tree node 📁 render Code
(fallback Name nếu rỗng) + tooltip=tên đầy đủ + sort theo mã.

Verify: dotnet build 0/0 - npm build x2 PASS - test 434 (45D+389I).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-29 16:00:03 +07:00
parent c22b06e14a
commit 8ee8f3e248
7 changed files with 17 additions and 10 deletions

View File

@ -169,6 +169,7 @@ export function PurchaseEvaluationsListPage() {
type ProjectGroup = {
projectId: string | null
projectName: string
projectCode: string
workItems: WorkItemGroup[]
totalCount: number
}
@ -192,7 +193,7 @@ export function PurchaseEvaluationsListPage() {
const projName = p.projectName?.trim() || '(Dự án đã xoá)'
let pg = yg.projects.find(g => (g.projectId ?? '__no_project__') === projKey)
if (!pg) {
pg = { projectId: p.projectId ?? null, projectName: projName, workItems: [], totalCount: 0 }
pg = { projectId: p.projectId ?? null, projectName: projName, projectCode: p.projectCode || '', workItems: [], totalCount: 0 }
yg.projects.push(pg)
}
const wiKey = p.workItemId ?? '__no_workitem__'
@ -209,7 +210,7 @@ export function PurchaseEvaluationsListPage() {
const arr = Array.from(yearMap.values())
arr.sort((a, b) => b.year - a.year)
for (const yg of arr) {
yg.projects.sort((a, b) => a.projectName.localeCompare(b.projectName, 'vi'))
yg.projects.sort((a, b) => (a.projectCode || a.projectName).localeCompare(b.projectCode || b.projectName, 'vi'))
for (const pg of yg.projects) {
// S59 — numeric:true vì tên PMH bắt đầu bằng STT không pad ("2 Mat…" < "10 Mat…")
pg.workItems.sort((a, b) => a.workItemName.localeCompare(b.workItemName, 'vi', { numeric: true }))
@ -449,7 +450,7 @@ export function PurchaseEvaluationsListPage() {
<summary className="flex cursor-pointer items-center gap-1.5 px-3 py-1.5 hover:bg-slate-50 [&::-webkit-details-marker]:hidden">
<svg className="h-3 w-3 shrink-0 text-slate-400 transition-transform group-open/proj:rotate-90" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /></svg>
<span className="text-sm">📁</span>
<span className="flex-1 truncate text-[12px] font-medium text-slate-700">{pg.projectName}</span>
<span className="flex-1 truncate text-[12px] font-medium text-slate-700" title={pg.projectName}>{pg.projectCode || pg.projectName}</span>
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[10px] text-slate-600">{pg.totalCount}</span>
</summary>
<div className="ml-3 border-l border-slate-200">

View File

@ -113,6 +113,7 @@ export type PeListItem = {
phase: number
projectId: string
projectName: string
projectCode: string
// S57bis — Hạng mục công việc (nullable cho phiếu cũ). BE list DTO trả workItemName.
workItemId: string | null
workItemName: string | null

View File

@ -169,6 +169,7 @@ export function PurchaseEvaluationsListPage() {
type ProjectGroup = {
projectId: string | null
projectName: string
projectCode: string
workItems: WorkItemGroup[]
totalCount: number
}
@ -192,7 +193,7 @@ export function PurchaseEvaluationsListPage() {
const projName = p.projectName?.trim() || '(Dự án đã xoá)'
let pg = yg.projects.find(g => (g.projectId ?? '__no_project__') === projKey)
if (!pg) {
pg = { projectId: p.projectId ?? null, projectName: projName, workItems: [], totalCount: 0 }
pg = { projectId: p.projectId ?? null, projectName: projName, projectCode: p.projectCode || '', workItems: [], totalCount: 0 }
yg.projects.push(pg)
}
const wiKey = p.workItemId ?? '__no_workitem__'
@ -209,7 +210,7 @@ export function PurchaseEvaluationsListPage() {
const arr = Array.from(yearMap.values())
arr.sort((a, b) => b.year - a.year)
for (const yg of arr) {
yg.projects.sort((a, b) => a.projectName.localeCompare(b.projectName, 'vi'))
yg.projects.sort((a, b) => (a.projectCode || a.projectName).localeCompare(b.projectCode || b.projectName, 'vi'))
for (const pg of yg.projects) {
// S59 — numeric:true vì tên PMH bắt đầu bằng STT không pad ("2 Mat…" < "10 Mat…")
pg.workItems.sort((a, b) => a.workItemName.localeCompare(b.workItemName, 'vi', { numeric: true }))
@ -449,7 +450,7 @@ export function PurchaseEvaluationsListPage() {
<summary className="flex cursor-pointer items-center gap-1.5 px-3 py-1.5 hover:bg-slate-50 [&::-webkit-details-marker]:hidden">
<svg className="h-3 w-3 shrink-0 text-slate-400 transition-transform group-open/proj:rotate-90" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /></svg>
<span className="text-sm">📁</span>
<span className="flex-1 truncate text-[12px] font-medium text-slate-700">{pg.projectName}</span>
<span className="flex-1 truncate text-[12px] font-medium text-slate-700" title={pg.projectName}>{pg.projectCode || pg.projectName}</span>
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[10px] text-slate-600">{pg.totalCount}</span>
</summary>
<div className="ml-3 border-l border-slate-200">

View File

@ -117,6 +117,7 @@ export type PeListItem = {
phase: number
projectId: string
projectName: string
projectCode: string
// S57bis — Hạng mục công việc (nullable cho phiếu cũ). BE list DTO trả workItemName.
workItemId: string | null
workItemName: string | null

View File

@ -177,7 +177,7 @@ public class ListApprovedPurchaseEvaluationsQueryHandler(IApplicationDbContext d
orderby e.CreatedAt descending
select new PurchaseEvaluationListItemDto(
e.Id, e.MaPhieu, e.TenGoiThau, e.Type, e.Phase,
e.ProjectId, p.Name,
e.ProjectId, p.Name, p.Code,
e.WorkItemId, wi != null ? wi.Name : null, wi != null ? wi.Code : null,
e.SelectedSupplierId, s != null ? s.Name : null,
e.ContractId, e.SlaDeadline, e.CreatedAt, e.UpdatedAt,

View File

@ -11,6 +11,8 @@ public record PurchaseEvaluationListItemDto(
PurchaseEvaluationPhase Phase,
Guid ProjectId,
string ProjectName,
// [S93 anh Kiệt FDC] Mã dự án — tree node 📁 Dự án hiện CODE (ngắn) thay tên dài.
string ProjectCode,
// [Mig 49 S57bis] Hạng mục công việc — loose-Guid resolve LEFT join WorkItems
// (mirror ProjectName). Nullable cho 4 phiếu cũ chưa gắn hạng mục.
Guid? WorkItemId,

View File

@ -604,7 +604,8 @@ public class ListPurchaseEvaluationsQueryHandler(
q = q.Where(x =>
(x.e.MaPhieu != null && x.e.MaPhieu.Contains(s)) ||
x.e.TenGoiThau.Contains(s) ||
x.p.Name.Contains(s));
x.p.Name.Contains(s) ||
x.p.Code.Contains(s));
}
// S23 t2 UAT: sort theo "phiếu vừa update" — UpdatedAt fallback CreatedAt
@ -620,7 +621,7 @@ public class ListPurchaseEvaluationsQueryHandler(
.Skip((request.Page - 1) * request.PageSize).Take(request.PageSize)
.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.ProjectId, x.p.Name, x.p.Code,
x.e.WorkItemId, x.wi != null ? x.wi.Name : null, x.wi != null ? x.wi.Code : null,
x.e.SelectedSupplierId, x.s != null ? x.s.Name : null,
x.e.ContractId, x.e.SlaDeadline, x.e.CreatedAt, x.e.UpdatedAt,
@ -737,7 +738,7 @@ public class GetMyPurchaseEvaluationInboxQueryHandler(
.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.ProjectId, x.p.Name, x.p.Code,
x.e.WorkItemId, x.wi != null ? x.wi.Name : null, x.wi != null ? x.wi.Code : null,
x.e.SelectedSupplierId, x.s != null ? x.s.Name : null,
x.e.ContractId, x.e.SlaDeadline, x.e.CreatedAt, x.e.UpdatedAt,