diff --git a/furniture-3d-system/src/server/services/__tests__/oa-board-sync.service.test.ts b/furniture-3d-system/src/server/services/__tests__/oa-board-sync.service.test.ts index aabb70ad..f4379087 100644 --- a/furniture-3d-system/src/server/services/__tests__/oa-board-sync.service.test.ts +++ b/furniture-3d-system/src/server/services/__tests__/oa-board-sync.service.test.ts @@ -1,5 +1,6 @@ import { calculateOaBoardOverallActualDays, + calculateOaBoardPricingFlowActualDays, getRunningTasks, type OaBoardLifecycleFlowRecord, } from '../oa-board-sync.service' @@ -71,3 +72,39 @@ describe('OA 看板当前任务选择', () => { expect(tasks[0]?.taskId).toBe('new-running-task') }) }) +describe('OA 看板限价时效', () => { + it('限价子流程尚未发起时,从首单和美术并行流程最后完成时间开始计算', () => { + const actualDays = calculateOaBoardPricingFlowActualDays( + [ + { + flowType: 'review', + status: 'COMPLETED', + result: 'AGREE', + fieldSnapshots: [ + { + fieldName: '是否需要视觉出图', + fieldValue: '视觉出图', + fieldExtValue: null, + normalizedValue: '视觉出图', + metadata: null, + }, + ], + }, + { + flowType: 'order', + status: 'COMPLETED', + finishedAtSource: new Date('2026-08-01T05:00:00.000Z'), + }, + { + flowType: 'art', + status: 'COMPLETED', + finishedAtSource: new Date('2026-08-02T05:00:00.000Z'), + }, + ] as never, + '限价-发起', + new Date('2026-08-04T05:00:00.000Z'), + ) + + expect(actualDays).toBe(2) + }) +}) diff --git a/furniture-3d-system/src/server/services/oa-board-sync.service.ts b/furniture-3d-system/src/server/services/oa-board-sync.service.ts index f8281ca6..984526cd 100644 --- a/furniture-3d-system/src/server/services/oa-board-sync.service.ts +++ b/furniture-3d-system/src/server/services/oa-board-sync.service.ts @@ -1984,6 +1984,39 @@ function pickFlowActualDays(records: NormalizedFlowRecord[], flowType: OaBoardFl return Number(Math.max(...values).toFixed(2)) } +export function calculateOaBoardPricingFlowActualDays( + records: NormalizedFlowRecord[], + pendingStageName: string | null, + now = new Date(), +) { + const pricingActual = pickFlowActualDays(records, 'pricing') + if (pricingActual !== null) return pricingActual + + // 限价子流程尚未发起时,待发起阶段已由前序流程完成触发。 + // 该阶段从最后一个必经的并行流程完成时开始计时,直到真正发起限价。 + if (pendingStageName !== '限价-发起') return null + + const reviewRecord = records.find((record) => record.flowType === 'review') ?? null + const orderRecord = records.find((record) => record.flowType === 'order') ?? null + const artRecord = records.find((record) => record.flowType === 'art') ?? null + const orderFinishedAt = + getFlowLifecycleStatus(orderRecord) === 'COMPLETED' ? orderRecord?.finishedAtSource ?? null : null + if (!orderFinishedAt) return null + + const artFinishedAt = isVisualDesignRequired(reviewRecord) + ? getFlowLifecycleStatus(artRecord) === 'COMPLETED' + ? artRecord?.finishedAtSource ?? null + : null + : null + if (isVisualDesignRequired(reviewRecord) && !artFinishedAt) return null + + const startedAt = [orderFinishedAt, artFinishedAt] + .filter((value): value is Date => Boolean(value)) + .sort((left, right) => right.getTime() - left.getTime())[0] + + return startedAt ? calculateHours(startedAt, null, now) : null +} + function buildTimelineMetrics( records: NormalizedFlowRecord[], productType: string | null, @@ -2066,7 +2099,7 @@ function buildTimelineMetrics( pricingParallelActual, ]) const artActual = pickFlowActualDays(records, 'art') - const pricingFlowActual = pickFlowActualDays(records, 'pricing') + const pricingFlowActual = calculateOaBoardPricingFlowActualDays(records, pendingStageName) const listingActual = pickFlowActualDays(records, 'main') const definitions: TimelineMetricDefinition[] = [ {