From 045d0d815b3fed3cbdfa190dc645d5aeb84af6a8 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 9 Sep 2026 02:48:10 +0800 Subject: [PATCH] test(overseas): cover split relabel inventory --- .../phase4-overseas-pda-contract.test.mjs | 2 +- ...-overseas-pda-operations-contract.test.mjs | 2 +- .../phase4-overseas-pda-operations.mjs | 71 +++++++++++++------ .../phase4-overseas-pda.mjs | 7 +- 4 files changed, 57 insertions(+), 25 deletions(-) diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda-contract.test.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda-contract.test.mjs index d5100357a..5c9daa3b9 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda-contract.test.mjs @@ -183,7 +183,7 @@ test('PDA receiving lifecycle is ordered from candidate claim through photos, mo '/current/receipts/bulk', '/current/receipts/carton', "'FBA模式硬拒绝逐箱收货'", "'OVERSEAS模式硬拒绝批量收货'", 'PHOTO_ROLES.every', "finalStatus === 'COMPLETED'", 'completedContainers.push', - 'completedContainerFacts.push', 'expectedPalletCost', 'physicalPallets', 'resolvedPallet.data?.unitPrice', + 'completedContainerFacts.push', 'expectedPalletCost', 'physicalPallets', '完成入库后读取托盘计费单价', ]) assert.ok(source.includes(token), token) }) diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs index 754514e19..e687ecbd3 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs @@ -49,7 +49,7 @@ test('P2 to P5 cover all frozen endpoints, replays and conflict boundaries witho 'SUBMITTED', '移回原库位', 'WAIT_REVIEW', 'REWORK', 'APPROVE', 'COMPLETED', 'chargeAmount: 0.01', 'cartonMark:', 'fbaCartonMark:', 'inventory-candidates', 'sourceInventoryBalanceId:', 'sourceInventoryPositionId:', - 'sourceInboundOrderId:', 'operationWarehouseId:', + 'sourceInboundOrderId:', 'operationWarehouseId:', 'relabelItemForUnit', 'sourceLines.map', 'P5换标费用冻结后开放PDA作业', 'P5换标完成后钱包结算收敛']) { assert.ok(source.includes(marker), marker) } diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs index cef16b7bb..0a1ddccbd 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs @@ -29,6 +29,15 @@ function operationPallet(result, palletCode) { return (result?.pallets || []).find((item) => item?.palletCode === palletCode) } +function relabelItemForUnit(detail, unitIndex) { + let remaining = unitIndex + for (const item of detail?.items || []) { + if (remaining < Number(item?.expectedPackages || 0)) return item + remaining -= Number(item?.expectedPackages || 0) + } + return null +} + function blocker(context, name, reason, response = null) { context.addCoverageGap(MODULE, SCENARIO, name, reason, 'fixture-unavailable', { deploymentBlocker: true }) context.assert(MODULE, `${name}覆盖前置`, response, false, `${reason}; ${trace(response)}`, OPTIONS) @@ -344,35 +353,51 @@ async function verifyRelabel(context, token, merchantToken, fixture) { }), undefined, {}, merchantToken) const inventoryCandidates = success(inventoryCandidatesResponse) && Array.isArray(inventoryCandidatesResponse.body?.data) ? inventoryCandidatesResponse.body.data : [] - const source = inventoryCandidates.find((item) => id(item?.inventoryBalanceId) === id(inventory?.balanceId)) - || inventoryCandidates.find((item) => item?.skuCode === inventory.skuCode - && item?.inboundCode === fixture.inboundCode) - const sourcePosition = source?.positions?.find((item) => id(item?.positionId) === id(inventory?.positionId) - && Number(item?.availablePackages) >= 3) - || source?.positions?.find((item) => Number(item?.availablePackages) >= 3) + const orderedCandidates = [...inventoryCandidates].sort((left, right) => { + const leftPreferred = id(left?.inventoryBalanceId) === id(inventory?.balanceId) ? 1 : 0 + const rightPreferred = id(right?.inventoryBalanceId) === id(inventory?.balanceId) ? 1 : 0 + return rightPreferred - leftPreferred + }) + const sourceLines = [] + let requiredPackages = 3 + for (const source of orderedCandidates) { + if (!id(source?.inventoryBalanceId) || !id(source?.inboundOrderId)) continue + for (const position of source?.positions || []) { + if (!id(position?.positionId) || Number(position?.availablePackages) <= 0) continue + const packageCount = Math.min(requiredPackages, Number(position.availablePackages)) + sourceLines.push({ source, position, packageCount }) + requiredPackages -= packageCount + if (requiredPackages === 0) break + } + if (requiredPackages === 0) break + } context.addStep(MODULE, 'P5客户库存候选返回换标权威来源', inventoryCandidatesResponse, - Boolean(source) && Boolean(sourcePosition) && id(source?.inboundOrderId) !== '', - `balanceId=${id(source?.inventoryBalanceId) || 'missing'}, positionId=${id(sourcePosition?.positionId) || 'missing'}, inboundOrderId=${id(source?.inboundOrderId) || 'missing'}; ${trace(inventoryCandidatesResponse)}`, OPTIONS) - if (!source || !sourcePosition || !id(source.inboundOrderId)) { - blocker(context, 'P5本轮库存换标来源', '客户库存候选未返回本轮库存的余额、至少三箱位置和来源入库单', inventoryCandidatesResponse) + requiredPackages === 0, + `sourceLines=${sourceLines.length}, selectedPackages=${3 - requiredPackages}; ${trace(inventoryCandidatesResponse)}`, OPTIONS) + if (requiredPackages !== 0) { + blocker(context, 'P5本轮库存换标来源', '客户库存候选未返回可组合三箱的权威余额、位置和来源入库单', inventoryCandidatesResponse) return false } const relabelBase = '/api/platform/v1/last-leg/relabel-applications' const pdaBase = '/api/pda/v1/last-leg/relabel-tasks' - const newSku = `${inventory.skuCode}-NEW-${String(context.marker).slice(-8)}`.slice(0, 128) + const markerSuffix = String(context.marker).slice(-8) const createBody = { customerOrderNo: String(customerOrderNo).slice(0, 128), operationWarehouseId: id(fixture.overseasWarehouseId), labelDeadline: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 19), contactName: 'PDA换标验收', contactPhone: '13800000006', chargeAmount: 0.01, remark: context.marker, idempotencyKey: key(context, 'RELABEL-CREATE'), - items: [{ sourceInventoryBalanceId: id(source.inventoryBalanceId), - sourceInventoryPositionId: id(sourcePosition.positionId), sourceInboundOrderId: id(source.inboundOrderId), + items: sourceLines.map(({ source, position, packageCount }, index) => ({ + sourceInventoryBalanceId: id(source.inventoryBalanceId), + sourceInventoryPositionId: id(position.positionId), sourceInboundOrderId: id(source.inboundOrderId), skuCode: source.skuCode, productNameZh: source.productNameZh || inventory.productName || '本轮库存商品', - totalPackageCount: 3, notifiedPackageCount: 3, shipmentNo: `SHIP-${context.marker}`.slice(0, 128), - newSkuCode: newSku, labelRequirement: context.marker, - cartonMark: `CM-${String(context.marker).slice(-32)}`, - fbaCartonMark: `FBA-${String(context.marker).slice(-32)}`, - destinationFbaWarehouseCode: source.destinationFbaWarehouseCode }], + totalPackageCount: packageCount, notifiedPackageCount: packageCount, + shipmentNo: `SHIP-${context.marker}-${index + 1}`.slice(0, 128), + newSkuCode: `${source.skuCode}-NEW-${markerSuffix}-${index + 1}`.slice(0, 128), + labelRequirement: context.marker, + cartonMark: `CM-${String(context.marker).slice(-28)}-${index + 1}`, + fbaCartonMark: `FBA-${String(context.marker).slice(-27)}-${index + 1}`, + destinationFbaWarehouseCode: source.destinationFbaWarehouseCode, + })), } const createdResponse = await context.requestOverseas('POST', query(relabelBase, { customerId }), createBody) @@ -407,13 +432,13 @@ async function verifyRelabel(context, token, merchantToken, fixture) { claimReplay.data?.taskStatus === 'PROCESSING' && claimed.data?.taskStatus === 'PROCESSING', trace(claimReplay.response), OPTIONS) let detail = claimed.data || available.data - const item = detail?.items?.[0] + const item = relabelItemForUnit(detail, 0) if (!id(item?.itemId)) { blocker(context, 'P5换标明细', '认领后的换标任务没有真实明细', claimed.response) return false } const unitPath = `${pdaBase}/${encodeURIComponent(relabelNo)}/units` - const scanBody = { itemId: id(item.itemId), oldLabel: `${context.marker}-OLD-1`, newLabel: newSku, + const scanBody = { itemId: id(item.itemId), oldLabel: `${context.marker}-OLD-1`, newLabel: item.newSkuCode, expectedVersion: Number(detail.version), idempotencyKey: key(context, 'RELABEL-NORMAL') } const scanned = await request(context, token, 'POST', 'P5正常换标一箱', unitPath, scanBody) const scanReplay = await request(context, token, 'POST', 'P5正常换标同键重放', unitPath, scanBody) @@ -424,7 +449,8 @@ async function verifyRelabel(context, token, merchantToken, fixture) { await expectConflict(context, token, 'POST', 'P5正常换标旧版本A0409', unitPath, { ...scanBody, oldLabel: `${context.marker}-OLD-STALE`, idempotencyKey: key(context, 'RELABEL-STALE') }) detail = scanned.data - const skipBody = { itemId: id(item.itemId), oldLabel: `${context.marker}-OLD-2`, + const skipItem = relabelItemForUnit(detail, 1) + const skipBody = { itemId: id(skipItem?.itemId), oldLabel: `${context.marker}-OLD-2`, reason: `${context.marker}-skip`, expectedVersion: Number(detail.version), idempotencyKey: key(context, 'RELABEL-SKIP') } const skipped = await request(context, token, 'POST', 'P5跳过换标一箱', `${unitPath}/skip`, skipBody) @@ -432,7 +458,8 @@ async function verifyRelabel(context, token, merchantToken, fixture) { context.assert(MODULE, 'P5跳过换标重放不重复计数', skipReplay.response, Number(skipReplay.data?.skippedPackages) === 1, trace(skipReplay.response), OPTIONS) detail = skipped.data - const exceptionBody = { itemId: id(item.itemId), oldLabel: `${context.marker}-OLD-3`, + const exceptionItem = relabelItemForUnit(detail, 2) + const exceptionBody = { itemId: id(exceptionItem?.itemId), oldLabel: `${context.marker}-OLD-3`, exceptionCode: 'DAMAGED', reason: `${context.marker}-damaged`, expectedVersion: Number(detail.version), idempotencyKey: key(context, 'RELABEL-EXCEPTION') } const exception = await request(context, token, 'POST', 'P5登记换标异常一箱', diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda.mjs index db1c8b9b7..bae7421e0 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda.mjs @@ -212,7 +212,12 @@ async function verifyWarehouseQueries(context, token, inboundCode, pallet, desti && Number(resolvedPallet.data?.occupiedPackages) > 0, `palletId=${id(resolvedPallet.data?.palletId) || 'missing'}; ${trace(resolvedPallet.response)}`, OPTIONS) - if (Number(resolvedPallet.data?.unitPrice) > 0) pallet.unitPrice = Number(resolvedPallet.data.unitPrice) + const palletDetail = await context.requestOverseas('GET', + `/api/platform/v1/warehouse-admin/${encodeURIComponent(pallet.warehouseId)}/pallets/${encodeURIComponent(pallet.palletId)}`) + context.addStep(MODULE, '完成入库后读取托盘计费单价', palletDetail, + success(palletDetail) && Number(palletDetail.body?.data?.unitPrice) > 0, + `palletId=${pallet.palletId}, unitPrice=${palletDetail.body?.data?.unitPrice ?? 'missing'}; ${trace(palletDetail)}`, OPTIONS) + if (Number(palletDetail.body?.data?.unitPrice) > 0) pallet.unitPrice = Number(palletDetail.body.data.unitPrice) const locationCode = resolvedPallet.data?.locationCode || pallet.locationCode const location = await pda(context, token, 'POST', '完成入库后扫码查询库位',