From 4977b09af95b5928ac4cb087881c32d5a360d931 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 24 Aug 2026 11:49:31 +0800 Subject: [PATCH] fix phase3 loading acceptance sequence --- ...e3-loading-dynamic-final-contract.test.mjs | 3 +- .../admin-test-env-modules/phase3-loading.mjs | 48 ++++++------------- .../phase3-producer-chain-contract.test.mjs | 4 +- 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/scripts/admin-test-env-modules/phase3-loading-dynamic-final-contract.test.mjs b/scripts/admin-test-env-modules/phase3-loading-dynamic-final-contract.test.mjs index e3d6a3c4f..479d296a9 100644 --- a/scripts/admin-test-env-modules/phase3-loading-dynamic-final-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase3-loading-dynamic-final-contract.test.mjs @@ -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=/) }) diff --git a/scripts/admin-test-env-modules/phase3-loading.mjs b/scripts/admin-test-env-modules/phase3-loading.mjs index 2da661cf5..42923a2f0 100644 --- a/scripts/admin-test-env-modules/phase3-loading.mjs +++ b/scripts/admin-test-env-modules/phase3-loading.mjs @@ -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', } } diff --git a/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs b/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs index 15aa9de48..5178677cd 100644 --- a/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs @@ -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', () => {