76 lines
4.8 KiB
JavaScript
76 lines
4.8 KiB
JavaScript
const MODULE = 'phase3-loading-finalize'
|
|||
|
|
const OPTIONS = { scenario: 'phase3-loading-hard-gate-finalize', deploymentBlocker: true }
|
||
|
|
|
||
|
|
function id(value) { return value == null ? '' : String(value) }
|
||
|
|
|
||
|
|
async function request(context, method, path, body, headers = {}) {
|
||
|
|
try {
|
||
|
|
const response = await fetch(`${context.options.gateway}${path}`, {
|
||
|
|
method,
|
||
|
|
headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}`,
|
||
|
|
...(body === undefined ? {} : { 'Content-Type': 'application/json; charset=utf-8' }), ...headers },
|
||
|
|
body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(60_000),
|
||
|
|
})
|
||
|
|
const raw = await response.text(); let payload = null
|
||
|
|
try { payload = raw ? JSON.parse(raw) : null } catch { /* bounded raw evidence */ }
|
||
|
|
return { method, path, http: response.status, body: payload,
|
||
|
|
raw: payload === null ? raw.slice(0, 500) : null,
|
||
|
|
requestId: payload?.requestId || response.headers.get('x-request-id'),
|
||
|
|
traceId: payload?.traceId || response.headers.get('x-trace-id') }
|
||
|
|
} catch (error) {
|
||
|
|
return { method, path, http: 0, body: null, raw: String(error), requestId: null, traceId: null }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function run(context) {
|
||
|
|
const fixture = context.createdResources?.phase3Loading
|
||
|
|
const tenantId = id(fixture?.tenantId); const inboundId = id(fixture?.inboundId)
|
||
|
|
if (!tenantId || !inboundId || fixture?.marker !== context.marker
|
||
|
|
|| fixture?.loadingPlanStatus !== 'COMPLETED') {
|
||
|
|
context.addStep(MODULE, '取得下游门禁待完成装柜 fixture', null, false,
|
||
|
|
`tenantId=${tenantId || 'missing'}, inboundId=${inboundId || 'missing'}, marker=${fixture?.marker || 'missing'}, plan=${fixture?.loadingPlanStatus || 'missing'}`, OPTIONS)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
const detailPath = `/api/platform/inbounds/${inboundId}?tenantId=${encodeURIComponent(tenantId)}`
|
||
|
|
const beforeResponse = await context.request('GET', detailPath)
|
||
|
|
const before = context.apiSuccess(MODULE, '读取下游门禁齐备后的装柜状态', beforeResponse, '', OPTIONS)
|
||
|
|
const version = Number(before?.summary?.version)
|
||
|
|
context.assert(MODULE, '最终装柜前保持 LOADING 且版本有效', beforeResponse,
|
||
|
|
before?.summary?.processStatus === 'LOADING' && Number.isInteger(version),
|
||
|
|
`status=${before?.summary?.processStatus || 'missing'}, version=${before?.summary?.version ?? 'missing'}`, OPTIONS)
|
||
|
|
if (!Number.isInteger(version)) return
|
||
|
|
|
||
|
|
const base = `/api/platform/v1/inbounds/${inboundId}`
|
||
|
|
const completeResponse = await request(context, 'POST',
|
||
|
|
`${base}/loading/complete?tenantId=${encodeURIComponent(tenantId)}`,
|
||
|
|
{ expectedVersion: version, remark: context.marker },
|
||
|
|
{ 'Idempotency-Key': `${context.marker}-p3-complete-after-downstream`.slice(0, 128) })
|
||
|
|
const completed = context.apiSuccess(MODULE, '订舱拖车报关齐备后完成装柜', completeResponse, '', OPTIONS)
|
||
|
|
const evidences = completed?.evidences?.evidences || []
|
||
|
|
context.assert(MODULE, '统一硬门禁通过后推进 LOADED 且五类有效证据仍完整', completeResponse,
|
||
|
|
completed?.mainStatus === 'LOADED' && fixture.evidenceTypes?.every?.((type) =>
|
||
|
|
evidences.some((item) => item.evidenceType === type && item.status !== 'VOID')),
|
||
|
|
`mainStatus=${completed?.mainStatus || 'missing'}, evidences=${evidences.length}`, OPTIONS)
|
||
|
|
|
||
|
|
const replayResponse = await request(context, 'POST',
|
||
|
|
`${base}/loading/complete?tenantId=${encodeURIComponent(tenantId)}`,
|
||
|
|
{ expectedVersion: version, remark: context.marker },
|
||
|
|
{ 'Idempotency-Key': `${context.marker}-p3-complete-after-downstream`.slice(0, 128) })
|
||
|
|
context.assert(MODULE, 'LOADED 完成请求 Idempotency-Key 重放保持终态', replayResponse,
|
||
|
|
replayResponse.http === 200 && String(replayResponse.body?.code) === '200'
|
||
|
|
&& replayResponse.body?.data?.mainStatus === 'LOADED',
|
||
|
|
`HTTP=${replayResponse.http}, code=${replayResponse.body?.code ?? 'missing'}, status=${replayResponse.body?.data?.mainStatus || 'missing'}`, OPTIONS)
|
||
|
|
|
||
|
|
const finalResponse = await context.request('GET', detailPath)
|
||
|
|
const finalData = context.apiSuccess(MODULE, '回查三期装柜完成终态', finalResponse, '', OPTIONS)
|
||
|
|
context.assert(MODULE, '入库单终态 LOADED、版本递增且审计历史存在', finalResponse,
|
||
|
|
finalData?.summary?.processStatus === 'LOADED' && Number(finalData?.summary?.version) === version + 1
|
||
|
|
&& finalData?.statusHistory?.some?.((item) => item.action === 'COMPLETE_LOADING'
|
||
|
|
&& item.operatorName && item.fromStatus === 'LOADING' && item.toStatus === 'LOADED'),
|
||
|
|
`status=${finalData?.summary?.processStatus || 'missing'}, version=${finalData?.summary?.version ?? 'missing'}, histories=${finalData?.statusHistory?.length ?? 'n/a'}`, OPTIONS)
|
||
|
|
|
||
|
|
context.createdResources.phase3Loading = {
|
||
|
|
...fixture, inboundVersion: Number(finalData?.summary?.version), inboundStatus: 'LOADED', finalStatus: 'LOADED',
|
||
|
|
}
|
||
|
|
}
|