Files
2026-10-22 14:37:59 +08:00

87 lines
7.5 KiB
JavaScript

import { dispatchIo, replay, version } from './grouped-dispatch-acceptance.mjs'
const API = '/api/platform/v1/last-leg'
const id = value => String(value ?? '')
const rowFact = row => JSON.stringify([id(row.dispatchItemId), row.sourceKey, id(row.customerId), id(row.warehousePalletId),
row.inboundCode, Number(row.scheduledPackages), Number(row.pickedPackages)])
/** 把原散板实收货加入本车唯一主单,并核对既有货物和新增实托守恒。 */
export async function arrangeLooseboardPool(context, { demand, tripProof }) {
const receipt = demand?.receipt, binding = demand?.poolBinding
if (context.createdResources?.phase4OverseasBusiness?.looseboard !== demand || demand?.marker !== context.marker
|| !context.createdResources?.fleetTrips?.includes(tripProof) || binding?.tripNo !== tripProof?.tripNo
|| receipt?.status !== 'RECEIVED_READY_FOR_DISPATCH' || !receipt.incomingNo || receipt.dispatch
|| !receipt.pallets?.length || typeof context.checkpoint !== 'function') throw Error('散板排入原车缺少本轮确认实收证据')
const proof = receipt.dispatch = { marker: context.marker, status: 'ARRANGING', requests: [], dispatchNo: tripProof.dispatchNo }
const io = dispatchIo(context, 'phase4-overseas-business', proof, `LOOSEBOARD-${binding.bookingNo}`)
const tripPath = `${API}/fleet/trips/${encodeURIComponent(tripProof.tripNo)}`
const dispatchPath = `${API}/dispatches/${encodeURIComponent(tripProof.dispatchNo)}`
const check = (name, ok) => io.check(name, null, Boolean(ok))
try {
let current = await io.command('GET', dispatchPath, undefined, '散板加货前原车详情')
const before = await io.command('GET', `${dispatchPath}/items`, undefined, '冻结原车既有货物')
check('原车仍未扫码且原货完整', current?.dispatchNo === tripProof.dispatchNo && current.status === 'ACTIVE'
&& ['BEFORE_LOADING', 'LOADING'].includes(current.stage) && Number(current.counts?.pickedPackageCount) === 0
&& Number(current.counts?.shippedPackageCount) === 0 && before?.dispatchNo === tripProof.dispatchNo
&& Array.isArray(before.items) && before.items.length > 0
&& before.items.every(row => row.inboundCode !== receipt.incomingNo && Number(row.pickedPackages) === 0))
const originalRows = before.items.map(rowFact)
const originalPlan = await io.command('GET', `${tripPath}/capacity-plan`, undefined, '冻结原车容量计划')
check('本车加货前容量计划有效', originalPlan?.planValid === true && !originalPlan.blockReason && originalPlan.capacityPlan?.pallets?.length > 0)
if (current.stage === 'LOADING') {
const body = { recordVersion: version(current.recordVersion), idempotencyKey: io.key('return-review'), reason: '本轮零扫码明确退回,加入散板原单实际实收货物' }
const result = await io.command('POST', `${dispatchPath}/return-to-review`, body, '原车零扫码退回审核')
await replay(io, `${dispatchPath}/return-to-review`, body, result, '退回审核同键重放')
current = await io.command('GET', dispatchPath, undefined, '回查原车可加货阶段')
}
check('原车已处装车前', current?.dispatchNo === tripProof.dispatchNo && current.stage === 'BEFORE_LOADING')
const list = await io.command('GET', `${tripPath}/bookings`, undefined, '读取原散板承运最新版本')
const rows = list?.records?.filter(row => row.bookingNo === binding.bookingNo) || [], booking = rows[0]
check('仅把原承运原来货排入本车', rows.length === 1 && booking.tripNo === tripProof.tripNo
&& id(booking.customerId) === id(demand.customerId) && booking.incomingNo === receipt.incomingNo
&& booking.status === 'RECEIVED' && !booking.dispatchNo
&& booking.receipt?.source?.receiptHash === receipt.acceptedReceipt?.source?.receiptHash)
const body = proof.arrangeRequest = { tripRecordVersion: version(list.tripRecordVersion), recordVersion: version(booking.recordVersion), idempotencyKey: io.key('arrange') }
const path = `${API}/fleet/capacity-offers/inquiries/${encodeURIComponent(binding.bookingNo)}/arrange-dispatch`
const arranged = await io.command('POST', path, body, '正式接口排入本车唯一主单')
check('排入结果绑定同一主单和来源', arranged?.booking?.status === 'BOUND' && arranged.booking.bookingNo === binding.bookingNo
&& arranged.booking.tripNo === tripProof.tripNo && arranged.booking.dispatchNo === tripProof.dispatchNo
&& arranged.booking.incomingNo === receipt.incomingNo && id(arranged.booking.customerId) === id(demand.customerId))
const again = await replay(io, path, body, arranged, '排入本车同键重放')
check('排入重放明确返回原结果', again?.replayed === true)
const after = await io.command('GET', `${dispatchPath}/items`, undefined, '加货后回查全部真实货物')
const added = after?.items?.filter(row => row.inboundCode === receipt.incomingNo) || []
check('加货不删除或重复既有货物', after?.dispatchNo === tripProof.dispatchNo && after.items?.length === before.items.length + receipt.pallets.length
&& originalRows.every(fact => after.items.filter(row => rowFact(row) === fact).length === 1))
check('新增行精确对应原散板实收各托箱数', added.length === receipt.pallets.length
&& receipt.pallets.every(p => added.filter(row => id(row.warehousePalletId) === id(p.warehousePalletId)
&& id(row.customerId) === id(demand.customerId) && row.palletNo === p.palletNo
&& Number(row.scheduledPackages) === p.packages && Number(row.pickedPackages) === 0).length === 1))
const plan = await io.command('GET', `${tripPath}/capacity-plan`, undefined, '新增实收后读取容量依据')
const detail = await io.command('GET', tripPath, undefined, '读取原车真实卸货站')
check('容量货物仅属于本车唯一主单', plan?.planValid === false && plan.cargo?.every(row => row.dispatchNo === tripProof.dispatchNo))
const destinationKey = binding.createRequest.cargo.destinationKey
const stopIndexes = detail?.trip?.stops?.map((stop, index) => stop.destinationKey === destinationKey ? index : -1).filter(index => index > 0) || []
check('实收目的地对应唯一卸货站', detail?.trip?.tripNo === tripProof.tripNo && stopIndexes.length === 1)
const pallets = structuredClone(originalPlan.capacityPlan.pallets)
for (const p of receipt.pallets) {
const measured = receipt.acceptedReceipt?.source?.pallets?.find(row => id(row.warehousePalletId) === id(p.warehousePalletId))
check('新增容量仅用实际实托及实测', measured && Number(measured.weightKg) > 0 && Number(measured.volumeCbm) > 0
&& !pallets.some(row => id(row.warehousePalletId) === id(p.warehousePalletId)))
pallets.push({ warehousePalletId: p.warehousePalletId, palletSpaces: 1,
weightKg: Number(measured.weightKg), volumeCbm: Number(measured.volumeCbm), deliveryIndex: stopIndexes[0] })
}
const refreshed = await io.command('POST', `${tripPath}/capacity-plan`, { recordVersion: version(plan.recordVersion), manifestHash: plan.manifestHash,
pallets, reason: '保留原货预计占用,追加散板实际实收容量,仍须独立完成装车确认' }, '重核原车加货后的容量计划')
check('原车加货后容量有效', refreshed?.planValid === true && !refreshed.blockReason)
proof.status = 'BOUND_AWAITING_REVIEW'
proof.pallets = receipt.pallets
proof.addedItems = added
await context.checkpoint()
return proof
} catch (error) {
proof.status = 'FAILED'; proof.failure = error.message
await context.checkpoint(); throw error
}
}