贴标申请,仓储转运

This commit is contained in:
2026-09-17 17:37:29 +08:00
parent 3b8b6f5aa2
commit 29f05b7b09
106 changed files with 5019 additions and 471 deletions
@@ -22,8 +22,8 @@ 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) })
...(body === undefined || body instanceof FormData ? {} : { 'Content-Type': 'application/json' }) },
body: body === undefined ? undefined : body instanceof FormData ? body : 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') {
@@ -48,12 +48,32 @@ 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',
let task = await api(overseas, pdaToken, 'GET', `/api/pda/v1/last-leg/dispatches/${dispatchNo}`)
let alreadyDeparted = task.status === 'DEPARTED'
|| (task.status === 'COMPLETED' && String(task.departBlockReason || '').includes('已发车'))
if (!alreadyDeparted && task.status === 'PENDING') {
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 existingPage = await api(overseas, platformToken, 'GET',
`/api/platform/v1/last-leg/delivery-orders?customerId=${customerId}&page=1&size=20&dispatchNo=${encodeURIComponent(dispatchNo)}`)
const existingDeliveries = (existingPage.records || []).filter((value) => value.dispatchNo === dispatchNo)
const existingOutboundNos = new Set(existingDeliveries.map((value) => value.outboundNo).filter(Boolean))
assert(existingOutboundNos.size <= 1, 'Dispatch is linked to multiple outbound orders')
const transfer = existingDeliveries.length > 0 ? {
outboundNo: [...existingOutboundNos][0],
deliveries: existingDeliveries.map((value) => ({
deliveryNo: value.deliveryNo, deliveryStatus: value.status,
destinationWarehouseId: value.destinationWarehouseId,
destinationWarehouseCode: value.destinationWarehouseCode,
destinationWarehouseName: value.destinationWarehouseName,
palletCount: value.palletCount, packageCount: value.packageCount,
})),
replayed: true,
} : 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: '尾程验收车队',
@@ -62,29 +82,53 @@ const transfer = await api(overseas, merchantToken, 'POST',
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')
const transferDeliveries = Array.isArray(transfer.deliveries) ? transfer.deliveries : []
assert(transfer.outboundNo && transferDeliveries.length > 0,
'Transfer did not create outbound and delivery')
assert(transferDeliveries.length === 1,
`Expected one destination delivery, got ${transferDeliveries.length}`)
const deliveryNo = transferDeliveries[0].deliveryNo
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
if (!alreadyDeparted) {
if (task.items.every(item => Number(item.pickedPackages) === 0)) {
await api(overseas, platformToken, 'PUT', `/api/platform/v1/last-leg/dispatches/${dispatchNo}`, {
vehiclePlate: `UAT-${marker.slice(-8)}`, driverName: '尾程验收司机', driverPhone: '19900000000',
carrierName: '尾程验收车队', fbaAppointmentNo: `UAT-${marker.slice(-8)}`,
fbaAppointmentStatus: 'CONFIRMED', vehicleAppointmentStatus: 'CONFIRMED',
estimatedArrivalAt: new Date(Date.now() + 172800000).toISOString().slice(0, 19),
})
}
assert(remaining === 0, `PDA item ${item.dispatchItemId} has insufficient inventory`)
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`)
}
task = await api(overseas, pdaToken, 'GET', `/api/pda/v1/last-leg/dispatches/${dispatchNo}`)
alreadyDeparted = task.status === 'DEPARTED'
|| (task.status === 'COMPLETED' && String(task.departBlockReason || '').includes('已发车'))
}
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',
if (!alreadyDeparted && task.status !== 'COMPLETED') {
const pickingCompleted = await api(overseas, pdaToken, 'POST',
`/api/pda/v1/last-leg/dispatches/${dispatchNo}/complete`, { idempotencyKey: key('PICK-COMPLETE') })
assert(pickingCompleted.status === 'READY_TO_DEPART', 'Dispatch did not reach READY_TO_DEPART')
task = await api(overseas, pdaToken, 'GET', `/api/pda/v1/last-leg/dispatches/${dispatchNo}`)
}
assert(alreadyDeparted || task.status === 'COMPLETED', 'Dispatch did not finish picking')
const departed = alreadyDeparted ? {
status: 'DEPARTED',
shippedPackages: task.items.reduce((total, item) => total + Number(item.pickedPackages), 0),
} : 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')
@@ -92,24 +136,47 @@ 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,
`/api/platform/v1/last-leg/delivery-orders/${deliveryNo}?customerId=${customerId}`)
if (delivery.order.status === 'WAIT_DELIVERY') {
const delivering = await api(overseas, platformToken, 'PUT',
`/api/platform/v1/last-leg/delivery-orders/${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/${deliveryNo}?customerId=${customerId}`)
}
if (delivery.order.status !== 'DELIVERED') {
assert(delivery.order.status === 'DELIVERING', `Unexpected delivery status ${delivery.order.status}`)
const documentsPath = `/api/platform/v1/last-leg/delivery-orders/${deliveryNo}/documents?customerId=${customerId}`
const documents = await api(overseas, platformToken, 'GET', documentsPath)
const form = new FormData()
form.append('file', new Blob([`${marker} receipt acceptance for ${deliveryNo}`], { type: 'text/plain' }),
`${marker}-${deliveryNo}-receipt.txt`)
const receipt = await api(overseas, platformToken, 'POST',
`/api/platform/v1/last-leg/delivery-orders/${deliveryNo}/documents/RECEIPT/files?customerId=${customerId}`, form)
const existingBindings = (documents.categories || []).map((category) => ({
category: category.category,
fileIds: (category.files || []).map((file) => id(file.fileId)),
})).filter((binding) => binding.category !== 'RECEIPT' && binding.fileIds.length > 0)
await api(overseas, platformToken, 'PUT', documentsPath, {
expectedVersion: Number(documents.version), idempotencyKey: key('SAVE-RECEIPT'),
bolNo: documents.bolNo || null,
documents: [...existingBindings, { category: 'RECEIPT', fileIds: [id(receipt.fileId)] }],
})
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')
delivery = await api(overseas, platformToken, 'GET',
`/api/platform/v1/last-leg/delivery-orders/${deliveryNo}?customerId=${customerId}`)
}
assert(delivery.order.status === 'DELIVERED', 'Receipt did not automatically complete delivery')
const visits = await api(overseas, platformToken, 'GET',
`/api/platform/v1/last-leg/customer-visits?page=1&size=200&customerId=${customerId}&keyword=${encodeURIComponent(deliveryNo)}`)
const visit = (visits.records || []).find((value) => value.deliveryNo === deliveryNo)
assert(visit?.status === 'PENDING', 'Delivery completion did not create a pending customer visit')
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,
outboundStatus: outbound.outbound.status, deliveryNo,
deliveryStatus: delivery.order.status, visitId: id(visit.id), 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))