From 19ce18fa0b85b73cd6d3e4e155470ae9d9997794 Mon Sep 17 00:00:00 2001 From: jingchuan Date: Fri, 31 Jul 2026 15:44:02 +0800 Subject: [PATCH] fix(oa-board): align stage overtime followups --- .../app/api/intake/oa-board/export/route.ts | 5 ++- .../src/app/intake/oa-board/OaBoardClient.tsx | 11 +++-- .../src/server/services/oa-board.service.ts | 44 ++++++++++++++++--- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/furniture-3d-system/src/app/api/intake/oa-board/export/route.ts b/furniture-3d-system/src/app/api/intake/oa-board/export/route.ts index 4f5b1997..88991db1 100644 --- a/furniture-3d-system/src/app/api/intake/oa-board/export/route.ts +++ b/furniture-3d-system/src/app/api/intake/oa-board/export/route.ts @@ -3,6 +3,7 @@ import ExcelJS from 'exceljs' import { handleRequest, jsonError } from '@/server/core/response' import { ErrorCode } from '@/server/core/errors' import { + hasOaBoardProjectOvertime, oaBoardService, type OaBoardProjectListFilter, } from '@/server/services/oa-board.service' @@ -382,7 +383,7 @@ export async function GET(request: NextRequest) { overallTargetHours: item.overallTargetHours ?? '', overallActualHours: item.overallActualHours ?? '', overallTimeoutHours: item.overallTimeoutHours ?? '', - overallTimeoutFlag: item.overallTimeoutFlag ? '是' : '否', + overallTimeoutFlag: hasOaBoardProjectOvertime(item) ? '是' : '否', firstImageUrl: item.styleImages[0]?.url || '', workflowLink: item.formSummary.workflowLink || '', }) @@ -494,7 +495,7 @@ export async function GET(request: NextRequest) { creatorLabel: item.originatorUserName || '', overallTargetHours: item.overallTargetHours ?? '', overallActualHours: item.overallActualHours ?? '', - isOvertime: item.overallTimeoutFlag ? '是' : '否', + isOvertime: hasOaBoardProjectOvertime(item) ? '是' : '否', ...timeoutFollowupValues, ...Object.fromEntries( TIMING_DETAIL_DEFINITIONS.map((field) => [ diff --git a/furniture-3d-system/src/app/intake/oa-board/OaBoardClient.tsx b/furniture-3d-system/src/app/intake/oa-board/OaBoardClient.tsx index 3a4e45f0..d97bd6d8 100644 --- a/furniture-3d-system/src/app/intake/oa-board/OaBoardClient.tsx +++ b/furniture-3d-system/src/app/intake/oa-board/OaBoardClient.tsx @@ -615,11 +615,13 @@ function filterOaBoardItems(items: OaBoardProjectListItemDTO[], filters: FilterS return false } - if (filters.timeoutStatus === 'YES' && !item.overallTimeoutFlag) { + const isOvertime = item.overallTimeoutFlag || getOvertimeStages(item).length > 0 + + if (filters.timeoutStatus === 'YES' && !isOvertime) { return false } - if (filters.timeoutStatus === 'NO' && item.overallTimeoutFlag) { + if (filters.timeoutStatus === 'NO' && isOvertime) { return false } @@ -1814,6 +1816,7 @@ function DetailTableRow({ }) { const timingFields = buildTimingDetailFields(item) const overtimeStages = getOvertimeStages(item) + const isOvertime = item.overallTimeoutFlag || overtimeStages.length > 0 const timeoutFollowupSummary = getTimeoutFollowupSummary(overtimeStages, timeoutFollowups) const competitorLink = formatCompetitorLinkSummary(item.formSummary.competitorLink) const primaryStyleImage = item.styleImages[0] @@ -1989,8 +1992,8 @@ function DetailTableRow({ {formatDayValue(item.overallActualHours)} diff --git a/furniture-3d-system/src/server/services/oa-board.service.ts b/furniture-3d-system/src/server/services/oa-board.service.ts index a8731906..821800cb 100644 --- a/furniture-3d-system/src/server/services/oa-board.service.ts +++ b/furniture-3d-system/src/server/services/oa-board.service.ts @@ -10,6 +10,7 @@ import { type OaBoardFlowType, type OaBoardStageGraphLane, } from './oa-board.constants' +import { pickCurrentOverdueStageMetric } from '@/lib/oa-board-overtime' import { queryOaBoardPreview, queryOaBoardSnapshotLatestSyncedAt, @@ -713,7 +714,7 @@ export function createMockOaBoardDashboard(): OaBoardDashboardDTO { items, summary: { totalProjects: items.length, - timeoutProjects: items.filter((item) => item.overallTimeoutFlag).length, + timeoutProjects: items.filter(hasOaBoardProjectOvertime).length, runningProjects: items.filter((item) => item.overallStatus === 'RUNNING').length, completedProjects: items.filter((item) => item.overallStatus === 'COMPLETED').length, productTypes, @@ -1071,6 +1072,37 @@ function resolveCurrentApproverDisplay(item: OaBoardProjectListItemDTO) { return Array.from(new Set(owners)).join(';') } +export function hasOaBoardProjectOvertime(item: OaBoardProjectListItemDTO) { + if (item.overallTimeoutFlag) return true + + const stageNames = splitApprovalStages( + item.formSummary.approvalStageDisplay || item.currentApprovalStageName, + ) + const currentNodeNames = splitApprovalStages(item.formSummary.currentNodeDisplay) + const hasCurrentNodeOvertime = item.nodeAssessments.some((assessment) => { + if (assessment.status !== 'RUNNING' || !assessment.isOvertime) return false + if ( + assessment.targetHours === null || + assessment.actualHours === null || + assessment.overtimeHours === null || + assessment.overtimeHours <= 0 + ) { + return false + } + if (currentNodeNames.length > 0 && !currentNodeNames.includes(assessment.nodeName)) return false + if ( + stageNames.length > 0 && + assessment.approvalStageName && + !stageNames.includes(assessment.approvalStageName) + ) { + return false + } + return true + }) + + return hasCurrentNodeOvertime || Boolean(pickCurrentOverdueStageMetric(stageNames, item.timelineMetrics)) +} + export function filterOaBoardProjectItems( allItems: OaBoardProjectListItemDTO[], filter: OaBoardProjectListFilter = {}, @@ -1199,15 +1231,17 @@ export function filterOaBoardProjectItems( return false } - if (filter.timeoutOnly && !item.overallTimeoutFlag) { + const isOvertime = hasOaBoardProjectOvertime(item) + + if (filter.timeoutOnly && !isOvertime) { return false } - if (filter.timeoutStatus === 'YES' && !item.overallTimeoutFlag) { + if (filter.timeoutStatus === 'YES' && !isOvertime) { return false } - if (filter.timeoutStatus === 'NO' && item.overallTimeoutFlag) { + if (filter.timeoutStatus === 'NO' && isOvertime) { return false } @@ -1278,7 +1312,7 @@ export class OaBoardService { items, summary: { totalProjects: items.length, - timeoutProjects: items.filter((item) => item.overallTimeoutFlag).length, + timeoutProjects: items.filter(hasOaBoardProjectOvertime).length, runningProjects: items.filter((item) => item.overallStatus === 'RUNNING').length, completedProjects: items.filter((item) => item.overallStatus === 'COMPLETED').length, productTypes,