This commit is contained in:
2026-09-14 20:03:22 +08:00
parent d53de922db
commit ebc299d58a
28 changed files with 756 additions and 95 deletions
@@ -29,6 +29,70 @@ async function request(context, method, path, body, headers = {}) {
}
}
async function verifyTruckDeparturePayables(context, aggregate) {
const appointmentId = id(aggregate?.truckAppointmentId)
if (!appointmentId) return
const truckResponse = await request(context, 'GET', `/api/platform/v1/truck-appointments/${appointmentId}`)
const truck = context.apiSuccess(MODULE, '回查国内离仓关联拖车', truckResponse, '', OPTIONS)
if (!truck) return
const bookingResponse = await request(context, 'GET', `/api/platform/v1/ocean-bookings/${id(truck.oceanBookingId)}`)
const booking = context.apiSuccess(MODULE, '核对拖车订舱全部要求货柜', bookingResponse, '', OPTIONS)
if (!booking) return
const expectedCount = (booking.containerRequirements || []).reduce((sum, item) => sum + Number(item.quantity), 0)
const departed = []
for (const container of booking.containers || []) {
const response = await request(context, 'GET', `${BASE}/${id(container.containerBookingId)}`)
const detail = context.apiSuccess(MODULE, '回查拖车关联货柜离仓事实', response, '', OPTIONS)
if (detail?.aggregate) departed.push(detail.aggregate)
}
const ready = expectedCount > 0 && departed.length === expectedCount
&& departed.every(item => item.dispatchStatus === 'WAITING_FOR_LOADING')
context.assert(MODULE, '全部要求货柜离仓才自动完成拖车', truckResponse,
ready ? truck.status === 'COMPLETED' : truck.status !== 'COMPLETED',
`ready=${ready}, status=${truck.status}`, OPTIONS)
if (!ready || truck.status !== 'COMPLETED') return
const historyResponse = await request(context, 'GET', `/api/platform/v1/truck-appointments/${appointmentId}/history`)
const history = context.apiSuccess(MODULE, '回查拖车自动完成历史', historyResponse, '', OPTIONS)
context.assert(MODULE, '拖车自动完成仅记录一次', historyResponse,
Array.isArray(history) && history.filter(item => item.action === 'AUTO_COMPLETE').length === 1, '', OPTIONS)
const fees = new Map()
for (const container of departed) {
const customers = [...new Set(container.inbounds.map(item => id(item.customerId)))]
for (const customer of customers) {
const response = await request(context, 'GET', `/api/platform/v1/loading-plans/${id(container.loadingPlanId)}/fee-details?accountId=${customer}`)
const details = context.apiSuccess(MODULE, '回查拖车生成的柜次应付', response, '', OPTIONS)
for (const fee of Array.isArray(details) ? details : []) {
if (fee.direction === 'PAYABLE' && String(fee.remark || '').includes(`国内拖车单 ${truck.appointmentNo};`)) fees.set(id(fee.id), fee)
}
}
}
const totalCents = [...fees.values()].reduce((sum, fee) => sum + Math.round(Number(fee.amount) * 100), 0)
context.assert(MODULE, '拖车应付分摊守恒且自动审核', null,
totalCents === Math.round(Number(truck.payableAmount || 0) * 100)
&& [...fees.values()].every(fee => fee.currency === 'CNY' && fee.reviewStatus === 'APPROVED'
&& fee.settlementMode === 'ON_ACCOUNT' && id(fee.supplierId) === id(truck.carrierId)),
`sourceCount=${fees.size}, totalCents=${totalCents}`, OPTIONS)
const imported = new Map()
for (let attempt = 0; attempt < 12 && imported.size < fees.size; attempt++) {
for (const customer of new Set([...fees.values()].map(fee => id(fee.tenantId)))) {
for (let page = 1; ; page++) {
const response = await request(context, 'GET', `/api/platform/finance/v1/fee-sources?customerId=${customer}&direction=PAYABLE&page=${page}&pageSize=200`)
const rows = context.apiSuccess(MODULE, '回查拖车应付财务入池', response, '', OPTIONS)
if (!Array.isArray(rows)) break
rows.filter(row => row.sourceSystem === 'FIRSTLEG' && row.sourceType === 'CONTAINER_FEE'
&& fees.has(id(row.sourceFeeId))).forEach(row => imported.set(id(row.sourceFeeId), row))
if (page * 200 >= Number(response.body?.page?.total || 0)) break
}
}
if (imported.size < fees.size) await new Promise(resolve => setTimeout(resolve, 2000))
}
context.assert(MODULE, '拖车费用真实流转到财务应付来源', null,
imported.size === fees.size && [...imported.values()].every(row => row.currency === 'CNY'
&& row.direction === 'PAYABLE' && row.reviewStatus === 'APPROVED'
&& Math.round(Number(row.amount) * 100) === Math.round(Number(fees.get(id(row.sourceFeeId)).amount) * 100)),
`expected=${fees.size}, imported=${imported.size}`, OPTIONS)
}
export async function run(context) {
const fixture = context.createdResources?.phase3Loading
|| context.createdResources?.phase3ContainerLoading
@@ -80,6 +144,7 @@ export async function run(context) {
`truck=${aggregate?.truckAppointmentStatus || 'none'}, ocean=${aggregate?.oceanBookingStatus || 'none'}`, OPTIONS)
if (aggregate?.dispatchStatus === 'WAITING_FOR_LOADING') {
await verifyTruckDeparturePayables(context, aggregate)
context.assert(MODULE, '整柜报关完成后系统已自动确认国内离仓', detailResponse,
aggregate?.inbounds?.every?.((item) => item?.inboundStatus === 'LOADED')
&& detail?.timeline?.some?.((item) => item.module === 'OUTBOUND_DISPATCH'
@@ -178,6 +243,7 @@ export async function run(context) {
&& item.action === 'REVERSE_DISPATCH'),
`dispatch=${finalDetail?.aggregate?.dispatchStatus || 'missing'}, inbounds=${finalDetail?.aggregate?.inbounds?.length ?? 'missing'}, version=${finalDetail?.aggregate?.version ?? 'missing'}`, OPTIONS)
if (confirmed?.aggregate) {
await verifyTruckDeparturePayables(context, confirmed.aggregate)
context.createdResources.phase3OutboundDispatch = {
marker: context.marker, inboundId, containerBookingId,
orderId: id(inboundBinding?.orderId), tenantId: id(inboundBinding?.tenantId),