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 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() 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, '')}` report.pdaUsername = process.env.LOCAL_TRANSFER_PDA_USERNAME check(Boolean(report.pdaUsername), '必须配置既有 LOCAL_TRANSFER_PDA_USERNAME,不自动创建PDA账号') 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 }