import { DISPATCH_BASE as BASE, id as stringId, version, createBody, candidatePage, readDispatch, replay } from './grouped-dispatch-acceptance.mjs' import { fleetConsumerIo, readyFleetDispatch, stableRows, verifyFleetFeeCoverage } from './fleet-fee-coverage-acceptance.mjs' const clone = value => structuredClone(value) const id = value => { if (typeof value === 'number' && !Number.isSafeInteger(value)) throw Error('服务端ID精度无效,必须保留Long字符串'); return stringId(value) } const money = value => { const text = String(value); if (!/^\d+(?:\.\d{1,2})?$/.test(text)) throw Error('外接约定金额或单价不完整'); const [whole, fraction = ''] = text.split('.'); return BigInt(whole) * 100n + BigInt(fraction.padEnd(2, '0')) } const identity = item => ({ dispatchItemId: id(item.dispatchItemId), sourceKey: item.sourceKey, customerId: id(item.customerId), warehousePalletId: id(item.warehousePalletId), scheduledPackages: Number(item.scheduledPackages), pickedPackages: Number(item.pickedPackages) }) const groupIdentity = (customerId, destinationKey) => JSON.stringify([id(customerId), destinationKey]) const sha = value => /^[a-f0-9]{64}$/i.test(value || '') const feeIdentity = fee => `${fee.direction}:${fee.feeType}:${fee.currency}:${money(fee.amount)}` function validateNativeArrangement(context, tripProof, plan) { if (plan?.mode !== 'NATIVE' || plan.marker !== context.marker || plan.dispatchNo !== tripProof.dispatchNo || typeof plan.plannedDeliveryAt !== 'string' || !Number.isFinite(Date.parse(plan.plannedDeliveryAt)) || !plan.deliveryMethod || !plan.pickupMethod || plan.loadingType !== 'PALLET' || plan.appointmentAccount !== 'SELF' || !Array.isArray(plan.groups) || !plan.groups.length) throw Error('普通原生安排须明确本轮主单、派送时间、PALLET/SELF及逐客户目的地费用') const keys = new Set() for (const group of plan.groups) { const key = groupIdentity(group.customerId, group.destinationKey) if (!/^[1-9]\d*$/.test(id(group.customerId)) || typeof group.destinationKey !== 'string' || !group.destinationKey.trim() || keys.has(key) || !Array.isArray(group.fees) || Object.hasOwn(group, 'feeCoverage')) throw Error('普通安排的客户目的地配置缺失、重复或含原费覆盖选择') keys.add(key) const fees = new Set() for (const fee of group.fees) { const feeKey = `${fee.direction}:${fee.feeType}` if (!['RECEIVABLE', 'PAYABLE'].includes(fee.direction) || !['DELIVERY_FEE', 'LOADING_FEE', 'APPOINTMENT_FEE'].includes(fee.feeType) || !fee.feeName || !['CNY', 'USD'].includes(fee.currency) || money(fee.amount) <= 0n || fees.has(feeKey)) throw Error('普通安排费用必须逐组明确正数、币种和服务,不得重复') fees.add(feeKey) } } } // 仅将明确的客户/目的地配置映射到实时groupKey与摘要;不推算费用或原费覆盖。 export function nativeArrangementGroups(preview, plan, receipt) { const configured = new Map(plan.groups.map(g => [groupIdentity(g.customerId, g.destinationKey), g])) if (!Array.isArray(preview.groups) || preview.groups.length !== configured.size || new Set(preview.groups.map(g => groupIdentity(g.customerId, g.destination?.key))).size !== configured.size) throw Error('普通安排配置必须精确覆盖全车全部真实客户目的地') return preview.groups.map(group => { const config = configured.get(groupIdentity(group.customerId, group.destination?.key)) if (!config || !group.groupKey || !sha(group.groupCargoHash) || !sha(group.externalFreightHash) || !sha(group.feeCoverageCargoHash) || !Array.isArray(group.blockReasons) || group.blockReasons.length || !Array.isArray(group.requiredServices) || !Array.isArray(group.externalQuotes) || !Array.isArray(group.feeSources) || !Array.isArray(group.cargo) || !group.cargo.length || group.cargo.some(c => id(c.customerId) !== id(group.customerId) || c.destination?.key !== group.destination.key)) throw Error('普通原生安排的实际分组或来源门禁不完整') if (group.externalQuotes.some(q => q.bookingNo !== receipt.bookingNo || q.inboundCode !== receipt.incomingNo || id(q.customerId) !== id(group.customerId) || q.groupKey !== group.groupKey || q.sourceHash !== group.externalFreightHash || q.currency !== receipt.finalBooking.cargo.currency || money(q.amount) !== money(receipt.finalBooking.agreedAmount))) throw Error('普通安排的外接报价已变化或越过客户来源') if (group.requiredServices.some(service => !config.fees.some(f => f.direction === 'RECEIVABLE' && f.feeType === service))) throw Error('普通安排必须明确填写本组尚未覆盖的服务费用') const quotedAll = group.cargo.every(c => group.externalQuotes.some(q => q.inboundCode === c.inboundCode)) if (quotedAll && config.fees.some(f => f.direction === 'RECEIVABLE' && f.feeType === 'DELIVERY_FEE')) throw Error('本组派送已由外接约定覆盖,不得重复登记应收') return { groupKey: group.groupKey, groupCargoHash: group.groupCargoHash, plannedDeliveryAt: plan.plannedDeliveryAt, deliveryMethod: plan.deliveryMethod, pickupMethod: plan.pickupMethod, loadingType: plan.loadingType, appointmentAccount: plan.appointmentAccount, waybillNo: plan.waybillNo || null, carrierName: plan.carrierName || null, vehicleType: plan.vehicleType || null, vehicleSize: plan.vehicleSize || null, sellerName: null, remark: plan.remark || null, externalFreightHash: group.externalFreightHash, feeCoverageCargoHash: group.feeCoverageCargoHash, feeCoverage: [], fees: clone(config.fees) } }) } async function arrangeNative(context, module, io, proof, tripProof, plan, pickerToken, actualMeasurements) { const current = await readyFleetDispatch(context, module, io, tripProof, { pickerToken, actualMeasurements }) const path = `${BASE}/${encodeURIComponent(tripProof.dispatchNo)}`, preview = await io.get(`${path}/delivery-arrangement`, '普通模式读取装车后实时全车派送分组') io.check('普通安排使用当前公共版本和真实实载摘要', null, preview?.dispatchNo === tripProof.dispatchNo && preview.status === 'ACTIVE' && preview.stage === 'AFTER_LOADING' && id(version(preview.recordVersion)) === id(version(current.data.recordVersion)) && preview.arrange?.allowed === true && !preview.arrangement && sha(preview.cargoHash) && sha(preview.loadingCompletionHash)) const groups = nativeArrangementGroups(preview, plan, tripProof.externalReceipt), totals = new Map() for (const group of preview.groups) for (const row of group.cargo) { io.check('普通派送货物保留实际客户目的地和原物理行', null, current.items.items.some(i => id(i.dispatchItemId) === id(row.dispatchItemId) && id(i.customerId) === id(group.customerId) && i.destination.key === group.destination.key && id(i.warehousePalletId) === id(row.warehousePalletId))) totals.set(id(row.dispatchItemId), (totals.get(id(row.dispatchItemId)) || 0) + Number(row.packages)) } io.check('普通安排逐物理行箱数完整守恒', null, totals.size === current.items.items.length && current.items.items.every(i => totals.get(id(i.dispatchItemId)) === Number(i.scheduledPackages))) const body = { recordVersion: version(preview.recordVersion), idempotencyKey: io.key('native-arrangement'), cargoHash: preview.cargoHash, loadingCompletionHash: preview.loadingCompletionHash, groups } proof.nativeArrangement = { mode: 'NATIVE', confirmationRequest: clone(body), status: 'CHECKING' }; await context.checkpoint() const noDocuments = async () => { for (const customerId of new Set(preview.groups.map(g => id(g.customerId)))) { const rows = []; let total for (let page = 1; ; page++) { const data = await io.get(`/api/platform/v1/last-leg/outbound-orders?${new URLSearchParams({ customerId, page: String(page), size: '200' })}`, '普通安排拒绝前后按客户回查无出库写入') io.check('普通安排出库分页完整稳定', null, Array.isArray(data?.records) && Number.isSafeInteger(Number(data.total)) && Number(data.total) >= 0 && Number(data.page) === page && Number(data.size) === 200 && (total === undefined || total === Number(data.total)) && (data.records.length > 0 || rows.length === Number(data.total))) total ??= Number(data.total); rows.push(...data.records); if (rows.length >= total) break } io.check('普通安排拒绝未生成本主单出库', null, rows.length === total && new Set(rows.map(r => r.outboundNo)).size === rows.length && rows.every(r => r.dispatchNo !== tripProof.dispatchNo)) } } const reject = async (action, changed, pattern) => { const before = await readDispatch(io, tripProof.dispatchNo) await io.request('POST', `${path}/transfer-delivery`, { ...clone(body), ...changed, idempotencyKey: io.key(action) }, action, pattern) await noDocuments(); io.check(`${action}不改整车版本及实物`, null, JSON.stringify(await readDispatch(io, tripProof.dispatchNo)) === JSON.stringify(before)) } await noDocuments() const changedHash = hash => `${hash[0] === '0' ? '1' : '0'}${hash.slice(1)}` await reject('普通安排陈旧货物摘要拒绝', { cargoHash: changedHash(body.cargoHash) }, /货物.*变化/) const quoted = preview.groups.find(g => g.externalQuotes.length) io.check('普通安排包含本轮已确认外接报价', null, Boolean(quoted)) const stale = clone(groups); stale.find(g => g.groupKey === quoted.groupKey).externalFreightHash = changedHash(quoted.externalFreightHash) await reject('普通安排陈旧外接报价摘要拒绝', { groups: stale }, /外接.*变化/) const duplicate = clone(groups), quote = quoted.externalQuotes[0] duplicate.find(g => g.groupKey === quoted.groupKey).fees.push({ direction: 'RECEIVABLE', feeType: 'DELIVERY_FEE', feeName: '本轮重复外接应收反例', currency: quote.currency, amount: quote.amount }) await reject('普通安排重复外接应收拒绝', { groups: duplicate }, /重复|覆盖/) const command = await io.request('POST', `${path}/transfer-delivery`, body, '普通原生模式一次安排全车全部客户目的地'), result = command?.result io.check('普通原生命令完整返回真实客户出库和目的地派送', null, command?.dispatchNo === tripProof.dispatchNo && command.action === 'ARRANGE_DELIVERY' && command.status === 'ACTIVE' && command.stage === 'AFTER_LOADING' && BigInt(version(command.recordVersion)) > BigInt(version(body.recordVersion)) && result?.dispatchNo === tripProof.dispatchNo && result.cargoHash === body.cargoHash && result.deliveries?.length === preview.groups.length && new Set(result.deliveries.map(d => d.deliveryNo)).size === result.deliveries.length && result.outbounds?.length === new Set(preview.groups.map(g => id(g.customerId))).size && result.deliveries.every(d => d.deliveryNo && d.status === 'WAIT_DELIVERY' && preview.groups.some(g => g.groupKey === d.groupKey && id(g.customerId) === id(d.customerId) && g.destination.key === d.destination?.key && g.cargo.reduce((n, c) => n + Number(c.packages), 0) === Number(d.packageCount)) && result.outbounds.some(o => o.outboundNo === d.outboundNo && id(o.customerId) === id(d.customerId))) && result.outbounds.every(o => o.outboundNo && o.status === 'WAIT_OUTBOUND' && Number(o.packageCount) === result.deliveries.filter(d => d.outboundNo === o.outboundNo).reduce((n, d) => n + Number(d.packageCount), 0))) const snapshots = {} for (const ref of result.deliveries) { const group = preview.groups.find(g => g.groupKey === ref.groupKey), input = groups.find(g => g.groupKey === ref.groupKey) const detail = await io.get(`/api/platform/v1/last-leg/delivery-orders/${encodeURIComponent(ref.deliveryNo)}?customerId=${encodeURIComponent(id(ref.customerId))}`, '普通安排回查客户目的地及明确费用') const expectedFees = [...input.fees.map(feeIdentity), ...group.externalQuotes.filter(q => money(q.amount) > 0n).map(q => feeIdentity({ direction: 'RECEIVABLE', feeType: 'DELIVERY_FEE', currency: q.currency, amount: q.amount }))] io.check('普通派送保留本客户本目的地费用,无伪零、重复或原费覆盖回执', null, detail?.order?.dispatchNo === tripProof.dispatchNo && detail.order.deliveryNo === ref.deliveryNo && id(detail.order.customerId) === id(ref.customerId) && detail.order.outboundNo === ref.outboundNo && id(detail.order.destinationWarehouseCode || detail.order.destinationWarehouseId) === id(group.destination.warehouseCode || group.destination.warehouseId) && (detail.feeCoverage == null || detail.feeCoverage.references?.length === 0) && Array.isArray(detail.fees) && new Set(detail.fees.map(f => id(f.id))).size === detail.fees.length && detail.fees.every(f => money(f.amount) > 0n && f.sourceStatus === 'ACTIVE' && f.reviewStatus === 'APPROVED') && stableRows(detail.fees.map(f => feeIdentity({ direction: f.feeDirection, feeType: f.feeType, currency: f.currency, amount: f.amount }))) === stableRows(expectedFees)) snapshots[ref.deliveryNo] = { fees: clone(detail.fees), feeCoverage: clone(detail.feeCoverage) } } const again = await replay(io, `${path}/transfer-delivery`, body, command, '普通安排原键原载荷重放'); io.check('普通安排明确恢复原成功命令', null, again.replayed === true) for (const ref of result.deliveries) { const after = await io.get(`/api/platform/v1/last-leg/delivery-orders/${encodeURIComponent(ref.deliveryNo)}?customerId=${encodeURIComponent(id(ref.customerId))}`, '普通安排重放后核对不追加应收') io.check('普通安排重放不增费不改覆盖快照', null, JSON.stringify({ fees: after.fees, feeCoverage: after.feeCoverage }) === JSON.stringify(snapshots[ref.deliveryNo])) } const after = await io.get(`${path}/delivery-arrangement`, '普通安排后回查报价与原费用候选未被改写') const originalFacts = value => stableRows(value.groups.map(g => ({ groupKey: g.groupKey, externalQuotes: g.externalQuotes, feeSources: g.feeSources }))) io.check('普通安排保持报价及原费用来源快照', null, originalFacts(after) === originalFacts(preview)) Object.assign(proof.nativeArrangement, { status: 'VERIFIED', command, snapshots }) return { confirmationRequest: body, deliveries: result.deliveries, outbounds: result.outbounds } } /** 已确认实收来源只ADD_CARGO到原整车;复审、真实扫码、显式装车后一次安排全部客户。 * options二选一:coverageCase为本轮PARTIAL;或arrangement={mode:'NATIVE',marker,dispatchNo, * plannedDeliveryAt,deliveryMethod,pickupMethod,loadingType:'PALLET',appointmentAccount:'SELF', * groups:[{customerId,destinationKey,fees:FeeItem[]}]}; 金额和费用覆盖均不自动推断。 */ export async function verifyFleetExternalDispatch(context, module, tripProof, { pickerToken, actualMeasurements, coverageCase, arrangement } = {}) { const receipt = tripProof?.externalReceipt, cases = context.createdResources?.fleetFeeCoverageCases if (!context.createdResources?.fleetTrips?.includes(tripProof) || receipt?.status !== 'VERIFIED' || tripProof.externalDispatch) throw Error('外接只接受本轮已运营确认且尚未装入本车的真实来货') if (Boolean(coverageCase) === Boolean(arrangement)) throw Error('必须且只能选择同主单未扫码PARTIAL费用覆盖或普通NATIVE安排模式') if (arrangement) validateNativeArrangement(context, tripProof, arrangement) if (coverageCase && (!cases?.includes(coverageCase) || coverageCase.marker !== context.marker || coverageCase.mode !== 'PARTIAL' || coverageCase.dispatchNo !== tripProof.dispatchNo || coverageCase.proof) || !pickerToken || typeof context.checkpoint !== 'function') throw Error('外接加货前须有同主单未扫码PARTIAL用例及本轮PDA身份;必须先外接后扫码安排') const proof = tripProof.externalDispatch = { marker: context.marker, dispatchNo: tripProof.dispatchNo, arrangementMode: arrangement ? 'NATIVE' : 'FEE_COVERAGE', status: 'CHECKING', requests: [] } const io = fleetConsumerIo(context, module, proof, `EXTERNAL-${tripProof.dispatchNo}`) const base = `/api/platform/v1/last-leg/fleet/trips/${encodeURIComponent(tripProof.tripNo)}`, path = `${BASE}/${encodeURIComponent(tripProof.dispatchNo)}` const commandReplay = async (path, body, result, label) => { const again = await replay(io, path, body, result, label); io.check(`${label}明确恢复原结果`, null, again.replayed === true) } try { let current = await readDispatch(io, tripProof.dispatchNo) io.check('只调整本轮同仓零扫码原主单', null, id(current.warehouseId) === id(tripProof.warehouseId) && current.data.status === 'ACTIVE' && ['BEFORE_LOADING', 'LOADING'].includes(current.data.stage) && Number(current.data.counts.pickedPackageCount) === 0 && Number(current.data.counts.shippedPackageCount) === 0) const ownItems = current.items.items.map(identity) const before = await io.get(`${base}/capacity-plan`, '冻结外接预留与真实原容量计划') io.check('原容量和外接承诺均完整', null, before?.planValid === true && before.capacityPlan?.pallets?.length > 0 && JSON.stringify(before.availableSegments) === JSON.stringify(receipt.finalSegments)) let list = await io.get(`${base}/bookings`, '核对运营确认后的原booking和原报价') const booking = list.records?.find(row => row.bookingNo === receipt.bookingNo), accepted = booking?.receipt io.check('实时booking保持本轮客户仓实收和运营确认', null, booking?.status === 'RECEIVED' && !booking.dispatchNo && booking.incomingNo === receipt.incomingNo && id(booking.customerId) === id(receipt.createRequest?.customerId) && accepted?.source?.warehouseId === id(tripProof.warehouseId) && accepted.source.incomingNo === receipt.incomingNo && accepted.source.status === 'READY_FOR_DISPATCH' && accepted.source.receiptHash && accepted.source.pallets?.length > 0 && Array.isArray(accepted.pallets) && accepted.operatorId && Number.isFinite(Date.parse(accepted.confirmedAt)) && JSON.stringify(booking.cargo) === JSON.stringify(receipt.finalBooking.cargo) && String(booking.agreedAmount) === String(receipt.finalBooking.agreedAmount)) const actual = accepted.source.pallets, packages = actual.reduce((n, p) => n + Number(p.packages), 0) io.check('真实来货托盘和箱数守恒', null, new Set(actual.map(p => id(p.warehousePalletId))).size === actual.length && packages === Number(accepted.source.receivedPackages) && actual.every(p => p.warehousePalletId && p.palletNo && Number.isSafeInteger(Number(p.packages)) && Number(p.packages) > 0) && actual.some(p => id(p.warehousePalletId) === id(receipt.warehousePalletId))) const page = await candidatePage(io, tripProof.warehouseId, { orderNo: receipt.incomingNo }) const candidates = page.filter(c => c.inboundCode === receipt.incomingNo && id(c.customerId) === id(booking.customerId) && id(c.warehouseId) === id(tripProof.warehouseId) && actual.some(p => id(p.warehousePalletId) === id(c.selection?.warehousePalletId))) io.check('候选仅为本次确认的全部真实外接来源', null, candidates.length === actual.length && candidates.every(c => c.destination.key === booking.cargo.destinationKey && actual.some(p => id(p.warehousePalletId) === id(c.selection.warehousePalletId) && p.palletNo === c.palletNo && Number(p.packages) === Number(c.availablePackages)))) const addition = createBody(context.marker, `EXTERNAL-${tripProof.dispatchNo}`, candidates) if (arrangement) { const actualGroups = new Set([...current.items.items.map(i => groupIdentity(i.customerId, i.destination?.key)), ...candidates.map(c => groupIdentity(c.customerId, c.destination?.key))]) io.check('普通安排配置在加货前精确覆盖全部实际客户目的地', null, stableRows([...actualGroups]) === stableRows(arrangement.groups.map(g => groupIdentity(g.customerId, g.destinationKey)))) } if (current.data.stage === 'LOADING') { proof.returnRequest = { recordVersion: version(current.data.recordVersion), idempotencyKey: io.key('explicit-return'), reason: '本轮零扫码外接实收显式退回装车前,保留同一整车' } const returned = await io.request('POST', `${path}/return-to-review`, proof.returnRequest, '零扫码明确退回审核') await commandReplay(`${path}/return-to-review`, proof.returnRequest, returned, '退回审核原键重放') current = await readDispatch(io, tripProof.dispatchNo) } io.check('同一主单已处于装车前可加货', null, current.data.stage === 'BEFORE_LOADING') proof.addRequest = { recordVersion: version(current.data.recordVersion), idempotencyKey: io.key('add-real-incoming'), cargo: addition.cargo, batchAllocations: addition.batchAllocations } const added = await io.request('POST', `${path}/items`, proof.addRequest, '真实已运营确认来源ADD_CARGO加入原主单') io.check('加货命令保持原整车身份', null, added.dispatchNo === tripProof.dispatchNo && added.action === 'ADD_CARGO') await commandReplay(`${path}/items`, proof.addRequest, added, '同主单加货原键重放') current = await readDispatch(io, tripProof.dispatchNo) io.check('加货和重放保留原客户物理行,仅增加本次完整来源', null, current.data.stage === 'BEFORE_LOADING' && current.items.items.length === ownItems.length + candidates.length && ownItems.every(i => current.items.items.some(j => JSON.stringify(identity(j)) === JSON.stringify(i))) && candidates.every(c => current.items.items.some(i => i.sourceKey === c.sourceKey && i.sourceVersion === c.sourceVersion && id(i.customerId) === id(booking.customerId) && id(i.warehousePalletId) === id(c.selection.warehousePalletId) && i.inboundCode === receipt.incomingNo && Number(i.scheduledPackages) === Number(c.availablePackages) && Number(i.pickedPackages) === 0))) list = await io.get(`${base}/bookings`, '加货后刷新booking与真实车次版本') const latest = list.records.find(b => b.bookingNo === receipt.bookingNo) proof.bindRequest = { tripRecordVersion: version(list.tripRecordVersion), recordVersion: version(latest.recordVersion), dispatchNo: tripProof.dispatchNo, idempotencyKey: io.key('bind-same-whole') } const bound = await io.request('POST', `${base}/bookings/${encodeURIComponent(receipt.bookingNo)}/dispatch`, proof.bindRequest, '原booking绑定本车唯一整车主单') io.check('实收转关联只指向原主单', null, bound.booking?.status === 'BOUND' && bound.booking.dispatchNo === tripProof.dispatchNo && bound.booking.bookingNo === receipt.bookingNo && JSON.stringify(bound.booking.cargo) === JSON.stringify(booking.cargo) && String(bound.booking.agreedAmount) === String(booking.agreedAmount)) await commandReplay(`${base}/bookings/${encodeURIComponent(receipt.bookingNo)}/dispatch`, proof.bindRequest, bound, '同主单booking关联原键重放') const after = await io.get(`${base}/capacity-plan`, '真实加货和预留转关联后重新核对容量') io.check('实际新增货物使旧容量计划失效且没有第二张客户排车', null, after.planValid === false && after.cargo.every(c => c.dispatchNo === tripProof.dispatchNo) && candidates.every(c => after.cargo.some(i => id(i.warehousePalletId) === id(c.selection.warehousePalletId) && Number(i.packages) === Number(c.availablePackages)))) const pallets = new Map(before.capacityPlan.pallets.map(p => [id(p.warehousePalletId), clone(p)])) for (const p of actual) { const measurement = accepted.pallets.find(m => id(m.warehousePalletId) === id(p.warehousePalletId)), index = current.route.stops.findIndex(s => s.destinationKey === booking.cargo.destinationKey) io.check('新托仅使用实际运营确认托位和来货实测', null, measurement && Number.isSafeInteger(measurement.palletSpaces) && measurement.palletSpaces > 0 && Number(measurement.weightKg ?? p.weightKg) > 0 && Number(measurement.volumeCbm ?? p.volumeCbm) > 0 && index > 0) pallets.set(id(p.warehousePalletId), { warehousePalletId: id(p.warehousePalletId), palletSpaces: measurement.palletSpaces, weightKg: measurement.weightKg ?? p.weightKg, volumeCbm: measurement.volumeCbm ?? p.volumeCbm, deliveryIndex: index }) } io.check('新容量逐托覆盖全部实际货物且没有额外资源', null, new Set(after.cargo.map(c => id(c.warehousePalletId))).size === pallets.size && after.cargo.every(c => pallets.has(id(c.warehousePalletId)))) proof.capacityRequest = { recordVersion: version(after.recordVersion), manifestHash: after.manifestHash, pallets: [...pallets.values()], reason: '本轮同主单外接实收按实际运营确认和原计划量重新核对' } const planned = await io.request('POST', `${base}/capacity-plan`, proof.capacityRequest, '重新确认实际货物容量') io.check('预留转实际后分段占用守恒', null, planned.planValid === true && JSON.stringify(planned.availableSegments) === JSON.stringify(before.availableSegments)) const trip = await io.get(base, '复审前核对资源和唯一整车关联') io.check('原车辆司机起运仓和唯一排车保持不变', null, trip.dispatches?.length === 1 && trip.dispatches[0].dispatchNo === tripProof.dispatchNo && id(trip.trip?.warehouseId) === id(tripProof.warehouseId) && id(trip.trip.vehicleId) === id(tripProof.vehicleId) && id(trip.trip.driverId) === id(tripProof.driverId)) tripProof.stops = trip.trip.stops for (const action of ['submit-review', 'dispatch-review', 'loading-review']) { const detail = await io.get(path, `${action}前刷新原主单版本`) const body = { recordVersion: version(detail.recordVersion), idempotencyKey: io.key(action), ...(action === 'submit-review' ? { remark: '本轮加入外接实物后全车重新提交' } : { decision: 'APPROVED', reason: '本轮外接实收全车重新审核' }) } const result = await io.request('POST', `${path}/${action}`, body, `整车${action}`) await commandReplay(`${path}/${action}`, body, result, `整车${action}原键重放`) } const ready = await io.get(path, '核对两级审核恢复真实扫码资格') io.check('同主单两级审核通过且仍零扫码', null, ready.stage === 'LOADING' && ready.reviewState === 'APPROVED' && ready.reviews?.dispatchReview?.status === 'APPROVED' && ready.reviews?.loadingReview?.status === 'APPROVED' && Number(ready.counts.pickedPackageCount) === 0) const execution = arrangement ? await arrangeNative(context, module, io, proof, tripProof, arrangement, pickerToken, actualMeasurements) : await verifyFleetFeeCoverage(context, module, coverageCase, { pickerToken, actualMeasurements, externalTrip: tripProof }) const groups = await io.get(`${path}/delivery-arrangement`, '安排后回查原生外接报价快照') const quotes = groups.groups.flatMap(g => g.externalQuotes), expected = receipt.finalBooking const matching = quotes.filter(q => q.bookingNo === receipt.bookingNo && q.inboundCode === receipt.incomingNo && id(q.customerId) === id(booking.customerId)) io.check('原外接报价属于本次客户和来源且等于运营确认约定金额', null, matching.length === 1 && money(matching[0].amount) === money(expected.agreedAmount) && matching[0].currency === expected.cargo.currency && matching[0].sourceHash && money(expected.agreedAmount) > 0n) const deliveries = execution.deliveries.filter(d => groups.groups.some(g => g.groupKey === d.groupKey && g.externalQuotes.some(q => q.bookingNo === receipt.bookingNo))) io.check('本次外接来源只落入其客户目的地派送', null, deliveries.length === 1 && id(deliveries[0].customerId) === id(booking.customerId)) proof.outboundNo = deliveries[0].outboundNo; proof.deliveryNo = deliveries[0].deliveryNo; proof.deliveries = execution.deliveries const delivery = await io.get(`/api/platform/v1/last-leg/delivery-orders/${encodeURIComponent(proof.deliveryNo)}?customerId=${encodeURIComponent(id(booking.customerId))}`, '回查本客户外接应收来源') const sources = await io.get(`/api/platform/v1/last-leg/finance-fee-sources?${new URLSearchParams({ customerId: id(booking.customerId), businessType: 'DELIVERY_ORDER', businessNo: proof.deliveryNo })}`, '按本次客户派送业务号回查外接应收来源') const fees = Array.isArray(sources) ? sources.filter(f => f.direction === 'RECEIVABLE' && f.feeItemCode === 'DELIVERY_FEE' && f.sourceStatus === 'ACTIVE' && f.inboundCode === receipt.incomingNo) : [] const quantity = expected.cargo.priceBasis === 'PALLET' ? expected.cargo.palletSpaces : expected.cargo.priceBasis === 'ORDER' ? 1 : null io.check('外接只登记本次约定金额应收且来源已审核', null, fees.length === 1 && money(fees[0].originalAmount) === money(expected.agreedAmount) && fees[0].originalCurrency === expected.cargo.currency && fees[0].reviewStatus === 'APPROVED' && id(fees[0].customerId) === id(booking.customerId) && quantity != null && Number(fees[0].chargeableQuantity) === Number(quantity) && money(fees[0].unitPrice) === money(expected.cargo.unitPrice) && fees[0].quantityUnit === (expected.cargo.priceBasis === 'PALLET' ? '托位' : '单') && fees[0].businessNo === proof.deliveryNo && delivery.fees.some(f => id(f.id) === id(fees[0].id))) proof.customerId = id(booking.customerId); proof.sourceFees = fees; proof.transferRequest = execution.confirmationRequest proof.finalTripVersion = (await io.get(base, '交接同车资源版本')).trip.recordVersion proof.financeAcknowledgement = 'NOT_VERIFIED'; proof.status = 'VERIFIED'; await context.checkpoint?.(); return proof } catch (error) { proof.status = proof.outcomeUnknown || context.createdResources.dispatchWriteBoundary?.outcomeUnknown ? 'OUTCOME_UNKNOWN' : 'UNPROVEN'; proof.error = error.message; await context.checkpoint?.(); throw error } }