Files
FEDEO/spaces/pages/outgoingInvoices.vue
flfeders 3167b6a20a Changed Plants to Objects
Changes in outgoinginvoices
2024-01-27 11:54:14 +01:00

835 lines
25 KiB
Vue

<script setup>
import dayjs from "dayjs"
const dataStore = useDataStore()
const user = useSupabaseUser()
const tabItems = [
{
label: "Editor"
},
{
label: "Vorschau"
}
]
const itemInfo = ref({
customer: 13,
contact: 8,
address: {
street: null,
special: null,
zip: null,
city: null,
},
project: 6,
documentNumber: "RE23-1409",
documentDate: "10.01.2024",
deliveryDate: "10.01.2024",
dateOfPerformance: null,
createdBy: user.value.id,
title: "TITEL",
description: "BESCHREIBUNG",
startText: "Sehr geehrte Frau Sindern,\n" +
"wir bedanken uns für Ihr entgegengebrachtes Vertrauen und Ihren Auftrag und stellen Ihnen\n" +
"folgende Positionen in Rechnung: ",
endText: "Bitte überweisen Sie den Rechnungsbetrag unter Angabe der Rechnungsnummer im Verwendungszweck innerhalb von 10 Tagen auf das unten angegebene Konto. Wir bedanken uns für das entgegengebrachte Vertrauen und freuen uns auf eine weitere gute Zusammenarbeit.",
rows: [
{
id: 1,
pos: 1,
mode: "free",
text: "FahrtkostenFahrtkostenFahrtkostenFahrtkosten",
quantity: 204,
unit: 3,
price: 0.80,
taxPercent: 19,
discountPercent: 0
},
{
id: 2,
pos: 2,
mode: "free",
text: "Test",
quantity: 1,
unit: 1,
price: 10,
taxPercent: 19,
discountPercent: 0
}
]
})
const getRowAmount = (row) => {
return String(Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(2)).replace('.',',')
}
const addPosition = (mode) => {
let lastId = 0
itemInfo.value.rows.forEach(row => {
if(row.id > lastId) lastId = row.id
})
if(mode === 'free'){
itemInfo.value.rows.push({
id: lastId +1,
mode: "free",
text: "",
quantity: 1,
unit: 1,
price: 0,
taxPercent: 19,
discountPercent: 0
})
} else if(mode === 'normal'){
itemInfo.value.rows.push({
id: lastId +1,
mode: "normal",
quantity: 1,
price: 0,
taxPercent: 19,
discountPercent: 0
})
} else if(mode === "pagebreak") {
itemInfo.value.rows.push({
id: lastId +1,
mode: "pagebreak",
})
}
setPosNumbers()
}
const removePosition = (id) => {
let rows = itemInfo.value.rows.filter(row => row.id !== id)
/*rows = rows.sort((a,b) => a.pos - b.pos)
rows.forEach((row,index) => {
rows[index] = {...row, pos: index + 1}
})*/
itemInfo.value.rows = rows
setPosNumbers()
}
const documentTotal = computed(() => {
let totalNet = 0
let total19 = 0
let totalGross = 0
itemInfo.value.rows.forEach(row => {
if(row.mode === 'free' || row.mode === 'normal'){
let rowPrice = Number(Number(row.quantity) * Number(row.price)).toFixed(2)
totalNet += Number(rowPrice)
if(row.taxPercent === 19) {
total19 += Number(rowPrice * 0.19)
}
}
})
return {
totalNet: `${String(totalNet.toFixed(2)).replace(".",",")}`,
total19: `${String(total19.toFixed(2)).replace(".",",")}`,
totalGross: `${String(Number(totalNet + total19).toFixed(2)).replace(".",",")}`
}
})
const getDocumentData = () => {
let customerData = dataStore.getCustomerById(itemInfo.value.customer)
let contactData = dataStore.getContactById(itemInfo.value.contact)
let userData = dataStore.getProfileById(user.value.id)
let rows = itemInfo.value.rows.map(row => {
let unit = dataStore.units.find(i => i.id === row.unit)
if(row.mode === 'free' || row.mode === 'normal') {
if(row.mode === 'normal') row.text = dataStore.getProductById(row.product).name
return {
...row,
rowAmount: `${getRowAmount(row)}`,
quantity: String(row.quantity).replace(".",","),
unit: unit.short,
pos: String(row.pos),
price: `${String(row.price.toFixed(2)).replace(".",",")}`,
discountText: `(Rabatt: ${row.discountPercent} %)`
}
} else {
return row
}
})
const returnData = {
recipient: {
name: customerData.name,
contact: `${contactData.firstName} ${contactData.lastName}`,
street: customerData.infoData.street,
special: "",
city: customerData.infoData.city,
zip: customerData.infoData.zip
},
info: {
customerNumber: customerData.customerNumber,
documentNumber: itemInfo.value.documentNumber,
documentDate: dayjs(itemInfo.value.documentDate, 'DD.MM.YYYY').format("DD.MM.YYYY"),
deliveryDate: dayjs(itemInfo.value.deliveryDate, 'DD.MM.YYYY').format("DD.MM.YYYY"),
contactPerson: userData.fullName ||"",
contactTel: userData.mobileTel ||"",
contactEMail: userData.email,
project: dataStore.getProjectById(itemInfo.value.project).name
},
title: itemInfo.value.title,
description: itemInfo.value.description,
endText: itemInfo.value.endText,
startText: itemInfo.value.startText,
rows: rows,
total: documentTotal.value
}
console.log(returnData)
return returnData
}
const showDocument = ref(false)
const uri = ref("")
const generateDocument = async () => {
uri.value = await useCreatePdf(getDocumentData())
//alert(uri.value)
showDocument.value = true
}
const onChangeTab = (index) => {
if(index === 1) {
generateDocument()
}
}
const setPosNumbers = () => {
let index = 1
let rows = itemInfo.value.rows.map(row => {
if(row.mode === 'free' ||row.mode === 'normal') {
row.pos = index
index += 1
}
return row
})
}
</script>
<template>
<!-- <UButton
@click="createPdf"
>TEST</UButton>-->
<UCard class="h-fit">
<UTabs :items="tabItems" @change="onChangeTab">
<template #item="{item}">
<div v-if="item.label === 'Editor'">
<InputGroup>
<div class="flex-auto mr-5">
<UFormGroup
label="Kunde:"
>
<USelectMenu
:options="dataStore.customers"
option-attribute="name"
value-attribute="id"
:search-attributes="['name']"
searchable
searchable-placeholder="Suche..."
v-model="itemInfo.customer"
>
<template #label>
{{dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).name : "Kein Kunde ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Ansprechpartner:"
>
<USelectMenu
:options="dataStore.getContactsByCustomerId(itemInfo.customer)"
option-attribute="fullName"
value-attribute="id"
:search-attributes="['name']"
searchable
searchable-placeholder="Suche..."
v-model="itemInfo.contact"
>
<template #label>
{{dataStore.getContactById(itemInfo.contact) ? dataStore.getContactById(itemInfo.contact).fullName : "Kein Kontakt ausgewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Adresse:"
>
<UInput
v-model="itemInfo.address.street"
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.street : 'Straße + Hausnummer'"
/>
<UInput
v-model="itemInfo.address.special"
class="mt-3"
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.special : 'Adresszusatz'"
/>
<InputGroup class="mt-3">
<UInput
class="flex-auto"
v-model="itemInfo.address.zip"
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.zip : 'PLZ'"
/>
<UInput
class="flex-auto"
v-model="itemInfo.address.city"
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.city : 'Ort'"
/>
</InputGroup>
</UFormGroup>
</div>
<div class="flex-auto">
<UFormGroup
label="Rechnungsnummer:"
>
<UInput
v-model="itemInfo.documentNumber"
/>
</UFormGroup>
<UFormGroup
label="Datum:"
>
<UInput
v-model="itemInfo.documentDate"
/>
</UFormGroup>
<UFormGroup
label="Lieferdatum:"
>
<UInput
v-model="itemInfo.deliveryDate"
/>
</UFormGroup>
<UFormGroup
label="Ansprechpartner:"
>
<UInput
/>
</UFormGroup>
<UFormGroup
label="Kontakt Telefon:"
>
<UInput
/>
</UFormGroup>
<UFormGroup
label="Kontakt E-Mail:"
>
<UInput
/>
</UFormGroup><UFormGroup
label="Kontakt E-Mail:"
>
<UInput
/>
</UFormGroup>
<UFormGroup
label="Projekt:"
>
<USelectMenu
:options="dataStore.projects"
v-model="itemInfo.project"
value-attribute="id"
option-attribute="name"
searchable
searchable-placeholder="Suche..."
:search-attributes="['name']"
>
<template #label>
{{dataStore.getProjectById(itemInfo.project) ? dataStore.getProjectById(itemInfo.project).name : "Kein Projekt ausgewählt"}}
</template>
<template #option="{option: project}">
{{dataStore.getCustomerById(project.customer).name}} - {{project.name}}
</template>
</USelectMenu>
</UFormGroup>
</div>
</InputGroup>
<UDivider
class="my-3"
/>
<UFormGroup
label="Titel:"
>
<UInput v-model="itemInfo.title"/>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
class="mt-3"
>
<UInput v-model="itemInfo.description"/>
</UFormGroup>
<UDivider
class="my-3"
/>
<UFormGroup
label="Einleitung:"
>
<UTextarea
v-model="itemInfo.startText"
/>
</UFormGroup>
<UDivider
class="my-3"
/>
<table class="w-full">
<thead>
<tr>
<th></th>
<th>Pos.</th>
<th>Produkt / Leistung</th>
<th>Menge</th>
<th>Einheit</th>
<th>Preis</th>
<th>Steuer</th>
<th>Rabatt</th>
<th>Gesamt</th>
</tr>
</thead>
<draggable
v-model="itemInfo.rows"
handle=".handle"
tag="tbody"
itemKey="pos"
@end="setPosNumbers"
>
<template #item="{element: row}">
<tr
>
<td>
<UIcon
class="handle"
name="i-mdi-menu"
/>
</td>
<td
v-if="row.mode === 'pagebreak'"
colspan="8"
>
<UDivider/>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
>{{row.pos}}</td>
<td
class="w-120"
v-if="row.mode === 'free'"
>
<UInput
v-model="row.text"
maxlength="40"
placeholder="Name"
/>
</td>
<td
class="w-120"
v-else-if="row.mode === 'normal'"
>
<USelectMenu
:options="dataStore.products"
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suche ..."
:search-attributes="['name']"
v-model="row.product"
@change="row.unit = dataStore.getProductById(row.product).unit,
row.price = dataStore.getProductById(row.product).sellingPrice || 0"
>
<template #label>
{{dataStore.getProductById(row.product) ?dataStore.getProductById(row.product).name : "Kein Produkt ausgewählt" }}
</template>
</USelectMenu>
</td>
<td
class="w-20"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.quantity"
type="number"
:step="dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).step : '1' "
/>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<USelectMenu
v-model="row.unit"
:options="dataStore.units"
option-attribute="name"
value-attribute="id"
>
<template #label>
{{dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).name : "Keine Einheit gewählt"}}
</template>
</USelectMenu>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.price"
type="number"
step="0.01"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<USelectMenu
:options="['19','7','0']"
v-model="row.taxPercent"
>
<template #option="{option}">
{{option}} %
</template>
<template #label>
{{row.taxPercent}} %
</template>
</USelectMenu>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.discountPercent"
type="number"
step="0.01"
placeholder="0"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
</template>
</UInput>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<div class="text-right font-bold">{{getRowAmount(row)}} </div>
</td>
<td>
<UButton
variant="ghost"
color="rose"
icon="i-heroicons-x-mark-16-solid"
@click="removePosition(row.id)"
/>
</td>
</tr>
</template>
</draggable>
<!-- <template v-for="row in itemInfo.rows">
<tr
>
<td
v-if="row.mode === 'pagebreak'"
colspan="8"
>
<UDivider/>
</td>
<td
rowspan="2"
v-if="row.mode === 'free' || row.mode === 'normal'"
>{{row.pos}}</td>
<td
class="w-120"
v-if="row.mode === 'free'"
>
<UInput
v-model="row.text"
maxlength="40"
placeholder="Name"
/>
</td>
<td
class="w-120"
v-else-if="row.mode === 'normal'"
>
<USelectMenu
:options="dataStore.products"
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suche ..."
:search-attributes="['name']"
v-model="row.product"
@change="row.unit = dataStore.getProductById(row.product).unit,
row.price = dataStore.getProductById(row.product).sellingPrice || 0"
>
<template #label>
{{dataStore.getProductById(row.product) ?dataStore.getProductById(row.product).name : "Kein Produkt ausgewählt" }}
</template>
</USelectMenu>
</td>
<td
class="w-20"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.quantity"
type="number"
:step="dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).step : '1' "
/>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<USelectMenu
v-model="row.unit"
:options="dataStore.units"
option-attribute="name"
value-attribute="id"
>
<template #label>
{{dataStore.units.find(i => i.id === row.unit) ? dataStore.units.find(i => i.id === row.unit).name : "Keine Einheit gewählt"}}
</template>
</USelectMenu>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.price"
type="number"
step="0.01"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<USelectMenu
:options="['19','7','0']"
v-model="row.taxPercent"
>
<template #option="{option}">
{{option}} %
</template>
<template #label>
{{row.taxPercent}} %
</template>
</USelectMenu>
&lt;!&ndash; <UInput
v-model="row.taxPercent"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
</template>
</UInput>&ndash;&gt;
</td>
<td
class="w-40"
v-if="row.mode === 'free' || row.mode === 'normal'"
>
<UInput
v-model="row.discountPercent"
type="number"
step="0.01"
placeholder="0"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
</template>
</UInput>
</td>
<td
v-if="row.mode === 'free' || row.mode === 'normal'"
rowspan="2"
>
<div class="text-right font-bold">{{getRowAmount(row)}} </div>
</td>
<td rowspan="2">
<UButton
variant="ghost"
color="rose"
icon="i-heroicons-x-mark-16-solid"
@click="removePosition(row.pos)"
/>
</td>
</tr>
<tr>
<td colspan="6">
<UTextarea
v-model="row.description"
placeholder="Beschreibung"
/>
</td>
</tr>
</template>-->
</table>
<table>
<tr>
<td class="font-bold">Netto:</td>
<td>{{documentTotal.totalNet}}</td>
</tr>
<tr>
<td class="font-bold">zzgl. 19 % USt:</td>
<td>{{documentTotal.total19}}</td>
</tr>
<tr>
<td class="font-bold">Brutto:</td>
<td>{{documentTotal.totalGross}}</td>
</tr>
</table>
<InputGroup>
<UButton
@click="addPosition('normal')"
class="mt-3"
>
+ Artikelposition
</UButton>
<UButton
@click="addPosition('free')"
class="mt-3"
>
+ Freitextposition
</UButton>
<UButton
@click="addPosition('pagebreak')"
class="mt-3"
>
+ Seitenumbruch
</UButton>
</InputGroup>
<UDivider
class="my-3"
/>
<UFormGroup
label="Nachbemerkung:"
>
<UTextarea
v-model="itemInfo.endText"
/>
</UFormGroup>
</div>
<div v-else-if="item.label === 'Vorschau'">
<!-- <UButton
@click="generateDocument"
>
Show
</UButton>-->
<object
:data="uri"
v-if="showDocument"
type="application/pdf"
class="w-full previewDocument"
/>
</div>
</template>
</UTabs>
</UCard>
</template>
<style scoped>
th {
text-align: left;
}
td {
padding: .4em;
}
/*tr:hover {
border: 1px solid #69c350;
}*/
.previewDocument {
height: 80vh;
}
</style>