Files
shanxia2.0/web-admin/scripts/pl-dispatch-calendar.test.ts
T

483 lines
15 KiB
TypeScript

import assert from 'node:assert/strict'
import test from 'node:test'
import type { Plan } from '../src/api/pl'
import {
calendarColumnCount,
countPlanTrips,
monthCells,
shiftMonth,
toCalendarTrips,
filterTrips,
filterVehicleScope,
groupVehicles,
summarizeTrips,
loadMonthPlans
} from '../src/views/pl/dispatch-calendar/model'
const plan = (value: Partial<Plan>): Plan =>
({ id: 'p1', planDate: '2026-10-24', followStatus: 'pending', ...value }) as Plan
const product = (id: string, name: string) =>
({ productId: id, productName: name }) as NonNullable<Plan['products']>[number]
test('每日默认仅自有车辆,全部包含承运商和未知车辆,不按车牌猜测', () => {
const rows = toCalendarTrips(
[
plan({ id: 'own', calendarVehicleType: 'self' }),
plan({ id: 'carrier', calendarVehicleType: 'carrier', licensePlate: '皖HA3690' }),
plan({ id: 'unknown', calendarVehicleType: 'unknown' }),
plan({
id: 'snapshot',
resourceSnapshot: {
vehicleId: 'snapshot-vehicle',
vehicleType: 'self'
} as Plan['resourceSnapshot']
})
],
'2026-10'
)
assert.deepEqual(
filterVehicleScope(rows).map((row) => row.planId),
['own', 'snapshot']
)
assert.equal(filterVehicleScope(rows, 'all').length, 4)
})
test('完整周历、跨年和闰月', () => {
assert.equal(monthCells('2026-10').length, 35)
assert.equal(monthCells('2026-10')[0], '2026-09-28')
assert.equal(monthCells('2026-10').at(-1), '2026-11-01')
assert.equal(monthCells('2026-11').length, 42)
assert.ok(monthCells('2024-02').includes('2024-02-29'))
assert.equal(shiftMonth('2026-12', 1), '2027-01')
assert.equal(shiftMonth('2026-01', -1), '2025-12')
})
test('按计划日期、真实车辆和多站路线转换,不混入草稿/作废/无日期', () => {
const rows = toCalendarTrips(
[
plan({
licensePlate: '皖HA3690',
vehicleId: 'v1',
driverId: 'd1',
driverName: '测试司机',
load: { customerName: '山峡库' },
deliveries: [
{ seq: 2, customerName: '乙' },
{ seq: 1, customerName: '甲' }
],
products: [product('a', '98酸')]
}),
plan({ id: 'draft', followStatus: 'draft' }),
plan({ id: 'cancel', followStatus: 'cancelled' }),
plan({ id: 'null', planDate: null, createDate: '2026-10-24' })
],
'2026-10'
)
assert.equal(rows.length, 2)
assert.equal(countPlanTrips(rows), 1)
assert.equal(rows[0].route, '山峡库 → 甲')
assert.equal(rows[1].route, '山峡库 → 乙')
assert.equal(rows[0].plate, '皖HA3690')
assert.equal(filterTrips(rows, 'v1').length, 2)
assert.equal(filterTrips(rows, 'other').length, 0)
})
test('多品种同车不增加总车次,同货品重复行去重', () => {
const rows = toCalendarTrips(
[
plan({ products: [product('a', '98酸'), product('a', '98酸'), product('b', 'cp酸')] }),
plan({ id: 'p2', products: [] })
],
'2026-10'
)
assert.equal(rows.length, 2)
assert.deepEqual(
summarizeTrips(rows).map((x) => [x.name, x.count]),
[
['98酸', 1],
['cp酸', 1],
['货品未注明', 1]
]
)
assert.equal(filterTrips(rows, '__missing__').length, 2)
})
test('资源快照回退车辆,货品别名优先展示', () => {
const rows = toCalendarTrips(
[
plan({
products: [{ ...product('a', '硫酸'), aliasName: '98酸' }],
resourceSnapshot: {
vehicleId: 'v2',
licensePlate: '皖H10291',
selectedDriverId: 'd2',
personnelList: [{ personnelId: 'd2', personnelName: '张师傅', role: 'driver' }]
} as Plan['resourceSnapshot']
})
],
'2026-10'
)
assert.equal(rows[0].plate, '皖H10291')
assert.equal(rows[0].products[0].name, '98酸')
assert.equal(filterTrips(rows, 'v2').length, 1)
})
test('整月超过一页时读全,使用计划日期边界', async () => {
const calls: any[] = []
const rows = await loadMonthPlans(async (query) => {
calls.push(query)
return {
total: 501,
list: Array.from({ length: query.page === 1 ? 500 : 1 }, (_, i) =>
plan({ id: String((query.page! - 1) * 500 + i) })
)
}
}, '2024-02')
assert.equal(rows?.length, 501)
assert.equal(calls.length, 2)
assert.equal(calls[0].planDateFrom, '2024-02-01')
assert.equal(calls[0].planDateTo, '2024-02-29')
assert.equal(calls[0].excludeFollowStatus, 'draft,cancelled')
assert.equal(calls[0].includeCustomerShortNames, 1)
})
test('翻月后停止旧请求;后续分页失败不返回部分月份', async () => {
assert.equal(
await loadMonthPlans(
async () => ({ total: 0, list: [] }),
'2026-10',
() => false
),
null
)
await assert.rejects(
loadMonthPlans(async (query) => {
if (query.page === 2) throw new Error('分页失败')
return { total: 501, list: Array.from({ length: 500 }, (_, i) => plan({ id: String(i) })) }
}, '2026-10'),
/分页失败/
)
})
test('同货品标准名和别名混用仍只计一次,优先显示别名', () => {
const rows = toCalendarTrips(
[
plan({
products: [
product('acid', '98%工业硫酸'),
{ ...product('acid', '98%工业硫酸'), aliasName: '98酸' }
]
})
],
'2026-10'
)
assert.equal(rows[0].products.length, 1)
assert.equal(rows[0].products[0].name, '98酸')
assert.equal(summarizeTrips(rows)[0].count, 1)
})
test('侧栏派车日历目录紧跟库存管理,页面具有独立权限码', async () => {
const { plRoutes } = await import('../src/router/modules/pl')
const { toNonLeafSidebarTree } = await import('../src/utils/router/leaf-group')
const transport = plRoutes.find((route) => route.path === '/pl')!
const sidebar = toNonLeafSidebarTree([transport])[0].children!
const stock = sidebar.findIndex((route) => route.name === 'PlStockModule')
assert.equal(sidebar[stock + 1]?.name, 'PlCalendarModule')
const group = transport.children!.find((route) => route.name === 'PlCalendarModule')!
assert.equal(group.children![0].meta.menuCode, 'pl:dispatch-calendar:list')
})
test('日历优先使用客户简称路线,没有时回退现有全称', () => {
const rows = toCalendarTrips(
[
plan({
id: 'short',
calendarRoute: '山峡 → 永恒',
supplierName: '山峡化工有限公司',
customerName: '永恒有限公司'
}),
plan({ id: 'full', supplierName: '山峡化工有限公司', customerName: '永恒有限公司' })
],
'2026-10'
)
assert.equal(rows.find((row) => row.id === 'short')?.route, '山峡 → 永恒')
assert.equal(rows.find((row) => row.id === 'full')?.route, '山峡化工有限公司 → 永恒有限公司')
})
test('卡片目标列数不超过三列,由容器宽度决定实际列数', () => {
assert.deepEqual([0, 1, 2, 5, 6, 11].map(calendarColumnCount), [1, 1, 2, 2, 3, 3])
})
test('车辆卡片按车次数从多到少排序,同车内任务顺序保持不变', () => {
const rows = toCalendarTrips(
[
plan({ id: '1', planNo: '1', vehicleId: 'a', licensePlate: '皖A1' }),
plan({ id: '2', planNo: '2', vehicleId: 'b', licensePlate: '皖A2' }),
plan({ id: '3', planNo: '3', vehicleId: 'c', licensePlate: '皖A3' }),
plan({ id: '4', planNo: '4', vehicleId: 'b', licensePlate: '皖A2' }),
plan({ id: '5', planNo: '5', vehicleId: 'c', licensePlate: '皖A3' }),
plan({ id: '6', planNo: '6', vehicleId: 'b', licensePlate: '皖A2' })
],
'2026-10'
)
const groups = groupVehicles(rows)
assert.deepEqual(
groups.map((group) => group.key),
['b', 'c', 'a']
)
assert.deepEqual(
groups[0].trips.map((trip) => trip.id),
['2', '4', '6']
)
})
test('同车同日同货品同路线合并车次,汇总及原计划保留', () => {
const rows = toCalendarTrips(
[
plan({
id: '1',
planNo: 'PL1',
vehicleId: 'v1',
calendarRoute: '安徽金顺 → 山峡',
products: [product('acid', '98% 工业硫酸')]
}),
plan({
id: '2',
planNo: 'PL2',
vehicleId: 'v1',
calendarRoute: '安徽金顺 → 山峡',
products: [product('acid', '98% 工业硫酸')]
}),
plan({
id: '3',
planNo: 'PL3',
vehicleId: 'v1',
calendarRoute: '山峡 → 安徽金顺',
products: [product('acid', '98% 工业硫酸')]
}),
plan({
id: '4',
planNo: 'PL4',
vehicleId: 'v1',
calendarRoute: '安徽金顺 → 山峡',
products: [product('acid', '50% 工业硫酸')]
}),
plan({
id: '5',
planNo: 'PL5',
vehicleId: 'v2',
calendarRoute: '安徽金顺 → 山峡',
products: [product('acid', '98% 工业硫酸')]
})
],
'2026-10'
)
const groups = groupVehicles(rows)
assert.equal(groups[0].trips.length, 4)
assert.equal(groups[0].tasks.length, 3)
assert.deepEqual(
groups[0].tasks.map((task) => task.count),
[2, 1, 1]
)
assert.deepEqual(groups[0].tasks[0].planNos, ['PL1', 'PL2'])
assert.equal(groups[1].tasks[0].count, 1)
assert.equal(rows.length, 5)
assert.equal(
summarizeTrips(rows).reduce((sum, item) => sum + item.count, 0),
5
)
})
test('多货品顺序不同仍合并,货品组合或日期不同不合并', () => {
const rows = toCalendarTrips(
[
plan({ id: '1', vehicleId: 'v1', products: [product('a', '98酸'), product('b', '液碱')] }),
plan({ id: '2', vehicleId: 'v1', products: [product('b', '液碱'), product('a', '98酸')] }),
plan({ id: '3', vehicleId: 'v1', products: [product('a', '98酸')] }),
plan({
id: '4',
vehicleId: 'v1',
planDate: '2026-10-25',
products: [product('a', '98酸'), product('b', '液碱')]
})
],
'2026-10'
)
assert.deepEqual(
groupVehicles(rows)[0].tasks.map((task) => task.count),
[2, 1, 1]
)
})
test('路线连续重复客户只显示一次,保留跨站返回与原始送货段', () => {
const source = [
plan({ id: 'api', calendarRoute: '安庆山峡 → 安庆奇创 → 安庆奇创' }),
plan({
id: 'stops',
load: { customerName: '安庆山峡' },
deliveries: [
{ seq: 1, customerName: '安庆奇创' },
{ seq: 2, customerName: '安庆奇创' }
]
}),
plan({ id: 'return', calendarRoute: '安庆山峡 → 安庆奇创 → 安庆山峡' }),
plan({ id: 'alias', calendarRoute: '安庆山峡 → 山峡 → 安庆奇创' })
]
const rows = toCalendarTrips(source, '2026-10')
assert.equal(rows.find((row) => row.id === 'api')?.route, '山峡 → 安庆奇创')
assert.equal(rows.find((row) => row.planId === 'stops')?.route, '山峡 → 安庆奇创')
assert.equal(rows.find((row) => row.id === 'return')?.route, '山峡 → 安庆奇创 → 山峡')
assert.equal(rows.find((row) => row.id === 'alias')?.route, '山峡 → 安庆奇创')
assert.equal(source[1].deliveries?.length, 2)
assert.equal(rows.length, 5)
assert.equal(countPlanTrips(rows), 4)
})
test('一个计划三段送货先拆分,货品及简称按段对应,再合并相同段', () => {
const a = { ...product('a', '工业硫酸'), id: 'pp-a', aliasName: '98酸' }
const b = { ...product('b', '次氯酸钠'), id: 'pp-b' }
const rows = toCalendarTrips(
[
plan({
id: 'multi',
vehicleId: 'v1',
products: [a, b],
load: { customerName: '安庆山峡化工有限公司', calendarCustomerName: '安庆山峡' },
deliveries: [
{
id: 's3',
seq: 3,
customerName: '乙有限公司',
calendarCustomerName: '乙',
products: [{ ...b, planProductId: 'pp-b' }]
},
{
id: 's1',
seq: 1,
customerName: '甲有限公司',
calendarCustomerName: '甲',
products: [{ ...a, planProductId: 'pp-a' }]
},
{
id: 's2',
seq: 2,
customerName: '甲有限公司',
calendarCustomerName: '甲',
products: [{ ...a, planProductId: 'pp-a' }]
}
]
})
],
'2026-10'
)
assert.equal(rows.length, 3)
assert.equal(new Set(rows.map((row) => row.id)).size, 3)
assert.deepEqual(
rows.map((row) => row.route),
['山峡 → 甲', '山峡 → 甲', '山峡 → 乙']
)
assert.deepEqual(
rows.map((row) => row.products.map((item) => item.name)),
[['98酸'], ['98酸'], ['次氯酸钠']]
)
const group = groupVehicles(rows)[0]
assert.equal(group.tasks.length, 2)
assert.equal(group.tasks[0].count, 1)
assert.equal(group.tasks[0].segmentCount, 2)
assert.equal(countPlanTrips(rows), 1)
assert.deepEqual(
summarizeTrips(rows).map((item) => [item.name, item.count]),
[
['98酸', 1],
['次氯酸钠', 1]
]
)
})
test('多段缺失货品不套用整车货品,单段旧数据可回退;同名异址段不合并', () => {
const rows = toCalendarTrips(
[
plan({
id: 'multi',
products: [product('a', '98酸')],
deliveries: [
{ id: 's1', seq: 1, customerId: 'c1', customerName: '甲', address: '一厂', products: [] },
{ id: 's2', seq: 2, customerId: 'c1', customerName: '甲', address: '二厂', products: [] }
]
})
],
'2026-10'
)
assert.deepEqual(
rows.map((row) => row.products[0].name),
['货品未注明', '货品未注明']
)
assert.equal(groupVehicles(rows)[0].tasks.length, 2)
const single = toCalendarTrips(
[plan({ products: [product('a', '98酸')], deliveries: [{ customerName: '甲' }] })],
'2026-10'
)
assert.equal(single[0].products[0].name, '98酸')
})
test('装货段无客户ID时不同站点不能被共同供方覆盖合并', () => {
const rows = toCalendarTrips(
['一号装货点', '二号装货点'].map((name, index) =>
plan({
id: String(index),
supplierId: 'supplier',
vehicleId: 'v1',
load: { customerName: name },
deliveries: [
{ customerId: 'dest', customerName: '收货点', products: [product('a', '98酸')] }
]
})
),
'2026-10'
)
assert.equal(groupVehicles(rows)[0].tasks.length, 2)
})
test('同货品标准名和明确别名在送货段之间统一,规格不同仍保留', () => {
const rows = toCalendarTrips(
[
plan({
id: 'p',
deliveries: [
{ id: 's1', seq: 1, customerName: '甲', products: [product('a', '98%工业硫酸')] },
{
id: 's2',
seq: 2,
customerName: '甲',
products: [
{
...product('a', '98%工业硫酸'),
mobileProductMeta: { aliasName: '98酸', brandMode: '', brandId: '', brandName: '' }
}
]
}
]
})
],
'2026-10'
)
assert.deepEqual(
rows.map((row) => row.products[0].name),
['98酸', '98酸']
)
assert.equal(summarizeTrips(rows).length, 1)
assert.equal(groupVehicles(rows)[0].tasks.length, 1)
const distinct = toCalendarTrips(
[
plan({
deliveries: [
{ id: 's1', customerName: '甲', products: [{ ...product('a', '硫酸'), model: '50%' }] },
{ id: 's2', customerName: '甲', products: [{ ...product('a', '硫酸'), model: '98%' }] }
]
})
],
'2026-10'
)
assert.equal(groupVehicles(distinct)[0].tasks.length, 2)
})