This commit is contained in:
2026-08-26 17:50:56 +08:00
parent aad8dd6344
commit 64047df5b3
10 changed files with 193 additions and 20 deletions
@@ -13,12 +13,26 @@ function code(response) { return String(response?.body?.code ?? '') }
function traced(response) { return nonBlank(response?.requestId) && nonBlank(response?.traceId) }
function conflict(response) { return (response?.http === 409 || (response?.http === 200 && ['409', 'A0409'].includes(code(response)))) && traced(response) }
function a0409Message(response, pattern) { return response?.http === 200 && code(response) === 'A0409' && traced(response) && pattern.test(String(response?.body?.message || '')) }
function a0404Message(response, pattern) { return response?.http === 200 && code(response) === 'A0404' && traced(response) && pattern.test(String(response?.body?.message || '')) }
function cartonResolveRejected(response) { return response?.http === 200 && ['A0400', 'A0404', 'A0409'].includes(code(response)) && traced(response) }
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}-${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 records(data) { return Array.isArray(data?.records) ? data.records : Array.isArray(data) ? data : [] }
function receivingSnapshot(data) {
return JSON.stringify({
containerNo: data?.containerNo, status: data?.status, version: data?.version,
totalPackages: data?.totalPackages, receivedPackages: data?.receivedPackages,
pendingPackages: data?.pendingPackages,
inbounds: (Array.isArray(data?.inbounds) ? data.inbounds : []).map((item) => ({
inboundCode: item?.inboundCode, destinationType: item?.destinationType,
totalPackages: item?.totalPackages, receivedPackages: item?.receivedPackages,
pendingPackages: item?.pendingPackages,
})).sort((left, right) => `${left.inboundCode}:${left.destinationType}`.localeCompare(`${right.inboundCode}:${right.destinationType}`)),
})
}
function blockerGap(context, name, reason, category = 'fixture-unavailable', response = null) {
context.addCoverageGap(MODULE, SCENARIO, name, reason, category, { deploymentBlocker: true })
@@ -293,10 +307,14 @@ async function awaitPickupReadyCandidates(context, token, producers, allProducer
}
async function resolveCartonFixtures(context, token, inbounds, producerCartonMarks = []) {
const cartonCandidates = [...new Set((Array.isArray(producerCartonMarks) ? producerCartonMarks : [producerCartonMarks])
.map(id).filter(nonBlank))]
const manifestCartons = (Array.isArray(producerCartonMarks) ? producerCartonMarks : [producerCartonMarks])
.map(id).filter(nonBlank)
const fbaCartons = inbounds.filter((item) => item.destinationType === 'FBA')
.map((item, index) => `${item.inboundCode}#PDA-CONTRACT-${index + 1}`)
const overseasCartons = inbounds.some((item) => item.destinationType === 'OVERSEAS') ? manifestCartons : []
const cartonCandidates = [...new Set([...fbaCartons, ...overseasCartons])]
if (!cartonCandidates.length) {
blockerGap(context, '统一箱码解析链', '缺少本轮PDA任务冻结箱码,FBA和OVERSEAS均不能使用伪造箱号')
blockerGap(context, '统一箱码解析链', 'FBA缺少当前柜入仓码或OVERSEAS缺少真实manifest箱码')
return null
}
const cartonFixtures = []
@@ -307,7 +325,7 @@ async function resolveCartonFixtures(context, token, inbounds, producerCartonMar
const matches = inbounds.filter((item) => item.inboundCode === resolved?.inboundCode)
const destinations = new Set(matches.map((item) => item?.destinationType).filter(nonBlank))
const unique = success(response) && nonBlank(resolved?.inboundCode) && !Array.isArray(resolved?.inboundCode)
&& matches.length > 0 && destinations.size === 1
&& matches.length === 1 && destinations.size === 1
&& ['FBA', 'OVERSEAS'].includes(resolved?.destinationType)
&& destinations.has(resolved?.destinationType)
context.addStep(MODULE, `统一解析${resolved?.destinationType || 'UNKNOWN'}箱码${index + 1}并返回唯一入仓码`, response,
@@ -323,6 +341,41 @@ async function resolveCartonFixtures(context, token, inbounds, producerCartonMar
return cartonFixtures
}
async function probeCartonResolveNegatives(context, token, current) {
const fba = current.inbounds.find((item) => item.destinationType === 'FBA')
await expected(context, token, 'POST', '无#箱码被服务端拒绝',
'/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve',
{ cartonMark: fba?.inboundCode || `NO-HASH-${markerSuffix(context)}` }, cartonResolveRejected)
await expected(context, token, 'POST', '空#前缀箱码被服务端拒绝',
'/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve',
{ cartonMark: `#${markerSuffix(context)}` }, cartonResolveRejected)
await expected(context, token, 'POST', '未命中入仓码前缀被服务端拒绝',
'/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve',
{ cartonMark: `UNMATCHED-${markerSuffix(context)}#ANY-SUFFIX` }, cartonResolveRejected)
const allProducers = context.createdResources?.phase4InboundProducerFixtures?.containers || []
const crossContainerMark = allProducers
.filter((item) => id(item?.containerNo) !== id(current.containerNo))
.flatMap((item) => Array.isArray(item?.cartonMarks) ? item.cartonMarks : [item?.cartonMark])
.map(id).find(nonBlank)
if (crossContainerMark) {
await expected(context, token, 'POST', '其他柜箱码前缀被当前锁定柜拒绝',
'/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve',
{ cartonMark: crossContainerMark }, cartonResolveRejected)
} else {
blockerGap(context, '箱码跨柜负向', '缺少另一真实柜的冻结箱码,不能伪造跨柜归属反证')
}
const overseas = current.inbounds.find((item) => item.destinationType === 'OVERSEAS')
if (overseas) {
const total = Math.max(1, Number(overseas.totalPackages || 1))
await expected(context, token, 'POST', 'OVERSEAS缺少真实manifest行时不得按标准箱码推导',
'/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve',
{ cartonMark: `${overseas.inboundCode}#${total + 1}-1` },
(response) => a0404Message(response, /^Carton mark was not found$/))
}
}
async function receiveFba(context, token, inbound, pallet, cartonFixtures, index) {
const resolvedCartons = cartonFixtures.filter((item) => item.inbound.inboundCode === inbound.inboundCode
&& item.resolved.destinationType === 'FBA')
@@ -454,10 +507,18 @@ async function runReceiving(context, token, secondaryToken, merchantToken, prefe
{ ...signBody, photos: [...photos].reverse() }, conflict)
const current = await pda(context, token, 'GET', '签收后按账号恢复当前柜', '/api/pda/v1/last-leg/receiving-containers/current')
if (!success(current.response)) return
const beforeResolve = receivingSnapshot(current.data)
await probeCartonResolveNegatives(context, token, current.data)
const producerCartonMarks = preferredProducer?.cartonMarks?.length
? preferredProducer.cartonMarks : [preferredProducer?.cartonMark]
const cartonFixtures = await resolveCartonFixtures(context, token, current.data.inbounds, producerCartonMarks)
if (!cartonFixtures) return
const afterResolve = await pda(context, token, 'GET', '箱码解析正负向后回查当前柜零写入',
'/api/pda/v1/last-leg/receiving-containers/current')
context.assert(MODULE, 'FBA与OVERSEAS箱码resolve均为纯查询且零状态写入', afterResolve.response,
success(afterResolve.response) && receivingSnapshot(afterResolve.data) === beforeResolve,
`before=${beforeResolve}, after=${receivingSnapshot(afterResolve.data)}; ${trace(afterResolve.response)}`, OPTIONS)
if (!success(afterResolve.response) || receivingSnapshot(afterResolve.data) !== beforeResolve) return
const pallets = new Map()
for (const [index, inbound] of current.data.inbounds.entries()) {
const pallet = await createPallet(context, inbound, index)