fix(phase3): close loading and dispatch flows

This commit is contained in:
2026-08-10 17:42:17 +08:00
parent 074a95d82b
commit 84209bffd7
19 changed files with 221 additions and 43 deletions
@@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises'
const MODULE = 'inbound-measurement'
const SCENARIO = 'mojia-carton-measurement'
const OPTIONS = { scenario: SCENARIO, deploymentBlocker: true }
const DEFAULT_PALLET_CAPACITY_CBM = 1.2
function asId(value) { return value == null ? '' : String(value) }
function asNumber(value) { return Number(value) }
@@ -23,6 +24,41 @@ function validPdf(response) {
&& response.bytes?.length > 4 && new TextDecoder().decode(response.bytes.slice(0, 4)) === '%PDF'
}
async function provisionWarehouseResources(context, { inboundId, warehouseId, totalVolumeCbm }) {
const palletCount = Math.ceil(Number(totalVolumeCbm) / DEFAULT_PALLET_CAPACITY_CBM)
if (!warehouseId || !Number.isFinite(palletCount) || palletCount < 1) return false
const suffix = `${String(context.marker).replace(/\D/g, '').slice(-8)}${String(inboundId).slice(-6)}`
const resources = []
for (let index = 1; index <= palletCount; index += 1) {
const resourceSuffix = `${suffix}${String(index).padStart(2, '0')}`
const palletResponse = await context.request('POST', '/api/platform/v1/pallets?tenantId=0', {
warehouseId, palletType: 'STANDARD', capacityCbm: DEFAULT_PALLET_CAPACITY_CBM,
weightKg: 8, color: '蓝色', remark: `${context.marker}-INBOUND-${inboundId}`,
})
const palletId = asId(context.apiSuccess(MODULE, `创建测量占用托盘 ${index}/${palletCount}`,
palletResponse, `warehouseId=${warehouseId}`, OPTIONS))
const locationResponse = await context.request('POST', '/api/platform/v1/locations?tenantId=0', {
warehouseId, zoneCode: `E2E-${String(context.marker).slice(-8)}`, zoneName: '自动化验收区',
locationCode: `E2E-${resourceSuffix}`, locationName: `测量验收库位-${index}`,
locationType: 'PALLET', maxWeightKg: 1000, status: 'AVAILABLE',
remark: `${context.marker}-INBOUND-${inboundId}`,
})
const locationId = asId(context.apiSuccess(MODULE, `创建测量占用库位 ${index}/${palletCount}`,
locationResponse, `warehouseId=${warehouseId}`, OPTIONS))
if (!palletId || !locationId) return false
resources.push({ palletId, locationId })
}
context.createdResources.inboundWarehouseResources ??= {}
context.createdResources.inboundWarehouseResources[inboundId] = {
marker: context.marker, tenantId: '0', warehouseId: asId(warehouseId), palletCount,
palletCapacityCbm: DEFAULT_PALLET_CAPACITY_CBM, resources,
}
return true
}
function findIsolatedInbound(context) {
const roots = [context.createdResources, context.context]
const visited = new Set()
@@ -311,6 +347,18 @@ export async function run(context) {
return
}
const warehouseResourcesReady = await provisionWarehouseResources(context, {
inboundId, warehouseId: asId(summary?.warehouseId), totalVolumeCbm: totalVolume,
})
context.assert(MODULE, '测量提交前已创建足量真实托盘和库位', draftResponse,
warehouseResourcesReady,
`warehouseId=${asId(summary?.warehouseId) || 'missing'}, palletCount=${Math.ceil(totalVolume / DEFAULT_PALLET_CAPACITY_CBM)}`,
OPTIONS)
if (!warehouseResourcesReady) {
finishScenario(context, start)
return
}
const submitResponse = await context.request('POST',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/submit?${query(tenantId)}`,
{ measurementId: draft.measurementId, version: Number(draft.version) })
@@ -73,16 +73,39 @@ export async function run(context) {
const container = context.createdResources?.phase3Loading
|| context.createdResources?.phase3ContainerLoading
const tenantId = id(fixture?.tenantId)
const orderId = id(fixture?.orderId)
const parentOrderId = id(fixture?.orderId)
const inboundId = id(fixture?.inboundId)
const containerBookingId = id(container?.containerBookingId)
if (!tenantId || !orderId || !inboundId || !containerBookingId
const loadingPlanId = id(container?.loadingPlanId)
if (!tenantId || !parentOrderId || !inboundId || !containerBookingId
|| !['COMPLETED', 'LOADED'].includes(container?.finalStatus)) {
context.addStep(MODULE, '取得本轮客户/订单/入库单/柜次隔离 fixture', null, false,
`tenantId=${tenantId || 'missing'}, orderId=${orderId || 'missing'}, inboundId=${inboundId || 'missing'}, containerBookingId=${containerBookingId || 'missing'}, containerStatus=${container?.finalStatus || 'missing'}`, OPTIONS)
`tenantId=${tenantId || 'missing'}, parentOrderId=${parentOrderId || 'missing'}, inboundId=${inboundId || 'missing'}, containerBookingId=${containerBookingId || 'missing'}, loadingPlanId=${loadingPlanId || 'missing'}, containerStatus=${container?.finalStatus || 'missing'}`, OPTIONS)
return
}
const slicesPath = `/api/platform/v1/orders/container-bookings/${encodeURIComponent(containerBookingId)}/fulfillment-slices?tenantId=${encodeURIComponent(tenantId)}`
const slicesResponse = await context.request('GET', slicesPath)
const slices = context.apiSuccess(MODULE, '读取柜次真实履约分片以确定报关订单', slicesResponse,
`containerBookingId=${containerBookingId}, tenantId=${tenantId}`, OPTIONS)
const loadedSlices = Array.isArray(slices)
? slices.filter((item) => id(item?.tenantId) === tenantId
&& id(item?.containerBookingId) === containerBookingId
&& id(item?.parentOrderId) === parentOrderId
&& item?.splitRole === 'LOADED'
&& (!loadingPlanId || id(item?.loadingPlanId) === loadingPlanId))
: []
const loadedSlice = loadedSlices.length === 1 ? loadedSlices[0] : null
const orderId = id(loadedSlice?.childOrderId)
context.assert(MODULE, '报关绑定柜次实际承载的唯一 LOADED 子订单', slicesResponse,
loadedSlices.length === 1 && Boolean(orderId) && orderId !== parentOrderId
&& id(loadedSlice?.parentOrderId) === parentOrderId
&& id(loadedSlice?.containerBookingId) === containerBookingId
&& id(loadedSlice?.tenantId) === tenantId
&& (!loadingPlanId || id(loadedSlice?.loadingPlanId) === loadingPlanId),
`parentOrderId=${parentOrderId}, childOrderId=${orderId || 'missing'}, containerBookingId=${containerBookingId}, loadingPlanId=${id(loadedSlice?.loadingPlanId) || 'missing'}, matches=${loadedSlices.length}`, OPTIONS)
if (!orderId || loadedSlices.length !== 1 || orderId === parentOrderId) return
const loginResponse = await request(context, 'POST', '/api/auth/login', {
username: MERCHANT_USERNAME, password: MERCHANT_PASSWORD,
}, '')
@@ -114,12 +137,12 @@ export async function run(context) {
{ 'Idempotency-Key': key(context, 'case') })
const created = context.apiSuccess(MODULE, '创建客户订单柜次关联报关协同单', caseResponse, '', OPTIONS)
const caseId = id(created?.customsCase?.id)
context.assert(MODULE, '报关单绑定本轮客户、订单、入库单、柜次和清单版本', caseResponse,
context.assert(MODULE, '报关单绑定本轮 LOADED 子订单、入库单、柜次和清单版本', caseResponse,
Boolean(caseId) && id(created?.customsCase?.tenantId) === tenantId && id(created?.customsCase?.orderId) === orderId
&& id(created?.customsCase?.inboundId) === inboundId && id(created?.customsCase?.containerBookingId) === containerBookingId
&& id(created?.customsCase?.checklistVersionId) === checklistVersionId
&& created?.customsCase?.status === 'DOCUMENTS_PENDING' && Number(created?.customsCase?.version) === 0,
`caseId=${caseId || 'missing'}, status=${created?.customsCase?.status || 'missing'}`, OPTIONS)
`caseId=${caseId || 'missing'}, parentOrderId=${parentOrderId}, childOrderId=${orderId}, status=${created?.customsCase?.status || 'missing'}`, OPTIONS)
if (!caseId) return
const caseReplay = await request(context, 'POST', `${PLATFORM}/cases`, casePayload, context.token,
@@ -282,7 +305,7 @@ export async function run(context) {
`HTTP=${forgedReference.http}, code=${codeOf(forgedReference)}`, OPTIONS)
context.createdResources.phase3Customs = {
marker: context.marker, tenantId, orderId, inboundId, containerBookingId,
marker: context.marker, tenantId, parentOrderId, orderId, inboundId, containerBookingId, loadingPlanId,
customsCaseId: caseId, submissionBatchId: batchId, releasedReceiptId: releasedId,
finalStatus: 'CLEARANCE_COMPLETED',
}