import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' const MODULE = 'manual-preload-push-fixture' const OPTIONS = { scenario: 'manual-preload-push-fixture', deploymentBlocker: true } const SMART = '/api/platform/v1/first-leg/smart-loading' const CONTAINERS = '/api/platform/v1/containers' function id(value) { return value == null ? '' : String(value) } function rows(value) { return Array.isArray(value) ? value : [] } function itemSnapshot(items) { return rows(items).map((item) => ({ id: id(item.id), inboundId: id(item.inboundId), packageCount: Number(item.packageCount) })) .sort((left, right) => left.inboundId.localeCompare(right.inboundId) || left.id.localeCompare(right.id)) } async function loadSource(context) { const report = JSON.parse(await readFile(resolve(context.options.manualPreloadSourceReport), 'utf8')) const verification = report?.createdResources?.manualOrderSplittingVerification const sourceGateway = String(report?.gateway || '').replace(/\/$/, '') const valid = sourceGateway === context.options.gateway && verification?.loadingPlanId && verification?.containerBookingId && Array.isArray(verification?.verifiedAssignments) context.assert(MODULE, '读取同环境人工分柜核验报告', null, valid, `sourceMarker=${report?.marker || 'missing'}, sameGateway=${sourceGateway === context.options.gateway}, loadingPlanId=${id(verification?.loadingPlanId) || 'missing'}`, OPTIONS) return valid ? verification : null } export async function run(context) { const source = await loadSource(context) if (!source) return const planId = id(source.loadingPlanId) const containerId = id(source.containerBookingId) const planResponse = await context.request('GET', `${SMART}/plans/${encodeURIComponent(planId)}`) const plan = context.apiSuccess(MODULE, '推送前读取智能装柜计划', planResponse, '', OPTIONS) const items = itemSnapshot(plan?.items) context.assert(MODULE, '计划仍处于未推送 EDITING 且分柜明细完整', planResponse, id(plan?.id) === planId && id(plan?.bookingId || plan?.containerBookingId) === containerId && plan?.status === 'EDITING' && Number.isInteger(Number(plan?.version)) && items.length > 0, `planId=${id(plan?.id) || 'missing'}, status=${plan?.status || 'missing'}, version=${plan?.version ?? 'missing'}, items=${items.length}`, OPTIONS) if (!plan || plan.status !== 'EDITING' || items.length === 0) return const linksResponse = await context.request('GET', `${SMART}/plans/${encodeURIComponent(planId)}/preloads`) const links = context.apiSuccess(MODULE, '推送前回查尚未建立预装 bridge', linksResponse, '', OPTIONS) context.assert(MODULE, '未点击推送前不存在预装 bridge', linksResponse, rows(links).length === 0, `linkCount=${rows(links).length}`, OPTIONS) const containerResponse = await context.request('GET', `${CONTAINERS}/${encodeURIComponent(containerId)}`) const container = context.apiSuccess(MODULE, '推送前读取内部模拟柜', containerResponse, '', OPTIONS) context.assert(MODULE, '当前仍是无真实柜号且未关联订舱的内部模拟柜', containerResponse, id(container?.id) === containerId && container?.containerNo == null && container?.oceanBookingId == null && id(container?.warehouseId) === id(source.warehouseId) && container?.channelCode === source.channelCode, `containerId=${id(container?.id) || 'missing'}, cabinetNo=${container?.cabinetNo || 'missing'}, containerNo=${container?.containerNo || 'null'}, oceanBookingId=${id(container?.oceanBookingId) || 'null'}`, OPTIONS) context.createdResources.manualPreloadPushFixture = { schemaVersion: 1, sourceMarker: source.sourceMarker, preparedAt: new Date().toISOString(), stage: 'MANUAL_PUSH_PRELOAD_READY', loadingPlanId: planId, containerBookingId: containerId, cabinetNo: container?.cabinetNo || null, tenantId: id(source.tenantId), warehouseId: id(source.warehouseId), channelCode: source.channelCode, beforePlanVersion: Number(plan.version), beforeItems: items, expectedAssignments: source.verifiedAssignments, instructions: [ '在智能装柜计划页面点击推送预装。', '本节点保持无真实集装箱号、无订舱关联,不执行计划确认。', '推送成功后不要继续录入柜号或创建 PDA 任务,立即运行 manual-preload-push-verify。', ], } }