66 lines
4.3 KiB
TypeScript
66 lines
4.3 KiB
TypeScript
/** @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()
|
||
})
|