fix(dropship): generate pallet numbers atomically and extend full regressions

This commit is contained in:
2026-09-09 12:19:16 +08:00
100 changed files with 2889 additions and 234 deletions
@@ -113,7 +113,7 @@ async function waitForCompletedMeasurement(context, measurementPath, expectedSta
return { response, measurement, attempts }
}
async function completeDropshipPalletization(context, { inboundId, inboundCode, warehouseId, measurement }) {
export async function completeDropshipPalletization(context, { inboundId, inboundCode, warehouseId, measurement }) {
const pendingResponse = await context.request('GET',
`/api/platform/v1/first-leg/pda-dropship/tasks/pending?keyword=${encodeURIComponent(inboundCode)}`)
const pending = context.apiSuccess(MODULE, '查询一件代发待合托任务', pendingResponse,
@@ -135,15 +135,31 @@ async function completeDropshipPalletization(context, { inboundId, inboundCode,
`taskStatus=${claimed?.taskStatus ?? 'missing'}`, OPTIONS)
const totalCartons = Number(measurement?.actualPackageCount)
const palletDate = new Date().toISOString().slice(2, 10).replace(/-/g, '')
let palletHash = 0
for (const character of `${context.marker}:${taskId}`) {
palletHash = (Math.imul(palletHash, 31) + character.charCodeAt(0)) >>> 0
}
const palletNo = `DP-${palletDate}-${String(palletHash % 10_000).padStart(4, '0')}`
const createPath = `/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/pallets/create-and-bind`
const taskPath = `/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}?warehouseId=${warehouseId}`
const createPayload = { warehouseId, operationId: `${context.marker}-dropship-create-bind`.slice(0, 128) }
let palletNo = ''
let countedCartons = 0
let pallet = null
const palletSnapshot = (value) => JSON.stringify({
counted: value?.countedCartons, bound: value?.boundCartons, unbound: value?.unboundCartons,
pallets: value?.pallets?.map((item) => ({ id: asId(item.palletId), no: item.palletNo,
count: item.cartonCount, status: item.status, version: item.version })),
})
async function rejectEmptyCreation(before, suffix) {
const response = await context.request('POST', createPath,
{ ...createPayload, operationId: `${context.marker}-dropship-empty-${suffix}`.slice(0, 128) })
context.assert(MODULE, `无待绑箱创建托盘被拒绝-${suffix}`, response,
response.http === 200 && String(response.body?.code) === 'A0409',
`HTTP=${response.http}, code=${response.body?.code || 'missing'}`, OPTIONS)
const afterResponse = await context.request('GET', taskPath)
const after = context.apiSuccess(MODULE, `拒绝创建后回查任务-${suffix}`, afterResponse, '', OPTIONS)
context.assert(MODULE, `拒绝创建没有空托或计数漂移-${suffix}`, afterResponse,
palletSnapshot(after) === palletSnapshot(before), `pallets=${after?.pallets?.length ?? 'missing'}`, OPTIONS)
}
await rejectEmptyCreation(claimed, 'before-count')
for (let index = 0; index < totalCartons; index += 1) {
const boxCode = `${inboundCode}#${totalCartons}-${index + 1}`
const resolveResponse = await context.request('GET',
@@ -189,18 +205,21 @@ async function completeDropshipPalletization(context, { inboundId, inboundCode,
&& Number(replayed?.unboundCartons) === Number(counted?.unboundCartons),
`counted=${replayed?.countedCartons ?? 'missing'}, unbound=${replayed?.unboundCartons ?? 'missing'}`, OPTIONS)
const bindResponse = await context.request('POST',
`/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/pallets/bind-pending`, {
warehouseId, palletNo,
operationId: `${context.marker}-dropship-bind-pending`.slice(0, 128),
})
const bound = context.apiSuccess(MODULE, '扫描托盘并绑定全部待组托箱', bindResponse,
`taskId=${taskId}, palletNo=${palletNo}`, OPTIONS)
pallet = bound?.pallets?.find((item) => item?.palletNo === palletNo) || null
const bindResponse = await context.request('POST', createPath, createPayload)
const bound = context.apiSuccess(MODULE, '系统发码创建托盘并绑定全部待组托箱', bindResponse,
`taskId=${taskId}`, OPTIONS)
pallet = bound?.pallets?.find((item) => Number(item.cartonCount) === 1) || null
palletNo = pallet?.palletNo || ''
context.assert(MODULE, '待组托箱绑定到OPEN托盘且不再游离', bindResponse,
Number(bound?.unboundCartons) === 0 && Number(pallet?.cartonCount) === 1
&& pallet?.status === 'OPEN',
&& pallet?.status === 'OPEN' && /^DP-\d{6}-\d{4}$/.test(palletNo)
&& bound?.pallets?.length === 1,
`unbound=${bound?.unboundCartons ?? 'missing'}, palletStatus=${pallet?.status ?? 'missing'}`, OPTIONS)
const createReplayResponse = await context.request('POST', createPath, createPayload)
const createReplay = context.apiSuccess(MODULE, '系统发码同operationId重放', createReplayResponse, '', OPTIONS)
context.assert(MODULE, '系统发码重放不新增托盘且业务码和计数稳定', createReplayResponse,
palletSnapshot(createReplay) === palletSnapshot(bound), `palletNo=${palletNo}`, OPTIONS)
await rejectEmptyCreation(bound, 'after-bind')
} else {
pallet = counted?.pallet || pallet
context.assert(MODULE, `第${index + 1}箱直接进入当前OPEN托盘`, countResponse,
@@ -229,6 +248,11 @@ async function completeDropshipPalletization(context, { inboundId, inboundCode,
&& Number(sealed?.unboundCartons) === 0
&& sealed?.pallets?.every((item) => item?.status === 'SEALED'),
`taskStatus=${sealed?.taskStatus ?? 'missing'}, counted=${sealed?.countedCartons ?? 'missing'}, bound=${sealed?.boundCartons ?? 'missing'}`, OPTIONS)
const sealedReplayResponse = await context.request('POST', createPath, createPayload)
const sealedReplay = context.apiSuccess(MODULE, '封托完成后系统发码原操作仍可重放', sealedReplayResponse, '', OPTIONS)
context.assert(MODULE, '完成后重放不重开任务或新建空托', sealedReplayResponse,
sealedReplay?.taskStatus === 'COMPLETED' && palletSnapshot(sealedReplay) === palletSnapshot(sealed),
`taskStatus=${sealedReplay?.taskStatus || 'missing'}, palletNo=${palletNo}`, OPTIONS)
return sealed
}