fix(firstleg): persist ocean container allocation

This commit is contained in:
2026-08-08 21:49:41 +08:00
parent da85e30f33
commit 789d97675b
4 changed files with 75 additions and 38 deletions
@@ -20,6 +20,7 @@ async function createBooking(context, suffix, key) { return request(context, 'PO
function step(context, name, r, pass, evidence, options = OPTIONS) { context.addStep(MODULE, name, r, pass, evidence, { ...options, deploymentBlocker: options.deploymentBlocker && !pass }) }
export async function run(context) {
const containerFlow = context.createdResources?.phase3ContainerLoading
const suffix = context.marker.replace(/\D/g, '').slice(-10)
const list = await request(context, 'GET', `${BASE}?page=1&size=5&keyword=${encodeURIComponent(context.marker)}`)
if (missing(list) || list.http === 0) {
@@ -56,11 +57,32 @@ export async function run(context) {
await action('确认订舱', 'confirm', version, 'marker confirm')
const changed = await request(context, 'POST', `${path}/change-request`, { expectedVersion: version, vesselName: `改船-${suffix}`, voyageNo: `CV${suffix}`, reason: context.marker }, { 'Idempotency-Key': `${context.marker}-change` })
context.apiSuccess(MODULE, '申请改船', changed, '', OPTIONS); version = Number(data(changed)?.oceanBooking?.version)
await action('确认改船', 'change-confirm', version, 'confirm change')
const changeConfirmed = await action('确认改船', 'change-confirm', version, 'confirm change')
if (containerFlow?.finalStatus === 'COMPLETED') {
context.assert(MODULE, '订舱与本轮柜次客户归属一致', changed,
id(containerFlow?.tenantId) === id(changeConfirmed?.oceanBooking?.customerId),
`bookingCustomerId=${id(changeConfirmed?.oceanBooking?.customerId) || 'missing'}, containerTenantId=${id(containerFlow?.tenantId) || 'missing'}`, OPTIONS)
const requirement = changeConfirmed?.containerRequirements?.find?.((item) => item?.containerType === '40HQ')
context.assert(MODULE, '订舱存在匹配本轮柜型的箱型需求', changed,
Boolean(requirement?.id), `requirementId=${id(requirement?.id) || 'missing'}`, OPTIONS)
if (!requirement?.id || id(containerFlow?.tenantId) !== id(changeConfirmed?.oceanBooking?.customerId)) return
const allocation = await request(context, 'POST', `${path}/allocations`, {
requirementId: id(requirement.id), containerBookingId: id(containerFlow.containerBookingId), expectedVersion: version,
}, { 'Idempotency-Key': `${context.marker}-ocean-container-allocation`.slice(0, 128) })
const allocated = context.apiSuccess(MODULE, '放舱前分配本轮柜次', allocation, '', OPTIONS)
version = Number(allocated?.oceanBooking?.version)
context.assert(MODULE, '订舱柜次分配持久化且版本递增', allocation,
allocated?.containerAllocations?.some?.((item) => id(item?.containerBookingId) === id(containerFlow.containerBookingId))
&& Number.isInteger(version),
`containerBookingId=${id(containerFlow.containerBookingId)}, version=${version}`, OPTIONS)
}
await action('放舱', 'release', version, 'release')
const final = await request(context, 'GET', path); const fd = data(final)
context.apiSuccess(MODULE, '回查 RELEASED 订舱详情', final, '', OPTIONS)
context.assert(MODULE, '订舱状态历史、审计操作者和终态完整', final, fd?.oceanBooking?.status === 'RELEASED' && fd.histories?.length >= 5 && fd.histories.every((h) => h.operatorName), `status=${fd?.oceanBooking?.status || 'missing'}, histories=${fd?.histories?.length ?? 'n/a'}`, OPTIONS)
context.assert(MODULE, '订舱状态历史、审计操作者、柜次关联和终态完整', final,
fd?.oceanBooking?.status === 'RELEASED' && fd.histories?.length >= 5 && fd.histories.every((h) => h.operatorName)
&& (!containerFlow || fd?.containerAllocations?.some?.((item) => id(item?.containerBookingId) === id(containerFlow.containerBookingId))),
`status=${fd?.oceanBooking?.status || 'missing'}, histories=${fd?.histories?.length ?? 'n/a'}, allocations=${fd?.containerAllocations?.length ?? 'n/a'}`, OPTIONS)
const missingDetail = await request(context, 'GET', `${BASE}/999999999999999999`)
step(context, '不存在订舱详情安全失败', missingDetail, failure(missingDetail), `HTTP=${missingDetail.http}, code=${codeOf(missingDetail)}`)
}
@@ -5,26 +5,6 @@ const TRUCK = '/api/platform/v1/truck-appointments'
function id(value) { return value == null ? '' : String(value) }
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
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 ocean = context.createdResources?.oceanBooking
@@ -46,26 +26,15 @@ export async function run(context) {
&& id(containerFlow?.tenantId) === customerId
&& Boolean(containerFlow?.containerBookingId)
&& Boolean(containerFlow?.loadingPlanId)
let oceanVersion = Number(oceanDetail?.oceanBooking?.version)
if (containerFlow && !linkContainer) {
context.addStep(MODULE, '本轮柜次与订舱客户归属可串联', oceanResponse, false,
`oceanCustomerId=${customerId}, containerTenantId=${id(containerFlow?.tenantId) || 'missing'}, containerStatus=${containerFlow?.finalStatus || 'missing'}`, OPTIONS)
return
}
if (linkContainer) {
const requirement = oceanDetail?.containerRequirements?.find?.((item) => item?.containerType === '40HQ')
context.assert(MODULE, '订舱存在匹配本轮柜型的箱型需求', oceanResponse,
Boolean(requirement?.id), `requirementId=${id(requirement?.id) || 'missing'}`, OPTIONS)
if (!requirement?.id || !Number.isInteger(oceanVersion)) return
const allocationResponse = await request(context, 'POST', `${OCEAN}/${ocean.id}/allocations`, {
requirementId: id(requirement.id), containerBookingId: id(containerFlow.containerBookingId), expectedVersion: oceanVersion,
}, { 'Idempotency-Key': `${context.marker}-flow-container-allocation`.slice(0, 128) })
const allocated = context.apiSuccess(MODULE, '将本轮柜次分配至已放舱订舱', allocationResponse, '', OPTIONS)
oceanVersion = Number(allocated?.oceanBooking?.version)
context.assert(MODULE, '订舱分配保留柜次关联并递增版本', allocationResponse,
allocated?.containerAllocations?.some?.((item) => id(item?.containerBookingId) === id(containerFlow.containerBookingId))
&& Number.isInteger(oceanVersion),
`containerBookingId=${id(containerFlow.containerBookingId)}, version=${oceanVersion}`, OPTIONS)
context.assert(MODULE, '已放舱订舱保留本轮柜次分配', oceanResponse,
oceanDetail?.containerAllocations?.some?.((item) => id(item?.containerBookingId) === id(containerFlow.containerBookingId)),
`containerBookingId=${id(containerFlow.containerBookingId)}, allocations=${oceanDetail?.containerAllocations?.length ?? 'n/a'}`, OPTIONS)
}
const payload = {
outboundOrderId: linkContainer ? id(containerFixture?.orderId) || null : null,