test(pda): bind receiving fixtures to exact source container booking

This commit is contained in:
2026-10-08 21:36:53 +08:00
parent 6b6d217774
commit 4f9433c39a
2 changed files with 144 additions and 31 deletions
@@ -480,48 +480,56 @@ export async function uploadPhotos(context, token) {
return photos
}
export async function awaitProducerConsumption(context, producers) {
const expected = [...new Set(producers.map((item) => id(item?.containerNo)).filter(nonBlank))]
export async function awaitProducerConsumption(context, producers, { attempts = 90, delay = ms => new Promise(resolve => setTimeout(resolve, ms)) } = {}) {
const expected = [...new Map(producers.map(item => [id(item?.containerBookingId), item])).values()]
if (!expected.length) return true
if (expected.some(item => !longId(item?.containerBookingId) || !nonBlank(item?.containerNo))) return false
const consumed = new Set()
const lastResponses = new Map()
for (let attempt = 1; attempt <= 90; attempt += 1) {
const missing = expected.filter((containerNo) => !consumed.has(containerNo))
const results = await Promise.all(missing.map(async (containerNo) => {
const response = await context.request('GET', `/api/platform/v1/last-leg/orders?page=1&size=200&containerNo=${encodeURIComponent(containerNo)}`)
return { containerNo, response }
for (let attempt = 1; attempt <= attempts; attempt += 1) {
const missing = expected.filter(item => !consumed.has(id(item.containerBookingId)))
const results = await Promise.all(missing.map(async (producer) => {
const response = await context.request('GET', `/api/platform/v1/last-leg/orders/container-summaries/${encodeURIComponent(producer.containerBookingId)}`)
return { producer, response }
}))
for (const { containerNo, response } of results) {
lastResponses.set(containerNo, response)
const rows = records(response?.body?.data)
if (success(response) && rows.some((item) => id(item?.containerNo) === containerNo)) {
consumed.add(containerNo)
for (const { producer, response } of results) {
const bookingId = id(producer.containerBookingId)
lastResponses.set(bookingId, response)
const summary = response?.body?.data?.summary
if (success(response) && id(summary?.sourceContainerBookingId) === bookingId
&& summary?.containerNo === producer.containerNo && Number(summary?.totalOrderCount) > 0) {
consumed.add(bookingId)
}
}
if (expected.every((containerNo) => consumed.has(containerNo))) break
await new Promise((resolve) => setTimeout(resolve, 1000))
if (expected.every(item => consumed.has(id(item.containerBookingId)))) break
if (attempt < attempts) await delay(1000)
}
for (const containerNo of expected) {
const response = lastResponses.get(containerNo) || null
for (const producer of expected) {
const containerNo = producer.containerNo, bookingId = id(producer.containerBookingId)
const response = lastResponses.get(bookingId) || null
context.addStep(MODULE, `跨区广播消费观察 ${containerNo}`, response,
true,
`containerNo=${containerNo}, consumed=${consumed.has(containerNo)}; ${trace(response)}`, OBSERVATION_OPTIONS)
`containerNo=${containerNo}, sourceContainerBookingId=${bookingId}, consumed=${consumed.has(bookingId)}; ${trace(response)}`, OBSERVATION_OPTIONS)
}
return expected.every((containerNo) => consumed.has(containerNo))
return expected.every(item => consumed.has(id(item.containerBookingId)))
}
export async function convertReceivingContainers(context, producers) {
for (const producer of producers) {
const containerNo = id(producer?.containerNo)
const summaryResponse = await context.request('GET', `/api/platform/v1/last-leg/orders/container-summaries?page=1&size=20&containerNo=${encodeURIComponent(containerNo)}`, undefined, context.token, {})
const summaries = success(summaryResponse) && Array.isArray(summaryResponse.body?.data?.records)
? summaryResponse.body.data.records : []
const summary = summaries.find((item) => id(item?.containerNo) === containerNo)
const bookingId = id(producer?.containerBookingId)
if (!longId(bookingId) || !nonBlank(containerNo)) {
context.addStep(MODULE, '本轮生产柜次身份完整', null, false,
`containerNo=${containerNo || 'missing'}, sourceContainerBookingId=${bookingId || 'missing'}`, OPTIONS)
return false
}
const summaryResponse = await context.request('GET', `/api/platform/v1/last-leg/orders/container-summaries/${encodeURIComponent(bookingId)}`, undefined, context.token, {})
const summary = success(summaryResponse) ? summaryResponse.body?.data?.summary : null
const totalOrders = Number(summary?.totalOrderCount)
const inboundCodes = Number(summary?.inboundCodeCount)
const existingPlans = Number(summary?.planCount)
const pendingPlans = Number(summary?.pendingPlanCount)
const convertible = nonBlank(summary?.sourceContainerBookingId)
const convertible = id(summary?.sourceContainerBookingId) === bookingId && summary?.containerNo === containerNo
&& totalOrders > 0 && inboundCodes === totalOrders
&& Number(summary?.missingInboundCodeCount) === 0
&& ((existingPlans === 0 && pendingPlans === totalOrders)
@@ -530,7 +538,6 @@ export async function convertReceivingContainers(context, producers) {
convertible,
`sourceContainerBookingId=${id(summary?.sourceContainerBookingId) || 'missing'}, allPlansReady=${summary?.allPlansReady === true}, orders=${totalOrders}, inboundCodes=${inboundCodes}, plans=${existingPlans}, pending=${pendingPlans}; ${trace(summaryResponse)}`, OPTIONS)
if (!convertible) return false
const bookingId = id(summary.sourceContainerBookingId)
const arrivalQueryResponse = await context.request('POST', '/api/platform/v1/last-leg/orders/container-arrival-times/query', { sourceContainerBookingIds: [bookingId] }, context.token, {})
const arrivalTimes = success(arrivalQueryResponse) && Array.isArray(arrivalQueryResponse.body?.data)
? arrivalQueryResponse.body.data : []
@@ -539,7 +546,12 @@ export async function convertReceivingContainers(context, producers) {
Boolean(arrivalTime) && Number.isInteger(Number(arrivalTime?.version)),
`sourceContainerBookingId=${bookingId}, estimatedWarehouseArrivalAt=${arrivalTime?.estimatedWarehouseArrivalAt || 'missing'}, version=${arrivalTime?.version ?? 'missing'}; ${trace(arrivalQueryResponse)}`,
OPTIONS)
if (!arrivalTime) return false
if (!arrivalTime || !Number.isInteger(Number(arrivalTime.version))) return false
if (existingPlans > 0 && !nonBlank(arrivalTime.estimatedWarehouseArrivalAt)) {
context.addStep(MODULE, `已生成计划的整柜预计到仓时间不可补写 ${containerNo}`, arrivalQueryResponse,
false, `sourceContainerBookingId=${bookingId}, plans=${existingPlans}, estimatedWarehouseArrivalAt=missing`, OPTIONS)
return false
}
if (!nonBlank(arrivalTime.estimatedWarehouseArrivalAt)) {
const estimatedWarehouseArrivalAt = new Date(Date.now() + 2 * 86400000).toISOString().slice(0, 19)
const arrivalUpdateResponse = await context.request('PUT', `/api/platform/v1/last-leg/orders/container-summaries/${encodeURIComponent(bookingId)}/expected-arrival`, { estimatedWarehouseArrivalAt, expectedVersion: Number(arrivalTime.version),
@@ -623,29 +635,35 @@ export async function convertReceivingContainers(context, producers) {
return true
}
async function awaitPickupReadyCandidates(context, token, producers, allProducers) {
export async function awaitPickupReadyCandidates(context, token, producers, allProducers,
{ attempts = 30, delay = ms => new Promise(resolve => setTimeout(resolve, ms)) } = {}) {
const expected = [...new Set(producers.map((item) => id(item?.containerNo)).filter(nonBlank))]
const expectedSet = new Set(expected)
const awaiting = [...new Set(allProducers
.filter((item) => item?.shippingReady === false && !expectedSet.has(id(item?.containerNo)))
.map((item) => id(item?.containerNo)).filter(nonBlank))]
const known = new Set(allProducers.map((item) => id(item?.containerNo)).filter(nonBlank))
const isKnownProducer = candidate => allProducers.some(producer => longId(producer?.containerBookingId)
&& id(candidate?.sourceContainerBookingId) === id(producer.containerBookingId)
&& candidate?.containerNo === producer.containerNo)
if (!expected.length) return false
let response = null
let candidatesByContainer = new Map()
for (let attempt = 1; attempt <= 30; attempt += 1) {
let unexpectedHistorical = []
for (let attempt = 1; attempt <= attempts; attempt += 1) {
response = await context.request('GET', '/api/pda/v1/last-leg/receiving-containers/candidates', undefined, token, {})
const candidateRows = success(response) && Array.isArray(response.body?.data) ? response.body.data : []
unexpectedHistorical = candidateRows.filter(item => !isKnownProducer(item))
.map(item => `${id(item?.containerNo)}:${id(item?.sourceContainerBookingId)}`)
candidatesByContainer = new Map(candidateRows
.filter(isKnownProducer)
.map((item) => [id(item?.containerNo), item]).filter(([containerNo]) => nonBlank(containerNo)))
if (expected.every((containerNo) => candidatesByContainer.get(containerNo)?.operable === true)
&& awaiting.every((containerNo) => candidatesByContainer.get(containerNo)?.operable !== true)) break
await new Promise((resolve) => setTimeout(resolve, 1000))
if (attempt < attempts) await delay(1000)
}
const seenExpected = expected.filter((containerNo) => candidatesByContainer.has(containerNo))
const missingPickupReady = expected.filter((containerNo) => candidatesByContainer.get(containerNo)?.operable !== true)
const unexpectedAwaiting = awaiting.filter((containerNo) => candidatesByContainer.get(containerNo)?.operable === true)
const unexpectedHistorical = [...candidatesByContainer.keys()].filter((containerNo) => !known.has(containerNo))
const targetCorrelated = producers.every((item) => nonBlank(item?.resourceKey)
&& item?.shippingReady !== false && candidatesByContainer.get(id(item?.containerNo))?.operable === true)
const ready = missingPickupReady.length === 0 && unexpectedAwaiting.length === 0 && targetCorrelated