优化
This commit is contained in:
@@ -43,52 +43,52 @@ export async function run(context) {
|
||||
'本轮未取得 marker 隔离的柜次装柜 fixture,未使用历史资源', 'fixture')
|
||||
return
|
||||
}
|
||||
const selector = `inboundId=${encodeURIComponent(inboundId)}&containerBookingId=${encodeURIComponent(containerBookingId)}`
|
||||
const selector = `containerBookingId=${encodeURIComponent(containerBookingId)}`
|
||||
const listResponse = await request(context, 'GET', `${BASE}?${selector}`)
|
||||
const aggregates = context.apiSuccess(MODULE, '按入库单和柜次查询出仓主聚合', listResponse, selector, OPTIONS)
|
||||
const aggregate = Array.isArray(aggregates) ? aggregates.find((item) => id(item.inboundId) === inboundId
|
||||
&& id(item.containerBookingId) === containerBookingId) : null
|
||||
const aggregates = context.apiSuccess(MODULE, '按柜次查询唯一出仓主聚合', listResponse, selector, OPTIONS)
|
||||
const aggregate = Array.isArray(aggregates) ? aggregates.find((item) => id(item.containerBookingId) === containerBookingId) : null
|
||||
const inboundBinding = aggregate?.inbounds?.find?.((item) => id(item?.inboundId) === inboundId)
|
||||
const outboundFieldGaps = outboundAggregateFieldGaps(aggregate)
|
||||
context.assert(MODULE, '主聚合串联订单、入库单、柜次、计划与客户隔离键', listResponse,
|
||||
Boolean(aggregate?.orderId) && Boolean(aggregate?.tenantId) && Boolean(aggregate?.loadingPlanId)
|
||||
&& id(aggregate?.inboundId) === inboundId && id(aggregate?.containerBookingId) === containerBookingId,
|
||||
`orderId=${id(aggregate?.orderId) || 'missing'}, tenantId=${id(aggregate?.tenantId) || 'missing'}, planId=${id(aggregate?.loadingPlanId) || 'missing'}`, OPTIONS)
|
||||
Boolean(inboundBinding?.orderId) && Boolean(inboundBinding?.tenantId) && Boolean(aggregate?.loadingPlanId)
|
||||
&& id(aggregate?.containerBookingId) === containerBookingId,
|
||||
`orderId=${id(inboundBinding?.orderId) || 'missing'}, tenantId=${id(inboundBinding?.tenantId) || 'missing'}, planId=${id(aggregate?.loadingPlanId) || 'missing'}, inbounds=${aggregate?.inbounds?.length ?? 'missing'}`, OPTIONS)
|
||||
context.assert(MODULE, '国内出仓列表客户、订单入库、柜次计划、PDA订舱与门禁摘要完整', listResponse,
|
||||
outboundFieldGaps.length === 0, `missing=${outboundFieldGaps.join(',') || 'none'}`, OPTIONS)
|
||||
if (!aggregate) return
|
||||
|
||||
const orderResponse = await request(context, 'GET', `${BASE}?orderId=${encodeURIComponent(id(aggregate.orderId))}`)
|
||||
const orderAggregates = context.apiSuccess(MODULE, '按 orderId 查询全部出仓柜次', orderResponse, '', OPTIONS)
|
||||
context.assert(MODULE, 'orderId 查询包含本轮入库单柜次', orderResponse,
|
||||
Array.isArray(orderAggregates) && orderAggregates.some((item) => id(item.inboundId) === inboundId
|
||||
&& id(item.containerBookingId) === containerBookingId), `records=${orderAggregates?.length ?? 'n/a'}`, OPTIONS)
|
||||
|
||||
const detailResponse = await request(context, 'GET', `${BASE}/detail?${selector}`)
|
||||
const detailResponse = await request(context, 'GET', `${BASE}/${encodeURIComponent(containerBookingId)}`)
|
||||
const detail = context.apiSuccess(MODULE, '查询出仓主聚合与整体时间线', detailResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '整体时间线返回稳定数组且聚合定位不漂移', detailResponse,
|
||||
Array.isArray(detail?.timeline) && id(detail?.aggregate?.inboundId) === inboundId
|
||||
&& id(detail?.aggregate?.containerBookingId) === containerBookingId,
|
||||
&& id(detail?.aggregate?.containerBookingId) === containerBookingId
|
||||
&& detail?.aggregate?.inbounds?.some?.((item) => id(item?.inboundId) === inboundId),
|
||||
`timeline=${detail?.timeline?.length ?? 'n/a'}`, OPTIONS)
|
||||
|
||||
const gateResponse = await request(context, 'GET', `${BASE}/check-gate?${selector}`)
|
||||
const gateResponse = await request(context, 'GET', `${BASE}/${encodeURIComponent(containerBookingId)}/gate`)
|
||||
const gate = context.apiSuccess(MODULE, '执行国内出仓统一门禁', gateResponse, '', OPTIONS)
|
||||
const gateItems = Array.isArray(gate?.items) ? gate.items : []
|
||||
const gateItems = Array.isArray(gate?.inbounds)
|
||||
? gate.inbounds.flatMap((item) => Array.isArray(item?.gate?.items) ? item.gate.items : []) : []
|
||||
const expectedCodes = ['INBOUND_LOADED', 'LOADING_PLAN_COMPLETED', 'PDA_PICK_COMPLETED',
|
||||
'OCEAN_BOOKING_RELEASED', 'TRUCK_DEPARTED', 'LOADING_EVIDENCE_COMPLETE',
|
||||
'CUSTOMS_CLEARANCE', 'SAILING_SCHEDULE']
|
||||
'OCEAN_BOOKING_RELEASED', 'LOADING_EVIDENCE_COMPLETE', 'CUSTOMS_CLEARANCE']
|
||||
context.assert(MODULE, '门禁逐项公开真实状态且不省略下游模块', gateResponse,
|
||||
expectedCodes.every((code) => gateItems.some((item) => item.code === code && typeof item.passed === 'boolean')),
|
||||
expectedCodes.every((code) => gateItems.some((item) => item.code === code && typeof item.passed === 'boolean'))
|
||||
&& gateItems.every((item) => !['TRUCK_DEPARTED', 'SAILING_SCHEDULE'].includes(item.code)),
|
||||
`codes=${gateItems.map((item) => item.code).join(',')}`, OPTIONS)
|
||||
context.assert(MODULE, '拖车与独立船期仅作聚合参考且不参与出仓门禁', gateResponse,
|
||||
Object.hasOwn(aggregate, 'truckAppointmentStatus') && Object.hasOwn(aggregate, 'oceanBookingStatus')
|
||||
&& gateItems.every((item) => !['TRUCK_DEPARTED', 'SAILING_SCHEDULE'].includes(item.code)),
|
||||
`truck=${aggregate?.truckAppointmentStatus || 'none'}, ocean=${aggregate?.oceanBookingStatus || 'none'}`, OPTIONS)
|
||||
|
||||
const expectedVersion = Number(aggregate.version)
|
||||
const actionBody = { inboundId, containerBookingId, expectedVersion, remark: context.marker }
|
||||
const confirmKey = `${context.marker}-outbound-confirm`.slice(0, 128)
|
||||
if (!gate?.passed) {
|
||||
const blocked = await request(context, 'POST', `${BASE}/confirm-dispatch`, actionBody,
|
||||
const blocked = await request(context, 'POST', `${BASE}/${encodeURIComponent(containerBookingId)}/confirm`, { expectedVersion, remark: context.marker },
|
||||
{ 'Idempotency-Key': confirmKey })
|
||||
context.addStep(MODULE, '门禁未通过时 confirm-dispatch fail-closed', blocked, rejected(blocked),
|
||||
`HTTP=${blocked.http}, code=${codeOf(blocked)}`, OPTIONS)
|
||||
context.addStep(MODULE, '国内出仓八项门禁必须在同轮全部通过', gateResponse, false,
|
||||
context.addStep(MODULE, '国内出仓六项硬门禁必须在同轮全部通过', gateResponse, false,
|
||||
`failed=${gateItems.filter((item) => !item.passed).map((item) => `${item.code}:${item.actual || 'missing'}`).join(',') || 'gate.passed=false'}`,
|
||||
OPTIONS)
|
||||
for (const item of gateItems.filter((value) => !value.passed)) {
|
||||
@@ -99,7 +99,8 @@ export async function run(context) {
|
||||
return
|
||||
}
|
||||
|
||||
const confirmResponse = await request(context, 'POST', `${BASE}/confirm-dispatch`, actionBody,
|
||||
const confirmBody = { expectedVersion, remark: context.marker }
|
||||
const confirmResponse = await request(context, 'POST', `${BASE}/${encodeURIComponent(containerBookingId)}/confirm`, confirmBody,
|
||||
{ 'Idempotency-Key': confirmKey })
|
||||
const confirmed = context.apiSuccess(MODULE, '确认国内出仓进入等待装船', confirmResponse, '', OPTIONS)
|
||||
context.assert(MODULE, 'confirm-dispatch 推进 WAITING_FOR_LOADING 并递增版本', confirmResponse,
|
||||
@@ -109,7 +110,7 @@ export async function run(context) {
|
||||
`dispatch=${confirmed?.aggregate?.dispatchStatus || 'missing'}, inbound=${confirmed?.aggregate?.inboundStatus || 'missing'}, version=${confirmed?.aggregate?.version ?? 'missing'}`, OPTIONS)
|
||||
if (!confirmed?.aggregate) return
|
||||
|
||||
const replayResponse = await request(context, 'POST', `${BASE}/confirm-dispatch`, actionBody,
|
||||
const replayResponse = await request(context, 'POST', `${BASE}/${encodeURIComponent(containerBookingId)}/confirm`, confirmBody,
|
||||
{ 'Idempotency-Key': confirmKey })
|
||||
const replay = context.apiSuccess(MODULE, 'confirm-dispatch 幂等重放', replayResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '确认重放保持同一终态和版本', replayResponse,
|
||||
@@ -117,30 +118,30 @@ export async function run(context) {
|
||||
&& Number(replay?.aggregate?.version) === Number(confirmed?.aggregate?.version),
|
||||
`version=${replay?.aggregate?.version ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const hashConflict = await request(context, 'POST', `${BASE}/confirm-dispatch`, { ...actionBody, remark: 'forged' },
|
||||
const hashConflict = await request(context, 'POST', `${BASE}/${encodeURIComponent(containerBookingId)}/confirm`, { ...confirmBody, remark: 'forged' },
|
||||
{ 'Idempotency-Key': confirmKey })
|
||||
context.addStep(MODULE, '相同 Idempotency-Key 不同请求拒绝', hashConflict,
|
||||
hashConflict.http === 200 && codeOf(hashConflict) === 'A0409',
|
||||
`HTTP=${hashConflict.http}, code=${codeOf(hashConflict)}`, OPTIONS)
|
||||
|
||||
const reverseKey = `${context.marker}-outbound-reverse`.slice(0, 128)
|
||||
const reverseResponse = await request(context, 'POST', `${BASE}/reverse-dispatch`, {
|
||||
inboundId, containerBookingId, expectedVersion: Number(confirmed.aggregate.version), remark: context.marker,
|
||||
const reverseResponse = await request(context, 'POST', `${BASE}/${encodeURIComponent(containerBookingId)}/reverse`, {
|
||||
expectedVersion: Number(confirmed.aggregate.version), remark: context.marker,
|
||||
}, { 'Idempotency-Key': reverseKey })
|
||||
context.addStep(MODULE, '实际DEPARTED_WAREHOUSE节点产生后撤销精确返回A0409', reverseResponse,
|
||||
reverseResponse.http === 200 && codeOf(reverseResponse) === 'A0409'
|
||||
&& String(reverseResponse.body?.message || '').includes('DEPARTED_WAREHOUSE')
|
||||
&& String(reverseResponse.body?.message || '').includes('禁止撤销'),
|
||||
`HTTP=${reverseResponse.http}, code=${codeOf(reverseResponse)}, message=${reverseResponse.body?.message || 'missing'}`, OPTIONS)
|
||||
const reverseReplayResponse = await request(context, 'POST', `${BASE}/reverse-dispatch`, {
|
||||
inboundId, containerBookingId, expectedVersion: Number(confirmed.aggregate.version), remark: context.marker,
|
||||
const reverseReplayResponse = await request(context, 'POST', `${BASE}/${encodeURIComponent(containerBookingId)}/reverse`, {
|
||||
expectedVersion: Number(confirmed.aggregate.version), remark: context.marker,
|
||||
}, { 'Idempotency-Key': reverseKey })
|
||||
context.addStep(MODULE, '禁止撤销边界重复请求仍精确返回A0409', reverseReplayResponse,
|
||||
reverseReplayResponse.http === 200 && codeOf(reverseReplayResponse) === 'A0409'
|
||||
&& String(reverseReplayResponse.body?.message || '').includes('DEPARTED_WAREHOUSE'),
|
||||
`HTTP=${reverseReplayResponse.http}, code=${codeOf(reverseReplayResponse)}`, OPTIONS)
|
||||
|
||||
const finalDetailResponse = await request(context, 'GET', `${BASE}/detail?${selector}`)
|
||||
const finalDetailResponse = await request(context, 'GET', `${BASE}/${encodeURIComponent(containerBookingId)}`)
|
||||
const finalDetail = context.apiSuccess(MODULE, '负向与幂等验证后回查出仓聚合', finalDetailResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '出仓聚合保持 WAITING_FOR_LOADING、LOADED 且无成功撤销历史', finalDetailResponse,
|
||||
finalDetail?.aggregate?.dispatchStatus === 'WAITING_FOR_LOADING'
|
||||
@@ -154,7 +155,7 @@ export async function run(context) {
|
||||
if (confirmed?.aggregate) {
|
||||
context.createdResources.phase3OutboundDispatch = {
|
||||
marker: context.marker, inboundId, containerBookingId,
|
||||
orderId: id(confirmed.aggregate.orderId), tenantId: id(confirmed.aggregate.tenantId),
|
||||
orderId: id(inboundBinding?.orderId), tenantId: id(inboundBinding?.tenantId),
|
||||
confirmedStatus: confirmed.aggregate.dispatchStatus,
|
||||
confirmedInboundStatus: confirmed.aggregate.inboundStatus,
|
||||
confirmedVersion: Number(confirmed.aggregate.version),
|
||||
|
||||
Reference in New Issue
Block a user