[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
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:
@ -169,6 +169,7 @@ export function PurchaseEvaluationsListPage() {
|
|||||||
type ProjectGroup = {
|
type ProjectGroup = {
|
||||||
projectId: string | null
|
projectId: string | null
|
||||||
projectName: string
|
projectName: string
|
||||||
|
projectCode: string
|
||||||
workItems: WorkItemGroup[]
|
workItems: WorkItemGroup[]
|
||||||
totalCount: number
|
totalCount: number
|
||||||
}
|
}
|
||||||
@ -192,7 +193,7 @@ export function PurchaseEvaluationsListPage() {
|
|||||||
const projName = p.projectName?.trim() || '(Dự án đã xoá)'
|
const projName = p.projectName?.trim() || '(Dự án đã xoá)'
|
||||||
let pg = yg.projects.find(g => (g.projectId ?? '__no_project__') === projKey)
|
let pg = yg.projects.find(g => (g.projectId ?? '__no_project__') === projKey)
|
||||||
if (!pg) {
|
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)
|
yg.projects.push(pg)
|
||||||
}
|
}
|
||||||
const wiKey = p.workItemId ?? '__no_workitem__'
|
const wiKey = p.workItemId ?? '__no_workitem__'
|
||||||
@ -209,7 +210,7 @@ export function PurchaseEvaluationsListPage() {
|
|||||||
const arr = Array.from(yearMap.values())
|
const arr = Array.from(yearMap.values())
|
||||||
arr.sort((a, b) => b.year - a.year)
|
arr.sort((a, b) => b.year - a.year)
|
||||||
for (const yg of arr) {
|
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) {
|
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…")
|
// 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 }))
|
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">
|
<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>
|
<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="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>
|
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[10px] text-slate-600">{pg.totalCount}</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="ml-3 border-l border-slate-200">
|
<div className="ml-3 border-l border-slate-200">
|
||||||
|
|||||||
@ -113,6 +113,7 @@ export type PeListItem = {
|
|||||||
phase: number
|
phase: number
|
||||||
projectId: string
|
projectId: string
|
||||||
projectName: string
|
projectName: string
|
||||||
|
projectCode: string
|
||||||
// S57bis — Hạng mục công việc (nullable cho phiếu cũ). BE list DTO trả workItemName.
|
// S57bis — Hạng mục công việc (nullable cho phiếu cũ). BE list DTO trả workItemName.
|
||||||
workItemId: string | null
|
workItemId: string | null
|
||||||
workItemName: string | null
|
workItemName: string | null
|
||||||
|
|||||||
@ -169,6 +169,7 @@ export function PurchaseEvaluationsListPage() {
|
|||||||
type ProjectGroup = {
|
type ProjectGroup = {
|
||||||
projectId: string | null
|
projectId: string | null
|
||||||
projectName: string
|
projectName: string
|
||||||
|
projectCode: string
|
||||||
workItems: WorkItemGroup[]
|
workItems: WorkItemGroup[]
|
||||||
totalCount: number
|
totalCount: number
|
||||||
}
|
}
|
||||||
@ -192,7 +193,7 @@ export function PurchaseEvaluationsListPage() {
|
|||||||
const projName = p.projectName?.trim() || '(Dự án đã xoá)'
|
const projName = p.projectName?.trim() || '(Dự án đã xoá)'
|
||||||
let pg = yg.projects.find(g => (g.projectId ?? '__no_project__') === projKey)
|
let pg = yg.projects.find(g => (g.projectId ?? '__no_project__') === projKey)
|
||||||
if (!pg) {
|
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)
|
yg.projects.push(pg)
|
||||||
}
|
}
|
||||||
const wiKey = p.workItemId ?? '__no_workitem__'
|
const wiKey = p.workItemId ?? '__no_workitem__'
|
||||||
@ -209,7 +210,7 @@ export function PurchaseEvaluationsListPage() {
|
|||||||
const arr = Array.from(yearMap.values())
|
const arr = Array.from(yearMap.values())
|
||||||
arr.sort((a, b) => b.year - a.year)
|
arr.sort((a, b) => b.year - a.year)
|
||||||
for (const yg of arr) {
|
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) {
|
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…")
|
// 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 }))
|
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">
|
<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>
|
<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="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>
|
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-[10px] text-slate-600">{pg.totalCount}</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div className="ml-3 border-l border-slate-200">
|
<div className="ml-3 border-l border-slate-200">
|
||||||
|
|||||||
@ -117,6 +117,7 @@ export type PeListItem = {
|
|||||||
phase: number
|
phase: number
|
||||||
projectId: string
|
projectId: string
|
||||||
projectName: string
|
projectName: string
|
||||||
|
projectCode: string
|
||||||
// S57bis — Hạng mục công việc (nullable cho phiếu cũ). BE list DTO trả workItemName.
|
// S57bis — Hạng mục công việc (nullable cho phiếu cũ). BE list DTO trả workItemName.
|
||||||
workItemId: string | null
|
workItemId: string | null
|
||||||
workItemName: string | null
|
workItemName: string | null
|
||||||
|
|||||||
@ -177,7 +177,7 @@ public class ListApprovedPurchaseEvaluationsQueryHandler(IApplicationDbContext d
|
|||||||
orderby e.CreatedAt descending
|
orderby e.CreatedAt descending
|
||||||
select new PurchaseEvaluationListItemDto(
|
select new PurchaseEvaluationListItemDto(
|
||||||
e.Id, e.MaPhieu, e.TenGoiThau, e.Type, e.Phase,
|
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.WorkItemId, wi != null ? wi.Name : null, wi != null ? wi.Code : null,
|
||||||
e.SelectedSupplierId, s != null ? s.Name : null,
|
e.SelectedSupplierId, s != null ? s.Name : null,
|
||||||
e.ContractId, e.SlaDeadline, e.CreatedAt, e.UpdatedAt,
|
e.ContractId, e.SlaDeadline, e.CreatedAt, e.UpdatedAt,
|
||||||
|
|||||||
@ -11,6 +11,8 @@ public record PurchaseEvaluationListItemDto(
|
|||||||
PurchaseEvaluationPhase Phase,
|
PurchaseEvaluationPhase Phase,
|
||||||
Guid ProjectId,
|
Guid ProjectId,
|
||||||
string ProjectName,
|
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
|
// [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.
|
// (mirror ProjectName). Nullable cho 4 phiếu cũ chưa gắn hạng mục.
|
||||||
Guid? WorkItemId,
|
Guid? WorkItemId,
|
||||||
|
|||||||
@ -604,7 +604,8 @@ public class ListPurchaseEvaluationsQueryHandler(
|
|||||||
q = q.Where(x =>
|
q = q.Where(x =>
|
||||||
(x.e.MaPhieu != null && x.e.MaPhieu.Contains(s)) ||
|
(x.e.MaPhieu != null && x.e.MaPhieu.Contains(s)) ||
|
||||||
x.e.TenGoiThau.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
|
// 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)
|
.Skip((request.Page - 1) * request.PageSize).Take(request.PageSize)
|
||||||
.Select(x => new PurchaseEvaluationListItemDto(
|
.Select(x => new PurchaseEvaluationListItemDto(
|
||||||
x.e.Id, x.e.MaPhieu, x.e.TenGoiThau, x.e.Type, x.e.Phase,
|
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.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.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,
|
||||||
@ -737,7 +738,7 @@ public class GetMyPurchaseEvaluationInboxQueryHandler(
|
|||||||
.OrderByDescending(x => x.e.UpdatedAt ?? x.e.CreatedAt)
|
.OrderByDescending(x => x.e.UpdatedAt ?? x.e.CreatedAt)
|
||||||
.Select(x => new PurchaseEvaluationListItemDto(
|
.Select(x => new PurchaseEvaluationListItemDto(
|
||||||
x.e.Id, x.e.MaPhieu, x.e.TenGoiThau, x.e.Type, x.e.Phase,
|
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.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.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,
|
||||||
|
|||||||
Reference in New Issue
Block a user