This commit is contained in:
2026-09-30 15:16:39 +08:00
parent 0919a61661
commit 22584e7fde
15 changed files with 994 additions and 45 deletions
+69
View File
@@ -86,6 +86,72 @@ export function approvedInboundReady(orderDetail, taskResponse, tenantId, orderI
&& id(task?.orderId) === id(orderId)
&& id(task?.tenantId) === id(tenantId)
}
export function approvalReplayDigest(snapshot) {
return createHash('sha256').update(JSON.stringify(snapshot)).digest('hex')
}
export async function verifyOrderApprovalReplay(context, { tenantId, orderId, inboundId, inboundCode }) {
const scope = `tenantId=${encodeURIComponent(tenantId)}`
const orderPath = `/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}?${scope}`
const taskPath = `/api/platform/v1/first-leg/tasks/by-order/${encodeURIComponent(orderId)}?${scope}`
const inboundPath = `/api/platform/inbounds/${encodeURIComponent(inboundId)}?${scope}`
async function read(name) {
const orderResponse = await context.request('GET', orderPath)
const detail = context.apiSuccess(MODULE, `${name}订单及状态历史`, orderResponse, '', CONTRACT_OPTIONS)
const taskResponse = await context.request('GET', taskPath)
const task = context.apiSuccess(MODULE, `${name}原履约任务`, taskResponse, '', CONTRACT_OPTIONS)
const inboundResponse = await context.request('GET', inboundPath)
const inbound = context.apiSuccess(MODULE, `${name}原入库单及历史`, inboundResponse, '', CONTRACT_OPTIONS)
const rows = []; let total = null; let page = 1
while (total == null || rows.length < total) {
const response = await context.request('GET', `/api/platform/inbounds?${scope}`
+ `&inboundCode=${encodeURIComponent(inboundCode)}&page=${page}&size=100`)
const data = context.apiSuccess(MODULE, `${name}入仓码来源入库列表第${page}页`, response, '', CONTRACT_OPTIONS)
const valid = pageOk(data) && Number.isInteger(Number(data.total)) && Number(data.total) >= 0
&& (total == null || total === Number(data.total))
&& (data.records.length > 0 || Number(data.total) === 0)
context.assert(MODULE, `${name}入库分页计数完整`, response, valid,
`page=${page}, total=${data?.total ?? 'missing'}`, CONTRACT_OPTIONS)
if (!valid) throw new Error('审核重放核验缺少完整入库分页事实')
total = Number(data.total); rows.push(...data.records); page += 1
}
const exact = rows.filter(row => id(row.inboundCode) === inboundCode)
const valid = detail?.order?.status === 'APPROVED' && id(detail.order.id) === orderId
&& id(detail.order.inboundCode) === inboundCode && Array.isArray(detail.statusHistory)
&& id(task?.orderId) === orderId && id(task?.tenantId) === tenantId && Boolean(id(task?.id))
&& id(inbound?.summary?.id) === inboundId && id(inbound?.summary?.tenantId) === tenantId
&& id(inbound?.summary?.inboundCode) === inboundCode && Array.isArray(inbound?.statusHistory)
&& rows.length === total && new Set(rows.map(row => id(row.id))).size === total
&& exact.length === 1 && id(exact[0].id) === inboundId
context.assert(MODULE, `${name}审核态原身份与唯一入库事实有效`, inboundResponse, valid,
`exactInbounds=${exact.length}, taskId=${id(task?.id)}, inboundId=${id(inbound?.summary?.id)}`, CONTRACT_OPTIONS)
if (!valid) throw new Error('审核重放核验的订单任务或唯一入库身份不一致')
return { response: inboundResponse, facts: {
order: JSON.parse(orderPersistenceFacts(detail)), task, inbound, exactInbounds: exact,
} }
}
const before = await read('审核重放前')
const response = await context.request('POST',
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/approve?${scope}`)
const rejected = response?.http === 200 && String(response.body?.code) === 'A0409'
context.assert(MODULE, '已审核订单再次审核明确A0409', response, rejected,
`HTTP=${response?.http}, code=${response?.body?.code ?? 'missing'}`, CONTRACT_OPTIONS)
const after = await read('审核重放后')
const beforeDigest = approvalReplayDigest(before.facts)
const afterDigest = approvalReplayDigest(after.facts)
const unchanged = beforeDigest === afterDigest
context.assert(MODULE, '审核重复拒绝后订单入仓码历史任务及唯一入库事实零漂移', after.response,
unchanged, `inboundCount=1, orderHistories=${after.facts.order.statusHistory.length}`
+ `, inboundHistories=${after.facts.inbound.statusHistory.length}`, CONTRACT_OPTIONS)
const evidence = { marker: context.marker, tenantId, orderId, inboundId, inboundCode,
taskId: id(after.facts.task.id), inboundCount: after.facts.exactInbounds.length,
orderHistoryCount: after.facts.order.statusHistory.length,
inboundHistoryCount: after.facts.inbound.statusHistory.length,
beforeDigest, afterDigest, rejected, unchanged }
context.createdResources.orders.approvalReplay = evidence
return evidence
}
function tomorrow(days = 30) {
const date = new Date(); date.setUTCDate(date.getUTCDate() + days)
return date.toISOString().slice(0, 10)
@@ -742,6 +808,9 @@ export async function run(context) {
throw new Error('订单审核后的入库单与履约任务未在30秒内收敛,无法修改申报并向 OCR/墨家模块交接')
}
await verifyOrderApprovalReplay(context, { tenantId, orderId, inboundId,
inboundCode: id(inboundResult.detail?.summary?.inboundCode) })
const editableDetailResponse = await context.request('GET', firstLegDetailPath)
const editableDetail = context.apiSuccess(MODULE, 'Admin 读取审核后客户申报资料', editableDetailResponse)
const declarationDetail = editableDetail?.order?.order ? editableDetail.order : editableDetail