test: align overseas PDA group receipt semantics

This commit is contained in:
2026-09-11 00:24:35 +08:00
parent 2bd12f8a5f
commit 9abb0004dd
4 changed files with 44 additions and 20 deletions
@@ -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, /模式硬拒绝/)
@@ -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 入仓码')
@@ -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']",
@@ -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,