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:
7
backend/migrations/make_transport_product_nullable.sql
Normal file
7
backend/migrations/make_transport_product_nullable.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
-- Migration: Make productId and size nullable in transport table
|
||||||
|
-- This allows empty transports (moving vehicles without products)
|
||||||
|
|
||||||
|
ALTER TABLE falukant_data.transport
|
||||||
|
ALTER COLUMN product_id DROP NOT NULL,
|
||||||
|
ALTER COLUMN size DROP NOT NULL;
|
||||||
|
|
||||||
@@ -15,11 +15,11 @@ Transport.init(
|
|||||||
},
|
},
|
||||||
productId: {
|
productId: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: true, // Nullable für leere Transporte (nur Fahrzeuge bewegen)
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: true, // Nullable für leere Transporte (nur Fahrzeuge bewegen)
|
||||||
},
|
},
|
||||||
vehicleId: {
|
vehicleId: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
|
|||||||
@@ -92,12 +92,14 @@ function calcSellPrice(product, knowledgeFactor = 0) {
|
|||||||
return min + (max - min) * (knowledgeFactor / 100);
|
return min + (max - min) * (knowledgeFactor / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function calcRegionalSellPrice(product, knowledgeFactor, regionId) {
|
async function calcRegionalSellPrice(product, knowledgeFactor, regionId, worthPercent = null) {
|
||||||
// Hole TownProductWorth für diese Region und dieses Produkt
|
// Wenn worthPercent nicht übergeben wurde, hole es aus der Datenbank
|
||||||
|
if (worthPercent === null) {
|
||||||
const townWorth = await TownProductWorth.findOne({
|
const townWorth = await TownProductWorth.findOne({
|
||||||
where: { productId: product.id, regionId: regionId }
|
where: { productId: product.id, regionId: regionId }
|
||||||
});
|
});
|
||||||
const worthPercent = townWorth?.worthPercent || 50; // Default 50% wenn nicht gefunden
|
worthPercent = townWorth?.worthPercent || 50; // Default 50% wenn nicht gefunden
|
||||||
|
}
|
||||||
|
|
||||||
// Basispreis basierend auf regionalem worthPercent
|
// Basispreis basierend auf regionalem worthPercent
|
||||||
const basePrice = product.sellCost * (worthPercent / 100);
|
const basePrice = product.sellCost * (worthPercent / 100);
|
||||||
@@ -795,12 +797,24 @@ class FalukantService extends BaseService {
|
|||||||
}
|
}
|
||||||
const maxByVehicles = capacityPerVehicle * freeVehicles.length;
|
const maxByVehicles = capacityPerVehicle * freeVehicles.length;
|
||||||
|
|
||||||
|
// Produkt-Transport oder leerer Transport (nur Fahrzeuge bewegen)?
|
||||||
|
const isEmptyTransport = !productId || !quantity || quantity <= 0;
|
||||||
|
|
||||||
|
let inventory = [];
|
||||||
|
let available = 0;
|
||||||
|
let maxByInventory = 0;
|
||||||
|
let hardMax = 0;
|
||||||
|
let requested = 0;
|
||||||
|
let transportCost = 0.1; // Minimale Kosten für leeren Transport
|
||||||
|
|
||||||
|
if (!isEmptyTransport) {
|
||||||
|
// Produkt-Transport: Inventar prüfen
|
||||||
const stock = await FalukantStock.findOne({ where: { branchId: sourceBranch.id } });
|
const stock = await FalukantStock.findOne({ where: { branchId: sourceBranch.id } });
|
||||||
if (!stock) {
|
if (!stock) {
|
||||||
throw new Error('Stock not found');
|
throw new Error('Stock not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const inventory = await Inventory.findAll({
|
inventory = await Inventory.findAll({
|
||||||
where: { stockId: stock.id },
|
where: { stockId: stock.id },
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
@@ -812,15 +826,15 @@ class FalukantService extends BaseService {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
|
available = inventory.reduce((sum, i) => sum + i.quantity, 0);
|
||||||
if (available <= 0) {
|
if (available <= 0) {
|
||||||
throw new PreconditionError('noInventory');
|
throw new PreconditionError('noInventory');
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxByInventory = available;
|
maxByInventory = available;
|
||||||
const hardMax = Math.min(maxByVehicles, maxByInventory);
|
hardMax = Math.min(maxByVehicles, maxByInventory);
|
||||||
|
|
||||||
const requested = Math.max(1, parseInt(quantity, 10) || 0);
|
requested = Math.max(1, parseInt(quantity, 10) || 0);
|
||||||
if (requested > hardMax) {
|
if (requested > hardMax) {
|
||||||
throw new PreconditionError('quantityTooHigh');
|
throw new PreconditionError('quantityTooHigh');
|
||||||
}
|
}
|
||||||
@@ -829,7 +843,12 @@ class FalukantService extends BaseService {
|
|||||||
const productType = inventory[0]?.productType;
|
const productType = inventory[0]?.productType;
|
||||||
const unitValue = productType?.sellCost || 0;
|
const unitValue = productType?.sellCost || 0;
|
||||||
const totalValue = unitValue * requested;
|
const totalValue = unitValue * requested;
|
||||||
const transportCost = Math.max(0.1, totalValue * 0.01);
|
transportCost = Math.max(0.1, totalValue * 0.01);
|
||||||
|
} else {
|
||||||
|
// Leerer Transport: Ein Fahrzeug wird bewegt
|
||||||
|
requested = 1;
|
||||||
|
hardMax = 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (user.money < transportCost) {
|
if (user.money < transportCost) {
|
||||||
throw new PreconditionError('insufficientFunds');
|
throw new PreconditionError('insufficientFunds');
|
||||||
@@ -852,26 +871,32 @@ class FalukantService extends BaseService {
|
|||||||
|
|
||||||
for (const v of freeVehicles) {
|
for (const v of freeVehicles) {
|
||||||
if (remaining <= 0) break;
|
if (remaining <= 0) break;
|
||||||
const size = Math.min(remaining, capacityPerVehicle);
|
const size = isEmptyTransport ? 0 : Math.min(remaining, capacityPerVehicle);
|
||||||
const t = await Transport.create(
|
const t = await Transport.create(
|
||||||
{
|
{
|
||||||
sourceRegionId,
|
sourceRegionId,
|
||||||
targetRegionId,
|
targetRegionId,
|
||||||
productId,
|
productId: isEmptyTransport ? null : productId,
|
||||||
size,
|
size: isEmptyTransport ? 0 : size,
|
||||||
vehicleId: v.id,
|
vehicleId: v.id,
|
||||||
},
|
},
|
||||||
{ transaction: tx }
|
{ transaction: tx }
|
||||||
);
|
);
|
||||||
transportsCreated.push(t);
|
transportsCreated.push(t);
|
||||||
|
if (!isEmptyTransport) {
|
||||||
remaining -= size;
|
remaining -= size;
|
||||||
|
} else {
|
||||||
|
// Bei leerem Transport nur ein Fahrzeug bewegen
|
||||||
|
remaining = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remaining > 0) {
|
if (remaining > 0 && !isEmptyTransport) {
|
||||||
throw new Error('Not enough vehicle capacity for requested quantity');
|
throw new Error('Not enough vehicle capacity for requested quantity');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inventar in der Quell-Niederlassung reduzieren
|
// Inventar in der Quell-Niederlassung reduzieren (nur bei Produkt-Transport)
|
||||||
|
if (!isEmptyTransport && inventory.length > 0) {
|
||||||
let left = requested;
|
let left = requested;
|
||||||
for (const inv of inventory) {
|
for (const inv of inventory) {
|
||||||
if (left <= 0) break;
|
if (left <= 0) break;
|
||||||
@@ -890,6 +915,8 @@ class FalukantService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
notifyUser(user.user.hashedId, 'stock_change', { branchId: sourceBranch.id });
|
notifyUser(user.user.hashedId, 'stock_change', { branchId: sourceBranch.id });
|
||||||
|
}
|
||||||
|
|
||||||
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: sourceBranch.id });
|
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: sourceBranch.id });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -3721,7 +3748,8 @@ class FalukantService extends BaseService {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// TownProductWorth für alle Städte und dieses Produkt abrufen
|
// TownProductWorth für alle Städte und dieses Produkt einmalig abrufen
|
||||||
|
// (vermeidet N+1 Query Problem)
|
||||||
const townWorths = await TownProductWorth.findAll({
|
const townWorths = await TownProductWorth.findAll({
|
||||||
where: { productId: productId },
|
where: { productId: productId },
|
||||||
attributes: ['regionId', 'worthPercent']
|
attributes: ['regionId', 'worthPercent']
|
||||||
@@ -3729,13 +3757,14 @@ class FalukantService extends BaseService {
|
|||||||
const worthMap = new Map(townWorths.map(tw => [tw.regionId, tw.worthPercent]));
|
const worthMap = new Map(townWorths.map(tw => [tw.regionId, tw.worthPercent]));
|
||||||
|
|
||||||
// Berechne den regionalen Preis für die aktuelle Region (falls angegeben)
|
// Berechne den regionalen Preis für die aktuelle Region (falls angegeben)
|
||||||
|
// WICHTIG: Ignoriere den übergebenen currentPrice, da er möglicherweise nicht
|
||||||
|
// den regionalen Faktor berücksichtigt. Berechne stattdessen immer den korrekten
|
||||||
|
// regionalen Preis basierend auf currentRegionId.
|
||||||
let currentRegionalPrice = currentPrice; // Fallback auf übergebenen Preis
|
let currentRegionalPrice = currentPrice; // Fallback auf übergebenen Preis
|
||||||
if (currentRegionId) {
|
if (currentRegionId) {
|
||||||
const currentWorthPercent = worthMap.get(currentRegionId) || 50;
|
const currentWorthPercent = worthMap.get(currentRegionId) || 50;
|
||||||
const currentBasePrice = product.sellCost * (currentWorthPercent / 100);
|
// Verwende calcRegionalSellPrice mit bereits geladenem worthPercent (keine DB-Query)
|
||||||
const currentMin = currentBasePrice * 0.6;
|
currentRegionalPrice = await calcRegionalSellPrice(product, knowledgeFactor, currentRegionId, currentWorthPercent);
|
||||||
const currentMax = currentBasePrice;
|
|
||||||
currentRegionalPrice = currentMin + (currentMax - currentMin) * (knowledgeFactor / 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Für jede Stadt den Preis berechnen und Branch-Typ bestimmen
|
// Für jede Stadt den Preis berechnen und Branch-Typ bestimmen
|
||||||
@@ -3746,16 +3775,9 @@ class FalukantService extends BaseService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regionaler Preis-Faktor (worthPercent zwischen 40-60)
|
const worthPercent = worthMap.get(city.id) || 50;
|
||||||
const worthPercent = worthMap.get(city.id) || 50; // Default 50% wenn nicht gefunden
|
// Verwende calcRegionalSellPrice mit bereits geladenem worthPercent (keine DB-Query)
|
||||||
|
const priceInCity = await calcRegionalSellPrice(product, knowledgeFactor, city.id, worthPercent);
|
||||||
// Basispreis basierend auf regionalem worthPercent
|
|
||||||
const basePrice = product.sellCost * (worthPercent / 100);
|
|
||||||
|
|
||||||
// Dann Knowledge-Faktor anwenden (wie in calcSellPrice)
|
|
||||||
const min = basePrice * 0.6;
|
|
||||||
const max = basePrice;
|
|
||||||
const priceInCity = min + (max - min) * (knowledgeFactor / 100);
|
|
||||||
|
|
||||||
// Nur Städte zurückgeben, wo der Preis höher ist
|
// Nur Städte zurückgeben, wo der Preis höher ist
|
||||||
// Kleine Toleranz (0.01) für Rundungsfehler bei Gleitkommaberechnungen
|
// Kleine Toleranz (0.01) für Rundungsfehler bei Gleitkommaberechnungen
|
||||||
|
|||||||
@@ -116,6 +116,59 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<NewDirectorDialog ref="newDirectorDialog" />
|
<NewDirectorDialog ref="newDirectorDialog" />
|
||||||
</template>
|
</template>
|
||||||
@@ -126,7 +179,11 @@ import NewDirectorDialog from '@/dialogues/falukant/NewDirectorDialog.vue';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DirectorInfo",
|
name: "DirectorInfo",
|
||||||
props: { branchId: { type: Number, required: true } },
|
props: {
|
||||||
|
branchId: { type: Number, required: true },
|
||||||
|
vehicles: { type: Array, default: () => [] },
|
||||||
|
branches: { type: Array, default: () => [] },
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
NewDirectorDialog
|
NewDirectorDialog
|
||||||
},
|
},
|
||||||
@@ -135,6 +192,18 @@ export default {
|
|||||||
director: null,
|
director: null,
|
||||||
showNewDirectorDialog: false,
|
showNewDirectorDialog: false,
|
||||||
editIncome: null,
|
editIncome: null,
|
||||||
|
emptyTransportForm: {
|
||||||
|
vehicleTypeId: null,
|
||||||
|
targetBranchId: null,
|
||||||
|
distance: null,
|
||||||
|
durationHours: null,
|
||||||
|
eta: null,
|
||||||
|
durationLabel: '',
|
||||||
|
etaLabel: '',
|
||||||
|
routeNames: [],
|
||||||
|
cost: 0.1,
|
||||||
|
costLabel: '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -212,6 +281,140 @@ export default {
|
|||||||
teachDirector() {
|
teachDirector() {
|
||||||
alert(this.$t('falukant.branch.director.teachAlert'));
|
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>
|
</script>
|
||||||
@@ -284,4 +487,50 @@ export default {
|
|||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
padding: 4px 6px;
|
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>
|
</style>
|
||||||
@@ -151,7 +151,22 @@
|
|||||||
"teach": "Weiterbilden",
|
"teach": "Weiterbilden",
|
||||||
"produce": "Darf produzieren",
|
"produce": "Darf produzieren",
|
||||||
"sell": "Darf verkaufen",
|
"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": {
|
"sale": {
|
||||||
"title": "Inventar",
|
"title": "Inventar",
|
||||||
|
|||||||
@@ -24,7 +24,13 @@
|
|||||||
<div v-if="selectedBranch" class="branch-tab-content">
|
<div v-if="selectedBranch" class="branch-tab-content">
|
||||||
<!-- Direktor -->
|
<!-- Direktor -->
|
||||||
<div v-if="activeTab === 'director'" class="branch-tab-pane">
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Inventar / Verkauf -->
|
<!-- Inventar / Verkauf -->
|
||||||
|
|||||||
Reference in New Issue
Block a user