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 } }