This commit is contained in:
2026-08-14 16:06:27 +08:00
parent 87eb1aa72f
commit 87e919f3c4
54 changed files with 984 additions and 175 deletions
@@ -201,7 +201,7 @@ export async function run(context) {
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, '推送预装并创建 PDA 任务', pushResponse, '', OPTIONS)
const pushed = context.apiSuccess(MODULE, '推送预装计划', 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)
@@ -232,11 +232,34 @@ export async function run(context) {
containerNo: `TST-${suffix}`.slice(0, 64), voyageNo: `V-${suffix}`.slice(0, 64),
version: Number(containerDetail?.version ?? 0), reason: context.marker,
})
const transported = context.apiSuccess(MODULE, '绑定本轮真实箱号并补建 PDA 任务', transportResponse, '', OPTIONS)
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)
@@ -263,7 +286,7 @@ export async function run(context) {
const replacementContainerNo = `TST-R-${suffix}`.slice(0, 64)
const replacementResponse = await request(context, 'PATCH', `${CONTAINERS}/${containerId}/transport`, {
containerNo: replacementContainerNo, voyageNo: `V-${suffix}`.slice(0, 64),
version: Number(transported?.version), reason: `${context.marker}-replace-container-no`,
version: Number(releasedContainer?.version), reason: `${context.marker}-replace-container-no`,
})
const replaced = context.apiSuccess(MODULE, 'PDA 任务创建后替换真实柜号', replacementResponse, '', OPTIONS)
context.assert(MODULE, '任务创建后真实柜号替换成功', replacementResponse,
@@ -73,6 +73,14 @@ test('existing container number replacement keeps container plan and PDA identit
assert.match(containerLoadingAcceptance, /pdaIdentityStable: true/)
})
test('PDA task opens only after preload push, real container number and ocean release converge', () => {
assert.match(containerLoadingAcceptance, /无真实柜号时计划可排柜但 PDA 任务不开放/)
assert.match(containerLoadingAcceptance, /订舱 CONFIRMED 即使已有真实柜号也不开放 PDA 任务/)
assert.match(containerLoadingAcceptance, /ocean-bookings\/\$\{bookingId\}\/release/)
assert.match(containerLoadingAcceptance, /订舱放舱并开放现场 PDA 任务/)
assert.match(containerLoadingAcceptance, /releasedBooking\?\.oceanBooking\?\.status === 'RELEASED'/)
})
test('customer public display uses tenantName without a legalName fallback', () => {
for (const source of [truckList, containerList]) {
assert.match(source, /label: customer\.tenantName/)
@@ -150,7 +150,8 @@ test('container loading creates and confirms booking before scoped candidates',
assert.match(value, /tasks\?loadingPlanId=\$\{encodeURIComponent\(planId\)\}&warehouseId=/)
assert.match(value, /loadingPlanId: planId, containerBookingId: containerId/)
assert.ok(value.includes("'PATCH', `${CONTAINERS}/${containerId}/transport"))
assert.match(value, /绑定本轮真实箱号并补建 PDA 任务/)
assert.match(value, /在放舱前绑定本轮真实箱号/)
assert.match(value, /订舱放舱并开放现场 PDA 任务/)
assert.doesNotMatch(value, /POST', CONTAINERS, containerPayload/)
})
@@ -188,7 +189,9 @@ test('container loading enforces channel isolation and stable PDA identity acros
'创建异渠道隔离订舱',
'异渠道入库单拒绝装入当前柜次',
'无真实柜号时计划可排柜但 PDA 任务不开放',
'绑定本轮真实箱号并补建 PDA 任务',
'在放舱前绑定本轮真实箱号',
'订舱 CONFIRMED 即使已有真实柜号也不开放 PDA 任务',
'订舱放舱并开放现场 PDA 任务',
'换柜号前读取 PDA 稳定库存快照',
'PDA 任务创建后替换真实柜号',
'PDA 任务创建后清空真实柜号失败',
@@ -64,7 +64,7 @@ export async function run(context) {
'all warehouse role permissions must use platform scope', OPTIONS)
const unauthorized = await context.request('GET',
'/api/platform/v1/first-leg/pda-loading/tasks?containerNo=UNAUTHORIZED&warehouseId=1', undefined, '')
'/api/platform/v1/first-leg/pda-loading/tasks?cabinetNo=UNAUTHORIZED&warehouseId=1', undefined, '')
context.addStep(MODULE, '未登录访问仓库PDA任务被拒绝', unauthorized,
[401, 403].includes(unauthorized.http)
|| (unauthorized.http === 200 && ['A0401', 'A0403', '401', '403'].includes(String(unauthorized.body?.code))),