fix: align workflow milestones with confirmed review nodes
This commit is contained in:
parent
60fa35e87e
commit
378ae08ec4
@ -28,7 +28,7 @@ web
|
||||
- “数据口径”使用独立二级栏直接进入“公司组织架构”,在品牌中心外壳内复用 `/organization` 的真实组织树、人员明细与管理员权限。
|
||||
- “进行中项目”只同步两个已确认的钉钉流程类型,并只统计北京时间 2026 年 4 月 1 日起发起且尚未完结的流程;总览和所有下钻维度共用这一口径。
|
||||
- “进行中项目”项目明细按业务导出表展示审批标题、审批编号、需求人、需求部门、平台、品牌、类目、SPU、需求类型、标准时效、当前节点与负责人;需求人取流程发起人,需求部门按 HR 人员架构匹配。
|
||||
- 美术设计流程的明细开始时间取“品牌策划主管分配任务”节点创建时间,结束时间取“品牌策划主管审核”节点完成时间;内容视频流程的明细开始时间取“内容主管分配任务”节点创建时间,结束时间按实际分支取“编导审核”或“内容主管审核”节点完成时间。兼容上游当前使用的“内容部负责人”“编导”和“品牌中心负责人审核”等节点名,但对用户统一展示确认后的业务名称;若历史轨迹中同时存在两类审核,取较晚完成的实际结束节点。
|
||||
- 美术设计流程的明细开始时间取“品牌策划主管分配任务”节点创建时间,结束时间取“品牌策划主管审核”节点完成时间;内容视频流程的明细开始时间取已确认的“内容主管分配任务”节点创建时间,结束时间按规则分支取“编导审核”或“内容主管审核”节点完成时间。内容流程中多个主管分配节点、编导制作节点及普通领导者任务均不等同于最终审核;未知 activityId 不得仅凭负责人身份、AGREE 结果或完成状态推断为审核节点。
|
||||
- 美术设计申请流程的 `ding_WorkflowTask.activityId` 按已确认流程定义映射为精确中文节点名并写入本地快照;未知节点继续使用同步字段推断或明确回退,不改写上游只读数据。
|
||||
- 钉钉流程快照每小时从只读报表库的 `ding_Workflow`、`ding_WorkflowTask`、`ding_WorkflowScheme` 联合增量同步,只接收已批准的美术设计申请和内容视频流程;三表复合游标写入现有同步状态并继续复用同一批生产快照数据。
|
||||
- “本月上新”只读取表单字段“需求类型”为“新品”的六品牌美术设计流程,不以流程标题关键词判断,也不计“优化”等其他需求类型;分子为本月整条流程已完结数量,分母为本月已完结数量与当前进行中数量之和;其中进行中流程直接复用“进行中项目”的美术设计流程状态、时间与当前品牌中心处理人条件。
|
||||
|
||||
@ -161,6 +161,27 @@ describe('Ding workflow delivery SLA', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not use a leadership or content-owner task as the supervisor review', () => {
|
||||
const assessment = assessDingWorkflowDelivery({
|
||||
processCode: CONTENT_PROCESS_CODE,
|
||||
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
|
||||
sourceFinishedAt: '2026-08-02T00:00:00.000Z',
|
||||
formFields: formFields({ demandType: '白底图-策划专用', taskType: '布局拍摄' }),
|
||||
tasks: [
|
||||
task('内容主管分配任务', '2026-08-01T00:00:00.000Z', '2026-08-01T00:05:00.000Z'),
|
||||
task('内容部负责人', '2026-08-01T06:00:00.000Z', '2026-08-01T06:05:00.000Z'),
|
||||
task('品牌中心负责人审核', '2026-08-01T06:05:00.000Z', '2026-08-01T06:10:00.000Z'),
|
||||
],
|
||||
})
|
||||
|
||||
expect(assessment).toMatchObject({
|
||||
status: 'pending',
|
||||
actualDuration: null,
|
||||
finishedAt: null,
|
||||
endNodeLabel: '内容主管审核',
|
||||
})
|
||||
})
|
||||
|
||||
it('does not fall back to workflow completion when the required review task is missing', () => {
|
||||
const assessment = assessDingWorkflowDelivery({
|
||||
processCode: CONTENT_PROCESS_CODE,
|
||||
@ -182,6 +203,53 @@ describe('Ding workflow delivery SLA', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores a finish timestamp on an active review task', () => {
|
||||
const assessment = assessDingWorkflowDelivery({
|
||||
processCode: CONTENT_PROCESS_CODE,
|
||||
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
|
||||
sourceFinishedAt: null,
|
||||
formFields: formFields({ demandType: '抖音-千川素材', taskType: '布局拍摄' }),
|
||||
tasks: [
|
||||
task('内容主管分配任务', '2026-08-01T00:00:00.000Z', '2026-08-01T00:05:00.000Z'),
|
||||
{
|
||||
...task('编导审核', '2026-08-02T00:00:00.000Z', '2026-08-02T01:00:00.000Z'),
|
||||
isActive: true,
|
||||
reviewPassed: false,
|
||||
result: null,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(assessment).toMatchObject({
|
||||
status: 'pending',
|
||||
actualDuration: null,
|
||||
finishedAt: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores an unconfirmed review with a mirrored finish timestamp', () => {
|
||||
const assessment = assessDingWorkflowDelivery({
|
||||
processCode: CONTENT_PROCESS_CODE,
|
||||
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
|
||||
sourceFinishedAt: null,
|
||||
formFields: formFields({ demandType: '抖音-千川素材', taskType: '布局拍摄' }),
|
||||
tasks: [{
|
||||
...task('内容主管分配任务', '2026-08-01T00:00:00.000Z', '2026-08-01T00:05:00.000Z'),
|
||||
taskId: 'running-review-with-finish-time',
|
||||
nodeName: '内容主管审核',
|
||||
isActive: false,
|
||||
reviewPassed: false,
|
||||
result: null,
|
||||
}],
|
||||
})
|
||||
|
||||
expect(assessment).toMatchObject({
|
||||
status: 'pending',
|
||||
actualDuration: null,
|
||||
finishedAt: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps missing configuration or milestone data pending', () => {
|
||||
const assessment = assessDingWorkflowDelivery({
|
||||
processCode: CONTENT_PROCESS_CODE,
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
getShanghaiMonthRange,
|
||||
getDingWorkflowActivityNodeName,
|
||||
getDingWorkflowProjectType,
|
||||
isDingWorkflowTaskOngoing,
|
||||
isAllowedDingWorkflowProcessCode,
|
||||
isDingWorkflowStartedOnOrAfterCutoff,
|
||||
isDingWorkflowOngoing,
|
||||
@ -123,13 +124,19 @@ describe('ding workflow snapshot sync semantics', () => {
|
||||
|
||||
it('maps all confirmed content workflow milestone activity ids', () => {
|
||||
const contentCode = DING_WORKFLOW_ALLOWED_PROCESS_CODES[1]
|
||||
expect(getDingWorkflowActivityNodeName(contentCode, 'cc59_3e5a')).toBe('内容主管分配任务')
|
||||
expect(getDingWorkflowActivityNodeName(contentCode, 'cc59_3e5a')).toBe('内容负责人分配任务')
|
||||
expect(getDingWorkflowActivityNodeName(contentCode, '9fb8_32e7')).toBe('内容主管分配任务')
|
||||
expect(getDingWorkflowActivityNodeName(contentCode, 'a021_1e2e')).toBe('编导审核')
|
||||
expect(getDingWorkflowActivityNodeName(contentCode, '39ba_efb1')).toBe('编导审核')
|
||||
expect(getDingWorkflowActivityNodeName(contentCode, '6c43_575f')).toBe('内容主管审核')
|
||||
})
|
||||
|
||||
it('treats a task with finishTime as history even if its mirrored status is still running', () => {
|
||||
expect(isDingWorkflowTaskOngoing('RUNNING', null)).toBe(true)
|
||||
expect(isDingWorkflowTaskOngoing('RUNNING', '2026-08-25 11:20:00')).toBe(false)
|
||||
expect(isDingWorkflowTaskOngoing('COMPLETED', null)).toBe(false)
|
||||
})
|
||||
|
||||
it('upgrades the legacy workflow-only cursor to a three-table cursor', () => {
|
||||
expect(parseDingWorkflowSourceCursor('2026-08-26 19:42:36')).toEqual({
|
||||
version: 3,
|
||||
@ -296,7 +303,7 @@ describe('ding workflow snapshot sync semantics', () => {
|
||||
result: 'AGREE', createdAt: '2026-08-01T01:00:00.000Z', finishedAt: '2026-08-01T02:00:00.000Z',
|
||||
},
|
||||
{
|
||||
taskId: 'task-2', activityId: 'art-end', nodeName: '品牌策划主管审核',
|
||||
taskId: 'task-2', activityId: 'c255_84b3', nodeName: '品牌策划主管审核',
|
||||
assigneeUserId: 'user-2', assigneeName: '乙', assigneeDepartment: '品牌中心',
|
||||
isBrandCenter: true, isLeadership: true, isActive: false, reviewPassed: true,
|
||||
result: 'AGREE', createdAt: '2026-08-02T01:00:00.000Z', finishedAt: '2026-08-02T02:00:00.000Z',
|
||||
@ -315,7 +322,7 @@ describe('ding workflow snapshot sync semantics', () => {
|
||||
{ ...tasks[0], taskId: 'task-4', nodeName: '编导', finishedAt: '2026-08-05T02:00:00.000Z' },
|
||||
])).toEqual({
|
||||
assignmentNodeLabel: '内容主管分配任务',
|
||||
assignmentStartedAt: '2026-08-03T01:00:00.000Z',
|
||||
assignmentStartedAt: null,
|
||||
reviewNodeLabel: '内容主管审核',
|
||||
reviewFinishedAt: null,
|
||||
})
|
||||
@ -326,9 +333,34 @@ describe('ding workflow snapshot sync semantics', () => {
|
||||
{ ...tasks[0], taskId: 'task-6', nodeName: '品牌中心负责人审核', finishedAt: '2026-08-08T02:00:00.000Z' },
|
||||
])).toEqual({
|
||||
assignmentNodeLabel: '内容主管分配任务',
|
||||
assignmentStartedAt: '2026-08-06T01:00:00.000Z',
|
||||
assignmentStartedAt: null,
|
||||
reviewNodeLabel: '内容主管审核',
|
||||
reviewFinishedAt: '2026-08-08T02:00:00.000Z',
|
||||
reviewFinishedAt: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not treat unknown leadership tasks or assignment labels as review milestones', () => {
|
||||
const contentCode = DING_WORKFLOW_ALLOWED_PROCESS_CODES[1]
|
||||
expect(getDingWorkflowActivityNodeName(contentCode, '8814_bf03')).toBeNull()
|
||||
expect(resolveDingWorkflowDetailMilestones(contentCode, [{
|
||||
taskId: 'unknown-leadership-task',
|
||||
activityId: '8814_bf03',
|
||||
nodeName: '品牌中心负责人审核',
|
||||
assigneeUserId: 'leader-1',
|
||||
assigneeName: '领导者',
|
||||
assigneeDepartment: '品牌中心',
|
||||
isBrandCenter: true,
|
||||
isLeadership: true,
|
||||
isActive: false,
|
||||
reviewPassed: true,
|
||||
result: 'AGREE',
|
||||
createdAt: '2026-08-26T03:00:00.000Z',
|
||||
finishedAt: '2026-08-26T03:19:00.000Z',
|
||||
}])).toEqual({
|
||||
assignmentNodeLabel: '内容主管分配任务',
|
||||
assignmentStartedAt: null,
|
||||
reviewNodeLabel: '内容主管审核',
|
||||
reviewFinishedAt: null,
|
||||
})
|
||||
})
|
||||
|
||||
@ -371,6 +403,32 @@ describe('ding workflow snapshot sync semantics', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores an unconfirmed review that has a mirrored finish timestamp', () => {
|
||||
expect(resolveDingWorkflowDetailMilestones(
|
||||
DING_WORKFLOW_ALLOWED_PROCESS_CODES[1],
|
||||
[{
|
||||
taskId: 'running-supervisor-review',
|
||||
activityId: '6c43_575f',
|
||||
nodeName: '内容主管审核',
|
||||
assigneeUserId: 'user-2',
|
||||
assigneeName: '乙',
|
||||
assigneeDepartment: '品牌中心',
|
||||
isBrandCenter: true,
|
||||
isLeadership: true,
|
||||
isActive: false,
|
||||
reviewPassed: false,
|
||||
result: null,
|
||||
createdAt: '2026-08-27T03:00:00.000Z',
|
||||
finishedAt: '2026-08-27T03:19:00.000Z',
|
||||
}],
|
||||
)).toEqual({
|
||||
assignmentNodeLabel: '内容主管分配任务',
|
||||
assignmentStartedAt: null,
|
||||
reviewNodeLabel: '内容主管审核',
|
||||
reviewFinishedAt: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('matches initiators by DingTalk roster profile and formats the HR organization path', () => {
|
||||
expect(resolveDingWorkflowInitiator({
|
||||
displayName: '荀瑶',
|
||||
|
||||
@ -350,7 +350,12 @@ function latestTaskTimeByField(tasks: OngoingDingWorkflowActiveTask[], names: st
|
||||
}
|
||||
|
||||
function latestTaskTime(tasks: OngoingDingWorkflowActiveTask[], names: string[], after: number | null) {
|
||||
return tasks.flatMap((task) => names.includes(task.nodeName) && taskTime(task, 'finishedAt') ? [taskTime(task, 'finishedAt')!] : [])
|
||||
return tasks.flatMap((task) => names.includes(task.nodeName)
|
||||
&& task.isActive !== true
|
||||
&& task.reviewPassed === true
|
||||
&& taskTime(task, 'finishedAt')
|
||||
? [taskTime(task, 'finishedAt')!]
|
||||
: [])
|
||||
.filter((entry) => after === null || entry.timestamp >= after)
|
||||
.sort((a, b) => b.timestamp - a.timestamp)[0] || null
|
||||
}
|
||||
@ -514,14 +519,14 @@ export function assessDingWorkflowDelivery(input: DingWorkflowDeliveryInput): Di
|
||||
}
|
||||
|
||||
if (input.processCode === CONTENT_PROCESS_CODE) {
|
||||
const assignmentNames = ['内容主管分配任务', '内容负责人分配任务', '内容部负责人']
|
||||
const assignmentNames = ['内容主管分配任务', '内容负责人分配任务']
|
||||
const start = earliestTaskTime(input.tasks, assignmentNames, 'createdAt')
|
||||
|| earliestTaskTime(input.tasks, ['编导'], 'createdAt')
|
||||
|| (createdAt === null || !input.sourceCreatedAt ? null : { value: input.sourceCreatedAt, timestamp: createdAt })
|
||||
const rule = createdAt === null ? null : resolveContentRule(input.formFields, createdAt)
|
||||
const endNodeLabel = rule?.actualCalcTo === 'contentReview' ? '内容主管审核' : '编导审核'
|
||||
const endNames = rule?.actualCalcTo === 'contentReview'
|
||||
? ['内容主管审核', '内容负责人审核', '内容部负责人审核', '品牌中心负责人审核']
|
||||
? ['内容主管审核']
|
||||
: ['编导审核']
|
||||
const taskFinish = latestTaskTime(input.tasks, endNames, start?.timestamp ?? null)
|
||||
// 内容流程的实际用时必须截止到规则指定的审核节点;流程实例的最终
|
||||
|
||||
@ -41,6 +41,14 @@ export const DING_WORKFLOW_NEW_PRODUCT_DEMAND_TYPE = '新品'
|
||||
const ALLOWED_PROCESS_CODE_VALUES: string[] = [...DING_WORKFLOW_ALLOWED_PROCESS_CODES]
|
||||
const ALLOWED_PROCESS_CODE_SET = new Set<string>(DING_WORKFLOW_ALLOWED_PROCESS_CODES)
|
||||
const BRAND_CENTER_ACTIVE_TASK_JSON = [{ isBrandCenter: true, isActive: true }]
|
||||
// Review milestones come only from the confirmed activityId mapping. A
|
||||
// completed leadership task with AGREE is not sufficient to infer a review:
|
||||
// content flows contain several supervisor assignment and production tasks.
|
||||
const CONFIRMED_REVIEW_NODE_NAMES = new Set([
|
||||
'品牌策划主管审核',
|
||||
'编导审核',
|
||||
'内容主管审核',
|
||||
])
|
||||
const PROCESS_TYPE_LABELS: Record<string, string> = {
|
||||
[DING_WORKFLOW_ALLOWED_PROCESS_CODES[0]]: '美术设计流程',
|
||||
[DING_WORKFLOW_ALLOWED_PROCESS_CODES[1]]: '内容视频和图片拍摄',
|
||||
@ -73,7 +81,7 @@ const ACTIVITY_NODE_NAMES_BY_PROCESS_CODE: Readonly<Record<string, Readonly<Reco
|
||||
},
|
||||
[DING_WORKFLOW_ALLOWED_PROCESS_CODES[1]]: {
|
||||
'sid-startevent': '发起人',
|
||||
'cc59_3e5a': '内容主管分配任务',
|
||||
'cc59_3e5a': '内容负责人分配任务',
|
||||
'9fb8_32e7': '内容主管分配任务',
|
||||
'd742_3645': '内容主管分配任务',
|
||||
'b9a4_c03a': '内容主管分配任务',
|
||||
@ -660,12 +668,12 @@ const DETAIL_MILESTONE_NODES: Readonly<Record<string, {
|
||||
},
|
||||
[DING_WORKFLOW_ALLOWED_PROCESS_CODES[1]]: {
|
||||
assignmentNodeLabel: '内容主管分配任务',
|
||||
assignmentNodeNames: ['内容主管分配任务', '内容负责人分配任务', '内容部负责人'],
|
||||
assignmentNodeNames: ['内容主管分配任务', '内容负责人分配任务'],
|
||||
// 进行中项目只展示内容主管审核已完成的时间;编导审核是制作分支
|
||||
// 的过程节点,不能在主管尚未审核时被误显示为“审核结束时间”。
|
||||
reviewNodes: [{
|
||||
label: '内容主管审核',
|
||||
names: ['内容主管审核', '内容部负责人审核', '品牌中心负责人审核'],
|
||||
names: ['内容主管审核'],
|
||||
}],
|
||||
},
|
||||
}
|
||||
@ -691,8 +699,23 @@ export function resolveDingWorkflowDetailMilestones(
|
||||
const completedReviews = config.reviewNodes.flatMap((reviewNode) => tasks
|
||||
// A source task may carry a finish timestamp while the approval node is
|
||||
// still active (for example, during an upstream mirror update). An active
|
||||
// review node is not an end milestone, even when finishTime is present.
|
||||
.filter((task) => reviewNode.names.includes(task.nodeName) && task.finishedAt && !task.isActive)
|
||||
// or unconfirmed review node is not an end milestone, even when
|
||||
// finishTime is present.
|
||||
.filter((task) => {
|
||||
if (
|
||||
!reviewNode.names.includes(task.nodeName)
|
||||
|| !task.finishedAt
|
||||
|| task.isActive
|
||||
|| task.reviewPassed !== true
|
||||
) return false
|
||||
// When an activity id is present, require it to resolve to the same
|
||||
// canonical review label. This protects old snapshots from arbitrary
|
||||
// labels attached by the previous leadership-task fallback.
|
||||
const mappedName = task.activityId
|
||||
? getDingWorkflowActivityNodeName(processCode, task.activityId)
|
||||
: task.nodeName
|
||||
return mappedName === task.nodeName
|
||||
})
|
||||
.map((task) => ({
|
||||
label: reviewNode.label,
|
||||
finishedAt: task.finishedAt as string,
|
||||
@ -1115,7 +1138,11 @@ async function fetchWorkflowAggregates(pool: ConnectionPool, processInstanceIds:
|
||||
SELECT
|
||||
processInstanceId,
|
||||
COUNT_BIG(*) AS taskCount,
|
||||
SUM(CASE WHEN UPPER(LTRIM(RTRIM(COALESCE(status, '')))) NOT IN ('COMPLETED', 'TERMINATED', 'CANCELED') THEN 1 ELSE 0 END) AS activeTaskCount,
|
||||
SUM(CASE
|
||||
WHEN UPPER(LTRIM(RTRIM(COALESCE(status, '')))) NOT IN ('COMPLETED', 'TERMINATED', 'CANCELED')
|
||||
AND NULLIF(LTRIM(RTRIM(COALESCE(finishTime, ''))), '') IS NULL
|
||||
THEN 1 ELSE 0
|
||||
END) AS activeTaskCount,
|
||||
MAX(CASE WHEN latestRow = 1 THEN status END) AS latestTaskStatus,
|
||||
MAX(CASE WHEN latestRow = 1 THEN createTime END) AS latestTaskAtRaw
|
||||
FROM ranked_tasks
|
||||
@ -1182,7 +1209,11 @@ async function fetchWorkflowAggregates(pool: ConnectionPool, processInstanceIds:
|
||||
}
|
||||
}
|
||||
|
||||
function sourceTaskIsOngoing(status: unknown): boolean {
|
||||
export function isDingWorkflowTaskOngoing(status: unknown, finishTime: unknown): boolean {
|
||||
// The external dashboard treats a task with a finish timestamp as history,
|
||||
// even when the mirrored status field still says RUNNING. This is important
|
||||
// for assignment nodes that have already handed work to the next node.
|
||||
if (clean(finishTime)) return false
|
||||
const normalized = clean(status)?.toUpperCase()
|
||||
return Boolean(normalized && !TERMINAL_WORKFLOW_STATUSES.has(normalized))
|
||||
}
|
||||
@ -1306,28 +1337,40 @@ function buildSnapshotPayload(
|
||||
const assigneeUserId = clean(sourceTask.userId)
|
||||
const taskStatus = clean(sourceTask.status)?.toUpperCase() || null
|
||||
const taskResult = clean(sourceTask.result)?.toUpperCase() || null
|
||||
const isActive = ongoing && sourceTaskIsOngoing(sourceTask.status)
|
||||
const isActive = ongoing && isDingWorkflowTaskOngoing(sourceTask.status, sourceTask.finishTime)
|
||||
const matchingAssignments = assigneeUserId
|
||||
? assignments.filter((assignment) => assignment.userId === assigneeUserId)
|
||||
: []
|
||||
const employee = assigneeUserId ? employeeDirectory.get(assigneeUserId) : null
|
||||
const reviewPassed = taskStatus === 'COMPLETED'
|
||||
&& taskResult === 'AGREE'
|
||||
&& employee?.isBrandCenter === true
|
||||
&& employee.isLeadership
|
||||
const uniqueAssignmentNodeNames = Array.from(new Set(matchingAssignments.map((assignment) => assignment.nodeName)))
|
||||
const configuredNodeName = getDingWorkflowActivityNodeName(processCode, activityId)
|
||||
const inferredNodeName = processCode && activityId
|
||||
? activityNodeNames.get(`${processCode}:${activityId}`)
|
||||
: null
|
||||
// Inference can label otherwise unnamed production tasks, but it must
|
||||
// never manufacture a review milestone. Review nodes are identified by
|
||||
// the explicit activityId mapping above.
|
||||
const safeInferredNodeName = inferredNodeName && !CONFIRMED_REVIEW_NODE_NAMES.has(inferredNodeName)
|
||||
? inferredNodeName
|
||||
: null
|
||||
const safeAssignmentNodeName = uniqueAssignmentNodeNames.find(
|
||||
(nodeName) => !CONFIRMED_REVIEW_NODE_NAMES.has(nodeName),
|
||||
) || null
|
||||
const nodeName = configuredNodeName
|
||||
|| safeInferredNodeName
|
||||
|| safeAssignmentNodeName
|
||||
|| (isActive ? '当前处理节点' : '历史处理节点')
|
||||
// Once the activity id resolves to a confirmed review node, completion
|
||||
// is determined by the task decision itself. The employee directory is
|
||||
// intentionally not part of this gate: a stale/missing roster entry
|
||||
// must not erase a valid review timestamp from the dashboard.
|
||||
const reviewPassed = CONFIRMED_REVIEW_NODE_NAMES.has(nodeName)
|
||||
&& taskStatus === 'COMPLETED'
|
||||
&& taskResult === 'AGREE'
|
||||
return [{
|
||||
taskId,
|
||||
activityId,
|
||||
nodeName: configuredNodeName
|
||||
|| (reviewPassed ? '品牌中心负责人审核' : null)
|
||||
|| inferredNodeName
|
||||
|| (uniqueAssignmentNodeNames.length === 1 ? uniqueAssignmentNodeNames[0] : null)
|
||||
|| (isActive ? '当前处理节点' : '历史处理节点'),
|
||||
nodeName,
|
||||
assigneeUserId,
|
||||
assigneeName: matchingAssignments.find((assignment) => assignment.name)?.name
|
||||
|| employee?.name
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user