Enhance vehicle transport functionality in FalukantService and update UI components
- Modified the createTransport method in FalukantService to support optional vehicleIds, allowing for more flexible vehicle selection. - Implemented logic to ensure that either specific vehicleIds or a vehicleTypeId must be provided, improving error handling for vehicle availability. - Updated the BranchView component to include new UI elements for sending vehicles, including buttons for sending single or multiple vehicles of the same type. - Added a modal dialog for selecting target branches when sending vehicles, enhancing user experience and streamlining transport operations. - Updated German localization files to include new translations related to vehicle actions and transport functionalities.
This commit is contained in:
@@ -728,7 +728,7 @@ class FalukantService extends BaseService {
|
||||
};
|
||||
}
|
||||
|
||||
async createTransport(hashedUserId, { branchId, vehicleTypeId, productId, quantity, targetBranchId }) {
|
||||
async createTransport(hashedUserId, { branchId, vehicleTypeId, vehicleIds, productId, quantity, targetBranchId }) {
|
||||
const user = await getFalukantUserOrFail(hashedUserId);
|
||||
|
||||
const sourceBranch = await Branch.findOne({
|
||||
@@ -754,14 +754,61 @@ class FalukantService extends BaseService {
|
||||
const targetRegionId = targetBranch.regionId;
|
||||
const now = new Date();
|
||||
|
||||
const type = await VehicleType.findByPk(vehicleTypeId);
|
||||
let type;
|
||||
let freeVehicles = [];
|
||||
|
||||
// Wenn spezifische vehicleIds übergeben wurden, diese verwenden
|
||||
if (vehicleIds && Array.isArray(vehicleIds) && vehicleIds.length > 0) {
|
||||
const vehicles = await Vehicle.findAll({
|
||||
where: {
|
||||
id: { [Op.in]: vehicleIds },
|
||||
falukantUserId: user.id,
|
||||
regionId: sourceRegionId,
|
||||
availableFrom: { [Op.lte]: now },
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Transport,
|
||||
as: 'transports',
|
||||
required: false,
|
||||
attributes: ['id'],
|
||||
},
|
||||
{
|
||||
model: VehicleType,
|
||||
as: 'type',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
freeVehicles = vehicles.filter((v) => {
|
||||
const t = v.transports || [];
|
||||
return t.length === 0;
|
||||
});
|
||||
|
||||
if (freeVehicles.length === 0) {
|
||||
throw new PreconditionError('noVehiclesAvailable');
|
||||
}
|
||||
|
||||
// Alle Fahrzeuge müssen denselben Typ haben
|
||||
const vehicleTypeIds = [...new Set(freeVehicles.map(v => v.vehicleTypeId))];
|
||||
if (vehicleTypeIds.length !== 1) {
|
||||
throw new Error('All vehicles must be of the same type');
|
||||
}
|
||||
|
||||
type = await VehicleType.findByPk(vehicleTypeIds[0]);
|
||||
if (!type) {
|
||||
throw new Error('Vehicle type not found');
|
||||
}
|
||||
} else {
|
||||
// Standard-Verhalten: Alle freien Fahrzeuge dieses Typs verwenden
|
||||
if (!vehicleTypeId) {
|
||||
throw new Error('Either vehicleTypeId or vehicleIds must be provided');
|
||||
}
|
||||
|
||||
const route = await computeShortestRoute(type.transportMode, sourceRegionId, targetRegionId);
|
||||
if (!route) {
|
||||
throw new PreconditionError('noRoute');
|
||||
type = await VehicleType.findByPk(vehicleTypeId);
|
||||
if (!type) {
|
||||
throw new Error('Vehicle type not found');
|
||||
}
|
||||
|
||||
// Freie Fahrzeuge dieses Typs in der Quell-Region
|
||||
@@ -782,10 +829,20 @@ class FalukantService extends BaseService {
|
||||
],
|
||||
});
|
||||
|
||||
const freeVehicles = vehicles.filter((v) => {
|
||||
freeVehicles = vehicles.filter((v) => {
|
||||
const t = v.transports || [];
|
||||
return t.length === 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (!freeVehicles.length) {
|
||||
throw new PreconditionError('noVehiclesAvailable');
|
||||
}
|
||||
|
||||
const route = await computeShortestRoute(type.transportMode, sourceRegionId, targetRegionId);
|
||||
if (!route) {
|
||||
throw new PreconditionError('noRoute');
|
||||
}
|
||||
|
||||
if (!freeVehicles.length) {
|
||||
throw new PreconditionError('noVehiclesAvailable');
|
||||
|
||||
@@ -295,13 +295,26 @@
|
||||
"mode": "Art",
|
||||
"speed": "Geschwindigkeit",
|
||||
"availableFrom": "Verfügbar ab",
|
||||
"status": "Status"
|
||||
"status": "Status",
|
||||
"actions": "Aktionen"
|
||||
},
|
||||
"status": {
|
||||
"inUse": "In Benutzung (mit Transport verknüpft)",
|
||||
"building": "Im Bau",
|
||||
"free": "Verfügbar"
|
||||
}
|
||||
},
|
||||
"send": "Versenden",
|
||||
"sendSingle": "Einzelnes Fahrzeug versenden",
|
||||
"sendAllFree": "Alle freien Fahrzeuge versenden",
|
||||
"sendAllOfType": "{count} × {type} versenden",
|
||||
"sendVehiclesTitle": "Fahrzeuge versenden",
|
||||
"selectTargetBranch": "Ziel-Niederlassung",
|
||||
"selectTarget": "Ziel-Niederlassung auswählen",
|
||||
"selectTargetError": "Bitte wähle eine Ziel-Niederlassung aus.",
|
||||
"noVehiclesSelected": "Keine Fahrzeuge ausgewählt.",
|
||||
"sendSuccess": "Fahrzeuge erfolgreich versendet!",
|
||||
"sendError": "Fehler beim Versenden der Fahrzeuge.",
|
||||
"cancel": "Abbrechen"
|
||||
},
|
||||
"stocktype": {
|
||||
"wood": "Holzlager",
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
<th>{{ $t('falukant.branch.transport.table.speed') }}</th>
|
||||
<th>{{ $t('falukant.branch.transport.table.availableFrom') }}</th>
|
||||
<th>{{ $t('falukant.branch.transport.table.status') }}</th>
|
||||
<th>{{ $t('falukant.branch.transport.table.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -106,9 +107,36 @@
|
||||
{{ $t('falukant.branch.transport.status.free') }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
v-if="v.status === 'available'"
|
||||
@click="openSendVehicleDialog(v.id)"
|
||||
:title="$t('falukant.branch.transport.sendSingle')"
|
||||
>
|
||||
{{ $t('falukant.branch.transport.send') }}
|
||||
</button>
|
||||
<span v-else class="no-action">—</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Buttons zum Versenden aller freien Fahrzeuge eines Typs -->
|
||||
<div class="send-all-vehicles" v-if="freeVehiclesByType && Object.keys(freeVehiclesByType).length > 0">
|
||||
<h4>{{ $t('falukant.branch.transport.sendAllFree') }}</h4>
|
||||
<div class="send-all-buttons">
|
||||
<button
|
||||
v-for="(vehicleList, vehicleTypeId) in freeVehiclesByType"
|
||||
:key="vehicleTypeId"
|
||||
@click="openSendAllVehiclesDialog(parseInt(vehicleTypeId), vehicleList)"
|
||||
>
|
||||
{{ $t('falukant.branch.transport.sendAllOfType', {
|
||||
type: $t(`falukant.branch.vehicles.${vehicleList[0].type.tr}`),
|
||||
count: vehicleList.length
|
||||
}) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="no-vehicles">
|
||||
{{ $t('falukant.branch.transport.noVehicles') }}
|
||||
@@ -121,6 +149,32 @@
|
||||
:region-id="selectedBranch?.regionId"
|
||||
@bought="handleVehiclesBought"
|
||||
/>
|
||||
|
||||
<!-- Dialog zum Versenden von Fahrzeugen -->
|
||||
<div v-if="sendVehicleDialog.show" class="modal-overlay" @click.self="closeSendVehicleDialog">
|
||||
<div class="modal-content">
|
||||
<h3>{{ $t('falukant.branch.transport.sendVehiclesTitle') }}</h3>
|
||||
<div class="send-vehicle-form">
|
||||
<label>
|
||||
{{ $t('falukant.branch.transport.selectTargetBranch') }}
|
||||
<select v-model.number="sendVehicleDialog.targetBranchId">
|
||||
<option :value="null" disabled>{{ $t('falukant.branch.transport.selectTarget') }}</option>
|
||||
<option v-for="tb in targetBranchOptions()" :key="tb.id" :value="tb.id">
|
||||
{{ tb.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="modal-buttons">
|
||||
<button @click="sendVehicles" :disabled="!sendVehicleDialog.targetBranchId">
|
||||
{{ $t('falukant.branch.transport.send') }}
|
||||
</button>
|
||||
<button @click="closeSendVehicleDialog">
|
||||
{{ $t('falukant.branch.transport.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -180,11 +234,30 @@ export default {
|
||||
{ value: 'storage', label: 'falukant.branch.tabs.storage' },
|
||||
{ value: 'transport', label: 'falukant.branch.tabs.transport' },
|
||||
],
|
||||
sendVehicleDialog: {
|
||||
show: false,
|
||||
vehicleId: null,
|
||||
vehicleIds: null,
|
||||
vehicleTypeId: null,
|
||||
targetBranchId: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['socket', 'daemonSocket']),
|
||||
freeVehiclesByType() {
|
||||
const grouped = {};
|
||||
for (const v of this.vehicles || []) {
|
||||
if (v.status !== 'available' || !v.type || !v.type.id) continue;
|
||||
const typeId = v.type.id;
|
||||
if (!grouped[typeId]) {
|
||||
grouped[typeId] = [];
|
||||
}
|
||||
grouped[typeId].push(v);
|
||||
}
|
||||
return grouped;
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
@@ -506,6 +579,81 @@ export default {
|
||||
this.$refs.storageSection?.loadStorageData();
|
||||
this.$refs.saleSection?.loadTransports();
|
||||
},
|
||||
|
||||
openSendVehicleDialog(vehicleId) {
|
||||
this.sendVehicleDialog = {
|
||||
show: true,
|
||||
vehicleId: vehicleId,
|
||||
vehicleIds: null,
|
||||
vehicleTypeId: null,
|
||||
targetBranchId: null,
|
||||
};
|
||||
},
|
||||
|
||||
openSendAllVehiclesDialog(vehicleTypeId, vehicleList) {
|
||||
this.sendVehicleDialog = {
|
||||
show: true,
|
||||
vehicleId: null,
|
||||
vehicleIds: vehicleList.map(v => v.id),
|
||||
vehicleTypeId: vehicleTypeId,
|
||||
targetBranchId: null,
|
||||
};
|
||||
},
|
||||
|
||||
closeSendVehicleDialog() {
|
||||
this.sendVehicleDialog = {
|
||||
show: false,
|
||||
vehicleId: null,
|
||||
vehicleIds: null,
|
||||
vehicleTypeId: null,
|
||||
targetBranchId: null,
|
||||
};
|
||||
},
|
||||
|
||||
targetBranchOptions() {
|
||||
return (this.branches || [])
|
||||
.filter(b => ['store', 'fullstack'].includes(b.branchTypeLabelTr))
|
||||
.filter(b => b.id !== this.selectedBranch?.id)
|
||||
.map(b => ({
|
||||
id: b.id,
|
||||
label: `${b.cityName} – ${b.type}`,
|
||||
}));
|
||||
},
|
||||
|
||||
async sendVehicles() {
|
||||
if (!this.sendVehicleDialog.targetBranchId) {
|
||||
alert(this.$t('falukant.branch.transport.selectTargetError'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
branchId: this.selectedBranch.id,
|
||||
targetBranchId: this.sendVehicleDialog.targetBranchId,
|
||||
productId: null,
|
||||
quantity: 0,
|
||||
};
|
||||
|
||||
if (this.sendVehicleDialog.vehicleIds && this.sendVehicleDialog.vehicleIds.length > 0) {
|
||||
payload.vehicleIds = this.sendVehicleDialog.vehicleIds;
|
||||
} else if (this.sendVehicleDialog.vehicleId) {
|
||||
payload.vehicleIds = [this.sendVehicleDialog.vehicleId];
|
||||
} else if (this.sendVehicleDialog.vehicleTypeId) {
|
||||
payload.vehicleTypeId = this.sendVehicleDialog.vehicleTypeId;
|
||||
} else {
|
||||
alert(this.$t('falukant.branch.transport.noVehiclesSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.post('/api/falukant/transports', payload);
|
||||
await this.loadVehicles();
|
||||
this.closeSendVehicleDialog();
|
||||
alert(this.$t('falukant.branch.transport.sendSuccess'));
|
||||
} catch (error) {
|
||||
console.error('Error sending vehicles:', error);
|
||||
alert(this.$t('falukant.branch.transport.sendError'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -514,4 +662,112 @@ export default {
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.send-all-vehicles {
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.send-all-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.send-all-buttons button {
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-all-buttons button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.no-action {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
min-width: 400px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.send-vehicle-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.send-vehicle-form label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.send-vehicle-form select {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.modal-buttons button {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-buttons button:first-child {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal-buttons button:first-child:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.modal-buttons button:first-child:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.modal-buttons button:last-child {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal-buttons button:last-child:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user