diff --git a/scripts/admin-test-env-modules/inbound-measurement-box-code-contract.test.mjs b/scripts/admin-test-env-modules/inbound-measurement-box-code-contract.test.mjs index f5c7e101c..01193adbc 100644 --- a/scripts/admin-test-env-modules/inbound-measurement-box-code-contract.test.mjs +++ b/scripts/admin-test-env-modules/inbound-measurement-box-code-contract.test.mjs @@ -100,9 +100,13 @@ test('approval freezes measurement and requires quantity weight and volume conse test('dropship measurement completes the formal palletization workflow before smart loading', () => { assert.match(source, /pda-dropship\/tasks\/pending/) assert.match(source, /pda-dropship\/tasks\/\$\{taskId\}\/claim/) - assert.match(source, /pallets\/open/) - assert.match(source, /pallets\/\$\{palletId\}\/scan/) + assert.match(source, /cartons\/resolve/) + assert.match(source, /cartons\/count/) + assert.match(source, /pallets\/bind-pending/) assert.match(source, /pallets\/\$\{palletId\}\/seal/) + assert.match(source, /重复清点操作按operationId幂等返回/) + assert.doesNotMatch(source, /pallets\/open/) + assert.doesNotMatch(source, /pallets\/\$\{palletId\}\/scan/) assert.match(source, /dropship \? \['WAIT_PALLETIZE'\] : \['WAIT_SPLIT'\]/) assert.match(source, /一件代发合托完成后进入智能分柜门禁/) }) diff --git a/scripts/admin-test-env-modules/inbound-measurement.mjs b/scripts/admin-test-env-modules/inbound-measurement.mjs index a5397e769..45363ca0a 100644 --- a/scripts/admin-test-env-modules/inbound-measurement.mjs +++ b/scripts/admin-test-env-modules/inbound-measurement.mjs @@ -134,35 +134,85 @@ async function completeDropshipPalletization(context, { inboundId, inboundCode, claimed?.taskStatus === 'CLAIMED' && asId(claimed?.inboundId) === inboundId, `taskStatus=${claimed?.taskStatus ?? 'missing'}`, OPTIONS) - const openResponse = await context.request('POST', - `/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/pallets/open`, { - warehouseId, operationId: `${context.marker}-dropship-open`.slice(0, 128), - }) - let pallet = context.apiSuccess(MODULE, '开启一件代发工作托盘', openResponse, - `taskId=${taskId}`, OPTIONS) - const palletId = asId(pallet?.palletId) - if (!palletId) return null - const totalCartons = Number(measurement?.actualPackageCount) - let scannedCartons = 0 + 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')}` + let countedCartons = 0 + let pallet = null + for (let index = 0; index < totalCartons; index += 1) { const boxCode = `${inboundCode}#${totalCartons}-${index + 1}` - const scanResponse = await context.request('POST', - `/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/pallets/${palletId}/scan`, { - warehouseId, boxCode, cartonCount: 1, - operationId: `${context.marker}-dropship-scan-${index + 1}`.slice(0, 128), - }) - const scanned = context.apiSuccess(MODULE, `合托扫描第${index + 1}箱冻结箱唛`, scanResponse, + const resolveResponse = await context.request('GET', + `/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/cartons/resolve?warehouseId=${warehouseId}&boxCode=${encodeURIComponent(boxCode)}`) + const resolved = context.apiSuccess(MODULE, `解析第${index + 1}箱的冻结箱唛`, resolveResponse, `boxCode=${boxCode}`, OPTIONS) - context.assert(MODULE, `第${index + 1}箱进入工作托盘`, scanResponse, - Number(scanned?.addedCartons) === 1 && asId(scanned?.pallet?.palletId) === palletId, - `added=${scanned?.addedCartons ?? 'missing'}, remaining=${scanned?.remainingCartons ?? 'missing'}`, OPTIONS) - pallet = scanned?.pallet || pallet - if (Number(scanned?.addedCartons) === 1) scannedCartons += 1 + context.assert(MODULE, `第${index + 1}箱允许清点且每箱SKU数完整`, resolveResponse, + resolved?.canCount === true && Number(resolved?.expectedSkuQuantity) > 0, + `canCount=${resolved?.canCount ?? 'missing'}, expectedSkuQuantity=${resolved?.expectedSkuQuantity ?? 'missing'}`, OPTIONS) + if (resolved?.canCount !== true || Number(resolved?.expectedSkuQuantity) < 1) continue + + const countPayload = { + warehouseId, + boxCode, + actualSkuQuantity: Number(resolved.expectedSkuQuantity), + discrepancyAction: 'MATCH', + discrepancyRemark: null, + palletNo: index === 0 ? null : palletNo, + operationId: `${context.marker}-dropship-count-${index + 1}`.slice(0, 128), + } + const countResponse = await context.request('POST', + `/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/cartons/count`, countPayload) + const counted = context.apiSuccess(MODULE, `清点第${index + 1}箱并冻结实收SKU数`, countResponse, + `boxCode=${boxCode}`, OPTIONS) + context.assert(MODULE, `第${index + 1}箱清点结果与冻结箱唛一致`, countResponse, + asId(counted?.carton?.cartonId) === asId(resolved?.cartonId) + && Number(counted?.carton?.actualSkuQuantity) === Number(resolved?.expectedSkuQuantity) + && counted?.carton?.discrepancyStatus === 'MATCHED', + `cartonId=${asId(counted?.carton?.cartonId) || 'missing'}, discrepancy=${counted?.carton?.discrepancyStatus ?? 'missing'}`, OPTIONS) + if (asId(counted?.carton?.cartonId) === asId(resolved?.cartonId)) countedCartons += 1 + + if (index === 0) { + context.assert(MODULE, '先扫箱模式将已清点箱暂存为待组托', countResponse, + Number(counted?.unboundCartons) === 1 && counted?.pallet == null, + `unboundCartons=${counted?.unboundCartons ?? 'missing'}`, OPTIONS) + const replayResponse = await context.request('POST', + `/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/cartons/count`, countPayload) + const replayed = context.apiSuccess(MODULE, '重复清点操作按operationId幂等返回', replayResponse, + `boxCode=${boxCode}`, OPTIONS) + context.assert(MODULE, '重复清点没有重复累计箱数', replayResponse, + Number(replayed?.countedCartons) === Number(counted?.countedCartons) + && 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 + context.assert(MODULE, '待组托箱绑定到OPEN托盘且不再游离', bindResponse, + Number(bound?.unboundCartons) === 0 && Number(pallet?.cartonCount) === 1 + && pallet?.status === 'OPEN', + `unbound=${bound?.unboundCartons ?? 'missing'}, palletStatus=${pallet?.status ?? 'missing'}`, OPTIONS) + } else { + pallet = counted?.pallet || pallet + context.assert(MODULE, `第${index + 1}箱直接进入当前OPEN托盘`, countResponse, + counted?.pallet?.palletNo === palletNo && counted?.pallet?.status === 'OPEN', + `palletNo=${counted?.pallet?.palletNo ?? 'missing'}, status=${counted?.pallet?.status ?? 'missing'}`, OPTIONS) + } } context.assert(MODULE, '一件代发工作托盘覆盖测量冻结的全部箱数', null, - totalCartons > 0 && scannedCartons === totalCartons, - `scanned=${scannedCartons}, expected=${totalCartons || 'missing'}`, OPTIONS) + totalCartons > 0 && countedCartons === totalCartons && Number(pallet?.cartonCount) === totalCartons, + `counted=${countedCartons}, palletCartons=${pallet?.cartonCount ?? 'missing'}, expected=${totalCartons || 'missing'}`, OPTIONS) + + const palletId = asId(pallet?.palletId) + if (!palletId) return null const sealResponse = await context.request('POST', `/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/pallets/${palletId}/seal`, { @@ -173,9 +223,11 @@ async function completeDropshipPalletization(context, { inboundId, inboundCode, `taskId=${taskId}, palletId=${palletId}`, OPTIONS) context.assert(MODULE, '全部箱数封托后入仓单允许进入WAIT_SPLIT', sealResponse, sealed?.taskStatus === 'COMPLETED' - && Number(sealed?.allocatedCartons) === Number(sealed?.totalCartons) - && sealed?.pallets?.every((item) => item?.palletStatus === 'COMPLETED'), - `taskStatus=${sealed?.taskStatus ?? 'missing'}, allocated=${sealed?.allocatedCartons ?? 'missing'}`, OPTIONS) + && Number(sealed?.countedCartons) === Number(sealed?.totalCartons) + && Number(sealed?.boundCartons) === Number(sealed?.totalCartons) + && Number(sealed?.unboundCartons) === 0 + && sealed?.pallets?.every((item) => item?.status === 'SEALED'), + `taskStatus=${sealed?.taskStatus ?? 'missing'}, counted=${sealed?.countedCartons ?? 'missing'}, bound=${sealed?.boundCartons ?? 'missing'}`, OPTIONS) return sealed }