Changed Plants to Objects
Changes in outgoinginvoices
This commit is contained in:
11
demo.txt
Normal file
11
demo.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
E-Mail: demo@spaces.software
|
||||||
|
Passwort: Pxj7fZFwAf
|
||||||
|
|
||||||
|
Login PW: F8FPPs3wQ7
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ const navLinks = [
|
|||||||
icon: "i-heroicons-clipboard-document"
|
icon: "i-heroicons-clipboard-document"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Anlagen",
|
label: "Objekte",
|
||||||
to: "/plants",
|
to: "/plants",
|
||||||
icon: "i-heroicons-clipboard-document"
|
icon: "i-heroicons-clipboard-document"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ const openShowModal = ref(false)
|
|||||||
|
|
||||||
//Functions
|
//Functions
|
||||||
const openDocument = async () => {
|
const openDocument = async () => {
|
||||||
console.log("open")
|
|
||||||
//selectedDocument.value = doc
|
//selectedDocument.value = doc
|
||||||
openShowModal.value = true
|
openShowModal.value = true
|
||||||
}
|
}
|
||||||
|
|||||||
141
spaces/components/Editor.client.vue
Normal file
141
spaces/components/Editor.client.vue
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<script setup>
|
||||||
|
const editor = useEditor({
|
||||||
|
content: "<p>I'm running Tiptap with Vue.js. 🎉</p>",
|
||||||
|
extensions: [TiptapStarterKit],
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<InputGroup>
|
||||||
|
<UButtonGroup>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().undo().run()"
|
||||||
|
:disabled="!editor.can().chain().focus().undo().run()"
|
||||||
|
icon="i-mdi-undo"
|
||||||
|
class="px-3"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().redo().run()"
|
||||||
|
:disabled="!editor.can().chain().focus().redo().run()"
|
||||||
|
icon="i-mdi-redo"
|
||||||
|
class="px-3"
|
||||||
|
/>
|
||||||
|
</UButtonGroup>
|
||||||
|
|
||||||
|
<UButtonGroup v-if="editor">
|
||||||
|
|
||||||
|
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleBold().run()"
|
||||||
|
:disabled="!editor.can().chain().focus().toggleBold().run()"
|
||||||
|
:variant="editor.isActive('bold') ? 'solid' : 'outline'"
|
||||||
|
>
|
||||||
|
B
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleItalic().run()"
|
||||||
|
:disabled="!editor.can().chain().focus().toggleItalic().run()"
|
||||||
|
:variant="editor.isActive('italic') ? 'solid' : 'outline'"
|
||||||
|
>
|
||||||
|
<span class="italic">I</span>
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleStrike().run()"
|
||||||
|
:disabled="!editor.can().chain().focus().toggleStrike().run()"
|
||||||
|
:variant="editor.isActive('strike') ? 'solid' : 'outline'"
|
||||||
|
>
|
||||||
|
<span class="line-through">D</span>
|
||||||
|
</UButton>
|
||||||
|
</UButtonGroup>
|
||||||
|
<UButtonGroup>
|
||||||
|
<!-- <UButton
|
||||||
|
@click="editor.chain().focus().toggleCode().run()"
|
||||||
|
:disabled="!editor.can().chain().focus().toggleCode().run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('code') }"
|
||||||
|
>
|
||||||
|
code
|
||||||
|
</UButton>
|
||||||
|
<UButton @click="editor.chain().focus().unsetAllMarks().run()">
|
||||||
|
clear marks
|
||||||
|
</UButton>
|
||||||
|
<UButton @click="editor.chain().focus().clearNodes().run()">
|
||||||
|
clear nodes
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().setParagraph().run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('paragraph') }"
|
||||||
|
>
|
||||||
|
<span>P</span>
|
||||||
|
</UButton>-->
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"
|
||||||
|
icon="i-mdi-format-header-1"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
|
||||||
|
icon="i-mdi-format-header-2"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"
|
||||||
|
icon="i-mdi-format-header-3"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleHeading({ level: 4 }).run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"
|
||||||
|
icon="i-mdi-format-header-4"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleHeading({ level: 5 }).run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"
|
||||||
|
icon="i-mdi-format-header-5"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleHeading({ level: 6 }).run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"
|
||||||
|
icon="i-mdi-format-header-6"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleBulletList().run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('bulletList') }"
|
||||||
|
icon="i-mdi-format-list-bulleted"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleOrderedList().run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('orderedList') }"
|
||||||
|
icon="i-mdi-format-list-numbered"
|
||||||
|
/>
|
||||||
|
<!-- <UButton
|
||||||
|
@click="editor.chain().focus().toggleCodeBlock().run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('codeBlock') }"
|
||||||
|
>
|
||||||
|
code block
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
@click="editor.chain().focus().toggleBlockquote().run()"
|
||||||
|
:class="{ 'is-active': editor.isActive('blockquote') }"
|
||||||
|
>
|
||||||
|
blockquote
|
||||||
|
</UButton>
|
||||||
|
<UButton @click="editor.chain().focus().setHorizontalRule().run()">
|
||||||
|
horizontal rule
|
||||||
|
</UButton>
|
||||||
|
<UButton @click="editor.chain().focus().setHardBreak().run()">
|
||||||
|
hard break
|
||||||
|
</UButton>-->
|
||||||
|
|
||||||
|
</UButtonGroup>
|
||||||
|
</InputGroup>
|
||||||
|
|
||||||
|
<TiptapEditorContent class="mt-5" :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -46,7 +46,7 @@ const actions = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'new-plant',
|
id: 'new-plant',
|
||||||
label: 'Anlage hinzufügen',
|
label: 'Objekt hinzufügen',
|
||||||
icon: 'i-heroicons-clipboard-document',
|
icon: 'i-heroicons-clipboard-document',
|
||||||
to: "/plants/create" ,
|
to: "/plants/create" ,
|
||||||
},
|
},
|
||||||
@@ -85,7 +85,7 @@ const groups = computed(() =>
|
|||||||
commands: dataStore.tasks.map(item => { return {id: item.id, label: item.name, to: `/tasks/show/${item.id}`}})
|
commands: dataStore.tasks.map(item => { return {id: item.id, label: item.name, to: `/tasks/show/${item.id}`}})
|
||||||
},{
|
},{
|
||||||
key: "plants",
|
key: "plants",
|
||||||
label: "Anlagen",
|
label: "Objekte",
|
||||||
commands: dataStore.plants.map(item => { return {id: item.id, label: item.name, to: `/plants/show/${item.id}`}})
|
commands: dataStore.plants.map(item => { return {id: item.id, label: item.name, to: `/plants/show/${item.id}`}})
|
||||||
}
|
}
|
||||||
].filter(Boolean))
|
].filter(Boolean))
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const addHistoryItem = async () => {
|
|||||||
if(error) {
|
if(error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
} else {
|
} else {
|
||||||
|
addHistoryItemData.value = {}
|
||||||
toast.add({title: "Eintrag erfolgreich erstellt"})
|
toast.add({title: "Eintrag erfolgreich erstellt"})
|
||||||
showAddHistoryItemModal.value = false
|
showAddHistoryItemModal.value = false
|
||||||
await dataStore.fetchHistoryItems()
|
await dataStore.fetchHistoryItems()
|
||||||
|
|||||||
785
spaces/composables/usePDFGenerator.js
Normal file
785
spaces/composables/usePDFGenerator.js
Normal file
@@ -0,0 +1,785 @@
|
|||||||
|
import {PDFDocument, StandardFonts, rgb} from "pdf-lib"
|
||||||
|
|
||||||
|
let headerData = {
|
||||||
|
sender: "Federspiel Technology UG haftungsbeschränkt, Am Schwarzen Brack 14 26452 Sande",
|
||||||
|
recipient: {
|
||||||
|
name: "NOA Service GmbH",
|
||||||
|
contact: "Lena Kramer",
|
||||||
|
special: "Hinterm Haus",
|
||||||
|
address: "Oldenburger Str. 52",
|
||||||
|
city: "26340 Zetel"
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
invoiceNumber: "RE23-1409",
|
||||||
|
customerNumber: "10069",
|
||||||
|
invoiceDate: "15.09.2023",
|
||||||
|
dateOfPerformance: "31.08.2023",
|
||||||
|
contactPerson: "Florian Federspiel",
|
||||||
|
tel: "015755769509",
|
||||||
|
email: "f.federspiel@federspiel.tech"
|
||||||
|
},
|
||||||
|
title: "Rechnung-Nr. RE23-1409",
|
||||||
|
description: "BV: Stubbendränk 23, 26340 Zetel"
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCoordinatesForPDFLib = (x ,y, page) => {
|
||||||
|
/*
|
||||||
|
* @param x the wanted X Parameter in Millimeters from Top Left
|
||||||
|
* @param y the wanted Y Parameter in Millimeters from Top Left
|
||||||
|
* @param page the page Object
|
||||||
|
*
|
||||||
|
* @returns x,y object
|
||||||
|
* */
|
||||||
|
|
||||||
|
|
||||||
|
let retX = x * 2.83
|
||||||
|
let retY = page.getHeight()-(y*2.83)
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: retX,
|
||||||
|
y: retY
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const useCreatePdf = async (invoiceData) => {
|
||||||
|
|
||||||
|
const uri = ref("test")
|
||||||
|
const genPDF = async () => {
|
||||||
|
const pdfDoc = await PDFDocument.create()
|
||||||
|
|
||||||
|
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
||||||
|
const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold)
|
||||||
|
|
||||||
|
let pages = []
|
||||||
|
let pageCounter = 1
|
||||||
|
|
||||||
|
const backgroundPdfSourceBuffer = await fetch("/Briefpapier.pdf").then((res) => res.arrayBuffer())
|
||||||
|
const backgroudPdf = await PDFDocument.load(backgroundPdfSourceBuffer)
|
||||||
|
|
||||||
|
const firstPageBackground = await pdfDoc.embedPage(backgroudPdf.getPages()[0])
|
||||||
|
const secondPageBackground = await pdfDoc.embedPage(backgroudPdf.getPages()[1])
|
||||||
|
|
||||||
|
//console.log("TEST")
|
||||||
|
const page1 = pdfDoc.addPage()
|
||||||
|
|
||||||
|
//console.log(page1.getSize().width/2.83)
|
||||||
|
|
||||||
|
page1.drawPage(firstPageBackground, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
//console.log(page1.getSize())
|
||||||
|
pages.push(page1)
|
||||||
|
//console.log(pages)
|
||||||
|
|
||||||
|
|
||||||
|
//Falzmarke 1
|
||||||
|
pages[pageCounter - 1].drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(0,105,page1),
|
||||||
|
end: getCoordinatesForPDFLib(7,105,page1),
|
||||||
|
thickness: 0.25,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
//Lochmarke
|
||||||
|
pages[pageCounter - 1].drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(0,148.5,page1),
|
||||||
|
end: getCoordinatesForPDFLib(7,148.5,page1),
|
||||||
|
thickness: 0.25,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
//Falzmarke 2
|
||||||
|
pages[pageCounter - 1].drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(0,210,page1),
|
||||||
|
end: getCoordinatesForPDFLib(7,210,page1),
|
||||||
|
thickness: 0.25,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/*page1.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(20,45,page1),
|
||||||
|
end: getCoordinatesForPDFLib(105,45,page1),
|
||||||
|
thickness: 0.5,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})*/
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Federspiel Technology UG haftungsbeschränkt, Am Schwarzen Brack 14 26452 Sande", {
|
||||||
|
...getCoordinatesForPDFLib(21,48, page1),
|
||||||
|
size:6,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:6,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/*page1.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(20,50,page1),
|
||||||
|
end: getCoordinatesForPDFLib(105,50,page1),
|
||||||
|
thickness: 0.5,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.recipient.name, {
|
||||||
|
...getCoordinatesForPDFLib(21,55, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.recipient.contact, {
|
||||||
|
...getCoordinatesForPDFLib(21,60, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.recipient.special, {
|
||||||
|
...getCoordinatesForPDFLib(21,65, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.recipient.street, {
|
||||||
|
...getCoordinatesForPDFLib(21,70, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
pages[pageCounter - 1].drawText(`${invoiceData.recipient.zip} ${invoiceData.recipient.city}`, {
|
||||||
|
...getCoordinatesForPDFLib(21,75, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*page1.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(20,90,page1),
|
||||||
|
end: getCoordinatesForPDFLib(105,90,page1),
|
||||||
|
thickness: 0.5,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})*/
|
||||||
|
|
||||||
|
|
||||||
|
//Rechts
|
||||||
|
|
||||||
|
/*page1.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(125,50,page1),
|
||||||
|
end: getCoordinatesForPDFLib(200,50,page1),
|
||||||
|
thickness: 0.5,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})*/
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Rechnungsnummer", {
|
||||||
|
...getCoordinatesForPDFLib(126,55, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.info.documentNumber, {
|
||||||
|
y: getCoordinatesForPDFLib(126,55, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(126,55,page1).x + 210 - font.widthOfTextAtSize(headerData.info.invoiceNumber,10),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
pages[pageCounter - 1].drawText("Kundennummer", {
|
||||||
|
...getCoordinatesForPDFLib(126,60, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.info.customerNumber, {
|
||||||
|
y: getCoordinatesForPDFLib(126,60, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(126,60,page1).x + 210 - font.widthOfTextAtSize(headerData.info.customerNumber,10),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Belegdatum", {
|
||||||
|
...getCoordinatesForPDFLib(126,65, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.info.documentDate, {
|
||||||
|
y: getCoordinatesForPDFLib(126,65, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(126,65,page1).x + 210 - font.widthOfTextAtSize(headerData.info.invoiceDate,10),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
pages[pageCounter - 1].drawText("Lieferdatum", {
|
||||||
|
...getCoordinatesForPDFLib(126,70, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.info.deliveryDate, {
|
||||||
|
y: getCoordinatesForPDFLib(126,70, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(126,70,page1).x + 210 - font.widthOfTextAtSize(invoiceData.info.deliveryDate,10),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Ansprechpartner", {
|
||||||
|
...getCoordinatesForPDFLib(126,75, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.info.contactPerson, {
|
||||||
|
y: getCoordinatesForPDFLib(126,75, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(126,75,page1).x + 210 - font.widthOfTextAtSize(invoiceData.info.contactPerson,10),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Telefon", {
|
||||||
|
...getCoordinatesForPDFLib(126,80, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.info.contactTel, {
|
||||||
|
y: getCoordinatesForPDFLib(126,80, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(126,80,page1).x + 210 - font.widthOfTextAtSize(invoiceData.info.contactTel,10),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("E-Mail", {
|
||||||
|
...getCoordinatesForPDFLib(126,85, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.info.contactEMail, {
|
||||||
|
y: getCoordinatesForPDFLib(126,85, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(126,85,page1).x + 210 - font.widthOfTextAtSize(invoiceData.info.contactEMail,10),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
pages[pageCounter - 1].drawText("Projekt:", {
|
||||||
|
...getCoordinatesForPDFLib(126,90, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.info.project, {
|
||||||
|
y: getCoordinatesForPDFLib(126,90, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(126,90,page1).x + 340 - font.widthOfTextAtSize(invoiceData.info.project,10),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 100
|
||||||
|
})
|
||||||
|
|
||||||
|
/*page1.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(125,90,page1),
|
||||||
|
end: getCoordinatesForPDFLib(200,90,page1),
|
||||||
|
thickness: 0.5,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})*/
|
||||||
|
|
||||||
|
//Title
|
||||||
|
|
||||||
|
/*page1.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(20,95,page1),
|
||||||
|
end: getCoordinatesForPDFLib(200,95,page1),
|
||||||
|
thickness: 0.5,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})*/
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.title, {
|
||||||
|
...getCoordinatesForPDFLib(20,102, page1),
|
||||||
|
size:15,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:15,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
/*page1.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(20,105,page1),
|
||||||
|
end: getCoordinatesForPDFLib(200,105,page1),
|
||||||
|
thickness: 0.5,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})*/
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.description, {
|
||||||
|
...getCoordinatesForPDFLib(20,112, page1),
|
||||||
|
size:15,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:15,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.startText,{
|
||||||
|
...getCoordinatesForPDFLib(20,119, page1),
|
||||||
|
size: 10,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
lineHeight: 10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 500
|
||||||
|
})
|
||||||
|
|
||||||
|
/*page1.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(20,115,page1),
|
||||||
|
end: getCoordinatesForPDFLib(200,115,page1),
|
||||||
|
thickness: 0.5,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})*/
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawRectangle({
|
||||||
|
...getCoordinatesForPDFLib(20,140, page1),
|
||||||
|
width: 180 * 2.83,
|
||||||
|
height: 8 * 2.83,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 0.25
|
||||||
|
})
|
||||||
|
|
||||||
|
//Header
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Pos", {
|
||||||
|
...getCoordinatesForPDFLib(21,137, page1),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Menge", {
|
||||||
|
...getCoordinatesForPDFLib(35,137, page1),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Bezeichnung", {
|
||||||
|
...getCoordinatesForPDFLib(52,137, page1),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Einheitspreis", {
|
||||||
|
...getCoordinatesForPDFLib(135,137, page1),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Gesamt", {
|
||||||
|
y: getCoordinatesForPDFLib(25,137, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(25,137,page1).x + 490 - fontBold.widthOfTextAtSize("Gesamt",12),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
let rowHeight = 145
|
||||||
|
|
||||||
|
let pageIndex = 0
|
||||||
|
|
||||||
|
|
||||||
|
invoiceData.rows.forEach((row,index) => {
|
||||||
|
|
||||||
|
if(row.mode === 'free' || row.mode === 'normal'){
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(row.pos, {
|
||||||
|
...getCoordinatesForPDFLib(21,rowHeight, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(`${row.quantity} ${row.unit}`, {
|
||||||
|
...getCoordinatesForPDFLib(35,rowHeight, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(row.text, {
|
||||||
|
...getCoordinatesForPDFLib(52,rowHeight, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
if(row.description) {
|
||||||
|
pages[pageCounter - 1].drawText(row.description, {
|
||||||
|
...getCoordinatesForPDFLib(52,rowHeight + 7, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 220,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(row.price, {
|
||||||
|
...getCoordinatesForPDFLib(135,rowHeight, page1),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(row.rowAmount, {
|
||||||
|
y: getCoordinatesForPDFLib(25,rowHeight, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(25,rowHeight,page1).x + 490 - font.widthOfTextAtSize(row.rowAmount,10),
|
||||||
|
size:10,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
})
|
||||||
|
|
||||||
|
if(row.discountPercent > 0) {
|
||||||
|
pages[pageCounter - 1].drawText(row.discountText, {
|
||||||
|
y: getCoordinatesForPDFLib(25,rowHeight + 5, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(25,rowHeight + 5,page1).x + 490 - font.widthOfTextAtSize(row.discountText,8),
|
||||||
|
size:8,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rowHeight += 14
|
||||||
|
pageIndex += 1
|
||||||
|
|
||||||
|
|
||||||
|
} else if(row.mode === 'pagebreak') {
|
||||||
|
|
||||||
|
const page = pdfDoc.addPage()
|
||||||
|
|
||||||
|
page.drawPage(secondPageBackground, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
//Falzmarke 1
|
||||||
|
page.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(0,105,page1),
|
||||||
|
end: getCoordinatesForPDFLib(7,105,page1),
|
||||||
|
thickness: 0.25,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
//Lochmarke
|
||||||
|
page.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(0,148.5,page1),
|
||||||
|
end: getCoordinatesForPDFLib(7,148.5,page1),
|
||||||
|
thickness: 0.25,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
//Falzmarke 2
|
||||||
|
page.drawLine({
|
||||||
|
start: getCoordinatesForPDFLib(0,210,page1),
|
||||||
|
end: getCoordinatesForPDFLib(7,210,page1),
|
||||||
|
thickness: 0.25,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
page.drawRectangle({
|
||||||
|
...getCoordinatesForPDFLib(20,25, page1),
|
||||||
|
width: 180 * 2.83,
|
||||||
|
height: 8 * 2.83,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 0.25
|
||||||
|
})
|
||||||
|
|
||||||
|
//Header
|
||||||
|
|
||||||
|
page.drawText("Pos", {
|
||||||
|
...getCoordinatesForPDFLib(21,22, page1),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
page.drawText("Menge", {
|
||||||
|
...getCoordinatesForPDFLib(35,22, page1),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
page.drawText("Bezeichnung", {
|
||||||
|
...getCoordinatesForPDFLib(52,22, page1),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
page.drawText("Einheitspreis", {
|
||||||
|
...getCoordinatesForPDFLib(135,22, page1),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
page.drawText("Gesamt", {
|
||||||
|
y: getCoordinatesForPDFLib(25,22, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(25,22,page1).x + 490 - fontBold.widthOfTextAtSize("Gesamt",12),
|
||||||
|
size:12,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:12,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pageCounter += 1;
|
||||||
|
pageIndex = 0;
|
||||||
|
rowHeight = 30;
|
||||||
|
|
||||||
|
pages.push(page)
|
||||||
|
//console.log(pages)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
//Pos 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Footer
|
||||||
|
rowHeight += 25
|
||||||
|
pages[pageCounter - 1].drawRectangle({
|
||||||
|
...getCoordinatesForPDFLib(20,rowHeight, page1),
|
||||||
|
width: 180 * 2.83,
|
||||||
|
height: 8 * 2.83,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 0.25
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawRectangle({
|
||||||
|
...getCoordinatesForPDFLib(20,rowHeight +16, page1),
|
||||||
|
width: 180 * 2.83,
|
||||||
|
height: 8 * 2.83,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
opacity: 0.25
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Nettobetrag", {
|
||||||
|
...getCoordinatesForPDFLib(21,rowHeight-3, page1),
|
||||||
|
size:11,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:11,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.total.totalNet, {
|
||||||
|
y: getCoordinatesForPDFLib(21,rowHeight-3, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(21,rowHeight-3,page1).x + 500 - fontBold.widthOfTextAtSize(invoiceData.total.totalNet,11),
|
||||||
|
size:11,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:11,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font:fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("zzgl. 19% MwSt", {
|
||||||
|
...getCoordinatesForPDFLib(21,rowHeight+5, page1),
|
||||||
|
size:11,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:11,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.total.total19, {
|
||||||
|
y: getCoordinatesForPDFLib(21,rowHeight+5, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(21,rowHeight+5,page1).x + 500 - fontBold.widthOfTextAtSize(invoiceData.total.total19,11),
|
||||||
|
size:11,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:11,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font:fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText("Gesamtsumme", {
|
||||||
|
...getCoordinatesForPDFLib(21,rowHeight+13, page1),
|
||||||
|
size:11,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:11,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font: fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.total.totalGross, {
|
||||||
|
y: getCoordinatesForPDFLib(21,rowHeight+13, page1).y,
|
||||||
|
x: getCoordinatesForPDFLib(21,rowHeight+13,page1).x + 500 - fontBold.widthOfTextAtSize(invoiceData.total.totalGross,11),
|
||||||
|
size:11,
|
||||||
|
color:rgb(0,0,0),
|
||||||
|
lineHeight:11,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 240,
|
||||||
|
font:fontBold
|
||||||
|
})
|
||||||
|
|
||||||
|
pages[pageCounter - 1].drawText(invoiceData.endText,{
|
||||||
|
...getCoordinatesForPDFLib(20,rowHeight+22, page1),
|
||||||
|
size: 10,
|
||||||
|
color: rgb(0,0,0),
|
||||||
|
lineHeight: 10,
|
||||||
|
opacity: 1,
|
||||||
|
maxWidth: 500
|
||||||
|
})
|
||||||
|
|
||||||
|
//console.log(await pdfDoc.saveAsBase64({dataUri: true}))
|
||||||
|
|
||||||
|
uri.value = await pdfDoc.saveAsBase64({dataUri: true})
|
||||||
|
//console.log(uri.value)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
await genPDF()
|
||||||
|
|
||||||
|
//const pdfBytes = await pdfDoc.save()
|
||||||
|
|
||||||
|
//const pdfDataUri = await pdfDoc.saveAsBase64({dataUri: true})
|
||||||
|
return uri.value
|
||||||
|
//let blob = new Blob(pdfBytes, {type: "application/pdf"})
|
||||||
|
/*let link = document.createElement('a')
|
||||||
|
link.href = pdfDataUri//window.URL.createObjectURL(blob)
|
||||||
|
link.download = "test.pdf"
|
||||||
|
link.click()*/
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,7 +19,8 @@ export default defineNuxtConfig({
|
|||||||
'@nuxtjs/fontaine',
|
'@nuxtjs/fontaine',
|
||||||
'@nuxtjs/google-fonts',
|
'@nuxtjs/google-fonts',
|
||||||
'@vite-pwa/nuxt',
|
'@vite-pwa/nuxt',
|
||||||
'nuxt-viewport'
|
'nuxt-viewport',
|
||||||
|
'nuxt-tiptap-editor'
|
||||||
|
|
||||||
],
|
],
|
||||||
routeRules: {
|
routeRules: {
|
||||||
@@ -36,7 +37,7 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
icons: ['heroicons']
|
icons: ['heroicons','mdi']
|
||||||
},
|
},
|
||||||
colorMode: {
|
colorMode: {
|
||||||
preference: 'dark'
|
preference: 'dark'
|
||||||
@@ -60,6 +61,9 @@ export default defineNuxtConfig({
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
type: "module"
|
type: "module"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
tiptap: {
|
||||||
|
prefix: "Tiptap"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@nuxtjs/supabase": "^1.1.4",
|
"@nuxtjs/supabase": "^1.1.4",
|
||||||
"@vite-pwa/nuxt": "^0.3.3",
|
"@vite-pwa/nuxt": "^0.3.3",
|
||||||
"nuxt": "^3.8.0",
|
"nuxt": "^3.8.0",
|
||||||
|
"nuxt-tiptap-editor": "^0.0.13",
|
||||||
"vite-plugin-pwa": "^0.17.3",
|
"vite-plugin-pwa": "^0.17.3",
|
||||||
"vue": "^3.3.7",
|
"vue": "^3.3.7",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
"@fullcalendar/resource-timeline": "^6.1.10",
|
"@fullcalendar/resource-timeline": "^6.1.10",
|
||||||
"@fullcalendar/timegrid": "^6.1.10",
|
"@fullcalendar/timegrid": "^6.1.10",
|
||||||
"@fullcalendar/vue3": "^6.1.10",
|
"@fullcalendar/vue3": "^6.1.10",
|
||||||
|
"@iconify/json": "^2.2.171",
|
||||||
"@nuxt/content": "^2.9.0",
|
"@nuxt/content": "^2.9.0",
|
||||||
"@nuxt/ui-pro": "^0.7.0",
|
"@nuxt/ui-pro": "^0.7.0",
|
||||||
"@nuxtjs/fontaine": "^0.4.1",
|
"@nuxtjs/fontaine": "^0.4.1",
|
||||||
@@ -49,6 +51,7 @@
|
|||||||
"nuxt-editorjs": "^1.0.4",
|
"nuxt-editorjs": "^1.0.4",
|
||||||
"nuxt-viewport": "^2.0.6",
|
"nuxt-viewport": "^2.0.6",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"sass": "^1.69.7",
|
"sass": "^1.69.7",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ const itemInfo = ref({
|
|||||||
end: "",
|
end: "",
|
||||||
notes: null,
|
notes: null,
|
||||||
projectId: null,
|
projectId: null,
|
||||||
type: null
|
type: null,
|
||||||
|
state: "Entwurf"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -300,9 +301,10 @@ const setState = async (newState) => {
|
|||||||
:dark="useColorMode().value !== 'light'"
|
:dark="useColorMode().value !== 'light'"
|
||||||
:format="format"
|
:format="format"
|
||||||
:preview-format="format"
|
:preview-format="format"
|
||||||
:disabled="itemInfo.state !== 'Entwurf'"
|
:disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
|
||||||
/>
|
/>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
{{itemInfo.state === 'Entwurf'}}{{itemInfo.state}}
|
||||||
<UFormGroup
|
<UFormGroup
|
||||||
label="Ende:"
|
label="Ende:"
|
||||||
>
|
>
|
||||||
@@ -316,7 +318,7 @@ const setState = async (newState) => {
|
|||||||
:dark="useColorMode().value !== 'light'"
|
:dark="useColorMode().value !== 'light'"
|
||||||
:format="format"
|
:format="format"
|
||||||
:preview-format="format"
|
:preview-format="format"
|
||||||
:disabled="itemInfo.state !== 'Entwurf'"
|
:disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
|
||||||
/>
|
/>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
<!-- <UFormGroup
|
<!-- <UFormGroup
|
||||||
@@ -334,7 +336,7 @@ const setState = async (newState) => {
|
|||||||
v-model="itemInfo.user"
|
v-model="itemInfo.user"
|
||||||
option-attribute="fullName"
|
option-attribute="fullName"
|
||||||
value-attribute="id"
|
value-attribute="id"
|
||||||
:disabled="itemInfo.state !== 'Entwurf'"
|
:disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.profiles.find(profile => profile.id === itemInfo.user) ? dataStore.profiles.find(profile => profile.id === itemInfo.user).firstName : "Benutzer auswählen"}}
|
{{dataStore.profiles.find(profile => profile.id === itemInfo.user) ? dataStore.profiles.find(profile => profile.id === itemInfo.user).firstName : "Benutzer auswählen"}}
|
||||||
@@ -352,7 +354,7 @@ const setState = async (newState) => {
|
|||||||
searchable
|
searchable
|
||||||
searchable-placeholder="Suche..."
|
searchable-placeholder="Suche..."
|
||||||
:search-attributes="['name']"
|
:search-attributes="['name']"
|
||||||
:disabled="itemInfo.state !== 'Entwurf'"
|
:disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.projects.find(project => project.id === itemInfo.projectId) ? dataStore.projects.find(project => project.id === itemInfo.projectId).name : "Projekt auswählen"}}
|
{{dataStore.projects.find(project => project.id === itemInfo.projectId) ? dataStore.projects.find(project => project.id === itemInfo.projectId).name : "Projekt auswählen"}}
|
||||||
@@ -367,7 +369,7 @@ const setState = async (newState) => {
|
|||||||
:options="timeTypes"
|
:options="timeTypes"
|
||||||
option-attribute="label"
|
option-attribute="label"
|
||||||
value-attribute="label"
|
value-attribute="label"
|
||||||
:disabled="itemInfo.state !== 'Entwurf'"
|
:disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{itemInfo.type ? itemInfo.type : "Kategorie auswählen"}}
|
{{itemInfo.type ? itemInfo.type : "Kategorie auswählen"}}
|
||||||
@@ -379,7 +381,7 @@ const setState = async (newState) => {
|
|||||||
>
|
>
|
||||||
<UTextarea
|
<UTextarea
|
||||||
v-model="itemInfo.notes"
|
v-model="itemInfo.notes"
|
||||||
:disabled="itemInfo.state !== 'Entwurf'"
|
:disabled="configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf'"
|
||||||
/>
|
/>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|
||||||
|
|||||||
835
spaces/pages/outgoingInvoices.vue
Normal file
835
spaces/pages/outgoingInvoices.vue
Normal file
@@ -0,0 +1,835 @@
|
|||||||
|
<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>
|
||||||
|
<!– <UInput
|
||||||
|
v-model="row.taxPercent"
|
||||||
|
>
|
||||||
|
<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 === '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>
|
||||||
@@ -12,6 +12,11 @@ const router = useRouter()
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const id = ref(route.params.id ? route.params.id : null )
|
const id = ref(route.params.id ? route.params.id : null )
|
||||||
|
|
||||||
|
const editor = useEditor({
|
||||||
|
content: "<p>I'm running Tiptap with Vue.js. 🎉</p>",
|
||||||
|
extensions: [TiptapStarterKit],
|
||||||
|
});
|
||||||
|
|
||||||
let currentItem = null
|
let currentItem = null
|
||||||
|
|
||||||
//Working
|
//Working
|
||||||
@@ -25,6 +30,8 @@ const tabItems = [
|
|||||||
label: "Projekte"
|
label: "Projekte"
|
||||||
},{
|
},{
|
||||||
label: "Aufgaben"
|
label: "Aufgaben"
|
||||||
|
},{
|
||||||
|
label: "Dokumentation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -93,6 +100,11 @@ setupPage()
|
|||||||
|
|
||||||
</UTable>
|
</UTable>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="item.label === 'Dokumentation'">
|
||||||
|
|
||||||
|
<Editor/>
|
||||||
|
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UTabs>
|
</UTabs>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<UButton @click="router.push(`/plants/create/`)">+ Anlage</UButton>
|
<UButton @click="router.push(`/plants/create/`)">+ Objekt</UButton>
|
||||||
|
|
||||||
<UInput
|
<UInput
|
||||||
v-model="searchString"
|
v-model="searchString"
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ setupPage()
|
|||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|
||||||
<UFormGroup
|
<UFormGroup
|
||||||
label="Anlage:"
|
label="Objekt:"
|
||||||
>
|
>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
v-model="itemInfo.plant"
|
v-model="itemInfo.plant"
|
||||||
@@ -490,7 +490,7 @@ setupPage()
|
|||||||
:search-attributes="['name']"
|
:search-attributes="['name']"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Anlage auswählen"}}
|
{{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Objekt auswählen"}}
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const itemColumns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "plant",
|
key: "plant",
|
||||||
label: "Anlage",
|
label: "Objekt",
|
||||||
sortable: true
|
sortable: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const itemColumns = [
|
|||||||
|
|
||||||
const selectItem = (item) => {
|
const selectItem = (item) => {
|
||||||
console.log(item)
|
console.log(item)
|
||||||
router.push(`/inventory/spaces/show/${item.id} `)
|
router.push(`/spaces/show/${item.id} `)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ setupPage()
|
|||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
<UFormGroup
|
<UFormGroup
|
||||||
label="Anlage:"
|
label="Objekt:"
|
||||||
>
|
>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
v-model="itemInfo.plant"
|
v-model="itemInfo.plant"
|
||||||
@@ -174,7 +174,7 @@ setupPage()
|
|||||||
:search-attributes="['name']"
|
:search-attributes="['name']"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
{{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Keine Anlage ausgewählt"}}
|
{{dataStore.getPlantById(itemInfo.plant) ? dataStore.getPlantById(itemInfo.plant).name : "Kein Objekt ausgewählt"}}
|
||||||
</template>
|
</template>
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const columns = [
|
|||||||
sortable: true
|
sortable: true
|
||||||
},{
|
},{
|
||||||
key: "plant",
|
key: "plant",
|
||||||
label: "Anlage:",
|
label: "Objekt:",
|
||||||
sortable: true
|
sortable: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
5
spaces/plugins/vue-draggable.client.js
Normal file
5
spaces/plugins/vue-draggable.client.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
nuxtApp.vueApp.component('draggable', draggable);
|
||||||
|
})
|
||||||
BIN
spaces/public/Briefpapier.pdf
Normal file
BIN
spaces/public/Briefpapier.pdf
Normal file
Binary file not shown.
@@ -39,8 +39,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
redirect:true
|
redirect:true
|
||||||
},
|
},
|
||||||
plants: {
|
plants: {
|
||||||
label: "Anlagen",
|
label: "Objekte",
|
||||||
labelSingle: "Anlage",
|
labelSingle: "Objekte",
|
||||||
redirect:true
|
redirect:true
|
||||||
},
|
},
|
||||||
products: {
|
products: {
|
||||||
|
|||||||
Reference in New Issue
Block a user