feat(dispatch): unify whole vehicle scheduling, source ownership and staged execution
This commit is contained in:
@@ -25,7 +25,8 @@ function requireRun(context, tripProof) {
|
||||
const run = context.createdResources?.fleetAcceptance
|
||||
if (!context.marker || !context.createdResources?.fleetTrips?.includes(tripProof) || !tripProof?.tripNo || !tripProof.marker
|
||||
|| run?.marker !== context.marker || run.tripNo !== tripProof.tripNo
|
||||
|| run.departure?.tripNo !== tripProof.tripNo || run.departure.status !== 'IN_TRANSIT' || !run.departure.departedAt)
|
||||
|| run.departure?.dispatchNo !== tripProof.dispatchNo || run.departure.status !== 'ACTIVE'
|
||||
|| run.departure.stage !== 'DELIVERING' || !run.departure.result?.departedAt || !count(run.departure.result.shippedPackages))
|
||||
throw new Error('在途交接仅接受当前marker登记的原车次及真实DEPART结果,禁止借用旧样本')
|
||||
return run
|
||||
}
|
||||
@@ -60,10 +61,17 @@ function io(context, module, proof) {
|
||||
}
|
||||
return { check, request }
|
||||
}
|
||||
function dispatchFacts(row) {
|
||||
const fields = ['dispatchNo', 'overseasWarehouseId', 'status', 'packageCount', 'pickedPackageCount', 'shippedPackageCount',
|
||||
'vehiclePlate', 'driverName', 'driverPhone', 'outboundNo', 'departAt', 'estimatedArrivalAt', 'items']
|
||||
return Object.fromEntries(fields.filter(name => row[name] !== undefined).map(name => [name, row[name]]))
|
||||
function cargoFacts(values) {
|
||||
const totals = new Map()
|
||||
for (const row of values) {
|
||||
const facts = { itemId: text(row.dispatchItemId), customerId: text(row.customerId), palletId: text(row.warehousePalletId),
|
||||
destinationKey: row.destination?.key ?? row.destinationKey }
|
||||
if (!facts.itemId || !facts.customerId || !facts.palletId || !facts.destinationKey) throw new Error('原生货物缺少真实明细客户或目的地')
|
||||
const identity = stable(facts), packages = Number(row.scheduledPackages ?? row.packages)
|
||||
if (!count(packages)) throw new Error('原生货物缺少有效真实箱数')
|
||||
totals.set(identity, { ...facts, packages: (totals.get(identity)?.packages || 0) + packages })
|
||||
}
|
||||
return [...totals.values()].sort((a, b) => stable(a).localeCompare(stable(b)))
|
||||
}
|
||||
function feeFacts(rows) {
|
||||
if (!Array.isArray(rows)) throw new Error('费用来源不是正式列表')
|
||||
@@ -85,10 +93,9 @@ export async function verifyFleetHandover(context, module, tripProof, pickerToke
|
||||
if (tripProof.handover || !pickerToken || !Array.isArray(dispatchDetails) || !dispatchDetails.length)
|
||||
throw new Error('交接验收缺少本轮PDA或排车依据,或已开始;保留原请求,不自动续跑')
|
||||
const numbers = dispatchDetails.map(row => row.dispatchNo)
|
||||
if (new Set(numbers).size !== numbers.length || numbers.some(number => !number)
|
||||
|| !same([...numbers].sort(), run.departure.dispatches?.map(row => row.dispatchNo).sort())
|
||||
|| !dispatchDetails.every(row => count(row.packageCount))
|
||||
|| Number(run.departure.shippedPackages) !== dispatchDetails.reduce((sum, row) => sum + Number(row.packageCount), 0))
|
||||
if (numbers.length !== 1 || numbers[0] !== tripProof.dispatchNo || numbers[0] !== run.departure.dispatchNo
|
||||
|| !dispatchDetails.every(row => count(row.counts?.packageCount))
|
||||
|| Number(run.departure.result.shippedPackages) !== dispatchDetails.reduce((sum, row) => sum + Number(row.counts.packageCount), 0))
|
||||
throw new Error('交接输入排车清单及箱数未覆盖本轮真实发车')
|
||||
const proof = tripProof.handover = { marker: context.marker, fixtureMarker: tripProof.marker, status: 'CHECKING', requests: [], probes: [],
|
||||
dispatchNos: [...numbers], stockEvidence: 'CALLER_FULL_CHAIN_INVENTORY_AUDIT_REQUIRED' }
|
||||
@@ -96,7 +103,7 @@ export async function verifyFleetHandover(context, module, tripProof, pickerToke
|
||||
try {
|
||||
const detail = await request('GET', base, undefined, '读取真实在途车次及分配资源')
|
||||
check('真实车次属于本轮且已经发车', detail?.trip?.tripNo === tripProof.tripNo && detail.trip.status === 'IN_TRANSIT'
|
||||
&& detail.trip.departedAt === run.departure.departedAt && text(detail.trip.vehicleId) === text(tripProof.vehicleId)
|
||||
&& detail.trip.departedAt === run.departure.result.departedAt && text(detail.trip.vehicleId) === text(tripProof.vehicleId)
|
||||
&& text(detail.trip.driverId) === text(tripProof.driverId) && text(detail.trip.warehouseId) === text(tripProof.warehouseId)
|
||||
&& validVersion(detail.trip.recordVersion) && same(detail.dispatches?.map(row => row.dispatchNo).sort(), [...numbers].sort()))
|
||||
const initialHistory = await request('GET', `${base}/history?page=1&size=200`, undefined, '读取不可变原DEPART证据')
|
||||
@@ -109,26 +116,36 @@ export async function verifyFleetHandover(context, module, tripProof, pickerToke
|
||||
&& text(original.trip.vehicleId) === text(detail.trip.vehicleId) && text(original.trip.driverId) === text(detail.trip.driverId)
|
||||
&& original.confirmedLoad?.cargo?.length > 0 && original.confirmedLoad?.pallets?.length > 0)
|
||||
proof.previous = { vehicleId: text(detail.trip.vehicleId), driverId: text(detail.trip.driverId), ...transport(detail.trip) }
|
||||
proof.dispatchBefore = []; proof.pdaBefore = []; proof.feesBefore = []
|
||||
proof.dispatchBefore = []; proof.pdaBefore = []; proof.pdaTasksBefore = []; proof.feesBefore = []
|
||||
for (const expected of dispatchDetails) {
|
||||
const row = await request('GET', `${DISPATCHES}/${encodeURIComponent(expected.dispatchNo)}`, undefined, '核对客户排车已经真实出库')
|
||||
const binding = detail.dispatches.find(item => item.dispatchNo === expected.dispatchNo)
|
||||
const items = await request('GET', `${DISPATCHES}/${encodeURIComponent(expected.dispatchNo)}/items`, undefined, '读取原生主单逐客户真实货物')
|
||||
const cargo = original.confirmedLoad.cargo.filter(item => item.dispatchNo === expected.dispatchNo)
|
||||
const items = values => values.map(item => ({ itemId: text(item.id ?? item.dispatchItemId), palletId: text(item.warehousePalletId),
|
||||
packages: Number(item.packageCount ?? item.packages) })).sort((a, b) => a.itemId.localeCompare(b.itemId))
|
||||
check('每张排车已发车且货量与客户起运仓一致', row?.dispatchNo === expected.dispatchNo && row.status === 'DEPARTED'
|
||||
&& Number(row.shippedPackageCount) === Number(expected.packageCount) && Number(row.pickedPackageCount) === Number(expected.packageCount)
|
||||
&& binding?.customerId && cargo.length > 0 && cargo.every(item => text(item.customerId) === text(binding.customerId))
|
||||
&& same(items(row.items || []), items(cargo)) && text(row.overseasWarehouseId) === text(tripProof.warehouseId))
|
||||
proof.dispatchBefore.push(dispatchFacts(row))
|
||||
check('每张排车已发车且货量与客户起运仓一致', row?.dispatchNo === expected.dispatchNo && row.status === 'ACTIVE' && row.stage === 'DELIVERING'
|
||||
&& Number(row.counts?.shippedPackageCount) === Number(expected.counts.packageCount) && Number(row.counts?.pickedPackageCount) === Number(expected.counts.packageCount)
|
||||
&& items.dispatchNo === row.dispatchNo && text(items.recordVersion) === text(row.recordVersion) && cargo.length > 0
|
||||
&& same(cargoFacts(items.items || []), cargoFacts(cargo)) && row.documents?.deliveries?.length > 0)
|
||||
proof.dispatchBefore.push({ dispatchNo: row.dispatchNo, detail: row, items })
|
||||
const task = await request('GET', `/api/pda/v1/last-leg/dispatches/${encodeURIComponent(row.dispatchNo)}`, undefined, 'PDA读取原生整车任务和客户来源', { token: pickerToken })
|
||||
check('PDA原生作业与整车真实客户货物核平', task.dispatchNo === row.dispatchNo && task.status === 'ACTIVE' && task.stage === 'DELIVERING'
|
||||
&& text(task.warehouseId) === text(tripProof.warehouseId) && text(task.recordVersion) === text(row.recordVersion)
|
||||
&& same(task.counts, row.counts) && task.pickingReconciled === true
|
||||
&& same(task.items?.map(i => ({ dispatchItemId: text(i.dispatchItemId), customerId: text(i.customerId), scheduledPackages: i.scheduledPackages, pickedPackages: i.pickedPackages }))
|
||||
.sort((a,b) => a.dispatchItemId.localeCompare(b.dispatchItemId)),
|
||||
items.items.map(i => ({ dispatchItemId: text(i.dispatchItemId), customerId: text(i.customerId), scheduledPackages: i.scheduledPackages, pickedPackages: i.pickedPackages }))
|
||||
.sort((a,b) => a.dispatchItemId.localeCompare(b.dispatchItemId))))
|
||||
proof.pdaTasksBefore.push(task)
|
||||
const pda = await request('GET', `/api/pda/v1/last-leg/dispatches/${encodeURIComponent(row.dispatchNo)}/handover-status`, undefined, 'PDA读取原运输资源', { token: pickerToken })
|
||||
check('PDA原资源与未签收货物对应当前排车', pda?.dispatchNo === row.dispatchNo && pda.tripNo === tripProof.tripNo
|
||||
&& pda.latestHandover == null && same(transport(pda.originalDeparture), transport(original.trip)) && pda.deliveries?.length > 0
|
||||
&& pda.deliveries.every(item => !item.terminalAt && item.status === 'WAIT_DELIVERY' && same(transport(item.transport), transport(detail.trip)))
|
||||
&& pda.deliveries.reduce((sum, item) => sum + Number(item.packages), 0) === Number(expected.packageCount))
|
||||
&& pda.deliveries.every(item => !item.terminalAt && item.status === 'DELIVERING' && same(transport(item.transport), transport(detail.trip)))
|
||||
&& pda.deliveries.reduce((sum, item) => sum + Number(item.packages), 0) === Number(expected.counts.packageCount)
|
||||
&& same(pda.deliveries.map(i => i.deliveryNo).sort(), row.documents.deliveries.map(i => i.deliveryNo).sort()))
|
||||
proof.pdaBefore.push(pda)
|
||||
for (const delivery of pda.deliveries) {
|
||||
const path = `/api/platform/v1/last-leg/finance-fee-sources?${new URLSearchParams({ customerId: text(binding.customerId), businessType: 'DELIVERY_ORDER', businessNo: delivery.deliveryNo })}`
|
||||
const reference = row.documents.deliveries.find(i => i.deliveryNo === delivery.deliveryNo)
|
||||
check('派送费用严格沿真实主单逐客户单据归属', reference?.customerId && cargo.some(i => text(i.customerId) === text(reference.customerId)))
|
||||
const path = `/api/platform/v1/last-leg/finance-fee-sources?${new URLSearchParams({ customerId: text(reference.customerId), businessType: 'DELIVERY_ORDER', businessNo: delivery.deliveryNo })}`
|
||||
proof.feesBefore.push({ path, facts: feeFacts(await request('GET', path, undefined, '冻结本次派送费用来源')) })
|
||||
}
|
||||
}
|
||||
@@ -214,8 +231,12 @@ export async function verifyFleetHandover(context, module, tripProof, pickerToke
|
||||
await request('POST', `${base}/handovers`, proof.staleRequest, '陈旧版本交接必须拒绝', { conflict: /车次.*(变化|修改)|版本/ })
|
||||
check('陈旧命令没有改变车次版本或资源', same(await request('GET', base, undefined, '拒绝后核对车次零变更'), after))
|
||||
const firstProbe = await createProbe(1)
|
||||
const assignment = { recordVersion: firstProbe.recordVersion, vehicleId: proof.previous.vehicleId, driverId: proof.previous.driverId, reason: '本轮旧资源占用核对' }
|
||||
await request('POST', `${pathFor(firstProbe.tripNo)}/assignment`, assignment, '交接后旧车辆仍被原车次占用', { conflict: /车辆.*占用/ })
|
||||
const assignment = { recordVersion: firstProbe.recordVersion, idempotencyKey: key(),
|
||||
vehicle: { mode: 'MASTER', vehicleId: proof.previous.vehicleId },
|
||||
driver: { mode: 'MASTER', driverId: proof.previous.driverId, evidence: { expectedTaskEndAt: detail.trip.expectedDriverEndAt } },
|
||||
plannedDepartureAt: new Date().toISOString(), estimatedArrivalAt: null, remark: `${context.marker}本轮旧资源占用核对` }
|
||||
firstProbe.assignmentRequest = clone(assignment)
|
||||
await request('PUT', `${DISPATCHES}/${encodeURIComponent(firstProbe.dispatchNo)}`, assignment, '交接后旧车辆仍被原车次占用', { conflict: /车辆.*占用/ })
|
||||
const releaseBase = `${base}/handovers/${encodeURIComponent(handover.handoverNo)}/resources/release`
|
||||
proof.vehicleReleaseRequest = { recordVersion: handover.recordVersion, resourceKind: 'VEHICLE', actualEndAt: new Date().toISOString(), location: `${context.marker}车辆任务结束点`, vehicleStatus: 'IN_SERVICE', idempotencyKey: key() }
|
||||
const vehicleReleased = await request('POST', releaseBase, proof.vehicleReleaseRequest, '单独结束交接前车辆任务')
|
||||
@@ -225,7 +246,7 @@ export async function verifyFleetHandover(context, module, tripProof, pickerToke
|
||||
&& vehicleReleased.vehicleRelease?.idempotencyKey === proof.vehicleReleaseRequest.idempotencyKey
|
||||
&& vehicleReleased.vehicleRelease?.location === proof.vehicleReleaseRequest.location && vehicleReleased.vehicleRelease?.vehicleStatus === 'IN_SERVICE'
|
||||
&& vehicleReleased.driverRelease == null)
|
||||
await request('POST', `${pathFor(firstProbe.tripNo)}/assignment`, assignment, '只释放车辆后旧司机仍被占用', { conflict: /司机.*占用/ })
|
||||
await request('PUT', `${DISPATCHES}/${encodeURIComponent(firstProbe.dispatchNo)}`, assignment, '只释放车辆后旧司机仍被占用', { conflict: /司机.*占用/ })
|
||||
proof.driverReleaseRequest = { recordVersion: vehicleReleased.recordVersion, resourceKind: 'DRIVER', actualEndAt: new Date().toISOString(), location: `${context.marker}司机任务结束点`, idempotencyKey: key() }
|
||||
const released = await request('POST', releaseBase, proof.driverReleaseRequest, '单独结束交接前司机任务')
|
||||
check('原车辆司机分别保留结束事实', released.handoverNo === handover.handoverNo && released.tripNo === tripProof.tripNo
|
||||
@@ -234,20 +255,53 @@ export async function verifyFleetHandover(context, module, tripProof, pickerToke
|
||||
&& Date.parse(released.driverRelease?.actualEndAt) === Date.parse(proof.driverReleaseRequest.actualEndAt)
|
||||
&& released.driverRelease?.idempotencyKey === proof.driverReleaseRequest.idempotencyKey
|
||||
&& released.driverRelease?.location === proof.driverReleaseRequest.location)
|
||||
const assigned = await request('POST', `${pathFor(firstProbe.tripNo)}/assignment`, assignment, '将已释放原资源分配给本轮无货计划')
|
||||
firstProbe.recordVersion = assigned.trip.recordVersion
|
||||
check('原资源真正可重新分配', text(assigned.trip.vehicleId) === proof.previous.vehicleId && text(assigned.trip.driverId) === proof.previous.driverId)
|
||||
const assigned = await request('PUT', `${DISPATCHES}/${encodeURIComponent(firstProbe.dispatchNo)}`, assignment, '将已释放原资源分配给本轮无货计划')
|
||||
check('原资源真正可重新分配', assigned.dispatchNo === firstProbe.dispatchNo && assigned.status === 'ACTIVE' && assigned.stage === 'BEFORE_LOADING'
|
||||
&& BigInt(text(assigned.recordVersion)) > BigInt(text(firstProbe.recordVersion)))
|
||||
firstProbe.recordVersion = assigned.recordVersion
|
||||
const candidateRows = []; let candidateTotal
|
||||
for (let page = 1; ; page++) {
|
||||
const candidates = await request('GET', `${FLEET}/trips?${new URLSearchParams({ keyword: proof.previous.vehiclePlate,
|
||||
warehouseId: text(tripProof.warehouseId), page: text(page), size: '200' })}`, undefined, '定位本轮空主单自动Fleet资源记录')
|
||||
check('资源候选分页完整且稳定', Array.isArray(candidates.records) && Number(candidates.page) === page && Number(candidates.size) === 200
|
||||
&& Number.isSafeInteger(Number(candidates.total)) && Number(candidates.total) >= 0
|
||||
&& (candidateTotal === undefined || candidateTotal === Number(candidates.total)))
|
||||
candidateTotal ??= Number(candidates.total)
|
||||
check('资源候选未缺页', candidates.records.length > 0 || candidateRows.length === candidateTotal)
|
||||
candidateRows.push(...candidates.records); if (candidateRows.length >= candidateTotal) break
|
||||
}
|
||||
check('资源候选总数和业务号无重复', candidateRows.length === candidateTotal && new Set(candidateRows.map(i => i.tripNo)).size === candidateRows.length)
|
||||
const matches = []
|
||||
for (const row of candidateRows.filter(row => text(row.vehicleId) === proof.previous.vehicleId && text(row.driverId) === proof.previous.driverId
|
||||
&& row.vehiclePlate === proof.previous.vehiclePlate && text(row.warehouseId) === text(tripProof.warehouseId))) {
|
||||
const candidate = await request('GET', pathFor(row.tripNo), undefined, '核对新资源记录与原生空单唯一绑定')
|
||||
if (candidate.dispatches?.some(i => i.dispatchNo === firstProbe.dispatchNo)) {
|
||||
check('新资源记录只能绑定本轮唯一空主单', candidate.trip?.tripNo === row.tripNo && candidate.dispatches.length === 1
|
||||
&& text(candidate.trip.vehicleId) === proof.previous.vehicleId && text(candidate.trip.driverId) === proof.previous.driverId)
|
||||
matches.push(candidate)
|
||||
}
|
||||
}
|
||||
check('原资源真正可重新分配且唯一定位', matches.length === 1 && matches[0].trip.tripNo !== tripProof.tripNo)
|
||||
firstProbe.tripNo = matches[0].trip.tripNo
|
||||
const persistedReleases = await request('GET', `${base}/handovers?page=1&size=10`, undefined, '读取持久化资源结束记录')
|
||||
const releaseSnapshot = persistedReleases.records?.find(row => row.handoverNo === proof.handoverNo)
|
||||
check('原车辆结束重放不改记录', same(await request('POST', releaseBase, proof.vehicleReleaseRequest, '新计划占用后重放旧车辆结束请求'), releaseSnapshot))
|
||||
check('原司机结束重放不改记录', same(await request('POST', releaseBase, proof.driverReleaseRequest, '新计划占用后重放旧司机结束请求'), releaseSnapshot))
|
||||
const secondProbe = await createProbe(2)
|
||||
await request('POST', `${pathFor(secondProbe.tripNo)}/assignment`, { ...assignment, recordVersion: secondProbe.recordVersion }, '旧请求重放不能释放下一计划车辆', { conflict: /车辆.*占用/ })
|
||||
const secondAssignment = { ...assignment, idempotencyKey: key(), recordVersion: secondProbe.recordVersion }
|
||||
await request('PUT', `${DISPATCHES}/${encodeURIComponent(secondProbe.dispatchNo)}`, secondAssignment, '旧请求重放不能释放下一计划车辆', { conflict: /车辆.*占用/ })
|
||||
const spareBody = { ...vehicleBody, plateNumber: `HO-PROBE-${randomUUID().slice(0, 10).toUpperCase()}` }
|
||||
const spare = await request('POST', VEHICLES, spareBody, '创建仅用于司机占用隔离的本轮车辆')
|
||||
check('隔离车辆已创建', spare?.id); proof.spareVehicleId = text(spare.id)
|
||||
await request('POST', `${pathFor(secondProbe.tripNo)}/assignment`, { ...assignment, vehicleId: spare.id, recordVersion: secondProbe.recordVersion }, '旧请求重放不能释放下一计划司机', { conflict: /司机.*占用/ })
|
||||
for (const expected of proof.dispatchBefore) check('交接不改变原排车出库和货物归属', same(dispatchFacts(await request('GET', `${DISPATCHES}/${encodeURIComponent(expected.dispatchNo)}`, undefined, '核对排车原始出库事实')), expected))
|
||||
await request('PUT', `${DISPATCHES}/${encodeURIComponent(secondProbe.dispatchNo)}`, { ...secondAssignment, idempotencyKey: key(), vehicle: { mode: 'MASTER', vehicleId: spare.id } }, '旧请求重放不能释放下一计划司机', { conflict: /司机.*占用/ })
|
||||
for (const expected of proof.dispatchBefore) {
|
||||
check('交接不改变原排车出库和货物归属', same(await request('GET', `${DISPATCHES}/${encodeURIComponent(expected.dispatchNo)}`, undefined, '核对原生排车出库事实'), expected.detail)
|
||||
&& same(await request('GET', `${DISPATCHES}/${encodeURIComponent(expected.dispatchNo)}/items`, undefined, '核对原生排车逐客户货物事实'), expected.items))
|
||||
}
|
||||
for (const previous of proof.pdaTasksBefore) {
|
||||
const current = await request('GET', `/api/pda/v1/last-leg/dispatches/${encodeURIComponent(previous.dispatchNo)}`, undefined, 'PDA回查原生整车版本及扫码归属未变化', { token: pickerToken })
|
||||
check('交接不改变PDA原生整车版本及扫码归属', same(current, previous))
|
||||
}
|
||||
proof.pdaAfter = []
|
||||
for (const previous of proof.pdaBefore) {
|
||||
const current = await request('GET', `/api/pda/v1/last-leg/dispatches/${encodeURIComponent(previous.dispatchNo)}/handover-status`, undefined, 'PDA只读核对接替资源与原始发车', { token: pickerToken })
|
||||
@@ -270,16 +324,19 @@ export async function verifyFleetHandover(context, module, tripProof, pickerToke
|
||||
proof.status = 'VERIFIED'
|
||||
// Caller finishFleetTrip must finish the currently assigned resources, not the original pair.
|
||||
tripProof.vehicleId = proof.replacement.vehicleId; tripProof.driverId = proof.replacement.driverId; tripProof.licenseFileId = proof.replacement.licenseFileId
|
||||
tripProof.marker = proof.replacement.vehiclePlate
|
||||
return proof
|
||||
} catch (error) { proof.status = 'FAILED'; proof.failure = { message: error.message, requestCount: proof.requests.length }; throw error }
|
||||
|
||||
async function createProbe(index) {
|
||||
const saved = { marker: context.marker, index }; proof.probes.push(saved)
|
||||
const value = await request('POST', `${FLEET}/trips`, { warehouseId: tripProof.warehouseId, idempotencyKey: key(),
|
||||
plannedDepartureAt: new Date(Date.now() + 3600000).toISOString(), expectedDriverEndAt: new Date(Date.now() + 48 * 3600000).toISOString(),
|
||||
stops: tripProof.stops, remark: `${context.marker}在途交接资源验证无货计划${index}` }, '创建本轮无货资源核对计划')
|
||||
check('核对计划不承载货物且尚未发车', value?.trip?.tripNo && value.trip.status === 'PLANNED' && value.dispatches?.length === 0 && validVersion(value.trip.recordVersion))
|
||||
saved.tripNo = value.trip.tripNo; saved.recordVersion = value.trip.recordVersion; return saved
|
||||
saved.createRequest = { warehouseId: text(tripProof.warehouseId), idempotencyKey: key(), vehicle: null, driver: null,
|
||||
plannedDepartureAt: new Date().toISOString(), estimatedArrivalAt: null, cargo: [], batchAllocations: [],
|
||||
remark: `${context.marker}在途交接资源验证原生空排车${index}` }
|
||||
const value = await request('POST', DISPATCHES, saved.createRequest, '创建本轮无货资源核对计划')
|
||||
check('核对计划不承载货物且尚未发车', value?.dispatchNo && value.status === 'ACTIVE' && value.stage === 'BEFORE_LOADING'
|
||||
&& value.counts?.packageCount === 0 && value.counts?.pickedPackageCount === 0 && value.counts?.shippedPackageCount === 0 && validVersion(value.recordVersion))
|
||||
saved.dispatchNo = value.dispatchNo; saved.recordVersion = value.recordVersion; return saved
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,10 +352,15 @@ export async function cleanupFleetHandoverResources(context, module, tripProof)
|
||||
check('整车结案且接替车辆司机均已释放', current.trip?.status === 'COMPLETED' && current.trip.vehicleRelease && current.trip.driverRelease
|
||||
&& text(current.trip.vehicleId) === proof.replacement.vehicleId && text(current.trip.driverId) === proof.replacement.driverId)
|
||||
for (const probe of [...proof.probes].reverse()) {
|
||||
const path = pathFor(probe.tripNo), detail = await request('GET', path, undefined, '核对仅本轮无货资源计划')
|
||||
check('仅取消本轮未执行无货计划', detail.trip?.tripNo === probe.tripNo && detail.trip.status === 'PLANNED' && detail.trip.remark?.includes(context.marker) && detail.dispatches?.length === 0)
|
||||
const cancelled = await request('POST', `${path}/cancel`, { recordVersion: detail.trip.recordVersion, reason: '本轮交接验证完成,取消无货核对计划' }, '取消本轮资源核对计划')
|
||||
check('无货计划已取消', cancelled.trip?.status === 'CANCELLED')
|
||||
const path = `${DISPATCHES}/${encodeURIComponent(probe.dispatchNo)}`, detail = await request('GET', path, undefined, '核对仅本轮原生空排车')
|
||||
const items = await request('GET', `${path}/items`, undefined, '核对本轮空排车零实际货物')
|
||||
check('仅取消本轮未执行无货计划', detail.dispatchNo === probe.dispatchNo && detail.status === 'ACTIVE' && detail.stage === 'BEFORE_LOADING'
|
||||
&& detail.documents?.remark?.includes(context.marker) && text(detail.recordVersion) === text(probe.recordVersion)
|
||||
&& detail.counts?.itemCount === 0 && detail.counts?.packageCount === 0 && detail.counts?.pickedPackageCount === 0 && detail.counts?.shippedPackageCount === 0
|
||||
&& items.dispatchNo === probe.dispatchNo && text(items.recordVersion) === text(detail.recordVersion) && items.items?.length === 0)
|
||||
probe.cancelRequest = { recordVersion: detail.recordVersion, idempotencyKey: key(), reason: '本轮交接验证完成,取消原生空排车并保留历史' }
|
||||
const cancelled = await request('POST', `${path}/cancel`, probe.cancelRequest, '取消本轮资源核对计划')
|
||||
check('无货计划已取消', cancelled.dispatchNo === probe.dispatchNo && cancelled.status === 'CANCELLED')
|
||||
}
|
||||
for (const vehicleId of [proof.replacement.vehicleId, proof.spareVehicleId]) {
|
||||
const vehicle = await request('GET', `${VEHICLES}/${encodeURIComponent(vehicleId)}`, undefined, '读取本轮接替或隔离车辆版本')
|
||||
|
||||
Reference in New Issue
Block a user