fix: validate transfer rejection and align dispatch HTTP contracts

This commit is contained in:
2026-09-14 13:46:32 +08:00
parent a2ea0ba9e6
commit d8e2dc59ae
9 changed files with 120 additions and 13 deletions
@@ -9,6 +9,15 @@ function id(value) { return value == null ? '' : String(value) }
function records(value) { return Array.isArray(value?.records) ? value.records : [] }
function codeOf(response) { return response?.body?.code == null ? '' : String(response.body.code) }
function success(response) { return response?.http === 200 && codeOf(response) === '200' }
export function dispatchBusinessSnapshot(value) {
return JSON.stringify({ id: id(value?.id), dispatchNo: value?.dispatchNo, status: value?.status,
packageCount: value?.packageCount, palletCount: value?.palletCount,
items: (value?.items || []).map(item => ({ planPalletId: id(item.planPalletId),
warehousePalletId: id(item.warehousePalletId), sourceStage: item.sourceStage,
packageCount: item.packageCount, inboundCode: item.inboundCode }))
.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))),
})
}
function conflict(response) { return response?.http === 409 || (response?.http === 200 && codeOf(response) === 'A0409') }
function trace(response) {
return `HTTP=${response?.http ?? 'missing'}, code=${codeOf(response) || 'missing'}, requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}`
@@ -76,12 +85,11 @@ async function runRelabelDispatch(context, relabel) {
relabelNo: relabel.relabelNo, planPalletId: null,
})
const detail = await api(context, 'GET', '回查本轮换标排车详情', `${BASE}/${encodeURIComponent(dispatchNo)}`)
const detailMarks = (detail.data?.items || []).flatMap((item) => String(item?.cartonMarks || '')
.split(',').map((mark) => mark.trim()).filter(Boolean))
context.assert(MODULE, '排车详情冻结全部换标后箱唛且不混入原箱唛', detail.response,
relabel.relabelCartonMarks.every((mark) => detailMarks.includes(mark))
&& relabel.sourceCartonMarks.every((mark) => !detailMarks.includes(mark)),
`frozenMarks=${detailMarks.join(',')}; ${trace(detail.response)}`, OPTIONS)
// DispatchResponse.Item omits cartonMarks. Phase5 verifies the persisted marks
// through OutboundDetail.pallets after transferring this exact dispatch.
context.assert(MODULE, '排车详情保留本轮换标托盘与箱数快照', detail.response,
success(detail.response) && dispatchBusinessSnapshot(detail.data) === dispatchBusinessSnapshot(arranged.data),
trace(detail.response), OPTIONS)
}
export async function run(context) {
@@ -233,8 +241,10 @@ export async function run(context) {
context.createdResources.phase4OverseasDispatch.finalStatus = arranged.data?.status || null
const duplicateArrange = await context.requestOverseas('POST', `${BASE}/pre-arrange`, preArrangeBody)
context.addStep(MODULE, '已转换正式锁的旧草稿载荷再次预排返回A0409', duplicateArrange,
conflict(duplicateArrange), trace(duplicateArrange), OPTIONS)
context.addStep(MODULE, '出库前同草稿重复预排复用原排车且业务快照不漂移', duplicateArrange,
success(duplicateArrange)
&& dispatchBusinessSnapshot(duplicateArrange.body?.data) === dispatchBusinessSnapshot(arranged.data),
trace(duplicateArrange), OPTIONS)
const pageResult = await api(context, 'GET', '按排车单号回查跨客户分页', query(BASE, {
page: 1, size: 20, dispatchNo,