285 lines
11 KiB
TypeScript
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')
|
|
})
|
|
})
|