Implement empty transport feature in DirectorInfo component
- Added functionality to allow directors to initiate empty transports without products, enhancing logistics management. - Introduced a new transport form in the DirectorInfo component, enabling selection of vehicle types and target branches. - Updated the i18n localization files to include new translations for the empty transport feature. - Enhanced the BranchView to pass vehicle and branch data to the DirectorInfo component, ensuring proper functionality. - This update aims to improve user experience and streamline transport operations within the application.
This commit is contained in:
@@ -116,6 +116,59 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transport ohne Produkte (nur wenn Direktor vorhanden und mayStartTransport aktiviert) -->
|
||||
<div v-if="director && director.mayStartTransport" class="director-transport-section">
|
||||
<h4>{{ $t('falukant.branch.director.emptyTransport.title') }}</h4>
|
||||
<p class="transport-description">
|
||||
{{ $t('falukant.branch.director.emptyTransport.description') }}
|
||||
</p>
|
||||
<div class="transport-form">
|
||||
<label>
|
||||
{{ $t('falukant.branch.director.emptyTransport.vehicleType') }}
|
||||
<select v-model.number="emptyTransportForm.vehicleTypeId" @change="loadEmptyTransportRoute">
|
||||
<option :value="null" disabled>{{ $t('falukant.branch.director.emptyTransport.selectVehicle') }}</option>
|
||||
<option v-for="vt in vehicleTypeOptions()" :key="vt.id" :value="vt.id">
|
||||
{{ $t(`falukant.branch.vehicles.${vt.tr}`) }} ({{ vt.count }} × {{ vt.capacity }})
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{ $t('falukant.branch.director.emptyTransport.targetBranch') }}
|
||||
<select v-model.number="emptyTransportForm.targetBranchId" @change="loadEmptyTransportRoute">
|
||||
<option :value="null" disabled>{{ $t('falukant.branch.director.emptyTransport.selectTarget') }}</option>
|
||||
<option v-for="tb in targetBranchOptions" :key="tb.id" :value="tb.id">
|
||||
{{ tb.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div v-if="emptyTransportForm.costLabel" class="transport-cost">
|
||||
{{ $t('falukant.branch.director.emptyTransport.cost', { cost: emptyTransportForm.costLabel }) }}
|
||||
</div>
|
||||
|
||||
<div class="transport-route" v-if="emptyTransportForm.durationLabel">
|
||||
<div>
|
||||
{{ $t('falukant.branch.director.emptyTransport.duration', { duration: emptyTransportForm.durationLabel }) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('falukant.branch.director.emptyTransport.arrival', { datetime: emptyTransportForm.etaLabel }) }}
|
||||
</div>
|
||||
<div v-if="emptyTransportForm.routeNames && emptyTransportForm.routeNames.length">
|
||||
{{ $t('falukant.branch.director.emptyTransport.route') }}:
|
||||
{{ emptyTransportForm.routeNames.join(' → ') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="createEmptyTransport"
|
||||
:disabled="!emptyTransportForm.vehicleTypeId || !emptyTransportForm.targetBranchId"
|
||||
>
|
||||
{{ $t('falukant.branch.director.emptyTransport.create') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NewDirectorDialog ref="newDirectorDialog" />
|
||||
</template>
|
||||
@@ -126,7 +179,11 @@ import NewDirectorDialog from '@/dialogues/falukant/NewDirectorDialog.vue';
|
||||
|
||||
export default {
|
||||
name: "DirectorInfo",
|
||||
props: { branchId: { type: Number, required: true } },
|
||||
props: {
|
||||
branchId: { type: Number, required: true },
|
||||
vehicles: { type: Array, default: () => [] },
|
||||
branches: { type: Array, default: () => [] },
|
||||
},
|
||||
components: {
|
||||
NewDirectorDialog
|
||||
},
|
||||
@@ -135,6 +192,18 @@ export default {
|
||||
director: null,
|
||||
showNewDirectorDialog: false,
|
||||
editIncome: null,
|
||||
emptyTransportForm: {
|
||||
vehicleTypeId: null,
|
||||
targetBranchId: null,
|
||||
distance: null,
|
||||
durationHours: null,
|
||||
eta: null,
|
||||
durationLabel: '',
|
||||
etaLabel: '',
|
||||
routeNames: [],
|
||||
cost: 0.1,
|
||||
costLabel: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
@@ -212,6 +281,140 @@ export default {
|
||||
teachDirector() {
|
||||
alert(this.$t('falukant.branch.director.teachAlert'));
|
||||
},
|
||||
|
||||
vehicleTypeOptions() {
|
||||
const groups = {};
|
||||
for (const v of this.vehicles || []) {
|
||||
if (v.status !== 'available' || !v.type || !v.type.id) continue;
|
||||
const id = v.type.id;
|
||||
if (!groups[id]) {
|
||||
groups[id] = {
|
||||
id,
|
||||
tr: v.type.tr,
|
||||
capacity: v.type.capacity,
|
||||
speed: v.type.speed,
|
||||
count: 0,
|
||||
};
|
||||
}
|
||||
groups[id].count += 1;
|
||||
}
|
||||
return Object.values(groups);
|
||||
},
|
||||
|
||||
targetBranchOptions() {
|
||||
return (this.branches || [])
|
||||
.filter(b => ['store', 'fullstack'].includes(b.branchTypeLabelTr))
|
||||
.filter(b => b.id !== this.branchId)
|
||||
.map(b => ({
|
||||
id: b.id,
|
||||
label: `${b.cityName} – ${b.type}`,
|
||||
}));
|
||||
},
|
||||
|
||||
async loadEmptyTransportRoute() {
|
||||
this.emptyTransportForm.distance = null;
|
||||
this.emptyTransportForm.durationHours = null;
|
||||
this.emptyTransportForm.eta = null;
|
||||
this.emptyTransportForm.durationLabel = '';
|
||||
this.emptyTransportForm.etaLabel = '';
|
||||
this.emptyTransportForm.routeNames = [];
|
||||
|
||||
const vType = this.vehicleTypeOptions().find(v => v.id === this.emptyTransportForm.vehicleTypeId);
|
||||
const targetBranch = (this.branches || []).find(b => b.id === this.emptyTransportForm.targetBranchId);
|
||||
const sourceBranch = (this.branches || []).find(b => b.id === this.branchId);
|
||||
|
||||
if (!vType || !targetBranch || !sourceBranch) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/transports/route', {
|
||||
params: {
|
||||
sourceRegionId: sourceBranch.regionId,
|
||||
targetRegionId: targetBranch.regionId,
|
||||
vehicleTypeId: vType.id,
|
||||
},
|
||||
});
|
||||
if (data && data.totalDistance != null) {
|
||||
const distance = data.totalDistance;
|
||||
const speed = vType.speed || 1;
|
||||
const hours = distance / speed;
|
||||
|
||||
this.emptyTransportForm.distance = distance;
|
||||
this.emptyTransportForm.durationHours = hours;
|
||||
|
||||
const now = new Date();
|
||||
const etaMs = now.getTime() + hours * 60 * 60 * 1000;
|
||||
const etaDate = new Date(etaMs);
|
||||
|
||||
const fullHours = Math.floor(hours);
|
||||
const minutes = Math.round((hours - fullHours) * 60);
|
||||
const parts = [];
|
||||
if (fullHours > 0) parts.push(`${fullHours} h`);
|
||||
if (minutes > 0) parts.push(`${minutes} min`);
|
||||
this.emptyTransportForm.durationLabel = parts.length ? parts.join(' ') : '0 min';
|
||||
this.emptyTransportForm.etaLabel = etaDate.toLocaleString();
|
||||
|
||||
this.emptyTransportForm.routeNames = (data.regions || []).map(r => r.name);
|
||||
}
|
||||
// Kosten für leeren Transport: 0.1
|
||||
this.emptyTransportForm.cost = 0.1;
|
||||
this.emptyTransportForm.costLabel = this.formatMoney(0.1);
|
||||
} catch (error) {
|
||||
console.error('Error loading transport route:', error);
|
||||
this.emptyTransportForm.distance = null;
|
||||
this.emptyTransportForm.durationHours = null;
|
||||
this.emptyTransportForm.eta = null;
|
||||
this.emptyTransportForm.durationLabel = '';
|
||||
this.emptyTransportForm.etaLabel = '';
|
||||
this.emptyTransportForm.routeNames = [];
|
||||
}
|
||||
},
|
||||
|
||||
formatMoney(amount) {
|
||||
if (amount == null) return '';
|
||||
try {
|
||||
return amount.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
} catch (e) {
|
||||
return String(amount);
|
||||
}
|
||||
},
|
||||
|
||||
async createEmptyTransport() {
|
||||
if (!this.emptyTransportForm.vehicleTypeId || !this.emptyTransportForm.targetBranchId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await apiClient.post('/api/falukant/transports', {
|
||||
branchId: this.branchId,
|
||||
vehicleTypeId: this.emptyTransportForm.vehicleTypeId,
|
||||
productId: null,
|
||||
quantity: 0,
|
||||
targetBranchId: this.emptyTransportForm.targetBranchId,
|
||||
});
|
||||
// Formular zurücksetzen
|
||||
this.emptyTransportForm = {
|
||||
vehicleTypeId: null,
|
||||
targetBranchId: null,
|
||||
distance: null,
|
||||
durationHours: null,
|
||||
eta: null,
|
||||
durationLabel: '',
|
||||
etaLabel: '',
|
||||
routeNames: [],
|
||||
cost: 0.1,
|
||||
costLabel: '',
|
||||
};
|
||||
alert(this.$t('falukant.branch.director.emptyTransport.success'));
|
||||
this.$emit('transportCreated');
|
||||
} catch (error) {
|
||||
console.error('Error creating empty transport:', error);
|
||||
alert(this.$t('falukant.branch.director.emptyTransport.error'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -284,4 +487,50 @@ export default {
|
||||
border: 1px solid #ddd;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.director-transport-section {
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.transport-description {
|
||||
margin-bottom: 1rem;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.transport-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.transport-form label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.transport-form select {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.transport-cost {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.transport-route {
|
||||
padding: 0.75rem;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.transport-route > div {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
@@ -151,7 +151,22 @@
|
||||
"teach": "Weiterbilden",
|
||||
"produce": "Darf produzieren",
|
||||
"sell": "Darf verkaufen",
|
||||
"starttransport": "Darf Transporte veranlassen"
|
||||
"starttransport": "Darf Transporte veranlassen",
|
||||
"emptyTransport": {
|
||||
"title": "Transport ohne Produkte",
|
||||
"description": "Bewege Transportmittel von dieser Niederlassung zu einer anderen, um sie besser zu nutzen.",
|
||||
"vehicleType": "Fahrzeugtyp",
|
||||
"selectVehicle": "Fahrzeugtyp auswählen",
|
||||
"targetBranch": "Ziel-Niederlassung",
|
||||
"selectTarget": "Ziel-Niederlassung auswählen",
|
||||
"cost": "Kosten: {cost}",
|
||||
"duration": "Dauer: {duration}",
|
||||
"arrival": "Ankunft: {datetime}",
|
||||
"route": "Route",
|
||||
"create": "Transport starten",
|
||||
"success": "Transport erfolgreich gestartet!",
|
||||
"error": "Fehler beim Starten des Transportes."
|
||||
}
|
||||
},
|
||||
"sale": {
|
||||
"title": "Inventar",
|
||||
|
||||
@@ -24,7 +24,13 @@
|
||||
<div v-if="selectedBranch" class="branch-tab-content">
|
||||
<!-- Direktor -->
|
||||
<div v-if="activeTab === 'director'" class="branch-tab-pane">
|
||||
<DirectorInfo :branchId="selectedBranch.id" ref="directorInfo" />
|
||||
<DirectorInfo
|
||||
:branchId="selectedBranch.id"
|
||||
:vehicles="vehicles"
|
||||
:branches="branches"
|
||||
ref="directorInfo"
|
||||
@transportCreated="handleTransportCreated"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Inventar / Verkauf -->
|
||||
|
||||
Reference in New Issue
Block a user