Files
baiyue-firstleg/scripts/admin-test-env-modules/manual-preload-push-verify.mjs
T

107 lines
7.1 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-preload-push-verify'
const OPTIONS = { scenario: 'manual-preload-push-readonly-verification', 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 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 loadFixture(context) {
const report = JSON.parse(await readFile(resolve(context.options.manualPreloadSourceReport), 'utf8'))
const fixture = report?.createdResources?.manualPreloadPushFixture
const sourceGateway = String(report?.gateway || '').replace(/\/$/, '')
const valid = sourceGateway === context.options.gateway && fixture?.schemaVersion === 1
&& fixture?.stage === 'MANUAL_PUSH_PRELOAD_READY' && fixture?.loadingPlanId
&& fixture?.containerBookingId && Array.isArray(fixture?.beforeItems)
context.assert(MODULE, '读取同环境推送预装准备报告', null, valid,
`sourceMarker=${report?.marker || 'missing'}, sameGateway=${sourceGateway === context.options.gateway}, loadingPlanId=${id(fixture?.loadingPlanId) || 'missing'}`,
OPTIONS)
return valid ? fixture : null
}
export async function run(context) {
const fixture = await loadFixture(context)
if (!fixture) return
const planId = id(fixture.loadingPlanId)
const containerId = id(fixture.containerBookingId)
const planResponse = await context.request('GET', `${SMART}/plans/${encodeURIComponent(planId)}`)
const plan = context.apiSuccess(MODULE, '读取人工推送后的智能装柜计划', planResponse, '', OPTIONS)
const afterItems = itemSnapshot(plan?.items)
context.assert(MODULE, '无号推送后计划保持 EDITING、版本递增且明细零漂移', planResponse,
id(plan?.id) === planId && id(plan?.bookingId || plan?.containerBookingId) === containerId
&& plan?.status === 'EDITING' && plan?.containerNo == null
&& Number(plan?.version) > Number(fixture.beforePlanVersion)
&& JSON.stringify(afterItems) === JSON.stringify(fixture.beforeItems),
`status=${plan?.status || 'missing'}, beforeVersion=${fixture.beforePlanVersion}, afterVersion=${plan?.version ?? 'missing'}, items=${afterItems.length}`,
OPTIONS)
if (!plan || plan.status !== 'EDITING') return
const linksResponse = await context.request('GET', `${SMART}/plans/${encodeURIComponent(planId)}/preloads`)
const links = context.apiSuccess(MODULE, '回查推送原子建立的预装 bridge', linksResponse, '', OPTIONS)
const linkRows = rows(links)
for (const expected of fixture.expectedAssignments || []) {
const matched = linkRows.filter((link) => id(link.inboundId) === id(expected.inboundId))
const allocated = matched.reduce((sum, link) => sum + Number(link.allocatedPackageCount || 0), 0)
context.assert(MODULE, `${expected.role} 预装 bridge 数量与人工分柜一致`, linksResponse,
matched.length >= 1 && allocated === Number(expected.expectedAllocatedPackageCount)
&& matched.every((link) => id(link.loadingPlanId) === planId
&& id(link.containerBookingId) === containerId && link.preloadStatus === 'PENDING'
&& link.loadingPlanStatus === 'EDITING' && Number(link.loadingPlanVersion) === Number(plan.version)),
`inboundId=${id(expected.inboundId)}, expected=${expected.expectedAllocatedPackageCount}, actual=${allocated}, links=${matched.length}`,
OPTIONS)
}
const expectedInboundIds = new Set((fixture.expectedAssignments || []).map((item) => id(item.inboundId)))
const unexpectedLinks = linkRows.filter((link) => !expectedInboundIds.has(id(link.inboundId)))
context.assert(MODULE, '预装 bridge 未混入人工分柜清单外入库单', linksResponse,
unexpectedLinks.length === 0 && linkRows.length >= expectedInboundIds.size,
`linkCount=${linkRows.length}, unexpectedInboundIds=${unexpectedLinks.map((link) => id(link.inboundId)).join(',') || 'none'}`,
OPTIONS)
const detailResponse = await context.request('GET',
`${FIRST_LEG}/loading-plans/${encodeURIComponent(planId)}?warehouseId=${encodeURIComponent(fixture.warehouseId)}`)
const detail = context.apiSuccess(MODULE, '读取推送后正式预装清单详情', detailResponse, '', OPTIONS)
context.assert(MODULE, '预装清单保持 EDITING 且无号阶段不能确认或创建 PDA', detailResponse,
id(detail?.summary?.planId) === planId && detail?.summary?.status === 'EDITING'
&& detail?.summary?.readiness?.canConfirm === false
&& detail?.summary?.readiness?.readyForPda === false
&& detail?.summary?.readiness?.pdaTaskId == null,
`planId=${id(detail?.summary?.planId) || 'missing'}, status=${detail?.summary?.status || 'missing'}, canConfirm=${detail?.summary?.readiness?.canConfirm}, readyForPda=${detail?.summary?.readiness?.readyForPda}`,
OPTIONS)
const taskResponse = await context.request('GET',
`${PDA}/tasks?loadingPlanId=${encodeURIComponent(planId)}&warehouseId=${encodeURIComponent(fixture.warehouseId)}`)
context.addStep(MODULE, '无真实柜号推送后 PDA 查询仍为 A0404', taskResponse,
taskResponse.http === 200 && codeOf(taskResponse) === 'A0404',
`HTTP=${taskResponse.http}, code=${codeOf(taskResponse)}, requestId=${taskResponse.requestId || 'missing'}, traceId=${taskResponse.traceId || 'missing'}`,
OPTIONS)
const containerResponse = await context.request('GET', `${CONTAINERS}/domestic/${encodeURIComponent(containerId)}`)
const container = context.apiSuccess(MODULE, '回查推送后内部柜次身份', containerResponse, '', OPTIONS)?.container
2026-08-26 16:50:22 +08:00
context.assert(MODULE, '推送预装不伪造真实柜号或订舱关联', containerResponse,
id(container?.id) === containerId && container?.cabinetNo === fixture.cabinetNo
&& container?.containerNo == null && container?.oceanBookingId == null,
`containerId=${id(container?.id) || 'missing'}, cabinetNo=${container?.cabinetNo || 'missing'}, containerNo=${container?.containerNo || 'null'}, oceanBookingId=${id(container?.oceanBookingId) || 'null'}`,
OPTIONS)
context.createdResources.manualPreloadPushVerification = {
sourceMarker: fixture.sourceMarker, verifiedAt: new Date().toISOString(), stage: 'PRELOAD_EDITING',
loadingPlanId: planId, containerBookingId: containerId, cabinetNo: fixture.cabinetNo,
tenantId: id(fixture.tenantId), warehouseId: id(fixture.warehouseId), channelCode: fixture.channelCode,
planVersion: Number(plan.version), preloadLinkCount: linkRows.length, pdaTaskCreated: false,
expectedAssignments: fixture.expectedAssignments,
nextStage: '关联 RELEASED 订舱与真实柜号后,人工执行整份预装计划确认。',
}
}