Files

89 lines
7.7 KiB
JavaScript

import { prepareLooseboardPool } from './looseboard-pool-preparation.mjs'
import { receiveLooseboardPool } from './looseboard-pool-receipt.mjs'
import { arrangeLooseboardPool } from './looseboard-pool-dispatch.mjs'
import { dispatchIo, version } from './grouped-dispatch-acceptance.mjs'
/** 默认主链复用原散板原车;测量仅为明确TEST样本,不代表生产实测。 */
export async function attachLooseboardToTrip(context, tripProof, pdaToken,
helpers = { prepare: prepareLooseboardPool, receive: receiveLooseboardPool, arrange: arrangeLooseboardPool }) {
const demand = context.createdResources?.phase4OverseasBusiness?.looseboard
if (!demand || demand.marker !== context.marker || demand.finalStatus !== 'PENDING_ACCEPTANCE'
|| !context.createdResources?.fleetTrips?.includes(tripProof) || !pdaToken || demand.fulfillment) throw Error('默认散板履约缺少本轮待接车原单或已执行')
const proof = demand.fulfillment = { marker: context.marker, status: 'ATTACHING', requests: [] }
const io = dispatchIo(context, 'phase5-overseas-fulfillment', proof, 'LOOSEBOARD-MAIN')
const check = (name, ok) => io.check(name, null, Boolean(ok))
try {
const original = await io.command('GET', `/api/platform/v1/last-leg/local-looseboard-orders/${encodeURIComponent(demand.orderNo)}?customerId=${encodeURIComponent(demand.customerId)}`, undefined, '读取原散板货量')
const row = original?.summary
check('原散板身份和货量可供受控实收', row?.orderNo === demand.orderNo && String(row.customerId) === String(demand.customerId)
&& row.status === 'PENDING_ACCEPTANCE' && Number.isSafeInteger(row.estimatedPalletCount) && row.estimatedPalletCount > 0
&& Number.isSafeInteger(row.packageCount) && row.packageCount >= row.estimatedPalletCount)
const destinationKey = tripProof.stops?.[1]?.destinationKey
check('原车存在明确卸货目的地', typeof destinationKey === 'string' && destinationKey.length > 0)
// 明确的两托三箱TEST样本,逐箱输入,不把整托数值或箱数推算成实际箱规。
check('受控箱规仅适用于本轮两托三箱样本', row.estimatedPalletCount === 2 && row.packageCount === 3)
const measurements = [
{ cartonCount: 2, lengthCm: 100, widthCm: 100, heightCm: 90, volumeCbm: 0.9, grossWeightKg: 40,
boxes: [{ length: 100, width: 100, height: 45, weight: 20, quantity: 1 }, { length: 100, width: 100, height: 45, weight: 20, quantity: 1 }] },
{ cartonCount: 1, lengthCm: 100, widthCm: 100, heightCm: 90, volumeCbm: 0.9, grossWeightKg: 40,
boxes: [{ length: 100, width: 100, height: 90, weight: 40, quantity: 1 }] },
]
proof.measurements = { kind: 'CONTROLLED_TEST_MEASUREMENT', measurements }
await helpers.prepare(context, { demand, tripProof, destinationKey, unitPrice: 25 })
await helpers.receive(context, { demand, tripProof, pdaToken, measurements })
const ledger = await io.command('GET', `/api/platform/v1/last-leg/inventory/ledger?${new URLSearchParams({
customerId: String(demand.customerId), overseasWarehouseId: String(tripProof.warehouseId),
businessOrderNo: demand.receipt.incomingNo, businessType: 'STOCK_IN', page: '1', size: '200',
})}`, undefined, '独立核验原散板来货入库流水')
check('散板实收仅产生足量且唯一的本票入库流水', Array.isArray(ledger?.records) && ledger.records.length > 0
&& Number(ledger.total) === ledger.records.length && new Set(ledger.records.map(r => String(r.id))).size === ledger.records.length
&& ledger.records.every(r => /^[1-9]\d*$/.test(String(r.id)) && r.businessOrderNo === demand.receipt.incomingNo
&& r.businessType === 'STOCK_IN' && String(r.customerId) === String(demand.customerId)
&& String(r.overseasWarehouseId) === String(tripProof.warehouseId)
&& Number.isSafeInteger(Number(r.deltaPackages)) && Number(r.deltaPackages) > 0)
&& ledger.records.reduce((sum, r) => sum + Number(r.deltaPackages), 0) === row.packageCount)
proof.stockInLedger = ledger.records
const arranged = await helpers.arrange(context, { demand, tripProof })
check('原散板全部实收货物已经加入本车', arranged?.status === 'BOUND_AWAITING_REVIEW' && arranged.dispatchNo === tripProof.dispatchNo
&& arranged.addedItems?.length === measurements.length && new Set(arranged.addedItems.map(r => r.sourceKey)).size === measurements.length)
const path = `/api/platform/v1/last-leg/dispatches/${encodeURIComponent(tripProof.dispatchNo)}`
let current = await io.command('GET', path, undefined, '散板加入后重新审核原车')
check('加货后仅在原车装车前重新审核', current?.dispatchNo === tripProof.dispatchNo && current.stage === 'BEFORE_LOADING'
&& Number(current.counts?.pickedPackageCount) === 0)
for (const action of ['submit-review', 'dispatch-review', 'loading-review']) {
current = await io.command('POST', `${path}/${action}`, { recordVersion: version(current.recordVersion), idempotencyKey: io.key(action),
...(action === 'submit-review' ? { remark: context.marker } : { decision: 'APPROVED', reason: context.marker }) }, `散板加货后${action}`)
}
const reviewed = await io.command('GET', path, undefined, '核对加货后两级审核及原车资源')
check('加货审核后原车资源不变且进入装车', reviewed?.stage === 'LOADING' && reviewed.reviewState === 'APPROVED'
&& reviewed.reviews?.dispatchReview?.status === 'APPROVED' && reviewed.reviews?.loadingReview?.status === 'APPROVED'
&& String(reviewed.vehicleInfo?.vehicle?.vehicleId) === String(tripProof.vehicleId)
&& String(reviewed.vehicleInfo?.driver?.driverId) === String(tripProof.driverId))
proof.status = 'READY_FOR_SHARED_LOADING'
proof.sourceKeys = arranged.addedItems.map(row => row.sourceKey)
proof.incomingNo = demand.receipt.incomingNo
await context.checkpoint()
return { proof, detail: reviewed }
} catch (error) {
proof.status = 'FAILED'; proof.failure = error.message; await context.checkpoint(); throw error
}
}
/** 同车正常签收后回查原散板商业单,不能用整车状态代替原单履约。 */
export async function verifyLooseboardDelivered(context, tripProof, deliveries) {
const demand = context.createdResources?.phase4OverseasBusiness?.looseboard, proof = demand?.fulfillment
if (demand?.marker !== context.marker || proof?.status !== 'READY_FOR_SHARED_LOADING') throw Error('缺少本轮原散板装车前证据')
const io = dispatchIo(context, 'phase5-overseas-fulfillment', proof, 'LOOSEBOARD-COMPLETE')
const detail = await io.command('GET', `/api/platform/v1/last-leg/local-looseboard-orders/${encodeURIComponent(demand.orderNo)}?customerId=${encodeURIComponent(demand.customerId)}`, undefined, '签收后回查原散板终态')
const row = detail?.summary, arrangement = row?.fleetArrangement
const passed = row?.orderNo === demand.orderNo && String(row.customerId) === String(demand.customerId) && row.status === 'COMPLETED'
&& arrangement?.fulfillmentStage === 'COMPLETED' && arrangement.bookingNo === demand.poolBinding.bookingNo
&& arrangement.tripNo === tripProof.tripNo && arrangement.dispatchNo === tripProof.dispatchNo
&& arrangement.incomingNo === demand.receipt.incomingNo && Number(arrangement.signedPackages) === demand.poolBinding.createRequest.cargo.packageCount
&& deliveries.some(d => d.deliveryNo === arrangement.deliveryNo && String(d.customerId) === String(demand.customerId))
io.check('原散板通过真实同车签收完成且数量守恒', null, Boolean(passed))
Object.assign(proof, { status: 'COMPLETED_HTTP_VERIFIED', deliveryNo: arrangement.deliveryNo })
demand.finalStatus = 'COMPLETED'
await context.checkpoint()
}