gongxiangp2/lib/__tests__/ding-workflow-delivery.test.ts
2026-08-28 14:12:02 +08:00

285 lines
11 KiB
TypeScript

import { assessDingWorkflowDelivery, assessDingWorkflowDeliveryBatch } from '@/lib/ding-workflow-delivery'
import type { DingWorkflowDetailFormFields, OngoingDingWorkflowActiveTask } from '@/lib/ding-workflow-sync'
const ART_PROCESS_CODE = 'PROC-3571C915-0506-4490-A91A-8AA7EA33F838'
const CONTENT_PROCESS_CODE = 'PROC-AE9FF3E1-A442-4E6A-85CF-0F2B86409673'
function formFields(overrides: Partial<DingWorkflowDetailFormFields> = {}): DingWorkflowDetailFormFields {
return {
platform: null,
brand: null,
category: null,
spu: null,
demandType: null,
standardOutputDeadline: null,
sourceType: null,
taskType: null,
installVideo: null,
quantity: null,
cameraQuantity: null,
planningTaskStats: null,
graphicTaskStats: null,
imageTaskStats: null,
...overrides,
}
}
function task(
nodeName: string,
createdAt: string,
finishedAt: string | null,
taskId = nodeName,
): OngoingDingWorkflowActiveTask {
return {
taskId,
activityId: null,
nodeName,
assigneeUserId: null,
assigneeName: '测试人员',
assigneeDepartment: '品牌中心',
isBrandCenter: true,
isLeadership: false,
isActive: finishedAt === null,
reviewPassed: finishedAt !== null,
result: finishedAt === null ? null : 'AGREE',
createdAt,
finishedAt,
}
}
describe('Ding workflow delivery SLA', () => {
it('uses post-July art task statistics and wall-clock days', () => {
const assessment = assessDingWorkflowDelivery({
processCode: ART_PROCESS_CODE,
sourceCreatedAt: '2026-07-10T00:00:00.000Z',
sourceFinishedAt: '2026-07-11T00:00:00.000Z',
formFields: formFields({
planningTaskStats: JSON.stringify([{
rowValue: [
{ label: '策划类型', value: '新品策划全案/新品全套图文' },
{ label: '策划细分', value: '全套新品的策划方案(主图视频脚本/策划方案/买家秀文案)' },
{ label: '策划数量', value: '1' },
],
}]),
}),
tasks: [
task('品牌策划主管分配任务', '2026-07-10T00:00:00.000Z', '2026-07-10T00:05:00.000Z'),
task('品牌策划主管审核', '2026-07-11T12:00:00.000Z', '2026-07-11T12:00:00.000Z'),
],
})
expect(assessment).toMatchObject({
status: 'on_time',
standardDuration: 2,
durationUnit: 'day',
actualDuration: 1.5,
startNodeLabel: '品牌策划主管分配任务',
endNodeLabel: '品牌策划主管审核',
})
})
it('uses the legacy art demand/source matrix before July', () => {
const assessment = assessDingWorkflowDelivery({
processCode: ART_PROCESS_CODE,
sourceCreatedAt: '2026-04-20T00:00:00.000Z',
sourceFinishedAt: '2026-04-24T00:00:00.000Z',
formFields: formFields({ demandType: '新品', sourceType: '仅出图' }),
tasks: [
task('品牌策划主管分配任务', '2026-04-20T00:00:00.000Z', '2026-04-20T00:05:00.000Z'),
task('品牌策划主管审核', '2026-04-24T00:00:00.000Z', '2026-04-24T00:00:00.000Z'),
],
})
expect(assessment).toMatchObject({ status: 'overtime', standardDuration: 3, actualDuration: 4 })
})
it('ends director-review content rules at 编导审核', () => {
const assessment = assessDingWorkflowDelivery({
processCode: CONTENT_PROCESS_CODE,
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
sourceFinishedAt: '2026-08-03T00:00:00.000Z',
formFields: formFields({ demandType: '抖音-千川素材', taskType: '布景拍摄' }),
tasks: [
task('内容主管分配任务', '2026-08-01T00:00:00.000Z', '2026-08-01T00:05:00.000Z'),
task('编导审核', '2026-08-03T00:00:00.000Z', '2026-08-03T00:00:00.000Z'),
],
})
expect(assessment).toMatchObject({
status: 'on_time',
standardDuration: 72,
durationUnit: 'hour',
actualDuration: 48,
endNodeLabel: '编导审核',
})
})
it('uses the confirmed content assignment and review timestamps after report-clock normalization', () => {
const assessment = assessDingWorkflowDelivery({
processCode: CONTENT_PROCESS_CODE,
sourceCreatedAt: '2026-08-14T06:52:00.000Z',
sourceFinishedAt: '2026-08-17T01:10:00.000Z',
formFields: formFields({ demandType: '抖音-千川素材', taskType: '二次剪辑' }),
tasks: [
task('内容主管分配任务', '2026-08-14T06:59:00.000Z', '2026-08-14T08:04:00.000Z'),
task('编导', '2026-08-15T07:25:00.000Z', '2026-08-15T07:25:00.000Z'),
task('编导审核', '2026-08-15T07:35:00.000Z', '2026-08-15T09:11:00.000Z'),
],
})
expect(assessment).toMatchObject({
status: 'on_time',
standardDuration: 48,
durationUnit: 'hour',
actualDuration: 26.2,
startedAt: '2026-08-14T06:59:00.000Z',
finishedAt: '2026-08-15T09:11:00.000Z',
startNodeLabel: '内容主管分配任务',
endNodeLabel: '编导审核',
})
})
it('ends content-supervisor rules at 内容主管审核', () => {
const assessment = assessDingWorkflowDelivery({
processCode: CONTENT_PROCESS_CODE,
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
sourceFinishedAt: '2026-08-02T06:00:00.000Z',
formFields: formFields({ demandType: '白底图-策划专用', taskType: '布局拍摄' }),
tasks: [
task('内容负责人分配任务', '2026-08-01T00:00:00.000Z', '2026-08-01T00:05:00.000Z'),
task('内容主管审核', '2026-08-02T06:00:00.000Z', '2026-08-02T06:00:00.000Z'),
],
})
expect(assessment).toMatchObject({
status: 'overtime',
standardDuration: 24,
durationUnit: 'hour',
actualDuration: 30,
startNodeLabel: '内容主管分配任务',
endNodeLabel: '内容主管审核',
})
})
it('does not fall back to workflow completion when the required review task is missing', () => {
const assessment = assessDingWorkflowDelivery({
processCode: CONTENT_PROCESS_CODE,
// 即使流程实例在本月完结,也必须有规则指定的审核节点完成时间。
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
sourceFinishedAt: '2026-08-03T00:00:00.000Z',
formFields: formFields({ demandType: '抖音-千川素材', taskType: '布局拍摄' }),
tasks: [
task('内容主管分配任务', '2026-08-01T00:00:00.000Z', '2026-08-01T00:05:00.000Z'),
],
})
expect(assessment).toMatchObject({
status: 'pending',
standardDuration: 72,
actualDuration: null,
finishedAt: null,
endNodeLabel: '编导审核',
})
})
it('keeps missing configuration or milestone data pending', () => {
const assessment = assessDingWorkflowDelivery({
processCode: CONTENT_PROCESS_CODE,
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
sourceFinishedAt: null,
formFields: formFields({ demandType: '未知类型', taskType: '布局拍摄' }),
tasks: [],
})
expect(assessment).toMatchObject({
status: 'pending',
standardDuration: null,
actualDuration: null,
})
})
it('defers content assessment start when the daily queue is full', () => {
const inputs = [
{
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-01T01:00:00.000Z', '2026-08-01T01:00:00.000Z'),
task('编导审核', '2026-08-02T00:00:00.000Z', '2026-08-02T00:00:00.000Z'),
],
workflowId: 'a',
workflowStatus: 'COMPLETED',
},
{
processCode: CONTENT_PROCESS_CODE,
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
sourceFinishedAt: '2026-08-03T00:00:00.000Z',
formFields: formFields({ demandType: '抖音-千川素材', taskType: '布局拍摄' }),
tasks: [
task('内容主管分配任务', '2026-08-01T02:00:00.000Z', '2026-08-01T02:00:00.000Z'),
task('编导审核', '2026-08-03T00:00:00.000Z', '2026-08-03T00:00:00.000Z'),
],
workflowId: 'b',
workflowStatus: 'COMPLETED',
},
{
processCode: CONTENT_PROCESS_CODE,
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
sourceFinishedAt: '2026-08-04T00:00:00.000Z',
formFields: formFields({ demandType: '抖音-千川素材', taskType: '布局拍摄' }),
tasks: [
task('内容主管分配任务', '2026-08-01T03:00:00.000Z', '2026-08-01T03:00:00.000Z'),
task('编导审核', '2026-08-04T00:00:00.000Z', '2026-08-04T00:00:00.000Z'),
],
workflowId: 'c',
workflowStatus: 'COMPLETED',
},
{
processCode: CONTENT_PROCESS_CODE,
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
sourceFinishedAt: '2026-08-05T00:00:00.000Z',
formFields: formFields({ demandType: '抖音-千川素材', taskType: '布局拍摄' }),
tasks: [
task('内容主管分配任务', '2026-08-01T04:00:00.000Z', '2026-08-01T04:00:00.000Z'),
task('编导审核', '2026-08-05T00:00:00.000Z', '2026-08-05T00:00:00.000Z'),
],
workflowId: 'd',
workflowStatus: 'COMPLETED',
},
]
const assessments = assessDingWorkflowDeliveryBatch(inputs)
expect(assessments.slice(0, 3).map((assessment) => assessment.queueShiftDays)).toEqual([0, 0, 0])
expect(assessments[3]).toMatchObject({
queueDeferred: true,
queueLimit: 3,
queueSequence: 1,
queueShiftDays: 1,
startedAt: '2026-08-02T01:00:00.000Z',
actualDuration: 71,
status: 'on_time',
})
})
it('uses a two-day interval for installation-video queue deferrals', () => {
const makeInput = (id: string, day: string) => ({
processCode: CONTENT_PROCESS_CODE,
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
sourceFinishedAt: `2026-08-${day === '01' ? '03' : '05'}T00:00:00.000Z`,
formFields: formFields({ demandType: '安装视频', taskType: '布局拍摄', installVideo: 'A档' }),
tasks: [
task('内容主管分配任务', `2026-08-${day}T01:00:00.000Z`, `2026-08-${day}T01:00:00.000Z`),
task('内容主管审核', `2026-08-${day === '01' ? '03' : '05'}T00:00:00.000Z`, `2026-08-${day === '01' ? '03' : '05'}T00:00:00.000Z`),
],
workflowId: id,
workflowStatus: 'COMPLETED',
})
const [first, second] = assessDingWorkflowDeliveryBatch([makeInput('a', '01'), makeInput('b', '01')])
expect(first.queueShiftDays).toBe(0)
expect(second.queueShiftDays).toBe(2)
expect(second.startedAt).toBe('2026-08-03T01:00:00.000Z')
})
})