fix phase3 loading acceptance sequence

This commit is contained in:
2026-08-24 11:49:31 +08:00
parent 257c019b36
commit 4977b09af9
3 changed files with 19 additions and 36 deletions
@@ -67,13 +67,14 @@ test('HTTP chain refreshes after stale inventory and keeps one stable PDA identi
'已拣项减量低于 minPackageCount 返回 A0409',
'已拣项删除返回 A0409',
'已拣项删除和减量失败后 plan/task/items 零写入',
'订舱或五类证据等保留门禁未齐备时 LOADED 拒绝',
'报关与最终完成前保持 LOADING 且不提前写 COMPLETE_LOADING',
])
assert.match(loading, /String\(staleIncrementBatch\.body\?\.message \|\| ''\)\.includes\('库存已变化,请刷新后重试'\)/)
assert.match(loading, /id\(incrementedTask\?\.loadingPlanId\) === loadingPlanId/)
assert.match(loading, /id\(incrementedTask\?\.containerBookingId\) === containerBookingId/)
assert.match(loading, /const taskDetailPath = `\$\{pdaBase\}\/tasks\/\$\{canonicalTaskId\}\?warehouseId=/)
assert.match(loading, /loadingPlanStatus: 'LOADING', finalStatus: 'AWAITING_REVIEW'/)
assert.doesNotMatch(loading, /complete-before-downstream/)
assert.doesNotMatch(loading, /\/loading\/complete\?tenantId=/)
})
@@ -1142,38 +1142,20 @@ export async function run(context) {
}
}
const beforeHardGateResponse = await context.request('GET', detailPath)
const beforeHardGate = context.apiSuccess(MODULE, '读取 LOADED 硬门禁调用前即时版本', beforeHardGateResponse, '', OPTIONS)
const beforeHardGateVersion = Number(beforeHardGate?.summary?.version)
context.assert(MODULE, 'LOADED 硬门禁即时基线状态和版本有效', beforeHardGateResponse,
beforeHardGate?.summary?.processStatus === 'LOADING' && Number.isInteger(beforeHardGateVersion),
`status=${beforeHardGate?.summary?.processStatus || 'missing'}, version=${beforeHardGate?.summary?.version ?? 'missing'}`, OPTIONS)
if (!Number.isInteger(beforeHardGateVersion)) return
const beforeBlockedTaskResponse = await context.request('GET', taskDetailPath)
const beforeBlockedTask = context.apiSuccess(MODULE, '读取下游门禁拒绝前 PDA 任务快照', beforeBlockedTaskResponse, '', OPTIONS)
const beforeBlockedPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
const beforeBlockedPlan = context.apiSuccess(MODULE, '读取下游门禁拒绝前计划快照', beforeBlockedPlanResponse, '', OPTIONS)
const blockedBeforeDownstream = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/complete`,
{ expectedTaskVersion: Number(beforeBlockedTask?.version),
operationId: `${context.marker}-complete-before-downstream`.slice(0, 128), remark: `${context.marker}-before-downstream` },
{ 'Idempotency-Key': `${context.marker}-p3-complete-before-downstream`.slice(0, 128) })
context.addStep(MODULE, '订舱或五类证据等保留门禁未齐备时 LOADED 拒绝', blockedBeforeDownstream,
blockedBeforeDownstream.http === 200 && codeOf(blockedBeforeDownstream) === 'A0409',
responseEvidence(blockedBeforeDownstream), OPTIONS)
const blockedDetailResponse = await context.request('GET', detailPath)
const blockedDetail = context.apiSuccess(MODULE, '回查 LOADED 硬门禁拒绝后的入库状态', blockedDetailResponse, '', OPTIONS)
const blockedTaskResponse = await context.request('GET', taskDetailPath)
const blockedTask = context.apiSuccess(MODULE, '回查 LOADED 硬门禁拒绝后的 PDA 任务', blockedTaskResponse, '', OPTIONS)
const blockedPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
const blockedPlan = context.apiSuccess(MODULE, '回查 LOADED 硬门禁拒绝后的计划', blockedPlanResponse, '', OPTIONS)
context.assert(MODULE, 'LOADED 硬门禁拒绝后 task/plan/inbound 零写入', blockedDetailResponse,
blockedDetail?.summary?.processStatus === 'LOADING'
&& Number(blockedDetail?.summary?.version) === beforeHardGateVersion
&& Number(blockedTask?.version) === Number(beforeBlockedTask?.version)
&& taskItemsSignature(blockedTask) === taskItemsSignature(beforeBlockedTask)
&& Number(blockedPlan?.version) === Number(beforeBlockedPlan?.version)
&& blockedPlan?.status === beforeBlockedPlan?.status,
`inboundStatus=${blockedDetail?.summary?.processStatus || 'missing'}, inboundVersion=${blockedDetail?.summary?.version ?? 'missing'}, taskVersion=${blockedTask?.version ?? 'missing'}, planVersion=${blockedPlan?.version ?? 'missing'}`, OPTIONS)
// 报关协同在本模块之后执行,最终 COMPLETE_LOADING 只由 phase3-loading-finalize 发起。
// 当前服务端明确不以拖车作为前置门禁,因此这里不得用一次预期失败的写请求维持 LOADING。
const stagedDetailResponse = await context.request('GET', detailPath)
const stagedDetail = context.apiSuccess(MODULE, '下游协同前读取装柜中入库状态', stagedDetailResponse, '', OPTIONS)
const stagedTaskResponse = await context.request('GET', taskDetailPath)
const stagedTask = context.apiSuccess(MODULE, '下游协同前读取待最终完成 PDA 任务', stagedTaskResponse, '', OPTIONS)
const stagedPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
const stagedPlan = context.apiSuccess(MODULE, '下游协同前读取装柜中计划', stagedPlanResponse, '', OPTIONS)
context.assert(MODULE, '报关与最终完成前保持 LOADING 且不提前写 COMPLETE_LOADING', stagedDetailResponse,
stagedDetail?.summary?.processStatus === 'LOADING'
&& stagedTask?.taskStatus === 'LOADING'
&& stagedTask?.workflowStatus === 'AWAITING_REVIEW'
&& stagedPlan?.status === 'LOADING',
`inboundStatus=${stagedDetail?.summary?.processStatus || 'missing'}, taskStatus=${stagedTask?.taskStatus || 'missing'}, workflow=${stagedTask?.workflowStatus || 'missing'}, planStatus=${stagedPlan?.status || 'missing'}`, OPTIONS)
context.createdResources.phase3Loading = {
tenantId, orderId: id(source?.orderId), inboundId, preloadPlanId: planId,
@@ -1189,7 +1171,7 @@ export async function run(context) {
inventorySnapshotVerified: inventorySnapshotVerified && atomicBatchReplayVerified,
inventoryMutationVerified, inventoryMutationCleanup,
dynamicIncrementVerified, incrementalInboundId,
pdaTaskVersion: Number(blockedTask?.version),
pdaTaskVersion: Number(stagedTask?.version),
marker: context.marker, inboundStatus: 'LOADING', loadingPlanStatus: 'LOADING', finalStatus: 'AWAITING_REVIEW',
}
}
@@ -445,9 +445,9 @@ test('phase3 loading proves stale inventory zero-write and defers final completi
'PDA 原子批次同参同键重放保持待人工确认',
'回查下游门禁前原子 Batch 审计',
'最终确认前仅保留 CLAIM/BATCH 且无 COMPLETE_LOADING',
'订舱或五类证据等保留门禁未齐备时 LOADED 拒绝',
'LOADED 硬门禁拒绝后 task/plan/inbound 零写入',
'报关与最终完成前保持 LOADING 且不提前写 COMPLETE_LOADING',
])
assert.doesNotMatch(loading, /complete-before-downstream/)
})
test('phase3 loading fixture forces A0 through reversible public location status APIs', () => {