Files
baiyue-firstleg/scripts/admin-test-env-modules/fleet-fee-coverage-acceptance.mjs
2026-10-21 14:30:41 +08:00

86 lines
7.3 KiB
JavaScript

import { DISPATCH_BASE as BASE, id as stringId, dispatchIo, readDispatch } from './grouped-dispatch-acceptance.mjs'
import { pickAll } from './phase5-overseas-fulfillment.mjs'
import { confirmFleetLoading } from './fleet-trip-fulfillment.mjs'
export const stableRows = rows => JSON.stringify(rows.map(row => JSON.stringify(row)).sort())
const id = value => { if (typeof value === 'number' && !Number.isSafeInteger(value)) throw Error('服务端ID精度无效,必须保留Long字符串'); return stringId(value) }
export function fleetConsumerIo(context, module, proof, namespace) {
const io = dispatchIo(context, module, proof, namespace)
const request = async (method, path, body, label, expected) => {
const response = await io.send(method, path, body, label)
io.check(`${label}诊断回执完整`, response, Boolean(response?.requestId) && Boolean(response?.traceId))
io.check(label, response, expected ? [200, 409].includes(response.http) && String(response.body?.code) === 'A0409' && expected.test(String(response.body?.message))
: response.http === 200 && String(response.body?.code) === '200')
return response.body?.data
}
return { ...io, request, get: (path, label) => request('GET', path, undefined, label) }
}
export async function readyFleetDispatch(context, module, io, trip, { pickerToken, actualMeasurements } = {}) {
io.check('真实履约写入须有正式持久化检查点', null, typeof context.checkpoint === 'function')
let current = await readDispatch(io, trip.dispatchNo)
io.check('本轮实车仓库及未发车范围', null, context.createdResources.fleetTrips?.includes(trip) && trip.tripNo
&& current.warehouseId === id(trip.warehouseId) && current.data.status === 'ACTIVE' && ['LOADING', 'DELIVERING'].includes(current.data.stage))
if (current.data.stage === 'LOADING') {
io.check('真实扫码需本轮PDA身份', null, typeof pickerToken === 'string' && pickerToken.length > 0)
io.check('PDA逐来源真实扫码完成', null, await pickAll(context, pickerToken, trip.dispatchNo))
await confirmFleetLoading(context, module, trip, pickerToken, actualMeasurements)
current = await readDispatch(io, trip.dispatchNo)
}
io.check('装车完成后自动派送并保留真实拣货', null, current.data.stage === 'DELIVERING' && current.data.pickingReconciled === true
&& Number(current.data.counts.packageCount) > 0 && Number(current.data.counts.pickedPackageCount) === Number(current.data.counts.packageCount)
&& current.items.items.every(i => Number(i.pickedPackages) === Number(i.scheduledPackages)))
return current
}
/** 装车完成已生成单据并发车;仅读权威单据,不补人工费用或覆盖选择。 */
export async function readAutomaticDelivery(io, current, trip) {
const documents = current.data.documents
const deliveries = documents?.deliveries, outbounds = documents?.outbounds
io.check('自动派送单据完整且客户箱数守恒', null, current.data.stage === 'DELIVERING'
&& Array.isArray(deliveries) && deliveries.length > 0 && Array.isArray(outbounds) && outbounds.length > 0
&& new Set(deliveries.map(d => d.deliveryNo)).size === deliveries.length
&& new Set(outbounds.map(o => o.outboundNo)).size === outbounds.length
&& deliveries.every(d => d.deliveryNo && d.status === 'DELIVERING' && outbounds.some(o => o.outboundNo === d.outboundNo && id(o.customerId) === id(d.customerId)))
&& outbounds.every(o => o.outboundNo && o.status === 'OUTBOUNDED')
&& deliveries.reduce((sum, d) => sum + Number(d.packageCount), 0) === Number(current.data.counts.packageCount)
&& current.items.items.every(i => deliveries.some(d => id(d.customerId) === id(i.customerId) && d.destination?.key === i.destination?.key)))
const snapshots = {}, feeRows = {}
for (const ref of deliveries) {
const detail = await io.get(`/api/platform/v1/last-leg/delivery-orders/${encodeURIComponent(ref.deliveryNo)}?customerId=${encodeURIComponent(id(ref.customerId))}`, '回查自动生成派送的客户、目的地和费用事实')
io.check('自动生成单据归属和货量正确', null, detail?.order?.dispatchNo === trip.dispatchNo && detail.order.deliveryNo === ref.deliveryNo
&& id(detail.order.customerId) === id(ref.customerId) && detail.order.outboundNo === ref.outboundNo
&& Number(detail.order.packageCount) === Number(ref.packageCount) && detail.order.status === 'DELIVERING'
&& Array.isArray(detail.fees) && new Set(detail.fees.map(f => id(f.id))).size === detail.fees.length)
snapshots[ref.deliveryNo] = detail.feeCoverage; feeRows[ref.deliveryNo] = stableRows(detail.fees)
}
const command = trip.loadingCommand
io.check('本轮装车成功回执完整', null, command?.body?.idempotencyKey && command.result?.action === 'LOADING_COMPLETE'
&& command.result.dispatchNo === trip.dispatchNo && command.result.stage === 'DELIVERING')
return { outbounds, deliveries, snapshots, feeRows, confirmationRequest: command.body, command: command.result }
}
export async function verifyFleetFeeCoverage(context, module, source, { reconcilePersisted = false, pickerToken, actualMeasurements, externalTrip } = {}) {
const cases = context.createdResources?.fleetFeeCoverageCases
if (!cases?.includes(source) || source.marker !== context.marker || !['FULL', 'PARTIAL'].includes(source.mode) || source.proof && !reconcilePersisted)
throw Error('验收需要本轮专用、未执行排车用例;保留原请求')
const pair = ['FULL', 'PARTIAL'].map(mode => cases.filter(c => c.marker === context.marker && c.mode === mode))
if (pair.some(rows => rows.length !== 1) || pair[0][0].dispatchNo === pair[1][0].dispatchNo) throw Error('需本轮两组不重叠真实排车')
const proof = reconcilePersisted ? source.proof : source.proof = { marker: context.marker, status: 'CHECKING', mode: source.mode, dispatchNo: source.dispatchNo, requests: [] }
const io = fleetConsumerIo(context, module, proof, `AUTO-${source.mode}`)
try {
const scopes = []
for (const [candidate] of pair) scopes.push(await readDispatch(io, candidate.dispatchNo))
io.check('两组真实来源和实托均不重叠', null, scopes.every(s => s.items.items.length > 0) && scopes[0].items.items.every(i => !scopes[1].items.items.some(j => id(i.warehousePalletId) === id(j.warehousePalletId) || i.sourceKey === j.sourceKey)))
const trip = context.createdResources.fleetTrips?.find(t => t.dispatchNo === source.dispatchNo)
io.check('本排车有本轮生产回执', null, Boolean(trip) && (!externalTrip || externalTrip === trip))
const current = await readyFleetDispatch(context, module, io, trip, { pickerToken, actualMeasurements })
const execution = await readAutomaticDelivery(io, current, trip)
if (reconcilePersisted) io.check('恢复仅读原生成单据集合', null, stableRows(proof.deliveryNos) === stableRows(execution.deliveries.map(d => d.deliveryNo)))
Object.assign(proof, execution, { outboundNo: execution.outbounds.find(o => id(o.customerId) === id(source.customerId))?.outboundNo,
deliveryNos: execution.deliveries.map(d => d.deliveryNo).sort(), feeCoverageSelection: 'NOT_APPLICABLE_AUTOMATIC_DELIVERY', status: 'VERIFIED' })
await context.checkpoint?.(); return proof
} catch (error) { proof.status = proof.outcomeUnknown ? 'OUTCOME_UNKNOWN' : 'UNPROVEN'; proof.error = error.message; await context.checkpoint?.(); throw error }
}