| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- 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)
- })
|