Merge branch 'dev' into beta

This commit is contained in:
2025-04-19 17:35:12 +02:00
7 changed files with 372 additions and 3 deletions

View File

@@ -1,6 +1,7 @@
<script setup> <script setup>
import dayjs from "dayjs"; import dayjs from "dayjs";
import MaterialComposing from "~/components/materialComposing.vue"; import MaterialComposing from "~/components/materialComposing.vue";
import PersonalComposing from "~/components/personalComposing.vue";
const props = defineProps({ const props = defineProps({
type: { type: {
@@ -545,6 +546,10 @@ const updateItem = async () => {
v-else-if="datapoint.inputType === 'materialComposing'" v-else-if="datapoint.inputType === 'materialComposing'"
:item="item" :item="item"
/> />
<PersonalComposing
v-else-if="datapoint.inputType === 'personalComposing'"
:item="item"
/>
<UButton <UButton
v-if="['text','number','select','date','datetime','textarea'].includes(datapoint.inputType)" v-if="['text','number','select','date','datetime','textarea'].includes(datapoint.inputType)"
@@ -791,6 +796,10 @@ const updateItem = async () => {
v-else-if="datapoint.inputType === 'materialComposing'" v-else-if="datapoint.inputType === 'materialComposing'"
:item="item" :item="item"
/> />
<PersonalComposing
v-else-if="datapoint.inputType === 'personalComposing'"
:item="item"
/>
<UButton <UButton
v-if="['text','number','select','date','datetime','textarea'].includes(datapoint.inputType)" v-if="['text','number','select','date','datetime','textarea'].includes(datapoint.inputType)"

View File

@@ -234,6 +234,11 @@ const links = computed(() => {
to: "/profiles", to: "/profiles",
icon: "i-heroicons-user-group" icon: "i-heroicons-user-group"
}, },
{
label: "Stundensätze",
to: "/standardEntity/hourrates",
icon: "i-heroicons-user-group"
},
... role.checkRight("vehicles") ? [{ ... role.checkRight("vehicles") ? [{
label: "Fahrzeuge", label: "Fahrzeuge",
to: "/standardEntity/vehicles", to: "/standardEntity/vehicles",

View File

@@ -69,6 +69,12 @@ const setRowData = (row) => {
</UButton> </UButton>
<table class="w-full mt-3"> <table class="w-full mt-3">
<tr>
<th>Artikel</th>
<th>Menge</th>
<th>Einheit</th>
<th>Verkaufspreis</th>
</tr>
<tr <tr
v-for="product in props.item.materialComposition" v-for="product in props.item.materialComposition"
> >

View File

@@ -0,0 +1,149 @@
<script setup>
import { v4 as uuidv4 } from 'uuid';
const supabase = useSupabaseClient()
const props = defineProps({
item: {
type: Object,
required: true
}
})
const hourrates = ref([])
const units = ref([])
const setup = async () => {
hourrates.value = await useSupabaseSelect("hourrates")
units.value = (await supabase.from("units").select()).data
}
setup()
const addRowToPersonalComposition = () => {
if(!props.item.personalComposition) props.item.personalComposition = []
props.item.personalComposition.push({
id: uuidv4(),
unit: 6,
quantity: 1,
price: 0,
name: "Standard"
})
calculateTotalPersonalPrice()
}
const removeRowFromPersonalComposition = (id) => {
props.item.personalComposition = props.item.personalComposition.filter((item) => item.id !== id)
calculateTotalPersonalPrice()
}
const calculateTotalPersonalPrice = () => {
let total = 0
props.item.personalComposition.forEach((item) => {
let rowSum = item.quantity * item.price
total += rowSum
})
props.item.sellingPriceComposed.worker = Number(total.toFixed(2))
props.item.sellingPriceComposed.total = Number((props.item.sellingPriceComposed.worker + props.item.sellingPriceComposed.material).toFixed(2))
}
const setRowData = (row) => {
let hourrate = hourrates.value.find(i => i.id === row.hourrate)
row.purchasePrice = hourrate.purchasePrice
row.price = hourrate.sellingPrice
}
</script>
<template>
<UCard class="w-full">
<UButton
@click="addRowToPersonalComposition"
>
+ Stundensatz
</UButton>
<table class="w-full mt-3">
<tr>
<th>Name</th>
<th>Menge</th>
<th>Einheit</th>
<th>Einkaufpreis</th>
<th>Verkaufspreis</th>
</tr>
<tr
v-for="row in props.item.personalComposition"
>
<td>
<USelectMenu
searchable
:search-attributes="['name']"
:options="hourrates"
value-attribute="id"
option-attribute="name"
v-model="row.hourrate"
:color="row.hourrate ? 'primary' : 'rose'"
@change="setRowData(row)"
>
<!-- <template #label>
{{products.find(i => i.id === product.product) ? products.find(i => i.id === product.product).name : 'Kein Artikel ausgewählt'}}
</template>-->
</USelectMenu>
</td>
<td>
<UInput
type="number"
v-model="row.quantity"
:step="units.find(i => i.id === row.unit) ? units.find(i => i.id === row.unit).step : 1"
@change="calculateTotalPersonalPrice"
/>
</td>
<td>
<USelectMenu
:options="units"
disabled
value-attribute="id"
option-attribute="name"
v-model="row.unit"
></USelectMenu>
</td>
<td>
<UInput
type="number"
v-model="row.purchasePrice"
step="0.01"
@change="calculateTotalPersonalPrice"
/>
</td>
<td>
<UInput
type="number"
v-model="row.price"
step="0.01"
@change="calculateTotalPersonalPrice"
/>
</td>
<td>
<UButton
icon="i-heroicons-x-mark"
@click="removeRowFromPersonalComposition(row.id)"
variant="outline"
color="rose"
/>
</td>
</tr>
</table>
</UCard>
</template>
<style scoped>
td {
margin-bottom: 0.5em;
}
</style>

View File

@@ -258,6 +258,18 @@ export const useRole = () => {
label: "Dokuemntenboxen erstellen", label: "Dokuemntenboxen erstellen",
parent: "documentboxes" parent: "documentboxes"
}, },
hourrates: {
label: "Stundensätze",
showToAllUsers: false
},
"hourrates-viewAll": {
label: "Alle Stundensätze einsehen",
parent: "hourrates"
},
"hourrates-create": {
label: "Stundensätze erstellen",
parent: "hourrates"
},
"inventory": { "inventory": {
label: "Lager", label: "Lager",
}, },

View File

@@ -696,6 +696,74 @@ const documentTotal = computed(() => {
} }
}) })
const documentReport = computed(() => {
let totalProductsPurchasePrice = 0
let totalProductsFromServicesPurchasePrice = 0
let totalHoursFromServices = {
total: 0,
totalPurchasePrice: 0,
byName: {}
}
let totalHoursSellingPrice = 0
itemInfo.value.rows.forEach(row => {
if(row.product) {
let product = products.value.find(i => i.id === row.product)
console.log(product)
totalProductsPurchasePrice += product.purchasePrice * row.quantity
} else if(row.service) {
let service = services.value.find(i => i.id === row.service)
console.log(service)
if(service.materialComposition) {
service.materialComposition.forEach(entry => {
let productData = products.value.find(i => i.id === entry.product)
console.log(productData)
totalProductsFromServicesPurchasePrice += productData.purchasePrice * entry.quantity * row.quantity
})
}
if(service.personalComposition) {
service.personalComposition.forEach(entry => {
totalHoursFromServices.total += entry.quantity
totalHoursFromServices.totalPurchasePrice += entry.quantity * entry.purchasePrice
totalHoursSellingPrice += entry.quantity * entry.price
if(totalHoursFromServices.byName[entry.name]) {
totalHoursFromServices.byName[entry.name] += entry.quantity
} else {
totalHoursFromServices.byName[entry.name] = entry.quantity
}
})
}
//totalProductsPurchasePrice += product.purchasePrice * row.quantity
}
})
let totalMargin = documentTotal.value.totalNet - totalProductsPurchasePrice - totalProductsFromServicesPurchasePrice - totalHoursFromServices.totalPurchasePrice
return {
totalProductsPurchasePrice,
totalProductsFromServicesPurchasePrice,
totalMargin,
totalHoursFromServices,
totalHoursSellingPrice,
}
})
const processDieselPosition = () => { const processDieselPosition = () => {
let agricultureData = { let agricultureData = {
dieselUsageTotal: 0, dieselUsageTotal: 0,
@@ -2499,11 +2567,41 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</tr> </tr>
</table> </table>
<UDivider class="my-3" v-if="itemInfo.rows.length > 0">Gesamt</UDivider> <UDivider class="my-3" v-if="itemInfo.rows.length > 0">Auswertung & Gesamt</UDivider>
<div class="w-full flex justify-end" v-if="itemInfo.type !== 'deliveryNotes'"> <div class="w-full flex justify-between" v-if="itemInfo.type !== 'deliveryNotes'">
<table class="w-1/3" v-if="itemInfo.rows.length > 0"> <table class="w-1/2">
<tr v-if="documentReport.totalProductsPurchasePrice !== 0">
<td>Einkaufspreis Artikel Gesamt:</td>
<td class="text-right">{{useCurrency(documentReport.totalProductsPurchasePrice)}}</td>
</tr>
<tr v-if="documentReport.totalProductsFromServicesPurchasePrice !== 0">
<td>Einkaufspreis Artikel aus Leistungen Gesamt:</td>
<td class="text-right">{{useCurrency(documentReport.totalProductsFromServicesPurchasePrice)}}</td>
</tr>
<tr v-if="documentReport.totalHoursFromServices.totalPurchasePrice !== 0">
<td>Einkaufspreis Personal aus Leistungen Gesamt:</td>
<td class="text-right">{{useCurrency(documentReport.totalHoursFromServices.totalPurchasePrice)}}</td>
</tr>
<tr>
<td>Gewinn Gesamt:</td>
<td class="text-right">{{useCurrency(documentReport.totalMargin)}}</td>
</tr>
<tr v-if="documentReport.totalHoursSellingPrice !== 0">
<td>Lohnkosten Verkauf:</td>
<td class="text-right">{{useCurrency(documentReport.totalHoursSellingPrice)}}</td>
</tr>
<tr v-for="key in Object.keys(documentReport.totalHoursFromServices.byName)">
<td>{{key}}</td>
<td class="text-right">{{documentReport.totalHoursFromServices.byName[key]}} h</td>
</tr>
<tr v-if="documentReport.totalHoursFromServices.total !== 0">
<td>Stunden Gesamt:</td>
<td class="text-right">{{documentReport.totalHoursFromServices.total}} h</td>
</tr>
</table>
<table class="w-1/3 h-full" v-if="itemInfo.rows.length > 0">
<tr> <tr>
<td class="font-bold">Netto:</td> <td class="font-bold">Netto:</td>
<td class="text-right">{{renderCurrency(documentTotal.totalNet)}}</td> <td class="text-right">{{renderCurrency(documentTotal.totalNet)}}</td>
@@ -2527,6 +2625,45 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</table> </table>
</div> </div>
<!-- <UDivider
class="my-3"
>Auswertung</UDivider>
<div class="w-full flex justify-end">
<table class="w-1/3">
<tr v-if="documentReport.totalProductsPurchasePrice !== 0">
<td>Einkaufspreis Artikel Gesamt:</td>
<td class="text-right">{{useCurrency(documentReport.totalProductsPurchasePrice)}}</td>
</tr>
<tr v-if="documentReport.totalProductsFromServicesPurchasePrice !== 0">
<td>Einkaufspreis Artikel aus Leistungen Gesamt:</td>
<td class="text-right">{{useCurrency(documentReport.totalProductsFromServicesPurchasePrice)}}</td>
</tr>
<tr v-if="documentReport.totalHoursFromServices.totalPurchasePrice !== 0">
<td>Einkaufspreis Personal aus Leistungen Gesamt:</td>
<td class="text-right">{{useCurrency(documentReport.totalHoursFromServices.totalPurchasePrice)}}</td>
</tr>
<tr>
<td>Gewinn Gesamt:</td>
<td class="text-right">{{useCurrency(documentReport.totalMargin)}}</td>
</tr>
<tr v-if="documentReport.totalHoursSellingPrice !== 0">
<td>Lohnkosten Verkauf:</td>
<td class="text-right">{{useCurrency(documentReport.totalHoursSellingPrice)}}</td>
</tr>
<tr v-for="key in Object.keys(documentReport.totalHoursFromServices.byName)">
<td>{{key}}</td>
<td class="text-right">{{documentReport.totalHoursFromServices.byName[key]}} h</td>
</tr>
<tr v-if="documentReport.totalHoursFromServices.total !== 0">
<td>Stunden Gesamt:</td>
<td class="text-right">{{documentReport.totalHoursFromServices.total}} h</td>
</tr>
</table>
</div>-->
<UDivider <UDivider
class="my-3" class="my-3"
/> />

View File

@@ -1687,6 +1687,11 @@ export const useDataStore = defineStore('data', () => {
label: "Zusammensetzung Materialpreis pro Einheit", label: "Zusammensetzung Materialpreis pro Einheit",
inputType: "materialComposing", inputType: "materialComposing",
}, },
{
key: "personalComposing",
label: "Zusammensetzung Personal pro Einheit",
inputType: "personalComposing",
},
{ {
key: "sellingPriceComposed.total", key: "sellingPriceComposed.total",
label: "Verkaufspreis Gesamt pro Einheit", label: "Verkaufspreis Gesamt pro Einheit",
@@ -1719,6 +1724,52 @@ export const useDataStore = defineStore('data', () => {
} }
] ]
}, },
hourrates: {
isArchivable: true,
label: "Stundensätze",
labelSingle: "Stundensatz",
isStandardEntity: true,
redirect: true,
supabaseSelectWithInformation: "*",
historyItemHolder: "hourrate",
filters: [{
name: "Archivierte ausblenden",
default: true,
"filterFunction": function (row) {
if(!row.archived) {
return true
} else {
return false
}
}
}],
templateColumns: [
{
key: "name",
label: "Name",
required: true,
title: true,
inputType: "text"
},
{
key: "purchasePrice",
label: "Einkauspreis",
inputType: "number",
component: purchasePrice,
},
{
key: "sellingPrice",
label: "Verkaufspreis",
inputType: "number",
component: sellingPrice,
},
],
showTabs: [
{
label: 'Informationen',
}
]
},
events: { events: {
isArchivable: true, isArchivable: true,
label: "Termine", label: "Termine",