test: release bookings before opening PDA
This commit is contained in:
@@ -342,10 +342,65 @@ export async function run(context) {
|
||||
context.addStep(MODULE, '已有排货后拒绝解绑或换绑', unlinkAfterLoadingResponse,
|
||||
failure(unlinkAfterLoadingResponse), `HTTP=${unlinkAfterLoadingResponse.http}, code=${codeOf(unlinkAfterLoadingResponse)}`, OPTIONS)
|
||||
|
||||
const missingContainerPushResponse = await request(context, 'POST', `${SMART}/plans/${planId}/push-preload`,
|
||||
{ expectedVersion: Number(assigned.version) },
|
||||
{ 'Idempotency-Key': `${context.marker}-container-push-without-container-no`.slice(0, 128) })
|
||||
const missingContainerPushRejected = failure(missingContainerPushResponse)
|
||||
context.addStep(MODULE, '无真实柜号拒绝推送预装计划', missingContainerPushResponse,
|
||||
missingContainerPushRejected,
|
||||
`HTTP=${missingContainerPushResponse.http}, code=${codeOf(missingContainerPushResponse)}, requestId=${missingContainerPushResponse.requestId || 'missing'}, traceId=${missingContainerPushResponse.traceId || 'missing'}`, OPTIONS)
|
||||
if (!missingContainerPushRejected) return
|
||||
const gatedTaskResponse = await context.request('GET',
|
||||
`${PDA}/tasks?loadingPlanId=${encodeURIComponent(planId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const gatedTaskAbsent = failure(gatedTaskResponse)
|
||||
|| !id(gatedTaskResponse?.body?.id || gatedTaskResponse?.body?.data?.id)
|
||||
context.addStep(MODULE, '无真实柜号推送失败后不生成 PDA 任务', gatedTaskResponse,
|
||||
gatedTaskAbsent,
|
||||
`HTTP=${gatedTaskResponse.http}, code=${codeOf(gatedTaskResponse)}, task=${id(gatedTaskResponse?.body?.id || gatedTaskResponse?.body?.data?.id) || 'none'}`, OPTIONS)
|
||||
if (!gatedTaskAbsent) return
|
||||
|
||||
const transportResponse = await request(context, 'PATCH', `${CONTAINERS}/${containerId}/transport`, {
|
||||
containerNo: `TST-${suffix}`.slice(0, 64), voyageNo: `V-${suffix}`.slice(0, 64),
|
||||
version: Number(linkedContainer.version), reason: context.marker,
|
||||
})
|
||||
const transported = context.apiSuccess(MODULE, '在放舱前绑定本轮真实箱号', transportResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '绑定后柜次保留本轮箱号', transportResponse,
|
||||
transported?.containerNo === `TST-${suffix}`.slice(0, 64)
|
||||
&& id(transported?.id) === containerId && transported?.cabinetNo === linkedContainer.cabinetNo,
|
||||
`containerNo=${transported?.containerNo || 'missing'}, containerBookingId=${id(transported?.id) || 'missing'}, loadingPlanId=${planId}`, OPTIONS)
|
||||
if (transported?.containerNo !== `TST-${suffix}`.slice(0, 64)) return
|
||||
|
||||
const confirmedTaskResponse = await context.request('GET',
|
||||
`${PDA}/tasks?loadingPlanId=${encodeURIComponent(planId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const confirmedTaskAbsent = failure(confirmedTaskResponse)
|
||||
|| !id(confirmedTaskResponse?.body?.id || confirmedTaskResponse?.body?.data?.id)
|
||||
context.addStep(MODULE, '订舱 CONFIRMED 即使已有真实柜号也不开放 PDA 任务', confirmedTaskResponse,
|
||||
confirmedTaskAbsent,
|
||||
`HTTP=${confirmedTaskResponse.http}, code=${codeOf(confirmedTaskResponse)}, task=${id(confirmedTaskResponse?.body?.id || confirmedTaskResponse?.body?.data?.id) || 'none'}`, OPTIONS)
|
||||
if (!confirmedTaskAbsent) return
|
||||
|
||||
const sailingAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()
|
||||
const arrivalAt = new Date(Date.now() + 20 * 24 * 60 * 60 * 1000).toISOString()
|
||||
const releaseResponse = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/release`, {
|
||||
expectedVersion: Number(confirmedBooking?.oceanBooking?.version),
|
||||
billOfLadingNo: `BL-${suffix}`.slice(0, 128), vesselName: `VESSEL-${suffix}`.slice(0, 128),
|
||||
voyageNo: `V-${suffix}`.slice(0, 64), estimatedSailingAt: sailingAt,
|
||||
estimatedArrivalAt: arrivalAt, reason: context.marker,
|
||||
}, { 'Idempotency-Key': `${context.marker}-loading-booking-release`.slice(0, 128) })
|
||||
const releasedBooking = context.apiSuccess(MODULE, '订舱五项运输字段放舱并满足 PDA 订舱门禁', releaseResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '放舱后订舱进入 RELEASED 且柜次分配保持稳定', releaseResponse,
|
||||
releasedBooking?.oceanBooking?.status === 'RELEASED'
|
||||
&& releasedBooking?.containerAllocations?.some?.((item) => id(item.containerBookingId) === containerId),
|
||||
`status=${releasedBooking?.oceanBooking?.status || 'missing'}, containerBookingId=${containerId}`, OPTIONS)
|
||||
if (releasedBooking?.oceanBooking?.status !== 'RELEASED') return
|
||||
|
||||
const releasedContainerResponse = await context.request('GET', `${CONTAINERS}/${containerId}`)
|
||||
const releasedContainer = context.apiSuccess(MODULE, '放舱后回查货柜运输版本', releasedContainerResponse, '', OPTIONS)
|
||||
|
||||
const pushKey = `${context.marker}-container-push`.slice(0, 128)
|
||||
const pushResponse = await request(context, 'POST', `${SMART}/plans/${planId}/push-preload`,
|
||||
{ expectedVersion: Number(assigned.version) }, { 'Idempotency-Key': pushKey })
|
||||
const pushed = context.apiSuccess(MODULE, '推送预装计划', pushResponse, '', OPTIONS)
|
||||
const pushed = context.apiSuccess(MODULE, '柜号与 RELEASED 订舱就绪后推送预装计划', pushResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '推送后计划保持 EDITING 且版本递增', pushResponse,
|
||||
pushed?.status === 'EDITING' && Number(pushed?.version) === Number(assigned.version) + 1,
|
||||
`status=${pushed?.status || 'missing'}, version=${pushed?.version ?? 'missing'}`, OPTIONS)
|
||||
@@ -359,12 +414,6 @@ export async function run(context) {
|
||||
&& Number(pushReplay?.version) === Number(pushed.version),
|
||||
`planId=${id(pushReplay?.id) || 'missing'}, version=${pushReplay?.version ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const gatedTaskResponse = await context.request('GET',
|
||||
`${PDA}/tasks?loadingPlanId=${encodeURIComponent(planId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
context.addStep(MODULE, '无真实柜号时计划可排柜但 PDA 任务不开放', gatedTaskResponse,
|
||||
failure(gatedTaskResponse) || !id(gatedTaskResponse?.body?.id || gatedTaskResponse?.body?.data?.id),
|
||||
`HTTP=${gatedTaskResponse.http}, code=${codeOf(gatedTaskResponse)}, task=${id(gatedTaskResponse?.body?.id || gatedTaskResponse?.body?.data?.id) || 'none'}`, OPTIONS)
|
||||
|
||||
const preloadPageResponse = await context.request('GET',
|
||||
`${FIRST_LEG}/loading-plans?warehouseId=${encodeURIComponent(warehouseId)}&keyword=${encodeURIComponent(containerNo)}&page=1&size=20`)
|
||||
const preloadPage = context.apiSuccess(MODULE, '查询本轮预装清单', preloadPageResponse, '', OPTIONS)
|
||||
@@ -372,38 +421,6 @@ export async function run(context) {
|
||||
records(preloadPage).some((item) => id(item.planId) === planId && item.status === 'EDITING'),
|
||||
`records=${records(preloadPage).length}`, OPTIONS)
|
||||
|
||||
const transportResponse = await request(context, 'PATCH', `${CONTAINERS}/${containerId}/transport`, {
|
||||
containerNo: `TST-${suffix}`.slice(0, 64), voyageNo: `V-${suffix}`.slice(0, 64),
|
||||
version: Number(linkedContainer.version), reason: context.marker,
|
||||
})
|
||||
const transported = context.apiSuccess(MODULE, '在放舱前绑定本轮真实箱号', transportResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '绑定后柜次保留本轮箱号', transportResponse,
|
||||
transported?.containerNo === `TST-${suffix}`.slice(0, 64),
|
||||
`containerNo=${transported?.containerNo || 'missing'}`, OPTIONS)
|
||||
|
||||
const confirmedTaskResponse = await context.request('GET',
|
||||
`${PDA}/tasks?loadingPlanId=${encodeURIComponent(planId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
context.addStep(MODULE, '订舱 CONFIRMED 即使已有真实柜号也不开放 PDA 任务', confirmedTaskResponse,
|
||||
failure(confirmedTaskResponse) || !id(confirmedTaskResponse?.body?.id || confirmedTaskResponse?.body?.data?.id),
|
||||
`HTTP=${confirmedTaskResponse.http}, code=${codeOf(confirmedTaskResponse)}, task=${id(confirmedTaskResponse?.body?.id || confirmedTaskResponse?.body?.data?.id) || 'none'}`, OPTIONS)
|
||||
|
||||
const sailingAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()
|
||||
const arrivalAt = new Date(Date.now() + 20 * 24 * 60 * 60 * 1000).toISOString()
|
||||
const releaseResponse = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/release`, {
|
||||
expectedVersion: Number(confirmedBooking?.oceanBooking?.version),
|
||||
billOfLadingNo: `BL-${suffix}`.slice(0, 128), vesselName: `VESSEL-${suffix}`.slice(0, 128),
|
||||
voyageNo: `V-${suffix}`.slice(0, 64), estimatedSailingAt: sailingAt,
|
||||
estimatedArrivalAt: arrivalAt, reason: context.marker,
|
||||
}, { 'Idempotency-Key': `${context.marker}-loading-booking-release`.slice(0, 128) })
|
||||
const releasedBooking = context.apiSuccess(MODULE, '订舱放舱并开放现场 PDA 任务', releaseResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '放舱后订舱进入 RELEASED 且柜次分配保持稳定', releaseResponse,
|
||||
releasedBooking?.oceanBooking?.status === 'RELEASED'
|
||||
&& releasedBooking?.containerAllocations?.some?.((item) => id(item.containerBookingId) === containerId),
|
||||
`status=${releasedBooking?.oceanBooking?.status || 'missing'}, containerBookingId=${containerId}`, OPTIONS)
|
||||
|
||||
const releasedContainerResponse = await context.request('GET', `${CONTAINERS}/${containerId}`)
|
||||
const releasedContainer = context.apiSuccess(MODULE, '放舱后回查货柜运输版本', releasedContainerResponse, '', OPTIONS)
|
||||
|
||||
const taskResponse = await context.request('GET',
|
||||
`${PDA}/tasks?loadingPlanId=${encodeURIComponent(planId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const task = context.apiSuccess(MODULE, '查询本轮柜次 PDA 拣货任务', taskResponse, '', OPTIONS)
|
||||
|
||||
@@ -74,11 +74,13 @@ test('existing container number replacement keeps container plan and PDA identit
|
||||
})
|
||||
|
||||
test('PDA task opens only after preload push, real container number and ocean release converge', () => {
|
||||
assert.match(containerLoadingAcceptance, /无真实柜号时计划可排柜但 PDA 任务不开放/)
|
||||
assert.match(containerLoadingAcceptance, /无真实柜号拒绝推送预装计划/)
|
||||
assert.match(containerLoadingAcceptance, /无真实柜号推送失败后不生成 PDA 任务/)
|
||||
assert.match(containerLoadingAcceptance, /version: Number\(linkedContainer\.version\), reason: context\.marker/)
|
||||
assert.match(containerLoadingAcceptance, /订舱 CONFIRMED 即使已有真实柜号也不开放 PDA 任务/)
|
||||
assert.match(containerLoadingAcceptance, /ocean-bookings\/\$\{bookingId\}\/release/)
|
||||
assert.match(containerLoadingAcceptance, /订舱放舱并开放现场 PDA 任务/)
|
||||
assert.match(containerLoadingAcceptance, /订舱五项运输字段放舱并满足 PDA 订舱门禁/)
|
||||
assert.match(containerLoadingAcceptance, /柜号与 RELEASED 订舱就绪后推送预装计划/)
|
||||
assert.match(containerLoadingAcceptance, /releasedBooking\?\.oceanBooking\?\.status === 'RELEASED'/)
|
||||
})
|
||||
|
||||
|
||||
@@ -274,10 +274,34 @@ export async function run(context) {
|
||||
`containerNo=${transported?.containerNo || 'missing'}, containerBookingId=${id(transported?.id) || 'missing'}, loadingPlanId=${loadingPlanId}`, OPTIONS)
|
||||
if (transported?.containerNo !== containerNo) return
|
||||
|
||||
const confirmedBookingTaskResponse = await context.request('GET',
|
||||
`${pdaBase}/tasks?loadingPlanId=${encodeURIComponent(loadingPlanId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const confirmedBookingTaskAbsent = failure(confirmedBookingTaskResponse)
|
||||
|| !id(confirmedBookingTaskResponse?.body?.id || confirmedBookingTaskResponse?.body?.data?.id)
|
||||
context.addStep(MODULE, '订舱 CONFIRMED 即使已绑定真实柜号也不生成 PDA 任务', confirmedBookingTaskResponse,
|
||||
confirmedBookingTaskAbsent,
|
||||
`HTTP=${confirmedBookingTaskResponse.http}, code=${codeOf(confirmedBookingTaskResponse)}, task=${id(confirmedBookingTaskResponse?.body?.id || confirmedBookingTaskResponse?.body?.data?.id) || 'none'}`, OPTIONS)
|
||||
if (!confirmedBookingTaskAbsent) return
|
||||
|
||||
const sailingAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()
|
||||
const arrivalAt = new Date(Date.now() + 20 * 24 * 60 * 60 * 1000).toISOString()
|
||||
const releaseResponse = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/release`, {
|
||||
expectedVersion: Number(bookingConfirmed?.oceanBooking?.version),
|
||||
billOfLadingNo: `P3BL-${suffix}`.slice(0, 128), vesselName: `P3VESSEL-${suffix}`.slice(0, 128),
|
||||
voyageNo: `P3V-${suffix}`.slice(0, 64), estimatedSailingAt: sailingAt,
|
||||
estimatedArrivalAt: arrivalAt, reason: context.marker,
|
||||
}, { 'Idempotency-Key': `${context.marker}-p3-booking-release`.slice(0, 128) })
|
||||
const bookingReleased = context.apiSuccess(MODULE, '三期订舱补齐五项运输字段并放舱', releaseResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '三期订舱 RELEASED 后保持模拟柜关联身份', releaseResponse,
|
||||
bookingReleased?.oceanBooking?.status === 'RELEASED'
|
||||
&& bookingReleased?.containerAllocations?.some?.((item) => id(item.containerBookingId) === containerBookingId),
|
||||
`status=${bookingReleased?.oceanBooking?.status || 'missing'}, containerBookingId=${containerBookingId}, loadingPlanId=${loadingPlanId}`, OPTIONS)
|
||||
if (bookingReleased?.oceanBooking?.status !== 'RELEASED') return
|
||||
|
||||
const pushResponse = await request(context, 'POST', `${smartLoadingBase}/plans/${loadingPlanId}/push-preload`,
|
||||
{ expectedVersion: Number(link.loadingPlanVersion) },
|
||||
{ 'Idempotency-Key': `${context.marker}-bridge-push`.slice(0, 128) })
|
||||
const pushedLoadingPlan = context.apiSuccess(MODULE, '绑定真实柜号后推送柜次计划并生成 PDA 任务', pushResponse, '', OPTIONS)
|
||||
const pushedLoadingPlan = context.apiSuccess(MODULE, '真实柜号与 RELEASED 订舱就绪后推送并生成 PDA 任务', pushResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '推送后柜次计划保持 EDITING 且版本递增', pushResponse,
|
||||
pushedLoadingPlan?.status === 'EDITING'
|
||||
&& Number(pushedLoadingPlan?.version) === Number(link.loadingPlanVersion) + 1,
|
||||
|
||||
@@ -90,7 +90,10 @@ test('bridge loading starts through a real PDA batch before preload confirmation
|
||||
'无真实柜号拒绝推送柜次计划',
|
||||
'无真实柜号推送失败后不生成 PDA 任务',
|
||||
'绑定真实柜号后开放三期 PDA 推送门禁',
|
||||
'绑定真实柜号后推送柜次计划并生成 PDA 任务',
|
||||
'订舱 CONFIRMED 即使已绑定真实柜号也不生成 PDA 任务',
|
||||
'三期订舱补齐五项运输字段并放舱',
|
||||
'三期订舱 RELEASED 后保持模拟柜关联身份',
|
||||
'真实柜号与 RELEASED 订舱就绪后推送并生成 PDA 任务',
|
||||
"pushedLoadingPlan?.status === 'EDITING'",
|
||||
"claimedPlan?.status === 'LOADING'",
|
||||
"const canonicalPick = await request",
|
||||
@@ -107,6 +110,8 @@ test('bridge loading starts through a real PDA batch before preload confirmation
|
||||
assert.match(value, /const missingContainerTaskAbsent =[\s\S]*if \(!missingContainerTaskAbsent\) return/)
|
||||
assert.match(value, /version: transportVersion/)
|
||||
assert.match(value, /Number\.isInteger\(transportVersion\)/)
|
||||
assert.match(value, /billOfLadingNo:[\s\S]*vesselName:[\s\S]*voyageNo:[\s\S]*estimatedSailingAt:[\s\S]*estimatedArrivalAt:/)
|
||||
assert.match(value, /bookingReleased\?\.oceanBooking\?\.status === 'RELEASED'/)
|
||||
})
|
||||
|
||||
test('PDA inventory snapshot gates batch and records the only safe mutation gap', () => {
|
||||
@@ -178,7 +183,10 @@ test('container loading arranges a simulation before linking a confirmed real bo
|
||||
assert.match(value, /loadingPlanId: planId, containerBookingId: containerId/)
|
||||
assert.ok(value.includes("'PATCH', `${CONTAINERS}/${containerId}/transport"))
|
||||
assert.match(value, /在放舱前绑定本轮真实箱号/)
|
||||
assert.match(value, /订舱放舱并开放现场 PDA 任务/)
|
||||
assert.match(value, /订舱五项运输字段放舱并满足 PDA 订舱门禁/)
|
||||
assert.match(value, /无真实柜号拒绝推送预装计划/)
|
||||
assert.match(value, /柜号与 RELEASED 订舱就绪后推送预装计划/)
|
||||
assert.match(value, /const confirmedTaskAbsent =[\s\S]*if \(!confirmedTaskAbsent\) return/)
|
||||
assert.match(value, /POST', CONTAINERS/)
|
||||
})
|
||||
|
||||
@@ -251,10 +259,12 @@ test('container loading enforces channel isolation and stable PDA identity acros
|
||||
'两个真实有效渠道之间拒绝跨渠道装柜',
|
||||
'人工分配本轮候选货物',
|
||||
'首次关联真实已确认订舱柜',
|
||||
'无真实柜号时计划可排柜但 PDA 任务不开放',
|
||||
'无真实柜号拒绝推送预装计划',
|
||||
'无真实柜号推送失败后不生成 PDA 任务',
|
||||
'在放舱前绑定本轮真实箱号',
|
||||
'订舱 CONFIRMED 即使已有真实柜号也不开放 PDA 任务',
|
||||
'订舱放舱并开放现场 PDA 任务',
|
||||
'订舱五项运输字段放舱并满足 PDA 订舱门禁',
|
||||
'柜号与 RELEASED 订舱就绪后推送预装计划',
|
||||
'换柜号前读取 PDA 稳定库存快照',
|
||||
'PDA 任务创建后替换真实柜号',
|
||||
'PDA 任务创建后清空真实柜号失败',
|
||||
|
||||
@@ -73,11 +73,22 @@ test('test-environment loading chains bind the latest container version before o
|
||||
const bridgeIndex = bridgeLoading.indexOf('原子关联入库预装与柜次装柜计划')
|
||||
const rejectedPushIndex = bridgeLoading.indexOf('无真实柜号拒绝推送柜次计划')
|
||||
const transportIndex = bridgeLoading.indexOf('绑定真实柜号后开放三期 PDA 推送门禁')
|
||||
const successfulPushIndex = bridgeLoading.indexOf('绑定真实柜号后推送柜次计划并生成 PDA 任务')
|
||||
const releaseIndex = bridgeLoading.indexOf('三期订舱补齐五项运输字段并放舱')
|
||||
const successfulPushIndex = bridgeLoading.indexOf('真实柜号与 RELEASED 订舱就绪后推送并生成 PDA 任务')
|
||||
const pdaIndex = bridgeLoading.indexOf('读取柜次级 PDA 唯一事实任务')
|
||||
assert.ok(bridgeIndex >= 0 && rejectedPushIndex > bridgeIndex && transportIndex > rejectedPushIndex
|
||||
&& successfulPushIndex > transportIndex && pdaIndex > successfulPushIndex)
|
||||
&& releaseIndex > transportIndex && successfulPushIndex > releaseIndex && pdaIndex > successfulPushIndex)
|
||||
assert.match(bridgeLoading, /const missingContainerPushRejected = failure\(missingContainerPushResponse\)[\s\S]*if \(!missingContainerPushRejected\) return/)
|
||||
assert.match(bridgeLoading, /const missingContainerTaskAbsent =[\s\S]*if \(!missingContainerTaskAbsent\) return/)
|
||||
assert.match(bridgeLoading, /version: transportVersion/)
|
||||
assert.match(bridgeLoading, /bookingReleased\?\.oceanBooking\?\.status === 'RELEASED'/)
|
||||
|
||||
const containerRejectedPushIndex = containerLoading.indexOf('无真实柜号拒绝推送预装计划')
|
||||
const containerTransportIndex = containerLoading.indexOf('在放舱前绑定本轮真实箱号')
|
||||
const containerReleaseIndex = containerLoading.indexOf('订舱五项运输字段放舱并满足 PDA 订舱门禁')
|
||||
const containerSuccessfulPushIndex = containerLoading.indexOf('柜号与 RELEASED 订舱就绪后推送预装计划')
|
||||
const containerPdaIndex = containerLoading.indexOf('查询本轮柜次 PDA 拣货任务')
|
||||
assert.ok(containerRejectedPushIndex >= 0 && containerTransportIndex > containerRejectedPushIndex
|
||||
&& containerReleaseIndex > containerTransportIndex && containerSuccessfulPushIndex > containerReleaseIndex
|
||||
&& containerPdaIndex > containerSuccessfulPushIndex)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user