This commit is contained in:
2026-08-18 16:09:52 +08:00
parent 82ee80226e
commit fa77170304
6 changed files with 92 additions and 82 deletions
@@ -161,9 +161,10 @@ export async function run(context) {
parties: [{ partyType: 'SHIPPER', partyName: `发货方-${suffix}` },
{ partyType: 'CONSIGNEE', partyName: `收货方-${suffix}` },
{ partyType: 'NOTIFY_PARTY', partyName: `通知方-${suffix}` }],
containers: [
{ containerNo: realContainerNo, ...primaryRequirement },
{ containerNo: wrongContainerNo, containerType: '20GP', containerKind: 'FCL', capacityCbm: 33, maxWeightKg: 28000 },
containerRequirements: [
{ containerType: primaryRequirement.containerType, containerKind: primaryRequirement.containerKind, quantity: 1,
capacityCbm: primaryRequirement.capacityCbm, maxWeightKg: primaryRequirement.maxWeightKg },
{ containerType: '20GP', containerKind: 'FCL', quantity: 1, capacityCbm: 33, maxWeightKg: 28000 },
],
}
context.assert(MODULE, '订舱渠道与仓库快照完整', inboundResponse, Boolean(channelCode && channelName && warehouseName),
@@ -373,8 +374,12 @@ export async function run(context) {
const bookingCreate = await request(context, 'POST', '/api/platform/v1/ocean-bookings',
{ expectedVersion: 0, data: bookingDraft }, { 'Idempotency-Key': `${context.marker}-loading-booking-create` })
const bookingCreated = context.apiSuccess(MODULE, '登记含渠道仓库和运输快照的已确认真实订舱', bookingCreate, '', OPTIONS)
const bookingId = id(bookingCreated?.oceanBooking?.id)
const bookingCreatedDraft = context.apiSuccess(MODULE, '登记含渠道仓库和运输快照的 DRAFT 订舱', bookingCreate, '', OPTIONS)
const bookingId = id(bookingCreatedDraft?.oceanBooking?.id)
const bookingRelease = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/release`, {
expectedVersion: Number(bookingCreatedDraft?.oceanBooking?.version), reason: context.marker,
}, { 'Idempotency-Key': `${context.marker}-loading-booking-release`.slice(0, 128) })
const bookingCreated = context.apiSuccess(MODULE, '放舱订舱并冻结运输事实', bookingRelease, '', OPTIONS)
const bookingNo = id(bookingCreated?.oceanBooking?.bookingNo)
if (!bookingId) return
const confirmedBooking = bookingCreated
@@ -383,35 +388,31 @@ export async function run(context) {
const frozenTransportMatches = transportFields.every((field) => frozenTransport[field] != null
&& String(frozenTransport[field]).trim() !== ''
&& String(frozenTransport[field]) === String(bookingDraft[field]))
context.assert(MODULE, '一次登记直接返回 CONFIRMED 且尚未占用模拟柜', bookingCreate,
confirmedBooking?.oceanBooking?.status === 'CONFIRMED'
&& Array.isArray(confirmedBooking?.containers) && confirmedBooking.containers.length === 2,
`status=${confirmedBooking?.oceanBooking?.status || 'missing'}, containers=${confirmedBooking?.containers?.length ?? 'n/a'}`, OPTIONS)
context.assert(MODULE, '已确认订舱回显完整且一致的冻结运输快照', bookingCreate,
context.assert(MODULE, '订舱放舱为 RELEASED 且不预生成真实货柜', bookingRelease,
confirmedBooking?.oceanBooking?.status === 'RELEASED'
&& Array.isArray(confirmedBooking?.containerRequirements) && confirmedBooking.containerRequirements.length === 2
&& (!confirmedBooking?.containers || confirmedBooking.containers.length === 0),
`status=${confirmedBooking?.oceanBooking?.status || 'missing'}, requirements=${confirmedBooking?.containerRequirements?.length ?? 'n/a'}, containers=${confirmedBooking?.containers?.length ?? 0}`, OPTIONS)
context.assert(MODULE, 'RELEASED 订舱回显完整且一致的冻结运输快照', bookingRelease,
frozenTransportMatches,
transportFields.map((field) => `${field}=${frozenTransport[field] || 'missing'}`).join(', '), OPTIONS)
if (!frozenTransportMatches) return
const confirmedListResponse = await context.request('GET',
`/api/platform/v1/ocean-bookings?page=1&size=20&keyword=${encodeURIComponent(bookingNo)}`)
const confirmedList = context.apiSuccess(MODULE, '按真实订舱号查询正式列表', confirmedListResponse, '', OPTIONS)
context.assert(MODULE, 'CONFIRMED 后真实订舱进入正式列表', confirmedListResponse,
records(confirmedList).some((item) => id(item.id) === bookingId && item.status === 'CONFIRMED'),
context.assert(MODULE, 'RELEASED 订舱进入正式列表', confirmedListResponse,
records(confirmedList).some((item) => id(item.id) === bookingId && item.status === 'RELEASED'),
`bookingId=${bookingId}, records=${records(confirmedList).length}`, OPTIONS)
const requirement = confirmedBooking?.containers?.find?.((item) => item.containerNo === realContainerNo)
const wrongTypeRequirement = confirmedBooking?.containers?.find?.((item) => item.containerNo === wrongContainerNo)
if (!requirement?.containerBookingId || !wrongTypeRequirement?.containerBookingId) return
const wrongTypeLinkResponse = await request(context, 'POST', `${CONTAINERS}/${containerId}/ocean-booking-link`, {
realContainerBookingId: id(wrongTypeRequirement.containerBookingId), expectedVersion: Number(simulationBeforeLink.version),
})
context.addStep(MODULE, '箱型不匹配拒绝关联真实订舱柜', wrongTypeLinkResponse,
failure(wrongTypeLinkResponse), `HTTP=${wrongTypeLinkResponse.http}, code=${codeOf(wrongTypeLinkResponse)}`, OPTIONS)
const requirement = confirmedBooking?.containerRequirements?.find?.((item) => item.containerType === primaryRequirement.containerType)
if (!requirement?.id) return
const beforeLinkItems = JSON.stringify(assigned.items?.map((item) => ({ id: id(item.id), inboundId: id(item.inboundId), packageCount: Number(item.packageCount) })) || [])
const linkResponse = await request(context, 'POST', `${CONTAINERS}/${containerId}/ocean-booking-link`, {
realContainerBookingId: id(requirement.containerBookingId), expectedVersion: Number(simulationBeforeLink.version),
oceanBookingId: bookingId, requirementId: id(requirement.id), containerNo: realContainerNo,
expectedVersion: Number(simulationBeforeLink.version),
})
const linkedContainer = context.apiSuccess(MODULE, '首次关联真实已确认订舱柜', linkResponse,
const linkedContainer = context.apiSuccess(MODULE, '首次关联 RELEASED 订舱需求并补真实柜号', linkResponse,
responseEvidence(linkResponse), OPTIONS)
if (!linkedContainer) return
const afterLinkPlanResponse = await context.request('GET', `${SMART}/plans/${planId}`)
@@ -426,24 +427,23 @@ export async function run(context) {
const activeContainersResponse = await context.request('GET',
`${CONTAINERS}?page=1&size=50&keyword=${encodeURIComponent(realContainerNo)}`)
const activeContainers = context.apiSuccess(MODULE, '合并后按真实柜号回查正常货柜列表', activeContainersResponse, '', OPTIONS)
const activeContainers = context.apiSuccess(MODULE, '关联后按真实柜号回查正常货柜列表', activeContainersResponse, '', OPTIONS)
const activeRealRows = records(activeContainers).filter((item) => item.containerNo === realContainerNo)
context.assert(MODULE, '合并后列表仅保留模拟柜原地转成的一条 ACTIVE 操作身份', activeContainersResponse,
activeRealRows.length === 1 && id(activeRealRows[0]?.id) === containerId
&& !records(activeContainers).some((item) => id(item?.id) === id(requirement.containerBookingId)),
`activeIds=${activeRealRows.map((item) => id(item?.id)).join('|') || 'none'}, mergedSourceId=${id(requirement.containerBookingId)}`, OPTIONS)
context.assert(MODULE, '关联后列表仅保留模拟柜原地补全的一条 ACTIVE 操作身份', activeContainersResponse,
activeRealRows.length === 1 && id(activeRealRows[0]?.id) === containerId,
`activeIds=${activeRealRows.map((item) => id(item?.id)).join('|') || 'none'}`, OPTIONS)
const containerGaps = containerFieldGaps(activeRealRows[0], { requireContainerNo: true })
context.assert(MODULE, '货柜列表关键展示字段来自正式柜次快照且格式完整', activeContainersResponse,
activeRealRows.length === 1 && containerGaps.length === 0,
`missing=${containerGaps.join(',') || 'none'}`, OPTIONS)
const mergedBookingResponse = await context.request('GET', `/api/platform/v1/ocean-bookings/${bookingId}`)
const mergedBooking = context.apiSuccess(MODULE, '合并后回查订舱真实柜当前操作身份', mergedBookingResponse, '', OPTIONS)
const mergedBooking = context.apiSuccess(MODULE, '关联后回查订舱 allocation 当前操作身份', mergedBookingResponse, '', OPTIONS)
const mergedContainer = mergedBooking?.containers?.find?.((item) => item.containerNo === realContainerNo)
context.assert(MODULE, '订舱真实柜 allocation 已原子改指稳定模拟柜身份', mergedBookingResponse,
context.assert(MODULE, '订舱 allocation 原子创建并指向稳定模拟柜身份', mergedBookingResponse,
id(mergedContainer?.containerBookingId) === containerId
&& id(mergedContainer?.requirementId) === id(requirement.requirementId)
&& id(mergedContainer?.allocationId) === id(requirement.allocationId),
&& id(mergedContainer?.requirementId) === id(requirement.id)
&& Boolean(mergedContainer?.allocationId),
`containerBookingId=${id(mergedContainer?.containerBookingId) || 'missing'}, requirementId=${id(mergedContainer?.requirementId) || 'missing'}, allocationId=${id(mergedContainer?.allocationId) || 'missing'}`, OPTIONS)
const duplicateSimulationResponse = await request(context, 'POST', CONTAINERS, {
@@ -452,9 +452,10 @@ export async function run(context) {
})
const duplicateSimulation = context.apiSuccess(MODULE, '新增重复容量占用负向模拟柜', duplicateSimulationResponse, '', OPTIONS)
const duplicateLinkResponse = await request(context, 'POST', `${CONTAINERS}/${id(duplicateSimulation?.id)}/ocean-booking-link`, {
realContainerBookingId: id(requirement.containerBookingId), expectedVersion: Number(duplicateSimulation?.version),
oceanBookingId: bookingId, requirementId: id(requirement.id), containerNo: realContainerNo,
expectedVersion: Number(duplicateSimulation?.version),
})
context.addStep(MODULE, '已消费 MERGED 真实柜拒绝再次关联', duplicateLinkResponse,
context.addStep(MODULE, '已消费订舱需求拒绝再次关联', duplicateLinkResponse,
failure(duplicateLinkResponse), `HTTP=${duplicateLinkResponse.http}, code=${codeOf(duplicateLinkResponse)}`, OPTIONS)
const unlinkAfterLoadingResponse = await request(context, 'DELETE', `${CONTAINERS}/${containerId}/ocean-booking-link`, {