Add bulk vehicle repair functionality in Falukant module
- Implemented a new repairAllVehicles method in FalukantService to handle the repair of multiple vehicles at once, including cost calculation and precondition checks. - Updated FalukantController to expose the repairAllVehicles endpoint, allowing users to initiate bulk repairs via the API. - Enhanced FalukantRouter to include a new route for bulk vehicle repairs. - Modified BranchView component to add UI elements for repairing all vehicles, including a dialog for confirmation and displaying repair details. - Updated German localization files to include translations related to bulk vehicle repair actions, improving user experience for German-speaking users.
This commit is contained in:
@@ -221,6 +221,10 @@ class FalukantController {
|
|||||||
(userId, req) => this.service.repairVehicle(userId, req.params.vehicleId),
|
(userId, req) => this.service.repairVehicle(userId, req.params.vehicleId),
|
||||||
{ successStatus: 200 }
|
{ successStatus: 200 }
|
||||||
);
|
);
|
||||||
|
this.repairAllVehicles = this._wrapWithUser(
|
||||||
|
(userId, req) => this.service.repairAllVehicles(userId, req.body.vehicleIds),
|
||||||
|
{ successStatus: 200 }
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ router.get('/vehicles/types', falukantController.getVehicleTypes);
|
|||||||
router.post('/vehicles', falukantController.buyVehicles);
|
router.post('/vehicles', falukantController.buyVehicles);
|
||||||
router.get('/vehicles', falukantController.getVehicles);
|
router.get('/vehicles', falukantController.getVehicles);
|
||||||
router.post('/vehicles/:vehicleId/repair', falukantController.repairVehicle);
|
router.post('/vehicles/:vehicleId/repair', falukantController.repairVehicle);
|
||||||
|
router.post('/vehicles/repair-all', falukantController.repairAllVehicles);
|
||||||
router.post('/transports', falukantController.createTransport);
|
router.post('/transports', falukantController.createTransport);
|
||||||
router.get('/transports/route', falukantController.getTransportRoute);
|
router.get('/transports/route', falukantController.getTransportRoute);
|
||||||
router.get('/transports/branch/:branchId', falukantController.getBranchTransports);
|
router.get('/transports/branch/:branchId', falukantController.getBranchTransports);
|
||||||
|
|||||||
@@ -1212,6 +1212,108 @@ class FalukantService extends BaseService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async repairAllVehicles(hashedUserId, vehicleIds) {
|
||||||
|
const user = await getFalukantUserOrFail(hashedUserId);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
if (!vehicleIds || !Array.isArray(vehicleIds) || vehicleIds.length === 0) {
|
||||||
|
throw new Error('Keine Fahrzeuge zum Reparieren angegeben');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Fahrzeuge laden
|
||||||
|
const vehicles = await Vehicle.findAll({
|
||||||
|
where: {
|
||||||
|
id: { [Op.in]: vehicleIds },
|
||||||
|
falukantUserId: user.id,
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VehicleType,
|
||||||
|
as: 'type',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: Transport,
|
||||||
|
as: 'transports',
|
||||||
|
required: false,
|
||||||
|
attributes: ['id'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (vehicles.length !== vehicleIds.length) {
|
||||||
|
throw new Error('Nicht alle angegebenen Fahrzeuge gefunden oder gehören nicht dem Benutzer');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe alle Fahrzeuge
|
||||||
|
const repairableVehicles = [];
|
||||||
|
let totalCost = 0;
|
||||||
|
|
||||||
|
for (const vehicle of vehicles) {
|
||||||
|
// Prüfen, ob Fahrzeug in Benutzung ist
|
||||||
|
const hasActiveTransport = Array.isArray(vehicle.transports) && vehicle.transports.length > 0;
|
||||||
|
const isBuilding = vehicle.availableFrom && new Date(vehicle.availableFrom).getTime() > now.getTime();
|
||||||
|
|
||||||
|
if (hasActiveTransport || isBuilding) {
|
||||||
|
continue; // Überspringe Fahrzeuge in Benutzung
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen, ob Reparatur nötig ist (Zustand < 100)
|
||||||
|
if (vehicle.condition >= 100) {
|
||||||
|
continue; // Überspringe bereits perfekte Fahrzeuge
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kosten berechnen: Baupreis * 0.8 * (100 - zustand) / 100
|
||||||
|
const baseCost = vehicle.type.cost;
|
||||||
|
const repairCost = Math.round(baseCost * 0.8 * (100 - vehicle.condition) / 100);
|
||||||
|
totalCost += repairCost;
|
||||||
|
repairableVehicles.push({ vehicle, repairCost });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repairableVehicles.length === 0) {
|
||||||
|
throw new PreconditionError('noVehiclesToRepair');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10% Rabatt für Reparatur aller Fahrzeuge
|
||||||
|
const discountedCost = Math.round(totalCost * 0.9);
|
||||||
|
|
||||||
|
if (user.money < discountedCost) {
|
||||||
|
throw new PreconditionError('insufficientFunds');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Reparaturen in einer Transaktion durchführen
|
||||||
|
await sequelize.transaction(async (tx) => {
|
||||||
|
// Geld abziehen
|
||||||
|
const moneyResult = await updateFalukantUserMoney(
|
||||||
|
user.id,
|
||||||
|
-discountedCost,
|
||||||
|
'repair_all_vehicles',
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
if (!moneyResult.success) {
|
||||||
|
throw new Error('Failed to update money');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Fahrzeuge reparieren
|
||||||
|
for (const { vehicle, repairCost } of repairableVehicles) {
|
||||||
|
const buildTimeMinutes = vehicle.type.buildTimeMinutes || 0;
|
||||||
|
const buildMs = buildTimeMinutes * 60 * 1000;
|
||||||
|
const availableFrom = new Date(now.getTime() + buildMs);
|
||||||
|
|
||||||
|
await vehicle.update({
|
||||||
|
condition: 100,
|
||||||
|
availableFrom: availableFrom,
|
||||||
|
}, { transaction: tx });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
repairedCount: repairableVehicles.length,
|
||||||
|
totalCost: discountedCost,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async createStock(hashedUserId, branchId, stockData) {
|
async createStock(hashedUserId, branchId, stockData) {
|
||||||
const u = await getFalukantUserOrFail(hashedUserId);
|
const u = await getFalukantUserOrFail(hashedUserId);
|
||||||
const b = await getBranchOrFail(u.id, branchId);
|
const b = await getBranchOrFail(u.id, branchId);
|
||||||
|
|||||||
@@ -28,6 +28,9 @@
|
|||||||
"notify_election_created": "Es wurde eine neue Wahl ausgeschrieben.",
|
"notify_election_created": "Es wurde eine neue Wahl ausgeschrieben.",
|
||||||
"production": {
|
"production": {
|
||||||
"overproduction": "Überproduktion: Deine Produktion liegt {value}% über dem Bedarf."
|
"overproduction": "Überproduktion: Deine Produktion liegt {value}% über dem Bedarf."
|
||||||
|
},
|
||||||
|
"transport": {
|
||||||
|
"waiting": "Transport wartet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"health": {
|
"health": {
|
||||||
@@ -319,8 +322,16 @@
|
|||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"repair": "Reparieren",
|
"repair": "Reparieren",
|
||||||
|
"repairWithCost": "Reparieren (Kosten: {cost})",
|
||||||
"repairTooltip": "Fahrzeug reparieren",
|
"repairTooltip": "Fahrzeug reparieren",
|
||||||
"repairVehicleTitle": "Fahrzeug reparieren",
|
"repairVehicleTitle": "Fahrzeug reparieren",
|
||||||
|
"repairAll": "Alle reparieren ({count} Fahrzeuge, Kosten: {cost})",
|
||||||
|
"repairAllTitle": "Alle Fahrzeuge reparieren",
|
||||||
|
"repairAllDescription": "Möchtest du alle {count} Fahrzeuge reparieren? Gesamtkosten: {cost}",
|
||||||
|
"repairAllDiscount": "Hinweis: Bei Reparatur aller Fahrzeuge erhältst du 10% Rabatt.",
|
||||||
|
"repairAllConfirm": "Alle reparieren",
|
||||||
|
"repairAllSuccess": "Alle Fahrzeuge werden repariert!",
|
||||||
|
"repairAllError": "Fehler beim Reparieren der Fahrzeuge.",
|
||||||
"repairVehicleType": "Fahrzeugtyp",
|
"repairVehicleType": "Fahrzeugtyp",
|
||||||
"repairCurrentCondition": "Aktueller Zustand",
|
"repairCurrentCondition": "Aktueller Zustand",
|
||||||
"repairCost": "Reparaturkosten",
|
"repairCost": "Reparaturkosten",
|
||||||
|
|||||||
@@ -68,9 +68,18 @@
|
|||||||
<!-- Transportmittel -->
|
<!-- Transportmittel -->
|
||||||
<div v-else-if="activeTab === 'transport'" class="branch-tab-pane">
|
<div v-else-if="activeTab === 'transport'" class="branch-tab-pane">
|
||||||
<p>{{ $t('falukant.branch.transport.placeholder') }}</p>
|
<p>{{ $t('falukant.branch.transport.placeholder') }}</p>
|
||||||
|
<div class="vehicle-action-buttons">
|
||||||
<button @click="openBuyVehicleDialog">
|
<button @click="openBuyVehicleDialog">
|
||||||
{{ $t('falukant.branch.transport.buy') }}
|
{{ $t('falukant.branch.transport.buy') }}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="repairableVehiclesCount > 0"
|
||||||
|
@click="openRepairAllVehiclesDialog"
|
||||||
|
class="repair-all-button"
|
||||||
|
>
|
||||||
|
{{ $t('falukant.branch.transport.repairAll', { count: repairableVehiclesCount, cost: formatMoney(repairAllCost) }) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="vehicle-overview" v-if="vehicles && vehicles.length">
|
<div class="vehicle-overview" v-if="vehicles && vehicles.length">
|
||||||
<table class="vehicle-table">
|
<table class="vehicle-table">
|
||||||
@@ -122,7 +131,7 @@
|
|||||||
:title="$t('falukant.branch.transport.repairTooltip')"
|
:title="$t('falukant.branch.transport.repairTooltip')"
|
||||||
class="repair-button"
|
class="repair-button"
|
||||||
>
|
>
|
||||||
{{ $t('falukant.branch.transport.repair') }}
|
{{ $t('falukant.branch.transport.repairWithCost', { cost: formatMoney(calculateRepairCost(v)) }) }}
|
||||||
</button>
|
</button>
|
||||||
<span v-if="v.status !== 'available'" class="no-action">—</span>
|
<span v-if="v.status !== 'available'" class="no-action">—</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -194,6 +203,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Dialog zum Reparieren aller Fahrzeuge -->
|
||||||
|
<div v-if="repairAllVehiclesDialog.show" class="modal-overlay" @click.self="closeRepairAllVehiclesDialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h3>{{ $t('falukant.branch.transport.repairAllTitle') }}</h3>
|
||||||
|
<div class="repair-all-form">
|
||||||
|
<p>
|
||||||
|
{{ $t('falukant.branch.transport.repairAllDescription', {
|
||||||
|
count: repairAllVehiclesDialog.vehicleIds.length,
|
||||||
|
cost: formatMoney(repairAllVehiclesDialog.totalCost)
|
||||||
|
}) }}
|
||||||
|
</p>
|
||||||
|
<p class="repair-all-discount">
|
||||||
|
{{ $t('falukant.branch.transport.repairAllDiscount') }}
|
||||||
|
</p>
|
||||||
|
<div class="modal-buttons">
|
||||||
|
<button @click="repairAllVehicles" class="repair-confirm-button">
|
||||||
|
{{ $t('falukant.branch.transport.repairAllConfirm') }}
|
||||||
|
</button>
|
||||||
|
<button @click="closeRepairAllVehiclesDialog">
|
||||||
|
{{ $t('falukant.branch.transport.cancel') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Dialog zum Reparieren von Fahrzeugen -->
|
<!-- Dialog zum Reparieren von Fahrzeugen -->
|
||||||
<div v-if="repairVehicleDialog.show" class="modal-overlay" @click.self="closeRepairVehicleDialog">
|
<div v-if="repairVehicleDialog.show" class="modal-overlay" @click.self="closeRepairVehicleDialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -301,6 +336,11 @@ export default {
|
|||||||
repairCost: null,
|
repairCost: null,
|
||||||
buildTimeMinutes: null,
|
buildTimeMinutes: null,
|
||||||
},
|
},
|
||||||
|
repairAllVehiclesDialog: {
|
||||||
|
show: false,
|
||||||
|
vehicleIds: [],
|
||||||
|
totalCost: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -318,6 +358,21 @@ export default {
|
|||||||
}
|
}
|
||||||
return grouped;
|
return grouped;
|
||||||
},
|
},
|
||||||
|
repairableVehicles() {
|
||||||
|
return (this.vehicles || []).filter(v =>
|
||||||
|
v.status === 'available' && v.condition < 100
|
||||||
|
);
|
||||||
|
},
|
||||||
|
repairableVehiclesCount() {
|
||||||
|
return this.repairableVehicles.length;
|
||||||
|
},
|
||||||
|
repairAllCost() {
|
||||||
|
const totalCost = this.repairableVehicles.reduce((sum, v) => {
|
||||||
|
return sum + this.calculateRepairCost(v);
|
||||||
|
}, 0);
|
||||||
|
// 10% Rabatt für Reparatur aller Fahrzeuge
|
||||||
|
return Math.round(totalCost * 0.9);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -755,14 +810,19 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
calculateRepairCost(vehicle) {
|
||||||
|
if (!vehicle || !vehicle.type || vehicle.condition >= 100) return 0;
|
||||||
|
const baseCost = vehicle.type.cost || 0;
|
||||||
|
return Math.round(baseCost * 0.8 * (100 - vehicle.condition) / 100);
|
||||||
|
},
|
||||||
|
|
||||||
openRepairVehicleDialog(vehicle) {
|
openRepairVehicleDialog(vehicle) {
|
||||||
if (!vehicle || vehicle.status !== 'available' || vehicle.condition >= 100) {
|
if (!vehicle || vehicle.status !== 'available' || vehicle.condition >= 100) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kosten berechnen: Baupreis * 0.8 * (100 - zustand) / 100
|
// Kosten berechnen: Baupreis * 0.8 * (100 - zustand) / 100
|
||||||
const baseCost = vehicle.type.cost || 0;
|
const repairCost = this.calculateRepairCost(vehicle);
|
||||||
const repairCost = Math.round(baseCost * 0.8 * (100 - vehicle.condition) / 100);
|
|
||||||
const buildTimeMinutes = vehicle.type.buildTimeMinutes || 0;
|
const buildTimeMinutes = vehicle.type.buildTimeMinutes || 0;
|
||||||
|
|
||||||
this.repairVehicleDialog = {
|
this.repairVehicleDialog = {
|
||||||
@@ -773,6 +833,47 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openRepairAllVehiclesDialog() {
|
||||||
|
const repairableVehicles = this.repairableVehicles;
|
||||||
|
if (repairableVehicles.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.repairAllVehiclesDialog = {
|
||||||
|
show: true,
|
||||||
|
vehicleIds: repairableVehicles.map(v => v.id),
|
||||||
|
totalCost: this.repairAllCost,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
closeRepairAllVehiclesDialog() {
|
||||||
|
this.repairAllVehiclesDialog = {
|
||||||
|
show: false,
|
||||||
|
vehicleIds: [],
|
||||||
|
totalCost: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async repairAllVehicles() {
|
||||||
|
if (!this.repairAllVehiclesDialog.vehicleIds || this.repairAllVehiclesDialog.vehicleIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiClient.post('/api/falukant/vehicles/repair-all', {
|
||||||
|
vehicleIds: this.repairAllVehiclesDialog.vehicleIds,
|
||||||
|
});
|
||||||
|
await this.loadVehicles();
|
||||||
|
this.closeRepairAllVehiclesDialog();
|
||||||
|
alert(this.$t('falukant.branch.transport.repairAllSuccess'));
|
||||||
|
this.$refs.statusBar?.fetchStatus();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error repairing all vehicles:', error);
|
||||||
|
const errorMessage = error.response?.data?.message || this.$t('falukant.branch.transport.repairAllError');
|
||||||
|
alert(errorMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
closeRepairVehicleDialog() {
|
closeRepairVehicleDialog() {
|
||||||
this.repairVehicleDialog = {
|
this.repairVehicleDialog = {
|
||||||
show: false,
|
show: false,
|
||||||
@@ -851,15 +952,17 @@ h2 {
|
|||||||
|
|
||||||
.send-all-buttons button {
|
.send-all-buttons button {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
background-color: #007bff;
|
background-color: #F9A22C;
|
||||||
color: white;
|
color: #000000;
|
||||||
border: none;
|
border: 1px solid #F9A22C;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.send-all-buttons button:hover {
|
.send-all-buttons button:hover {
|
||||||
background-color: #0056b3;
|
background-color: #fdf1db;
|
||||||
|
color: #7E471B;
|
||||||
|
border: 1px solid #7E471B;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-action {
|
.no-action {
|
||||||
@@ -998,4 +1101,36 @@ h2 {
|
|||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
color: #28a745;
|
color: #28a745;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vehicle-action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repair-all-button {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repair-all-button:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repair-all-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repair-all-discount {
|
||||||
|
color: #28a745;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user