1071 lines
34 KiB
Vue
1071 lines
34 KiB
Vue
<script setup>
|
|
import dayjs from "dayjs"
|
|
import Handlebars from "handlebars"
|
|
|
|
const dataStore = useDataStore()
|
|
const user = useSupabaseUser()
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const supabase = useSupabaseClient()
|
|
|
|
|
|
definePageMeta({
|
|
middleware: "auth"
|
|
})
|
|
|
|
|
|
|
|
const itemInfo = ref({
|
|
type: "invoices",
|
|
customer: null,
|
|
contact: null,
|
|
address: {
|
|
street: null,
|
|
special: null,
|
|
zip: null,
|
|
city: null,
|
|
},
|
|
project: null,
|
|
documentNumber: null,
|
|
documentNumberTitle: "Rechnungsnummer",
|
|
documentDate: dayjs(),
|
|
deliveryDate: dayjs(),
|
|
deliveryDateType: "Lieferdatum",
|
|
dateOfPerformance: null,
|
|
paymentDays: 7,
|
|
createdBy: user.value.id,
|
|
title: null,
|
|
description: null,
|
|
startText: null,
|
|
endText: null,
|
|
rows: [
|
|
|
|
],
|
|
contactPerson: null,
|
|
contactPersonName: null,
|
|
contactTel: null,
|
|
contactEMail: null,
|
|
})
|
|
|
|
const tabItems = computed(() => {
|
|
return [
|
|
{
|
|
label: "Editor"
|
|
},
|
|
{
|
|
label: "Vorschau",
|
|
disabled: !itemInfo.value.customer && !itemInfo.value.contact
|
|
}
|
|
]
|
|
})
|
|
|
|
const setupPage = () => {
|
|
if(route.params) {
|
|
if(route.params.id) itemInfo.value = dataStore.getCreatedDocumentById(Number(route.params.id))
|
|
|
|
if(!itemInfo.value.deliveryDateType) itemInfo.value.deliveryDateType = "Lieferdatum"
|
|
|
|
}
|
|
|
|
if(route.query) {
|
|
if(route.query.type) itemInfo.value.type = route.query.type
|
|
|
|
if(itemInfo.value.type === "invoices") {
|
|
itemInfo.value.documentNumberTitle = "Rechnungsnummer"
|
|
itemInfo.value.title = `Rechnung-Nr. ${itemInfo.value.documentNumber ? itemInfo.value.documentNumber : "XXXX"}`
|
|
} else if(itemInfo.value.type === "quotes") {
|
|
itemInfo.value.documentNumberTitle = "Angebotsnummer"
|
|
itemInfo.value.title = `Angebot-Nr. ${itemInfo.value.documentNumber ? itemInfo.value.documentNumber : "XXXX"}`
|
|
} else if(itemInfo.value.type === "deliveryNotes") {
|
|
itemInfo.value.documentNumberTitle = "Lieferscheinnummer"
|
|
itemInfo.value.title = `Lieferschein-Nr. ${itemInfo.value.documentNumber ? itemInfo.value.documentNumber : "XXXX"}`
|
|
}
|
|
|
|
itemInfo.value.startText = dataStore.getTextTemplatesByDocumentType(itemInfo.value.type).find(i => i.default && i.pos === "startText").text
|
|
itemInfo.value.endText = dataStore.getTextTemplatesByDocumentType(itemInfo.value.type).find(i => i.default && i.pos === "endText").text
|
|
|
|
setContactPersonData()
|
|
|
|
|
|
if(route.query.project) itemInfo.value.project = Number(route.query.project)
|
|
if(route.query.contact) itemInfo.value.contact = Number(route.query.contact)
|
|
if(route.query.customer) itemInfo.value.customer = Number(route.query.customer)
|
|
}
|
|
}
|
|
|
|
const setCustomerData = () => {
|
|
let customer = dataStore.getCustomerById(itemInfo.value.customer)
|
|
itemInfo.value.contact = null
|
|
if(customer) {
|
|
itemInfo.value.address.street = customer.infoData.street
|
|
itemInfo.value.address.zip = customer.infoData.zip
|
|
itemInfo.value.address.city = customer.infoData.city
|
|
itemInfo.value.address.special = customer.infoData.special
|
|
}
|
|
}
|
|
|
|
const setContactPersonData = () => {
|
|
if(!itemInfo.value.contactPerson) itemInfo.value.contactPerson = user.value.id
|
|
|
|
let profile = dataStore.getProfileById(itemInfo.value.contactPerson)
|
|
|
|
itemInfo.value.contactPersonName = profile.fullName
|
|
itemInfo.value.contactTel = profile.mobileTel || profile.fixedTel || ""
|
|
itemInfo.value.contactEMail = profile.email
|
|
|
|
}
|
|
|
|
|
|
|
|
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 === 'service'){
|
|
itemInfo.value.rows.push({
|
|
id: lastId +1,
|
|
mode: "service",
|
|
quantity: 1,
|
|
price: 0,
|
|
taxPercent: 19,
|
|
discountPercent: 0
|
|
})
|
|
} else if(mode === "pagebreak") {
|
|
itemInfo.value.rows.push({
|
|
id: lastId +1,
|
|
mode: "pagebreak",
|
|
})
|
|
}
|
|
|
|
setPosNumbers()
|
|
|
|
|
|
}
|
|
|
|
const editRowDescription = (row) => {
|
|
rowToEdit.value = row.description
|
|
showEditRowDescription.value = true
|
|
|
|
}
|
|
const showEditRowDescription = ref(false)
|
|
const rowToEdit = ref("")
|
|
const saveRowDescription = () => {
|
|
|
|
}
|
|
|
|
|
|
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 !== 'pagebreak'){
|
|
console.log(row)
|
|
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(2)
|
|
console.log(rowPrice)
|
|
totalNet = totalNet + Number(rowPrice)
|
|
|
|
if(row.taxPercent === 19) {
|
|
total19 = 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 businessInfo = dataStore.ownTenant.businessInfo
|
|
|
|
|
|
|
|
|
|
let rows = itemInfo.value.rows.map(row => {
|
|
|
|
let unit = dataStore.units.find(i => i.id === row.unit)
|
|
|
|
if(row.mode !== 'pagebreak') {
|
|
if(row.mode === 'normal') row.text = dataStore.getProductById(row.product).name
|
|
if(row.mode === 'service') row.text = dataStore.getServiceById(row.service).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
|
|
}
|
|
})
|
|
|
|
//Compile Start & EndText
|
|
const templateStartText = Handlebars.compile(itemInfo.value.startText);
|
|
const templateEndText = Handlebars.compile(itemInfo.value.endText);
|
|
console.log(templateStartText({vorname: contactData.firstName, nachname: contactData.lastName}))
|
|
console.log(templateEndText({zahlungsziel_in_tagen: 14}))
|
|
|
|
|
|
|
|
const returnData = {
|
|
adressLine: `${businessInfo.name}, ${businessInfo.street}, ${businessInfo.zip} ${businessInfo.city}`,
|
|
recipient: {
|
|
name: customerData.name,
|
|
contact: contactData ? `${contactData.firstName} ${contactData.lastName}` : "",
|
|
street: itemInfo.value.address.street || customerData.infoData.street,
|
|
special: itemInfo.value.address.special || customerData.infoData.special,
|
|
city: itemInfo.value.address.city || customerData.infoData.city,
|
|
zip: itemInfo.value.address.zip || customerData.infoData.zip
|
|
},
|
|
info: {
|
|
customerNumber: customerData.customerNumber,
|
|
documentNumber: itemInfo.value.documentNumber,
|
|
documentNumberTitle: itemInfo.value.documentNumberTitle,
|
|
documentDate: dayjs(itemInfo.value.documentDate, 'DD.MM.YYYY').format("DD.MM.YYYY"),
|
|
deliveryDate: dayjs(itemInfo.value.deliveryDate, 'DD.MM.YYYY').format("DD.MM.YYYY"),
|
|
deliveryDateType: itemInfo.value.deliveryDateType,
|
|
contactPerson: itemInfo.value.contactPersonName,
|
|
contactTel: itemInfo.value.contactTel,
|
|
contactEMail: itemInfo.value.contactEMail,
|
|
project: dataStore.getProjectById(itemInfo.value.project) ? dataStore.getProjectById(itemInfo.value.project).name : null
|
|
},
|
|
title: itemInfo.value.title,
|
|
description: itemInfo.value.description,
|
|
endText: templateEndText({zahlungsziel_in_tagen: itemInfo.value.paymentDays}),
|
|
startText: templateStartText({vorname: contactData.firstName, nachname: contactData.lastName}),
|
|
rows: rows,
|
|
total: documentTotal.value
|
|
}
|
|
|
|
console.log(returnData)
|
|
|
|
return returnData
|
|
}
|
|
|
|
|
|
const showDocument = ref(false)
|
|
const uri = ref("")
|
|
const generateDocument = async () => {
|
|
const ownTenant = dataStore.ownTenant
|
|
const path = ownTenant.letterheadConfig[itemInfo.value.type]
|
|
|
|
const {data,error} = await supabase.storage.from("files").download(path)
|
|
|
|
uri.value = await useCreatePdf(getDocumentData(), await data.arrayBuffer())
|
|
//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 !== 'pagebreak') {
|
|
row.pos = index
|
|
index += 1
|
|
}
|
|
return row
|
|
})
|
|
}
|
|
|
|
const saveDocument = async () => {
|
|
let createData = {
|
|
type: itemInfo.value.type,
|
|
state: itemInfo.value.state || "Entwurf",
|
|
customer: itemInfo.value.customer,
|
|
contact: itemInfo.value.contact,
|
|
address: itemInfo.value.address,
|
|
project: itemInfo.value.project,
|
|
documentNumber: itemInfo.value.documentNumber,
|
|
documentDate: itemInfo.value.documentDate,
|
|
deliveryDateType: itemInfo.value.deliveryDateType,
|
|
info: {
|
|
|
|
},
|
|
createdBy: itemInfo.value.createdBy,
|
|
title: itemInfo.value.title,
|
|
description: itemInfo.value.description,
|
|
startText: itemInfo.value.startText,
|
|
endText: itemInfo.value.endText,
|
|
rows: itemInfo.value.rows
|
|
}
|
|
|
|
let data = null
|
|
|
|
if(route.params.id) {
|
|
data = await dataStore.updateItem("createddocuments", {...createData, id: itemInfo.value.id})
|
|
} else {
|
|
data = await dataStore.createNewItem("createddocuments", createData)
|
|
}
|
|
|
|
await router.push(`/createDocument/edit/${data[0].id}`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
const closeDocument = async () => {
|
|
|
|
itemInfo.value.state = "Gebucht"
|
|
|
|
await saveDocument()
|
|
|
|
await generateDocument()
|
|
|
|
|
|
let fileData = {
|
|
tags: [],
|
|
project: null
|
|
}
|
|
|
|
fileData.project = itemInfo.value.project
|
|
fileData.createdDocument = itemInfo.value.id
|
|
fileData.tags.push(dataStore.documentTypesForCreation[itemInfo.value.type].labelSingle)
|
|
|
|
function dataURLtoFile(dataurl, filename) {
|
|
var arr = dataurl.split(","),
|
|
mime = arr[0].match(/:(.*?);/)[1],
|
|
bstr = atob(arr[arr.length - 1]),
|
|
n = bstr.length,
|
|
u8arr = new Uint8Array(n);
|
|
while (n--) {
|
|
u8arr[n] = bstr.charCodeAt(n);
|
|
}
|
|
return new File([u8arr], filename, { type: mime });
|
|
}
|
|
|
|
let file = dataURLtoFile(uri.value, `${itemInfo.value.documentNumber}.pdf`)
|
|
|
|
await dataStore.uploadFiles(fileData, [file], true)
|
|
|
|
|
|
|
|
await router.push(`/createDocument/show/${itemInfo.value.id}`)
|
|
|
|
//console.log(uri)
|
|
}
|
|
|
|
setupPage()
|
|
</script>
|
|
|
|
<template>
|
|
<UDashboardNavbar>
|
|
<template #right>
|
|
<UButton
|
|
icon="i-mdi-content-save"
|
|
@click="saveDocument"
|
|
>
|
|
Entwurf
|
|
</UButton>
|
|
<UButton
|
|
@click="closeDocument"
|
|
>
|
|
Fertigstellen
|
|
</UButton>
|
|
</template>
|
|
</UDashboardNavbar>
|
|
|
|
<UDashboardPanelContent>
|
|
<UTabs class="p-5" :items="tabItems" @change="onChangeTab">
|
|
<template #item="{item}">
|
|
|
|
|
|
<div v-if="item.label === 'Editor'">
|
|
<InputGroup>
|
|
<div class="flex-auto mr-5">
|
|
<UFormGroup
|
|
label="Dokumenttyp:"
|
|
>
|
|
<USelectMenu
|
|
:options="Object.keys(dataStore.documentTypesForCreation).map(i => {return {label: dataStore.documentTypesForCreation[i].labelSingle, type: i }})"
|
|
v-model="itemInfo.type"
|
|
value-attribute="type"
|
|
option-attribute="label"
|
|
>
|
|
<template #label>
|
|
{{dataStore.documentTypesForCreation[itemInfo.type].labelSingle}}
|
|
</template>
|
|
</USelectMenu>
|
|
</UFormGroup>
|
|
<UFormGroup
|
|
label="Kunde:"
|
|
>
|
|
<InputGroup>
|
|
<USelectMenu
|
|
:options="dataStore.customers"
|
|
option-attribute="name"
|
|
value-attribute="id"
|
|
:search-attributes="['name']"
|
|
searchable
|
|
searchable-placeholder="Suche..."
|
|
v-model="itemInfo.customer"
|
|
@change="setCustomerData"
|
|
class="flex-auto"
|
|
>
|
|
<UButton
|
|
:color="itemInfo.customer ? 'primary' : 'rose'"
|
|
variant="outline"
|
|
class="flex-1 justify-between">
|
|
{{dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).name : "Kein Kunde ausgewählt"}}
|
|
|
|
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="['transform rotate-90']" />
|
|
</UButton>
|
|
<!-- <template #label>
|
|
{{dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).name : "Kein Kunde ausgewählt"}}
|
|
</template>-->
|
|
</USelectMenu>
|
|
<UButton
|
|
variant="outline"
|
|
v-if="itemInfo.customer"
|
|
icon="i-heroicons-arrow-right-end-on-rectangle"
|
|
@click="router.push(`/customers/show/${itemInfo.customer}`)"
|
|
>Kunde</UButton>
|
|
</InputGroup>
|
|
|
|
</UFormGroup>
|
|
<UFormGroup
|
|
label="Ansprechpartner:"
|
|
>
|
|
<InputGroup>
|
|
<USelectMenu
|
|
:options="dataStore.getContactsByCustomerId(itemInfo.customer)"
|
|
option-attribute="fullName"
|
|
value-attribute="id"
|
|
:search-attributes="['name']"
|
|
searchable
|
|
searchable-placeholder="Suche..."
|
|
v-model="itemInfo.contact"
|
|
class="flex-auto"
|
|
>
|
|
<UButton
|
|
:color="itemInfo.contact ? 'primary' : 'rose'"
|
|
variant="outline"
|
|
class="flex-1 justify-between">
|
|
{{dataStore.getContactById(itemInfo.contact) ? dataStore.getContactById(itemInfo.contact).fullName : "Kein Kontakt ausgewählt"}}
|
|
|
|
<UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="['transform rotate-90']" />
|
|
</UButton>
|
|
<template #label>
|
|
{{dataStore.getContactById(itemInfo.contact) ? dataStore.getContactById(itemInfo.contact).fullName : "Kein Kontakt ausgewählt"}}
|
|
</template>
|
|
</USelectMenu>
|
|
<UButton
|
|
variant="outline"
|
|
v-if="itemInfo.contact"
|
|
icon="i-heroicons-arrow-right-end-on-rectangle"
|
|
@click="router.push(`/contacts/show/${itemInfo.contact}`)"
|
|
>Kontakt</UButton>
|
|
</InputGroup>
|
|
|
|
</UFormGroup>
|
|
|
|
<UFormGroup
|
|
label="Adresse:"
|
|
>
|
|
<UInput
|
|
v-model="itemInfo.address.street"
|
|
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.street : 'Straße + Hausnummer'"
|
|
:color="itemInfo.address.street ? 'primary' : 'rose'"
|
|
/>
|
|
<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'"
|
|
:color="itemInfo.address.zip ? 'primary' : 'rose'"
|
|
/>
|
|
<UInput
|
|
class="flex-auto"
|
|
v-model="itemInfo.address.city"
|
|
:placeholder="dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).infoData.city : 'Ort'"
|
|
:color="itemInfo.address.city ? 'primary' : 'rose'"
|
|
/>
|
|
</InputGroup>
|
|
</UFormGroup>
|
|
</div>
|
|
<div class="flex-auto">
|
|
<UFormGroup
|
|
:label="itemInfo.documentNumberTitle + ':'"
|
|
>
|
|
<UInput
|
|
v-model="itemInfo.documentNumber"
|
|
placeholder="XXXX"
|
|
disabled
|
|
/>
|
|
</UFormGroup>
|
|
|
|
<InputGroup class="w-full">
|
|
<UFormGroup
|
|
label="Datum:"
|
|
>
|
|
<UPopover :popper="{ placement: 'bottom-start' }">
|
|
<UButton
|
|
icon="i-heroicons-calendar-days-20-solid"
|
|
:label="itemInfo.documentDate ? dayjs(itemInfo.documentDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
|
variant="outline"
|
|
/>
|
|
|
|
<template #panel="{ close }">
|
|
<LazyDatePicker v-model="itemInfo.documentDate" @close="close" />
|
|
</template>
|
|
</UPopover>
|
|
</UFormGroup>
|
|
<UFormGroup
|
|
class="mt-3 w-80"
|
|
label="Lieferdatumsart:"
|
|
>
|
|
<USelectMenu
|
|
:options="['Lieferdatum'/*,'Lieferzeitraum'*/,'Leistungsdatum'/*,'Leistungszeitraum'*/,'Kein Lieferdatum anzeigen']"
|
|
v-model="itemInfo.deliveryDateType"
|
|
class="mb-2"
|
|
/>
|
|
|
|
|
|
</UFormGroup>
|
|
<UFormGroup
|
|
:label="itemInfo.deliveryDateType"
|
|
>
|
|
<UPopover :popper="{ placement: 'bottom-start' }">
|
|
<UButton
|
|
icon="i-heroicons-calendar-days-20-solid"
|
|
:label="itemInfo.deliveryDate ? dayjs(itemInfo.deliveryDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
|
variant="outline"
|
|
/>
|
|
|
|
<template #panel="{ close }">
|
|
<LazyDatePicker v-model="itemInfo.deliveryDate" @close="close" />
|
|
</template>
|
|
</UPopover>
|
|
</UFormGroup>
|
|
|
|
<UFormGroup
|
|
label="Zahlungziel in Tagen:"
|
|
class="flex-auto"
|
|
>
|
|
<UInput
|
|
type="number"
|
|
v-model="itemInfo.paymentDays"
|
|
/>
|
|
</UFormGroup>
|
|
</InputGroup>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<UFormGroup
|
|
label="Ansprechpartner:"
|
|
>
|
|
<USelectMenu
|
|
:options="dataStore.profiles"
|
|
v-model="itemInfo.contactPerson"
|
|
option-attribute="fullName"
|
|
value-attribute="id"
|
|
@change="setContactPersonData"
|
|
/>
|
|
</UFormGroup>
|
|
<UFormGroup
|
|
label="Kontakt Telefon:"
|
|
>
|
|
<UInput
|
|
v-model="itemInfo.contactTel"
|
|
/>
|
|
</UFormGroup>
|
|
<UFormGroup
|
|
label="Kontakt E-Mail:"
|
|
>
|
|
<UInput
|
|
v-model="itemInfo.contactEMail"
|
|
/>
|
|
</UFormGroup>
|
|
<UFormGroup
|
|
label="Projekt:"
|
|
>
|
|
<InputGroup>
|
|
<USelectMenu
|
|
:options="dataStore.projects"
|
|
v-model="itemInfo.project"
|
|
value-attribute="id"
|
|
option-attribute="name"
|
|
searchable
|
|
searchable-placeholder="Suche..."
|
|
:search-attributes="['name']"
|
|
class="w-full"
|
|
>
|
|
<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>
|
|
<UButton
|
|
variant="outline"
|
|
v-if="itemInfo.project"
|
|
icon="i-heroicons-arrow-right-end-on-rectangle"
|
|
@click="router.push(`/projects/show/${itemInfo.project}`)"
|
|
>Projekt</UButton>
|
|
</InputGroup>
|
|
|
|
</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="Vorlage auswählen"
|
|
>
|
|
<USelectMenu
|
|
:options="dataStore.getTextTemplatesByDocumentType(itemInfo.type)"
|
|
v-model="itemInfo.startText"
|
|
option-attribute="text"
|
|
value-attribute="text"
|
|
>
|
|
<template #option="{option}">
|
|
{{option.name}} - {{option.text}}
|
|
</template>
|
|
<template #label>
|
|
{{dataStore.texttemplates.find(i => i.text === itemInfo.startText) ? dataStore.texttemplates.find(i => i.text === itemInfo.startText).name : "Keine Vorlage ausgewählt oder Vorlage verändert"}}
|
|
</template>
|
|
</USelectMenu>
|
|
</UFormGroup>
|
|
|
|
|
|
<UFormGroup
|
|
label="Einleitung:"
|
|
>
|
|
<UTextarea
|
|
v-model="itemInfo.startText"
|
|
:rows="6"
|
|
/>
|
|
</UFormGroup>
|
|
|
|
|
|
<UDivider
|
|
class="my-3"
|
|
/>
|
|
|
|
<table class="w-full">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>Pos.</th>
|
|
<th>Name</th>
|
|
<th>Menge</th>
|
|
<th>Einheit</th>
|
|
<th>Preis</th>
|
|
<th>Steuer</th>
|
|
<th>Rabatt</th>
|
|
<th>Beschreibung</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="9"
|
|
>
|
|
<UDivider/>
|
|
</td>
|
|
<td
|
|
v-if="row.mode !== 'pagebreak'"
|
|
>{{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>
|
|
<span class="truncate">{{dataStore.getProductById(row.product) ?dataStore.getProductById(row.product).name : "Kein Produkt ausgewählt" }}</span>
|
|
</template>
|
|
</USelectMenu>
|
|
</td>
|
|
<td
|
|
class="w-120"
|
|
v-else-if="row.mode === 'service'"
|
|
>
|
|
<USelectMenu
|
|
:options="dataStore.services"
|
|
option-attribute="name"
|
|
value-attribute="id"
|
|
searchable
|
|
searchable-placeholder="Suche ..."
|
|
:search-attributes="['name']"
|
|
v-model="row.service"
|
|
@change="row.unit = dataStore.getServiceById(row.service).unit,
|
|
row.price = dataStore.getServiceById(row.service).sellingPrice || 0"
|
|
>
|
|
<template #label>
|
|
{{dataStore.getServiceById(row.service) ? dataStore.getServiceById(row.service).name : "Keine Leistung ausgewählt" }}
|
|
</template>
|
|
</USelectMenu>
|
|
</td>
|
|
<td
|
|
class="w-20"
|
|
v-if="row.mode !== 'pagebreak'"
|
|
>
|
|
<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 !== 'pagebreak'"
|
|
>
|
|
<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
|
|
class="w-40"
|
|
v-if="row.mode !== 'pagebreak'"
|
|
>
|
|
<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 !== 'pagebreak'"
|
|
|
|
>
|
|
<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 !== 'pagebreak'"
|
|
>
|
|
<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
|
|
class="w-40"
|
|
v-if="row.mode !== 'pagebreak'"
|
|
>
|
|
<UButton
|
|
icon="i-heroicons-document-text"
|
|
@click="row.showEdit = true"
|
|
/>
|
|
<UModal v-model="row.showEdit">
|
|
<UCard>
|
|
<template #header>
|
|
Beschreibung bearbeiten
|
|
</template>
|
|
<UTextarea
|
|
v-model="row.description"
|
|
|
|
>
|
|
|
|
</UTextarea>
|
|
<template #footer>
|
|
<UButton
|
|
@click="row.showEdit = false"
|
|
>
|
|
Speichern
|
|
</UButton>
|
|
</template>
|
|
</UCard>
|
|
|
|
|
|
</UModal>
|
|
</td>
|
|
<td
|
|
v-if="row.mode !== 'pagebreak'"
|
|
>
|
|
<p class="text-right font-bold whitespace-nowrap">{{getRowAmount(row)}} €</p>
|
|
</td>
|
|
<td>
|
|
<UButton
|
|
variant="ghost"
|
|
color="rose"
|
|
icon="i-heroicons-x-mark-16-solid"
|
|
@click="removePosition(row.id)"
|
|
/>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</draggable>
|
|
</table>
|
|
|
|
<InputGroup>
|
|
<UButton
|
|
@click="addPosition('service')"
|
|
class="mt-3"
|
|
>
|
|
+ Leistungsposition
|
|
</UButton>
|
|
<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>
|
|
|
|
<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>
|
|
|
|
|
|
|
|
<UDivider
|
|
class="my-3"
|
|
/>
|
|
|
|
<UFormGroup
|
|
label="Vorlage auswählen"
|
|
>
|
|
<USelectMenu
|
|
:options="dataStore.getTextTemplatesByDocumentType(itemInfo.type)"
|
|
v-model="itemInfo.endText"
|
|
option-attribute="text"
|
|
value-attribute="text"
|
|
>
|
|
<template #option="{option}">
|
|
{{option.name}} - {{option.text}}
|
|
</template>
|
|
<template #label>
|
|
{{dataStore.texttemplates.find(i => i.text === itemInfo.endText) ? dataStore.texttemplates.find(i => i.text === itemInfo.endText).name : "Keine Vorlage ausgewählt oder Vorlage verändert"}}
|
|
</template>
|
|
</USelectMenu>
|
|
</UFormGroup>
|
|
|
|
<UFormGroup
|
|
label="Nachbemerkung:"
|
|
>
|
|
<UTextarea
|
|
v-model="itemInfo.endText"
|
|
:rows="6"
|
|
/>
|
|
</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>
|
|
</UDashboardPanelContent>
|
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
<style scoped>
|
|
|
|
|
|
th {
|
|
text-align: left;
|
|
}
|
|
|
|
td {
|
|
padding: .4em;
|
|
}
|
|
|
|
/*tr:hover {
|
|
border: 1px solid #69c350;
|
|
}*/
|
|
|
|
.previewDocument {
|
|
height: 80vh;
|
|
}
|
|
</style> |