fix(acceptance): enforce immutable confirmed fees

This commit is contained in:
2026-09-10 21:18:52 +08:00
parent 7b35373da3
commit 800ebb875f
14 changed files with 116 additions and 53 deletions
@@ -486,7 +486,7 @@ async function loadOneFragment(context, state) {
export async function run(context) {
const fixture = context.createdResources?.phase3Loading || context.createdResources?.phase3ContainerLoading
const tenantId = id(fixture?.tenantId)
const inboundId = id(fixture?.inboundId)
const inboundId = id(fixture?.incrementalInboundId || fixture?.inboundId)
const warehouseId = id(fixture?.warehouseId)
const containerBookingId = id(fixture?.containerBookingId)
if (!tenantId || !inboundId || !warehouseId || !containerBookingId || fixture?.marker !== context.marker) {
@@ -513,8 +513,20 @@ export async function run(context) {
const firstTreeResponse = await context.request('GET', treePath(orderId, tenantId))
const firstTree = context.apiSuccess(MODULE, '查询首次拆分后的根履约树', firstTreeResponse,
`orderId=${orderId}, tenantId=${tenantId}`, OPTIONS)
const originalPackageCount = Number(firstTree?.originalPackageCount)
const initialLoadedPackageCount = Number(firstTree?.cumulativeLoadedPackageCount)
const initialRemainingPackageCount = Number(firstTree?.currentRemainderPackageCount)
const initialActive = activeRemainders(firstTree)
const initialActiveSequence = Number(initialActive[0]?.splitSequence)
context.assert(MODULE, '新批次由来源单承接已装且只创建一个余量子单', firstTreeResponse,
treeShape(firstTree, orderId) && snapshotMatches(firstTree, 4, 1, 3, 1)
treeShape(firstTree, orderId)
&& Number.isInteger(originalPackageCount) && originalPackageCount > 1
&& Number.isInteger(initialLoadedPackageCount) && initialLoadedPackageCount > 0
&& Number.isInteger(initialRemainingPackageCount) && initialRemainingPackageCount > 0
&& originalPackageCount === initialLoadedPackageCount + initialRemainingPackageCount
&& initialActive.length === 1 && Number.isInteger(initialActiveSequence)
&& snapshotMatches(firstTree, originalPackageCount, initialLoadedPackageCount,
initialRemainingPackageCount, initialActiveSequence)
&& firstTree.children.filter((row) => row.splitRole === 'LOADED').length === 1
&& firstTree.children.filter((row) => row.splitRole === 'REMAINDER').length === 1,
`original=${firstTree?.originalPackageCount ?? 'missing'}, loaded=${firstTree?.cumulativeLoadedPackageCount ?? 'missing'}, remaining=${firstTree?.currentRemainderPackageCount ?? 'missing'}`, OPTIONS)
@@ -536,7 +548,9 @@ export async function run(context) {
`/api/merchant/v1/orders/${encodeURIComponent(orderId)}/fulfillment-tree`, undefined, merchantToken)
const merchantTree = context.apiSuccess(MODULE, '客户查询本客户根履约树', merchantTreeResponse, '', OPTIONS)
context.assert(MODULE, '客户履约树与平台数量聚合一致', merchantTreeResponse,
treeShape(merchantTree, orderId) && snapshotMatches(merchantTree, 4, 1, 3, 1),
treeShape(merchantTree, orderId)
&& snapshotMatches(merchantTree, originalPackageCount, initialLoadedPackageCount,
initialRemainingPackageCount, initialActiveSequence),
`children=${merchantTree?.children?.length ?? 'missing'}`, OPTIONS)
const forbidden = await request(context, 'GET', treePath(orderId, tenantId), undefined, {}, merchantToken)
@@ -571,41 +585,54 @@ export async function run(context) {
cleanupNote: '公开 API 不允许删除订单/柜次/PDA审计记录;只有全部分片完成且无活动余量时标记业务清理完成。',
}
let tree = firstTree
for (const step of [
{ sourcePackageCount: 3, expectedLoaded: 2, expectedRemaining: 2, sourceSuffix: 1, activeSuffix: 2 },
{ sourcePackageCount: 2, expectedLoaded: 3, expectedRemaining: 1, sourceSuffix: 2, activeSuffix: 3 },
{ sourcePackageCount: 1, expectedLoaded: 4, expectedRemaining: 0, sourceSuffix: 3, activeSuffix: null },
]) {
let completedFragments = 0
while (activeRemainders(tree).length > 0) {
const active = activeRemainders(tree)
const source = active[0]
context.assert(MODULE, `链式拆分来源固定为根编号 -${step.sourceSuffix}`, null,
active.length === 1 && source?.orderNo === `${tree.parentOrderNo}-${step.sourceSuffix}`,
const sourceSuffix = Number(source?.splitSequence)
const sourcePackageCount = Number(source?.packageCount)
const beforeLoaded = Number(tree.cumulativeLoadedPackageCount)
const beforeRemaining = Number(tree.currentRemainderPackageCount)
context.assert(MODULE, `链式拆分来源固定为根编号 -${sourceSuffix}`, null,
active.length === 1 && Number.isInteger(sourceSuffix) && sourceSuffix > 0
&& source?.orderNo === `${tree.parentOrderNo}-${sourceSuffix}`
&& Number.isInteger(sourcePackageCount) && sourcePackageCount === beforeRemaining,
`active=${active.map((row) => row.orderNo).join(',') || 'none'}`, OPTIONS)
if (!source) return
const result = await loadOneFragment(context, {
tenantId, rootOrderId: orderId, sourceOrderId: id(source.orderId), sourceOrderNo: source.orderNo,
customerName, channelCode, channelName, warehouseId, warehouseName,
sourcePackageCount: step.sourcePackageCount, sequence: step.sourceSuffix,
sourcePackageCount, sequence: sourceSuffix,
})
if (!result?.tree) return
tree = result.tree
completedFragments += 1
const expectedLoaded = beforeLoaded + 1
const expectedRemaining = beforeRemaining - 1
const activeSuffix = expectedRemaining > 0 ? sourceSuffix + 1 : null
Object.assign(context.createdResources.phase3OrderSplitting, {
activeRemainderCount: activeRemainders(tree).length,
finalLoadedPackageCount: Number(tree.cumulativeLoadedPackageCount),
finalRemainingPackageCount: Number(tree.currentRemainderPackageCount),
})
context.assert(MODULE, `完成第 ${step.sourceSuffix + 1} 个分片后根数量守恒`, result.treeResponse,
context.assert(MODULE, `完成余量分片 -${sourceSuffix} 后根数量守恒`, result.treeResponse,
treeShape(tree, orderId)
&& snapshotMatches(tree, 4, step.expectedLoaded, step.expectedRemaining, step.activeSuffix),
&& snapshotMatches(tree, originalPackageCount, expectedLoaded, expectedRemaining, activeSuffix),
`original=${tree.originalPackageCount}, loaded=${tree.cumulativeLoadedPackageCount}, remaining=${tree.currentRemainderPackageCount}`, OPTIONS)
if (completedFragments > originalPackageCount) {
context.addStep(MODULE, '余量链在原始箱数边界内收敛', null, false,
`original=${originalPackageCount}, fragments=${completedFragments}`, OPTIONS)
return
}
}
const remainderCodes = tree.children.filter((row) => row.splitRole === 'REMAINDER').map((row) => row.orderNo)
const expectedRemainderCodes = Array.from({ length: completedFragments }, (_, index) =>
`${tree.parentOrderNo}-${initialActiveSequence + index}`)
context.assert(MODULE, '余量子单始终按根编号全局递增且禁止嵌套编号', null,
JSON.stringify(remainderCodes) === JSON.stringify([
`${tree.parentOrderNo}-1`, `${tree.parentOrderNo}-2`, `${tree.parentOrderNo}-3`,
]) && remainderCodes.every((code) => !/-[0-9]+-[0-9]+$/.test(code)),
JSON.stringify(remainderCodes) === JSON.stringify(expectedRemainderCodes)
&& remainderCodes.every((code) => !/-[0-9]+-[0-9]+$/.test(code)),
`codes=${remainderCodes.join(',')}`, OPTIONS)
context.assert(MODULE, '最后一次全量装完不再生成子单且根整体完成', null,
activeRemainders(tree).length === 0 && Number(tree.currentRemainderPackageCount) === 0