import assert from 'node:assert/strict' import test from 'node:test' import { loadCalendarPages, manualCalendarDays, manualCopyText, 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('现场复制隐藏起止地和备注,单据类型放在每条记录末尾的中文括号中', () => { 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车', ` ·皖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车', ' ·皖H10291 98% 工业硫酸 32吨(出库计划)', '精制硫酸2车', ' ·皖H57837 精制硫酸 32吨(出库计划)', ' ·皖HA3190 精制硫酸 32吨(出库计划)', '50% 工业硫酸1车', ' ·皖HA3690 50% 工业硫酸 28吨(出库计划)' ].join('\n') ) assert.deepEqual( records.map((r) => r.id), ['1', '2', '4', '3'] ) }) 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') ) })