From 9abb0004ddc7344daff236a48a41aa8eccdb2efb Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 11 Sep 2026 00:24:35 +0800 Subject: [PATCH] test: align overseas PDA group receipt semantics --- .../phase4-overseas-pda-contract.test.mjs | 6 ++- .../phase4-overseas-pda.mjs | 53 +++++++++++++------ ...s-wallet-and-pallet-cost-contract.test.mjs | 1 + ...phase4-overseas-wallet-and-pallet-cost.mjs | 4 +- 4 files changed, 44 insertions(+), 20 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 35543db25..556e3d9d5 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 @@ -107,10 +107,12 @@ test('PDA module covers the container-number receiving contract and excludes out assert.match(source, /安全投影已收敛/) assert.match(source, /projectionReady = success\(projectionResponse\) \|\| notFound\(projectionResponse\)/) assert.match(source, /setTimeout\(resolve, 500\)/) - assert.match(source, /超收被拒绝/) + assert.match(source, /重复累计不产生超收/) assert.match(source, /重复箱新键被拒绝/) assert.match(source, /key\(context, `fba-bulk-\$\{index\}`\)/) - assert.match(source, /packageCount = Number\(inbound\.pendingPackages \|\| 0\)/) + assert.match(source, /inbounds\.reduce\(\(total, inbound\) => total \+ Number\(inbound\.totalPackages \|\| 0\), 0\)/) + assert.match(source, /resolvedInboundCodes\.includes\(scannedInboundCode\)/) + assert.match(source, /duplicate\.data\?\.result === 'ALREADY_RECEIVED'/) assert.match(source, /if \(packageCount <= 0\) return true/) assert.match(source, /key\(context, `overseas-carton-\$\{index\}`\)/) assert.match(source, /模式硬拒绝/) diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda.mjs index e0ec1df74..e9f6d1913 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda.mjs @@ -465,12 +465,17 @@ export async function resolveCartonFixtures(context, token, inbounds, producerCa continue } const resolved = response.body?.data - const matches = inbounds.filter((item) => item.inboundCode === resolved?.inboundCode) + const delimiter = cartonMark.indexOf('#') + const scannedInboundCode = delimiter > 0 ? cartonMark.slice(0, delimiter).trim() : '' + const resolvedInboundCodes = Array.isArray(resolved?.inboundCodes) + ? resolved.inboundCodes.map(id) : [id(resolved?.inboundCode)].filter(nonBlank) + const matches = inbounds.filter((item) => item.inboundCode === scannedInboundCode) const destinations = new Set(matches.map((item) => item?.destinationType).filter(nonBlank)) const unique = success(response) && nonBlank(resolved?.inboundCode) && !Array.isArray(resolved?.inboundCode) && matches.length === 1 && destinations.size === 1 && ['FBA', 'OVERSEAS'].includes(resolved?.destinationType) && destinations.has(resolved?.destinationType) + && resolvedInboundCodes.includes(scannedInboundCode) && (!strictDropship || (resolved?.cartonMark === cartonMark && resolved?.inboundCode === dropshipProducer.inboundCode && resolved?.destinationType === dropshipProducer.destinationType)) context.addStep(MODULE, `统一解析${resolved?.destinationType || 'UNKNOWN'}箱码${index + 1}并返回唯一入仓码`, response, @@ -521,29 +526,33 @@ async function probeCartonResolveNegatives(context, token, current) { } } -async function receiveFba(context, token, inbound, pallet, cartonFixtures, index) { - const resolvedCartons = cartonFixtures.filter((item) => item.inbound.inboundCode === inbound.inboundCode +async function receiveFba(context, token, inbounds, pallet, cartonFixtures, index) { + const inboundCodes = new Set(inbounds.map((item) => item.inboundCode)) + const resolvedCartons = cartonFixtures.filter((item) => inboundCodes.has(item.inbound.inboundCode) && item.resolved.destinationType === 'FBA') - if (!resolvedCartons.length) { - blockerGap(context, `FBA入仓码${inbound.inboundCode}箱码解析`, '没有冻结箱码以200/code 200唯一解析到当前FBA入仓码') + if (resolvedCartons.length !== inbounds.length) { + blockerGap(context, `FBA组${[...inboundCodes].join('|')}箱码解析`, '没有为FBA组内每个入仓码完成唯一解析') return false } - const packageCount = Number(inbound.pendingPackages || 0) + const packageCount = inbounds.reduce((total, inbound) => total + Number(inbound.totalPackages || 0), 0) if (packageCount <= 0) return true - const body = { inboundCode: inbound.inboundCode, palletCode: pallet.palletCode, + const body = { inboundCode: inbounds[0].inboundCode, palletCode: pallet.palletCode, packageCount, idempotencyKey: key(context, `fba-bulk-${index}`) } - const first = await pda(context, token, 'POST', `FBA入仓码${inbound.inboundCode}按箱数实收`, + const first = await pda(context, token, 'POST', `FBA组${[...inboundCodes].join('|')}按累计箱数实收`, '/api/pda/v1/last-leg/receiving-containers/current/receipts/bulk', body) if (!success(first.response)) return false - const replay = await pda(context, token, 'POST', `FBA入仓码${inbound.inboundCode}同键同载荷重放`, + const replay = await pda(context, token, 'POST', `FBA组${[...inboundCodes].join('|')}同键同载荷重放`, '/api/pda/v1/last-leg/receiving-containers/current/receipts/bulk', body) - context.assert(MODULE, `FBA入仓码${inbound.inboundCode}幂等结果一致`, replay.response, + context.assert(MODULE, `FBA组${[...inboundCodes].join('|')}幂等结果一致`, replay.response, success(replay.response) && JSON.stringify(replay.data) === JSON.stringify(first.data), trace(replay.response), OPTIONS) - await expected(context, token, 'POST', `FBA入仓码${inbound.inboundCode}同键异载荷冲突`, + await expected(context, token, 'POST', `FBA组${[...inboundCodes].join('|')}同键异载荷冲突`, '/api/pda/v1/last-leg/receiving-containers/current/receipts/bulk', { ...body, packageCount: packageCount + 1 }, conflict) - await expected(context, token, 'POST', `FBA入仓码${inbound.inboundCode}超收被拒绝`, + const already = await pda(context, token, 'POST', `FBA组${[...inboundCodes].join('|')}新键重复累计为零增量`, '/api/pda/v1/last-leg/receiving-containers/current/receipts/bulk', - { ...body, packageCount: Number(inbound.totalPackages) + 1, idempotencyKey: key(context, `fba-over-${index}`) }, conflict) + { ...body, idempotencyKey: key(context, `fba-already-${index}`) }) + context.assert(MODULE, `FBA组${[...inboundCodes].join('|')}重复累计不产生超收`, already.response, + success(already.response) && already.data?.result === 'ALREADY_RECEIVED' + && Number(already.data?.acceptedPackages) === 0, trace(already.response), OPTIONS) return true } @@ -604,9 +613,12 @@ async function receiveDropship(context, token, producer, inbounds) { await expected(context, token, 'POST', `一件代发整托${index + 1}同键异载荷冲突`, '/api/pda/v1/last-leg/receiving-containers/current/receipts/pallet', { ...body, sourcePalletNo: `${sourcePalletNo}-MUTATED` }, conflict) - await expected(context, token, 'POST', `一件代发来源托盘重复收货被拒绝${index + 1}`, + const duplicate = await pda(context, token, 'POST', `一件代发来源托盘新键重复收货${index + 1}`, '/api/pda/v1/last-leg/receiving-containers/current/receipts/pallet', - { ...body, idempotencyKey: key(context, `dropship-pallet-duplicate-${index}`) }, conflict) + { ...body, idempotencyKey: key(context, `dropship-pallet-duplicate-${index}`) }) + context.assert(MODULE, `一件代发来源托盘重复收货不重复入账${index + 1}`, duplicate.response, + success(duplicate.response) && duplicate.data?.result === 'ALREADY_RECEIVED' + && Number(duplicate.data?.acceptedPackages) === 0, trace(duplicate.response), OPTIONS) } context.assert(MODULE, '一件代发整托实收箱数与待收箱数守恒', null, accepted === expectedPending, `accepted=${accepted}, expected=${expectedPending}`, OPTIONS) @@ -728,8 +740,15 @@ async function runReceiving(context, token, secondaryToken, merchantToken, prefe cartonMark: fbaCarton?.cartonMark, palletCode: pallets.get(firstFba.inboundCode).palletCode, idempotencyKey: key(context, 'fba-carton-forbidden'), }, (response) => a0409Message(response, /^FBA destination must use bulk count$/)) - for (const [index, inbound] of fbaInbounds.entries()) { - await receiveFba(context, token, inbound, pallets.get(inbound.inboundCode), cartonFixtures, index) + const groups = new Map() + for (const inbound of fbaInbounds) { + const fixture = cartonFixtures.find((item) => item.inbound.inboundCode === inbound.inboundCode) + const groupKey = fixture?.resolved?.groupKey || `INBOUND:${inbound.inboundCode}` + if (!groups.has(groupKey)) groups.set(groupKey, []) + groups.get(groupKey).push(inbound) + } + for (const [index, group] of [...groups.values()].entries()) { + await receiveFba(context, token, group, pallets.get(group[0].inboundCode), cartonFixtures, index) } } else if (!strictDropship && preferredProducer?.destinationType !== 'OVERSEAS') { blockerGap(context, 'FBA按箱数链', '当前FBA柜没有带真实托盘的 FBA 入仓码') diff --git a/scripts/admin-test-env-modules/phase4-overseas-wallet-and-pallet-cost-contract.test.mjs b/scripts/admin-test-env-modules/phase4-overseas-wallet-and-pallet-cost-contract.test.mjs index c88834fbb..052040aa6 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-wallet-and-pallet-cost-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-wallet-and-pallet-cost-contract.test.mjs @@ -18,6 +18,7 @@ test('采购单号按北京时间日期执行精确合同', () => { test('钱包闭环覆盖充值、授信、采购冻结结算和换标结算释放', () => { for (const token of [ '/accounts/${encodeURIComponent(customerId)}/credit-limit', + 'creditExpiresOn', '/accounts/${encodeURIComponent(customerId)}/adjustments', "const CLIENT_FINANCE = '/api/client/finance/v1'", "['VOUCHER', 'RECEIPT']", diff --git a/scripts/admin-test-env-modules/phase4-overseas-wallet-and-pallet-cost.mjs b/scripts/admin-test-env-modules/phase4-overseas-wallet-and-pallet-cost.mjs index ab094501e..f8dc1e461 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-wallet-and-pallet-cost.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-wallet-and-pallet-cost.mjs @@ -67,8 +67,10 @@ async function prepareWallet(context, customerId) { if (!before.account) return null const creditLimit = 2.00 + const creditExpiresOn = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10) const credit = await context.request('PUT', `${FINANCE}/accounts/${encodeURIComponent(customerId)}/credit-limit`, - { creditLimit, remark: context.marker, expectedAccountVersion: Number(before.account.accountVersion) }, context.token, + { creditLimit, creditExpiresOn, remark: context.marker, + expectedAccountVersion: Number(before.account.accountVersion) }, context.token, { 'Idempotency-Key': `${context.marker}-wallet-credit-limit`.slice(0, 128) }) assert(context, '授信额度可通过正式接口初始化', credit, success(credit) && money(credit.body.data?.creditLimitAfter) === creditLimit,