2026-10-09 22:05:51 +08:00
import { createPinia } from 'pinia'
2026-10-03 04:35:55 +08:00
import ElementPlus , { ElFormItem , ElDatePicker , ElSelect , ElCheckbox } from 'element-plus'
import AppDialog from '@/shared/components/AppDialog.vue'
2026-08-21 11:20:09 +08:00
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { enableAutoUnmount , flushPromises , mount } from '@vue/test-utils'
import { afterAll , afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
import QueryToolbar , { type QueryModel } from '@/shared/components/QueryToolbar.vue'
2026-09-30 14:01:58 +08:00
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
2026-10-03 04:35:55 +08:00
import { cancelDispatch , getDispatchBolPdf , getDispatchExternalFreight , getDispatchFeeCoverage , listDispatches , removeDispatchItem , transferDispatchToDelivery , updateDispatch } from '../api/dispatches'
import DeliveryFeeCoveragePanel from '../components/DeliveryFeeCoveragePanel.vue'
2026-08-21 11:20:09 +08:00
import DispatchesView from './DispatchesView.vue'
2026-10-09 17:36:32 +08:00
vi . mock ( '@/modules/warehouses' , () => ({
listAllPlatformWarehouses : vi.fn ( async ( query : { keyword : string }) => [{ id : query.keyword , warehouseCode : query.keyword , warehouseType : 'FBA' }]),
getPlatformWarehouse : vi.fn ( async ( id : string ) => ({ warehouse : { warehouseCode : id , warehouseType : id === '321' ? 'OVERSEAS' : 'FBA' }, addresses : [{ addressType : 'OPERATING' , addressLine1 : '100 Example Street' , city : 'City' , stateProvince : 'CA' , countryCode : 'US' }], capabilities : [] })),
}))
2026-08-21 11:20:09 +08:00
2026-10-08 14:11:54 +08:00
const push = vi . hoisted (() => vi . fn ())
2026-10-15 17:11:03 +08:00
vi . mock ( 'vue-router' , () => ({ useRouter : () => ({ push }), useRoute : () => ({ query : {} }) }))
2026-09-14 20:21:02 +08:00
const canPermission = vi . hoisted (() => vi . fn < ( permissionCode : string ) => boolean > (() => true ))
2026-09-01 14:23:53 +08:00
vi . mock ( '@/core/access/accessPreview' , () => ({ useAccessPreviewStore : () => ({ can : canPermission }) }))
2026-10-03 04:35:55 +08:00
vi . mock ( '../api/dispatches' , () => ({ listDispatches : vi.fn (), removeDispatchItem : vi.fn (), getDispatchBolPdf : vi.fn (), getDispatchExternalFreight : vi.fn (), getDispatchFeeCoverage : vi.fn (), updateDispatch : vi.fn (), cancelDispatch : vi.fn (), transferDispatchToDelivery : vi.fn () }))
2026-08-21 11:20:09 +08:00
class ResizeObserverStub implements ResizeObserver {
observe() { }
unobserve() { }
disconnect() { }
}
function memoryStorage () : Storage {
const values = new Map < string , string >()
return {
get length() { return values . size },
clear : () => values . clear (),
getItem : ( key ) => values . get ( key ) ?? null ,
key : ( index ) => [... values . keys ()][ index ] ?? null ,
removeItem : ( key ) => values . delete ( key ),
setItem : ( key , value ) => values . set ( key , value ),
}
}
enableAutoUnmount ( afterEach )
afterAll (() => vi . unstubAllGlobals ())
describe ( 'DispatchesView' , () => {
beforeEach (() => {
vi . stubGlobal ( 'ResizeObserver' , ResizeObserverStub )
vi . stubGlobal ( 'localStorage' , memoryStorage ())
vi . mocked ( listDispatches ). mockReset (). mockResolvedValue ({
records : [{
2026-10-16 15:41:56 +08:00
id : '190000000000000099' , dispatchNo : 'PC-1' , appointmentBatches : [], vehiclePlate : 'CA-100' , driverName : '张师傅' ,
2026-10-09 17:36:32 +08:00
driverPhone : '555-0100' , carrierName : 'Carrier A' , orderNos : 'LM-1,LM-2' , route : 'ONT8,LAX9' ,
overseasWarehouseId : '321' , overseasWarehouseName : '洛杉矶仓' , departAt : '2026-08-20T09:00:00' , fbaAppointmentNo : 'FBA-1' ,
2026-08-21 11:20:09 +08:00
fbaAppointmentStatus : 'CONFIRMED' , vehicleAppointmentStatus : 'BOOKED' , estimatedArrivalAt : '2026-08-20T18:00:00' ,
status : 'PENDING_DEPARTURE' , palletCount : 12 , packageCount : 80 , createdAt : '2026-08-19T16:00:00' ,
}],
page : 1 , size : 20 , total : 1 , pages : 1 ,
})
2026-09-01 14:23:53 +08:00
vi . mocked ( updateDispatch ). mockReset ()
2026-09-11 10:09:14 +08:00
vi . mocked ( getDispatchBolPdf ). mockReset (). mockResolvedValue ( new Blob ([ '%PDF-test' ], { type : 'application/pdf' }))
2026-09-13 21:10:05 +08:00
vi . mocked ( removeDispatchItem ). mockReset ()
2026-09-01 14:23:53 +08:00
vi . mocked ( cancelDispatch ). mockReset ()
vi . mocked ( transferDispatchToDelivery ). mockReset ()
2026-10-03 04:35:55 +08:00
vi . mocked ( getDispatchExternalFreight ). mockReset (). mockResolvedValue ({ snapshotHash : 'own-hash' , requiresDeliveryFee : true , quotes : [] })
vi . mocked ( getDispatchFeeCoverage ). mockReset (). mockResolvedValue ({ dispatchNo : 'PC-1' , sourceComplete : true , blockReason : null , cargoHash : 'cargo-hash' , cargo : [], feeSources : [] })
2026-09-01 14:23:53 +08:00
canPermission . mockReset (). mockReturnValue ( true )
2026-08-21 11:20:09 +08:00
})
2026-09-14 20:21:02 +08:00
2026-10-08 14:11:54 +08:00
it ( 'queries stages on the server and discards an older response after switching' , async () => {
let resolveOld !: ( value : Awaited < ReturnType < typeof listDispatches >>) => void
vi . mocked ( listDispatches ). mockImplementationOnce (() => new Promise ( resolve => { resolveOld = resolve }))
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-10-08 14:11:54 +08:00
await wrapper . get ( '[role="tablist"]' ). findAll ( 'button' )[ 2 ] ! . trigger ( 'click' )
await flushPromises ()
expect ( listDispatches ). toHaveBeenLastCalledWith ( expect . objectContaining ({ stage : 'LOADING' , page : 1 , status : undefined }))
expect ( wrapper . text ()). toContain ( 'PC-1' )
resolveOld ({ records : [], total : 0 , page : 1 , size : 10 , pages : 0 })
await flushPromises ()
expect ( wrapper . text ()). toContain ( 'PC-1' )
await wrapper . get ( '[role="tablist"]' ). findAll ( 'button' )[ 3 ] ! . trigger ( 'click' )
await flushPromises ()
expect ( listDispatches ). toHaveBeenLastCalledWith ( expect . objectContaining ({ stage : 'AFTER_LOADING' , page : 1 }))
})
2026-10-09 17:36:32 +08:00
it ( 'shows the shared route map only in the after-loading vehicle section without another query' , async () => {
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { attachTo : document.body , global : { plugins : [ createPinia (), ElementPlus ] } })
2026-10-09 17:36:32 +08:00
await flushPromises ()
expect ( wrapper . text ()). not . toContain ( '查看路线' )
await wrapper . get ( '[aria-label="排车执行阶段"]' ). findAll ( 'button' )[ 3 ] ! . trigger ( 'click' )
await flushPromises ()
const requests = vi . mocked ( listDispatches ). mock . calls . length
await wrapper . findAll ( 'button' ). find ( button => button . text () === '查看路线' ) ! . trigger ( 'click' )
await flushPromises ()
expect ( wrapper . findComponent ({ name : 'TransportRouteDialog' }). props ( 'modelValue' )). toBe ( true )
expect ( wrapper . findComponent ({ name : 'TransportRouteDialog' }). props ( 'origin' )). toEqual ({ kind : 'warehouse' , label : '洛杉矶仓' , warehouseId : '321' })
expect ( wrapper . findComponent ({ name : 'TransportRouteDialog' }). props ( 'destinations' )). toEqual ([ 'ONT8' , 'LAX9' ]. map ( code => ({ kind : 'warehouse' , label : code , warehouseCode : code })))
expect ( listDispatches ). toHaveBeenCalledTimes ( requests )
await wrapper . get ( '[aria-label="排车执行阶段"]' ). findAll ( 'button' )[ 1 ] ! . trigger ( 'click' )
await flushPromises ()
await wrapper . get ( '[aria-label="排车业务分区"]' ). findAll ( 'button' )[ 1 ] ! . trigger ( 'click' )
expect ( wrapper . findAll ( 'button' ). some ( button => button . text () === '查看路线' )). toBe ( false )
})
2026-10-08 14:11:54 +08:00
it ( 'shows review facts and opens the matching trip section with read permission' , async () => {
const result = await listDispatches ({ page : 1 , size : 10 })
result . records [ 0 ] ! . workflow = { tripNo : 'TR-A' , dispatchReview : { status : 'REJECTED' , reason : '请核对货量' , reviewedAt : null }, loadingReview : { status : 'PENDING' , reason : null , reviewedAt : null } }
vi . mocked ( listDispatches ). mockClear (). mockResolvedValue ( result )
canPermission . mockImplementation ( code => code === 'platform:fleet:trip:list' )
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-10-08 14:11:54 +08:00
await flushPromises ()
const sections = wrapper . get ( '[aria-label="排车业务分区"]' )
2026-10-09 17:36:32 +08:00
expect ( sections . findAll ( 'button' ). map ( button => button . text ())). toEqual ([ '车辆信息' ])
await wrapper . get ( '[aria-label="排车执行阶段"]' ). findAll ( 'button' )[ 1 ] ! . trigger ( 'click' )
await flushPromises ()
2026-10-08 14:11:54 +08:00
expect ( sections . findAll ( 'button' ). map ( button => button . text ())). toEqual ([ '车辆信息' , '调度审核' , '理货员审核' ])
expect ( wrapper . findAll ( '[role="tablist"]' )). toHaveLength ( 2 )
expect ( wrapper . text ()). not . toContain ( '请核对货量' )
await sections . findAll ( 'button' )[ 1 ] ! . trigger ( 'click' )
await flushPromises ()
expect ( wrapper . text ()). toContain ( '请核对货量' )
expect ( wrapper . findAll ( 'button' ). some ( button => button . text () === '打印BOL' )). toBe ( false )
await wrapper . findAll ( 'button' ). find ( button => button . text () === '查看审核' ) ! . trigger ( 'click' )
expect ( push ). toHaveBeenLastCalledWith ({ name : 'overseas-master-data-trip-detail' , params : { tripNo : 'TR-A' }, query : { tab : 'overview' } })
await sections . findAll ( 'button' )[ 2 ] ! . trigger ( 'click' )
await flushPromises ()
expect ( wrapper . text ()). not . toContain ( '请核对货量' )
expect ( wrapper . text ()). toContain ( '待审核' )
const links = wrapper . findAll ( 'button' ). filter ( button => button . text () === '查看审核' )
await links [ 0 ] ! . trigger ( 'click' )
expect ( push ). toHaveBeenLastCalledWith ({ name : 'overseas-master-data-trip-detail' , params : { tripNo : 'TR-A' }, query : { tab : 'loading' } })
expect ( wrapper . text ()). not . toContain ( '前往审核' )
2026-10-09 17:36:32 +08:00
expect ( listDispatches ). toHaveBeenCalledTimes ( 2 )
})
it . each ([ 0 , 2 , 3 , 4 ]. flatMap ( stageIndex => [ 1 , 2 ]. map ( sectionIndex => [ stageIndex , sectionIndex ])))( 'leaving a review section for stage %s resets section %s to vehicle information' , async ( stageIndex , sectionIndex ) => {
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-10-09 17:36:32 +08:00
await flushPromises ()
const stages = wrapper . get ( '[aria-label="排车执行阶段"]' ). findAll ( 'button' )
const sections = wrapper . get ( '[aria-label="排车业务分区"]' )
await stages [ 1 ] ! . trigger ( 'click' )
await flushPromises ()
await sections . findAll ( 'button' )[ sectionIndex ] ! . trigger ( 'click' )
expect ( wrapper . findAll ( 'button' ). some ( button => button . text () === '打印BOL' )). toBe ( false )
await stages [ stageIndex ] ! . trigger ( 'click' )
await flushPromises ()
expect ( sections . findAll ( 'button' ). map ( button => button . text ())). toEqual ([ '车辆信息' ])
expect ( sections . get ( 'button' ). attributes ( 'aria-selected' )). toBe ( 'true' )
expect ( wrapper . findAll ( 'button' ). some ( button => button . text () === '打印BOL' )). toBe ( true )
expect ( wrapper . findAll ( 'button' ). some ( button => [ '前往审核' , '查看审核' ]. includes ( button . text ()))). toBe ( false )
await stages [ 1 ] ! . trigger ( 'click' )
await flushPromises ()
expect ( sections . findAll ( 'button' ). map ( button => button . text ())). toEqual ([ '车辆信息' , '调度审核' , '理货员审核' ])
expect ( sections . get ( 'button' ). attributes ( 'aria-selected' )). toBe ( 'true' )
2026-10-08 14:11:54 +08:00
})
2026-09-14 20:21:02 +08:00
it . each ([
[[], [ '查看' , '打印BOL' ], false ],
[[ 'edit' ], [ '查看' , '打印BOL' , '编辑' ], false ],
[[ 'cancel' ], [ '查看' , '打印BOL' , '取消' ], false ],
[[ 'transfer-delivery' ], [ '查看' , '打印BOL' , '转派送' ], false ],
2026-09-30 14:01:58 +08:00
[[ 'edit' , 'transfer-delivery' ], [ '查看' , '打印BOL' , '转派送' , '更多' ], true ],
[[ 'edit' , 'cancel' ], [ '查看' , '打印BOL' , '编辑' , '更多' ], true ],
[[ 'edit' , 'cancel' , 'transfer-delivery' ], [ '查看' , '打印BOL' , '转派送' , '更多' ], true ],
2026-09-14 20:21:02 +08:00
])( 'uses the row action threshold for permissions %j' , async ( allowed , labels , overflow ) => {
canPermission . mockImplementation ( code => allowed . some ( action => code === 'platform:overseas-warehouse:dispatch:' + action ))
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-09-14 20:21:02 +08:00
await flushPromises ()
const actions = wrapper . get ( '.el-table__body-wrapper .density-actions' )
expect ( actions . findAll ( 'button' ). map ( button => button . text (). trim ())). toEqual ( labels )
expect ( actions . find ( '.el-dropdown' ). exists ()). toBe ( overflow )
})
2026-09-30 14:01:58 +08:00
it ( 'keeps the integrated header and filters usable through failure and retry' , async () => {
vi . mocked ( listDispatches ). mockRejectedValueOnce ( new Error ( '网络错误' ))
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-09-30 14:01:58 +08:00
await flushPromises ()
const card = wrapper . get ( '.data-table--integrated' )
expect ( card . get ( '.data-table__toolbar h1' ). text ()). toBe ( '排车列表' )
expect ( card . get ( '.data-table__toolbar button[aria-label="列设置"]' ). text ()). toBe ( '字段设置' )
expect ( card . get ( '.data-table__filters' ). findComponent ( QueryToolbar ). exists ()). toBe ( true )
expect ( card . find ( '.el-table' ). exists ()). toBe ( false )
expect ( card . find ( '.data-table__pagination' ). exists ()). toBe ( false )
wrapper . findComponent ( LoadErrorState ). vm . $emit ( 'retry' )
await flushPromises ()
expect ( card . find ( '.data-table__state' ). exists ()). toBe ( false )
expect ( card . get ( '.el-table' ). text ()). toContain ( 'PC-1' )
expect ( card . get ( '.data-table__pagination' ). text ()). toContain ( '共 1 条排车记录' )
})
2026-08-21 11:20:09 +08:00
it ( 'loads immediately and sends only supported page filters' , async () => {
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-08-21 11:20:09 +08:00
await flushPromises ()
2026-08-22 12:24:15 +08:00
expect ( listDispatches ). toHaveBeenCalledWith ({ page : 1 , size : 10 , dispatchNo : undefined , vehiclePlate : undefined , status : undefined , fbaAppointmentNo : undefined })
2026-08-21 11:20:09 +08:00
const model : QueryModel = {
dispatchNo : 'PC-1' , vehiclePlate : 'CA-100' ,
status : 'PENDING_DEPARTURE' , fbaAppointmentNo : 'FBA-1' ,
}
wrapper . findComponent ( QueryToolbar ). vm . $emit ( 'search' , model )
await flushPromises ()
expect ( listDispatches ). toHaveBeenCalledWith ({
2026-08-22 12:24:15 +08:00
page : 1 , size : 10 , dispatchNo : 'PC-1' ,
2026-08-21 11:20:09 +08:00
vehiclePlate : 'CA-100' , status : 'PENDING_DEPARTURE' , fbaAppointmentNo : 'FBA-1' ,
})
expect ( wrapper . text ()). toContain ( 'PC-1' )
expect ( wrapper . text ()). toContain ( '待发车' )
2026-09-03 17:17:11 +08:00
expect ( wrapper . text ()). toContain ( 'FBA-1 · 已确认' )
expect ( wrapper . text ()). toContain ( '已预约' )
2026-09-30 14:01:58 +08:00
expect ( wrapper . get ( '.data-table' ). classes ()). not . toContain ( 'admin-text-actions' )
expect ( wrapper . get ( '.query-toolbar' ). classes ()). not . toContain ( 'admin-text-actions' )
expect ( wrapper . get ( '.data-table__toolbar button[aria-label="列设置"]' ). find ( 'svg' ). exists ()). toBe ( true )
expect ( wrapper . get ( '.query-toolbar__actions .el-button--primary' ). text ()). toBe ( '查询' )
expect ( wrapper . get ( 'button[aria-label="上一页"]' ). find ( 'svg' ). exists ()). toBe ( true )
expect ( wrapper . findAll ( '.query-toolbar__actions button svg' )). toHaveLength ( 3 )
expect ( wrapper . get ( '.el-table__body-wrapper .row-actions' ). classes ()). toContain ( 'admin-text-actions' )
2026-08-21 11:20:09 +08:00
})
it ( 'keeps long dispatch identifiers as strings' , async () => {
vi . mocked ( listDispatches ). mockResolvedValueOnce ({
2026-10-16 15:41:56 +08:00
records : [{ id : '190000000000000099' , dispatchNo : '190000000000000099' , appointmentBatches : [] }],
2026-08-21 11:20:09 +08:00
page : 1 , size : 20 , total : 1 , pages : 1 ,
})
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-08-21 11:20:09 +08:00
await flushPromises ()
expect ( wrapper . html ()). toContain ( '190000000000000099' )
})
2026-09-11 10:09:14 +08:00
it ( 'opens the server-generated BOL PDF with one request' , async () => {
const pdfWindow = { location : { href : '' }, close : vi.fn () }
vi . spyOn ( window , 'open' ). mockReturnValue ( pdfWindow as unknown as Window )
vi . stubGlobal ( 'URL' , { createObjectURL : vi.fn (() => 'blob:dispatch-bol' ), revokeObjectURL : vi.fn () })
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-09-10 21:22:06 +08:00
await flushPromises ()
2026-09-11 10:09:14 +08:00
expect ( getDispatchBolPdf ). not . toHaveBeenCalled ()
2026-09-10 21:22:06 +08:00
await wrapper . findAll ( '.el-table__body-wrapper button' ). find (( button ) => button . text (). trim () === '打印BOL' ) ! . trigger ( 'click' )
await flushPromises ()
2026-09-11 10:09:14 +08:00
expect ( getDispatchBolPdf ). toHaveBeenCalledOnce ()
expect ( getDispatchBolPdf ). toHaveBeenCalledWith ( 'PC-1' )
expect ( pdfWindow . location . href ). toBe ( 'blob:dispatch-bol' )
2026-09-10 21:22:06 +08:00
})
2026-08-21 11:20:09 +08:00
it ( 'documents the backend feature flag required by a missing endpoint' , () => {
const source = readFileSync ( resolve ( process . cwd (), 'src/modules/overseas-warehouse/pages/DispatchesView.vue' ), 'utf8' )
expect ( source ). toContain ( '请确认海外尾程排车接口已发布' )
})
2026-09-01 14:23:53 +08:00
2026-10-03 04:35:55 +08:00
it ( 'loads external quotes only when opened and submits their hash without a duplicate delivery charge' , async () => {
vi . mocked ( getDispatchExternalFreight ). mockResolvedValue ({ snapshotHash : 'external-hash' , requiresDeliveryFee : false , quotes : [
{ bookingNo : 'FB-1' , recordVersion : '4' , incomingNo : 'WT-1' , inboundCode : 'IN-1' , customerName : '客户甲' , destinationKey : 'FBA:ONT8' , currency : 'CNY' , amount : 60 , quantity : 2 , quantityUnit : '托位' , unitPrice : 30 },
] })
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-10-03 04:35:55 +08:00
await flushPromises ()
expect ( getDispatchExternalFreight ). not . toHaveBeenCalled ()
await wrapper . findAll ( 'button' ). find ( button => button . text () === '转派送' ) ! . trigger ( 'click' )
await flushPromises ()
expect ( getDispatchExternalFreight ). toHaveBeenCalledOnce ()
expect ( getDispatchExternalFreight ). toHaveBeenCalledWith ( 'PC-1' )
const dialog = wrapper . findAllComponents ( AppDialog ). find ( item => item . props ( 'title' ) === '转派送' ) !
expect ( dialog . text ()). toContain ( 'FB-1' )
expect ( dialog . text ()). not . toContain ( '请输入派送费用' )
dialog . findComponent ( ElDatePicker ). vm . $emit ( 'update:modelValue' , '2026-10-03T12:00:00' )
for ( const [ label , value ] of [[ '派送方式' , 'TRUCK_PALLET' ], [ '提货方式' , 'WAREHOUSE_PICKUP' ], [ '装车类型' , 'PALLET' ]]) {
dialog . findAllComponents ( ElFormItem ). find ( item => item . props ( 'label' ) === label ) ! . findComponent ( ElSelect ). vm . $emit ( 'update:modelValue' , value )
}
await flushPromises ()
dialog . vm . $emit ( 'confirm' )
await flushPromises ()
expect ( transferDispatchToDelivery ). toHaveBeenCalledWith ( 'PC-1' , expect . objectContaining ({ fees : [], externalFreightHash : 'external-hash' }))
})
it ( 'confirms an original delivery fee without submitting a new receivable' , async () => {
vi . mocked ( getDispatchFeeCoverage ). mockResolvedValue ({ dispatchNo : 'PC-1' , sourceComplete : true , blockReason : null , cargoHash : 'actual-cargo' ,
cargo : [{ dispatchItemId : '201' , warehousePalletId : '501' , palletNo : 'PLT-1' , orderId : '401' , sourceRegion : 'FIRSTLEG' , sourceInboundId : '11' , inboundCode : 'IN-11' , packages : 20 , dispatchInboundCode : 'IN-11' }],
feeSources : [{ financeFeeId : '711' , sourceFeeId : '811' , sourceType : 'INBOUND_FEE' , sourceVersion : 3 , customerId : '101' , sourceInboundId : '11' , inboundCode : 'IN-11' , feeItemCode : 'FREIGHT' , feeItemName : '原运费' , currency : 'CNY' , amount : 100 , sourceHash : 'original-fee-hash' , status : 'AVAILABLE' }],
})
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-10-03 04:35:55 +08:00
await flushPromises ()
expect ( getDispatchFeeCoverage ). not . toHaveBeenCalled ()
await wrapper . findAll ( 'button' ). find ( button => button . text () === '转派送' ) ! . trigger ( 'click' )
await flushPromises ()
expect ( getDispatchFeeCoverage ). toHaveBeenCalledTimes ( 1 )
const dialog = wrapper . findAllComponents ( AppDialog ). find ( item => item . props ( 'title' ) === '转派送' ) !
await dialog . findComponent ( DeliveryFeeCoveragePanel ). findAllComponents ( ElCheckbox ). find ( row => row . text () === '已含派送' ) ! . find ( 'input' ). setValue ( true )
dialog . findComponent ( ElDatePicker ). vm . $emit ( 'update:modelValue' , '2026-10-03T12:00:00' )
for ( const [ label , value ] of [[ '派送方式' , 'TRUCK_PALLET' ], [ '提货方式' , 'WAREHOUSE_PICKUP' ], [ '装车类型' , 'PALLET' ]]) {
dialog . findAllComponents ( ElFormItem ). find ( item => item . props ( 'label' ) === label ) ! . findComponent ( ElSelect ). vm . $emit ( 'update:modelValue' , value )
}
await flushPromises ()
expect ( dialog . text ()). toContain ( '本次全部货物已覆盖:派送' )
dialog . vm . $emit ( 'confirm' )
await flushPromises ()
expect ( transferDispatchToDelivery ). toHaveBeenCalledWith ( 'PC-1' , expect . objectContaining ({ fees : [], feeCoverageCargoHash : 'actual-cargo' , feeCoverage : [{ financeFeeId : '711' , sourceVersion : 3 , sourceHash : 'original-fee-hash' , services : [ 'DELIVERY_FEE' ] }] }))
})
2026-09-01 14:23:53 +08:00
it ( 'gates edit, cancel and transfer with independent permissions and service statuses' , async () => {
const source = readFileSync ( resolve ( process . cwd (), 'src/modules/overseas-warehouse/pages/DispatchesView.vue' ), 'utf8' )
expect ( source ). toContain ( 'permission.dispatchEdit' )
expect ( source ). toContain ( 'permission.dispatchCancel' )
expect ( source ). toContain ( 'permission.dispatchTransferDelivery' )
2026-09-18 14:58:40 +08:00
expect ( source ). toContain ( "import RowActions, { type RowActionItem } from '@/shared/components/RowActions.vue'" )
expect ( source ). toContain ( "{ key: 'transfer', label: '转派送', priority: 250" )
2026-09-13 21:10:05 +08:00
expect ( source ). toMatch ( /function isPendingDeparture\(row: DispatchListItem\)\s*{\s*return row\.statusCode === 'PENDING_DEPARTURE'/ )
expect ( source ). toMatch ( /access\.can\(permission\.dispatchEdit\)\s*&&\s*isPendingDeparture\(row\)/ )
expect ( source ). toMatch ( /access\.can\(permission\.dispatchTransferDelivery\)\s*&&\s*\['PENDING_DEPARTURE', 'PICKING', 'READY_TO_DEPART'\]\.includes\(row\.statusCode\)/ )
expect ( source ). toContain ( '未生成出库单前可移除托盘' )
expect ( source ). toContain ( 'removeDispatchItem' )
2026-09-15 12:08:07 +08:00
expect ( source ). not . toContain ( '托盘箱唛确认' )
expect ( source ). not . toContain ( 'palletCartonMarks' )
2026-09-15 14:30:50 +08:00
expect ( source ). toContain ( 'v-model="editForm.route" readonly' )
expect ( source ). not . toContain ( 'route: editForm.route.trim()' )
2026-09-15 12:17:20 +08:00
expect ( source ). toContain ( 'dispatchVehicleSizeOptions' )
2026-09-01 14:23:53 +08:00
expect ( source ). not . toContain ( 'departDispatch' )
canPermission . mockReturnValue ( false )
2026-10-09 22:05:51 +08:00
const wrapper = mount ( DispatchesView , { global : { plugins : [ createPinia (), ElementPlus ] } })
2026-09-01 14:23:53 +08:00
await flushPromises ()
expect ( wrapper . text ()). not . toContain ( '转派送' )
})
2026-08-21 11:20:09 +08:00
})