feat(phase3): 收口父子单与全链路验收

This commit is contained in:
2026-08-10 12:23:43 +08:00
parent b294695169
commit 75d5fd4e26
55 changed files with 1110 additions and 115 deletions
@@ -1,10 +1,23 @@
import { registerPendingContract } from './_phase3-pending-contract.mjs'
const MODULE = 'phase3-dispatch-gate'
const OPTIONS = { scenario: 'domestic-dispatch-gate', deploymentBlocker: true }
const BASE = '/api/platform/v1/first-leg/outbound-dispatches'
export async function run(context) {
registerPendingContract(context, {
module: 'phase3-dispatch-gate', scenario: 'domestic-dispatch-gate', name: '国内出仓统一门禁',
reason: '统一门禁、confirm-dispatch/reverse-dispatch 已实现并推送,待目标提交部署且真实 HTTP 通过后删除此项', category: 'deployment-validation-pending',
fixtures: ['phase3Loading', 'phase3ContainerLoading', 'oceanBooking', 'truckAppointment'], contractSource: 'PlatformOutboundDispatchController / phase3-outbound-dispatch-v1.yaml',
assertions: ['门禁逐项返回真实阻断原因', '旧 version 和重复确认安全冲突', '确认与撤销均保留审计时间线'],
})
const fixture = context.createdResources?.phase3OutboundDispatch
if (!fixture?.inboundId || !fixture?.containerBookingId || fixture.marker !== context.marker) {
context.addCoverageGap(MODULE, OPTIONS.scenario, '订舱/拖车/报关/PDA/五类证据统一硬门禁',
'本轮未取得 phase3-outbound-dispatch 的 marker 隔离聚合,未使用历史资源', 'fixture')
return
}
const selector = `inboundId=${encodeURIComponent(fixture.inboundId)}&containerBookingId=${encodeURIComponent(fixture.containerBookingId)}`
const response = await context.request('GET', `${BASE}/check-gate?${selector}`)
const gate = context.apiSuccess(MODULE, '复核国内出仓统一硬门禁', response, selector, OPTIONS)
const items = Array.isArray(gate?.items) ? gate.items : []
const required = ['INBOUND_LOADED', 'LOADING_PLAN_COMPLETED', 'PDA_PICK_COMPLETED',
'OCEAN_BOOKING_RELEASED', 'TRUCK_DEPARTED', 'LOADING_EVIDENCE_COMPLETE',
'CUSTOMS_CLEARANCE', 'SAILING_SCHEDULE']
context.assert(MODULE, '八项业务门禁全部由服务端真实判定并通过', response,
gate?.passed === true && required.every((code) => items.some((item) => item.code === code
&& item.passed === true && item.actual !== 'MODULE_UNAVAILABLE' && item.actual !== 'ASSESSMENT_ERROR')),
`passed=${gate?.passed ?? 'missing'}, gates=${items.map((item) => `${item.code}:${item.passed}`).join(',')}`, OPTIONS)
}