fix(oa-board): align stage overtime followups

This commit is contained in:
jingchuan 2026-07-31 15:44:02 +08:00
parent adc97204ff
commit 19ce18fa0b
3 changed files with 49 additions and 11 deletions

View File

@ -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) => [

View File

@ -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({
<td className="px-3 py-3 align-top text-[11px]">{formatDayValue(item.overallActualHours)}</td>
<td className="px-3 py-3 align-top text-[11px]">
<StatusBadge
label={item.overallTimeoutFlag ? '是' : '否'}
tone={item.overallTimeoutFlag ? 'danger' : 'success'}
label={isOvertime ? '是' : '否'}
tone={isOvertime ? 'danger' : 'success'}
/>
</td>
<td className="px-3 py-3 align-top text-[11px]">

View File

@ -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,