test: sequence terminal drayage around unloading
This commit is contained in:
@@ -20,22 +20,15 @@ function blocker(context, name, evidence, response = null) {
|
||||
context.addStep(MODULE, name, response, false, `${evidence}; ${trace(response)}`, OPTIONS)
|
||||
context.addCoverageGap(MODULE, SCENARIO, name, evidence, 'fixture', { deploymentBlocker: true })
|
||||
}
|
||||
function fixture(context) {
|
||||
const matrix = context.createdResources?.phase4InboundProducerFixtures
|
||||
function completedTarget(context) {
|
||||
const prepared = context.createdResources?.phase5TerminalDrayage?.prepared
|
||||
const pda = context.createdResources?.phase4OverseasPda
|
||||
if (matrix?.marker !== context.marker || pda?.marker !== context.marker
|
||||
if (prepared?.marker !== context.marker || pda?.marker !== context.marker
|
||||
|| pda?.finalStatus !== 'COMPLETED') return null
|
||||
const producers = Array.isArray(matrix.containers) ? matrix.containers : []
|
||||
for (const completed of pda.completedContainerFacts || []) {
|
||||
const producer = producers.find((item) => id(item?.containerBookingId) === id(completed?.containerBookingId)
|
||||
&& id(item?.containerNo) === id(completed?.containerNo)
|
||||
&& item?.shippingReady === true && id(item?.carrierScheduleQueryId)
|
||||
&& item?.carrierLatestActualAt)
|
||||
if (producer) return {
|
||||
matrix, producer, pda, completed, containerBookingId: id(producer.containerBookingId),
|
||||
}
|
||||
}
|
||||
return null
|
||||
const completed = (pda.completedContainerFacts || []).find((item) =>
|
||||
id(item?.containerBookingId) === id(prepared.containerBookingId)
|
||||
&& id(item?.containerNo) === id(prepared.containerNo))
|
||||
return completed ? { ...prepared, pda, completed } : null
|
||||
}
|
||||
async function command(context, target, action, path, body) {
|
||||
const key = `${context.marker}-terminal-${path}`.replace(/[^A-Za-z0-9:_-]/g, '').slice(0, 128)
|
||||
@@ -58,32 +51,37 @@ async function command(context, target, action, path, body) {
|
||||
return first
|
||||
}
|
||||
|
||||
export async function run(context) {
|
||||
context.createdResources.phase5TerminalDrayage = {
|
||||
export async function prepareTerminalDrayage(context, producer) {
|
||||
const matrix = context.createdResources?.phase4InboundProducerFixtures
|
||||
if (matrix?.marker !== context.marker || !producer || producer?.shippingReady !== true
|
||||
|| !id(producer?.containerBookingId) || !id(producer?.containerNo)
|
||||
|| !id(producer?.carrierScheduleQueryId) || !producer?.carrierLatestActualAt) {
|
||||
blocker(context, '取得本轮PICKUP_READY货柜执行码头提柜前置',
|
||||
`matrix=${matrix?.marker || 'missing'}, container=${id(producer?.containerNo) || 'missing'}; 禁止借历史货柜`)
|
||||
return null
|
||||
}
|
||||
const resource = context.createdResources.phase5TerminalDrayage || {
|
||||
marker: context.marker, containerBookingId: null, containerNo: null, finalStatus: null,
|
||||
cleanup: 'Terminal drayage ends at immutable EMPTY_RETURNED and is retained as acceptance evidence',
|
||||
}
|
||||
const target = fixture(context)
|
||||
if (!target) {
|
||||
blocker(context, '取得本轮PICKUP_READY且已卸柜货柜',
|
||||
`matrix=${context.createdResources?.phase4InboundProducerFixtures?.marker || 'missing'}, shippingReady=${context.createdResources?.phase4InboundProducerFixtures?.containers?.filter((item) => item?.shippingReady === true).length ?? 0}, pda=${context.createdResources?.phase4OverseasPda?.finalStatus || 'missing'}; 禁止借历史货柜`)
|
||||
return
|
||||
}
|
||||
context.createdResources.phase5TerminalDrayage = resource
|
||||
const target = { marker: context.marker, producer, containerBookingId: id(producer.containerBookingId),
|
||||
containerNo: id(producer.containerNo) }
|
||||
const path = `${BASE}/${encodeURIComponent(target.containerBookingId)}`
|
||||
const initial = await api(context, 'GET', '读取目的港码头拖车初始聚合', path)
|
||||
context.assert(MODULE, '初始聚合绑定本轮货柜且允许选择车队', initial.response,
|
||||
const initial = await api(context, 'GET', '卸柜前读取目的港码头拖车初始聚合', path)
|
||||
context.assert(MODULE, 'PICKUP_READY货柜允许选择车队', initial.response,
|
||||
id(initial.data?.containerBookingId) === target.containerBookingId
|
||||
&& id(initial.data?.containerNo) === id(target.completed.containerNo)
|
||||
&& id(initial.data?.containerNo) === target.containerNo
|
||||
&& Number(initial.data?.version) === 0 && initial.data?.allowedActions?.includes('SELECT_FLEET'),
|
||||
`status=${initial.data?.status || 'missing'}, actions=${initial.data?.allowedActions?.join(',') || 'missing'}; ${trace(initial.response)}`, OPTIONS)
|
||||
if (!success(initial.response)) return
|
||||
if (!success(initial.response) || !initial.data?.allowedActions?.includes('SELECT_FLEET')) return null
|
||||
|
||||
const suppliers = await api(context, 'GET', '查询ACTIVE码头拖车供应商',
|
||||
'/api/platform/v1/suppliers?tenantId=0&category=TRUCKING&status=ACTIVE&page=1&size=100')
|
||||
const fleet = suppliers.data?.records?.find((item) => id(item?.id) && item?.status === 'ACTIVE')
|
||||
if (!fleet) {
|
||||
blocker(context, '取得有效码头拖车供应商', '没有可复用的ACTIVE + TRUCKING公共供应商;脚本不临时创建主数据', suppliers.response)
|
||||
return
|
||||
return null
|
||||
}
|
||||
const selected = await command(context, target, '选择车队', 'fleet', {
|
||||
expectedVersion: Number(initial.data.version), fleetSupplierId: id(fleet.id),
|
||||
@@ -92,9 +90,9 @@ export async function run(context) {
|
||||
context.assert(MODULE, '选车队后只绑定公共供应商并开放预约', selected.response,
|
||||
id(selected.data?.fleetSupplierId) === id(fleet.id) && selected.data?.fleetSupplierName === fleet.name
|
||||
&& selected.data?.allowedActions?.includes('SCHEDULE_PICKUP'), trace(selected.response), OPTIONS)
|
||||
if (!success(selected.response)) return
|
||||
if (!success(selected.response)) return null
|
||||
|
||||
const latestActual = Date.parse(`${target.producer.carrierLatestActualAt}Z`)
|
||||
const latestActual = Date.parse(`${producer.carrierLatestActualAt}Z`)
|
||||
const appointmentAt = localIso(new Date((Number.isNaN(latestActual) ? Date.now() : latestActual) + 60 * 60_000))
|
||||
const scheduled = await command(context, target, '预约提柜', 'pickup-appointment', {
|
||||
expectedVersion: Number(selected.data.version), pickupAppointmentAt: appointmentAt, reason: context.marker,
|
||||
@@ -103,22 +101,51 @@ export async function run(context) {
|
||||
scheduled.data?.pickupAppointmentAt === appointmentAt
|
||||
&& Number(scheduled.data?.version) === Number(selected.data.version) + 1,
|
||||
trace(scheduled.response), OPTIONS)
|
||||
if (!success(scheduled.response)) return
|
||||
if (!success(scheduled.response)) return null
|
||||
|
||||
const pickupAt = localIso(new Date(Date.parse(`${appointmentAt}Z`) + 60 * 60_000))
|
||||
const picked = await command(context, target, '确认实际提柜', 'pickup', {
|
||||
expectedVersion: Number(scheduled.data.version), actualAt: pickupAt, remark: context.marker,
|
||||
})
|
||||
context.assert(MODULE, '实际提柜同步PICKED_UP事实并在已卸柜聚合开放还柜', picked.response,
|
||||
picked.data?.status === 'UNLOADED' && picked.data?.actualPickupAt === pickupAt
|
||||
&& picked.data?.allowedActions?.includes('RECORD_EMPTY_RETURN'), trace(picked.response), OPTIONS)
|
||||
if (!success(picked.response)) return
|
||||
context.assert(MODULE, '实际提柜同步PICKED_UP并等待海外仓卸柜', picked.response,
|
||||
picked.data?.status === 'PICKED_UP' && picked.data?.actualPickupAt === pickupAt,
|
||||
trace(picked.response), OPTIONS)
|
||||
if (!success(picked.response) || picked.data?.status !== 'PICKED_UP') return null
|
||||
resource.prepared = { ...target, appointmentAt, pickupAt, pickedVersion: Number(picked.data.version) }
|
||||
resource.containerBookingId = target.containerBookingId
|
||||
resource.containerNo = target.containerNo
|
||||
return resource.prepared
|
||||
}
|
||||
|
||||
const unloadingAt = Date.parse(`${picked.data?.unloadingCompletedAt}Z`)
|
||||
const returnBase = Math.max(Date.parse(`${pickupAt}Z`), Number.isNaN(unloadingAt) ? 0 : unloadingAt)
|
||||
export async function run(context) {
|
||||
const resource = context.createdResources.phase5TerminalDrayage || {
|
||||
marker: context.marker, containerBookingId: null, containerNo: null, finalStatus: null,
|
||||
cleanup: 'Terminal drayage ends at immutable EMPTY_RETURNED and is retained as acceptance evidence',
|
||||
}
|
||||
context.createdResources.phase5TerminalDrayage = resource
|
||||
const target = completedTarget(context)
|
||||
if (!target) {
|
||||
blocker(context, '取得本轮已提柜且已卸柜货柜',
|
||||
`prepared=${resource.prepared?.marker || 'missing'}, pda=${context.createdResources?.phase4OverseasPda?.finalStatus || 'missing'}; 禁止借历史货柜`)
|
||||
return
|
||||
}
|
||||
const path = `${BASE}/${encodeURIComponent(target.containerBookingId)}`
|
||||
const initial = await api(context, 'GET', '卸柜后读取目的港码头拖车聚合', path)
|
||||
context.assert(MODULE, '卸柜聚合保留提柜事实并开放还柜', initial.response,
|
||||
id(initial.data?.containerBookingId) === target.containerBookingId
|
||||
&& id(initial.data?.containerNo) === id(target.completed.containerNo)
|
||||
&& Number(initial.data?.version) === Number(target.pickedVersion)
|
||||
&& initial.data?.status === 'UNLOADED'
|
||||
&& initial.data?.actualPickupAt === target.pickupAt
|
||||
&& initial.data?.allowedActions?.includes('RECORD_EMPTY_RETURN'),
|
||||
`status=${initial.data?.status || 'missing'}, actions=${initial.data?.allowedActions?.join(',') || 'missing'}; ${trace(initial.response)}`, OPTIONS)
|
||||
if (!success(initial.response)) return
|
||||
|
||||
const unloadingAt = Date.parse(`${initial.data?.unloadingCompletedAt}Z`)
|
||||
const returnBase = Math.max(Date.parse(`${target.pickupAt}Z`), Number.isNaN(unloadingAt) ? 0 : unloadingAt)
|
||||
const returnedAt = localIso(new Date(returnBase + 60 * 60_000))
|
||||
const returned = await command(context, target, '确认实际还柜', 'empty-return', {
|
||||
expectedVersion: Number(picked.data.version), actualAt: returnedAt, remark: context.marker,
|
||||
expectedVersion: Number(initial.data.version), actualAt: returnedAt, remark: context.marker,
|
||||
})
|
||||
context.assert(MODULE, '实际还柜同步EMPTY_RETURNED并关闭全部动作', returned.response,
|
||||
returned.data?.status === 'EMPTY_RETURNED' && returned.data?.actualEmptyReturnAt === returnedAt
|
||||
|
||||
Reference in New Issue
Block a user