const MODULE = 'phase3-outbound-aggregate' const OPTIONS = { scenario: 'outbound-aggregate', deploymentBlocker: true } const BASE = '/api/platform/v1/first-leg/outbound-dispatches' function id(value) { return value == null ? '' : String(value) } function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) } function rejected(response) { return [400, 401, 403, 404, 409, 503].includes(response?.http) || (response?.http === 200 && codeOf(response) !== '200') } function ordered(items) { return items.every((item, index) => index === 0 || String(items[index - 1]?.occurredAt || '') <= String(item?.occurredAt || '')) } export async function run(context) { const fixture = context.createdResources?.phase3OutboundDispatch if (!fixture?.inboundId || !fixture?.containerBookingId || fixture.marker !== context.marker) { context.addStep(MODULE, '取得同 marker 的出仓主聚合前置', null, false, `inboundId=${fixture?.inboundId || 'missing'}, containerBookingId=${fixture?.containerBookingId || 'missing'}, fixtureMarker=${fixture?.marker || 'missing'}, runMarker=${context.marker}`, OPTIONS) context.addCoverageGap(MODULE, OPTIONS.scenario, '出仓单/柜次主聚合与整体时间线', '本轮未取得 phase3-outbound-dispatch 的 marker 隔离聚合,未使用历史资源', 'fixture') return } const response = await context.request('GET', `${BASE}/${encodeURIComponent(fixture.containerBookingId)}`) const detail = context.apiSuccess(MODULE, '复核柜级出仓主聚合与统一时间线', response, `containerBookingId=${fixture.containerBookingId}`, OPTIONS) const aggregate = detail?.aggregate const inbound = aggregate?.inbounds?.find?.((item) => id(item?.inboundId) === id(fixture.inboundId)) const timeline = Array.isArray(detail?.timeline) ? detail.timeline : [] const expectedModules = ['INBOUND', 'LOADING_PLAN', 'PDA_PICK', 'LOADING_EVIDENCE', 'OCEAN_BOOKING', 'CUSTOMS', 'OUTBOUND_DISPATCH'] context.assert(MODULE, '聚合唯一关联本轮订单、入库单、柜次和客户', response, id(inbound?.orderId) === id(fixture.orderId) && id(inbound?.tenantId) === id(fixture.tenantId) && id(inbound?.inboundId) === id(fixture.inboundId) && id(aggregate?.containerBookingId) === id(fixture.containerBookingId), `orderId=${id(inbound?.orderId) || 'missing'}, inboundId=${id(inbound?.inboundId) || 'missing'}, containerBookingId=${id(aggregate?.containerBookingId) || 'missing'}`, OPTIONS) context.assert(MODULE, '统一时间线有序且各子流程包含审计字段', response, timeline.length > 0 && ordered(timeline) && expectedModules.every((module) => timeline.some((item) => item.module === module)) && timeline.every((item) => Boolean(item.action) && Boolean(item.occurredAt)), `timeline=${timeline.length}, modules=${[...new Set(timeline.map((item) => item.module))].join(',')}`, OPTIONS) context.assert(MODULE, '拖车和独立船期时间线只作参考', response, ['TRUCK_APPOINTMENT', 'SAILING_SCHEDULE'].every((module) => timeline .filter((item) => item.module === module).every((item) => Boolean(item.action) && Boolean(item.occurredAt))), `referenceModules=${timeline.filter((item) => ['TRUCK_APPOINTMENT', 'SAILING_SCHEDULE'].includes(item.module)).length}`, OPTIONS) }