test(firstleg): cover simulated container booking links
This commit is contained in:
@@ -108,6 +108,7 @@ export async function run(context) {
|
||||
OPTIONS)
|
||||
if (!selectedChannel) return
|
||||
const channelName = selectedChannel.channelName
|
||||
const primaryRequirement = { containerType: '40HQ', containerKind: 'FCL', capacityCbm: 100, maxWeightKg: 100000, quantity: 1 }
|
||||
const bookingDraft = {
|
||||
customerId: tenantId, customerName: inbound?.summary?.customerName || `验收客户-${tenantId}`,
|
||||
channelCode, warehouseId, warehouseName,
|
||||
@@ -115,31 +116,27 @@ export async function run(context) {
|
||||
bookingAgentId: null, bookingAgentName: `验收代理-${suffix}`,
|
||||
departurePortCode: 'CNSZX', departurePortName: '深圳',
|
||||
destinationPortCode: 'USLAX', destinationPortName: '洛杉矶', placeOfDelivery: 'LAX',
|
||||
serviceTerm: 'CY/CY', freightTerm: 'PREPAID', remark: context.marker,
|
||||
serviceTerm: 'CY-CY', freightTerm: 'NINGBO', remark: context.marker,
|
||||
parties: [{ partyType: 'SHIPPER', partyName: `发货方-${suffix}` },
|
||||
{ partyType: 'CONSIGNEE', partyName: `收货方-${suffix}` },
|
||||
{ partyType: 'NOTIFY_PARTY', partyName: `通知方-${suffix}` }],
|
||||
containerRequirements: [{ containerType: '40HQ', containerKind: 'FCL', capacityCbm: 100, maxWeightKg: 100000, quantity: 1 }],
|
||||
containerRequirements: [primaryRequirement,
|
||||
{ containerType: '20GP', containerKind: 'FCL', capacityCbm: 28, maxWeightKg: 22000, quantity: 1 }],
|
||||
}
|
||||
context.assert(MODULE, '订舱渠道与仓库快照完整', inboundResponse, Boolean(channelCode && channelName && warehouseName),
|
||||
`channelCode=${channelCode || 'missing'}, warehouseId=${warehouseId}, warehouseName=${warehouseName || 'missing'}`, OPTIONS)
|
||||
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)
|
||||
if (!bookingId) return
|
||||
const bookingVersion = Number(bookingCreated.oceanBooking.version)
|
||||
const submit = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/submit`,
|
||||
{ expectedVersion: bookingVersion, reason: context.marker }, { 'Idempotency-Key': `${context.marker}-loading-booking-submit` })
|
||||
const submitted = context.apiSuccess(MODULE, '提交隔离订舱', submit, '', OPTIONS)
|
||||
const confirm = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/confirm`,
|
||||
{ expectedVersion: Number(submitted?.oceanBooking?.version), reason: context.marker }, { 'Idempotency-Key': `${context.marker}-loading-booking-confirm` })
|
||||
const confirmedBooking = context.apiSuccess(MODULE, '确认订舱并自动生成真实柜次', confirm, '', OPTIONS)
|
||||
const allocation = confirmedBooking?.containerAllocations?.[0]
|
||||
const containerId = id(allocation?.containerBookingId)
|
||||
context.assert(MODULE, '确认响应包含自动生成的真实柜次', confirm,
|
||||
Boolean(containerId) && id(confirmedBooking?.oceanBooking?.id) === bookingId,
|
||||
`bookingId=${bookingId}, containerBookingId=${containerId || 'missing'}`, OPTIONS)
|
||||
const simulationResponse = await request(context, 'POST', CONTAINERS, {
|
||||
channelCode, warehouseId, containerType: primaryRequirement.containerType,
|
||||
capacityCbm: primaryRequirement.capacityCbm, maxWeightKg: primaryRequirement.maxWeightKg,
|
||||
})
|
||||
const simulation = context.apiSuccess(MODULE, '新增内部模拟柜', simulationResponse,
|
||||
`warehouseId=${warehouseId}, channelCode=${channelCode}`, OPTIONS)
|
||||
const containerId = id(simulation?.id)
|
||||
context.assert(MODULE, '模拟柜由服务端生成稳定身份且不伪造真实柜号或订舱', simulationResponse,
|
||||
Boolean(containerId) && Boolean(simulation?.cabinetNo) && simulation?.containerNo == null
|
||||
&& simulation?.oceanBookingId == null && simulation?.channelCode === channelCode
|
||||
&& id(simulation?.warehouseId) === warehouseId,
|
||||
`containerBookingId=${containerId || 'missing'}, cabinetNo=${simulation?.cabinetNo || 'missing'}, oceanBookingId=${id(simulation?.oceanBookingId) || 'null'}`, OPTIONS)
|
||||
if (!containerId) return
|
||||
|
||||
const candidateResponse = await context.request('GET', `${SMART}/candidates?bookingId=${encodeURIComponent(containerId)}&warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
@@ -160,22 +157,12 @@ export async function run(context) {
|
||||
`${channelIsolation.reason}; originWarehouseId=${warehouseId}, primaryChannelCode=${channelCode}`, OPTIONS)
|
||||
} else {
|
||||
const foreignChannel = channelIsolation.pair.foreign
|
||||
const foreignBookingCreate = await request(context, 'POST', '/api/platform/v1/ocean-bookings', {
|
||||
expectedVersion: 0,
|
||||
data: { ...bookingDraft, channelCode: foreignChannel.channelCode, remark: `${context.marker}-other-channel` },
|
||||
}, { 'Idempotency-Key': `${context.marker}-other-channel-booking-create`.slice(0, 128) })
|
||||
const foreignBookingCreated = context.apiSuccess(MODULE, '创建真实异渠道隔离订舱', foreignBookingCreate, '', OPTIONS)
|
||||
const foreignBookingId = id(foreignBookingCreated?.oceanBooking?.id)
|
||||
if (!foreignBookingId) return
|
||||
const foreignSubmit = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${foreignBookingId}/submit`, {
|
||||
expectedVersion: Number(foreignBookingCreated?.oceanBooking?.version), reason: context.marker,
|
||||
}, { 'Idempotency-Key': `${context.marker}-other-channel-booking-submit`.slice(0, 128) })
|
||||
const foreignSubmitted = context.apiSuccess(MODULE, '提交真实异渠道隔离订舱', foreignSubmit, '', OPTIONS)
|
||||
const foreignConfirm = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${foreignBookingId}/confirm`, {
|
||||
expectedVersion: Number(foreignSubmitted?.oceanBooking?.version), reason: context.marker,
|
||||
}, { 'Idempotency-Key': `${context.marker}-other-channel-booking-confirm`.slice(0, 128) })
|
||||
const foreignConfirmed = context.apiSuccess(MODULE, '确认真实异渠道订舱并自动生成柜次', foreignConfirm, '', OPTIONS)
|
||||
const foreignContainerId = id(foreignConfirmed?.containerAllocations?.[0]?.containerBookingId)
|
||||
const foreignContainerResponse = await request(context, 'POST', CONTAINERS, {
|
||||
channelCode: foreignChannel.channelCode, warehouseId, containerType: primaryRequirement.containerType,
|
||||
capacityCbm: primaryRequirement.capacityCbm, maxWeightKg: primaryRequirement.maxWeightKg,
|
||||
})
|
||||
const foreignContainer = context.apiSuccess(MODULE, '新增真实异渠道模拟柜', foreignContainerResponse, '', OPTIONS)
|
||||
const foreignContainerId = id(foreignContainer?.id)
|
||||
if (!foreignContainerId) return
|
||||
const foreignPlanResponse = await request(context, 'POST', `${SMART}/plans`, {
|
||||
bookingId: foreignContainerId, remark: `${context.marker}-other-channel`,
|
||||
@@ -240,6 +227,108 @@ export async function run(context) {
|
||||
staleAssignResponse.http === 200 && ['A0409', '409'].includes(codeOf(staleAssignResponse)),
|
||||
`HTTP=${staleAssignResponse.http}, code=${codeOf(staleAssignResponse)}`, OPTIONS)
|
||||
|
||||
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 bookingNo = id(bookingCreated?.oceanBooking?.bookingNo)
|
||||
if (!bookingId) return
|
||||
const hiddenDraftResponse = await context.request('GET',
|
||||
`/api/platform/v1/ocean-bookings?page=1&size=20&keyword=${encodeURIComponent(bookingNo)}`)
|
||||
const hiddenDraftPage = context.apiSuccess(MODULE, '正式订舱列表查询不含草稿', hiddenDraftResponse, '', OPTIONS)
|
||||
context.assert(MODULE, '真实订舱确认前不进入正式列表', hiddenDraftResponse,
|
||||
!records(hiddenDraftPage).some((item) => id(item.id) === bookingId),
|
||||
`bookingId=${bookingId}, records=${records(hiddenDraftPage).length}`, OPTIONS)
|
||||
const draftFilterResponse = await request(context, 'GET', '/api/platform/v1/ocean-bookings?page=1&size=5&status=DRAFT')
|
||||
context.addStep(MODULE, '正式订舱列表拒绝 DRAFT 状态筛选', draftFilterResponse,
|
||||
failure(draftFilterResponse), `HTTP=${draftFilterResponse.http}, code=${codeOf(draftFilterResponse)}`, OPTIONS)
|
||||
|
||||
const submit = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/submit`,
|
||||
{ expectedVersion: Number(bookingCreated.oceanBooking.version), reason: context.marker },
|
||||
{ 'Idempotency-Key': `${context.marker}-loading-booking-submit` })
|
||||
const submitted = context.apiSuccess(MODULE, '提交隔离订舱', submit, '', OPTIONS)
|
||||
const submittedFilterResponse = await request(context, 'GET', '/api/platform/v1/ocean-bookings?page=1&size=5&status=SUBMITTED')
|
||||
context.addStep(MODULE, '正式订舱列表拒绝 SUBMITTED 状态筛选', submittedFilterResponse,
|
||||
failure(submittedFilterResponse), `HTTP=${submittedFilterResponse.http}, code=${codeOf(submittedFilterResponse)}`, OPTIONS)
|
||||
const confirm = await request(context, 'POST', `/api/platform/v1/ocean-bookings/${bookingId}/confirm`,
|
||||
{ expectedVersion: Number(submitted?.oceanBooking?.version), reason: context.marker },
|
||||
{ 'Idempotency-Key': `${context.marker}-loading-booking-confirm` })
|
||||
const confirmedBooking = context.apiSuccess(MODULE, '确认真实订舱但不自动创建内部柜', confirm, '', OPTIONS)
|
||||
context.assert(MODULE, 'CONFIRMED 只产生真实订舱事实且尚未占用模拟柜', confirm,
|
||||
confirmedBooking?.oceanBooking?.status === 'CONFIRMED'
|
||||
&& Array.isArray(confirmedBooking?.containerAllocations) && confirmedBooking.containerAllocations.length === 0,
|
||||
`status=${confirmedBooking?.oceanBooking?.status || 'missing'}, allocations=${confirmedBooking?.containerAllocations?.length ?? 'n/a'}`, OPTIONS)
|
||||
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'),
|
||||
`bookingId=${bookingId}, records=${records(confirmedList).length}`, OPTIONS)
|
||||
|
||||
const requirement = confirmedBooking?.containerRequirements?.find?.((item) => item.containerType === primaryRequirement.containerType)
|
||||
const wrongTypeRequirement = confirmedBooking?.containerRequirements?.find?.((item) => item.containerType !== primaryRequirement.containerType)
|
||||
if (!requirement?.id || !wrongTypeRequirement?.id) return
|
||||
const wrongTypeLinkResponse = await request(context, 'POST', `${CONTAINERS}/${containerId}/ocean-booking-link`, {
|
||||
oceanBookingId: bookingId, requirementId: id(wrongTypeRequirement.id), expectedVersion: Number(containerDetail.version),
|
||||
})
|
||||
context.addStep(MODULE, '箱型不匹配拒绝关联真实订舱柜', wrongTypeLinkResponse,
|
||||
failure(wrongTypeLinkResponse), `HTTP=${wrongTypeLinkResponse.http}, code=${codeOf(wrongTypeLinkResponse)}`, OPTIONS)
|
||||
|
||||
const emptySimulationResponse = await request(context, 'POST', CONTAINERS, {
|
||||
channelCode, warehouseId, containerType: primaryRequirement.containerType,
|
||||
capacityCbm: primaryRequirement.capacityCbm, maxWeightKg: primaryRequirement.maxWeightKg,
|
||||
})
|
||||
let emptySimulation = context.apiSuccess(MODULE, '新增空模拟柜验证解绑换绑', emptySimulationResponse, '', OPTIONS)
|
||||
const emptyContainerId = id(emptySimulation?.id)
|
||||
if (!emptyContainerId) return
|
||||
const emptyLink = await request(context, 'POST', `${CONTAINERS}/${emptyContainerId}/ocean-booking-link`, {
|
||||
oceanBookingId: bookingId, requirementId: id(requirement.id), expectedVersion: Number(emptySimulation.version),
|
||||
})
|
||||
emptySimulation = context.apiSuccess(MODULE, '空模拟柜首次关联真实订舱', emptyLink, '', OPTIONS)
|
||||
const emptyUnlink = await request(context, 'DELETE', `${CONTAINERS}/${emptyContainerId}/ocean-booking-link`, {
|
||||
expectedVersion: Number(emptySimulation.version), reason: context.marker,
|
||||
})
|
||||
emptySimulation = context.apiSuccess(MODULE, '空模拟柜允许解除关联', emptyUnlink, '', OPTIONS)
|
||||
const emptyRelink = await request(context, 'POST', `${CONTAINERS}/${emptyContainerId}/ocean-booking-link`, {
|
||||
oceanBookingId: bookingId, requirementId: id(requirement.id), expectedVersion: Number(emptySimulation.version),
|
||||
})
|
||||
emptySimulation = context.apiSuccess(MODULE, '空模拟柜解绑后允许重新关联', emptyRelink, '', OPTIONS)
|
||||
const emptyFinalUnlink = await request(context, 'DELETE', `${CONTAINERS}/${emptyContainerId}/ocean-booking-link`, {
|
||||
expectedVersion: Number(emptySimulation.version), reason: `${context.marker}-release-capacity`,
|
||||
})
|
||||
context.apiSuccess(MODULE, '空模拟柜最终解绑释放真实订舱容量', emptyFinalUnlink, '', OPTIONS)
|
||||
|
||||
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`, {
|
||||
oceanBookingId: bookingId, requirementId: id(requirement.id), expectedVersion: Number(containerDetail.version),
|
||||
})
|
||||
const linkedContainer = context.apiSuccess(MODULE, '首次关联真实已确认订舱柜', linkResponse, '', OPTIONS)
|
||||
const afterLinkPlanResponse = await context.request('GET', `${SMART}/plans/${planId}`)
|
||||
const afterLinkPlan = context.apiSuccess(MODULE, '关联后回查原装柜计划与明细', afterLinkPlanResponse, '', OPTIONS)
|
||||
const afterLinkItems = JSON.stringify(afterLinkPlan?.items?.map((item) => ({ id: id(item.id), inboundId: id(item.inboundId), packageCount: Number(item.packageCount) })) || [])
|
||||
context.assert(MODULE, '关联前后内部柜次、计划和排货明细身份稳定', linkResponse,
|
||||
id(linkedContainer?.id) === containerId && linkedContainer?.cabinetNo === simulation.cabinetNo
|
||||
&& id(linkedContainer?.oceanBookingId) === bookingId && id(afterLinkPlan?.id) === planId
|
||||
&& beforeLinkItems === afterLinkItems,
|
||||
`containerBookingId=${containerId}, cabinetNo=${linkedContainer?.cabinetNo || 'missing'}, loadingPlanId=${id(afterLinkPlan?.id) || 'missing'}`, OPTIONS)
|
||||
|
||||
const duplicateSimulationResponse = await request(context, 'POST', CONTAINERS, {
|
||||
channelCode, warehouseId, containerType: primaryRequirement.containerType,
|
||||
capacityCbm: primaryRequirement.capacityCbm, maxWeightKg: primaryRequirement.maxWeightKg,
|
||||
})
|
||||
const duplicateSimulation = context.apiSuccess(MODULE, '新增重复容量占用负向模拟柜', duplicateSimulationResponse, '', OPTIONS)
|
||||
const duplicateLinkResponse = await request(context, 'POST', `${CONTAINERS}/${id(duplicateSimulation?.id)}/ocean-booking-link`, {
|
||||
oceanBookingId: bookingId, requirementId: id(requirement.id), expectedVersion: Number(duplicateSimulation?.version),
|
||||
})
|
||||
context.addStep(MODULE, '同一真实订舱箱型容量拒绝重复关联', duplicateLinkResponse,
|
||||
failure(duplicateLinkResponse), `HTTP=${duplicateLinkResponse.http}, code=${codeOf(duplicateLinkResponse)}`, OPTIONS)
|
||||
|
||||
const unlinkAfterLoadingResponse = await request(context, 'DELETE', `${CONTAINERS}/${containerId}/ocean-booking-link`, {
|
||||
expectedVersion: Number(linkedContainer.version), reason: context.marker,
|
||||
})
|
||||
context.addStep(MODULE, '已有排货后拒绝解绑或换绑', unlinkAfterLoadingResponse,
|
||||
failure(unlinkAfterLoadingResponse), `HTTP=${unlinkAfterLoadingResponse.http}, code=${codeOf(unlinkAfterLoadingResponse)}`, OPTIONS)
|
||||
|
||||
const pushKey = `${context.marker}-container-push`.slice(0, 128)
|
||||
const pushResponse = await request(context, 'POST', `${SMART}/plans/${planId}/push-preload`,
|
||||
{ expectedVersion: Number(assigned.version) }, { 'Idempotency-Key': pushKey })
|
||||
|
||||
Reference in New Issue
Block a user