107 lines
7.1 KiB
JavaScript
107 lines
7.1 KiB
JavaScript
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
|
|
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 订舱与真实柜号后,人工执行整份预装计划确认。',
|
|
}
|
|
}
|