This commit is contained in:
2026-08-17 15:41:42 +08:00
parent 4abcb2b80f
commit 5f79616623
60 changed files with 1457 additions and 270 deletions
+217 -50
View File
@@ -652,39 +652,28 @@ export async function run(context) {
atomicBatchReplayVerified,
`HTTP=${canonicalBatchReplay.http}, code=${codeOf(canonicalBatchReplay)}, taskVersion=${canonicalBatchReplay.body?.data?.version ?? 'missing'}`, OPTIONS)
const completeResponse = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/complete`, {
expectedTaskVersion: Number(pickedCanonical?.version),
operationId: `${context.marker}-bridge-complete`.slice(0, 128), remark: context.marker,
})
const completedCanonical = context.apiSuccess(MODULE, '人工确认柜次 PDA 装柜完成', completeResponse, '', OPTIONS)
const directPdaCompletion = completedCanonical?.taskStatus === 'COMPLETED'
&& completedCanonical?.workflowStatus === 'COMPLETED'
context.assert(MODULE, '人工确认后 PDA 任务进入 COMPLETED', completeResponse,
directPdaCompletion && completedCanonical?.items?.every?.((item) => item.itemStatus === 'PICKED'),
`taskStatus=${completedCanonical?.taskStatus || 'missing'}, workflow=${completedCanonical?.workflowStatus || 'missing'}`, OPTIONS)
const completionOperationsResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}/operations`)
const completionOperations = context.apiSuccess(MODULE, '回查原子 Batch 完成审计', completionOperationsResponse, '', OPTIONS)
context.assert(MODULE, '人工完成后保留 CLAIM/BATCH/COMPLETE_LOADING 审计', completionOperationsResponse,
Array.isArray(completionOperations)
&& completionOperations.some((item) => item.actionType === 'CLAIM'
const preFinalOperationsResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}/operations`)
const preFinalOperations = context.apiSuccess(MODULE, '回查下游门禁前原子 Batch 审计', preFinalOperationsResponse, '', OPTIONS)
context.assert(MODULE, '最终确认前仅保留 CLAIM/BATCH 且无 COMPLETE_LOADING', preFinalOperationsResponse,
Array.isArray(preFinalOperations)
&& preFinalOperations.some((item) => item.actionType === 'CLAIM'
&& id(item.operatorId) && Boolean(item.createdAt))
&& completionOperations.some((item) => item.actionType === 'BATCH'
&& preFinalOperations.some((item) => item.actionType === 'BATCH'
&& id(item.operatorId) && Boolean(item.createdAt))
&& completionOperations.some((item) => item.actionType === 'COMPLETE_LOADING'
&& id(item.operatorId) && Boolean(item.createdAt))
&& !completionOperations.some((item) => item.actionType === 'REVIEW'),
`actions=${Array.isArray(completionOperations) ? completionOperations.map((item) => item.actionType).join(',') : 'missing'}`, OPTIONS)
&& !preFinalOperations.some((item) => item.actionType === 'COMPLETE_LOADING')
&& !preFinalOperations.some((item) => item.actionType === 'REVIEW'),
`actions=${Array.isArray(preFinalOperations) ? preFinalOperations.map((item) => item.actionType).join(',') : 'missing'}`, OPTIONS)
const pickedPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
const pickedPlan = context.apiSuccess(MODULE, '首笔真实拣货后回查柜次计划', pickedPlanResponse, '', OPTIONS)
context.assert(MODULE, '人工确认后柜次装柜计划完成', pickedPlanResponse,
directPdaCompletion && pickedPlan?.status === 'COMPLETED',
context.assert(MODULE, '下游门禁前柜次计划保持 LOADING 且等待最终完成', pickedPlanResponse,
pickedPlan?.status === 'LOADING' && pickedPlan?.finalCompletionStatus === 'IN_PROGRESS'
&& id(pickedPlan?.pdaTaskId) === canonicalTaskId && pickedPlan?.pdaTaskStatus === 'LOADING',
`status=${pickedPlan?.status || 'missing'}, version=${pickedPlan?.version ?? 'missing'}`, OPTIONS)
const preload = await context.request('GET', `${base}/preload?tenantId=${encodeURIComponent(tenantId)}`)
const preloadData = context.apiSuccess(MODULE, '回查已确认预装清单', preload, '', OPTIONS)
context.assert(MODULE, 'PDA 完成后预装清单仍为 CONFIRMED 且归属稳定', preload,
context.assert(MODULE, 'PDA 最终完成前预装清单仍为 CONFIRMED 且归属稳定', preload,
id(preloadData?.id) === planId && id(preloadData?.inboundId) === inboundId && id(preloadData?.tenantId) === tenantId
&& preloadData?.status === 'CONFIRMED',
`planId=${id(preloadData?.id) || 'missing'}, items=${preloadData?.items?.length ?? 'n/a'}`, OPTIONS)
@@ -697,18 +686,6 @@ export async function run(context) {
`status=${loading?.summary?.processStatus || 'missing'}, version=${loading?.summary?.version ?? 'missing'}`, OPTIONS)
if (!Number.isInteger(loadingVersion)) return
// COMPLETE_LOADING is the only positive completion action; REVIEW is a
// negative compatibility assertion after the task is already complete.
const canonicalReview = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/review`, {
expectedTaskVersion: Number(completedCanonical?.version),
operationId: `${context.marker}-bridge-review`.slice(0, 128), remark: context.marker,
})
context.addStep(MODULE, '柜次 PDA 已完成后拒绝重复复核', canonicalReview,
directPdaCompletion
? canonicalReview.http === 200 && codeOf(canonicalReview) === 'A0409'
: canonicalReview.http === 200 && codeOf(canonicalReview) === '200',
`HTTP=${canonicalReview.http}, code=${codeOf(canonicalReview)}`, OPTIONS)
context.addStep(MODULE, '柜次 PDA 作为现场作业唯一事实源', null, true,
'本场景不调用旧入库级 picking/start 写接口,避免客户端汇总数量覆盖柜次任务事实', OPTIONS)
@@ -759,8 +736,9 @@ export async function run(context) {
`bytes=${download.bytes.length}, contentType=${download.contentType || 'missing'}`, OPTIONS)
if (evidenceType === 'PHOTO_STEP_4') {
const missingSeal = await request(context, 'POST', `${base}/loading/complete?tenantId=${encodeURIComponent(tenantId)}`,
{ expectedVersion: loadingVersion, remark: 'must require seal' },
const missingSeal = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/complete`,
{ expectedTaskVersion: Number(pickedCanonical?.version),
operationId: `${context.marker}-complete-before-seal`.slice(0, 128), remark: 'must require seal' },
{ 'Idempotency-Key': `${context.marker}-p3-complete-before-seal`.slice(0, 128) })
context.addStep(MODULE, '缺铅封照片和锁码时拒绝完成装柜', missingSeal, failure(missingSeal),
`HTTP=${missingSeal.http}, code=${codeOf(missingSeal)}`, OPTIONS)
@@ -864,8 +842,10 @@ export async function run(context) {
duplicateVoid.http === 200 && codeOf(duplicateVoid) === 'A0409',
`HTTP=${duplicateVoid.http}, code=${codeOf(duplicateVoid)}`, OPTIONS)
const missingTypeComplete = await request(context, 'POST', `${base}/loading/complete?tenantId=${encodeURIComponent(tenantId)}`,
{ expectedVersion: loadingVersion, remark: `${context.marker}-missing-photo-step-4` },
const missingTypeComplete = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/complete`,
{ expectedTaskVersion: Number(pickedCanonical?.version),
operationId: `${context.marker}-complete-missing-photo-step-4`.slice(0, 128),
remark: `${context.marker}-missing-photo-step-4` },
{ 'Idempotency-Key': `${context.marker}-p3-complete-missing-photo-step-4`.slice(0, 128) })
context.addStep(MODULE, '作废唯一证据类型后 LOADED 门禁拒绝', missingTypeComplete,
missingTypeComplete.http === 200 && codeOf(missingTypeComplete) === 'A0409',
@@ -916,6 +896,178 @@ export async function run(context) {
&& evidenceListData.evidences.some((item) => item.evidenceType === 'SEAL' && item.sealNo === `SEAL-${suffix}`),
`evidences=${evidenceListData?.evidences?.length ?? 'n/a'}`, OPTIONS)
const incrementalFixture = context.createdResources?.orchestrationFixtures?.phase3LoadingIncremental
const incrementalInboundId = id(incrementalFixture?.inboundId)
let dynamicIncrementVerified = false
if (!incrementalInboundId || incrementalFixture?.marker !== context.marker || incrementalInboundId === inboundId) {
context.addStep(MODULE, '取得本轮独立动态增量入库单', null, false,
`inboundId=${incrementalInboundId || 'missing'}, primaryInboundId=${inboundId}, marker=${incrementalFixture?.marker || 'missing'}`, OPTIONS)
} else {
const dynamicCandidatesResponse = await context.request('GET',
`${smartLoadingBase}/candidates?bookingId=${encodeURIComponent(containerBookingId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
const dynamicCandidates = context.apiSuccess(MODULE, '查询同柜动态增量候选', dynamicCandidatesResponse, '', OPTIONS)
const dynamicCandidate = Array.isArray(dynamicCandidates) ? dynamicCandidates.find((item) =>
id(item?.inboundId) === incrementalInboundId && Number(item?.availablePackageCount) > 0) : null
context.assert(MODULE, '动态增量候选保持客户、仓库和渠道隔离', dynamicCandidatesResponse,
Boolean(dynamicCandidate?.inboundDetailId) && id(dynamicCandidate?.accountId) === tenantId
&& id(dynamicCandidate?.warehouseId) === warehouseId
&& dynamicCandidate?.channelCode === initial?.summary?.channelCode,
`inboundId=${incrementalInboundId}, detailId=${id(dynamicCandidate?.inboundDetailId) || 'missing'}, available=${dynamicCandidate?.availablePackageCount ?? 'missing'}`, OPTIONS)
if (dynamicCandidate?.inboundDetailId) {
const planBeforeIncrementResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
const planBeforeIncrement = context.apiSuccess(MODULE, '读取 LOADING 动态增量前计划', planBeforeIncrementResponse, '', OPTIONS)
const taskBeforeIncrementResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
const taskBeforeIncrement = context.apiSuccess(MODULE, '读取 LOADING 动态增量前任务', taskBeforeIncrementResponse, '', OPTIONS)
const evidenceBeforeIncrement = JSON.stringify(evidenceListData?.evidences || [])
const incrementResponse = await request(context, 'POST',
`${smartLoadingBase}/plans/${loadingPlanId}/assignments`, {
inboundId: incrementalInboundId, inboundDetailId: id(dynamicCandidate.inboundDetailId), packageCount: 1,
expectedVersion: Number(planBeforeIncrement?.version),
operationId: `${context.marker}-dynamic-assign`.slice(0, 128),
})
const incrementedPlan = context.apiSuccess(MODULE, 'LOADING 阶段向同一 PDA task 增加入库单', incrementResponse, '', OPTIONS)
const incrementalPlanItem = incrementedPlan?.items?.find?.((item) => id(item?.inboundId) === incrementalInboundId)
const incrementedTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
const incrementedTask = context.apiSuccess(MODULE, '增量后回查稳定 PDA 四元身份', incrementedTaskResponse, '', OPTIONS)
const incrementalTaskItem = incrementedTask?.items?.find?.((item) =>
id(item?.loadingPlanItemId) === id(incrementalPlanItem?.id))
const evidenceAfterIncrementResponse = await context.request('GET', `${base}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`)
const evidenceAfterIncrement = context.apiSuccess(MODULE, '增量后回查既有五类证据', evidenceAfterIncrementResponse, '', OPTIONS)
context.assert(MODULE, '增量保持稳定四元身份、LOADING 不回退并撤回最终完成门禁', incrementResponse,
id(incrementedPlan?.id) === loadingPlanId && incrementedPlan?.status === 'LOADING'
&& incrementedPlan?.finalCompletionStatus === 'IN_PROGRESS'
&& id(incrementedPlan?.pdaTaskId) === canonicalTaskId && incrementedPlan?.pdaTaskStatus === 'LOADING'
&& Number(incrementedPlan?.version) > Number(planBeforeIncrement?.version)
&& Number(incrementedPlan?.pdaTaskVersion) > Number(taskBeforeIncrement?.version)
&& Number(incrementalPlanItem?.version) === 0 && Number(incrementalPlanItem?.pickedPackageCount) === 0
&& Number(incrementalPlanItem?.minPackageCount) === 1
&& incrementalPlanItem?.canAdjust === true && incrementalPlanItem?.canRemove === true
&& id(incrementedTask?.loadingPlanId) === loadingPlanId
&& id(incrementedTask?.containerBookingId) === containerBookingId
&& incrementedTask?.workflowStatus === 'IN_PROGRESS'
&& JSON.stringify(evidenceAfterIncrement?.evidences || []) === evidenceBeforeIncrement,
`planVersion=${incrementedPlan?.version ?? 'missing'}, taskVersion=${incrementedTask?.version ?? 'missing'}, workflow=${incrementedTask?.workflowStatus || 'missing'}`, OPTIONS)
if (incrementalTaskItem?.id) {
const staleIncrementBatch = await request(context, 'POST', batchPath, {
warehouseId, loadingPlanId, containerBookingId,
expectedVersion: Number(incrementedTask?.version),
inventorySnapshotToken: effectiveBatchPayload.inventorySnapshotToken,
items: [{ itemId: id(incrementalTaskItem.id), action: 'PICK', pickedPackageCount: 1,
releaseMode: 'MANUAL', locationIds: [], version: Number(incrementalTaskItem.version) }],
}, { 'Idempotency-Key': `${context.marker}-dynamic-stale-inventory`.slice(0, 128) })
context.addStep(MODULE, '增量后旧 inventory token 明确 A0409 并要求刷新', staleIncrementBatch,
staleIncrementBatch.http === 200 && codeOf(staleIncrementBatch) === 'A0409'
&& String(staleIncrementBatch.body?.message || '').includes('库存已变化,请刷新后重试'),
responseEvidence(staleIncrementBatch), OPTIONS)
const taskAfterStaleResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
const taskAfterStale = context.apiSuccess(MODULE, '旧 token 冲突后回查增量任务', taskAfterStaleResponse, '', OPTIONS)
const planAfterStaleResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
const planAfterStale = context.apiSuccess(MODULE, '旧 token 冲突后回查增量计划', planAfterStaleResponse, '', OPTIONS)
context.assert(MODULE, '增量旧 token 冲突后 plan/task/items 零写入', taskAfterStaleResponse,
Number(taskAfterStale?.version) === Number(incrementedTask?.version)
&& taskItemsSignature(taskAfterStale) === taskItemsSignature(incrementedTask)
&& Number(planAfterStale?.version) === Number(incrementedPlan?.version),
`taskVersion=${taskAfterStale?.version ?? 'missing'}, planVersion=${planAfterStale?.version ?? 'missing'}`, OPTIONS)
const removeResponse = await request(context, 'DELETE',
`${smartLoadingBase}/plans/${loadingPlanId}/items/${encodeURIComponent(id(incrementalPlanItem.id))}`, {
expectedPlanVersion: Number(incrementedPlan.version), expectedItemVersion: Number(incrementalPlanItem.version),
operationId: `${context.marker}-dynamic-remove-unpicked`.slice(0, 128),
})
const removedPlan = context.apiSuccess(MODULE, '未拣动态增量明细允许删除', removeResponse, '', OPTIONS)
context.assert(MODULE, '删除未拣项递增计划和任务版本且稳定身份不变', removeResponse,
!removedPlan?.items?.some?.((item) => id(item?.id) === id(incrementalPlanItem.id))
&& Number(removedPlan?.version) > Number(incrementedPlan?.version)
&& Number(removedPlan?.pdaTaskVersion) > Number(incrementedPlan?.pdaTaskVersion)
&& id(removedPlan?.pdaTaskId) === canonicalTaskId && removedPlan?.status === 'LOADING',
`planVersion=${removedPlan?.version ?? 'missing'}, taskVersion=${removedPlan?.pdaTaskVersion ?? 'missing'}`, OPTIONS)
const reassignResponse = await request(context, 'POST', `${smartLoadingBase}/plans/${loadingPlanId}/assignments`, {
inboundId: incrementalInboundId, inboundDetailId: id(dynamicCandidate.inboundDetailId), packageCount: 1,
expectedVersion: Number(removedPlan?.version), operationId: `${context.marker}-dynamic-reassign`.slice(0, 128),
})
const reassignedPlan = context.apiSuccess(MODULE, '删除后重新加入同一动态入库单', reassignResponse, '', OPTIONS)
let effectiveDynamicPlan = reassignedPlan
let reassignedPlanItem = reassignedPlan?.items?.find?.((item) => id(item?.inboundId) === incrementalInboundId)
context.assert(MODULE, '动态增量 fixture 至少保留两件可用数量供调整与最小已拣门禁', dynamicCandidatesResponse,
Number(dynamicCandidate?.availablePackageCount) >= 2,
`availablePackageCount=${dynamicCandidate?.availablePackageCount ?? 'missing'}`, OPTIONS)
if (Number(dynamicCandidate?.availablePackageCount) < 2) return
if (Number(dynamicCandidate?.availablePackageCount) >= 2 && reassignedPlanItem?.id) {
const adjustResponse = await request(context, 'PATCH',
`${smartLoadingBase}/plans/${loadingPlanId}/items/${encodeURIComponent(id(reassignedPlanItem.id))}`, {
packageCount: 2, expectedPlanVersion: Number(reassignedPlan?.version),
expectedItemVersion: Number(reassignedPlanItem.version),
operationId: `${context.marker}-dynamic-adjust`.slice(0, 128),
})
const adjustedPlan = context.apiSuccess(MODULE, '未拣动态增量明细允许调整数量', adjustResponse, '', OPTIONS)
const adjustedItem = adjustedPlan?.items?.find?.((item) => id(item?.id) === id(reassignedPlanItem.id))
context.assert(MODULE, '调整数量递增 plan/task/item 版本并保持稳定任务', adjustResponse,
Number(adjustedPlan?.version) > Number(reassignedPlan?.version)
&& Number(adjustedPlan?.pdaTaskVersion) > Number(reassignedPlan?.pdaTaskVersion)
&& Number(adjustedItem?.version) > Number(reassignedPlanItem?.version)
&& Number(adjustedItem?.packageCount) === 2 && id(adjustedPlan?.pdaTaskId) === canonicalTaskId,
`planVersion=${adjustedPlan?.version ?? 'missing'}, taskVersion=${adjustedPlan?.pdaTaskVersion ?? 'missing'}, itemVersion=${adjustedItem?.version ?? 'missing'}`, OPTIONS)
effectiveDynamicPlan = adjustedPlan
reassignedPlanItem = adjustedItem
}
const refreshedDynamicTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
const refreshedDynamicTask = context.apiSuccess(MODULE, '重新增量后刷新 PDA task detail', refreshedDynamicTaskResponse, '', OPTIONS)
const refreshedDynamicTaskItem = refreshedDynamicTask?.items?.find?.((item) =>
id(item?.loadingPlanItemId) === id(reassignedPlanItem?.id))
const refreshedDynamicInventoryResponse = await context.request('GET',
`${pdaBase}/tasks/${canonicalTaskId}/inventory-snapshot?warehouseId=${encodeURIComponent(warehouseId)}`)
const refreshedDynamicInventory = context.apiSuccess(MODULE, '重新增量后刷新 inventory snapshot', refreshedDynamicInventoryResponse, '', OPTIONS)
const dynamicBatchResponse = await request(context, 'POST', batchPath, {
warehouseId, loadingPlanId, containerBookingId,
expectedVersion: Number(refreshedDynamicTask?.version),
inventorySnapshotToken: refreshedDynamicInventory?.snapshotToken,
items: [{ itemId: id(refreshedDynamicTaskItem?.id), action: 'PICK', pickedPackageCount: Number(reassignedPlanItem?.packageCount),
releaseMode: 'MANUAL', locationIds: [], version: Number(refreshedDynamicTaskItem?.version) }],
}, { 'Idempotency-Key': `${context.marker}-dynamic-pick`.slice(0, 128) })
const dynamicPickedTask = context.apiSuccess(MODULE, '刷新后完成动态新增项拣货', dynamicBatchResponse, '', OPTIONS)
context.assert(MODULE, '动态新增项拣完后同一任务重新进入 AWAITING_REVIEW', dynamicBatchResponse,
id(dynamicPickedTask?.id) === canonicalTaskId && dynamicPickedTask?.workflowStatus === 'AWAITING_REVIEW'
&& dynamicPickedTask?.taskStatus === 'LOADING'
&& dynamicPickedTask?.items?.every?.((item) => item.itemStatus !== 'PENDING'),
`taskId=${id(dynamicPickedTask?.id) || 'missing'}, workflow=${dynamicPickedTask?.workflowStatus || 'missing'}`, OPTIONS)
const pickedDynamicPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
const pickedDynamicPlan = context.apiSuccess(MODULE, '动态新增项拣完后回查 minPackageCount', pickedDynamicPlanResponse, '', OPTIONS)
const pickedDynamicPlanItem = pickedDynamicPlan?.items?.find?.((item) => id(item?.id) === id(reassignedPlanItem?.id))
const beforePickedMutationTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
const beforePickedMutationTask = context.apiSuccess(MODULE, '已拣项负向前读取任务快照', beforePickedMutationTaskResponse, '', OPTIONS)
const reducePickedResponse = await request(context, 'PATCH',
`${smartLoadingBase}/plans/${loadingPlanId}/items/${encodeURIComponent(id(pickedDynamicPlanItem?.id))}`, {
packageCount: Math.max(1, Number(pickedDynamicPlanItem?.minPackageCount) - 1),
expectedPlanVersion: Number(pickedDynamicPlan?.version), expectedItemVersion: Number(pickedDynamicPlanItem?.version),
operationId: `${context.marker}-dynamic-reduce-picked`.slice(0, 128),
})
context.addStep(MODULE, '已拣项减量低于 minPackageCount 返回 A0409', reducePickedResponse,
reducePickedResponse.http === 200 && codeOf(reducePickedResponse) === 'A0409', responseEvidence(reducePickedResponse), OPTIONS)
const deletePickedResponse = await request(context, 'DELETE',
`${smartLoadingBase}/plans/${loadingPlanId}/items/${encodeURIComponent(id(pickedDynamicPlanItem?.id))}`, {
expectedPlanVersion: Number(pickedDynamicPlan?.version), expectedItemVersion: Number(pickedDynamicPlanItem?.version),
operationId: `${context.marker}-dynamic-delete-picked`.slice(0, 128),
})
context.addStep(MODULE, '已拣项删除返回 A0409', deletePickedResponse,
deletePickedResponse.http === 200 && codeOf(deletePickedResponse) === 'A0409', responseEvidence(deletePickedResponse), OPTIONS)
const afterPickedMutationPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
const afterPickedMutationPlan = context.apiSuccess(MODULE, '已拣项负向后回查计划', afterPickedMutationPlanResponse, '', OPTIONS)
const afterPickedMutationTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
const afterPickedMutationTask = context.apiSuccess(MODULE, '已拣项负向后回查任务', afterPickedMutationTaskResponse, '', OPTIONS)
context.assert(MODULE, '已拣项删除和减量失败后 plan/task/items 零写入', afterPickedMutationPlanResponse,
Number(afterPickedMutationPlan?.version) === Number(pickedDynamicPlan?.version)
&& Number(afterPickedMutationTask?.version) === Number(beforePickedMutationTask?.version)
&& taskItemsSignature(afterPickedMutationTask) === taskItemsSignature(beforePickedMutationTask),
`planVersion=${afterPickedMutationPlan?.version ?? 'missing'}, taskVersion=${afterPickedMutationTask?.version ?? 'missing'}`, OPTIONS)
dynamicIncrementVerified = dynamicPickedTask?.workflowStatus === 'AWAITING_REVIEW'
&& Number(pickedDynamicPlanItem?.minPackageCount) === Number(reassignedPlanItem?.packageCount)
&& Number(effectiveDynamicPlan?.version) <= Number(pickedDynamicPlan?.version)
}
}
}
const beforeHardGateResponse = await context.request('GET', detailPath)
const beforeHardGate = context.apiSuccess(MODULE, '读取 LOADED 硬门禁调用前即时版本', beforeHardGateResponse, '', OPTIONS)
const beforeHardGateVersion = Number(beforeHardGate?.summary?.version)
@@ -923,18 +1075,31 @@ export async function run(context) {
beforeHardGate?.summary?.processStatus === 'LOADING' && Number.isInteger(beforeHardGateVersion),
`status=${beforeHardGate?.summary?.processStatus || 'missing'}, version=${beforeHardGate?.summary?.version ?? 'missing'}`, OPTIONS)
if (!Number.isInteger(beforeHardGateVersion)) return
const blockedBeforeDownstream = await request(context, 'POST', `${base}/loading/complete?tenantId=${encodeURIComponent(tenantId)}`,
{ expectedVersion: beforeHardGateVersion, remark: `${context.marker}-before-downstream` },
const beforeBlockedTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
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,
failure(blockedBeforeDownstream),
`HTTP=${blockedBeforeDownstream.http}, code=${codeOf(blockedBeforeDownstream)}`, OPTIONS)
blockedBeforeDownstream.http === 200 && codeOf(blockedBeforeDownstream) === 'A0409',
responseEvidence(blockedBeforeDownstream), OPTIONS)
const blockedDetailResponse = await context.request('GET', detailPath)
const blockedDetail = context.apiSuccess(MODULE, '回查 LOADED 硬门禁拒绝后的入库状态', blockedDetailResponse, '', OPTIONS)
context.assert(MODULE, 'LOADED 硬门禁拒绝后状态和版本零写入', blockedDetailResponse,
const blockedTaskResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}`)
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,
`status=${blockedDetail?.summary?.processStatus || 'missing'}, version=${blockedDetail?.summary?.version ?? 'missing'}`, OPTIONS)
&& 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)
context.createdResources.phase3Loading = {
tenantId, orderId: id(source?.orderId), inboundId, preloadPlanId: planId,
@@ -946,9 +1111,11 @@ export async function run(context) {
oceanBookingId: bookingId, loadingPlanId, containerBookingId, containerNo, warehouseId,
canonicalPdaTaskId: canonicalTaskId,
pickingTaskId: canonicalTaskId, inboundVersion: loadingVersion,
atomicBatchVerified: awaitingReview && directPdaCompletion && atomicBatchReplayVerified,
inventorySnapshotVerified: inventorySnapshotVerified && directPdaCompletion && atomicBatchReplayVerified,
atomicBatchVerified: awaitingReview && atomicBatchReplayVerified,
inventorySnapshotVerified: inventorySnapshotVerified && atomicBatchReplayVerified,
inventoryMutationVerified, inventoryMutationCleanup,
marker: context.marker, inboundStatus: 'LOADING', loadingPlanStatus: 'COMPLETED', finalStatus: 'COMPLETED',
dynamicIncrementVerified, incrementalInboundId,
pdaTaskVersion: Number(blockedTask?.version),
marker: context.marker, inboundStatus: 'LOADING', loadingPlanStatus: 'LOADING', finalStatus: 'AWAITING_REVIEW',
}
}