Files
baiyue-firstleg/scripts/admin-test-env-modules/manual-loading-complete-fixture.mjs
T

118 lines
7.7 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-loading-complete-fixture'
const OPTIONS = { scenario: 'manual-loading-complete-fixture', deploymentBlocker: true }
const PDA = '/api/platform/v1/first-leg/pda-loading'
const SMART = '/api/platform/v1/first-leg/smart-loading'
const CONTAINERS = '/api/platform/v1/containers'
const REQUIRED_EVIDENCE_TYPES = ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL']
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.manualLoadingCompleteSourceReport), 'utf8'))
const source = report?.createdResources?.manualPdaBatchVerification
const sourceGateway = String(report?.gateway || '').replace(/\/$/, '')
const valid = sourceGateway === context.options.gateway && source?.stage === 'PDA_AWAITING_REVIEW'
&& 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 taskItems(value) {
return rows(value).map((item) => ({ id: id(item.id), tenantId: id(item.tenantId),
inboundId: id(item.inboundId), inboundDetailId: id(item.inboundDetailId),
plannedPackageCount: Number(item.plannedPackageCount), pickedPackageCount: Number(item.pickedPackageCount),
itemStatus: item.itemStatus, version: Number(item.version) }))
.sort((left, right) => left.inboundId.localeCompare(right.inboundId) || left.id.localeCompare(right.id))
}
function sourceInboundSnapshots(items) {
const values = new Map()
for (const item of rows(items)) {
const inboundId = id(item.inboundId)
const current = values.get(inboundId) || { tenantId: id(item.tenantId), inboundId, pickedPackageCount: 0 }
current.pickedPackageCount += Number(item.pickedPackageCount || 0)
values.set(inboundId, current)
}
return [...values.values()]
}
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 beforeItems = taskItems(task?.items)
const evidenceOwner = rows(task?.items).find((item) => id(item.inboundId) === id(task?.inboundId))
|| rows(task?.items)[0]
context.assert(MODULE, '任务全部拣齐、待复核且证据归属入库单明确', taskResponse,
id(task?.id) === taskId && task?.taskStatus === 'LOADING'
&& task?.workflowStatus === 'AWAITING_REVIEW' && task?.assignment?.assigneeId
&& beforeItems.length > 0 && beforeItems.every((item) => item.itemStatus === 'PICKED'
&& item.pickedPackageCount === item.plannedPackageCount)
&& evidenceOwner?.tenantId && evidenceOwner?.inboundId,
`workflowStatus=${task?.workflowStatus || 'missing'}, items=${beforeItems.length}, evidenceTenantId=${id(evidenceOwner?.tenantId) || 'missing'}, evidenceInboundId=${id(evidenceOwner?.inboundId) || 'missing'}`,
OPTIONS)
if (!evidenceOwner?.tenantId || !evidenceOwner?.inboundId) return
const evidencePath = `/api/platform/v1/inbounds/${encodeURIComponent(evidenceOwner.inboundId)}/loading/evidence?tenantId=${encodeURIComponent(evidenceOwner.tenantId)}`
const evidenceResponse = await context.request('GET', evidencePath)
const evidence = context.apiSuccess(MODULE, '上传五类证据前读取空基线', evidenceResponse, '', OPTIONS)
const activeEvidence = rows(evidence?.evidences).filter((item) => item.status !== 'VOID')
context.assert(MODULE, '本轮最终完成前尚未上传 ACTIVE 装柜证据', evidenceResponse,
activeEvidence.length === 0,
`activeEvidence=${activeEvidence.map((item) => item.evidenceType).join(',') || 'none'}`, OPTIONS)
const planResponse = await context.request('GET', `${SMART}/plans/${encodeURIComponent(source.loadingPlanId)}`)
const plan = context.apiSuccess(MODULE, '最终完成前读取运行中装柜计划', planResponse, '', OPTIONS)
context.assert(MODULE, '计划保持 LOADING 且尚未最终完成', planResponse,
id(plan?.id) === id(source.loadingPlanId) && plan?.status === 'LOADING'
&& plan?.finalCompletionStatus === 'IN_PROGRESS',
`status=${plan?.status || 'missing'}, finalCompletionStatus=${plan?.finalCompletionStatus || 'missing'}, version=${plan?.version ?? 'missing'}`,
OPTIONS)
const operationsResponse = await context.request('GET', `${PDA}/tasks/${encodeURIComponent(taskId)}/operations`)
const operations = context.apiSuccess(MODULE, '最终完成前读取任务审计', operationsResponse, '', OPTIONS)
context.assert(MODULE, '完成前只有一次 CLAIM/BATCH 且无 COMPLETE_LOADING', operationsResponse,
rows(operations).filter((item) => item.actionType === 'CLAIM').length === 1
&& rows(operations).filter((item) => item.actionType === 'BATCH').length === 1
&& !rows(operations).some((item) => ['REVIEW', 'COMPLETE_LOADING'].includes(item.actionType)),
`actions=${rows(operations).map((item) => item.actionType).join(',') || 'none'}`, OPTIONS)
const containerResponse = await context.request('GET', `${CONTAINERS}/domestic/${encodeURIComponent(source.containerBookingId)}`)
const container = context.apiSuccess(MODULE, '最终完成前回查 RELEASED 订舱柜次', containerResponse, '', OPTIONS)?.container
2026-08-26 16:50:22 +08:00
const bookingResponse = container?.oceanBookingId
? await context.request('GET', `/api/platform/v1/ocean-bookings/${encodeURIComponent(container.oceanBookingId)}`)
: containerResponse
const booking = context.apiSuccess(MODULE, '最终完成前回查订舱门禁', bookingResponse, '', OPTIONS)
context.assert(MODULE, '完成前真实柜号与 RELEASED 订舱仍有效', bookingResponse,
id(container?.id) === id(source.containerBookingId) && container?.containerNo === source.containerNo
&& booking?.oceanBooking?.status === 'RELEASED',
`containerNo=${container?.containerNo || 'missing'}, oceanBookingId=${id(container?.oceanBookingId) || 'missing'}, bookingStatus=${booking?.oceanBooking?.status || 'missing'}`,
OPTIONS)
context.createdResources.manualLoadingCompleteFixture = {
schemaVersion: 1, sourceMarker: source.sourceMarker, preparedAt: new Date().toISOString(),
stage: 'MANUAL_EVIDENCE_AND_COMPLETE_READY', pdaTaskId: taskId,
loadingPlanId: id(source.loadingPlanId), containerBookingId: id(source.containerBookingId),
cabinetNo: source.cabinetNo, containerNo: source.containerNo, warehouseId: id(source.warehouseId),
evidenceTenantId: id(evidenceOwner.tenantId), evidenceInboundId: id(evidenceOwner.inboundId),
requiredEvidenceTypes: REQUIRED_EVIDENCE_TYPES, beforeTaskVersion: Number(task.version),
beforePlanVersion: Number(plan?.version), beforeItems,
sourceInbounds: sourceInboundSnapshots(task.items),
instructions: [
'在 PDA 当前任务中依次现场拍摄空仓、半仓、满仓、锁仓和带锁码照五类证据。',
'五类分别对应 PHOTO_STEP_1、PHOTO_STEP_2、PHOTO_STEP_3、PHOTO_STEP_4、SEAL;SEAL 必须填写非空锁码。',
'确认五类证据均为 ACTIVE 后,只点击一次最终完成;不要调用旧入库单兼容完成入口。',
'完成成功后任务、计划和所有来源入库单应进入唯一终态,立即运行 manual-loading-complete-verify。',
],
}
}