gongxiangp2/lib/__tests__/art-workflow-points.test.ts

66 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @jest-environment node */
import { artPointPersonResolver, assessArtWorkflowPoints, calculateArtWorkflowPoints, parseArtPointRows } from '../art-workflow-points'
import { normalizeDetailFormFields, normalizeWorkflowTasks } from '../ding-workflow-sync'
import type { DingWorkflowDeliveryAssessment } from '../ding-workflow-delivery'
const people = [
{ id: 'p1', dingtalkUserId: 'ding1', realName: '甲', flowerName: '花甲', displayName: '花甲' },
{ id: 'p2', dingtalkUserId: 'ding2', realName: '乙', flowerName: '花乙', displayName: '花乙' },
]
const assessment: DingWorkflowDeliveryAssessment = { status: 'overtime', standardDuration: 2, actualDuration: 3.2, durationUnit: 'day', startedAt: '', finishedAt: '', startNodeLabel: '', endNodeLabel: '' }
const input = { workflowKey: '20260901-key', plannerId: 'p1', planningRows: [{ 策划数量: '2', 策划单分值: '10' }], graphicRows: [{ 全案策划: '花甲', 平面数量: '3', 平面单分值: '10' }, { 全案策划: '花乙', 平面数量: '1', 平面单分值: '4' }], imageRows: [], assessment }
test('策划与平面/出图数量乘单分值,超时天数向上取整每天扣5,同人只扣一次,允许负积分', () => {
const result = calculateArtWorkflowPoints(input, people)
expect(result.get('p1')).toBe(40) // 20+30-ceil(1.2)*5
expect(result.get('p2')).toBe(-6)
})
test('角色免扣仅在承担扣分的角色生效;人工增减只应用一次', () => {
const result = calculateArtWorkflowPoints(input, people, {
overtimeExemptions: [{ workflowKey: input.workflowKey, name: '花甲', role: 'fullCase' }],
deductions: [{ workflowKey: input.workflowKey, name: '甲', points: 3, type: 'add' }, { workflowKey: input.workflowKey, name: '甲', points: 2, type: 'deduct' }, { workflowKey: input.workflowKey, name: '甲', points: 99, enabled: false }],
})
expect(result.get('p1')).toBe(51)
expect(result.get('p2')).toBe(-6)
})
test('只有人工调整的人员不承担该流程超时扣分', () => {
const result = calculateArtWorkflowPoints({ ...input, graphicRows: [], imageRows: [] }, people, {
overtimeExemptions: [], deductions: [{ workflowKey: input.workflowKey, name: '花乙', points: 3, type: 'add' }],
})
expect(result.get('p2')).toBe(3)
})
test('稳定钉钉ID优先,同名冲突与多选不归人,JSON表单解析保留联系人', () => {
const resolve = artPointPersonResolver([...people, { ...people[1], id: 'p3', flowerName: '花甲', displayName: '花甲', dingtalkUserId: 'ding3' }])
expect(resolve('花甲')).toBeNull()
expect(resolve([{ emplId: 'ding1', name: '花甲' }])).toBe('p1')
expect(resolve(JSON.stringify([{ emplId: 'ding1', name: '花甲' }]))).toBe('p1')
expect(resolve([{ emplId: 'ding1' }, { emplId: 'ding2' }])).toBeNull()
const rows = parseArtPointRows(JSON.stringify([{ rowValue: [{ label: '全案策划', value: [{ emplId: 'ding1' }] }] }]))
expect(resolve(rows[0]['全案策划'])).toBe('p1')
expect(parseArtPointRows('invalid')).toEqual([])
})
test('千位逗号按源码移除,按角色分别保留两位小数后汇总', () => {
const result = calculateArtWorkflowPoints({ ...input,
planningRows: [{ 策划数量: '1,000', 策划单分值: '0.001006' }],
graphicRows: [{ 全案策划: '花甲', 平面数量: '1', 平面单分值: '1.006' }],
assessment: { ...assessment, actualDuration: null, standardDuration: null },
}, people)
expect(result.get('p1')).toBe(2.02)
})
test('积分节点只使用指定活动ID;首次分配至末次审核连续计时,无时效仍保留审核时间', () => {
const tasks = normalizeWorkflowTasks([
{ taskId: 'assign', activityId: '38da_544a', createdAt: '2026-09-01T00:00:00Z' },
{ taskId: 'assign-again', activityId: '38da_544a', createdAt: '2026-09-03T00:00:00Z' },
{ taskId: 'old-review', activityId: 'c255_84b3', finishedAt: '2026-08-31T00:00:00Z' },
{ taskId: 'review', activityId: 'c255_84b3', finishedAt: '2026-09-04T03:00:00Z', reviewPassed: false },
{ taskId: 'other', activityId: 'other', nodeName: '品牌策划主管审核', finishedAt: '2026-10-01T00:00:00Z' },
])
const result = assessArtWorkflowPoints(tasks, normalizeDetailFormFields({}))
expect(result.startedAt).toBe('2026-09-01T00:00:00.000Z')
expect(result.finishedAt).toBe('2026-09-04T03:00:00.000Z')
expect(result.actualDuration).toBe(3.13)
expect(result.standardDuration).toBeNull()
})