Merge branch 'dev' into beta
This commit is contained in:
@@ -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)"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
149
components/personalComposing.vue
Normal file
149
components/personalComposing.vue
Normal 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>
|
||||||
@@ -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",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user