Merge remote-tracking branch 'origin/test' into dev
This commit is contained in:
@@ -645,6 +645,20 @@ export async function run(context) {
|
||||
context.assert(MODULE, '未变化库存返回稳定 token、版本和数量快照', repeatedInventoryResponse,
|
||||
inventorySnapshotVerified,
|
||||
`sameToken=${repeatedInventory?.snapshotToken === inventory.snapshotToken}, taskVersion=${repeatedInventory?.taskVersion ?? 'missing'}`, OPTIONS)
|
||||
const inventoryBlockSnapshots = (inventory?.inbounds || []).map((entry) => ({
|
||||
inboundId: id(entry?.inboundId),
|
||||
inboundCode: canonicalTask?.items?.find?.((item) => id(item?.inboundId) === id(entry?.inboundId))?.inboundCode || null,
|
||||
resources: (entry?.resources || []).map((resource) => ({
|
||||
blockId: id(resource?.blockId), slotNo: Number(resource?.slotNo),
|
||||
reservationId: id(resource?.reservationId), locationId: id(resource?.locationId),
|
||||
sharedOverflow: resource?.sharedOverflow === true,
|
||||
})),
|
||||
})).filter((entry) => entry.inboundId && entry.resources.length > 0)
|
||||
const inventoryBlockIdentityVerified = inventoryBlockSnapshots.length > 0
|
||||
&& inventoryBlockSnapshots.every((entry) => new Set(entry.resources.map((resource) => resource.blockId)).size === 1)
|
||||
context.assert(MODULE, 'PDA 库存快照按入仓单返回唯一整票库位块身份', inventoryResponse,
|
||||
inventoryBlockIdentityVerified,
|
||||
`inbounds=${inventoryBlockSnapshots.length}, blocks=${inventoryBlockSnapshots.map((entry) => entry.resources[0]?.blockId).join(',')}`, OPTIONS)
|
||||
|
||||
const batchPath = `${pdaBase}/tasks/${canonicalTaskId}/batch`
|
||||
const batchKey = `${context.marker}-bridge-batch`.slice(0, 128)
|
||||
@@ -745,105 +759,7 @@ export async function run(context) {
|
||||
id(restoredAssignment?.assignment?.assigneeId) === id(currentAssigneeId),
|
||||
`assigneeId=${id(restoredAssignment?.assignment?.assigneeId) || 'missing'}`, OPTIONS)
|
||||
|
||||
let inventoryMutationVerified = false
|
||||
let inventoryMutationCleanup = null
|
||||
let effectiveBatchPayload = batchPayload
|
||||
const overflowResource = inventoryInbound?.resources?.find?.((resource) =>
|
||||
resource?.sharedOverflow === true && id(resource?.occupancyId))
|
||||
if (overflowResource) {
|
||||
const mutationSuffix = markerSuffix(context.marker)
|
||||
const targetLocationResponse = await context.request('POST', '/api/platform/v1/locations?tenantId=0', {
|
||||
warehouseId, zoneCode: `SNAP-${mutationSuffix.slice(-6)}`, zoneName: '库存快照验收区',
|
||||
locationCode: `SNAP-${mutationSuffix}`, locationName: '库存快照换绑库位',
|
||||
locationType: 'PALLET', maxWeightKg: 1000, status: 'AVAILABLE',
|
||||
remark: `${context.marker}-inventory-snapshot-mutation`,
|
||||
})
|
||||
const targetLocationId = id(context.apiSuccess(MODULE, '创建库存快照变化目标库位',
|
||||
targetLocationResponse, '', OPTIONS))
|
||||
if (!targetLocationId) {
|
||||
context.addStep(MODULE, '库存变化fixture创建失败后停止旧snapshot提交', targetLocationResponse,
|
||||
false, responseEvidence(targetLocationResponse), OPTIONS)
|
||||
return
|
||||
}
|
||||
if (targetLocationId) {
|
||||
inventoryMutationCleanup = {
|
||||
cleaned: false, targetLocationId,
|
||||
reason: '目标库位在真实换绑后属于活动占用,公开删除接口会拒绝,不伪报已清理',
|
||||
}
|
||||
}
|
||||
const beforeMutationTaskResponse = await context.request('GET',
|
||||
`${pdaBase}/tasks?loadingPlanId=${encodeURIComponent(loadingPlanId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const beforeMutationTask = context.apiSuccess(MODULE, '库存变化前读取 PDA 任务基线', beforeMutationTaskResponse, '', OPTIONS)
|
||||
const beforeMutationOperationsResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}/operations`)
|
||||
const beforeMutationOperations = context.apiSuccess(MODULE, '库存变化前读取 PDA 操作历史基线',
|
||||
beforeMutationOperationsResponse, '', OPTIONS)
|
||||
const rebindResponse = targetLocationId
|
||||
? await request(context, 'POST',
|
||||
`/api/platform/v1/locations/temporary-occupancies/${encodeURIComponent(id(overflowResource.occupancyId))}/rebind`, {
|
||||
targetLocationId, idempotencyKey: `${context.marker}-snapshot-rebind`.slice(0, 128),
|
||||
reason: `${context.marker}-inventory-changed`,
|
||||
})
|
||||
: targetLocationResponse
|
||||
const rebound = context.apiSuccess(MODULE, '通过公开 A0 换绑接口制造真实库存变化', rebindResponse, '', OPTIONS)
|
||||
const reboundValid = id(rebound?.occupancyId) === id(overflowResource.occupancyId)
|
||||
&& id(rebound?.previousLocationId) === id(overflowResource.locationId)
|
||||
&& id(rebound?.locationId) === targetLocationId
|
||||
context.assert(MODULE, '真实库存换绑保持同仓且位置发生变化', rebindResponse,
|
||||
reboundValid,
|
||||
`occupancyId=${id(rebound?.occupancyId) || 'missing'}, previousLocationId=${id(rebound?.previousLocationId) || 'missing'}, locationId=${id(rebound?.locationId) || 'missing'}`, OPTIONS)
|
||||
if (!reboundValid) {
|
||||
context.addStep(MODULE, '库存换绑未成功后停止旧snapshot提交', rebindResponse,
|
||||
false, responseEvidence(rebindResponse), OPTIONS)
|
||||
return
|
||||
}
|
||||
|
||||
const staleInventoryBatch = await request(context, 'POST', batchPath, batchPayload,
|
||||
{ 'Idempotency-Key': `${context.marker}-bridge-batch-stale-inventory`.slice(0, 128) })
|
||||
context.addStep(MODULE, '真实库存变化后旧 snapshot token 提交返回冲突', staleInventoryBatch,
|
||||
staleInventoryBatch.http === 200 && codeOf(staleInventoryBatch) === 'A0409'
|
||||
&& String(staleInventoryBatch.body?.message || '').includes('库存已变化'),
|
||||
`HTTP=${staleInventoryBatch.http}, code=${codeOf(staleInventoryBatch)}, message=${staleInventoryBatch.body?.message || 'missing'}`, OPTIONS)
|
||||
|
||||
const afterStaleTaskResponse = await context.request('GET',
|
||||
`${pdaBase}/tasks?loadingPlanId=${encodeURIComponent(loadingPlanId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const afterStaleTask = context.apiSuccess(MODULE, '旧库存快照冲突后回查 PDA 任务', afterStaleTaskResponse, '', OPTIONS)
|
||||
const afterStaleOperationsResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}/operations`)
|
||||
const afterStaleOperations = context.apiSuccess(MODULE, '旧库存快照冲突后回查 PDA 操作历史',
|
||||
afterStaleOperationsResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '旧 token 冲突对任务、明细、已拣数量和操作历史零写入', afterStaleTaskResponse,
|
||||
Number(afterStaleTask?.version) === Number(beforeMutationTask?.version)
|
||||
&& taskItemsSignature(afterStaleTask) === taskItemsSignature(beforeMutationTask)
|
||||
&& operationsSignature(afterStaleOperations) === operationsSignature(beforeMutationOperations),
|
||||
`taskVersion=${afterStaleTask?.version ?? 'missing'}, items=${taskItemsSignature(afterStaleTask)}, operations=${afterStaleOperations?.length ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const refreshedInventoryResponse = await context.request('GET',
|
||||
`${pdaBase}/tasks/${canonicalTaskId}/inventory-snapshot?warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const refreshedInventory = context.apiSuccess(MODULE, '库存变化后刷新 PDA 实时库存快照',
|
||||
refreshedInventoryResponse, '', OPTIONS)
|
||||
const refreshedInbound = refreshedInventory?.inbounds?.find?.((item) => id(item.inboundId) === inboundId)
|
||||
inventoryMutationVerified = refreshedInventory?.snapshotToken !== inventory.snapshotToken
|
||||
&& Number(refreshedInventory?.taskVersion) === Number(inventory.taskVersion)
|
||||
&& refreshedInbound?.resources?.some?.((resource) => id(resource.occupancyId) === id(overflowResource.occupancyId)
|
||||
&& id(resource.locationId) === targetLocationId)
|
||||
context.assert(MODULE, '刷新快照返回新 token、同任务版本和变更后库存', refreshedInventoryResponse,
|
||||
inventoryMutationVerified,
|
||||
`tokenChanged=${refreshedInventory?.snapshotToken !== inventory.snapshotToken}, taskVersion=${refreshedInventory?.taskVersion ?? 'missing'}, targetLocation=${targetLocationId || 'missing'}`, OPTIONS)
|
||||
if (refreshedInventory?.snapshotToken) {
|
||||
effectiveBatchPayload = { ...batchPayload, inventorySnapshotToken: refreshedInventory.snapshotToken }
|
||||
}
|
||||
} else if (source?.requireInventoryMutation !== false) {
|
||||
context.addStep(MODULE, '自动制造库存变化并验证旧 snapshot token A0409', null, false,
|
||||
`本轮 ${inventoryInbound?.resources?.length ?? 0} 条占用没有可安全换绑的 A0 临时占用;不得以 coverageGap 作为通过`, OPTIONS)
|
||||
context.addCoverageGap(MODULE, OPTIONS.scenario, 'PDA 真实库存变化后的旧快照冲突与刷新重试',
|
||||
`inventory-snapshot 已真实返回 taskId=${canonicalTaskId}、warehouseId=${warehouseId} 并验证稳定 token/version=${inventorySnapshotVerified};本轮 ${inventoryInbound?.resources?.length ?? 0} 条占用均为普通库位,当前唯一平台代理库存变更入口只允许 A0 临时占用换绑,不能安全改动普通占用或调用内部 Warehouse 接口`,
|
||||
'fixture')
|
||||
} else {
|
||||
context.addStep(MODULE, '非快照专项装柜链不重复制造 A0 库存变化', null, true,
|
||||
'当前模块作为四期真实业务前置,仅验证正常装柜链;旧 snapshot token A0409 由独立 phase3-loading 专项覆盖',
|
||||
{ scenario: OPTIONS.scenario })
|
||||
}
|
||||
|
||||
const canonicalPick = await request(context, 'POST', batchPath, effectiveBatchPayload,
|
||||
const canonicalPick = await request(context, 'POST', batchPath, batchPayload,
|
||||
{ 'Idempotency-Key': batchKey })
|
||||
const pickedCanonical = context.apiSuccess(MODULE, '原子提交柜次 PDA 整批拣货', canonicalPick, '', OPTIONS)
|
||||
const awaitingReview = pickedCanonical?.taskStatus === 'LOADING'
|
||||
@@ -852,7 +768,7 @@ export async function run(context) {
|
||||
awaitingReview && pickedCanonical?.items?.every?.((item) => item.itemStatus !== 'PENDING'),
|
||||
`taskStatus=${pickedCanonical?.taskStatus || 'missing'}, workflow=${pickedCanonical?.workflowStatus || 'missing'}`, OPTIONS)
|
||||
|
||||
const canonicalBatchReplay = await request(context, 'POST', batchPath, effectiveBatchPayload,
|
||||
const canonicalBatchReplay = await request(context, 'POST', batchPath, batchPayload,
|
||||
{ 'Idempotency-Key': batchKey })
|
||||
const atomicBatchReplayVerified = canonicalBatchReplay.http === 200 && codeOf(canonicalBatchReplay) === '200'
|
||||
&& id(canonicalBatchReplay.body?.data?.id) === canonicalTaskId
|
||||
@@ -1177,6 +1093,7 @@ export async function run(context) {
|
||||
const incrementalInboundId = id(incrementalFixture?.inboundId)
|
||||
let skipBatchVerified = false
|
||||
let partialBatchVerified = false
|
||||
let partialBlockSnapshot = null
|
||||
let dynamicIncrementVerified = false
|
||||
if (!incrementalInboundId || incrementalFixture?.marker !== context.marker || incrementalInboundId === inboundId) {
|
||||
context.addStep(MODULE, '取得本轮独立动态增量入库单', null, false,
|
||||
@@ -1275,6 +1192,22 @@ export async function run(context) {
|
||||
const partialInventoryResponse = await context.request('GET',
|
||||
`${pdaBase}/tasks/${canonicalTaskId}/inventory-snapshot?warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
const partialInventory = context.apiSuccess(MODULE, '部分拣货前刷新实时库存快照', partialInventoryResponse, '', OPTIONS)
|
||||
const partialInventoryInbound = partialInventory?.inbounds?.find?.((item) =>
|
||||
id(item?.inboundId) === incrementalInboundId)
|
||||
const partialResources = (partialInventoryInbound?.resources || []).map((resource) => ({
|
||||
blockId: id(resource?.blockId), slotNo: Number(resource?.slotNo),
|
||||
reservationId: id(resource?.reservationId), locationId: id(resource?.locationId),
|
||||
sharedOverflow: resource?.sharedOverflow === true,
|
||||
}))
|
||||
partialBlockSnapshot = {
|
||||
inboundId: incrementalInboundId,
|
||||
inboundCode: unresolvedTaskItem?.inboundCode || null,
|
||||
resources: partialResources,
|
||||
}
|
||||
context.assert(MODULE, 'PARTIAL 前余货属于唯一整票库位块', partialInventoryResponse,
|
||||
partialResources.length > 0
|
||||
&& new Set(partialResources.map((resource) => resource.blockId)).size === 1,
|
||||
`inboundId=${incrementalInboundId}, resources=${partialResources.length}, blockId=${partialResources[0]?.blockId || 'missing'}`, OPTIONS)
|
||||
const partialBatchResponse = await request(context, 'POST', batchPath, {
|
||||
warehouseId, loadingPlanId, containerBookingId,
|
||||
expectedVersion: Number(afterUnresolvedCompleteTask?.version),
|
||||
@@ -1348,7 +1281,7 @@ export async function run(context) {
|
||||
const staleIncrementBatch = await request(context, 'POST', batchPath, {
|
||||
warehouseId, loadingPlanId, containerBookingId,
|
||||
expectedVersion: Number(incrementedTask?.version),
|
||||
inventorySnapshotToken: effectiveBatchPayload.inventorySnapshotToken,
|
||||
inventorySnapshotToken: batchPayload.inventorySnapshotToken,
|
||||
items: [{ itemId: id(incrementalTaskItem.id), action: 'PICK', pickedPackageCount: 1,
|
||||
version: Number(incrementalTaskItem.version) }],
|
||||
}, { 'Idempotency-Key': `${context.marker}-dynamic-stale-inventory`.slice(0, 128) })
|
||||
@@ -1508,8 +1441,8 @@ export async function run(context) {
|
||||
pickingTaskId: canonicalTaskId, inboundVersion: loadingVersion,
|
||||
atomicBatchVerified: awaitingReview && atomicBatchReplayVerified,
|
||||
inventorySnapshotVerified: inventorySnapshotVerified && atomicBatchReplayVerified,
|
||||
inventoryMutationVerified, inventoryMutationCleanup,
|
||||
inventoryMutationRequired: source?.requireInventoryMutation !== false,
|
||||
inventoryBlockIdentityVerified, inventoryBlockSnapshots,
|
||||
partialBlockSnapshot,
|
||||
skipBatchVerified, partialBatchVerified, dynamicIncrementVerified,
|
||||
expectedBatchOperationIds, incrementalInboundId,
|
||||
pdaTaskVersion: Number(stagedTask?.version),
|
||||
|
||||
Reference in New Issue
Block a user