贴标申请,仓储转运

This commit is contained in:
2026-09-24 09:01:14 +08:00
parent 10a2ab3e42
commit 186b6be7d8
27 changed files with 1203 additions and 118 deletions
@@ -9,6 +9,7 @@ const overseas = process.env.ADMIN_OVERSEAS_GATEWAY || 'http://127.0.0.1:9300'
let customerId = process.env.OVERSEAS_TEST_CUSTOMER_ID || ''
let warehouseId = process.env.OVERSEAS_TEST_WAREHOUSE_ID || ''
const destinationCode = process.env.OVERSEAS_TEST_DESTINATION_CODE || 'ONT8'
const partialRelabel = process.env.WAREHOUSE_TRANSFER_PARTIAL_RELABEL === 'true'
const output = resolve(process.env.WAREHOUSE_TRANSFER_REMAINING_OUTPUT
|| 'reports/warehouse-transfer-remaining-acceptance.json')
const marker = `WT-REMAIN-${new Date().toISOString().replace(/\D/g, '').slice(0, 14)}-${process.pid}`
@@ -189,13 +190,14 @@ try {
}
const cartonMarks = [`CM-${marker}-1`, `CM-${marker}-2`]
const cartonRelabelBindings = partialRelabel ? cartonMarks.slice(0, 1) : cartonMarks
const transfer = await api(overseas, merchant.overseasAccessToken, 'POST',
'/api/merchant/v1/last-leg/warehouse-transfers', {
operatingWarehouseId: warehouseId,
fbaWarehouseCode: destinationCode,
destinationType: 'FBA',
destinationWarehouseCode: destinationCode,
destinationWarehouseName: destinationCode,
fbaWarehouseCode: partialRelabel ? null : destinationCode,
destinationType: partialRelabel ? null : 'FBA',
destinationWarehouseCode: partialRelabel ? null : destinationCode,
destinationWarehouseName: partialRelabel ? null : destinationCode,
deliveryAddress: `Acceptance inbound address ${marker}`,
packageCount: 2,
volumeCbm: 0.12,
@@ -214,7 +216,7 @@ try {
idempotencyKey: key('CREATE'),
items: [{ skuCode: `SKU-${marker}`.slice(0, 128), productNameZh: 'Acceptance cargo',
packageCount: 2, shippingMark: cartonMarks.join(',') }],
relabelItems: cartonMarks.map((cartonMark, index) => ({
relabelItems: cartonRelabelBindings.map((cartonMark, index) => ({
operationType: 'CARTON_AND_SKU', customerCartonMarks: [cartonMark],
originalSku: `SKU-${marker}`.slice(0, 128),
newSku: `NEW-${marker}-${index + 1}`.slice(0, 128), quantity: 1,
@@ -278,9 +280,18 @@ try {
status: transferDetail.status, relabelNo,
})
const reviewedRelabel = await api(overseas, admin.overseasAccessToken, 'PUT',
`/api/platform/v1/last-leg/relabel-applications/${relabelNo}/status`, {
status: 'PENDING_PROCESSING', remark: marker,
})
assert(reviewedRelabel.application.status === 'PENDING_PROCESSING',
`Linked relabel review did not approve execution: ${reviewedRelabel.application.status}`)
step('Platform approved linked relabel for PDA execution', { status: reviewedRelabel.application.status })
let relabel = await api(overseas, pda.token, 'POST',
`/api/pda/v1/last-leg/relabel-tasks/${relabelNo}/claim`, { idempotencyKey: key('RELABEL-CLAIM') })
assert(relabel.items.length === 2, `Expected two linked relabel items, got ${relabel.items.length}`)
assert(relabel.items.length === (partialRelabel ? 1 : 2),
`Expected ${partialRelabel ? 1 : 2} linked relabel items, got ${relabel.items.length}`)
for (const [index, item] of relabel.items.entries()) {
relabel = await api(overseas, pda.token, 'POST',
`/api/pda/v1/last-leg/relabel-tasks/${relabelNo}/cartons/scan`, {
@@ -330,7 +341,8 @@ try {
transferDetail = await api(overseas, merchant.overseasAccessToken, 'GET',
`/api/merchant/v1/last-leg/warehouse-transfers/${transferNo}`)
assert(confirmedRelabel.application.status === 'READY_FOR_OUTBOUND', 'Linked relabel did not reach outbound-ready')
assert(transferDetail.status === 'READY_FOR_DISPATCH', 'Warehouse transfer did not return to dispatch-ready')
assert(transferDetail.status === (partialRelabel ? 'WAIT_FBA_DESTINATION' : 'READY_FOR_DISPATCH'),
`Warehouse transfer reached unexpected status ${transferDetail.status}`)
step('Completed linked relabel and released transfer for dispatch', {
relabelStatus: confirmedRelabel.application.status, transferStatus: transferDetail.status,
})
@@ -339,17 +351,30 @@ try {
`/api/platform/v1/last-leg/dispatches/candidates?orderNo=${encodeURIComponent(relabelNo)}&sourceStage=RECEIVED`)
const targetIds = new Set(targetPallets.map((value) => id(value.palletId)))
const rows = candidates.filter((value) => value.sourceStage === 'RELABEL' && targetIds.has(id(value.warehousePalletId)))
assert(rows.length === 2, `Expected two smart-dispatch pallets, got ${rows.length}`)
assert(rows.length === (partialRelabel ? 1 : 2),
`Expected ${partialRelabel ? 1 : 2} smart-dispatch pallets, got ${rows.length}`)
const dispatch = await api(overseas, admin.overseasAccessToken, 'POST',
'/api/platform/v1/last-leg/dispatches/pre-arrange', {
planPalletIds: rows.map((value) => id(value.warehousePalletId)), route: marker, relabelNo,
})
step('Created smart pre-dispatch', { dispatchNo: dispatch.dispatchNo, status: dispatch.status })
if (partialRelabel) {
const outboundCandidates = await api(overseas, admin.overseasAccessToken, 'GET',
`/api/platform/v1/last-leg/outbound-orders/warehouse-transfer/candidates?customerId=${encodeURIComponent(customerId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
const remaining = outboundCandidates.filter((value) => value.transferNo === transferNo)
assert(remaining.length === 1 && Number(remaining[0].packageCount) === 1,
`Expected one unlabelled carton in warehouse-transfer outbound candidates, got ${JSON.stringify(remaining)}`)
step('Verified split outbound routing', {
relabelDispatchPallets: rows.length, remainingOutboundCandidates: remaining.length,
remainingPackageCount: remaining[0].packageCount,
})
}
const sourceReport = resolve('reports', `${marker}-relabel-source.json`)
const fulfillmentReport = resolve('reports', `${marker}-fulfillment.json`)
await writeFile(sourceReport, JSON.stringify({ marker, passed: true, scenarios: [{
name: 'SPLIT-TWO-TARGETS', relabelNo, dispatchNo: dispatch.dispatchNo,
name: 'SPLIT-TWO-TARGETS', relabelNo, dispatchNo: dispatch.dispatchNo, partialRelabel,
}] }, null, 2), 'utf8')
const child = spawnSync(process.execPath, [resolve('scripts', 'overseas-relabel-fulfillment-acceptance.mjs')], {
cwd: resolve('.'), stdio: 'inherit', env: {
@@ -366,11 +391,70 @@ try {
})
if (child.error) throw child.error
if (child.status !== 0) throw new Error(`Fulfillment acceptance failed with exit code ${child.status}`)
const finalTransfer = await api(overseas, merchant.overseasAccessToken, 'GET',
let finalTransfer = await api(overseas, merchant.overseasAccessToken, 'GET',
`/api/merchant/v1/last-leg/warehouse-transfers/${transferNo}`)
let remainingDispatchNo = null
let remainingFulfillmentReport = null
if (partialRelabel) {
assert(finalTransfer.status === 'WAIT_FBA_DESTINATION',
`Partial relabel dispatch incorrectly closed the transfer: ${finalTransfer.status}`)
const beforeBind = await api(overseas, admin.overseasAccessToken, 'GET',
`/api/platform/v1/last-leg/outbound-orders/warehouse-transfer/candidates?customerId=${encodeURIComponent(customerId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
const remainingBeforeBind = beforeBind.filter((value) => value.transferNo === transferNo)
assert(remainingBeforeBind.length === 1 && Number(remainingBeforeBind[0].packageCount) === 1
&& remainingBeforeBind[0].palletId && remainingBeforeBind[0].warehousePalletId,
`Expected one selectable remaining pallet before destination binding, got ${JSON.stringify(remainingBeforeBind)}`)
await api(overseas, admin.overseasAccessToken, 'POST',
'/api/platform/v1/last-leg/warehouse-transfers/bind-destination', {
transfers: [{ customerId, transferNo }], destinationType: 'FBA',
destinationWarehouseCode: destinationCode, destinationWarehouseName: destinationCode,
idempotencyKey: key('BIND-REMAINING-DESTINATION'),
})
const afterBind = await api(overseas, admin.overseasAccessToken, 'GET',
`/api/platform/v1/last-leg/outbound-orders/warehouse-transfer/candidates?customerId=${encodeURIComponent(customerId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
assert(!afterBind.some((value) => value.transferNo === transferNo),
'Destination-bound transfer remained in the destination-completion outbound list')
const remainingCandidates = await api(overseas, admin.overseasAccessToken, 'GET',
`/api/platform/v1/last-leg/dispatches/candidates?orderNo=${encodeURIComponent(transferNo)}&sourceStage=RECEIVED`)
const remainingRows = remainingCandidates.filter((value) => value.sourceStage === 'RECEIVED'
&& Number(value.packageCount) === 1 && value.destinationWarehouseCode === destinationCode)
assert(remainingRows.length === 1,
`Expected one destination-bound smart-dispatch pallet, got ${JSON.stringify(remainingRows)}`)
const remainingDispatch = await api(overseas, admin.overseasAccessToken, 'POST',
'/api/platform/v1/last-leg/dispatches/pre-arrange', {
planPalletIds: remainingRows.map((value) => id(value.planPalletId)), route: `${marker}-REMAINING`,
})
remainingDispatchNo = remainingDispatch.dispatchNo
const remainingSourceReport = resolve('reports', `${marker}-remaining-source.json`)
remainingFulfillmentReport = resolve('reports', `${marker}-remaining-fulfillment.json`)
await writeFile(remainingSourceReport, JSON.stringify({ marker, passed: true, scenarios: [{
name: 'SPLIT-TWO-TARGETS', relabelNo: '', dispatchNo: remainingDispatchNo, partialRelabel: true,
}] }, null, 2), 'utf8')
const remainingChild = spawnSync(process.execPath,
[resolve('scripts', 'overseas-relabel-fulfillment-acceptance.mjs')], {
cwd: resolve('.'), stdio: 'inherit', env: {
...process.env, ADMIN_TEST_GATEWAY: domestic, ADMIN_OVERSEAS_GATEWAY: overseas,
ADMIN_OVERSEAS_ACCESS_TOKEN: admin.overseasAccessToken,
MERCHANT_OVERSEAS_ACCESS_TOKEN: merchant.overseasAccessToken,
PDA_OVERSEAS_ACCESS_TOKEN: pda.token, OVERSEAS_TEST_CUSTOMER_ID: customerId,
OVERSEAS_ACCEPTANCE_SOURCE: remainingSourceReport,
OVERSEAS_FULFILLMENT_OUTPUT: remainingFulfillmentReport,
},
})
if (remainingChild.error) throw remainingChild.error
if (remainingChild.status !== 0) throw new Error(`Remaining fulfillment failed with exit code ${remainingChild.status}`)
finalTransfer = await api(overseas, merchant.overseasAccessToken, 'GET',
`/api/merchant/v1/last-leg/warehouse-transfers/${transferNo}`)
assert(finalTransfer.status === 'OUTBOUNDED',
`Remaining transfer did not complete outbound: ${finalTransfer.status}`)
step('Bound remaining carton destination and completed its smart dispatch', {
remainingDispatchNo, remainingPackageCount: 1, transferStatus: finalTransfer.status,
})
}
Object.assign(report, {
passed: true, transferNo, relabelNo, dispatchNo: dispatch.dispatchNo,
transferStatus: finalTransfer.status, fulfillmentReport, finishedAt: new Date().toISOString(),
remainingDispatchNo, transferStatus: finalTransfer.status, fulfillmentReport,
remainingFulfillmentReport, finishedAt: new Date().toISOString(),
})
} catch (error) {
report.error = error instanceof Error ? error.message : String(error)