优化
This commit is contained in:
@@ -3,7 +3,6 @@ import { truckAppointmentFieldGaps } from './fulfillment-list-field-contract.mjs
|
||||
const MODULE = 'truck-appointments'
|
||||
const OPTIONS = { scenario: 'truck-appointment-lifecycle', deploymentBlocker: true }
|
||||
const BASE = '/api/platform/v1/truck-appointments'
|
||||
const OCEAN_BOOKING_BASE = '/api/platform/v1/ocean-bookings'
|
||||
const SUPPLIER_BASE = '/api/platform/v1/suppliers?tenantId=0'
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
@@ -69,17 +68,28 @@ export function inspectOceanBookingIdentity(marker, resource, detail = null) {
|
||||
: { identity, reason: null, missingFields: [] }
|
||||
}
|
||||
|
||||
export function truckAppointmentPayload(context, suffix, identity, version = 0) {
|
||||
export function selectIndependentTruckCustomer(context) {
|
||||
const candidates = [
|
||||
context.createdResources?.orders,
|
||||
context.createdResources?.merchantQualificationFixture,
|
||||
context.createdResources?.phase3Loading,
|
||||
context.createdResources?.phase3ContainerLoading,
|
||||
].filter(Boolean)
|
||||
const source = candidates.find((item) => item.marker === context.marker && id(item.tenantId))
|
||||
return source ? { customerId: id(source.tenantId), customerName: source.customerName || null } : null
|
||||
}
|
||||
|
||||
export function truckAppointmentPayload(context, suffix, identity, version = 0, associations = {}) {
|
||||
const supplier = context.createdResources?.truckSupplier || {}
|
||||
const supplierId = id(supplier.supplierId)
|
||||
const supplierName = id(supplier.supplierName)
|
||||
const carrierSnapshot = supplierId && supplierName
|
||||
? { carrierId: supplierId, carrierName: supplierName }
|
||||
: {}
|
||||
return {
|
||||
outboundOrderId: null, oceanBookingId: identity.oceanBookingId, containerBookingId: null, loadingPlanId: null,
|
||||
warehouseId: identity.warehouseId, customerId: identity.customerId, customerName: identity.customerName,
|
||||
...carrierSnapshot, fleetContact: '车队联系人', fleetPhone: '13800000004',
|
||||
outboundOrderId: null, oceanBookingId: associations.oceanBookingId || null,
|
||||
containerBookingId: associations.containerBookingId || null,
|
||||
loadingPlanId: associations.loadingPlanId || null, warehouseId: associations.warehouseId || null,
|
||||
customerId: identity.customerId, customerName: identity.customerName || null,
|
||||
carrierId: supplierId || null, carrierName: supplierName || null,
|
||||
fleetContact: '车队联系人', fleetPhone: '13800000004',
|
||||
driverName: '司机验收', driverPhone: '13800000005', driverIdCard: null, vehicleNo: `粤B-${suffix.slice(-6)}`,
|
||||
storeName: `门店-${suffix}`, storeContact: '门店联系人', storePhone: '13800000006', storeAddress: '深圳市南山区验收地址',
|
||||
storeCity: '深圳市', storeZipcode: '518000', packageCount: 10, grossWeightKg: 100, volumeCbm: 2.5,
|
||||
@@ -103,14 +113,15 @@ async function ensureTruckSupplier(context, suffix) {
|
||||
const detailResponse = await request(context, 'GET', `/api/platform/v1/suppliers/${encodeURIComponent(supplierId)}?tenantId=0`)
|
||||
supplier = data(detailResponse)
|
||||
}
|
||||
context.addStep(MODULE, '创建本轮有效拖车供应商(可选)', response,
|
||||
!supplierId || (supplier?.category === 'TRUCKING' && supplier?.status === 'ACTIVE' && Boolean(supplier?.name)),
|
||||
context.addStep(MODULE, '创建本轮有效 ACTIVE TRUCKING 拖车供应商', response,
|
||||
Boolean(supplierId) && supplier?.category === 'TRUCKING' && supplier?.status === 'ACTIVE' && Boolean(supplier?.name),
|
||||
`supplierId=${supplierId || 'missing'}, category=${supplier?.category || 'missing'}, status=${supplier?.status || 'missing'}`, OPTIONS)
|
||||
if (supplierId) context.createdResources.truckSupplier = {
|
||||
marker: context.marker, supplierId, supplierName: supplier.name || supplier.supplierName || null,
|
||||
category: supplier.category, status: supplier.status, finalStatus: 'ACTIVE', retained: true,
|
||||
}
|
||||
return context.createdResources.truckSupplier || null
|
||||
return supplierId && supplier?.category === 'TRUCKING' && supplier?.status === 'ACTIVE'
|
||||
? context.createdResources.truckSupplier : null
|
||||
}
|
||||
|
||||
export async function run(context) {
|
||||
@@ -124,43 +135,69 @@ export async function run(context) {
|
||||
context.addStep(MODULE, `拖车预约 ${status} 筛选`, response, response.http === 200 && codeOf(response) === '200' && Array.isArray(result?.records) && result.records.every((item) => item.status === status), `HTTP=${response.http}, code=${codeOf(response)}, records=${result?.records?.length ?? 'n/a'}`, OPTIONS)
|
||||
}
|
||||
|
||||
if (!context.createdResources?.oceanBooking?.id) {
|
||||
const oceanBookings = await import(new URL('./ocean-bookings.mjs', import.meta.url))
|
||||
await oceanBookings.run(context)
|
||||
}
|
||||
const oceanBooking = context.createdResources?.oceanBooking
|
||||
if (!oceanBooking?.id) {
|
||||
context.addStep(MODULE, '创建拖车预约所需隔离订舱 fixture', null, false,
|
||||
'本轮 ocean-bookings 未返回可关联的隔离订舱,未使用历史订舱', OPTIONS)
|
||||
const customerIdentity = selectIndependentTruckCustomer(context)
|
||||
if (!customerIdentity) {
|
||||
context.addStep(MODULE, '取得本轮独立拖车客户身份', null, false,
|
||||
'缺少同 marker 的 customerId;未借用历史客户或订舱', OPTIONS)
|
||||
return
|
||||
}
|
||||
let identityResult = inspectOceanBookingIdentity(context.marker, oceanBooking)
|
||||
if (!identityResult.identity && identityResult.missingFields?.length) {
|
||||
const bookingPath = `${OCEAN_BOOKING_BASE}/${encodeURIComponent(id(oceanBooking.id))}`
|
||||
const bookingResponse = await request(context, 'GET', bookingPath)
|
||||
if (bookingResponse.http !== 200 || codeOf(bookingResponse) !== '200') {
|
||||
context.addStep(MODULE, '读取本轮订舱身份快照', bookingResponse, false,
|
||||
`path=${bookingPath}, HTTP=${bookingResponse.http}, code=${codeOf(bookingResponse)}, requestId=${bookingResponse.requestId || 'missing'}, traceId=${bookingResponse.traceId || 'missing'}`, OPTIONS)
|
||||
return
|
||||
const supplier = await ensureTruckSupplier(context, suffix)
|
||||
if (!supplier?.supplierId || !supplier?.supplierName) return
|
||||
|
||||
const basePayload = truckAppointmentPayload(context, suffix, customerIdentity)
|
||||
for (const [name, payload] of [
|
||||
['缺少 customerId 拒绝创建独立拖车', { ...basePayload, customerId: null, customerName: null }],
|
||||
['缺少 ACTIVE TRUCKING carrier 拒绝创建独立拖车', { ...basePayload, carrierId: null, carrierName: null }],
|
||||
['缺少 plannedLoadingAt 拒绝创建独立拖车', { ...basePayload, plannedLoadingAt: null }],
|
||||
]) {
|
||||
const rejected = await request(context, 'POST', BASE, payload)
|
||||
context.addStep(MODULE, name, rejected, failure(rejected),
|
||||
`HTTP=${rejected.http}, code=${codeOf(rejected)}, requestId=${rejected.requestId || 'missing'}, traceId=${rejected.traceId || 'missing'}`, OPTIONS)
|
||||
}
|
||||
|
||||
const linkedFlow = [context.createdResources?.phase3Loading, context.createdResources?.phase3ContainerLoading]
|
||||
.find((item) => item?.marker === context.marker && id(item.containerBookingId) && id(item.loadingPlanId))
|
||||
if (linkedFlow) {
|
||||
const invalidPlanId = (BigInt(id(linkedFlow.loadingPlanId)) + 1n).toString()
|
||||
const mismatchedPlan = await request(context, 'POST', BASE,
|
||||
truckAppointmentPayload(context, `${suffix}P`, customerIdentity, 0, {
|
||||
containerBookingId: id(linkedFlow.containerBookingId), loadingPlanId: invalidPlanId,
|
||||
warehouseId: id(linkedFlow.warehouseId), oceanBookingId: id(linkedFlow.oceanBookingId) || null,
|
||||
}))
|
||||
context.addStep(MODULE, 'loadingPlanId 与关联柜次当前计划不一致时零写入拒绝', mismatchedPlan,
|
||||
failure(mismatchedPlan),
|
||||
`containerBookingId=${id(linkedFlow.containerBookingId)}, loadingPlanId=${invalidPlanId}, HTTP=${mismatchedPlan.http}, code=${codeOf(mismatchedPlan)}`, OPTIONS)
|
||||
if (id(linkedFlow.warehouseId)) {
|
||||
const mismatchedWarehouseId = (BigInt(id(linkedFlow.warehouseId)) + 1n).toString()
|
||||
const mismatchedWarehouse = await request(context, 'POST', BASE,
|
||||
truckAppointmentPayload(context, `${suffix}W`, customerIdentity, 0, {
|
||||
containerBookingId: id(linkedFlow.containerBookingId), warehouseId: mismatchedWarehouseId,
|
||||
}))
|
||||
context.addStep(MODULE, 'warehouseId 与可选关联柜次不一致时零写入拒绝', mismatchedWarehouse,
|
||||
failure(mismatchedWarehouse),
|
||||
`containerBookingId=${id(linkedFlow.containerBookingId)}, warehouseId=${mismatchedWarehouseId}, HTTP=${mismatchedWarehouse.http}, code=${codeOf(mismatchedWarehouse)}`, OPTIONS)
|
||||
}
|
||||
const unrelatedBooking = context.createdResources?.oceanBookingUnassignedCustomer
|
||||
if (unrelatedBooking?.marker === context.marker && id(unrelatedBooking.id)) {
|
||||
const mismatchedAllocation = await request(context, 'POST', BASE,
|
||||
truckAppointmentPayload(context, `${suffix}A`, customerIdentity, 0, {
|
||||
oceanBookingId: id(unrelatedBooking.id), containerBookingId: id(linkedFlow.containerBookingId),
|
||||
}))
|
||||
context.addStep(MODULE, '订舱与柜次无 allocation 关系时零写入拒绝', mismatchedAllocation,
|
||||
failure(mismatchedAllocation),
|
||||
`oceanBookingId=${id(unrelatedBooking.id)}, containerBookingId=${id(linkedFlow.containerBookingId)}, HTTP=${mismatchedAllocation.http}, code=${codeOf(mismatchedAllocation)}`, OPTIONS)
|
||||
}
|
||||
identityResult = inspectOceanBookingIdentity(context.marker, oceanBooking, data(bookingResponse))
|
||||
context.addStep(MODULE, '读取本轮订舱身份快照', bookingResponse, Boolean(identityResult.identity),
|
||||
identityResult.identity
|
||||
? `oceanBookingId=${identityResult.identity.oceanBookingId}, customerId=${identityResult.identity.customerId}, warehouseId=${identityResult.identity.warehouseId}`
|
||||
: `${identityResult.reason}, path=${bookingPath}, requestId=${bookingResponse.requestId || 'missing'}, traceId=${bookingResponse.traceId || 'missing'}`, OPTIONS)
|
||||
}
|
||||
if (!identityResult.identity) {
|
||||
context.addStep(MODULE, '创建拖车预约所需本轮订舱身份', null, false,
|
||||
`${identityResult.reason};未使用历史订舱或硬编码客户/仓库`, OPTIONS)
|
||||
return
|
||||
}
|
||||
const bookingIdentity = Object.freeze({ ...identityResult.identity })
|
||||
|
||||
await ensureTruckSupplier(context, suffix)
|
||||
|
||||
const createResponse = await request(context, 'POST', BASE, truckAppointmentPayload(context, suffix, bookingIdentity)); const appointment = context.apiSuccess(MODULE, '创建隔离拖车预约', createResponse, '', OPTIONS)
|
||||
const createResponse = await request(context, 'POST', BASE, basePayload); const appointment = context.apiSuccess(MODULE, '创建无订舱柜次计划关联的独立拖车预约', createResponse, '', OPTIONS)
|
||||
const appointmentId = id(appointment?.id)
|
||||
context.assert(MODULE, '拖车预约费用、司机车辆和地址字段完整', createResponse, Boolean(appointmentId) && appointment?.status === 'DRAFT' && appointment?.payableAmount != null && appointment?.receivableAmount != null, `id=${appointmentId || 'missing'}, status=${appointment?.status || 'missing'}`, OPTIONS)
|
||||
context.assert(MODULE, '独立拖车仅冻结客户承运商与自身业务字段', createResponse,
|
||||
Boolean(appointmentId) && appointment?.status === 'DRAFT'
|
||||
&& appointment?.oceanBookingId == null && appointment?.containerBookingId == null
|
||||
&& appointment?.loadingPlanId == null && id(appointment?.customerId) === customerIdentity.customerId
|
||||
&& id(appointment?.carrierId) === id(supplier.supplierId)
|
||||
&& appointment?.payableAmount != null && appointment?.receivableAmount != null,
|
||||
`id=${appointmentId || 'missing'}, status=${appointment?.status || 'missing'}, oceanBookingId=${id(appointment?.oceanBookingId) || 'null'}, containerBookingId=${id(appointment?.containerBookingId) || 'null'}`, OPTIONS)
|
||||
if (!appointmentId) return
|
||||
context.createdResources.truckAppointment = { id: appointmentId, marker: context.marker, finalStatus: 'COMPLETED' }
|
||||
const path = `${BASE}/${appointmentId}`
|
||||
@@ -183,8 +220,8 @@ export async function run(context) {
|
||||
}
|
||||
|
||||
let version = Number(appointment.version)
|
||||
const edit = await request(context, 'PUT', path, { ...truckAppointmentPayload(context, suffix, bookingIdentity, version), vehicleNo: `粤C-${suffix.slice(-6)}` }); const edited = context.apiSuccess(MODULE, '编辑拖车预约', edit, `version=${version}`, OPTIONS); version = Number(edited?.version)
|
||||
const stale = await request(context, 'PUT', path, { ...truckAppointmentPayload(context, suffix, bookingIdentity, 0), vehicleNo: `STALE-${suffix}` })
|
||||
const edit = await request(context, 'PUT', path, { ...truckAppointmentPayload(context, suffix, customerIdentity, version), vehicleNo: `粤C-${suffix.slice(-6)}` }); const edited = context.apiSuccess(MODULE, '编辑独立拖车预约且关联身份保持为空', edit, `version=${version}`, OPTIONS); version = Number(edited?.version)
|
||||
const stale = await request(context, 'PUT', path, { ...truckAppointmentPayload(context, suffix, customerIdentity, 0), vehicleNo: `STALE-${suffix}` })
|
||||
context.addStep(MODULE, '拖车预约旧版本编辑返回冲突', stale, stale.http === 200 && ['A0409','409'].includes(codeOf(stale)), `HTTP=${stale.http}, code=${codeOf(stale)}`, OPTIONS)
|
||||
const after = await request(context, 'GET', path); const afterData = data(after); context.apiSuccess(MODULE, '冲突后回查拖车预约', after, '', OPTIONS)
|
||||
context.assert(MODULE, '拖车预约冲突未部分更新', after, afterData?.vehicleNo === edited?.vehicleNo, `vehicleNo=${afterData?.vehicleNo || 'missing'}`, OPTIONS); version = Number(afterData?.version)
|
||||
@@ -195,6 +232,9 @@ export async function run(context) {
|
||||
context.assert(MODULE, '拖车列表客户、仓库、订舱柜次、供应商与关键时间字段完整', final,
|
||||
appointmentFieldGaps.length === 0, `missing=${appointmentFieldGaps.join(',') || 'none'}`, OPTIONS)
|
||||
context.assert(MODULE, '拖车预约状态终态和费用快照正确', final, finalData?.status === 'COMPLETED' && finalData?.payableAmount != null && finalData?.receivableAmount != null, `status=${finalData?.status || 'missing'}`, OPTIONS)
|
||||
context.assert(MODULE, '独立拖车完成自身状态机后仍不产生履约关联', final,
|
||||
finalData?.oceanBookingId == null && finalData?.containerBookingId == null && finalData?.loadingPlanId == null,
|
||||
`oceanBookingId=${id(finalData?.oceanBookingId) || 'null'}, containerBookingId=${id(finalData?.containerBookingId) || 'null'}, loadingPlanId=${id(finalData?.loadingPlanId) || 'null'}`, OPTIONS)
|
||||
const history = await request(context, 'GET', `${path}/history`); const histories = context.apiSuccess(MODULE, '查询拖车预约状态历史', history, '', OPTIONS)
|
||||
const actions = Array.isArray(histories) ? histories.map((item) => item.action) : []
|
||||
context.assert(MODULE, '拖车状态历史动作、版本和审计操作者完整', history, ['SUBMIT','DISPATCH','PICKUP','ARRIVE','LOAD','DEPART','COMPLETE'].every((item) => actions.includes(item)) && histories.every((item) => Number.isFinite(Number(item.revision)) && Boolean(item.operatorName) && Boolean(item.createdAt)), `actions=${actions.join(',')}`, OPTIONS)
|
||||
|
||||
Reference in New Issue
Block a user