Files
shanxia2.0/web-admin/scripts/manual-dispatch-calendar.test.ts
T
wuzilongandCursor 8e679da8e3 feat(pl): 在现场日历复制文本中统计入库和出库车次。
便于按产品一眼看出入出数量,并把入库明细排在出库前面。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-07 12:41:07 +08:00

318 lines
10 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.
import assert from 'node:assert/strict'
import test from 'node:test'
import {
loadCalendarPages,
manualCalendarDays,
manualCopyText,
manualVehicleType,
validateManualDraft
} from '../src/views/pl/dispatch-calendar/manual-model'
const row = (id: string, date = '2026-10-26', vehicleId = 'v1') => ({
id,
dispatchDate: date,
vehicleId,
licensePlate: '皖H12345',
productId: 'p1',
productName: '液碱',
origin: '山峡',
destination: '云栋',
quantity: '',
calendarKind: 'acid' as const,
remark: '',
createTime: '',
updateTime: ''
})
test('同车同日同路线保留独立车次,车辆筛选与汇总一致', () => {
const days = manualCalendarDays(
[row('1'), row('2'), row('3', '2026-10-26', 'v2'), row('4', '2026-11-01')],
'2026-10',
'v1'
)
const day = days.find((d) => d.date === '2026-10-26')!
assert.equal(day.rows.length, 2)
assert.equal(day.vehicles.length, 1)
assert.equal(day.products[0].count, 2)
assert.ok(days.some((d) => d.date === '2026-10-01' && d.rows.length === 0))
})
test('完整分页加载,不悄悄展示截断数据', async () => {
const calls: number[] = []
const rows = await loadCalendarPages(async ({ page }) => {
calls.push(page)
return { list: [row(String(page))], total: 3 }
})
assert.deepEqual(calls, [1, 2, 3])
assert.equal(rows?.length, 3)
await assert.rejects(
loadCalendarPages(async () => ({ list: [], total: 1 })),
/不完整/
)
})
test('过期请求不继续加载,不返回旧月份数据', async () => {
let current = true
assert.equal(
await loadCalendarPages(
async () => {
current = false
return { list: [row('1')], total: 3 }
},
() => current
),
null
)
})
test('复制保留重复车次,文本不混入实际运输计划', () => {
const text = manualCopyText('2026-10-26', [row('1'), row('2')])
assert.match(text, /2026-10-26 派车计划/)
assert.equal(text.split('山峡 → 云栋').length - 1, 2)
})
test('同一车辆按创建时间正序,复制时排在一起且每条换行', () => {
const later = {
...row('b', '2026-10-28', 'qa'),
licensePlate: '皖QA1024',
productName: '测试',
origin: '2',
destination: '3',
remark: '23',
createTime: '2026-10-28 10:00:00'
}
const earlier = {
...row('a', '2026-10-28', 'qa'),
licensePlate: '皖QA1024',
productName: '测试',
origin: '1',
destination: '2',
remark: '12',
createTime: '2026-10-28 09:00:00'
}
const other = {
...row('c', '2026-10-28', 'other'),
licensePlate: '皖H00001',
createTime: '2026-10-28 09:30:00'
}
const day = manualCalendarDays([later, other, earlier], '2026-10').find(
(item) => item.date === '2026-10-28'
)!
assert.deepEqual(
day.vehicles.find((vehicle) => vehicle.id === 'qa')!.rows.map((item) => item.id),
['a', 'b']
)
const text = manualCopyText('2026-10-28', [later, other, earlier])
assert.deepEqual(text.split('\n'), [
'2026-10-28 派车计划',
' ·皖QA1024',
' 测试 1 → 2(12)',
' 测试 2 → 3(23)',
' ·皖H00001',
' 液碱 山峡 → 云栋'
])
assert.equal(text.startsWith('2026-10-28'), true)
})
test('表单允许起止地留空,仍拒绝无效日期和超长地址', () => {
for (const calendarKind of ['acid', 'alkali', 'other', 'site'] as const) {
for (const addresses of [
{ origin: '', destination: '云栋' },
{ origin: '山峡', destination: '' },
{ origin: ' ', destination: ' ' }
]) {
assert.equal(validateManualDraft({ ...row('1'), calendarKind, ...addresses }), '')
}
}
for (const field of ['origin', 'destination']) {
assert.equal(
validateManualDraft({ ...row('1'), [field]: '地'.repeat(256) }),
'始发地和目的地最多255个字符'
)
}
assert.equal(
validateManualDraft({ ...row('1'), dispatchDate: '2026-02-30' }),
'请选择有效的派车日期'
)
assert.equal(validateManualDraft(row('1')), '')
})
test('数量是可空文本,复制逐条展示,日汇总仍只计车次', () => {
const tons = { ...row('1'), quantity: '32.50' }
const blank = { ...row('2'), quantity: ' ' }
const day = manualCalendarDays([tons, blank], '2026-10').find(
(item) => item.date === '2026-10-26'
)!
assert.deepEqual(day.products[0], { id: 'p1', name: '液碱', count: 2 })
const text = manualCopyText('2026-10-26', [tons, blank])
assert.match(text, /液碱 32\.50吨 山峡 → 云栋/)
assert.equal(text.split('山峡 → 云栋').length - 1, 2)
assert.equal((text.match(/吨/g) || []).length, 1)
assert.equal(validateManualDraft({ ...row('1'), quantity: '' }), '')
assert.equal(validateManualDraft({ ...row('1'), quantity: ' 12.5 ' }), '')
assert.equal(
validateManualDraft({ ...row('1'), quantity: '12.345' }),
'数量须为数字,最多两位小数'
)
assert.equal(validateManualDraft({ ...row('1'), quantity: '1吨' }), '数量须为数字,最多两位小数')
})
test('现场复制隐藏起止地和备注,单据类型放在每条记录末尾的中文括号中', () => {
const summary: Record<string, string> = {
入库计划: '(入2车)',
出库计划: '(出2车)',
出入库计划: '',
现场调拨: ''
}
for (const documentType of ['入库计划', '出库计划', '出入库计划', '现场调拨']) {
const record = {
...row('site'),
calendarKind: 'site' as const,
documentType,
quantity: '32.50',
remark: '内部备注'
}
assert.equal(validateManualDraft(record), '')
assert.deepEqual(
manualCopyText('2026-10-26', [record, { ...record, id: 'site-2', quantity: '' }]).split('\n'),
[
'2026-10-26 派车计划:',
'',
`液碱 2车${summary[documentType]}`,
` ·皖H12345 液碱 32.50吨(${documentType})`,
` ·皖H12345 液碱(${documentType})`
]
)
assert.equal(
manualCalendarDays([record], '2026-10').find((d) => d.date === record.dispatchDate)?.rows
.length,
1
)
}
for (const documentType of [null, undefined, '', ' ']) {
const record = {
...row('site'),
calendarKind: 'site' as const,
documentType,
remark: '内部备注'
}
assert.equal(
manualCopyText('2026-10-26', [record]),
'2026-10-26 派车计划:\n\n液碱 1车\n ·皖H12345 液碱'
)
}
})
test('单据类型选填且最多50个字符', () => {
assert.equal(validateManualDraft({ ...row('site'), calendarKind: 'site', documentType: '' }), '')
assert.match(
validateManualDraft({ ...row('site'), calendarKind: 'site', documentType: '字'.repeat(51) }),
/单据类型.*50/
)
})
test('现场按产品分组统计,交错产品集中展示且符合指定复制样例', () => {
const site = (
id: string,
productId: string,
productName: string,
licensePlate: string,
quantity: string
) => ({
...row(id, '2026-11-01', licensePlate),
calendarKind: 'site' as const,
productId,
productName,
licensePlate,
quantity,
documentType: '出库计划',
remark: '内部备注'
})
const records = [
site('1', 'p98', '98% 工业硫酸', '皖H10291', '32'),
site('2', 'pure', '精制硫酸', '皖H57837', '32'),
site('4', 'p50', '50% 工业硫酸', '皖HA3690', '28'),
site('3', 'pure', '精制硫酸', '皖HA3190', '32')
]
assert.equal(
manualCopyText('2026-11-01', records),
[
'2026-11-01 派车计划:',
'',
'98% 工业硫酸 1车(出1车)',
' ·皖H10291 98% 工业硫酸 32吨(出库计划)',
'精制硫酸 2车(出2车)',
' ·皖H57837 精制硫酸 32吨(出库计划)',
' ·皖HA3190 精制硫酸 32吨(出库计划)',
'50% 工业硫酸 1车(出1车)',
' ·皖HA3690 50% 工业硫酸 28吨(出库计划)'
].join('\n')
)
assert.deepEqual(
records.map((r) => r.id),
['1', '2', '4', '3']
)
})
test('现场产品组统计入出车次,入库排在出库前面,其他类型不计入入出', () => {
const site = (
id: string,
licensePlate: string,
documentType: string,
createTime: string
) => ({
...row(id, '2026-11-01', licensePlate),
calendarKind: 'site' as const,
productName: '98%工业硫酸',
licensePlate,
quantity: '32',
documentType,
createTime
})
assert.equal(
manualCopyText('2026-11-01', [
site('out-later', '皖H58559', '出库计划', '2026-11-01 10:00:00'),
site('direct', '皖H10001', '直销计划', '2026-11-01 07:00:00'),
site('in', '皖H57837', ' 入库计划 ', '2026-11-01 11:00:00'),
site('out-early', '皖H56153', '出库计划', '2026-11-01 08:00:00')
]),
[
'2026-11-01 派车计划:',
'',
'98%工业硫酸 4车(入1车 出2车)',
' ·皖H57837 98%工业硫酸 32吨(入库计划)',
' ·皖H56153 98%工业硫酸 32吨(出库计划)',
' ·皖H58559 98%工业硫酸 32吨(出库计划)',
' ·皖H10001 98%工业硫酸 32吨(直销计划)'
].join('\n')
)
})
test('现场同车重复记录逐条计车,同名不同产品ID保持独立且组内按创建顺序', () => {
const first = { ...row('1'), calendarKind: 'site' as const, createTime: '2026-10-26 01:00:00' }
const later = { ...first, id: '2', createTime: '2026-10-26 02:00:00', quantity: '20' }
const different = { ...first, id: '3', productId: 'p2' }
assert.equal(
manualCopyText('2026-10-26', [later, different, first]),
[
'2026-10-26 派车计划:',
'',
'液碱 2车',
' ·皖H12345 液碱',
' ·皖H12345 液碱 20吨',
'液碱 1车',
' ·皖H12345 液碱'
].join('\n')
)
})
test('车辆分类以待派车ID优先,其余仅接受类型白名单,不从车牌或产品推断', () => {
for (const vehicleType of ['self', 'carrier', 'pending', 'unknown'] as const) {
assert.equal(manualVehicleType({ ...row('1'), vehicleType }), vehicleType)
assert.equal(
manualVehicleType({ ...row('1'), vehicleId: '__pending_vehicle__', vehicleType }),
'pending'
)
}
for (const vehicleType of [undefined, null, '', 'legacy', 'SELF']) {
assert.equal(manualVehicleType({ vehicleId: 'v1', vehicleType }), 'unknown')
}
const misleadingPlate = { ...row('1'), licensePlate: '待派车' }
const otherProduct = { ...row('1'), productName: '盐酸' }
assert.equal(manualVehicleType(misleadingPlate), 'unknown')
assert.equal(manualVehicleType(otherProduct), 'unknown')
})