This commit is contained in:
2026-08-17 16:17:20 +08:00
parent 75580c3585
commit 592da4d7e2
9 changed files with 97 additions and 8 deletions
@@ -3,6 +3,11 @@ const OPTIONS = { scenario: 'pda-picking-convergence', deploymentBlocker: true }
function id(value) { return value == null ? '' : String(value) }
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
function failure(response) { return [400, 401, 403, 404, 409, 503].includes(response?.http) || (response?.http === 200 && codeOf(response) !== '200') }
export function buildPendingActions(task) {
if (task?.workflowStatus === 'AWAITING_REVIEW' || task?.workflowStatus === 'COMPLETED') return []
return (task?.items || []).filter((item) => ['PENDING', 'PICKING'].includes(item?.itemStatus))
.map((item) => ({ itemId: id(item?.id), action: 'PICK' }))
}
async function request(context, method, path, body, headers = {}) {
try {
const response = await fetch(`${context.options.gateway}${path}`, { method,
@@ -40,8 +45,36 @@ export async function run(context) {
['AWAITING_REVIEW', 'COMPLETED'].includes(projection?.status) && Number(projection?.expectedCount) > 0
&& Number(projection?.expectedCount) === Number(projection?.pickedCount),
`status=${projection?.status || 'missing'}, expected=${projection?.expectedCount ?? 'missing'}, picked=${projection?.pickedCount ?? 'missing'}`, OPTIONS)
const taskPath = `/api/platform/v1/first-leg/pda-loading/tasks/${taskId}`
const taskDetailResponse = await request(context, 'GET',
`${taskPath}?warehouseId=${encodeURIComponent(warehouseId)}`)
const taskDetail = context.apiSuccess(MODULE, '按稳定 taskId 读取 PDA 任务详情', taskDetailResponse, '', OPTIONS)
const pendingResponse = await request(context, 'GET',
`/api/platform/v1/first-leg/pda-loading/tasks/pending?keyword=${encodeURIComponent(containerNo)}`)
const pendingTasks = context.apiSuccess(MODULE, '读取当前操作员 PDA pending 列表', pendingResponse, '', OPTIONS)
const pendingTask = Array.isArray(pendingTasks)
? pendingTasks.find((item) => id(item?.taskId) === taskId)
: null
const unresolvedItems = (taskDetail?.items || []).filter((item) =>
['PENDING', 'PICKING'].includes(item?.itemStatus))
context.assert(MODULE, 'pending 与 detail 使用同一 workflowStatus 和未完成数量事实', pendingResponse,
Boolean(pendingTask) && pendingTask?.workflowStatus === taskDetail?.workflowStatus
&& Number(pendingTask?.pendingItemCount) === unresolvedItems.length
&& Number(pendingTask?.itemCount) === Number(taskDetail?.items?.length),
`taskId=${taskId}, pendingWorkflow=${pendingTask?.workflowStatus || 'missing'}, detailWorkflow=${taskDetail?.workflowStatus || 'missing'}, pendingItemCount=${pendingTask?.pendingItemCount ?? 'missing'}, detailUnresolved=${unresolvedItems.length}`, OPTIONS)
const pendingActions = buildPendingActions(taskDetail)
context.assert(MODULE, 'AWAITING_REVIEW 已全拣任务 actions 为空且不再提交 batch', taskDetailResponse,
taskDetail?.workflowStatus === 'AWAITING_REVIEW' && unresolvedItems.length === 0
&& pendingActions.length === 0,
`workflow=${taskDetail?.workflowStatus || 'missing'}, unresolved=${unresolvedItems.length}, actions=${pendingActions.length}`, OPTIONS)
const evidenceResponse = await request(context, 'GET',
`/api/platform/v1/inbounds/${inboundId}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`)
const evidence = context.apiSuccess(MODULE, 'AWAITING_REVIEW 可进入装柜取证只读入口', evidenceResponse, '', OPTIONS)
context.assert(MODULE, 'AWAITING_REVIEW 取证 GET 开放且不依赖重复 batch', evidenceResponse,
taskDetail?.workflowStatus === 'AWAITING_REVIEW' && Array.isArray(evidence?.evidences),
`workflow=${taskDetail?.workflowStatus || 'missing'}, evidences=${evidence?.evidences?.length ?? 'missing'}, actions=${pendingActions.length}`, OPTIONS)
const operationsResponse = await request(context, 'GET',
`/api/platform/v1/first-leg/pda-loading/tasks/${taskId}/operations`)
`${taskPath}/operations`)
const operations = context.apiSuccess(MODULE, '查询柜次 PDA 不可变操作历史', operationsResponse, '', OPTIONS)
context.assert(MODULE, '最终完成前保留领取操作且不伪造复核或完成历史', operationsResponse,
Array.isArray(operations) && operations.some((item) => item.actionType === 'CLAIM'
@@ -51,8 +84,8 @@ export async function run(context) {
`actions=${Array.isArray(operations) ? operations.map((item) => item.actionType).join(',') : 'missing'}`, OPTIONS)
context.assert(MODULE, 'PDA 原子批次完成审计包含 BATCH 操作', operationsResponse,
Array.isArray(operations) && operations.some((item) => item.actionType === 'BATCH'
&& Boolean(item.operatorId) && Boolean(item.createdAt)),
Array.isArray(operations) && operations.filter((item) => item.actionType === 'BATCH'
&& Boolean(item.operatorId) && Boolean(item.createdAt)).length === 1,
`actions=${Array.isArray(operations) ? operations.map((item) => item.actionType).join(',') : 'missing'}`, OPTIONS)
const forged = await request(context, 'POST',
`/api/platform/v1/inbounds/${inboundId}/loading/picking/start?tenantId=${encodeURIComponent(tenantId)}`,