This commit is contained in:
2026-08-17 13:24:48 +08:00
parent ac6cf9c904
commit 6739c83467
43 changed files with 1049 additions and 315 deletions
@@ -120,11 +120,12 @@ export async function run(context) {
const base = `/api/platform/v1/inbounds/${inboundId}`
const suffix = context.marker.replace(/\D/g, '').slice(-10)
const warehouseId = id(initial?.summary?.warehouseId)
const containerNo = `P3B-${suffix}`.slice(0, 64)
const smartLoadingBase = '/api/platform/v1/first-leg/smart-loading'
const pdaBase = '/api/platform/v1/first-leg/pda-loading'
const bookingDraft = {
customerId: tenantId, customerName: initial?.summary?.customerName,
warehouseId, warehouseName: initial?.summary?.warehouseName,
warehouseId,
channelCode: initial?.summary?.channelCode,
billOfLadingNo: `P3BL-${suffix}`.slice(0, 128),
vesselName: `P3VESSEL-${suffix}`.slice(0, 128), voyageNo: `P3V-${suffix}`.slice(0, 64),
@@ -135,12 +136,12 @@ export async function run(context) {
parties: [{ partyType: 'SHIPPER', partyName: `P3-SHIPPER-${suffix}` },
{ partyType: 'CONSIGNEE', partyName: `P3-CONSIGNEE-${suffix}` },
{ partyType: 'NOTIFY_PARTY', partyName: `P3-NOTIFY-${suffix}` }],
containerRequirements: [{ containerType: '40HQ', containerKind: 'FCL', capacityCbm: 100,
maxWeightKg: 100000, quantity: 1 }],
containers: [{ containerNo, containerType: '40HC', containerKind: 'FCL', capacityCbm: 76,
maxWeightKg: 28000 }],
}
const simulationResponse = await request(context, 'POST', '/api/platform/v1/containers', {
channelCode: initial?.summary?.channelCode, warehouseId, containerType: '40HQ',
capacityCbm: 100, maxWeightKg: 100000,
channelCode: initial?.summary?.channelCode, warehouseId, containerType: '40HC',
capacityCbm: 76, maxWeightKg: 28000,
})
const simulation = context.apiSuccess(MODULE, '新增三期内部模拟柜', simulationResponse, '', OPTIONS)
const containerBookingId = id(simulation?.id)
@@ -159,7 +160,6 @@ export async function run(context) {
`candidate=${Boolean(candidate)}, warehouseId=${warehouseId || 'missing'}`, OPTIONS)
if (!candidate?.inboundDetailId || !warehouseId) return
const containerNo = `P3B-${suffix}`
const loadingPlanResponse = await request(context, 'POST', `${smartLoadingBase}/plans`, {
bookingId: containerBookingId, remark: context.marker,
@@ -278,21 +278,9 @@ export async function run(context) {
`changed=${rejectedDiff.join('|') || 'none'}; sameKey=${responseEvidence(sameKeyDifferentBody)}; newKey=${responseEvidence(newKeyDuplicate)}; task=${responseEvidence(rejectedTaskResponse)}`, OPTIONS)
if (rejectedDiff.length !== 0 || codeOf(rejectedTaskResponse) !== 'A0404') return
const transportResponse = await request(context, 'PATCH',
`/api/platform/v1/first-leg/loading-plans/${loadingPlanId}/transport?warehouseId=${encodeURIComponent(warehouseId)}`, {
containerNo, voyageNo: `P3V-${suffix}`, expectedVersion: Number(postPushPlan.version),
operationReason: `${context.marker}-bind-container-no`,
}, { 'Idempotency-Key': `${context.marker}-preload-transport`.slice(0, 128) })
const transported = context.apiSuccess(MODULE, '预装 EDITING 清单绑定真实柜号', transportResponse, '', OPTIONS)
context.assert(MODULE, '预装补号保持 loadingPlanId/containerBookingId/cabinetNo 身份稳定', transportResponse,
transported?.summary?.containerNo === containerNo && id(transported?.summary?.planId) === loadingPlanId
&& id(transported?.summary?.containerBookingId) === containerBookingId,
`containerNo=${transported?.summary?.containerNo || 'missing'}, loadingPlanId=${id(transported?.summary?.planId) || 'missing'}`, OPTIONS)
if (transported?.summary?.containerNo !== containerNo) return
const unlinkedTaskResponse = await context.request('GET',
`${pdaBase}/tasks?loadingPlanId=${encodeURIComponent(loadingPlanId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
context.addStep(MODULE, '已补号但未关联订舱仍无 PDA 任务', unlinkedTaskResponse,
context.addStep(MODULE, '无号且未关联订舱仍无 PDA 任务', unlinkedTaskResponse,
unlinkedTaskResponse.http === 200 && codeOf(unlinkedTaskResponse) === 'A0404',
`HTTP=${unlinkedTaskResponse.http}, code=${codeOf(unlinkedTaskResponse)}`, OPTIONS)
@@ -306,18 +294,18 @@ export async function run(context) {
const frozenTransportValid = transportFields.every((field) => frozenTransport[field] != null && String(frozenTransport[field]).trim() !== '')
const frozenTransportMatches = frozenTransportValid
&& transportFields.every((field) => String(frozenTransport[field]) === String(bookingDraft[field]))
const requirement = bookingConfirmed?.containerRequirements?.find?.((item) => item.containerType === '40HQ')
const requirement = bookingConfirmed?.containers?.find?.((item) => item.containerNo === containerNo)
context.assert(MODULE, 'CONFIRMED 订舱回显完整且冻结五项运输快照', bookingCreateResponse,
Boolean(bookingId) && frozenTransportMatches && Boolean(requirement?.id)
&& bookingConfirmed?.containerAllocations?.length === 0,
Boolean(bookingId) && frozenTransportMatches && Boolean(requirement?.containerBookingId)
&& bookingConfirmed?.containers?.length === 1,
`bookingId=${bookingId || 'missing'}, status=${bookingConfirmed?.oceanBooking?.status || 'missing'}`, OPTIONS)
if (!bookingId || !requirement?.id || !frozenTransportMatches) return
if (!bookingId || !requirement?.containerBookingId || !frozenTransportMatches) return
const latestContainerResponse = await context.request('GET', `/api/platform/v1/containers/${containerBookingId}`)
const latestContainer = context.apiSuccess(MODULE, '补号后读取模拟柜最新版本用于订舱关联', latestContainerResponse, '', OPTIONS)
const latestContainer = context.apiSuccess(MODULE, '合并前读取模拟柜最新版本', latestContainerResponse, '', OPTIONS)
const linkResponse = await request(context, 'POST', `/api/platform/v1/containers/${containerBookingId}/ocean-booking-link`, {
oceanBookingId: bookingId, requirementId: id(requirement.id), expectedVersion: Number(latestContainer?.version),
realContainerBookingId: id(requirement.containerBookingId), expectedVersion: Number(latestContainer?.version),
})
const linked = context.apiSuccess(MODULE, '预装补号后关联三期 CONFIRMED 订舱', linkResponse, '', OPTIONS)
const linked = context.apiSuccess(MODULE, '模拟柜原地合并三期 CONFIRMED 真实订舱柜', linkResponse, '', OPTIONS)
context.assert(MODULE, '订舱关联保持柜次计划与真实柜号身份', linkResponse,
id(linked?.id) === containerBookingId && linked?.cabinetNo === simulation.cabinetNo
&& linked?.containerNo === containerNo && id(linked?.oceanBookingId) === bookingId,
@@ -337,7 +325,8 @@ export async function run(context) {
const bookingReleased = context.apiSuccess(MODULE, '三期订舱携带已冻结运输快照并放舱', releaseResponse, '', OPTIONS)
context.assert(MODULE, '三期订舱 RELEASED 后保持模拟柜关联身份', releaseResponse,
bookingReleased?.oceanBooking?.status === 'RELEASED'
&& bookingReleased?.containerAllocations?.some?.((item) => id(item.containerBookingId) === containerBookingId)
&& bookingReleased?.containers?.some?.((item) => id(item.containerBookingId) === containerBookingId
&& item.containerNo === containerNo)
&& transportFields.every((field) => String(bookingReleased?.oceanBooking?.[field]) === String(frozenTransport[field])),
`status=${bookingReleased?.oceanBooking?.status || 'missing'}, containerBookingId=${containerBookingId}`, OPTIONS)
if (bookingReleased?.oceanBooking?.status !== 'RELEASED') return
@@ -352,7 +341,7 @@ export async function run(context) {
`/api/platform/v1/first-leg/loading-plans/${loadingPlanId}?warehouseId=${encodeURIComponent(warehouseId)}`)
const confirmable = context.apiSuccess(MODULE, '计划级确认前读取即时版本与就绪门禁', confirmableResponse, '', OPTIONS)
const confirmableVersion = Number(confirmable?.summary?.version)
context.assert(MODULE, '补号关联放舱后整份计划允许确认且 PDA 尚未创建', confirmableResponse,
context.assert(MODULE, '真实柜合并放舱后整份计划允许确认且 PDA 尚未创建', confirmableResponse,
Number.isInteger(confirmableVersion) && confirmable?.summary?.status === 'EDITING'
&& confirmable?.summary?.readiness?.canConfirm === true
&& confirmable?.summary?.readiness?.readyForPda === false