feat(fleet): publish capacity from a single guided dialog
This commit is contained in:
@@ -8,11 +8,13 @@ import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import { getTripLoading, confirmTripLoading, getTripCapacityPlan, confirmTripCapacityPlan } from '../api/trips'
|
||||
import type { FleetTrip, TripLoading, TripCapacityPlanning, TripMeasurement } from '../tripTypes'
|
||||
|
||||
const props = defineProps<{ trip: FleetTrip; editable: boolean; planning?: boolean }>()
|
||||
const emit = defineEmits<{ saved: [loading: TripLoading]; planned: [plan: TripCapacityPlanning] }>()
|
||||
const props = defineProps<{ trip: FleetTrip; editable: boolean; planning?: boolean; embedded?: boolean }>()
|
||||
const emit = defineEmits<{ saved: [loading: TripLoading]; planned: [plan: TripCapacityPlanning]; changed: [value: string] }>()
|
||||
type PalletRow = { warehousePalletId: string; palletNo: string; destinations: string; destinationKeys: string[]; palletSpaces: number; weightKg: number | null; volumeCbm: number | null; deliveryIndex: number | null }
|
||||
const info = ref<TripLoading | TripCapacityPlanning | null>(null), rows = ref<PalletRow[]>([]), busy = ref(false), loading = ref(false), error = ref<ApiErrorDetails | null>(null)
|
||||
let sequence = 0
|
||||
const baseline = ref('')
|
||||
watch(rows, value => emit('changed', JSON.stringify(value)), { deep: true })
|
||||
const columns = computed<DataTableColumn[]>(() => [{ key: 'palletNo', prop: 'palletNo', label: '实物托盘', minWidth: 150 },
|
||||
{ key: 'destinations', prop: 'destinations', label: '货物目的地', minWidth: 160 }, { key: 'spaces', label: '占用标准托位', slotName: 'spaces', width: 175 },
|
||||
{ key: 'weight', label: props.planning ? '预计重量(kg)' : '实测重量(kg)', slotName: 'weight', width: 190 }, { key: 'volume', label: props.planning ? '预计体积(m³)' : '实测体积(m³)', slotName: 'volume', width: 190 },
|
||||
@@ -45,6 +47,7 @@ function apply(value: TripLoading | TripCapacityPlanning) {
|
||||
palletSpaces: saved?.palletSpaces ?? 1, weightKg: saved?.weightKg ?? null, volumeCbm: saved?.volumeCbm ?? null, deliveryIndex: saved && saved.deliveryIndex > 0 && props.trip.stops[saved.deliveryIndex]?.destinationKey === cargo.destinationKey ? saved.deliveryIndex : (index > 0 ? index : null) })
|
||||
}
|
||||
rows.value = [...pallets.values()]
|
||||
baseline.value = JSON.stringify(rows.value)
|
||||
}
|
||||
async function load() {
|
||||
const current = ++sequence; loading.value = true; error.value = null
|
||||
@@ -53,7 +56,9 @@ async function load() {
|
||||
finally { if (current === sequence) loading.value = false }
|
||||
}
|
||||
async function save() {
|
||||
if (busy.value || loading.value || !props.editable || !info.value) return
|
||||
if (busy.value || loading.value || !info.value) return
|
||||
if (props.planning && 'planValid' in info.value && info.value.planValid && baseline.value === JSON.stringify(rows.value)) return info.value
|
||||
if (!props.editable) { ElMessage.warning('计划容量需重新核对,请由有车次编辑权限的调度操作'); return }
|
||||
if (destinationIssue.value) { ElMessage.warning(destinationIssue.value); return }
|
||||
if (rows.value.some(row => !matchingStops(row).some(stop => stop.index === row.deliveryIndex))) { ElMessage.warning('请选择与货物目的地匹配的卸货站'); return }
|
||||
if (!rows.value.length || rows.value.some(row => row.weightKg == null || row.volumeCbm == null || row.deliveryIndex == null)) { ElMessage.warning(props.planning ? '请填写每托预计重量、体积和卸货站' : '请填写每托实测重量、体积和卸货站'); return }
|
||||
@@ -65,7 +70,7 @@ async function save() {
|
||||
const body = { recordVersion: snapshot.recordVersion, manifestHash: snapshot.manifestHash, pallets, reason: props.planning ? '核对本车自有货计划载荷' : '核对实物托盘及实际装载' }
|
||||
if (props.planning) {
|
||||
const result = await confirmTripCapacityPlan(props.trip.tripNo, body)
|
||||
if (current === sequence) { apply(result); emit('planned', result); ElMessage.success('计划容量已核对') }
|
||||
if (current === sequence) { apply(result); emit('planned', result); if (!props.embedded) ElMessage.success('计划容量已核对'); return result }
|
||||
} else {
|
||||
const result = await confirmTripLoading(props.trip.tripNo, body)
|
||||
if (current === sequence) { apply(result); emit('saved', result); ElMessage.success('实际装载已确认') }
|
||||
@@ -74,6 +79,7 @@ async function save() {
|
||||
finally { busy.value = false }
|
||||
}
|
||||
watch(() => [props.trip.tripNo, props.trip.recordVersion, props.planning], () => { if (info.value?.tripNo !== props.trip.tripNo || info.value.recordVersion !== props.trip.recordVersion || !!props.planning !== ('capacityPlan' in info.value)) void load() }, { immediate: true })
|
||||
defineExpose({ save, destinationIssue })
|
||||
onBeforeUnmount(() => { sequence++ })
|
||||
</script>
|
||||
<template>
|
||||
@@ -89,8 +95,8 @@ onBeforeUnmount(() => { sequence++ })
|
||||
<template #spaces="{ row }"><el-input-number v-model="row.palletSpaces" :min="1" :precision="0" :disabled="!editable || busy" placeholder="占用托位" /></template>
|
||||
<template #weight="{ row }"><el-input-number v-model="row.weightKg" :min="0.001" :precision="3" :disabled="!editable || busy" :placeholder="planning ? '预计重量' : '实际重量'" /></template>
|
||||
<template #volume="{ row }"><el-input-number v-model="row.volumeCbm" :min="0.001" :precision="3" :disabled="!editable || busy" :placeholder="planning ? '预计体积' : '实际体积'" /></template>
|
||||
<template #stop="{ row }"><el-select v-model="row.deliveryIndex" :disabled="!editable || busy || loading || !matchingStops(row as PalletRow).length" placeholder="请先匹配货物目的地"><el-option v-for="stop in matchingStops(row as PalletRow)" :key="stop.index" :value="stop.index" :label="stop.name" /></el-select></template>
|
||||
<template #toolbar-actions><el-button @click="load">{{ planning ? '重新读取容量计划' : '重新读取装载' }}</el-button><el-button v-if="editable" type="primary" :loading="busy" :disabled="loading || !!info?.blockReason || !!destinationIssue || !rows.length" @click="save">{{ planning ? '确认计划容量' : '确认实际装载' }}</el-button></template>
|
||||
<template #stop="{ row }"><span v-if="matchingStops(row as PalletRow).length === 1">{{ matchingStops(row as PalletRow)[0]?.name }}</span><el-select v-else v-model="row.deliveryIndex" :disabled="!editable || busy || loading || !matchingStops(row as PalletRow).length" placeholder="请先匹配货物目的地"><el-option v-for="stop in matchingStops(row as PalletRow)" :key="stop.index" :value="stop.index" :label="stop.name" /></el-select></template>
|
||||
<template v-if="!embedded" #toolbar-actions><el-button @click="load">{{ planning ? '重新读取容量计划' : '重新读取装载' }}</el-button><el-button v-if="editable" type="primary" :loading="busy" :disabled="loading || !!info?.blockReason || !!destinationIssue || !rows.length" @click="save">{{ planning ? '确认计划容量' : '确认实际装载' }}</el-button></template>
|
||||
</DataTable>
|
||||
<table v-if="savedLoad && !stalePlan" class="density-table"><thead><tr><th>行驶区间</th><th>占用托位</th><th>重量(kg)</th><th>体积(m³)</th><th v-if="planning">剩余托位 / kg / m³</th></tr></thead><tbody><tr v-for="segment in segments" :key="segment.fromIndex"><td>{{ trip.stops[segment.fromIndex]?.name }} → {{ trip.stops[segment.toIndex]?.name }}</td><td>{{ segment.occupied.palletSpaces }} / {{ trip.maxPalletSpaces }}</td><td>{{ segment.occupied.weightKg }} / {{ trip.maxLoadKg }}</td><td>{{ segment.occupied.volumeCbm }} / {{ trip.maxVolumeCbm }}</td><td v-if="planning">{{ segment.remaining.palletSpaces }} / {{ segment.remaining.weightKg }} / {{ segment.remaining.volumeCbm }}</td></tr></tbody></table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user