Merge OA latest child flow selection

This commit is contained in:
jingchuan 2026-08-17 11:39:18 +08:00
commit dded0237d0
3 changed files with 144 additions and 1 deletions

View File

@ -0,0 +1,92 @@
import { selectLatestRestartableChildFlows } from './oa-board-flow-selection'
type TestFlowRecord = {
flowType: string
processInstanceId: string
createdAtSource: Date | null
updatedAtSource: Date | null
status: string
}
function buildFlow(
flowType: string,
processInstanceId: string,
createdAtSource: string | null,
status: string,
updatedAtSource = createdAtSource,
): TestFlowRecord {
return {
flowType,
processInstanceId,
createdAtSource: createdAtSource ? new Date(createdAtSource) : null,
updatedAtSource: updatedAtSource ? new Date(updatedAtSource) : null,
status,
}
}
describe('OA 看板子流程版本选择', () => {
it('首单、美术和限价流程各自只保留最晚发起的一条', () => {
const records = [
buildFlow('review', 'review-1', '2026-08-03T02:30:00Z', 'COMPLETED'),
buildFlow('order', 'order-refused', '2026-08-03T08:00:00Z', 'REFUSED'),
buildFlow('order', 'order-running', '2026-08-04T08:00:00Z', 'RUNNING'),
buildFlow('art', 'art-refused', '2026-08-05T08:00:00Z', 'REFUSED'),
buildFlow('art', 'art-completed', '2026-08-06T08:00:00Z', 'COMPLETED'),
buildFlow('pricing', 'pricing-terminated', '2026-08-17T01:49:00Z', 'TERMINATED'),
buildFlow('pricing', 'pricing-completed', '2026-08-17T01:51:00Z', 'COMPLETED'),
buildFlow('main', 'main-1', '2026-08-17T02:00:00Z', 'RUNNING'),
]
expect(
selectLatestRestartableChildFlows(records).map((record) => record.processInstanceId),
).toEqual([
'review-1',
'order-running',
'art-completed',
'pricing-completed',
'main-1',
])
})
it('以发起时间为主,不让旧流程的较晚更新时间覆盖新流程', () => {
const records = [
buildFlow(
'art',
'old-art-updated-later',
'2026-08-05T08:00:00Z',
'REFUSED',
'2026-08-08T08:00:00Z',
),
buildFlow(
'art',
'new-art',
'2026-08-06T08:00:00Z',
'RUNNING',
'2026-08-06T09:00:00Z',
),
]
expect(selectLatestRestartableChildFlows(records)).toEqual([records[1]])
})
it('发起时间相同时用更新时间稳定决定最新记录', () => {
const records = [
buildFlow(
'pricing',
'pricing-older-update',
'2026-08-17T01:51:00Z',
'TERMINATED',
'2026-08-17T01:52:00Z',
),
buildFlow(
'pricing',
'pricing-newer-update',
'2026-08-17T01:51:00Z',
'COMPLETED',
'2026-08-17T01:58:00Z',
),
]
expect(selectLatestRestartableChildFlows(records)).toEqual([records[1]])
})
})

View File

@ -0,0 +1,47 @@
const RESTARTABLE_CHILD_FLOW_TYPES = new Set(['order', 'art', 'pricing'])
export type OaBoardFlowSelectionRecord = {
flowType: string
processInstanceId: string
createdAtSource: Date | null
updatedAtSource: Date | null
}
function toTimestamp(value: Date | null) {
const timestamp = value?.getTime() ?? 0
return Number.isFinite(timestamp) ? timestamp : 0
}
function compareFlowRecency(
left: OaBoardFlowSelectionRecord,
right: OaBoardFlowSelectionRecord,
) {
const createdAtDiff = toTimestamp(left.createdAtSource) - toTimestamp(right.createdAtSource)
if (createdAtDiff !== 0) return createdAtDiff
const updatedAtDiff = toTimestamp(left.updatedAtSource) - toTimestamp(right.updatedAtSource)
if (updatedAtDiff !== 0) return updatedAtDiff
return left.processInstanceId.localeCompare(right.processInstanceId)
}
export function selectLatestRestartableChildFlows<T extends OaBoardFlowSelectionRecord>(
records: T[],
) {
const latestByFlowType = new Map<string, T>()
records.forEach((record) => {
if (!RESTARTABLE_CHILD_FLOW_TYPES.has(record.flowType)) return
const currentLatest = latestByFlowType.get(record.flowType)
if (!currentLatest || compareFlowRecency(record, currentLatest) > 0) {
latestByFlowType.set(record.flowType, record)
}
})
return records.filter(
(record) =>
!RESTARTABLE_CHILD_FLOW_TYPES.has(record.flowType) ||
latestByFlowType.get(record.flowType) === record,
)
}

View File

@ -6,6 +6,7 @@ import {
import type sql from 'mssql'
import { Prisma } from '@prisma/client'
import { prisma } from '@/lib/prisma'
import { selectLatestRestartableChildFlows } from '@/lib/oa-board-flow-selection'
import { externalEmployeeDirectoryService } from './external-employee-directory.service'
import type { OaBoardProjectGraphInput } from './oa-board.service'
import {
@ -3106,7 +3107,10 @@ function buildProjectGroups(records: NormalizedFlowRecord[]): PreparedProjectGro
})
}
return Array.from(groups.values())
return Array.from(groups.values()).map((group) => ({
...group,
flowRecords: selectLatestRestartableChildFlows(group.flowRecords),
}))
}
function countNestedRecords(projectGraphs: OaBoardProjectGraphInput[]) {