2026-08-20 18:02:52 +08:00
import ElementPlus from 'element-plus'
2026-08-27 15:56:13 +08:00
import { createPinia } from 'pinia'
2026-08-20 18:02:52 +08:00
import { enableAutoUnmount , flushPromises , mount } from '@vue/test-utils'
import { afterAll , afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
import { listPlatformTenants } from '@/modules/iam'
2026-09-08 14:15:28 +08:00
import DataTable from '@/shared/components/DataTable.vue'
import { listInboundOrders } from '../api/inboundOrders'
2026-08-20 18:02:52 +08:00
import { getInboundPlan } from '../api/inboundPlans'
import { listInventoryLedger } from '../api/inventory'
2026-08-27 15:56:13 +08:00
import { getLastMileOrder } from '../api/lastMileOrders'
2026-08-20 18:02:52 +08:00
import InboundOrderDetailView from './InboundOrderDetailView.vue'
2026-09-08 14:15:28 +08:00
import InboundOrdersView from './InboundOrdersView.vue'
2026-08-20 18:02:52 +08:00
import InventoryTransactionsView from './InventoryTransactionsView.vue'
2026-08-27 15:56:13 +08:00
import LastMileOrderDetailView from './LastMileOrderDetailView.vue'
2026-08-20 18:02:52 +08:00
const routing = vi . hoisted (() => ({ push : vi.fn (), route : { params : {} as Record < string , string >, query : {} as Record < string , string > } }))
vi . mock ( 'vue-router' , () => ({ useRoute : () => routing . route , useRouter : () => ({ push : routing.push }) }))
vi . mock ( '@/modules/iam' , () => ({ listPlatformTenants : vi.fn () }))
2026-09-08 14:15:28 +08:00
vi . mock ( '../api/inboundOrders' , () => ({
listInboundOrders : vi.fn (), createFbaAppointment : vi.fn (), getFbaAppointment : vi.fn (),
updateFbaAppointment : vi.fn (), updateInboundPlan : vi.fn (),
}))
2026-08-20 18:02:52 +08:00
vi . mock ( '../api/inboundPlans' , () => ({ getInboundPlan : vi.fn () }))
vi . mock ( '../api/inventory' , () => ({ listInventoryLedger : vi.fn () }))
2026-08-27 15:56:13 +08:00
vi . mock ( '../api/lastMileOrders' , () => ({ getLastMileOrder : vi.fn (), createLastMileOrderFee : vi.fn () }))
2026-08-20 18:02:52 +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 ( 'overseas warehouse mounted real API pages' , () => {
beforeEach (() => {
vi . stubGlobal ( 'ResizeObserver' , ResizeObserverStub ); vi . stubGlobal ( 'localStorage' , memoryStorage ()); routing . push . mockReset (); routing . route . params = {}; routing . route . query = {}
vi . mocked ( listPlatformTenants ). mockReset (). mockResolvedValue ({ records : [{ id : '10001' , tenantName : '客户甲' , status : 'ACTIVE' } as never ], page : 1 , size : 200 , total : 1 , pages : 1 })
2026-09-08 14:15:28 +08:00
vi . mocked ( listInboundOrders ). mockReset (). mockResolvedValue ({ records : [], page : 1 , size : 10 , total : 0 , pages : 0 })
2026-09-18 14:58:40 +08:00
vi . mocked ( getInboundPlan ). mockReset (). mockResolvedValue ({ id : '9007199254740993' , planNo : 'IP-1' , customerId : '10001' , sourceOrderId : '9007199254740995' , sourceOrderNo : 'FL-WH-20260911-000001' , customerName : '客户甲' , orderType : 'ONE_CLICK_DROPSHIP' , overseasWarehouseCode : 'US-LAX-01' , overseasWarehouseName : '洛杉矶接柜仓' , status : 'PENDING_ASSIGNMENT' , lineCount : 1 , palletCount : 1 , totalGrossWeightKg : 30 , totalNetWeightKg : 22 , totalVolumeCbm : 2 , createdAt : '2026-09-11T10:00:00' , updatedAt : '2026-09-11T10:10:00' , remark : '页面验收' , lines : [{ id : '1' , orderId : '9007199254740995' , inboundCode : 'IN-1' , fbaWarehouseCode : 'ABE3' , destinationWarehouseCode : 'ABE3' , destinationWarehouseName : 'ABE3目的仓' , grossWeightKg : 30 , netWeightKg : 22 , volumeCbm : 2 , requiredPackageCount : 10 , plannedPalletCount : 1 }], pallets : [], allocations : [] })
2026-08-30 17:43:03 +08:00
vi . mocked ( listInventoryLedger ). mockReset (). mockResolvedValue ({ records : [{ id : '9007199254740997' , customerId : '10001' , customerName : '客户甲' , destinationWarehouseCode : 'LAX' , inboundCode : 'NYEXX2608300001' , inboundOrderNo : 'IP-1' , businessType : 'STOCK_IN' , beforePackages : 0 , deltaPackages : 10 , afterPackages : 10 , occurredAt : '2026-08-20T10:00:00' }], page : 1 , size : 20 , total : 1 , pages : 1 })
2026-09-11 16:17:21 +08:00
vi . mocked ( getLastMileOrder ). mockReset (). mockResolvedValue ({ order : { id : '9007199254740999' , customerId : '10001' , customerName : '华远贸易' , orderNo : 'LMO-260827-0001' , inboundCode : 'NYEXX2608280033' , containerNo : 'TSTU0000061' , containerNode : 'PICKUP_READY' , currentStatus : 'PENDING_ASSIGNMENT' }, cargo : [], statusHistory : [], fees : [], operationHistory : [] })
2026-08-20 18:02:52 +08:00
})
2026-09-08 14:15:28 +08:00
it ( 'keeps receiving warehouse and FBA warehouse independent in the inbound-plan list' , async () => {
vi . mocked ( listInboundOrders ). mockResolvedValue ({
records : [
2026-09-18 14:58:40 +08:00
{ lineId : '1' , planId : '11' , planNo : 'IP-1' , customerId : '10001' , orderId : '21' , inboundCode : 'IN-1' , overseasWarehouseCode : 'US-LAX-01' , overseasWarehouseName : '洛杉矶接柜仓' , warehouseId : '31' , warehouseCode : 'ABE3' , warehouseName : 'ABE3目的仓' , fbaWarehouseCode : 'ABE3' , grossWeightKg : 30 , netWeightKg : 22 },
2026-09-08 14:15:28 +08:00
{ lineId : '2' , planId : '12' , planNo : 'IP-2' , customerId : '10001' , orderId : '22' , inboundCode : 'IN-2' , overseasWarehouseCode : null , overseasWarehouseName : '不充整接柜仓' , warehouseId : '32' , warehouseCode : 'ONT8' , warehouseName : 'ONT8目的仓' , fbaWarehouseCode : 'ONT8' },
], page : 1 , size : 10 , total : 2 , pages : 1 ,
})
const wrapper = mount ( InboundOrdersView , { global : { plugins : [ createPinia (), ElementPlus ] } })
await flushPromises ()
2026-09-18 14:58:40 +08:00
const rows = wrapper . findComponent ( DataTable ). props ( 'data' ) as Array < { warehouse : string ; fbaWarehouse : string ; destinationWarehouseName : string ; grossWeight : number ; netWeight : number } >
expect ( rows [ 0 ]). toMatchObject ({ warehouse : 'US-LAX-01 · 洛杉矶接柜仓' , fbaWarehouse : 'ABE3' , destinationWarehouseName : 'ABE3目的仓' , grossWeight : 30 , netWeight : 22 })
2026-09-08 14:15:28 +08:00
expect ( rows [ 1 ]). toMatchObject ({ warehouse : '—' , fbaWarehouse : 'ONT8' , destinationWarehouseName : 'ONT8目的仓' })
})
2026-09-18 14:58:40 +08:00
it ( 'mounts inbound detail with business references and independent receiving and FBA warehouse references' , async () => { routing . route . params = { orderId : 'IP-1' }; routing . route . query = { customerId : '10001' }; const wrapper = mount ( InboundOrderDetailView , { global : { plugins : [ ElementPlus ] } }); await flushPromises (); expect ( getInboundPlan ). toHaveBeenCalledWith ( 'IP-1' , '10001' ); expect ( wrapper . text ()). toContain ( 'IP-1' ); expect ( wrapper . text ()). toContain ( 'IN-1' ); expect ( wrapper . text ()). not . toContain ( '9007199254740995' ); expect ( wrapper . text ()). toContain ( 'FL-WH-20260911-000001' ); expect ( wrapper . text ()). toContain ( '客户甲' ); expect ( wrapper . text ()). toContain ( '一件代发' ); expect ( wrapper . text ()). toContain ( '2026-09-11 10:00' ); expect ( wrapper . text ()). toContain ( '页面验收' ); expect ( wrapper . text ()). toContain ( 'US-LAX-01 · 洛杉矶接柜仓' ); expect ( wrapper . text ()). toContain ( 'ABE3' ); expect ( wrapper . text ()). toContain ( '货物毛重(kg)30' ); expect ( wrapper . text ()). toContain ( '货物净重(kg)22' ); expect ( wrapper . text ()). not . toContain ( 'ABE3目的仓' ) })
2026-09-11 16:11:31 +08:00
it ( 'shows the real pallet and owner plan for a cross-plan merged allocation' , async () => {
2026-09-18 14:58:40 +08:00
vi . mocked ( getInboundPlan ). mockResolvedValueOnce ({ id : '101' , planNo : 'IP-CURRENT' , customerId : '10001' , sourceOrderId : '1' , overseasWarehouseCode : 'US-LAX-01' , overseasWarehouseName : '洛杉矶接柜仓' , status : 'PICKING' , lineCount : 1 , palletCount : 1 , totalGrossWeightKg : 30 , totalNetWeightKg : 22 , totalVolumeCbm : 2 , lines : [{ id : '201' , orderId : '1' , inboundCode : 'EXXHK2609100014' , fbaWarehouseCode : 'ACY2' , requiredPackageCount : 150 , plannedPalletCount : 1 }], pallets : [{ id : '301' , palletNo : 'DRYU2309389-101-005' , sequenceNo : 1 , actualPackageCount : 0 }], allocations : [{ lineId : '201' , palletId : '302' , palletNo : 'DRYU2309389-101-006' , palletOwnerPlanNo : 'IP-OWNER' , palletOwnedByCurrentPlan : false , plannedPackageCount : 150 , countedPackageCount : 150 , pickedPackageCount : 150 , status : 'PICKED' , version : 0 }] })
2026-09-11 16:11:31 +08:00
routing . route . params = { orderId : 'IP-CURRENT' }; routing . route . query = { customerId : '10001' }
const wrapper = mount ( InboundOrderDetailView , { global : { plugins : [ ElementPlus ] } })
await flushPromises ()
const rows = wrapper . findComponent ( DataTable ). props ( 'data' ) as Array < { palletNo : string ; palletOwner : string ; packageCount : number } >
expect ( rows ). toEqual ([ expect . objectContaining ({ palletNo : 'DRYU2309389-101-006' , palletOwner : 'IP-OWNER(跨计划合托)' , packageCount : 150 })])
expect ( wrapper . text ()). not . toContain ( 'DRYU2309389-101-005(跨计划合托)' )
})
2026-09-08 14:15:28 +08:00
it ( 'shows an empty receiving warehouse when either frozen field is missing without falling back to destination data' , async () => {
2026-09-18 14:58:40 +08:00
vi . mocked ( getInboundPlan ). mockResolvedValueOnce ({ id : '1' , planNo : 'IP-2' , customerId : '10001' , sourceOrderId : '2' , overseasWarehouseCode : null , overseasWarehouseName : '不充整接柜仓' , status : 'PENDING_ASSIGNMENT' , lineCount : 1 , palletCount : 0 , totalGrossWeightKg : 0 , totalNetWeightKg : 0 , totalVolumeCbm : 0 , lines : [{ id : '3' , orderId : '2' , inboundCode : 'IN-2' , fbaWarehouseCode : 'ABE3' , destinationWarehouseCode : 'ABE3' , destinationWarehouseName : 'ABE3目的仓' , requiredPackageCount : 1 , plannedPalletCount : 0 }], pallets : [], allocations : [] })
2026-09-08 14:15:28 +08:00
routing . route . params = { orderId : 'IP-2' }; routing . route . query = { customerId : '10001' }
const wrapper = mount ( InboundOrderDetailView , { global : { plugins : [ ElementPlus ] } })
await flushPromises ()
const field = ( label : string ) => wrapper . findAll ( 'dt' ). find (( item ) => item . text () === label ) ? . element . parentElement ? . querySelector ( 'dd' ) ? . textContent
expect ( field ( '海外仓' )). toBe ( '—' )
expect ( field ( 'FBA仓库' )). toBe ( 'ABE3' )
expect ( wrapper . text ()). not . toContain ( 'ABE3目的仓' )
})
2026-09-11 10:32:29 +08:00
it ( 'does not expose a numeric route id as an inbound business number when scope is missing' , async () => {
routing . route . params = { orderId : '9007199254740995' }
routing . route . query = {}
const wrapper = mount ( InboundOrderDetailView , { global : { plugins : [ ElementPlus ] } })
await flushPromises ()
expect ( wrapper . text ()). toContain ( '详情缺少客户范围' )
expect ( wrapper . text ()). not . toContain ( '9007199254740995' )
})
2026-09-04 17:46:15 +08:00
it ( 'mounts inventory ledger as localized read-only business facts without exposing internal ids' , async () => { const wrapper = mount ( InventoryTransactionsView , { global : { plugins : [ createPinia (), ElementPlus ] } }); await flushPromises (); expect ( listInventoryLedger ). toHaveBeenCalledWith ( expect . objectContaining ({ page : 1 , size : 10 })); expect ( listInventoryLedger ). not . toHaveBeenCalledWith ( expect . objectContaining ({ customerId : expect.anything () })); expect ( wrapper . text ()). toContain ( '客户甲' ); expect ( wrapper . text ()). toContain ( 'NYEXX2608300001' ); expect ( wrapper . text ()). not . toContain ( '10001' ); expect ( wrapper . text ()). toContain ( 'IP-1' ); expect ( wrapper . text ()). toContain ( '10' ); expect ( wrapper . text ()). toContain ( '入库' ); expect ( wrapper . text ()). not . toContain ( 'STOCK_IN' ); expect ( wrapper . text ()). toContain ( '不提供出库单跳转' ); expect ( wrapper . text ()). not . toContain ( '当前流水 DTO' ); expect ( wrapper . text ()). not . toContain ( '操作人名称待补充' ) })
2026-09-11 16:17:21 +08:00
it ( 'renders complete business references with single-line ellipsis and titles' , async () => { routing . route . params = { orderId : '9007199254740999' }; const wrapper = mount ( LastMileOrderDetailView , { global : { plugins : [ createPinia (), ElementPlus ] } }); await flushPromises (); expect ( getLastMileOrder ). toHaveBeenCalledWith ( '9007199254740999' ); expect ( wrapper . text ()). toContain ( '华远贸易' ); expect ( wrapper . text ()). toContain ( 'LMO-260827-0001' ); expect ( wrapper . text ()). toContain ( '货柜节点' ); expect ( wrapper . text ()). toContain ( '已清关、可提柜' ); expect ( wrapper . text ()). not . toContain ( '清关状态' ); expect ( wrapper . get ( '.order-reference-text' ). attributes ( 'title' )). toBe ( 'LMO-260827-0001' ); const businessValues = wrapper . findAll ( '.detail-field-value--business' ); expect ( businessValues . map (( item ) => item . attributes ( 'title' ))). toEqual ( expect . arrayContaining ([ 'NYEXX2608280033' , 'TSTU0000061' ])); expect ( businessValues . every (( item ) => item . text (). length > 0 )). toBe ( true ) })
2026-08-20 18:02:52 +08:00
})