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
@@ -299,6 +299,102 @@ export function dispatchStockConservation(context, { customerId, warehouseId, di
return { passed, proof, newLedger }
}
// Completion follows the formal root and every real child; a signed shipment alone is not the aggregate.
export async function verifyFulfillmentRootStatus(context, { sourceOrderId, customerId, merchantToken,
pollAttempts = 45, pollIntervalMs = 1_000 }) {
const rawPath = orderId => query(`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}`, { tenantId: customerId })
const source = await platform(context, 'GET', '读取签收来源原始订单履约归属', rawPath(sourceOrderId))
const sourceOrder = source.data?.order
const rootOrderId = sourceOrder?.orderRole === 'MASTER' ? id(sourceOrder.id) : id(sourceOrder?.rootOrderId)
const sourceOwned = success(source.response) && id(sourceOrder?.id) === sourceOrderId
&& id(sourceOrder?.tenantId) === customerId && id(sourceOrder?.customerId) === customerId
&& /^[1-9][0-9]*$/.test(rootOrderId)
&& (sourceOrder.orderRole === 'MASTER' || (sourceOrder.orderRole === 'CHILD' && id(sourceOrder.parentOrderId) === rootOrderId))
context.assert(MODULE, '签收来源订单属于本企业及唯一正式履约根', source.response, sourceOwned, `sourceOrderId=${sourceOrderId}, rootOrderId=${rootOrderId}`, OPTIONS)
if (!sourceOwned) return null
let root = rootOrderId === sourceOrderId ? source : await platform(context, 'GET', '读取原始履约根订单', rawPath(rootOrderId))
const treeResult = await platform(context, 'GET', '读取签收后的完整正式履约树',
query(`/api/platform/v1/orders/${encodeURIComponent(rootOrderId)}/fulfillment-tree`, { tenantId: customerId }))
const tree = treeResult.data
const rows = Array.isArray(tree?.children) ? tree.children : []
const treeOrderIds = new Set([rootOrderId, ...rows.map(row => id(row.orderId))])
const consumed = new Set(rows.filter(row => row.splitRole === 'LOADED').map(row => id(row.sourceOrderId)))
const loaded = rows.filter(row => row.splitRole === 'LOADED').reduce((sum, row) => sum + Number(row.packageCount), 0)
const remaining = rows.filter(row => row.splitRole === 'REMAINDER' && !consumed.has(id(row.orderId)))
.reduce((sum, row) => sum + Number(row.packageCount), 0)
const treeComplete = success(root.response) && success(treeResult.response)
&& id(root.data?.order?.id) === rootOrderId && root.data.order.orderRole === 'MASTER'
&& id(root.data.order.tenantId) === customerId && id(root.data.order.customerId) === customerId
&& Array.isArray(root.data.statusHistory)
&& id(tree?.parentOrderId) === rootOrderId && tree.parentOrderNo === root.data.order.orderNo
&& Array.isArray(tree.children)
&& ['originalPackageCount', 'cumulativeLoadedPackageCount', 'currentRemainderPackageCount'].every(field =>
tree[field] !== null && tree[field] !== undefined && Number.isSafeInteger(Number(tree[field])) && Number(tree[field]) >= 0)
&& loaded === Number(tree.cumulativeLoadedPackageCount) && remaining === Number(tree.currentRemainderPackageCount)
&& loaded + remaining === Number(tree.originalPackageCount)
&& rows.every(row => /^[1-9][0-9]*$/.test(id(row.orderId)) && /^[1-9][0-9]*$/.test(id(row.sourceOrderId))
&& treeOrderIds.has(id(row.sourceOrderId))
&& nonBlank(row.orderNo) && ['LOADED', 'REMAINDER'].includes(row.splitRole)
&& (row.splitRole === 'LOADED' ? id(row.orderId) === id(row.sourceOrderId) : id(row.orderId) !== id(row.sourceOrderId))
&& Number.isSafeInteger(Number(row.packageCount)) && Number(row.packageCount) > 0)
&& (sourceOrderId === rootOrderId || rows.some(row => id(row.orderId) === sourceOrderId))
context.assert(MODULE, '履约根及全部分片身份和树数量完整守恒', treeResult.response, treeComplete,
`rootOrderId=${rootOrderId}, loaded=${loaded}, remaining=${remaining}, original=${tree?.originalPackageCount ?? 'missing'}`, OPTIONS)
if (!treeComplete) return null
const children = []
const statuses = ['DRAFT', 'SUBMITTED', 'APPROVED', 'ACCEPTED', 'REJECTED', 'CANCELLED', 'PROCESSING', 'COMPLETED']
for (const childId of [...new Set(rows.map(row => id(row.orderId)).filter(value => value !== rootOrderId))]) {
const current = await platform(context, 'GET', '逐项读取真实履约子单完成事实', rawPath(childId))
const child = current.data?.order
const owned = success(current.response) && id(child?.id) === childId && id(child?.tenantId) === customerId
&& id(child?.customerId) === customerId && child?.orderRole === 'CHILD'
&& id(child?.rootOrderId) === rootOrderId && id(child?.parentOrderId) === rootOrderId
&& rows.filter(row => id(row.orderId) === childId).every(row => row.orderNo === child.orderNo)
&& statuses.includes(child?.status)
context.assert(MODULE, '真实履约子单存在且客户根身份和状态有效', current.response, owned, `childOrderId=${childId}`, OPTIONS)
if (!owned) return null
children.push({ orderId: childId, orderNo: child.orderNo, status: child.status, rootOrderId })
}
const pendingChildren = children.filter(child => !['COMPLETED', 'CANCELLED'].includes(child.status))
const rootDelivered = root.data.statusHistory?.some(row => row.action === 'OVERSEAS_DELIVERED') === true
const expectedCompleted = rootDelivered && pendingChildren.length === 0
const owned = context.createdResources.phase5OverseasFulfillment
if (owned) owned.orderCompletionProof = { sourceOrderId, rootOrderId,
tree: { parentOrderId: rootOrderId, parentOrderNo: tree.parentOrderNo, originalPackageCount: Number(tree.originalPackageCount),
cumulativeLoadedPackageCount: loaded, currentRemainderPackageCount: remaining,
children: rows.map(row => ({ orderId: id(row.orderId), orderNo: row.orderNo, sourceOrderId: id(row.sourceOrderId),
splitRole: row.splitRole, packageCount: Number(row.packageCount) })) },
children, rootDelivered, expectedCompleted, pendingChildOrderIds: pendingChildren.map(child => child.orderId) }
const domestic = await poll(() => context.request('GET',
query(`/api/platform/v1/orders/${encodeURIComponent(rootOrderId)}`, { tenantId: customerId })),
response => success(response) && id(response.body?.data?.summary?.orderId) === rootOrderId
&& (expectedCompleted ? response.body.data.summary.mainStatus === 'COMPLETED'
: rootDelivered ? response.body.data.summary.mainStatus === 'DELIVERED'
: nonBlank(response.body.data.summary.mainStatus) && response.body.data.summary.mainStatus !== 'COMPLETED'), pollAttempts, pollIntervalMs)
if (expectedCompleted) root = await platform(context, 'GET', '全部有效分片结束后回查原始履约根', rawPath(rootOrderId))
const rawPassed = success(root.response) && (expectedCompleted
? root.data?.order?.status === 'COMPLETED' && root.data.statusHistory?.some(row => row.action === 'FULFILLMENT_COMPLETED')
: statuses.includes(root.data?.order?.status) && root.data.order.status !== 'COMPLETED'
&& !root.data.statusHistory?.some(row => row.action === 'FULFILLMENT_COMPLETED'))
const platformPassed = success(domestic.result) && id(domestic.result?.body?.data?.summary?.orderId) === rootOrderId
&& (expectedCompleted ? domestic.result.body.data.summary.mainStatus === 'COMPLETED'
: rootDelivered ? domestic.result.body.data.summary.mainStatus === 'DELIVERED'
: nonBlank(domestic.result.body.data.summary.mainStatus) && domestic.result.body.data.summary.mainStatus !== 'COMPLETED')
context.assert(MODULE, expectedCompleted ? '全部有效子单签收后原始履约根COMPLETED' : '有效余量或根票未签收时原始履约根不得提前COMPLETED',
root.response, rawPassed, `rootOrderId=${rootOrderId}, pendingChildren=${pendingChildren.map(row => row.orderNo).join(',')}`, OPTIONS)
context.addStep(MODULE, expectedCompleted ? '同订单全部有效履约分片签收后统一订单进入COMPLETED' : '本票签收但有效余量未完时统一订单保留未完成状态',
domestic.result, platformPassed, `rootOrderId=${rootOrderId}, pollRequests=${domestic.attempts}, mainStatus=${domestic.result?.body?.data?.summary?.mainStatus || 'missing'}`, OPTIONS)
const customer = await merchant(context, merchantToken, 'GET', '客户读取本企业履约根真实完成状态', `/api/merchant/v1/orders/${encodeURIComponent(rootOrderId)}`)
const customerPassed = success(customer.response) && id(customer.data?.summary?.orderId) === rootOrderId
&& customer.data?.summary?.mainStatus === domestic.result?.body?.data?.summary?.mainStatus && platformPassed
context.assert(MODULE, expectedCompleted ? '客户本企业全部履约完成后订单最终状态为COMPLETED' : '客户本企业仍有有效未完成履约时不得展示COMPLETED',
customer.response, customerPassed, `rootOrderId=${rootOrderId}, mainStatus=${customer.data?.summary?.mainStatus || 'missing'}`, OPTIONS)
if (owned) Object.assign(owned.orderCompletionProof, { rawRootStatus: root.data?.order?.status,
platformMainStatus: domestic.result?.body?.data?.summary?.mainStatus, customerMainStatus: customer.data?.summary?.mainStatus,
rawPassed: Boolean(rawPassed), platformPassed, customerPassed })
return { rootOrderId, expectedCompleted, pendingChildren, passed: Boolean(rawPassed && platformPassed && customerPassed) }
}
// A dedicated multi-fragment producer must supply these codes. Multiple FBA destinations alone are not proof.
export async function verifyPartialOrderCompletion(context, { orderId, customerId, deliveredInboundCode, pendingInboundCodes }) {
if (!id(orderId) || !id(customerId) || !nonBlank(deliveredInboundCode)
@@ -1012,19 +1108,8 @@ export async function run(context) {
blocker(context, '取得签收对应头程订单', '头程入仓详情缺少来源订单ID', domesticInbound.result)
return
}
const domesticOrder = await poll(async () => context.request('GET',
query(`/api/platform/v1/orders/${encodeURIComponent(sourceOrderId)}`, { tenantId: customerId })),
(response) => success(response) && response.body?.data?.summary?.mainStatus === 'COMPLETED')
context.addStep(MODULE, '同订单全部入仓签收后统一订单进入COMPLETED', domesticOrder.result,
success(domesticOrder.result) && domesticOrder.result?.body?.data?.summary?.mainStatus === 'COMPLETED',
`orderId=${sourceOrderId}, pollRequests=${domesticOrder.attempts}, mainStatus=${domesticOrder.result?.body?.data?.summary?.mainStatus || 'missing'}; ${trace(domesticOrder.result)}`, OPTIONS)
context.createdResources.phase5OverseasFulfillment.sourceOrderId = sourceOrderId
const customerCompletedOrder = await merchant(context, merchantToken, 'GET', '客户读取签收后的统一订单最终状态',
`/api/merchant/v1/orders/${encodeURIComponent(sourceOrderId)}`)
context.assert(MODULE, '客户本企业订单最终状态为COMPLETED', customerCompletedOrder.response,
customerCompletedOrder.data?.summary?.mainStatus === 'COMPLETED'
&& id(customerCompletedOrder.data?.summary?.orderId) === sourceOrderId,
trace(customerCompletedOrder.response), OPTIONS)
await verifyFulfillmentRootStatus(context, { sourceOrderId, customerId, merchantToken })
const foreignMerchantToken = context.context?.phase5TailVerification?.foreignMerchantToken
|| await createSignedDocumentIsolationCustomer(context)
await verifyCustomerSignedDocuments(context, merchantToken,