2026-10-19 10:23:09 +08:00
import { mkdir , readFile , writeFile } from 'node:fs/promises'
import { resolve , dirname } from 'node:path'
import { createHash , randomUUID } from 'node:crypto'
// 本入口只访问显式指定的本机统一 Gateway;所有业务数据均经正式 HTTP 创建。
const gateway = String ( process . env . ADMIN_TEST_GATEWAY || '' ). replace ( /\/$/ , '' )
if ( ! gateway || ! [ 'localhost' , '127.0.0.1' ]. includes ( new URL ( gateway ). hostname )) throw new Error ( '仅允许显式配置本机 Gateway' )
if ( ! process . env . ADMIN_TEST_USERNAME || ! process . env . ADMIN_TEST_PASSWORD || ! process . env . LOCAL_TRANSFER_PDA_PASSWORD ) throw new Error ( '缺少本机平台与PDA验收凭据' )
const priorReport = process . env . PICKUP_ACCEPTANCE_RESUME ? JSON . parse ( await readFile ( resolve ( process . env . PICKUP_ACCEPTANCE_RESUME ), 'utf8' )) : null
if ( priorReport ? . passed ) throw new Error ( '本批次已验收完成,不重复执行业务;请查看原报告或创建新批次' )
const marker = priorReport ? . marker || `PICKUP- ${ new Date (). toISOString (). replace ( /\D/g , '' ). slice ( 0 , 14 ) } `
const output = resolve ( process . env . PICKUP_ACCEPTANCE_RESUME || process . env . PICKUP_ACCEPTANCE_OUTPUT || `reports/ ${ marker } .json` )
const report = priorReport || { marker , environment : 'LOCAL' , evidence : 'SYNTHETIC_TEST_PNG_NOT_REAL_PHYSICAL_PICKUP' , startedAt : new Date (). toISOString (), passed : false , orders : [], trips : [], checks : [] }
const transferBase = '/api/platform/v1/last-leg/warehouse-transfers' , pickupBase = '/api/platform/v1/last-leg/fleet/pickups' , tripBase = '/api/platform/v1/last-leg/fleet/trips'
const png = Buffer . from ( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=' , 'base64' )
const attemptStart = report . checks . length
report . attempts ||= []
const attempt = { startedAt : new Date (). toISOString (), checkOffset : attemptStart , passed : false }
report . attempts . push ( attempt )
let admin , pda
/** 每个业务请求保留真实错误码和诊断,不保存会话、密码和完整客户资料。 */
async function api ( token , method , path , body , expectedCode ) {
const form = body instanceof FormData
// 写入前留脱敏意图,网络结果未知时可用原请求键定位,不能当作未提交再次造单。
if ( method !== 'GET' && ! path . startsWith ( '/api/auth/' )) {
report . intents ||= []
report . intents . push ({ method , path , idempotencyKey : body ? . idempotencyKey , payloadSha256 : form ? undefined : createHash ( 'sha256' ). update ( JSON . stringify ( body ?? null )). digest ( 'hex' ), at : new Date (). toISOString () })
await save ()
}
const response = await fetch ( ` ${ gateway }${ path } ` , { method , signal : AbortSignal . timeout ( 45000 ), headers : { ...( token ? { Authorization : `Bearer ${ token } ` } : {}), ...( body !== undefined && ! form ? { 'Content-Type' : 'application/json' } : {}) }, body : body === undefined ? undefined : form ? body : JSON . stringify ( body ) })
const value = await response . json (). catch (() => null ), code = String ( value ? . code )
const passed = response . status === 200 && ( expectedCode ? code === expectedCode : code === '200' )
report . checks . push ({ method , path , code , http : response . status , expectedCode , passed , requestId : response . headers . get ( 'x-request-id' ) || value ? . requestId , traceId : response . headers . get ( 'x-trace-id' ) || value ? . traceId , message : code === '200' ? undefined : value ? . message })
await save ()
if ( ! passed ) throw new Error ( ` ${ method } ${ path } : ${ response . status } / ${ code } ${ value ? . message || '' } ` )
return expectedCode ? value : value . data
}
/** 断言必须对应服务端读回结果。 */
function check ( condition , label ) { report . checks . push ({ label , passed : Boolean ( condition ) }); if ( ! condition ) throw new Error ( label ) }
/** 每个创建阶段立即落检查点,失败不删除本轮已成功单据。 */
async function save () { await mkdir ( dirname ( output ), { recursive : true }); await writeFile ( output , JSON . stringify ( report , null , 2 )) }
/** 本地模拟资料仅验证文件归属、签名和上传链路,不冒充实际照片或驾驶证。 */
function image ( name ) { const body = new FormData (); body . append ( 'file' , new Blob ([ png ], { type : 'image/png' }), ` ${ marker } - ${ name } -TEST.png` ); return body }
/** 所有执行操作使用当前车次版本和全新命令标识。 */
const command = ( trip , reason ) => ({ recordVersion : trip . trip . recordVersion , idempotencyKey : randomUUID (), reason })
/** 以既有有效客户创建本轮新业务单,不读取或推进历史订单。 */
async function order ( customer , warehouse , index , method = 'WAREHOUSE_PICKUP' , review = true ) {
if ( priorReport ? . orders [ index - 1 ]) return priorReport . orders [ index - 1 ]
const products = [ '手机硅胶保护壳' , '折叠收纳盒' , 'USB-C充电线' , '宠物饮水器' , '不锈钢保温杯' , '桌面理线器' ]
const counts = index % 3 === 0 ? [ 3 , 2 , 1 ] : index % 2 === 0 ? [ 4 , 2 ] : [ 3 ]
const payload = { operatingWarehouseId : warehouse . id , destinationType : 'PRIVATE' , privateDestinationAddress : '100 TEST Distribution Road, Ontario, CA' , privateDestinationPostalCode : '91761' , privateDestinationContactName : '取货验收收件人' , privateDestinationContactPhone : '0000000000' ,
deliveryAddress : ` ${ 1580 + index } TEST Pickup Ave, Ontario, CA 91761 ( ${ marker } )` , pickupContactName : `取货验收员 ${ index } ` , pickupContactPhone : '0000000000' ,
packageCount : counts . reduce (( sum , value ) => sum + value , 0 ), volumeCbm : 0 , deliveryMethod : method , estimatedArrivalAt : new Date ( Date . now () + 86400000 ). toISOString (). slice ( 0 , 19 ),
estimatedShipAt : new Date ( Date . now () + 172800000 ). toISOString (). slice ( 0 , 19 ), remark : ` ${ marker } 场景 ${ index } 本地业务接口验收单` , needRelabel : false , idempotencyKey : ` ${ marker } - ${ index } ` ,
items : counts . map (( packageCount , j ) => ({ skuCode : ` ${ marker } - ${ index } - ${ j + 1 } ` , productNameZh : products [( index + j ) % products . length ], quantity : packageCount * ( 8 + j * 4 ), packageCount , shippingMark : ` ${ marker } -CUSTOMER- ${ index } - ${ j + 1 } ` })) }
let detail
if ( index === 1 ) {
const token = await merchant ( customer . id )
detail = await api ( token , 'POST' , '/api/merchant/v1/last-leg/warehouse-transfers' , payload )
detail = await api ( token , 'POST' , `/api/merchant/v1/last-leg/warehouse-transfers/ ${ detail . transferNo } /submit` )
check ( detail . status === 'PENDING_REVIEW' , '客户正式下单提交到待审核' )
} else detail = await api ( admin , 'POST' , ` ${ transferBase } ?customerId= ${ customer . id } ` , payload )
if ( review ) detail = await api ( admin , 'POST' , ` ${ transferBase } / ${ detail . transferNo } /review?customerId= ${ customer . id } ` , { decision : 'APPROVE' , remark : ` ${ marker } 本地取货验收` })
const row = { customerId : customer . id , transferNo : detail . transferNo , lastlegOrderNo : detail . lastlegOrderNo , warehouseId : warehouse . id , method , packageCount : detail . packageCount , status : detail . status , scenario : `场景 ${ index } ` , cartonMarks : detail . cartons . map ( box => box . cartonMark ), declaredQuantity : detail . items . reduce (( sum , item ) => sum + Number ( item . quantity || 0 ), 0 ) }
report . orders . push ( row ); await save (); return row
}
/** 创建取货车次后保留单号,便于页面人工验收。 */
async function plan ( sources , scenario ) {
const existing = report . trips . find ( row => row . scenario === scenario )
if ( existing ) return api ( admin , 'GET' , ` ${ tripBase } / ${ existing . tripNo } ` )
const body = { sources : sources . map (({ customerId , transferNo }) => ({ customerId , transferNo })), plannedDepartureAt : new Date ( Date . now () - 300000 ). toISOString (), expectedDriverEndAt : new Date ( Date . now () + 86400000 ). toISOString (), remark : ` ${ marker } ${ scenario } ` , idempotencyKey : randomUUID () }
const detail = await api ( admin , 'POST' , pickupBase , body )
report . trips . push ({ tripNo : detail . trip . tripNo , scenario , sources : body . sources , status : detail . trip . status }); await save ()
const replay = await api ( admin , 'POST' , pickupBase , body ); check ( replay . trip . tripNo === detail . trip . tripNo , '安排重试保持原车次' )
return detail
}
/** 创建本轮专用司机和车辆,避免占用既有业务资源。 */
async function resources ( warehouse , index ) {
if ( report . resources ? .[ index - 1 ]) { const row = report . resources [ index - 1 ]; return { vehicleId : row . vehicleId , driverId : row . driverId } }
const driverBase = '/api/platform/v1/last-leg/fleet/drivers'
let driver = await api ( admin , 'POST' , driverBase , { name : ` ${ marker } 取货司机 ${ index } ` , phone : '0000000000' , ownership : 'OWNED' , region : 'OVERSEAS' , licenseNumber : ` ${ marker } - ${ index } ` , licenseType : 'Class A' , licenseZone : 'UTC' , licenseExpiresOn : new Date ( Date . now () + 365 * 86400000 ). toISOString (). slice ( 0 , 10 ), remark : ` ${ marker } TEST licence image, local fixture only` , profile : { warehouseId : warehouse . id , vehicleModels : '26ft Box Truck' , languagesAndArea : 'Ontario local TEST route' } })
driver = await api ( admin , 'POST' , ` ${ driverBase } / ${ driver . id } /license?recordVersion= ${ driver . recordVersion } ` , image ( `driver- ${ index } ` ))
driver = await api ( admin , 'POST' , ` ${ driverBase } / ${ driver . id } /review` , { recordVersion : driver . recordVersion , decision : 'APPROVED' , reason : ` ${ marker } 本地模拟资质审核` })
const vehicle = await api ( admin , 'POST' , '/api/platform/v1/last-leg/master-data/vehicles' , { plateNumber : `TEST-PU- ${ marker . slice ( - 8 ) } - ${ index } ` , model : '26ft Box Truck' , fuelConsumptionPer100Km : 22 , mileageKm : 18000 + index * 700 , driverOrDispatchCompany : ` ${ marker } 本地验收车队` , phone : '0000000000' , region : 'OVERSEAS' , ownership : 'OWNED' , status : 'IN_SERVICE' , remark : ` ${ marker } 上门取货验收专用` , maxPalletSpaces : 12 , maxLoadKg : 8000 , maxVolumeCbm : 45 })
report . resources ||= []; report . resources . push ({ driverId : driver . id , driverNo : driver . driverNo , vehicleId : vehicle . id , plateNumber : vehicle . plateNumber }); await save ()
return { vehicleId : vehicle . id , driverId : driver . id }
}
/** 分配资源沿原接口,取货车与派送车共用占用规则。 */
async function assign ( trip , resource ) { if ( trip . trip . vehicleId === resource . vehicleId && trip . trip . driverId === resource . driverId ) return trip ; return api ( admin , 'POST' , ` ${ tripBase } / ${ trip . trip . tripNo } /assignment` , { recordVersion : trip . trip . recordVersion , ... resource , reason : ` ${ marker } 分配取货车与司机` }) }
/** 发车使用取货链,不拣货、不生成出库单。 */
async function depart ( trip ) { if ( trip . trip . status !== 'PLANNED' ) return trip ; return api ( admin , 'POST' , ` ${ pickupBase } / ${ trip . trip . tripNo } /depart` , command ( trip , ` ${ marker } 车辆已出发取货` )) }
/** 逐票取货并验证同键重放,不重复扣减或改写客户预报。 */
async function picked ( trip , source , actual = source . packageCount ) {
const existing = ( await api ( admin , 'GET' , ` ${ pickupBase } / ${ trip . trip . tripNo } /tasks` )). find ( row => row . customerId === source . customerId && row . transferNo === source . transferNo )
if ( existing && [ 'PICKED' , 'ARRIVED' ]. includes ( existing . status )) return { trip , file : existing . evidence [ 0 ] }
const path = ` ${ pickupBase } / ${ trip . trip . tripNo } /tasks/ ${ source . customerId } / ${ source . transferNo } `
const file = await api ( admin , 'POST' , ` ${ path } /files` , image ( source . transferNo ))
const body = { recordVersion : trip . trip . recordVersion , idempotencyKey : randomUUID (), actualPackages : actual , evidenceFileIds : [ file . fileId ], remark : actual === source . packageCount ? '取货现场箱数已核对' : '实提与预报不同,等待仓库独立清点' }
await api ( admin , 'POST' , ` ${ path } /picked` , body ); await api ( admin , 'POST' , ` ${ path } /picked` , body )
await api ( admin , 'POST' , ` ${ path } /picked` , { ... body , actualPackages : actual + 1 }, 'A0409' )
const detail = await api ( admin , 'GET' , ` ${ transferBase } / ${ source . transferNo } ?customerId= ${ source . customerId } ` )
check ( detail . packageCount === source . packageCount && detail . status === 'WAIT_PDA_RECEIVING' , '实提不覆盖预报箱数或完成测量' )
check ( JSON . stringify ( detail . cartons . map ( box => box . cartonMark )) === JSON . stringify ( source . cartonMarks ), '取货运输保留原预报箱唛' )
return { trip : await api ( admin , 'GET' , ` ${ tripBase } / ${ trip . trip . tripNo } ` ), file }
}
/** 到仓确认后才允许仓库作业。 */
async function arrive ( trip , source ) {
if (( await api ( admin , 'GET' , ` ${ pickupBase } / ${ trip . trip . tripNo } /tasks` )). some ( row => row . customerId === source . customerId && row . transferNo === source . transferNo && row . status === 'ARRIVED' )) return trip
await api ( admin , 'POST' , ` ${ pickupBase } / ${ trip . trip . tripNo } /tasks/ ${ source . customerId } / ${ source . transferNo } /arrive` , command ( trip , ` ${ marker } 货已送达入仓月台` ))
return api ( admin , 'GET' , ` ${ tripBase } / ${ trip . trip . tripNo } ` )
}
/** 以平台正式代登录接口取得客户会话,仅用于本轮读回与越权测试。 */
async function merchant ( customerId ) {
const ticket = await api ( admin , 'POST' , `/api/platform/v1/iam/tenants/ ${ customerId } /impersonation` , { reason : ` ${ marker } 验证客户本票取货回显与隔离` })
return ( await api ( '' , 'POST' , '/api/auth/impersonations/exchange' , { code : ticket . code })). accessToken
}
/** 送仓后走原 PDA 托盘、逐箱清点、照片及差异确认完整链,运输不代替仓内事实。 */
async function measure ( source ) {
const path = `/api/pda/v1/last-leg/warehouse-transfers/ ${ source . transferNo } `
const current = await api ( pda , 'GET' , path )
if ( current . status !== 'WAIT_PDA_RECEIVING' ) { check ( current . status === 'WAIT_CUSTOMER_CONFIRMATION' , '续验测量完成状态' ); return }
let detail = current . pallets . length ? current : await api ( pda , 'POST' , ` ${ path } /pallets/drafts` , { idempotencyKey : randomUUID () })
let pallet = detail . pallets [ 0 ]
if ( pallet . status === 'DRAFT' ) detail = await api ( pda , 'POST' , ` ${ path } /pallets/ ${ pallet . id } /generate` , { idempotencyKey : randomUUID () })
pallet = detail . pallets . find ( row => row . id === pallet . id )
if ( pallet . status !== 'BOUND' ) detail = await api ( pda , 'POST' , ` ${ path } /pallets/ ${ pallet . id } /bind` , { idempotencyKey : randomUUID (), palletCode : pallet . palletNo })
const declared = detail . items . flatMap ( item => Array . from ({ length : item . packageCount }, () => ({ skuCode : item . skuCode , quantity : Number ( item . quantity ) / item . packageCount })))
check ( declared . length === detail . cartons . length , '预报SKU箱数与固定箱唛总数一致' )
const boxes = detail . cartons . map (( box , index ) => ({ cartonMark : box . cartonMark , transferPalletId : pallet . id , length : 40 + index , width : 30 , height : 25 , weight : 4 + index / 10 , items : [{ ... declared [ index ], quantity : declared [ index ]. quantity - ( index === 0 ? 1 : 0 ) }] }))
if ( ! detail . cartons . some ( row => row . countedAt )) detail = await api ( pda , 'POST' , ` ${ path } /cartons/count` , { idempotencyKey : randomUUID (), forecastPackageCount : detail . packageCount , boxes })
const photo = await api ( pda , 'POST' , ` ${ path } /measurement-photos/files` , image ( 'measurement' ))
await api ( pda , 'POST' , ` ${ path } /palletize` , { idempotencyKey : randomUUID (), photoFileIds : [ photo . fileId ] }, 'A0400' )
detail = await api ( pda , 'POST' , ` ${ path } /palletize` , { idempotencyKey : randomUUID (), photoFileIds : [ photo . fileId ], discrepancyReason : '本地验收:首箱SKU实点短收1件,已复点并拍照留存' })
check ( detail . status === 'WAIT_CUSTOMER_CONFIRMATION' && detail . orderStatus === 'INBOUND_COMPLETED' , '取货送仓后PDA完整测量成功,进入客户确认' )
check ( JSON . stringify ( detail . cartons . map ( box => box . cartonMark )) === JSON . stringify ( source . cartonMarks ), '测量完成保留客户预报箱唛身份' )
check ( detail . items . reduce (( sum , item ) => sum + Number ( item . quantity ), 0 ) === source . declaredQuantity , '测量不覆盖客户SKU预报件数' )
source . palletNo = pallet . palletNo
source . scenario = '取货送仓后已完成PDA测量,SKU短收1件且已填写原因'
await save ()
}
try {
const login = await api ( '' , 'POST' , '/api/auth/login' , { loginId : process . env . ADMIN_TEST_USERNAME , password : process . env . ADMIN_TEST_PASSWORD })
admin = login . accessToken
2026-10-27 17:44:41 +08:00
const required = [ 'cancel' , 'complete' , 'release' ]. map ( key => `platform:fleet:trip: ${ key } ` )
check ( Array . isArray ( login . authorities ) && required . every ( code => login . authorities . includes ( code )), '固定平台账号缺少正式业务权限,不自动补权限或创建角色' )
report . operatorUsername = process . env . ADMIN_TEST_USERNAME ; await save ()
2026-10-19 10:23:09 +08:00
const tenants = await api ( admin , 'GET' , '/api/platform/v1/iam/tenants?page=1&size=200' )
const customers = tenants . records . filter ( row => row . status === 'ACTIVE' ). sort (( a , b ) => Number ( ! String ( a . tenantName || a . name ). includes ( '测试' )) - Number ( ! String ( b . tenantName || b . name ). includes ( '测试' )))
check ( customers . length >= 2 , '本地至少两名有效客户用于共车隔离验收' )
const warehouses = ( await api ( admin , 'GET' , '/api/platform/v1/warehouse-admin?page=1&size=100&warehouseType=OVERSEAS&status=ACTIVE' )). records
const warehouse = warehouses . find ( row => row . warehouseCode === 'US-LAX-01' ) || warehouses [ 0 ], otherWarehouse = warehouses . find ( row => row . id !== warehouse ? . id )
check ( warehouse && otherWarehouse , '本地至少两个有效海外仓用于异仓拒绝验收' )
report . warehouseId = warehouse . id
const roles = await api ( admin , 'GET' , '/api/platform/v1/iam/roles' ), role = roles . find ( row => row . roleCode === 'OVERSEAS_WAREHOUSE_PDA_OPERATOR' && row . status === 'ACTIVE' )
check ( role , '存在海外PDA作业角色' )
const username = `pda_pickup_ ${ marker . replace ( /\D/g , '' ) } `
2026-10-27 17:44:41 +08:00
report . pdaUsername = process . env . LOCAL_TRANSFER_PDA_USERNAME
check ( Boolean ( report . pdaUsername ), '必须配置既有 LOCAL_TRANSFER_PDA_USERNAME,不自动创建PDA账号' )
2026-10-19 10:23:09 +08:00
const invalid = { sources : [ waiting , differentWarehouse ]. map (({ customerId , transferNo }) => ({ customerId , transferNo })), plannedDepartureAt : new Date (). toISOString (), expectedDriverEndAt : new Date ( Date . now () + 86400000 ). toISOString (), idempotencyKey : randomUUID () }
await api ( admin , 'POST' , pickupBase , invalid , 'A0400' )
await api ( pda , 'POST' , `/api/pda/v1/last-leg/warehouse-transfers/ ${ waiting . transferNo } /pallets/drafts` , { idempotencyKey : randomUUID () }, 'A0409' )
let shared = await plan ([ sharedA , sharedB ], '两客户多点共车取货,全流程验收' )
await api ( admin , 'POST' , pickupBase , { ... invalid , sources : [{ customerId : sharedA . customerId , transferNo : sharedA . transferNo }], idempotencyKey : randomUUID () }, 'A0409' )
const resource = await resources ( warehouse , 1 ); shared = await assign ( shared , resource )
let cancelTrip = await plan ([ orders [ 3 ]], '取消后原单回到取货池' )
await api ( admin , 'POST' , ` ${ tripBase } / ${ cancelTrip . trip . tripNo } /assignment` , { recordVersion : cancelTrip . trip . recordVersion , ... resource , reason : '验收同一资源不能同时占用两车' }, 'A0409' )
if ( cancelTrip . trip . status !== 'CANCELLED' ) cancelTrip = await api ( admin , 'POST' , ` ${ tripBase } / ${ cancelTrip . trip . tripNo } /cancel` , { recordVersion : cancelTrip . trip . recordVersion , reason : ` ${ marker } 改派验收` })
check (( await api ( admin , 'GET' , ` ${ pickupBase } ?keyword= ${ orders [ 3 ]. transferNo } ` )). records . some ( row => row . transferNo === orders [ 3 ]. transferNo ), '取消车次后原单可重新安排' )
const replanned = await plan ([ orders [ 3 ], orders [ 4 ]], '取消后的重新安排,保留计划中供页面验收' ); orders [ 3 ]. scenario = '已改派重新安排' ; orders [ 4 ]. scenario = '已排车未分配车辆'
shared = await depart ( shared )
if ( ! ( await api ( admin , 'GET' , ` ${ pickupBase } / ${ shared . trip . tripNo } /tasks` )). some ( row => row . status !== 'ASSIGNED' )) await api ( admin , 'POST' , ` ${ pickupBase } / ${ shared . trip . tripNo } /complete` , command ( shared , '验收未取货不能结案' ), 'A0409' )
await api ( admin , 'POST' , ` ${ tripBase } / ${ shared . trip . tripNo } /cancel` , { recordVersion : shared . trip . recordVersion , reason : '验收已发车不可取消' }, 'A0409' )
const pickedA = await picked ( shared , sharedA , sharedA . packageCount - 1 ); shared = pickedA . trip
const pathB = ` ${ pickupBase } / ${ shared . trip . tripNo } /tasks/ ${ sharedB . customerId } / ${ sharedB . transferNo } `
if (( await api ( admin , 'GET' , ` ${ pickupBase } / ${ shared . trip . tripNo } /tasks` )). some ( row => row . transferNo === sharedB . transferNo && row . status === 'ASSIGNED' )) await api ( admin , 'POST' , ` ${ pathB } /picked` , { recordVersion : shared . trip . recordVersion , idempotencyKey : randomUUID (), actualPackages : sharedB . packageCount , evidenceFileIds : [ pickedA . file . fileId ] }, 'A0400' )
const pickedB = await picked ( shared , sharedB ); shared = pickedB . trip
shared = await arrive ( shared , sharedA ); shared = await arrive ( shared , sharedB )
const firstMerchant = await merchant ( sharedA . customerId ), secondMerchant = await merchant ( sharedB . customerId )
const progress = await api ( firstMerchant , 'GET' , `/api/merchant/v1/last-leg/warehouse-transfers/ ${ sharedA . transferNo } /pickup-progress` )
check ( progress . status === 'ARRIVED' && progress . evidence . length === 1 && progress . tripNo === shared . trip . tripNo , '客户详情回显本票取货车次、进度和凭证' )
await api ( secondMerchant , 'GET' , `/api/merchant/v1/last-leg/warehouse-transfers/ ${ sharedA . transferNo } /pickup-progress` , undefined , 'A0404' )
await api ( secondMerchant , 'GET' , `/api/merchant/v1/last-leg/warehouse-transfers/ ${ sharedB . transferNo } /pickup-evidence/ ${ pickedA . file . fileId } ` , undefined , 'A0404' )
const binary = await fetch ( ` ${ gateway } /api/merchant/v1/last-leg/warehouse-transfers/ ${ sharedA . transferNo } /pickup-evidence/ ${ pickedA . file . fileId } ` , { headers : { Authorization : `Bearer ${ firstMerchant } ` } })
check ( binary . status === 200 && Buffer . from ( await binary . arrayBuffer ()). equals ( png ), '本票取货凭证真实文件可下载' )
await api ( pda , 'POST' , `/api/pda/v1/last-leg/warehouse-transfers/ ${ sharedA . transferNo } /pallets/drafts` , { idempotencyKey : randomUUID () })
check (( await api ( admin , 'GET' , ` ${ transferBase } / ${ sharedA . transferNo } ?customerId= ${ sharedA . customerId } ` )). status === 'WAIT_PDA_RECEIVING' , '送仓后PDA可开始,运输不冒充测量完成' )
if ( shared . trip . status !== 'COMPLETED' ) shared = await api ( admin , 'POST' , ` ${ pickupBase } / ${ shared . trip . tripNo } /complete` , command ( shared , '全车已送仓,结束取货运输' ))
for ( const resourceKind of [ 'VEHICLE' , 'DRIVER' ]) if ( ! shared . trip [ resourceKind === 'VEHICLE' ? 'vehicleRelease' : 'driverRelease' ]) shared = await api ( admin , 'POST' , ` ${ tripBase } / ${ shared . trip . tripNo } /resources/release` , { ... command ( shared , '实际车辆司机任务结束' ), resourceKind , actualEndAt : new Date (). toISOString (), endLocation : warehouse . warehouseName || '本地测试海外仓' , vehicleStatus : resourceKind === 'VEHICLE' ? 'IN_SERVICE' : null })
sharedA . scenario = '已送仓并已开始PDA测量,司机实提少一箱' ; sharedB . scenario = '已送仓,取货运输已结案,仓库尚未清点'
await measure ( sharedB )
// 保留三类不同履约阶段,方便用户进入页面继续验收。
let onRoad = await plan ([ orders [ 5 ], orders [ 6 ]], '已取货未送仓供页面验收' ); onRoad = await assign ( onRoad , resource ); onRoad = await depart ( onRoad ); onRoad = ( await picked ( onRoad , orders [ 5 ])). trip ; onRoad = ( await picked ( onRoad , orders [ 6 ])). trip
orders [ 5 ]. scenario = orders [ 6 ]. scenario = '已取货未送仓'
const secondResource = await resources ( warehouse , 2 )
let assigned = await plan ([ orders [ 7 ], orders [ 8 ]], '已分配车辆司机尚未发车供页面验收' ); assigned = await assign ( assigned , secondResource )
orders [ 7 ]. scenario = orders [ 8 ]. scenario = '已分配车辆司机,尚未发车'
for ( const entry of report . trips ) {
const detail = await api ( admin , 'GET' , ` ${ tripBase } / ${ entry . tripNo } ` ); entry . status = detail . trip . status ; entry . vehiclePlate = detail . trip . vehiclePlate
}
for ( const entry of report . orders ) {
const detail = await api ( admin , 'GET' , ` ${ transferBase } / ${ entry . transferNo } ?customerId= ${ entry . customerId } ` ); entry . status = detail . status
const progress = await api ( admin , 'GET' , ` ${ transferBase } / ${ entry . transferNo } /pickup-progress?customerId= ${ entry . customerId } ` ); entry . pickupStatus = progress ? . status || null ; entry . tripNo = progress ? . tripNo || null
}
check ( replanned . trip . tripNo && assigned . trip . vehicleId , '改派与已分配阶段单据持久保留' )
check ( report . checks . slice ( attemptStart ). every ( row => row . passed ), '本次执行所有断言通过;前次失败保留审计记录' )
attempt . passed = true ; attempt . completedAt = new Date (). toISOString ();
report . passed = true ; delete report . error ; report . completedAt = new Date (). toISOString (); await save ()
console . log ( JSON . stringify ({ passed : true , marker , orders : report . orders . length , trips : report . trips . length , checks : report . checks . length , report : output }))
} catch ( error ) { attempt . error = error . message ; report . error = error . message ; await save (); console . error ( JSON . stringify ({ passed : false , marker , error : error . message , report : output })); process . exitCode = 1 }