Files
baiyue-firstleg/scripts/overseas-relabel-fulfillment-acceptance.mjs
T

116 lines
7.8 KiB
JavaScript

import { readFile, writeFile } from 'node:fs/promises'
const domestic = process.env.ADMIN_TEST_GATEWAY || 'http://127.0.0.1:9000'
const overseas = process.env.ADMIN_OVERSEAS_GATEWAY || 'http://127.0.0.1:9300'
const sourceReport = process.env.OVERSEAS_ACCEPTANCE_SOURCE
const directDispatchNo = process.env.OVERSEAS_DISPATCH_NO
const directRelabelNo = process.env.OVERSEAS_RELABEL_NO
const output = process.env.OVERSEAS_FULFILLMENT_OUTPUT || 'reports/overseas-relabel-fulfillment-acceptance.json'
const customerId = process.env.OVERSEAS_TEST_CUSTOMER_ID
const directPlatformToken = process.env.ADMIN_OVERSEAS_ACCESS_TOKEN
const directMerchantToken = process.env.MERCHANT_OVERSEAS_ACCESS_TOKEN
const directPdaToken = process.env.PDA_OVERSEAS_ACCESS_TOKEN
const marker = `FUL-${new Date().toISOString().replace(/\D/g, '').slice(0, 14)}-${process.pid}`
if (!directPlatformToken && (!process.env.ADMIN_TEST_USERNAME || !process.env.ADMIN_TEST_PASSWORD)) throw new Error('Missing platform credentials or token')
if (!directMerchantToken && (!process.env.ADMIN_TEST_USERNAME || !process.env.ADMIN_TEST_PASSWORD)) throw new Error('Missing merchant token or platform credentials')
if (!directPdaToken && (!process.env.PDA_TEST_USERNAME || !process.env.PDA_TEST_PASSWORD)) throw new Error('Missing PDA credentials or token')
if ((!sourceReport && !directDispatchNo) || !customerId) throw new Error('Missing acceptance source/dispatch or customer id')
function id(value) { return value == null ? '' : String(value) }
function key(value) { return `${marker}-${value}`.slice(0, 200) }
function assert(value, message) { if (!value) throw new Error(message) }
async function api(base, token, method, path, body) {
const response = await fetch(`${base}${path}`, { method,
headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}),
...(body === undefined ? {} : { 'Content-Type': 'application/json' }) },
body: body === undefined ? undefined : JSON.stringify(body) })
const text = await response.text(); let value
try { value = text ? JSON.parse(text) : null } catch { value = { raw: text } }
if (!response.ok || String(value?.code) !== '200') {
throw new Error(`${method} ${path}: HTTP=${response.status}, code=${value?.code}, message=${value?.message}`)
}
return value.data
}
const source = sourceReport ? JSON.parse(await readFile(sourceReport, 'utf8')) : null
const scenario = source?.scenarios.find((value) => value.name === 'SPLIT-TWO-TARGETS')
|| { dispatchNo: directDispatchNo, relabelNo: directRelabelNo || '' }
assert(scenario?.dispatchNo, 'Dispatch not found in source report')
const dispatchNo = scenario.dispatchNo
const platformLogin = directPlatformToken ? null : await api(domestic, '', 'POST', '/api/auth/login', {
loginId: process.env.ADMIN_TEST_USERNAME, password: process.env.ADMIN_TEST_PASSWORD })
const pdaLogin = directPdaToken ? null : await api(domestic, '', 'POST', '/api/auth/login', {
loginId: process.env.PDA_TEST_USERNAME, password: process.env.PDA_TEST_PASSWORD })
const ticket = directMerchantToken ? null : await api(domestic, platformLogin.accessToken, 'POST',
`/api/platform/v1/iam/tenants/${customerId}/impersonation`, { reason: `${marker} 海外尾程履约验收` })
const merchantLogin = directMerchantToken ? null : await api(domestic, '', 'POST', '/api/auth/impersonations/exchange', { code: ticket.code })
const platformToken = directPlatformToken || platformLogin.overseasAccessToken
const merchantToken = directMerchantToken || merchantLogin.overseasAccessToken
const pdaToken = directPdaToken || pdaLogin.overseasAccessToken
await api(overseas, platformToken, 'PUT', `/api/platform/v1/last-leg/dispatches/${dispatchNo}`, {
vehiclePlate: `UAT-${marker.slice(-8)}`, driverName: '尾程验收司机', driverPhone: '19900000000',
carrierName: '尾程验收车队', route: `${marker}-ROUTE`,
estimatedArrivalAt: new Date(Date.now() + 86400000).toISOString(),
})
const transfer = await api(overseas, merchantToken, 'POST',
`/api/merchant/v1/last-leg/delivery-orders?dispatchNo=${encodeURIComponent(dispatchNo)}`, {
idempotencyKey: key('TRANSFER'), plannedDeliveryAt: new Date(Date.now() + 172800000).toISOString(),
deliveryMethod: 'TRUCK_PALLET', waybillNo: `${marker}-WAYBILL`, carrierName: '尾程验收车队',
pickupMethod: 'WAREHOUSE_PICKUP', loadingType: 'PALLET', vehicleType: 'TRUCK', vehicleSize: '15托',
driverName: '尾程验收司机', driverPhone: '19900000000', vehiclePlate: `UAT-${marker.slice(-8)}`,
appointmentAccount: 'SELF', remark: marker, fees: [{ feeType: 'DELIVERY_FEE', feeName: '派送费',
direction: 'RECEIVABLE', currency: 'CNY', amount: 10, remark: marker }],
})
assert(transfer.outboundNo && transfer.deliveryNo, 'Transfer did not create outbound and delivery')
let task = await api(overseas, pdaToken, 'GET', `/api/pda/v1/last-leg/dispatches/${dispatchNo}`)
assert(Array.isArray(task.items) && task.items.length === 2, 'Expected two PDA pallet items')
for (const [itemIndex, item] of task.items.entries()) {
let remaining = Number(item.scheduledPackages) - Number(item.pickedPackages)
for (const [positionIndex, position] of item.positions.entries()) {
if (remaining <= 0) break
const packages = Math.min(remaining, Number(position.availablePackages))
if (packages <= 0) continue
await api(overseas, pdaToken, 'POST',
`/api/pda/v1/last-leg/dispatches/${dispatchNo}/items/${id(item.dispatchItemId)}/pick`, {
positionId: id(position.id), packages, productQuantity: packages,
idempotencyKey: key(`PICK-${itemIndex}-${positionIndex}`), remark: marker,
})
remaining -= packages
}
assert(remaining === 0, `PDA item ${item.dispatchItemId} has insufficient inventory`)
}
const completed = await api(overseas, pdaToken, 'POST',
`/api/pda/v1/last-leg/dispatches/${dispatchNo}/complete`, { idempotencyKey: key('PICK-COMPLETE') })
assert(completed.status === 'READY_TO_DEPART', 'Dispatch did not reach READY_TO_DEPART')
const departed = await api(overseas, pdaToken, 'POST',
`/api/pda/v1/last-leg/dispatches/${dispatchNo}/depart`, { idempotencyKey: key('DEPART'), remark: marker })
assert(departed.status === 'DEPARTED', 'Dispatch did not reach DEPARTED')
const outbound = await api(overseas, platformToken, 'GET',
`/api/platform/v1/last-leg/outbound-orders/${transfer.outboundNo}?page=1&size=20`)
assert(outbound.outbound.status === 'OUTBOUNDED', 'Outbound did not reach OUTBOUNDED')
let delivery = await api(overseas, platformToken, 'GET',
`/api/platform/v1/last-leg/delivery-orders/${transfer.deliveryNo}?customerId=${customerId}`)
const delivering = await api(overseas, platformToken, 'PUT',
`/api/platform/v1/last-leg/delivery-orders/${transfer.deliveryNo}/status?customerId=${customerId}`, {
status: 'DELIVERING', expectedVersion: Number(delivery.order.version), remark: marker,
})
assert(delivering.deliveryStatus === 'DELIVERING', 'Delivery did not reach DELIVERING')
delivery = await api(overseas, platformToken, 'GET',
`/api/platform/v1/last-leg/delivery-orders/${transfer.deliveryNo}?customerId=${customerId}`)
const delivered = await api(overseas, platformToken, 'POST',
`/api/platform/v1/last-leg/delivery-orders/${transfer.deliveryNo}/complete?customerId=${customerId}`, {
status: 'DELIVERED', expectedVersion: Number(delivery.order.version), remark: marker,
})
assert(delivered.deliveryStatus === 'DELIVERED', 'Delivery did not reach DELIVERED')
const report = { marker, scope: 'OVERSEAS_LASTLEG_ONLY', sourceReport, relabelNo: scenario.relabelNo,
dispatchNo, dispatchStatus: departed.status, outboundNo: transfer.outboundNo,
outboundStatus: outbound.outbound.status, deliveryNo: transfer.deliveryNo,
deliveryStatus: delivered.deliveryStatus, visitId: id(delivered.visitId), shippedPackages: departed.shippedPackages,
passed: true, finishedAt: new Date().toISOString() }
await writeFile(output, JSON.stringify(report, null, 2), 'utf8')
console.log(JSON.stringify(report, null, 2))