Füge neue Modelle für Produktion, Inventar und kaufbare Bestände hinzu; aktualisiere bestehende Modelle und Routen
This commit is contained in:
@@ -31,26 +31,150 @@
|
||||
<div class="sale-section">
|
||||
<h3>{{ $t('falukant.branch.sale.title') }}</h3>
|
||||
<p>{{ $t('falukant.branch.sale.info') }}</p>
|
||||
<div class="inventory-table" v-if="inventory.length > 0">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.branch.sale.region') }}</th>
|
||||
<th>{{ $t('falukant.branch.sale.product') }}</th>
|
||||
<th>{{ $t('falukant.branch.sale.quality') }}</th>
|
||||
<th>{{ $t('falukant.branch.sale.quantity') }}</th>
|
||||
<th>{{ $t('falukant.branch.sale.sell') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in inventory"
|
||||
:key="`${item.region.id}-${item.product.id}-${item.quality}`">
|
||||
<td>{{ item.region.name }} ({{ $t(`falukant.regionType.${item.region.regionType.labelTr}`)
|
||||
}})</td>
|
||||
<td>{{ $t(`falukant.product.${item.product.labelTr}`) }}</td>
|
||||
<td>{{ item.quality }}</td>
|
||||
<td>{{ item.totalQuantity }}</td>
|
||||
<td>
|
||||
<input type="number" v-model.number="item.sellQuantity" :min="1"
|
||||
:max="item.totalQuantity" :placeholder="item.totalQuantity" />
|
||||
<button @click="sellItem(index)">{{ $t('falukant.branch.sale.sellButton') }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button @click="sellAll">{{ $t('falukant.branch.sale.sellAllButton') }}</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>{{ $t('falukant.branch.sale.noInventory') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Production Section -->
|
||||
<div class="production-section">
|
||||
<div class="production-section" v-if="['fullstack', 'production'].includes(branchData?.branchType?.labelTr)">
|
||||
<h3>{{ $t('falukant.branch.production.title') }}</h3>
|
||||
<p>{{ $t('falukant.branch.production.info') }}</p>
|
||||
<div v-if="this.productions?.length > 0">
|
||||
<h4>{{ $t('falukant.branch.production.current') }}</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.branch.production.product') }}</th>
|
||||
<th>{{ $t('falukant.branch.production.quantity') }}</th>
|
||||
<th>{{ $t('falukant.branch.production.ending') }}</th>
|
||||
<th>{{ $t('falukant.branch.production.remainingTime') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="production in productions" :key="production.id">
|
||||
<td>{{ $t(`falukant.product.${production.productType.labelTr}`) }}</td>
|
||||
<td>{{ production.quantity }}</td>
|
||||
<td>{{ calculateEndDateTime(production.startTimestamp,
|
||||
production.productType.productionTime) }}</td>
|
||||
<td>{{ calculateRemainingTime(production.startTimestamp,
|
||||
production.productType.productionTime) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<template v-if="!this.productions || this.productions.length < 2">
|
||||
<div>
|
||||
<label for="product">{{ $t('falukant.branch.production.selectProduct') }}</label>
|
||||
<select name="product" id="product" v-model="selectedProduct">
|
||||
<option v-for="product in products" :key="product.id" :value="product.id">
|
||||
{{ $t(`falukant.product.${product.labelTr}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="quantity">{{ $t('falukant.branch.production.quantity') }}</label>
|
||||
<input type="number" id="quantity" v-model.number="productionQuantity" min="1" />
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
{{ $t('falukant.branch.production.cost') }}:
|
||||
<strong>{{ calculateProductionCost() }}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('falukant.branch.production.duration') }}:
|
||||
<strong>{{ calculateProductionDuration(selectedProduct?.id) }}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('falukant.branch.production.revenue') }}:
|
||||
<strong>{{ calculateProductionRevenue() }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<button @click="startProduction" :disabled="!selectedProduct || productionQuantity < 1">
|
||||
{{ $t('falukant.branch.production.start') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="revenue-section">
|
||||
<h3>
|
||||
<button @click="toggleRevenueTable">
|
||||
{{ $t('falukant.branch.revenue.title') }}
|
||||
{{ isRevenueTableOpen ? '▲' : '▼' }}
|
||||
</button>
|
||||
</h3>
|
||||
<div v-if="isRevenueTableOpen" class="revenue-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.branch.revenue.product') }}</th>
|
||||
<th>{{ $t('falukant.branch.revenue.knowledge') }}</th>
|
||||
<th>{{ $t('falukant.branch.revenue.absolute') }}</th>
|
||||
<th>{{ $t('falukant.branch.revenue.perMinute') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="product in products" :key="product.id">
|
||||
<td>{{ $t(`falukant.product.${product.labelTr}`) }}</td>
|
||||
<td>{{ product.knowledges[0]?.knowledge }} %</td>
|
||||
<td>{{ calculateProductRevenue(product).absolute }}</td>
|
||||
<td>{{ calculateProductRevenue(product).perMinute }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MessageDialog ref="messageDialog" />
|
||||
<ErrorDialog ref="errorDialog" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusBar from '@/components/falukant/StatusBar.vue';
|
||||
import FormattedDropdown from '@/components/form/FormattedDropdown.vue';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import MessageDialog from '@/dialogues/standard/MessageDialog.vue';
|
||||
import ErrorDialog from '@/dialogues/standard/ErrorDialog.vue';
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "BranchView",
|
||||
components: {
|
||||
StatusBar,
|
||||
FormattedDropdown,
|
||||
MessageDialog,
|
||||
ErrorDialog,
|
||||
},
|
||||
computed: {
|
||||
...mapState(["socket", "daemonSocket"]),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -60,19 +184,64 @@ export default {
|
||||
{ field: "cityName", label: this.$t('falukant.branch.columns.city') },
|
||||
{ field: "type", label: this.$t('falukant.branch.columns.type') },
|
||||
],
|
||||
products: [],
|
||||
branchData: null,
|
||||
selectedProduct: null,
|
||||
productionQuantity: 1,
|
||||
isRevenueTableOpen: false,
|
||||
inventory: [],
|
||||
currentTime: Date.now(),
|
||||
timer: null,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadBranches();
|
||||
const branchId = this.$route.params.branchId;
|
||||
console.log('route params:', this.$route.params, branchId);
|
||||
const products = await apiClient.get('/api/falukant/products');
|
||||
this.products = products.data;
|
||||
console.log('products loaded');
|
||||
if (branchId) {
|
||||
console.log('branch selected');
|
||||
this.selectedBranch = this.branches.find(branch => branch.id === parseInt(branchId)) || null;
|
||||
} else {
|
||||
console.log('main branch selected');
|
||||
this.selectMainBranch();
|
||||
}
|
||||
if (this.socket) {
|
||||
this.socket.on("falukantBranchUpdate", this.fetchBranch);
|
||||
}
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener('message', (event) => {
|
||||
try {
|
||||
if (event.data === "ping") {
|
||||
return;
|
||||
}
|
||||
const message = JSON.parse(event.data);
|
||||
console.log('Daemon WebSocket message received in BranchView:', message);
|
||||
|
||||
if (message.event === 'production_ready' && message.branch_id === this.selectedBranch?.id) {
|
||||
this.handleProductionReadyEvent(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing WebSocket message in BranchView:', error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('Daemon socket is not initialized.');
|
||||
}
|
||||
this.startTimer();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.stopTimer();
|
||||
if (this.socket) {
|
||||
this.socket.off("falukantBranchUpdate", this.fetchBranch);
|
||||
}
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.onmessage = null;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async selectedBranch(newBranch) {
|
||||
await this.loadBranchData(newBranch);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadBranches() {
|
||||
@@ -84,7 +253,6 @@ export default {
|
||||
type: this.$t(`falukant.branch.types.${branch.branchType.labelTr}`),
|
||||
isMainBranch: branch.isMainBranch,
|
||||
}));
|
||||
// Wenn keine selectedBranch gesetzt ist, versuche die Main Branch zu wählen
|
||||
if (!this.selectedBranch) {
|
||||
this.selectMainBranch();
|
||||
}
|
||||
@@ -92,11 +260,16 @@ export default {
|
||||
console.error('Error loading branches:', error);
|
||||
}
|
||||
},
|
||||
async loadBranchData(newBranch) {
|
||||
if (newBranch) {
|
||||
this.getBranchData(newBranch);
|
||||
await this.loadInventory();
|
||||
}
|
||||
},
|
||||
selectMainBranch() {
|
||||
const main = this.branches.find(b => b.isMainBranch) || null;
|
||||
if (main !== this.selectedBranch) {
|
||||
this.selectedBranch = main;
|
||||
console.log("Main branch selected:", this.selectedBranch);
|
||||
}
|
||||
},
|
||||
createBranch() {
|
||||
@@ -109,6 +282,163 @@ export default {
|
||||
);
|
||||
}
|
||||
},
|
||||
calculateProductionCost() {
|
||||
if (!this.products) {
|
||||
return 0;
|
||||
}
|
||||
const product = this.products.find(p => p.id === this.selectedProduct);
|
||||
if (product) {
|
||||
return 7 * product.category * this.productionQuantity;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
calculateProductionDuration(productId) {
|
||||
if (!this.products || !productId) {
|
||||
return 0;
|
||||
}
|
||||
const product = this.products.find(p => p.id === productId);
|
||||
if (!product) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const totalMinutes = product.productionTime * 60;
|
||||
const decimalTime = (totalMinutes / 60).toFixed(2);
|
||||
|
||||
return decimalTime;
|
||||
},
|
||||
async startProduction() {
|
||||
if (this.selectedBranch && this.selectedProduct && this.productionQuantity > 0) {
|
||||
try {
|
||||
await apiClient.post(`/api/falukant/production`, {
|
||||
branchId: this.selectedBranch.id,
|
||||
productId: this.selectedProduct,
|
||||
quantity: this.productionQuantity,
|
||||
});
|
||||
this.$root.$refs.messageDialog.open(this.$t('falukant.branch.production.success'));
|
||||
} catch (error) {
|
||||
this.$root.$refs.errorDialog.open(this.$t(`falukant.branch.production.error${error.response.data.error}`));
|
||||
}
|
||||
}
|
||||
},
|
||||
calculateProductionRevenue() {
|
||||
if (!this.selectedProduct) {
|
||||
return 0;
|
||||
}
|
||||
if (!this.products) {
|
||||
return 0;
|
||||
}
|
||||
const product = this.products.find(p => p.id === this.selectedProduct);
|
||||
if (!product) {
|
||||
return 0;
|
||||
}
|
||||
const productRevenue = this.calculateProductRevenue(product);
|
||||
return productRevenue.absolute;
|
||||
},
|
||||
toggleRevenueTable() {
|
||||
this.isRevenueTableOpen = !this.isRevenueTableOpen;
|
||||
},
|
||||
calculateProductRevenue(product) {
|
||||
if (!product.knowledges || product.knowledges.length === 0) {
|
||||
return { absolute: 0, perMinute: 0 };
|
||||
}
|
||||
const knowledgeFactor = product.knowledges[0]?.knowledge || 0;
|
||||
const maxPrice = product.category * 9;
|
||||
const minPrice = maxPrice * 0.6;
|
||||
const revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
|
||||
const productionTimeInMinutes = product.productionTime;
|
||||
const perMinute = (revenuePerUnit / productionTimeInMinutes).toFixed(2);
|
||||
return {
|
||||
absolute: revenuePerUnit.toFixed(2),
|
||||
perMinute: perMinute,
|
||||
};
|
||||
},
|
||||
async fetchBranch(data) {
|
||||
try {
|
||||
if (data.branchId === this.selectedBranch.id) {
|
||||
this.getBranchData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
async getBranchData(newBranch = null) {
|
||||
try {
|
||||
const response = await apiClient.get(`/api/falukant/branches/${newBranch ? newBranch.id : this.selectedBranch.id}`);
|
||||
this.branchData = response.data;
|
||||
this.director = response.data.director;
|
||||
this.productions = response.data.productions.sort((a, b) => {
|
||||
const endTimeA = new Date(a.startTimestamp).getTime() + a.productType.productionTime * 60 * 1000;
|
||||
const endTimeB = new Date(b.startTimestamp).getTime() + b.productType.productionTime * 60 * 1000;
|
||||
return endTimeA - endTimeB;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
calculateEndDateTime(startTimestamp, productionTime) {
|
||||
const startTime = new Date(startTimestamp);
|
||||
const endTime = new Date(startTime.getTime() + productionTime * 60 * 1000);
|
||||
return endTime.toLocaleString(); // Datum und Uhrzeit
|
||||
},
|
||||
calculateRemainingTime(startTimestamp, productionTime) {
|
||||
const startTime = new Date(startTimestamp).getTime();
|
||||
const endTime = startTime + productionTime * 60 * 1000;
|
||||
const now = Date.now();
|
||||
const remainingSeconds = Math.max(Math.floor((endTime - now) / 1000), 0);
|
||||
return remainingSeconds; // Verbleibende Zeit in Sekunden
|
||||
},
|
||||
startTimer() {
|
||||
this.timer = setInterval(() => {
|
||||
this.$forceUpdate(); // Aktualisiert die verbleibende Zeit in der Tabelle
|
||||
}, 1000);
|
||||
},
|
||||
stopTimer() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
sellItem(index) {
|
||||
const item = this.inventory[index];
|
||||
console.log(`Selling ${item.sellQuantity || item.totalQuantity} of ${item.product.labelTr}`);
|
||||
const quantityToSell = item.sellQuantity || item.totalQuantity;
|
||||
item.totalQuantity -= quantityToSell;
|
||||
if (item.totalQuantity <= 0) {
|
||||
this.inventory.splice(index, 1);
|
||||
}
|
||||
item.sellQuantity = null;
|
||||
},
|
||||
sellAll() {
|
||||
console.log('Selling all items in inventory');
|
||||
this.inventory.forEach(item => {
|
||||
console.log(`Selling ${item.totalQuantity} of ${item.product.labelTr}`);
|
||||
});
|
||||
this.inventory = []; // Leert das Inventar
|
||||
},
|
||||
async loadInventory() {
|
||||
try {
|
||||
const response = await apiClient.get(`/api/falukant/inventory/${this.selectedBranch?.id}`, {});
|
||||
this.inventory = response.data.map(item => ({
|
||||
...item,
|
||||
sellQuantity: item.totalQuantity, // Voreinstellung
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error loading inventory:', error);
|
||||
this.$refs.errorDialog.open(this.$t('falukant.branch.sale.loadError'));
|
||||
}
|
||||
},
|
||||
async handleProductionReadyEvent(message) {
|
||||
try {
|
||||
console.log('Production ready event received:', message);
|
||||
if (message.branch_id === this.selectedBranch?.id) {
|
||||
await this.loadBranchData(this.selectedBranch);
|
||||
await this.loadInventory();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing production_ready event:', error);
|
||||
console.error(message);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -127,4 +457,110 @@ export default {
|
||||
button {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.production-section {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.production-section label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.production-section input[type="number"] {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.revenue-section {
|
||||
border: 1px solid #ccc;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.revenue-section h3 {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.revenue-section button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #007bff;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.revenue-section button:hover {
|
||||
color: #0056b3;
|
||||
}
|
||||
|
||||
.revenue-table {
|
||||
margin-top: 10px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.revenue-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.revenue-table th,
|
||||
.revenue-table td {
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.revenue-table th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.production-section table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.production-section th,
|
||||
.production-section td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.production-section th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.inventory-table {
|
||||
margin-top: 10px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.inventory-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.inventory-table th,
|
||||
.inventory-table td {
|
||||
text-align: left;
|
||||
padding: 2px 3px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.inventory-table td button {
|
||||
margin: 0 0 0 5px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.inventory-table th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user