test: complete overseas carton and sailing fixtures
This commit is contained in:
@@ -366,6 +366,7 @@ export async function run(context) {
|
|||||||
sourceOrderId: orderId,
|
sourceOrderId: orderId,
|
||||||
sourceInboundId: inboundId,
|
sourceInboundId: inboundId,
|
||||||
cartonMark: fixture.cartonMark || null,
|
cartonMark: fixture.cartonMark || null,
|
||||||
|
cartonMarks: Array.isArray(fixture.cartonMarks) ? fixture.cartonMarks : [],
|
||||||
destinationType: fixture.destinationType || null,
|
destinationType: fixture.destinationType || null,
|
||||||
destinationWarehouseId: id(fixture.destinationWarehouseId),
|
destinationWarehouseId: id(fixture.destinationWarehouseId),
|
||||||
destinationWarehouseCode: fixture.destinationWarehouseCode || null,
|
destinationWarehouseCode: fixture.destinationWarehouseCode || null,
|
||||||
|
|||||||
@@ -1213,6 +1213,8 @@ export async function run(context) {
|
|||||||
context.createdResources.phase3Loading = {
|
context.createdResources.phase3Loading = {
|
||||||
tenantId, orderId: id(source?.orderId), inboundId, preloadPlanId: planId,
|
tenantId, orderId: id(source?.orderId), inboundId, preloadPlanId: planId,
|
||||||
cartonMark: source?.boxCode || source?.cartonMark || null,
|
cartonMark: source?.boxCode || source?.cartonMark || null,
|
||||||
|
cartonMarks: [...new Set((stagedTask?.items || []).flatMap((item) => item?.boxCodes || [])
|
||||||
|
.map((value) => String(value || '').trim()).filter(Boolean))],
|
||||||
destinationType: initial?.summary?.destinationType || source?.destinationType || null,
|
destinationType: initial?.summary?.destinationType || source?.destinationType || null,
|
||||||
destinationWarehouseId: id(initial?.summary?.destinationWarehouseId || source?.destinationWarehouseId),
|
destinationWarehouseId: id(initial?.summary?.destinationWarehouseId || source?.destinationWarehouseId),
|
||||||
destinationWarehouseCode: initial?.summary?.destinationCode || source?.destinationWarehouseCode || null,
|
destinationWarehouseCode: initial?.summary?.destinationCode || source?.destinationWarehouseCode || null,
|
||||||
|
|||||||
@@ -122,8 +122,7 @@ export async function run(context) {
|
|||||||
|| context.createdResources?.phase3ContainerLoading
|
|| context.createdResources?.phase3ContainerLoading
|
||||||
const ocean = await ensureOceanBooking(context, suffix, master, flow?.oceanBookingId)
|
const ocean = await ensureOceanBooking(context, suffix, master, flow?.oceanBookingId)
|
||||||
if (!ocean?.id) return
|
if (!ocean?.id) return
|
||||||
const containerBookingId = ['COMPLETED', 'LOADED'].includes(flow?.finalStatus)
|
const containerBookingId = id(flow?.containerBookingId) || null
|
||||||
? id(flow.containerBookingId) : null
|
|
||||||
const knownAllocation = containerBookingId
|
const knownAllocation = containerBookingId
|
||||||
&& ocean.allocatedContainerBookingIds?.includes(containerBookingId) ? containerBookingId : null
|
&& ocean.allocatedContainerBookingIds?.includes(containerBookingId) ? containerBookingId : null
|
||||||
if (containerBookingId && !knownAllocation) {
|
if (containerBookingId && !knownAllocation) {
|
||||||
|
|||||||
@@ -401,6 +401,8 @@ test('preload and loading versions use immediate operation baselines and exact p
|
|||||||
assert.match(loading, /shippingCompanyId: id\(shippingCarrier\.id\), shippingCompanyName: shippingCarrier\.name/)
|
assert.match(loading, /shippingCompanyId: id\(shippingCarrier\.id\), shippingCompanyName: shippingCarrier\.name/)
|
||||||
assert.match(loading, /bookingAgentId: id\(supplier\.id\), bookingAgentName: supplier\.name/)
|
assert.match(loading, /bookingAgentId: id\(supplier\.id\), bookingAgentName: supplier\.name/)
|
||||||
assert.doesNotMatch(loading, /shippingCompanyId: id\(supplier\.id\), shippingCompanyName: supplier\.name/)
|
assert.doesNotMatch(loading, /shippingCompanyId: id\(supplier\.id\), shippingCompanyName: supplier\.name/)
|
||||||
|
assert.match(loading, /cartonMarks: \[\.\.\.new Set\(\(stagedTask\?\.items \|\| \[\]\)\.flatMap/)
|
||||||
|
assert.match(source('./phase3-loading-finalize.mjs'), /cartonMarks: Array\.isArray\(fixture\.cartonMarks\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('PDA completion acceptance verifies immutable BATCH and COMPLETE_LOADING audit with operator metadata', () => {
|
test('PDA completion acceptance verifies immutable BATCH and COMPLETE_LOADING audit with operator metadata', () => {
|
||||||
@@ -510,6 +512,8 @@ test('downstream container consumers prefer the phase3Loading fixture', () => {
|
|||||||
assert.doesNotMatch(serviceCenter, /customerName: `服务中心链路客户-/)
|
assert.doesNotMatch(serviceCenter, /customerName: `服务中心链路客户-/)
|
||||||
const schedules = source('./phase3-port-schedules.mjs')
|
const schedules = source('./phase3-port-schedules.mjs')
|
||||||
assert.match(schedules, /ensureOceanBooking\(context, suffix, master, flow\?\.oceanBookingId\)/)
|
assert.match(schedules, /ensureOceanBooking\(context, suffix, master, flow\?\.oceanBookingId\)/)
|
||||||
|
assert.match(schedules, /const containerBookingId = id\(flow\?\.containerBookingId\) \|\| null/)
|
||||||
|
assert.doesNotMatch(schedules, /\['COMPLETED', 'LOADED'\]\.includes\(flow\?\.finalStatus\)/)
|
||||||
assert.match(schedules, /allocatedContainerBookingIds: \(detail\.containers \|\| \[\]\)/)
|
assert.match(schedules, /allocatedContainerBookingIds: \(detail\.containers \|\| \[\]\)/)
|
||||||
assert.match(schedules, /ocean\.allocatedContainerBookingIds\?\.includes\(containerBookingId\)/)
|
assert.match(schedules, /ocean\.allocatedContainerBookingIds\?\.includes\(containerBookingId\)/)
|
||||||
assert.match(schedules, /if \(containerBookingId && !knownAllocation\)/)
|
assert.match(schedules, /if \(containerBookingId && !knownAllocation\)/)
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ test('PDA module covers the container-number receiving contract and excludes out
|
|||||||
assert.doesNotMatch(source, /const warehouseId = id\(inbound\?\.destinationWarehouseId\)/)
|
assert.doesNotMatch(source, /const warehouseId = id\(inbound\?\.destinationWarehouseId\)/)
|
||||||
assert.match(source, /目的仓 .* 不得代替物理仓/)
|
assert.match(source, /目的仓 .* 不得代替物理仓/)
|
||||||
assert.match(source, /retained as acceptance evidence/)
|
assert.match(source, /retained as acceptance evidence/)
|
||||||
|
assert.match(source, /preferredProducer\?\.cartonMarks\?\.length/)
|
||||||
|
assert.match(source, /for \(const \[index, cartonMark\] of cartonMarks\.entries\(\)\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('missing WAREHOUSE_PICKER overseas authority is a deployment blocker', async () => {
|
test('missing WAREHOUSE_PICKER overseas authority is a deployment blocker', async () => {
|
||||||
|
|||||||
@@ -202,27 +202,40 @@ async function receiveFba(context, token, inbound, pallet) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function receiveOverseas(context, token, inbound, pallet, producerCartonMark = null) {
|
async function receiveOverseas(context, token, inbounds, pallets, producerCartonMarks = []) {
|
||||||
const cartonMark = producerCartonMark || context.createdResources?.inboundMeasurement?.boxCode
|
const cartonMarks = [...new Set((Array.isArray(producerCartonMarks) ? producerCartonMarks : [producerCartonMarks])
|
||||||
if (!nonBlank(cartonMark)) {
|
.map(id).filter(nonBlank))]
|
||||||
blockerGap(context, `OVERSEAS入仓码${inbound.inboundCode}逐箱`, '缺少本轮冻结箱唛,逐箱实收不能使用伪造箱号')
|
if (!cartonMarks.length) {
|
||||||
|
blockerGap(context, 'OVERSEAS逐箱链', '缺少本轮PDA任务冻结箱唛,逐箱实收不能使用伪造箱号')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const resolved = await pda(context, token, 'POST', '解析OVERSEAS冻结箱唛', '/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve', { cartonMark })
|
for (const [index, cartonMark] of cartonMarks.entries()) {
|
||||||
if (!success(resolved.response) || resolved.data?.inboundCode !== inbound.inboundCode) {
|
const resolved = await pda(context, token, 'POST', `解析OVERSEAS冻结箱唛${index + 1}`,
|
||||||
blockerGap(context, `OVERSEAS入仓码${inbound.inboundCode}箱唛归属`, `冻结箱唛未解析到目标入仓码;cartonMark=${cartonMark}`, 'fixture-mismatch', resolved.response)
|
'/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve', { cartonMark })
|
||||||
return false
|
const inbound = inbounds.find((item) => item.inboundCode === resolved.data?.inboundCode)
|
||||||
|
const pallet = inbound ? pallets.get(inbound.inboundCode) : null
|
||||||
|
if (!success(resolved.response) || !inbound || !pallet) {
|
||||||
|
blockerGap(context, `OVERSEAS箱唛${cartonMark}归属`,
|
||||||
|
`冻结箱唛未解析到带真实托盘的本柜入仓码;inboundCode=${resolved.data?.inboundCode || 'missing'}`,
|
||||||
|
'fixture-mismatch', resolved.response)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const body = { cartonMark, palletCode: pallet.palletCode,
|
||||||
|
idempotencyKey: key(context, 'overseas-carton', cartonMark) }
|
||||||
|
const first = await pda(context, token, 'POST', `OVERSEAS逐箱实收${index + 1}`,
|
||||||
|
'/api/pda/v1/last-leg/receiving-containers/current/receipts/carton', body)
|
||||||
|
if (!success(first.response)) return false
|
||||||
|
const replay = await pda(context, token, 'POST', `OVERSEAS逐箱${index + 1}同键同载荷重放`,
|
||||||
|
'/api/pda/v1/last-leg/receiving-containers/current/receipts/carton', body)
|
||||||
|
context.assert(MODULE, `OVERSEAS逐箱${index + 1}幂等结果一致`, replay.response,
|
||||||
|
success(replay.response) && JSON.stringify(replay.data) === JSON.stringify(first.data), trace(replay.response), OPTIONS)
|
||||||
|
await expected(context, token, 'POST', `OVERSEAS逐箱${index + 1}同键异载荷冲突`,
|
||||||
|
'/api/pda/v1/last-leg/receiving-containers/current/receipts/carton',
|
||||||
|
{ ...body, palletCode: `${pallet.palletCode}-MUTATED` }, conflict)
|
||||||
|
await expected(context, token, 'POST', `OVERSEAS重复箱新键被拒绝${index + 1}`,
|
||||||
|
'/api/pda/v1/last-leg/receiving-containers/current/receipts/carton',
|
||||||
|
{ ...body, idempotencyKey: key(context, 'overseas-duplicate', cartonMark) }, conflict)
|
||||||
}
|
}
|
||||||
const body = { cartonMark, palletCode: pallet.palletCode, idempotencyKey: key(context, 'overseas-carton') }
|
|
||||||
const first = await pda(context, token, 'POST', 'OVERSEAS逐箱实收', '/api/pda/v1/last-leg/receiving-containers/current/receipts/carton', body)
|
|
||||||
if (!success(first.response)) return false
|
|
||||||
const replay = await pda(context, token, 'POST', 'OVERSEAS逐箱同键同载荷重放', '/api/pda/v1/last-leg/receiving-containers/current/receipts/carton', body)
|
|
||||||
context.assert(MODULE, 'OVERSEAS逐箱幂等结果一致', replay.response,
|
|
||||||
success(replay.response) && JSON.stringify(replay.data) === JSON.stringify(first.data), trace(replay.response), OPTIONS)
|
|
||||||
await expected(context, token, 'POST', 'OVERSEAS逐箱同键异载荷冲突', '/api/pda/v1/last-leg/receiving-containers/current/receipts/carton',
|
|
||||||
{ ...body, palletCode: `${pallet.palletCode}-MUTATED` }, conflict)
|
|
||||||
await expected(context, token, 'POST', 'OVERSEAS重复箱新键被拒绝', '/api/pda/v1/last-leg/receiving-containers/current/receipts/carton',
|
|
||||||
{ ...body, idempotencyKey: key(context, 'overseas-duplicate') }, conflict)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +325,7 @@ async function runReceiving(context, token, secondaryToken, merchantToken, prefe
|
|||||||
success(resolved.response) && resolved.data?.compatible === true && resolved.data?.destinationType === inbound.destinationType, trace(resolved.response), OPTIONS)
|
success(resolved.response) && resolved.data?.compatible === true && resolved.data?.destinationType === inbound.destinationType, trace(resolved.response), OPTIONS)
|
||||||
}
|
}
|
||||||
const fbaInbounds = current.data.inbounds.filter((item) => item.destinationType === 'FBA')
|
const fbaInbounds = current.data.inbounds.filter((item) => item.destinationType === 'FBA')
|
||||||
const overseasInbound = current.data.inbounds.find((item) => item.destinationType === 'OVERSEAS')
|
const overseasInbounds = current.data.inbounds.filter((item) => item.destinationType === 'OVERSEAS')
|
||||||
if (fbaInbounds.length && fbaInbounds.every((item) => pallets.has(item.inboundCode))) {
|
if (fbaInbounds.length && fbaInbounds.every((item) => pallets.has(item.inboundCode))) {
|
||||||
const firstFba = fbaInbounds[0]
|
const firstFba = fbaInbounds[0]
|
||||||
await expected(context, token, 'POST', 'FBA模式硬拒绝逐箱收货', '/api/pda/v1/last-leg/receiving-containers/current/receipts/carton', {
|
await expected(context, token, 'POST', 'FBA模式硬拒绝逐箱收货', '/api/pda/v1/last-leg/receiving-containers/current/receipts/carton', {
|
||||||
@@ -323,13 +336,15 @@ async function runReceiving(context, token, secondaryToken, merchantToken, prefe
|
|||||||
} else if (preferredProducer?.destinationType !== 'OVERSEAS') {
|
} else if (preferredProducer?.destinationType !== 'OVERSEAS') {
|
||||||
blockerGap(context, 'FBA SKU按箱数链', '当前FBA柜没有带真实托盘的 FBA 入仓码')
|
blockerGap(context, 'FBA SKU按箱数链', '当前FBA柜没有带真实托盘的 FBA 入仓码')
|
||||||
}
|
}
|
||||||
if (overseasInbound && pallets.has(overseasInbound.inboundCode)) {
|
if (overseasInbounds.length && overseasInbounds.every((item) => pallets.has(item.inboundCode))) {
|
||||||
const pallet = pallets.get(overseasInbound.inboundCode)
|
const firstOverseas = overseasInbounds[0]
|
||||||
|
const pallet = pallets.get(firstOverseas.inboundCode)
|
||||||
await expected(context, token, 'POST', 'OVERSEAS模式硬拒绝批量收货', '/api/pda/v1/last-leg/receiving-containers/current/receipts/bulk', {
|
await expected(context, token, 'POST', 'OVERSEAS模式硬拒绝批量收货', '/api/pda/v1/last-leg/receiving-containers/current/receipts/bulk', {
|
||||||
inboundCode: overseasInbound.inboundCode, skuCode: 'MODE-REJECT', palletCode: pallet.palletCode,
|
inboundCode: firstOverseas.inboundCode, skuCode: 'MODE-REJECT', palletCode: pallet.palletCode,
|
||||||
packageCount: 1, idempotencyKey: key(context, 'overseas-bulk-forbidden'),
|
packageCount: 1, idempotencyKey: key(context, 'overseas-bulk-forbidden'),
|
||||||
}, (response) => conflictMessage(response, /OVERSEAS.*carton|bulk.*forbidden/i))
|
}, (response) => conflictMessage(response, /OVERSEAS.*carton|bulk.*forbidden/i))
|
||||||
await receiveOverseas(context, token, overseasInbound, pallet, preferredProducer?.cartonMark)
|
await receiveOverseas(context, token, overseasInbounds, pallets,
|
||||||
|
preferredProducer?.cartonMarks?.length ? preferredProducer.cartonMarks : [preferredProducer?.cartonMark])
|
||||||
} else if (preferredProducer?.destinationType !== 'FBA') {
|
} else if (preferredProducer?.destinationType !== 'FBA') {
|
||||||
blockerGap(context, 'OVERSEAS逐箱链', '当前OVERSEAS柜没有带真实冻结箱唛与托盘的 OVERSEAS 入仓码')
|
blockerGap(context, 'OVERSEAS逐箱链', '当前OVERSEAS柜没有带真实冻结箱唛与托盘的 OVERSEAS 入仓码')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user