fix: complete phase4 receiving acceptance chain

This commit is contained in:
2026-08-24 22:14:41 +08:00
parent 8cc4c6f6da
commit 3fbb7653f7
12 changed files with 79 additions and 22 deletions
@@ -15,8 +15,8 @@ function conflictMessage(response, pattern) { return conflict(response) && patte
function notFound(response) { return (response?.http === 404 || (response?.http === 200 && ['404', 'A0404'].includes(code(response)))) && traced(response) }
function forbidden(response) { return [401, 403].includes(response?.http) || (response?.http === 200 && ['A0401', 'A0403'].includes(code(response))) }
function trace(response) { return `HTTP=${response?.http ?? 'missing'}, code=${response?.body?.code ?? 'missing'}, requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}` }
function key(context, action, suffix = '') { return `${context.marker}-${action}-${suffix}`.replace(/[^A-Za-z0-9:_-]/g, '').slice(0, 120) }
function markerSuffix(context) { return String(context.marker).replace(/[^A-Za-z0-9]/g, '').slice(-16).toUpperCase() }
function key(context, action, suffix = '') { return `${context.marker}-${context.pdaContainerSuffix || ''}-${action}-${suffix}`.replace(/[^A-Za-z0-9:_-]/g, '').slice(0, 120) }
function markerSuffix(context) { return `${context.marker}${context.pdaContainerSuffix || ''}`.replace(/[^A-Za-z0-9]/g, '').slice(-16).toUpperCase() }
function blockerGap(context, name, reason, category = 'fixture-unavailable', response = null) {
context.addCoverageGap(MODULE, SCENARIO, name, reason, category, { deploymentBlocker: true })
@@ -183,8 +183,8 @@ async function receiveFba(context, token, inbound, pallet) {
return true
}
async function receiveOverseas(context, token, inbound, pallet) {
const cartonMark = context.createdResources?.inboundMeasurement?.boxCode
async function receiveOverseas(context, token, inbound, pallet, producerCartonMark = null) {
const cartonMark = producerCartonMark || context.createdResources?.inboundMeasurement?.boxCode
if (!nonBlank(cartonMark)) {
blockerGap(context, `OVERSEAS入仓码${inbound.inboundCode}逐箱`, '缺少本轮冻结箱唛,逐箱实收不能使用伪造箱号')
return false
@@ -207,12 +207,12 @@ async function receiveOverseas(context, token, inbound, pallet) {
return true
}
async function runReceiving(context, token, secondaryToken, merchantToken) {
if (context.overseasToken) {
async function runReceiving(context, token, secondaryToken, merchantToken, preferredProducer = null, runAuthorizationProbes = true) {
if (runAuthorizationProbes && context.overseasToken) {
const denied = await context.requestOverseas('GET', '/api/pda/v1/last-leg/receiving-containers/candidates', undefined, {}, context.overseasToken)
context.addStep(MODULE, '平台管理员海外令牌不能冒充仓库PDA操作员', denied, forbidden(denied) && traced(denied), trace(denied), OPTIONS)
}
if (merchantToken) {
if (runAuthorizationProbes && merchantToken) {
const denied = await context.requestOverseas('GET', '/api/pda/v1/last-leg/receiving-containers/candidates', undefined, {}, merchantToken)
context.addStep(MODULE, '客户海外令牌不能冒充仓库PDA操作员', denied, forbidden(denied) && traced(denied), trace(denied), OPTIONS)
}
@@ -229,8 +229,16 @@ async function runReceiving(context, token, secondaryToken, merchantToken) {
blockerGap(context, 'PDA柜号候选', `当前客户没有由三期装柜完成事件生成的待收入仓计划;${trace(candidates.response)}`, 'producer-chain', candidates.response)
return
}
const preferredContainer = context.createdResources?.phase3Loading?.containerNo
const candidate = candidates.data.find((item) => item.containerNo === preferredContainer) || candidates.data[0]
const preferredContainer = id(preferredProducer?.containerNo)
|| context.createdResources?.phase3Loading?.containerNo
const candidate = preferredContainer
? candidates.data.find((item) => item.containerNo === preferredContainer)
: candidates.data[0]
if (!candidate) {
blockerGap(context, `本轮柜号${preferredContainer || 'missing'}候选`,
'三期装柜完成生产者已落地,但海外候选尚未出现该柜;禁止回退处理历史或其他柜', 'producer-chain', candidates.response)
return
}
const containerNo = id(candidate?.containerNo)
context.assert(MODULE, '柜号候选来自本轮生产链且包含进度', candidates.response,
nonBlank(containerNo) && Number(candidate?.totalPackages) > 0 && Number(candidate?.receivedPackages) >= 0,
@@ -300,7 +308,7 @@ async function runReceiving(context, token, secondaryToken, merchantToken) {
inboundCode: overseasInbound.inboundCode, skuCode: 'MODE-REJECT', palletCode: pallet.palletCode,
packageCount: 1, idempotencyKey: key(context, 'overseas-bulk-forbidden'),
}, (response) => conflictMessage(response, /OVERSEAS.*carton|bulk.*forbidden/i))
await receiveOverseas(context, token, overseasInbound, pallet)
await receiveOverseas(context, token, overseasInbound, pallet, preferredProducer?.cartonMark)
} else blockerGap(context, 'OVERSEAS逐箱链', '当前柜没有带真实冻结箱唛与托盘的 OVERSEAS 入仓码')
const refreshed = await pda(context, token, 'GET', '回查柜内逐入仓码实收进度', '/api/pda/v1/last-leg/receiving-containers/current')
if (!success(refreshed.response) || Number(refreshed.data?.pendingPackages) !== 0) {
@@ -321,7 +329,7 @@ async function runReceiving(context, token, secondaryToken, merchantToken) {
export async function run(context) {
context.createdResources.phase4OverseasPda = {
marker: context.marker, containerNo: null, customerId: null,
palletIds: [], locationIds: [], finalStatus: null, pickerMembershipIds: [],
palletIds: [], locationIds: [], finalStatus: null, pickerMembershipIds: [], completedContainers: [],
overseasWarehouseId: null, overseasWarehouseName: null,
cleanup: 'Shared warehouse, pallets and locations used by completed inventory are retained as acceptance evidence',
}
@@ -345,8 +353,20 @@ export async function run(context) {
blockerGap(context, '海外PDA双操作员账号', '未能创建并登录两个WAREHOUSE_PICKER平台账号,无法验证全局一柜锁')
return
}
await runReceiving(context, primary.token, secondary.token, merchantToken)
const matrix = context.createdResources?.phase4InboundProducerFixtures
const producers = [matrix?.byDestinationType?.FBA?.[0], matrix?.byDestinationType?.OVERSEAS?.[0]]
.filter((item) => nonBlank(item?.containerNo))
for (const [index, producer] of (producers.length ? producers : [null]).entries()) {
context.pdaContainerSuffix = producer?.containerNo || `CONTAINER-${index + 1}`
context.createdResources.phase4OverseasPda.finalStatus = null
await runReceiving(context, primary.token, secondary.token,
index === 0 ? merchantToken : null, producer, index === 0)
if (context.createdResources.phase4OverseasPda.finalStatus === 'COMPLETED') {
context.createdResources.phase4OverseasPda.completedContainers.push(producer?.containerNo || null)
}
}
} finally {
delete context.pdaContainerSuffix
await disablePicker(context, secondary, 2)
await disablePicker(context, primary, 1)
}