import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' const MODULE = 'manual-pda-ready-fixture' const OPTIONS = { scenario: 'manual-pda-ready-fixture', deploymentBlocker: true } const SMART = '/api/platform/v1/first-leg/smart-loading' const FIRST_LEG = '/api/platform/v1/first-leg' const CONTAINERS = '/api/platform/v1/containers' const PDA = `${FIRST_LEG}/pda-loading` function id(value) { return value == null ? '' : String(value) } function rows(value) { return Array.isArray(value) ? value : [] } function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) } function items(value) { return rows(value).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.manualPdaSourceReport), 'utf8')) const source = report?.createdResources?.manualPreloadPushVerification const sourceGateway = String(report?.gateway || '').replace(/\/$/, '') const valid = sourceGateway === context.options.gateway && source?.stage === 'PRELOAD_EDITING' && source?.loadingPlanId && source?.containerBookingId && source?.pdaTaskCreated === false && Array.isArray(source?.expectedAssignments) && source.expectedAssignments.length > 0 context.assert(MODULE, '读取同环境推送预装核验报告', null, valid, `sourceMarker=${report?.marker || 'missing'}, sameGateway=${sourceGateway === context.options.gateway}, loadingPlanId=${id(source?.loadingPlanId) || 'missing'}`, OPTIONS) return valid ? source : 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, 'PDA 就绪操作前读取预装计划', planResponse, '', OPTIONS) const beforeItems = items(plan?.items) context.assert(MODULE, '操作前计划仍为无号已推送 EDITING', planResponse, id(plan?.id) === planId && plan?.status === 'EDITING' && plan?.containerNo == null && Number(plan?.version) === Number(source.planVersion) && beforeItems.length > 0, `status=${plan?.status || 'missing'}, expectedVersion=${source.planVersion}, actualVersion=${plan?.version ?? 'missing'}, items=${beforeItems.length}`, OPTIONS) if (!plan || beforeItems.length === 0) return const containerResponse = await context.request('GET', `${CONTAINERS}/domestic/${encodeURIComponent(containerId)}`) const container = context.apiSuccess(MODULE, 'PDA 就绪操作前读取内部模拟柜', containerResponse, '', OPTIONS)?.container context.assert(MODULE, '操作前柜次仍无真实柜号和订舱关联', containerResponse, id(container?.id) === containerId && container?.cabinetNo === source.cabinetNo && container?.containerNo == null && container?.oceanBookingId == null && Number.isInteger(Number(container?.version)), `cabinetNo=${container?.cabinetNo || 'missing'}, containerNo=${container?.containerNo || 'null'}, oceanBookingId=${id(container?.oceanBookingId) || 'null'}, version=${container?.version ?? 'missing'}`, OPTIONS) const detailResponse = await context.request('GET', `${FIRST_LEG}/loading-plans/${encodeURIComponent(planId)}?warehouseId=${encodeURIComponent(source.warehouseId)}`) const detail = context.apiSuccess(MODULE, '操作前读取预装确认门禁', detailResponse, '', OPTIONS) context.assert(MODULE, '缺少真实柜号与 RELEASED 订舱时不能确认预装', detailResponse, detail?.summary?.status === 'EDITING' && detail?.summary?.readiness?.canConfirm === false && detail?.summary?.readiness?.readyForPda === false && detail?.summary?.readiness?.pdaTaskId == null, `canConfirm=${detail?.summary?.readiness?.canConfirm}, readyForPda=${detail?.summary?.readiness?.readyForPda}, blockers=${rows(detail?.summary?.readiness?.blockers).join(',')}`, OPTIONS) const taskResponse = await context.request('GET', `${PDA}/tasks?loadingPlanId=${encodeURIComponent(planId)}&warehouseId=${encodeURIComponent(source.warehouseId)}`) context.addStep(MODULE, '操作前 PDA 任务仍不存在', taskResponse, taskResponse.http === 200 && codeOf(taskResponse) === 'A0404', `HTTP=${taskResponse.http}, code=${codeOf(taskResponse)}`, OPTIONS) context.createdResources.manualPdaReadyFixture = { schemaVersion: 1, sourceMarker: source.sourceMarker, preparedAt: new Date().toISOString(), stage: 'MANUAL_BOOKING_LINK_CONFIRM_READY', loadingPlanId: planId, containerBookingId: containerId, cabinetNo: source.cabinetNo, tenantId: id(source.tenantId), warehouseId: id(source.warehouseId), channelCode: source.channelCode, beforePlanVersion: Number(plan.version), beforeContainerVersion: Number(container?.version), beforeItems, expectedAssignments: source.expectedAssignments || [], instructions: [ '在订舱页面创建与本清单客户、起运仓、渠道和柜型一致的 DRAFT 订舱,并填写承运船司代码 carrierCode 及完整运输资料。', '确认放舱,使订舱进入 RELEASED;放舱本身不得创建货柜。', '在原 containerBookingId 上选择该订舱未占用的柜型需求,录入唯一真实 containerNo 完成关联。', '回到预装详情,确认 canConfirm=true 后执行整份计划确认;不要领取 PDA 任务。', '记录 bookingNo、containerNo,并立即运行 manual-pda-ready-verify。', ], } }