完善财务全量验收链路

This commit is contained in:
2026-08-12 13:24:17 +08:00
parent 505e834536
commit d2704f68a6
11 changed files with 295 additions and 44 deletions
@@ -85,6 +85,13 @@ function operationsSignature(operations) {
})))
}
function taskItemsSignature(task) {
return JSON.stringify((task?.items || []).map((item) => ({
id: id(item?.id), status: item?.itemStatus || null,
picked: Number(item?.pickedPackageCount), version: Number(item?.version),
})))
}
export async function run(context) {
const source = fixture(context)
const tenantId = id(source?.tenantId)
@@ -300,16 +307,12 @@ export async function run(context) {
const afterInvalidOperationsResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}/operations`)
const afterInvalidOperations = context.apiSuccess(MODULE, '非法混合批次后回查 PDA 操作历史',
afterInvalidOperationsResponse, '', OPTIONS)
const itemSignature = (task) => JSON.stringify((task?.items || []).map((item) => ({
id: id(item?.id), status: item?.itemStatus || null, picked: Number(item?.pickedPackageCount),
version: Number(item?.version),
})))
context.assert(MODULE, '合法明细加非法明细失败后任务、全部明细和操作历史零写入', unchangedAfterInvalidResponse,
unchangedAfterInvalid?.taskStatus === beforeInvalidTask?.taskStatus
&& Number(unchangedAfterInvalid?.version) === Number(beforeInvalidTask?.version)
&& itemSignature(unchangedAfterInvalid) === itemSignature(beforeInvalidTask)
&& taskItemsSignature(unchangedAfterInvalid) === taskItemsSignature(beforeInvalidTask)
&& operationsSignature(afterInvalidOperations) === operationsSignature(beforeInvalidOperations),
`taskVersion=${unchangedAfterInvalid?.version ?? 'missing'}, items=${itemSignature(unchangedAfterInvalid)}, operations=${afterInvalidOperations?.length ?? 'missing'}`, OPTIONS)
`taskVersion=${unchangedAfterInvalid?.version ?? 'missing'}, items=${taskItemsSignature(unchangedAfterInvalid)}, operations=${afterInvalidOperations?.length ?? 'missing'}`, OPTIONS)
const merchantLogin = await context.request('POST', '/api/auth/login', {
username: MERCHANT_USERNAME, password: MERCHANT_PASSWORD,
@@ -414,15 +417,11 @@ export async function run(context) {
const afterStaleOperationsResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}/operations`)
const afterStaleOperations = context.apiSuccess(MODULE, '旧库存快照冲突后回查 PDA 操作历史',
afterStaleOperationsResponse, '', OPTIONS)
const beforeItem = beforeMutationTask?.items?.find?.((item) => id(item.id) === id(canonicalItem.id))
const afterItem = afterStaleTask?.items?.find?.((item) => id(item.id) === id(canonicalItem.id))
context.assert(MODULE, '旧 token 冲突对任务、明细、已拣数量和操作历史零写入', afterStaleTaskResponse,
Number(afterStaleTask?.version) === Number(beforeMutationTask?.version)
&& afterItem?.itemStatus === beforeItem?.itemStatus
&& Number(afterItem?.pickedPackageCount) === Number(beforeItem?.pickedPackageCount)
&& Number(afterItem?.version) === Number(beforeItem?.version)
&& taskItemsSignature(afterStaleTask) === taskItemsSignature(beforeMutationTask)
&& operationsSignature(afterStaleOperations) === operationsSignature(beforeMutationOperations),
`taskVersion=${afterStaleTask?.version ?? 'missing'}, itemStatus=${afterItem?.itemStatus || 'missing'}, picked=${afterItem?.pickedPackageCount ?? 'missing'}, operations=${afterStaleOperations?.length ?? 'missing'}`, OPTIONS)
`taskVersion=${afterStaleTask?.version ?? 'missing'}, items=${taskItemsSignature(afterStaleTask)}, operations=${afterStaleOperations?.length ?? 'missing'}`, OPTIONS)
const refreshedInventoryResponse = await context.request('GET',
`${pdaBase}/tasks/${canonicalTaskId}/inventory-snapshot?warehouseId=${encodeURIComponent(warehouseId)}`)
@@ -464,6 +463,17 @@ export async function run(context) {
atomicBatchReplayVerified,
`HTTP=${canonicalBatchReplay.http}, code=${codeOf(canonicalBatchReplay)}, taskVersion=${canonicalBatchReplay.body?.data?.version ?? 'missing'}`, OPTIONS)
const completionOperationsResponse = await context.request('GET', `${pdaBase}/tasks/${canonicalTaskId}/operations`)
const completionOperations = context.apiSuccess(MODULE, '回查原子 Batch 完成审计', completionOperationsResponse, '', OPTIONS)
context.assert(MODULE, '合法 Batch 完成后保留 CLAIM/BATCH 操作者审计且无伪造 REVIEW', completionOperationsResponse,
Array.isArray(completionOperations)
&& completionOperations.some((item) => item.actionType === 'CLAIM'
&& id(item.operatorId) && Boolean(item.createdAt))
&& completionOperations.some((item) => item.actionType === 'BATCH'
&& id(item.operatorId) && Boolean(item.createdAt))
&& !completionOperations.some((item) => item.actionType === 'REVIEW'),
`actions=${Array.isArray(completionOperations) ? completionOperations.map((item) => item.actionType).join(',') : 'missing'}`, OPTIONS)
const pickedPlanResponse = await context.request('GET', `${smartLoadingBase}/plans/${loadingPlanId}`)
const pickedPlan = context.apiSuccess(MODULE, '首笔真实拣货后回查柜次计划', pickedPlanResponse, '', OPTIONS)
context.assert(MODULE, '首笔真实拣货触发装柜状态并由单明细批次直接完成', pickedPlanResponse,