Files
baiyue-firstleg/scripts/admin-test-env-modules/manual-pda-batch-fixture.mjs
T

106 lines
6.9 KiB
JavaScript
Raw Normal View History

2026-08-26 16:50:22 +08:00
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
const MODULE = 'manual-pda-batch-fixture'
const OPTIONS = { scenario: 'manual-pda-batch-fixture', deploymentBlocker: true }
const PDA = '/api/platform/v1/first-leg/pda-loading'
function id(value) { return value == null ? '' : String(value) }
function rows(value) { return Array.isArray(value) ? value : [] }
async function loadSource(context) {
const report = JSON.parse(await readFile(resolve(context.options.manualPdaBatchSourceReport), 'utf8'))
const source = report?.createdResources?.manualPdaReadyVerification
const sourceGateway = String(report?.gateway || '').replace(/\/$/, '')
const valid = sourceGateway === context.options.gateway && source?.stage === 'PDA_UNASSIGNED'
&& source?.pdaTaskId && source?.loadingPlanId && source?.containerBookingId
context.assert(MODULE, '读取同环境 PDA 待领取核验报告', null, valid,
`sourceMarker=${report?.marker || 'missing'}, sameGateway=${sourceGateway === context.options.gateway}, pdaTaskId=${id(source?.pdaTaskId) || 'missing'}`,
OPTIONS)
return valid ? source : null
}
function taskItemSnapshot(items) {
return rows(items).map((item) => ({
id: id(item.id), loadingPlanItemId: id(item.loadingPlanItemId), tenantId: id(item.tenantId),
inboundId: id(item.inboundId), inboundDetailId: id(item.inboundDetailId),
orderNo: item.orderNo || null, inboundCode: item.inboundCode || null, cargoName: item.cargoName || null,
boxCodes: rows(item.boxCodes).map(String),
scanBoxCodes: rows(item.boxCodes).map(String).slice(0, Number(item.plannedPackageCount)),
plannedPackageCount: Number(item.plannedPackageCount),
})).sort((left, right) => left.inboundId.localeCompare(right.inboundId) || left.id.localeCompare(right.id))
}
export async function run(context) {
const source = await loadSource(context)
if (!source) return
const taskId = id(source.pdaTaskId)
const taskResponse = await context.request('GET',
`${PDA}/tasks/${encodeURIComponent(taskId)}?warehouseId=${encodeURIComponent(source.warehouseId)}`)
const task = context.apiSuccess(MODULE, 'PDA 人工作业前读取稳定任务详情', taskResponse, '', OPTIONS)
const taskItems = taskItemSnapshot(task?.items)
const scanBoxCodes = taskItems.flatMap((item) => item.scanBoxCodes)
const cartonCodesReady = taskItems.length > 0 && taskItems.every((item) => item.id && item.inboundId
&& item.plannedPackageCount > 0 && item.boxCodes.length >= item.plannedPackageCount
&& item.scanBoxCodes.length === item.plannedPackageCount
&& new Set(item.boxCodes).size === item.boxCodes.length)
&& new Set(scanBoxCodes).size === scanBoxCodes.length
context.assert(MODULE, '待领取任务下发稳定箱码且计划箱数可逐箱扫描', taskResponse,
id(task?.id) === taskId && id(task?.loadingPlanId) === id(source.loadingPlanId)
&& id(task?.containerBookingId) === id(source.containerBookingId)
&& task?.taskStatus === 'LOADING' && task?.workflowStatus === 'UNASSIGNED'
&& task?.assignment == null && cartonCodesReady,
`workflowStatus=${task?.workflowStatus || 'missing'}, items=${taskItems.length}, plannedPackages=${taskItems.reduce((sum, item) => sum + item.plannedPackageCount, 0)}, boxCodes=${taskItems.reduce((sum, item) => sum + item.boxCodes.length, 0)}`,
OPTIONS)
if (!cartonCodesReady || task?.workflowStatus !== 'UNASSIGNED') return
const pendingResponse = await context.request('GET',
`${PDA}/tasks/pending?keyword=${encodeURIComponent(source.containerNo)}`)
const pending = context.apiSuccess(MODULE, '作业前读取当前操作员 PDA 待办', pendingResponse, '', OPTIONS)
const pendingTask = rows(pending).find((item) => id(item.taskId) === taskId)
context.assert(MODULE, '未领取任务在 PDA 待办中唯一可见', pendingResponse,
Boolean(pendingTask) && pendingTask.workflowStatus === 'UNASSIGNED'
&& Number(pendingTask.pendingItemCount) === taskItems.length,
`matches=${rows(pending).filter((item) => id(item.taskId) === taskId).length}, pendingItemCount=${pendingTask?.pendingItemCount ?? 'missing'}`,
OPTIONS)
const inventoryResponse = await context.request('GET',
`${PDA}/tasks/${encodeURIComponent(taskId)}/inventory-snapshot?warehouseId=${encodeURIComponent(source.warehouseId)}`)
const inventory = context.apiSuccess(MODULE, '作业前读取实时库存快照基线', inventoryResponse, '', OPTIONS)
const inventoryReady = id(inventory?.taskId) === taskId
&& id(inventory?.loadingPlanId) === id(source.loadingPlanId)
&& id(inventory?.containerBookingId) === id(source.containerBookingId)
&& Number(inventory?.taskVersion) === Number(task?.version)
&& typeof inventory?.snapshotToken === 'string' && inventory.snapshotToken.length === 64
&& rows(inventory?.inbounds).length > 0
context.assert(MODULE, '库存快照与待领取任务版本和四元身份一致', inventoryResponse,
inventoryReady,
`taskVersion=${task?.version ?? 'missing'}, snapshotTaskVersion=${inventory?.taskVersion ?? 'missing'}, inboundCount=${rows(inventory?.inbounds).length}`,
OPTIONS)
const operationsResponse = await context.request('GET', `${PDA}/tasks/${encodeURIComponent(taskId)}/operations`)
const operations = context.apiSuccess(MODULE, '作业前读取 PDA 操作历史', operationsResponse, '', OPTIONS)
context.assert(MODULE, '作业前不存在 CLAIM、BATCH、REVIEW 或 COMPLETE_LOADING', operationsResponse,
rows(operations).every((item) => !['CLAIM', 'BATCH', 'REVIEW', 'COMPLETE_LOADING'].includes(item.actionType)),
`actions=${rows(operations).map((item) => item.actionType).join(',') || 'none'}`,
OPTIONS)
context.createdResources.manualPdaBatchFixture = {
schemaVersion: 1, sourceMarker: source.sourceMarker, preparedAt: new Date().toISOString(),
stage: 'MANUAL_PDA_CLAIM_AND_BATCH_READY', pdaTaskId: taskId,
loadingPlanId: id(source.loadingPlanId), containerBookingId: id(source.containerBookingId),
cabinetNo: source.cabinetNo, containerNo: source.containerNo, tenantId: id(source.tenantId),
warehouseId: id(source.warehouseId), beforeTaskVersion: Number(task.version), taskItems,
inventoryBaseline: rows(inventory?.inbounds).map((item) => ({ inboundId: id(item.inboundId),
availablePackageCount: Number(item.availablePackageCount), resourceCount: rows(item.resources).length })),
instructions: [
'使用目标 PDA 作业员进入待办并领取本任务,确认任务进入 IN_PROGRESS。',
'按 taskItems.scanBoxCodes 在真机逐箱扫描;每个箱码只扫描一次,处理完全部计划箱数。',
'点击提交时由 PDA 重新获取库存快照,并只发送一次原子 batch;不要使用逐明细兼容接口。',
'提交成功停在 AWAITING_REVIEW,不上传装柜证据、不复核、不执行最终完成。',
'HTTP 只能核验箱码下发和服务端数量,逐次扫码动作需保留真机人工证据。',
],
}
}