Files
FEDEO/pages/createDocument/edit/[[id]].vue
2025-02-04 12:45:50 +01:00

2242 lines
80 KiB
Vue

<script setup>
import dayjs from "dayjs"
import Handlebars from "handlebars"
import { v4 as uuidv4 } from 'uuid';
import {useFunctions} from "~/composables/useFunctions.js";
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
const supabase = useSupabaseClient()
definePageMeta({
middleware: "auth"
})
const showProductSelectionModal = ref(false)
const showServiceSelectionModal = ref(false)
const itemInfo = ref({
type: "invoices",
taxType: "Standard",
customer: null,
contact: null,
address: {
street: null,
special: null,
zip: null,
city: null,
},
project: null,
documentNumber: null,
documentNumberTitle: "Rechnungsnummer",
documentDate: dayjs(),
deliveryDate: dayjs(),
deliveryDateEnd: null,
deliveryDateType: "Lieferdatum",
dateOfPerformance: null,
paymentDays: profileStore.ownTenant.standardPaymentDays,
createdBy: profileStore.activeProfile.id,
title: null,
description: null,
startText: null,
endText: null,
rows: [
],
contactPerson: profileStore.activeProfile.id,
contactPersonName: null,
contactTel: null,
contactEMail: null,
serialConfig: {
firstExecution: "",
intervall: "monatlich",
executionUntil: "",
active: true,
dateDirection: "Rückwirkend",
},
letterhead: null,
agriculture: {},
usedAdvanceInvoices: []
})
console.log(profileStore.ownTenant)
const letterheads = ref([])
const createddocuments = ref([])
const projects = ref([])
const plants = ref([])
const products = ref([])
const productcategories = ref([])
const selectedProductcategorie = ref(null)
const services = ref([])
const servicecategories = ref([])
const selectedServicecategorie = ref(null)
const customers = ref([])
const contacts = ref([])
const texttemplates = ref([])
const loaded = ref(false)
const setupPage = async () => {
letterheads.value = (await useSupabaseSelect("letterheads","*")).filter(i => i.documentTypes.length === 0 || i.documentTypes.includes(itemInfo.value.type))
createddocuments.value = (await useSupabaseSelect("createddocuments","*"))
projects.value = (await useSupabaseSelect("projects","*"))
plants.value = (await useSupabaseSelect("plants","*"))
services.value = (await useSupabaseSelect("services","*"))
servicecategories.value = (await useSupabaseSelect("servicecategories","*"))
products.value = (await useSupabaseSelect("products","*"))
productcategories.value = (await useSupabaseSelect("productcategories","*"))
customers.value = (await useSupabaseSelect("customers","*","customerNumber"))
contacts.value = (await useSupabaseSelect("contacts","*"))
texttemplates.value = (await useSupabaseSelect("texttemplates","*"))
if(productcategories.value.length > 0) selectedProductcategorie.value = productcategories.value[0].id
if(servicecategories.value.length > 0) selectedServicecategorie.value = servicecategories.value[0].id
if(route.params) {
if(route.params.id) itemInfo.value = await useSupabaseSelectSingle("createddocuments", route.params.id)
if(!itemInfo.value.deliveryDateType) itemInfo.value.deliveryDateType = "Lieferdatum"
if(itemInfo.value.rows.find(i => i.agriculture)) {
processDieselPosition()
}
}
if(route.query) {
if(route.query.type) itemInfo.value.type = route.query.type
if(!itemInfo.value.startText && !itemInfo.value.endText) {
setDocumentTypeConfig(true)
} else {
setDocumentTypeConfig(false)
}
setContactPersonData()
if(route.query.linkedDocuments) {
let linkedDocuments = (await supabase.from("createddocuments").select().in("id",JSON.parse(route.query.linkedDocuments))).data
//TODO: Implement Checking for Same Customer, Contact and Project
itemInfo.value.customer = linkedDocuments[0].customer
itemInfo.value.project = linkedDocuments[0].project
itemInfo.value.contact = linkedDocuments[0].contact
setCustomerData()
let firstDate = null
let lastDate = null
linkedDocuments.forEach(doc => {
let lastId = 0
itemInfo.value.rows.forEach(row => {
if(row.id > lastId) lastId = row.id
})
if(dayjs(doc.documentDate).isBefore(firstDate) || !firstDate) firstDate = doc.documentDate
if(dayjs(doc.documentDate).isAfter(lastDate) || !lastDate) lastDate = doc.documentDate
itemInfo.value.rows.push(...[
{
id:uuidv4(),
mode: "title",
text: `${doc.title} vom ${dayjs(doc.documentDate).format("DD.MM.YYYY")}`
},
...doc.rows
])
})
itemInfo.value.deliveryDateType = "Leistungszeitraum"
itemInfo.value.deliveryDate = firstDate
itemInfo.value.deliveryDateEnd = lastDate
itemInfo.value.rows.forEach(row => {
row.discountPercent = 0
setRowData(row)
})
setPosNumbers()
console.log(linkedDocuments)
if(linkedDocuments.find(i => i.rows.find( x => x.agriculture.dieselUsage))){
console.log("has diesel")
//Remove Existing Total Diesel Pos
itemInfo.value.rows = itemInfo.value.rows.filter(i => i.key !== "dieselPos")
//Add Total Title
itemInfo.value.rows.push({
id:uuidv4(),
mode: "title",
text: "Allgemein"
})
processDieselPosition()
}
}
if(route.query.linkedDocument) {
itemInfo.value.linkedDocument = route.query.linkedDocument
let linkedDocument = await useSupabaseSelectSingle("createddocuments",itemInfo.value.linkedDocument)
itemInfo.value.rows = linkedDocument.rows
itemInfo.value.customer = linkedDocument.customer
itemInfo.value.project = linkedDocument.project
itemInfo.value.contact = linkedDocument.contact
itemInfo.value.description = linkedDocument.description
itemInfo.value.deliveryDate = linkedDocument.deliveryDate
itemInfo.value.deliveryDateType = linkedDocument.deliveryDateType
setCustomerData()
if(route.query.loadMode === "storno") {
itemInfo.value.rows.forEach(row => {
row.price = row.price * -1
})
itemInfo.value.description = `Stornorechnung zu Rechnung ${linkedDocument.documentNumber} vom ${dayjs(linkedDocument.documentDate).format('DD.MM.YYYY')}`
itemInfo.value.type = "cancellationInvoices"
setDocumentTypeConfig(true)
}
}
if(route.query.project) {
itemInfo.value.project = Number(route.query.project)
let project = await useSupabaseSelectSingle("projects",itemInfo.value.project)
if(!itemInfo.value.description){
itemInfo.value.description = project.customerRef
}
}
if(route.query.plant) {
itemInfo.value.plant = Number(route.query.plant)
}
if(route.query.contact) itemInfo.value.contact = Number(route.query.contact)
if(route.query.customer) {
itemInfo.value.customer = Number(route.query.customer)
setCustomerData()
}
}
//if(itemInfo.value.project) checkForOpenAdvanceInvoices()
loaded.value = true
}
setupPage()
const openAdvanceInvoices = ref([])
const checkForOpenAdvanceInvoices = async () => {
const {data,error} = await supabase.from("createddocuments").select().eq("project", itemInfo.value.project).eq("advanceInvoiceResolved", false).eq("type","advanceInvoices")
console.log(data)
openAdvanceInvoices.value = data
}
const addAdvanceInvoiceToInvoice = (advanceInvoice) => {
itemInfo.value.usedAdvanceInvoices.push(advanceInvoice)
}
const setDocumentTypeConfig = (withTexts = false) => {
if(itemInfo.value.type === "invoices" ||itemInfo.value.type === "advanceInvoices" || itemInfo.value.type === "serialInvoices"|| itemInfo.value.type === "cancellationInvoices") {
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"}`
} else if(itemInfo.value.type === "confirmationOrders") {
itemInfo.value.documentNumberTitle = "Auftragsbestätigungsnr."
itemInfo.value.title = `Auftragsbestätigung-Nr. ${itemInfo.value.documentNumber ? itemInfo.value.documentNumber : "XXXX"}`
}
if(withTexts) {
itemInfo.value.startText = getTextTemplateByType(itemInfo.value.type).find(i => i.default && i.pos === "startText").text
itemInfo.value.endText = getTextTemplateByType(itemInfo.value.type).find(i => i.default && i.pos === "endText").text
//itemInfo.value.startText = texttemplates.value.find(i => i.documentType === itemInfo.value.type && i.default && i.pos === "startText").text
//itemInfo.value.endText = texttemplates.value.find(i => i.documentType === itemInfo.value.type && i.default && i.pos === "endText").text
}
itemInfo.value.letterhead = letterheads.value[0].id
}
const setCustomerData = () => {
let customer = customers.value.find(i => i.id === itemInfo.value.customer)
console.log(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
if(customer.customPaymentDays) itemInfo.value.paymentDays = customer.customPaymentDays
}
}
const setContactPersonData = async () => {
//console.log(itemInfo.value.contactPerson)
let profile = await useSupabaseSelectSingle("profiles",itemInfo.value.contactPerson, '*')
itemInfo.value.contactPersonName = profile.fullName
itemInfo.value.contactTel = profile.mobileTel || profile.fixedTel || ""
itemInfo.value.contactEMail = profile.email
}
const showAdvanceInvoiceCalcModal = ref(false)
const advanceInvoiceData = ref({
totalSumNet: 0,
partPerPecentage: 0,
part: 0
})
const importPositions = () => {
if(itemInfo.value.type === 'advanceInvoices') {
if(advanceInvoiceData.value.totalSumNet !== 0 && advanceInvoiceData.value.partPerPecentage !== 0 && advanceInvoiceData.value.part !== 0) {
showAdvanceInvoiceCalcModal.value = false
let lastId = 0
itemInfo.value.rows.forEach(row => {
if(row.id > lastId) lastId = row.id
})
itemInfo.value.rows.push({
id: lastId +1,
mode: "free",
text: "Abschlagszahlung",
quantity: 1,
unit: 10,
price: advanceInvoiceData.value.part,
taxPercent: 19,
discountPercent: 0,
advanceInvoiceData: advanceInvoiceData.value
})
setPosNumbers()
}
}
}
const getRowAmount = (row) => {
return String(Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(2)).replace('.',',')
}
const getRowAmountUndiscounted = (row) => {
return String(Number(Number(row.quantity) * Number(row.price)).toFixed(2)).replace('.',',')
}
const addPosition = (mode) => {
let lastId = 0
itemInfo.value.rows.forEach(row => {
if(row.id > lastId) lastId = row.id
})
if(mode === 'free'){
let rowData = {
id: uuidv4(),
mode: "free",
text: "",
quantity: 1,
unit: 1,
price: 0,
taxPercent: 19,
discountPercent: 0
}
itemInfo.value.rows.push({...rowData, ...profileStore.ownTenant.extraModules.includes("agriculture") ? {agriculture: {}}: {}})
} else if(mode === 'normal'){
itemInfo.value.rows.push({
id: uuidv4(),
mode: "normal",
quantity: 1,
price: 0,
taxPercent: 19,
discountPercent: 0,
unit: 1
})
} else if(mode === 'service'){
let rowData = {
id: uuidv4(),
mode: "service",
quantity: 1,
price: 0,
taxPercent: 19,
discountPercent: 0,
unit: 1
}
//Push Agriculture Holder only if Module is activated
itemInfo.value.rows.push({...rowData, ...profileStore.ownTenant.extraModules.includes("agriculture") ? {agriculture: {}}: {}})
} else if(mode === "pagebreak") {
itemInfo.value.rows.push({
id: uuidv4(),
mode: "pagebreak",
})
} else if(mode === "title") {
itemInfo.value.rows.push({
id: uuidv4(),
mode: "title",
})
} else if(mode === "text") {
itemInfo.value.rows.push({
id: uuidv4(),
mode: "text",
})
}
setPosNumbers()
}
const removePosition = (id) => {
itemInfo.value.rows = itemInfo.value.rows.filter(row => row.id !== id)
setPosNumbers()
}
const getRowMargin = (row) => {
if(row.mode === "normal" && row.product) {
let purchasePrice = products.value.find(i => i.id === row.product).purchasePrice || 0
return row.price - purchasePrice
} else {
return 0
}
}
const findDocumentErrors = computed(() => {
let errors = []
if(itemInfo.value.customer === null) errors.push({message: "Es ist kein Kunde ausgewählt", type: "breaking"})
if(itemInfo.value.contact === null) errors.push({message: "Es ist kein Kontakt ausgewählt", type: "info"})
if(itemInfo.value.letterhead === null) errors.push({message: "Es ist kein Briefpapier ausgewählt", type: "breaking"})
if(itemInfo.value.address.street === null) errors.push({message: "Es ist keine Straße im Adressat angegeben", type: "breaking"})
if(itemInfo.value.address.zip === null) errors.push({message: "Es ist keine Postleitzahl im Adressat angegeben", type: "breaking"})
if(itemInfo.value.address.city === null) errors.push({message: "Es ist keine Stadt im Adressat angegeben", type: "breaking"})
if(itemInfo.value.project === null) errors.push({message: "Es ist kein Projekt ausgewählt", type: "info"})
if(['Lieferzeitraum','Leistungszeitraum'].includes(itemInfo.value.deliveryDateType)) {
if(itemInfo.value.deliveryDateEnd === null) errors.push({message: `Es ist kein Enddatum für den ${itemInfo.value.deliveryDateType} angegeben`, type: "breaking"})
}
if(itemInfo.value.rows.length === 0) {
errors.push({message: "Es sind keine Positionen angegeben", type: "breaking"})
} else {
itemInfo.value.rows.forEach(row => {
if(row.mode === "normal" && !row.product) errors.push({message: `In Position ${row.pos} ist kein Artikel ausgewählt`, type: "breaking"})
if(row.mode === "service" && !row.service) errors.push({message: `In Position ${row.pos} ist keine Leistung ausgewählt`, type: "breaking"})
if(row.mode === "title" && !row.text) errors.push({message: `In Position ${row.pos} ist kein Titel hinterlegt`, type: "breaking"})
if(row.mode === "text" && !row.text) errors.push({message: `In einer Freitext Position ist kein Titel hinterlegt`, type: "breaking"})
if(row.mode === "free" && !row.text) errors.push({message: `In einer freien Position ist kein Titel hinterlegt`, type: "breaking"})
if(["normal","service","free"].includes(row.mode)){
if(!row.taxPercent && typeof row.taxPercent !== "number") errors.push({message: `In Position ${row.pos} ist kein Steuersatz hinterlegt`, type: "breaking"})
if(!row.price && typeof row.price !== "number") errors.push({message: `In Position ${row.pos} ist kein Preis hinterlegt`, type: "breaking"})
}
if(row.agriculture){
if(row.agriculture.dieselUsage && (!row.agriculture.dieselPrice && typeof row.agriculture.dieselPrice !== "number")) {
errors.push({message: `In Position ${row.pos} ist kein Dieselpreis hinterlegt`, type: "breaking"})
} else if(row.agriculture.dieselUsage && row.agriculture.dieselPrice === 0) {
errors.push({message: `In Position ${row.pos} ist 0,00 € als Dieselpreis hinterlegt`, type: "info"})
}
}
})
}
if(itemInfo.value.type === "serialInvoices") {
if(!itemInfo.value.serialConfig.intervall) errors.push({message: `Kein Intervall für die Ausführung festgelegt`, type: "breaking"})
if(!itemInfo.value.serialConfig.dateDirection) errors.push({message: `Kein Richtung für die Datierung festgelegt`, type: "breaking"})
if(!itemInfo.value.serialConfig.firstExecution) errors.push({message: `Kein Datum für die erste Ausführung festgelegt`, type: "breaking"})
if(!itemInfo.value.serialConfig.executionUntil) errors.push({message: `Kein Datum für die letzte Ausführung festgelegt`, type: "info"})
}
return errors.sort((a,b) => (a.type === "breaking") ? -1 : 1)
})
const tabItems = computed(() => {
return [
{
label: "Editor"
},
{
label: "Vorschau",
disabled: findDocumentErrors.value.filter(i => i.type === 'breaking').length > 0
}
]
})
const renderCurrency = (value, currency = "€") => {
return Number(value).toFixed(2).replace(".",",") + " €"
}
const documentTotal = computed(() => {
let totalNet = 0
let total19 = 0
let total7 = 0
itemInfo.value.rows.forEach(row => {
if(!['pagebreak','title','text'].includes(row.mode)){
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(3)
totalNet = totalNet + Number(rowPrice)
if(row.taxPercent === 19) {
total19 = total19 + Number(rowPrice * 0.19)
} else if(row.taxPercent === 7) {
total7 = total7 + Number(rowPrice * 0.07)
}
}
})
//Title Sum
let titleSums = {}
let lastTitle = ""
itemInfo.value.rows.forEach(row => {
if(row.mode === 'title'){
titleSums[`${row.pos} - ${row.text}`] = 0
lastTitle = `${row.pos} - ${row.text}`
} else if(!['pagebreak','text'].includes(row.mode)){
titleSums[lastTitle] = Number(titleSums[lastTitle]) + Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) )
}
})
let totalGross = Number(totalNet.toFixed(2)) + Number(total19.toFixed(2)) + Number(total7.toFixed(2))
let totalGrossAlreadyPaid = 0
itemInfo.value.usedAdvanceInvoices.forEach(advanceInvoiceId => {
let advanceInvoice = createddocuments.value.find(i => i.id === advanceInvoiceId)
let priceNet = advanceInvoice.rows.find(i => i.advanceInvoiceData).price
let partSum = priceNet * ((100 + advanceInvoice.rows.find(i => i.advanceInvoiceData).taxPercent) / 100)
totalGrossAlreadyPaid += partSum
})
console.log(totalGrossAlreadyPaid)
let sumToPay = totalGross - totalGrossAlreadyPaid
return {
titleSums: titleSums,
totalNet: totalNet,
total19: total19,
total7: total7,
totalGross: totalGross,
totalGrossAlreadyPaid: totalGrossAlreadyPaid,
totalSumToPay: sumToPay
}
})
const processDieselPosition = () => {
let agricultureData = {
dieselUsageTotal: 0,
dieselPriceTotal: 0
}
itemInfo.value.rows.forEach(row => {
if(row.agriculture && row.agriculture.dieselUsage) {
agricultureData.dieselUsageTotal += Number(row.agriculture.dieselUsage)
agricultureData.dieselPriceTotal += Number(row.agriculture.dieselPrice) * Number(row.agriculture.dieselUsage)
}
})
if(agricultureData.dieselUsageTotal !== 0) {
if(itemInfo.value.rows.find(i => i.key === "dieselPos")){
let existingIndex = itemInfo.value.rows.findIndex(i => i.key === "dieselPos")
itemInfo.value.rows[existingIndex] = {
...itemInfo.value.rows[existingIndex],
price: agricultureData.dieselPriceTotal,
text: `${agricultureData.dieselUsageTotal} L Diesel`,
}
} else {
itemInfo.value.rows.push({
mode: "free",
text: `${agricultureData.dieselUsageTotal} L Diesel`,
quantity: 1,
unit: 10,
price: agricultureData.dieselPriceTotal,
taxPercent: 19,
discountPercent: 0,
key: "dieselPos"
})
setPosNumbers()
}
}
itemInfo.value.agriculture = {...itemInfo.value.agriculture, ...agricultureData}
}
const getDocumentData = () => {
let customerData = customers.value.find(i => i.id === itemInfo.value.customer)
let contactData = dataStore.getContactById(itemInfo.value.contact)
let businessInfo = profileStore.ownTenant.businessInfo
if(profileStore.ownTenant.extraModules.includes("agriculture")) {
itemInfo.value.rows.forEach(row => {
if(row.agriculture && row.agriculture.dieselUsage) {
row.agriculture.description = `${row.agriculture.dieselUsage} L Diesel zu ${renderCurrency(row.agriculture.dieselPrice)}/L verbraucht ${row.description ? "\n" + row.description : ""}`
}
})
}
let rows = itemInfo.value.rows
if(itemInfo.value.taxType === "13b UStG") {
rows = rows.map(row => {
return {
...row,
taxPercent: 0
}
})
}
rows = itemInfo.value.rows.map(row => {
let unit = dataStore.units.find(i => i.id === row.unit)
if(!['pagebreak','title'].includes(row.mode)){
if(row.agriculture && row.agriculture.description) {
console.log("Row has Agri")
row.descriptionText = row.agriculture.description
} else if(row.description) {
console.log("Row has no Agri")
row.descriptionText = row.description
} else {
delete row.descriptionText
}
}
if(!['pagebreak','title','text'].includes(row.mode)) {
if(row.mode === 'normal') row.text = products.value.find(i => i.id === row.product).name
if(row.mode === 'service') row.text = services.value.find(i => i.id === 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);
const generateContext = (itemInfo, contactData) => {
return {
vorname:(contactData && contactData.firstName) || (customerData && customerData.firstname),
nachname: (contactData && contactData.lastName) || (customerData && customerData.lastname),
kundenname: customerData && customerData.name,
zahlungsziel_in_tagen:itemInfo.paymentDays,
diesel_gesamtverbrauch: (itemInfo.agriculture && itemInfo.agriculture.dieselUsageTotal) && itemInfo.agriculture.dieselUsageTotal
}
}
let contactPerson = profileStore.getProfileById(itemInfo.value.contactPerson)
let returnTitleSums = {}
Object.keys(documentTotal.value.titleSums).forEach(key => {
returnTitleSums[key] = renderCurrency(documentTotal.value.titleSums[key])
})
console.log(returnTitleSums)
const returnData = {
type: itemInfo.value.type,
taxType: itemInfo.value.taxType,
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).format("DD.MM.YYYY"),
deliveryDate: dayjs(itemInfo.value.deliveryDate).format("DD.MM.YYYY"),
deliveryDateEnd: itemInfo.value.deliveryDateEnd ? dayjs(itemInfo.value.deliveryDateEnd).format("DD.MM.YYYY") : null,
deliveryDateType: itemInfo.value.deliveryDateType,
contactPerson: contactPerson.fullName,
contactTel: contactPerson.fixedTel || contactPerson.mobileTel,
contactEMail: contactPerson.email,
project: projects.value.find(i => i.id === itemInfo.value.project) ? projects.value.find(i => i.id === itemInfo.value.project).name : null,
plant: plants.value.find(i => i.id === itemInfo.value.plant) ? plants.value.find(i => i.id === itemInfo.value.plants).name : null,
},*/
info: [
{
label: itemInfo.value.documentNumberTitle,
content: itemInfo.value.documentNumber || "XXXX",
},{
label: "Kundennummer",
content: customerData.customerNumber,
},{
label: "Belegdatum",
content: dayjs(itemInfo.value.documentDate).format("DD.MM.YYYY"),
},{
label: itemInfo.value.deliveryDateType,
content: !['Lieferzeitraum','Leistungszeitraum'].includes(itemInfo.value.deliveryDateType) ? dayjs(itemInfo.value.deliveryDate).format("DD.MM.YYYY") : `${dayjs(itemInfo.value.deliveryDate).format("DD.MM.YYYY")} - ${dayjs(itemInfo.value.deliveryDateEnd).format("DD.MM.YYYY")}`,
},{
label: "Ansprechpartner",
content: contactPerson.fullName,
},
... contactPerson.fixedTel || contactPerson.mobileTel ? [{
label: "Telefon",
content: contactPerson.fixedTel || contactPerson.mobileTel,
}] : [],
... contactPerson.email ? [{
label: "E-Mail",
content: contactPerson.email,
}]: [],
... itemInfo.value.plant ? [{
label: "Objekt",
content: plants.value.find(i => i.id === itemInfo.value.plant).name,
}] : [],
... itemInfo.value.project ? [{
label: "Projekt",
content: projects.value.find(i => i.id === itemInfo.value.project).name
}]: []
],
title: itemInfo.value.title,
description: itemInfo.value.description,
endText: templateEndText(generateContext(itemInfo.value, contactData)),
startText: templateStartText(generateContext(itemInfo.value, contactData)),
rows: rows,
total: {
totalNet: renderCurrency(documentTotal.value.totalNet),
total19: renderCurrency(documentTotal.value.total19),
totalGross: renderCurrency(documentTotal.value.totalGross),
totalGrossAlreadyPaid: renderCurrency(documentTotal.value.totalGrossAlreadyPaid),
totalSumToPay: renderCurrency(documentTotal.value.totalSumToPay),
titleSums: returnTitleSums
},
agriculture: itemInfo.value.agriculture,
usedAdvanceInvoices: itemInfo.value.usedAdvanceInvoices.map(i => {
return createddocuments.value.find(x => x.id === i)
})
}
console.log(returnData)
return returnData
}
const showDocument = ref(false)
const uri = ref("")
const generateDocument = async () => {
const ownTenant = profileStore.ownTenant
const path = letterheads.value.find(i => i.id === itemInfo.value.letterhead).path
/*const {data,error} = await supabase.functions.invoke('create_pdf',{
body: {
invoiceData: getDocumentData(),
backgroundPath: path,
returnMode: "base64"
}
})*/
uri.value = await useFunctions().useCreatePDF(getDocumentData(), path)
//const {data,error} = await supabase.storage.from("files").download(path)
//console.log(data)
//console.log(error)
//console.log(JSON.stringify(getDocumentData()))
//uri.value = `data:${data.mimeType};base64,${data.base64}`
//uri.value = await useCreatePdf(getDocumentData(), await data.arrayBuffer())
//alert(uri.value)
showDocument.value = true
//console.log(uri.value)
}
const onChangeTab = (index) => {
if(index === 1) {
generateDocument()
}
}
const setPosNumbers = () => {
let mainIndex = 1
let subIndex = 1
let rows = itemInfo.value.rows.map(row => {
if(row.mode === 'title') {
row.pos = mainIndex
mainIndex += 1
subIndex = 1
} else if(!['pagebreak','title','text'].includes(row.mode)) {
row.pos = itemInfo.value.rows.filter(i => i.mode === "title").length === 0 ? `${subIndex}` :`${ mainIndex - 1}.${subIndex}`
subIndex += 1
}
if(!row.id) {
row.id = uuidv4()
}
return row
})
}
const saveSerialInvoice = async () => {
let createData = {
type: itemInfo.value.type,
state: 'Erstellt',
customer: itemInfo.value.customer,
contact: itemInfo.value.contact,
address: itemInfo.value.address,
project: itemInfo.value.project,
paymentDays: itemInfo.value.paymentDays,
deliveryDateType: itemInfo.value.deliveryDateType,
createdBy: itemInfo.value.createdBy,
title: itemInfo.value.title,
description: itemInfo.value.description,
startText: itemInfo.value.startText,
endText: itemInfo.value.endText,
rows: itemInfo.value.rows,
contactPerson: itemInfo.value.contactPerson,
serialConfig: itemInfo.value.serialConfig,
letterhead: itemInfo.value.letterhead,
}
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 saveDocument = async (state,resetup = false) => {
itemInfo.value.state = state
if(state !== "Entwurf") {
console.log("???")
let type = ""
if(itemInfo.value.type === "advanceInvoices" || itemInfo.value.type === "cancellationInvoices"){
type = "invoices"
} else {
type = itemInfo.value.type
}
itemInfo.value.documentNumber = await useFunctions().useNextNumber(type) //data.usedNumber
setDocumentTypeConfig(false)
}
if(profileStore.ownTenant.extraModules.includes("agriculture")) {
itemInfo.value.rows.forEach(row => {
if(row.agriculture && row.agriculture.dieselUsage) {
row.agriculture.description = `${row.agriculture.dieselUsage} L Diesel zu ${renderCurrency(row.agriculture.dieselPrice)}/L verbraucht ${row.description ? "\n" + row.description : ""}`
}
})
}
//Check if Agricultural Description is Present
itemInfo.value.rows = itemInfo.value.rows.map(row => {
let descriptionText = ""
if(row.agriculture && row.agriculture.description) {
descriptionText = row.agriculture.description
} else {
descriptionText = row.description ? row.description : null
}
return {
...row,
descriptionText: descriptionText
}
})
let createData = {
type: itemInfo.value.type,
taxType: itemInfo.value.type === "invoices" ? itemInfo.value.taxType : null,
state: itemInfo.value.state || "Entwurf",
customer: itemInfo.value.customer,
contact: itemInfo.value.contact,
address: itemInfo.value.address,
project: itemInfo.value.project,
plant: itemInfo.value.plant,
documentNumber: itemInfo.value.documentNumber,
documentDate: itemInfo.value.documentDate,
deliveryDate: itemInfo.value.deliveryDate,
deliveryDateEnd: itemInfo.value.deliveryDateEnd,
paymentDays: itemInfo.value.paymentDays,
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,
contactPerson: itemInfo.value.contactPerson,
linkedDocument: itemInfo.value.linkedDocument,
agriculture: itemInfo.value.agriculture,
letterhead: itemInfo.value.letterhead,
usedAdvanceInvoices: itemInfo.value.usedAdvanceInvoices
}
if(route.params.id) {
await dataStore.updateItem("createddocuments", {...createData, id: itemInfo.value.id})
} else {
const data = await dataStore.createNewItem("createddocuments", createData)
console.log(data)
await router.push(`/createDocument/edit/${data[0].id}`)
}
if(resetup) await setupPage()
}
const closeDocument = async () => {
loaded.value = false
await saveDocument("Gebucht")
await generateDocument()
let fileData = {}
fileData.project = itemInfo.value.project
fileData.createddocument = itemInfo.value.id
let mappedType = itemInfo.value.type
if(mappedType === "advanceInvoices" || mappedType === "cancellationInvoices"){
mappedType = "invoices"
}
fileData.folder = (await supabase.from("folders").select("id").eq("tenant", profileStore.currentTenant).eq("function", mappedType).eq("year",dayjs().format("YYYY")).single()).data.id
let tag = (await supabase.from("filetags").select("id").eq("tenant", profileStore.currentTenant).eq("createddocumenttype", mappedType).single()).data
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],[tag.id], true)
await router.push(`/createDocument/show/${itemInfo.value.id}`)
}
const getTextTemplateByType = (type) => {
let finalType = type
if(type === "serialInvoices") {
finalType = "invoices"
}
return texttemplates.value.filter(i => i.documentType === finalType)
}
const setRowData = (row) => {
console.log(row)
if(row.service) {
row.unit = services.value.find(i => i.id === row.service).unit
row.price = services.value.find(i => i.id === row.service).sellingPriceComposed.total || services.value.find(i => i.id === row.service).sellingPrice
row.description = services.value.find(i => i.id === row.service).description
row.taxPercent = services.value.find(i => i.id === row.service).taxPercentage
}
if(row.product) {
row.unit = products.value.find(i => i.id === row.product).unit
row.price = products.value.find(i => i.id === row.product).sellingPrice
row.description = products.value.find(i => i.id === row.product).description
row.taxPercent = products.value.find(i => i.id === row.product).taxPercentage
}
}
</script>
<template>
<UDashboardNavbar>
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.back()/*router.push(`/standardEntity/${type}`)*/"
>
<!-- {{dataType.label}}-->
</UButton>
</template>
<template #right>
<ButtonWithConfirm
v-if="itemInfo.state === 'Entwurf'"
color="rose"
variant="outline"
@confirmed="dataStore.updateItem('createddocuments',{id:itemInfo.id,archived: true}),
router.push('/createDocument')"
>
<template #button>
Archivieren
</template>
<template #header>
<span class="text-md dark:text-whitetext-black font-bold">Archivieren bestätigen</span>
</template>
Möchten Sie diesen Ausgangsbeleg wirklich archivieren?
</ButtonWithConfirm>
<UButton
icon="i-mdi-content-save"
@click="saveDocument('Entwurf',true)"
v-if="itemInfo.type !== 'serialInvoices' "
:disabled="!itemInfo.customer"
>
Speichern
</UButton>
<UButton
@click="closeDocument"
v-if="itemInfo.id && itemInfo.type !== 'serialInvoices'"
>
Fertigstellen
</UButton>
<UButton
icon="i-mdi-content-save"
@click="saveSerialInvoice"
v-if="itemInfo.type === 'serialInvoices'"
>
Serienrechnung
</UButton>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<UTabs class="p-5" :items="tabItems" @change="onChangeTab" v-if="loaded">
<template #item="{item}">
<div v-if="item.label === 'Editor'">
<UAlert
class="my-5"
title="Vorhandene Probleme und Informationen:"
:color="findDocumentErrors.filter(i => i.type === 'breaking').length > 0 ? 'rose' : 'white'"
variant="outline"
v-if="findDocumentErrors.length > 0"
>
<template #description>
<ul class="list-disc ml-5">
<li v-for="error in findDocumentErrors" :class="[...error.type === 'breaking' ? ['text-rose-600'] : ['dark:text-white','text-black']]">
{{error.message}}
</li>
</ul>
</template>
</UAlert>
<InputGroup>
<div class="w-1/3 mr-5">
<UFormGroup
label="Dokumenttyp:"
>
<InputGroup>
<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"
@change="setDocumentTypeConfig"
class="flex-auto"
>
<template #label>
{{dataStore.documentTypesForCreation[itemInfo.type].labelSingle}}
</template>
</USelectMenu>
<UButton
variant="outline"
v-if="itemInfo.type === 'advanceInvoices'"
icon="i-heroicons-arrow-down-tray"
@click="showAdvanceInvoiceCalcModal = true"
>Auto Positionen</UButton>
</InputGroup>
<USlideover
v-model="showAdvanceInvoiceCalcModal"
>
<UCard class="h-full">
<template #header>
<UButton @click="importPositions">Übernehmen</UButton>
</template>
<UFormGroup
label="Gesamtsumme:"
>
<UInput
type="number"
:step="0.01"
v-model="advanceInvoiceData.totalSumNet"
@focusout="advanceInvoiceData.part = advanceInvoiceData.totalSumNet / 100 * advanceInvoiceData.partPerPecentage"
/>
</UFormGroup>
<UFormGroup
label="Prozent:"
>
<UInput
type="number"
:step="0.01"
v-model="advanceInvoiceData.partPerPecentage"
@focusout="advanceInvoiceData.part = advanceInvoiceData.totalSumNet / 100 * advanceInvoiceData.partPerPecentage"
/>
</UFormGroup>
<UFormGroup
label="Abzurechnender Anteil:"
>
<UInput
type="number"
:step="0.01"
v-model="advanceInvoiceData.part"
@focusout="advanceInvoiceData.partPerPecentage = Number((advanceInvoiceData.part / advanceInvoiceData.totalSumNet * 100).toFixed(2))"
/>
</UFormGroup>
</UCard>
</USlideover>
</UFormGroup>
<UFormGroup
label="Steuertyp:"
v-if="itemInfo.type === 'invoices'"
>
<USelectMenu
:options="['Standard','13b UStG']"
v-model="itemInfo.taxType"
class="w-full"
></USelectMenu>
</UFormGroup>
<UFormGroup
label="Briefpapier:"
>
<USelectMenu
:options="letterheads"
v-model="itemInfo.letterhead"
value-attribute="id"
option-attribute="name"
searchable
searchable-placeholder="Suche..."
:search-attributes="['name']"
class="w-full"
:color="itemInfo.letterhead ? 'primary' : 'rose'"
>
<template #label>
{{itemInfo.letterhead ? letterheads.find(i => i.id === itemInfo.letterhead).name : "Kein Briefpapier gewählt"}}
</template>
</USelectMenu>
</UFormGroup>
<UFormGroup
label="Kunde:"
>
<div class="flex flex-row">
<USelectMenu
:options="customers"
option-attribute="name"
value-attribute="id"
:search-attributes="['name']"
searchable
searchable-placeholder="Suche..."
v-model="itemInfo.customer"
@change="setCustomerData"
class="flex-auto mr-2"
>
<UButton
:color="itemInfo.customer ? 'primary' : 'rose'"
variant="outline"
class="w-full"
>
{{itemInfo.customer ? customers.find(i => i.id === 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>
</USelectMenu>
<UButton
variant="outline"
class="w-25"
v-if="itemInfo.customer"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="router.push(`/standardEntity/customers/show/${itemInfo.customer}`)"
>Kunde</UButton>
</div>
<UAlert
v-if="itemInfo.customer"
class="mt-2"
variant="solid"
color="white"
title="Info"
>
<template #description>
Kundennr: {{customers.find(i => i.id === itemInfo.customer).customerNumber}}<br>
Typ: {{customers.find(i => i.id === itemInfo.customer).isCompany ? 'Firma' : 'Privat'}}<br>
Notizen: <span class="truncate">{{customers.find(i => i.id === itemInfo.customer).notes}}</span>
</template>
</UAlert>
</UFormGroup>
<UFormGroup
label="Ansprechpartner:"
v-if="itemInfo.customer ? dataStore.getCustomerById(itemInfo.customer).isCompany : false "
>
<InputGroup>
<USelectMenu
:options="contacts.filter(i => i.customer === 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' : 'none'"
variant="outline"
class="w-full"
:disabled="!itemInfo.customer"
>
<span class="truncate">{{dataStore.getContactById(itemInfo.contact) ? dataStore.getContactById(itemInfo.contact).fullName : "Kein Kontakt ausgewählt"}}</span>
<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(`/standardEntity/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="w-2/3">
<UFormGroup
:label="itemInfo.documentNumberTitle + ':'"
v-if="itemInfo.type !== 'serialInvoices'"
>
<UInput
v-model="itemInfo.documentNumber"
placeholder="XXXX"
disabled
/>
</UFormGroup>
<InputGroup class="w-full">
<UFormGroup
class="w-80 mr-1"
label="Lieferdatumsart:"
v-if="itemInfo.type !== 'serialInvoices'"
>
<USelectMenu
:options="['Lieferdatum','Lieferzeitraum','Leistungsdatum','Leistungszeitraum','Kein Lieferdatum anzeigen']"
v-model="itemInfo.deliveryDateType"
/>
</UFormGroup>
<UFormGroup
:label="`${itemInfo.deliveryDateType}${['Lieferzeitraum', 'Leistungszeitraum'].includes(itemInfo.deliveryDateType) ? ' Start' : ''}:`"
v-if="itemInfo.type !== 'serialInvoices'"
class="mr-1"
>
<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"
class="mx-auto"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.deliveryDate" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup
:label="itemInfo.deliveryDateType + ' Ende:'"
v-if="itemInfo.type !== 'serialInvoices' && ['Lieferzeitraum','Leistungszeitraum'].includes(itemInfo.deliveryDateType)"
>
<UPopover
:popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.deliveryDateEnd ? dayjs(itemInfo.deliveryDateEnd).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
class="mx-auto"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.deliveryDateEnd" @close="close" />
</template>
</UPopover>
</UFormGroup>
</InputGroup>
<InputGroup class="w-full">
<UFormGroup
label="Belegdatum:"
class="mr-1"
v-if="itemInfo.type !== 'serialInvoices'"
>
<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="w-full"
label="Zahlungsziel in Tagen:"
>
<UInput
type="number"
v-model="itemInfo.paymentDays"
/>
</UFormGroup>
</InputGroup>
<UFormGroup
label="Ansprechpartner:"
>
<USelectMenu
:options="profileStore.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="Objekt:"
>
<InputGroup>
<USelectMenu
:options="plants.filter(i => i.customer === itemInfo.customer)"
v-model="itemInfo.plant"
value-attribute="id"
option-attribute="name"
searchable
searchable-placeholder="Suche..."
:search-attributes="['name']"
class="w-full"
:disabled="!itemInfo.customer"
@change="checkForOpenAdvanceInvoices"
>
<template #label>
{{plants.find(i => i.id === itemInfo.plant) ? plants.find(i => i.id === itemInfo.plant).name : "Kein Objekt ausgewählt"}}
</template>
<template #option="{option: plant}">
{{plant.name}}
</template>
</USelectMenu>
<UButton
variant="outline"
color="rose"
v-if="itemInfo.plant"
icon="i-heroicons-x-mark"
@click="itemInfo.plant = null"
/>
<UButton
variant="outline"
v-if="itemInfo.plant"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="router.push(`/standardEntity/plants/show/${itemInfo.plant}`)"
>Objekt</UButton>
</InputGroup>
</UFormGroup>
<UFormGroup
label="Projekt:"
>
<InputGroup>
<USelectMenu
:options="projects.filter(i => i.customer === itemInfo.customer && (itemInfo.plant ? itemInfo.plant === i.plant : true))"
v-model="itemInfo.project"
value-attribute="id"
option-attribute="name"
searchable
searchable-placeholder="Suche..."
:search-attributes="['name']"
class="w-full"
:disabled="!itemInfo.customer"
@change="checkForOpenAdvanceInvoices"
>
<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"
color="rose"
v-if="itemInfo.project"
icon="i-heroicons-x-mark"
@click="itemInfo.project = null"
/>
<UButton
variant="outline"
v-if="itemInfo.project"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="router.push(`/standardEntity/projects/show/${itemInfo.project}`)"
>Projekt</UButton>
</InputGroup>
</UFormGroup>
</div>
</InputGroup>
<div v-if="itemInfo.type === 'serialInvoices'" class="mb-5">
<UDivider class="mt-5 mb-3">
Einstellungen für die Serienrechnung
</UDivider>
<div class="flex flex-row">
<div class="w-1/3">
<UFormGroup
label="Datum erste Ausführung:"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.serialConfig.firstExecution ? dayjs(itemInfo.serialConfig.firstExecution).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.serialConfig.firstExecution" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UFormGroup
label="Datum letzte Ausführung:"
>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.serialConfig.executionUntil ? dayjs(itemInfo.serialConfig.executionUntil).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.serialConfig.executionUntil" @close="close" />
</template>
</UPopover>
</UFormGroup>
<UCheckbox
v-model="itemInfo.serialConfig.active"
label="Aktiv"
class="mt-3"
/>
</div>
<div class="w-2/3">
<UFormGroup
label="Intervall:"
>
<USelectMenu
v-model="itemInfo.serialConfig.intervall"
:options="['wöchentlich','2 - wöchentlich', 'monatlich', 'vierteljährlich','halbjährlich', 'jährlich']"
/>
</UFormGroup>
<UFormGroup
label="Richtung:"
>
<USelectMenu
v-model="itemInfo.serialConfig.dateDirection"
:options="['Rückwirkend','Im Voraus']"
/>
</UFormGroup>
<UAlert
title="Anfangs- und Enddatum"
description="Für das Anfangs- und Enddatum werden jeweils der ersten und letzte Tag des ausgewählten Intervalls und der Richtung automatisch ausgewählt"
class="mt-5"
/>
</div>
</div>
</div>
<UDivider
class="my-3"
/>
<UFormGroup
label="Titel:"
>
<UInput v-model="itemInfo.title" disabled/>
</UFormGroup>
<UFormGroup
label="Beschreibung:"
class="mt-3"
>
<UInput v-model="itemInfo.description"/>
</UFormGroup>
<UDivider
class="my-3"
/>
<UFormGroup
label="Vorlage auswählen"
>
<USelectMenu
:options="getTextTemplateByType(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 && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? dataStore.texttemplates.find(i => i.text === itemInfo.startText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).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" v-if="itemInfo.rows.length > 0">
<thead>
<tr>
<th></th>
<th>Pos.</th>
<th>Name</th>
<th>Menge</th>
<th>Einheit</th>
<th v-if="itemInfo.type !== 'deliveryNotes'">Preis</th>
<th v-if="itemInfo.type !== 'deliveryNotes'">Steuer</th>
<th v-if="itemInfo.type !== 'deliveryNotes'">Rabatt</th>
<th>Beschreibung</th>
<th v-if="itemInfo.type !== 'deliveryNotes'">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 === 'text'"
colspan="9"
>
<UInput
v-model="row.text"
placeholder="Titel"
maxlength="60"
/>
<UTextarea
class="mt-2"
v-model="row.description"
placeholder="Text"
/>
</td>
<td
v-if="!['pagebreak','text'].includes(row.mode)"
>{{row.pos}}</td>
<td
class="w-120"
v-if="row.mode === 'free'"
>
<UInput
v-model="row.text"
placeholder="Name"
class="min-w-40"
/>
</td>
<td
class="w-120"
v-else-if="row.mode === 'normal'"
>
<InputGroup class="w-full">
<USelectMenu
class="flex-auto"
:options="products"
:color="row.product ? 'primary' : 'rose'"
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suche ..."
:search-attributes="['name']"
v-model="row.product"
@change="setRowData(row)"
>
<template #label>
<span class="truncate">{{row.product ? products.find(i => i.id === row.product).name : "Kein Produkt ausgewählt" }}</span>
</template>
</USelectMenu>
<UButton
icon="i-heroicons-magnifying-glass"
@click="showProductSelectionModal = true"
/>
<UModal v-model="showProductSelectionModal">
<UCard>
<template #header>
Artikel Auswählen
</template>
<InputGroup class="w-full">
<UFormGroup label="Artikelkategorie:">
<USelectMenu
v-if="productcategories.length > 0"
:options="[{name: 'Nicht zugeordnet',id:'not set'},...productcategories]"
value-attribute="id"
option-attribute="name"
v-model="selectedProductcategorie"
/>
</UFormGroup>
</InputGroup>
<UTable
:rows="selectedProductcategorie !== 'not set' ? products.filter(i => i.productcategories.includes(selectedProductcategorie)) : products.filter(i => i.productcategories.length === 0)"
:columns="[
{key: 'name',label:'Name'},
{key: 'manufacturer',label:'Hersteller'},
{key: 'articleNumber',label:'Artikelnummer'},
]"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Artikel anzuzeigen' }"
@select=" (i) => {
row.product = i.id
row.unit = products.find(i => i.id === row.product).unit,
row.price = (products.find(i => i.id === row.product).sellingPrice || 0),
row.description = products.find(i => i.id === row.product).description
showProductSelectionModal = false}"
>
</UTable>
</UCard>
</UModal>
</InputGroup>
</td>
<td
class="w-120"
v-else-if="row.mode === 'service'"
>
<InputGroup class="w-full">
<USelectMenu
class="flex-auto"
:options="services"
:color="row.service ? 'primary' : 'rose'"
option-attribute="name"
value-attribute="id"
searchable
searchable-placeholder="Suche ..."
:search-attributes="['name']"
v-model="row.service"
@change="setRowData(row)"
>
<template #label>
<span class="truncate">{{services.find(i => i.id === row.service) ? services.find(i => i.id === row.service).name : "Keine Leistung ausgewählt" }}</span>
</template>
</USelectMenu>
<UButton
icon="i-heroicons-magnifying-glass"
@click="showServiceSelectionModal = true"
/>
<UModal v-model="showServiceSelectionModal">
<UCard>
<template #header>
Leistung Auswählen
</template>
<InputGroup class="w-full">
<UFormGroup label="Leistungskategorie:">
<USelectMenu
v-if="servicecategories.length > 0"
:options="[{name: 'Nicht zugeordnet',id:'not set'},...servicecategories]"
value-attribute="id"
option-attribute="name"
v-model="selectedServicecategorie"
/>
</UFormGroup>
</InputGroup>
<UTable
:rows="selectedServicecategorie !== 'not set' ? services.filter(i => i.servicecategories.includes(selectedServicecategorie)) : services.filter(i => i.servicecategories.length === 0)"
:columns="[
{key: 'name',label:'Name'},
{key: 'serviceNumber',label:'Leistungsnummer'},
{key: 'sellingPrice',label:'Verkaufspreis'},
]"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Leistungen anzuzeigen' }"
@select=" (i) => {
row.service = i.id
setRowData(row)
showServiceSelectionModal = false}"
>
</UTable>
</UCard>
</UModal>
</InputGroup>
</td>
<td
class="w-20"
v-if="!['pagebreak','title','text'].includes(row.mode)"
>
<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="!['pagebreak','title','text'].includes(row.mode)"
>
<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="!['pagebreak','title','text'].includes(row.mode) && itemInfo.type !== 'deliveryNotes'"
>
<UInput
v-model="row.price"
type="number"
step="0.001"
>
<template #trailing>
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
</template>
</UInput>
</td>
<td
class="w-40"
v-if="!['pagebreak','title','text'].includes(row.mode)&& itemInfo.type !== 'deliveryNotes'"
>
<USelectMenu
:options="[19,7,0]"
v-model="row.taxPercent"
:disabled="itemInfo.taxType === '13b UStG'"
>
<template #option="{option}">
{{option}} %
</template>
<template #label>
{{row.taxPercent}} %
</template>
</USelectMenu>
</td>
<td
class="w-40"
v-if="!['pagebreak','title','text'].includes(row.mode)&& itemInfo.type !== 'deliveryNotes'"
>
<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="!['pagebreak','title','text'].includes(row.mode)"
>
<UButton
icon="i-heroicons-document-text"
@click="row.showEdit = true"
/>
<UButton
icon="i-mdi-water-drop-outline"
class="ml-3"
v-if="row.agriculture"
@click="row.showEditDiesel = 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>
<UModal v-model="row.showEditDiesel">
<UCard>
<template #header>
Dieselverbrauch bearbeiten
</template>
<UFormGroup
label="Menge:"
>
<UInput
v-model="row.agriculture.dieselUsage"
>
<template #trailing>
L
</template>
</UInput>
</UFormGroup>
<UFormGroup
label="Preis:"
>
<UInput
v-model="row.agriculture.dieselPrice"
type="number"
steps="0.01"
>
<template #trailing>
/L
</template>
</UInput>
</UFormGroup>
<template #footer>
<UButton
@click="row.showEditDiesel = false,
processDieselPosition()"
>
Speichern
</UButton>
</template>
</UCard>
</UModal>
</td>
<td
v-if="!['pagebreak','title','text'].includes(row.mode) && itemInfo.type !== 'deliveryNotes'"
>
<p class="text-right font-bold whitespace-nowrap"><span v-if="row.discountPercent !== 0" class="line-through mr-2 text-rose-500">{{getRowAmountUndiscounted(row)}} </span>{{getRowAmount(row)}} </p>
</td>
<td
v-if="row.mode === 'title'"
colspan="8"
>
<UInput
v-model="row.text"
placeholder="Titel"
/>
</td>
<td>
<UButton
variant="ghost"
color="rose"
icon="i-heroicons-x-mark-16-solid"
@click="removePosition(row.id)"
/>
</td>
</tr>
</template>
</draggable>
</table>
<UAlert
v-else
title="Keine Positionen hinzugefügt"
color="rose"
variant="outline"
icon="i-heroicons-light-bulb"
></UAlert>
<InputGroup>
<UButton
@click="addPosition('service')"
class="mt-3"
:disabled="itemInfo.type === 'advanceInvoices'"
>
+ Leistung
</UButton>
<UButton
@click="addPosition('normal')"
class="mt-3"
:disabled="itemInfo.type === 'advanceInvoices'"
>
+ Artikel
</UButton>
<UButton
@click="addPosition('free')"
class="mt-3"
:disabled="itemInfo.type === 'advanceInvoices'"
>
+ Freie Position
</UButton>
<UButton
@click="addPosition('pagebreak')"
class="mt-3"
>
+ Seitenumbruch
</UButton>
<UButton
@click="addPosition('title')"
class="mt-3"
>
+ Titel
</UButton>
<UButton
@click="addPosition('text')"
class="mt-3"
>
+ Freitext
</UButton>
</InputGroup>
<UDivider
class="mt-5 mb-3"
v-if="openAdvanceInvoices.length > 0"
>
Noch nicht abgerechnete Abschlagsrechnungen
</UDivider>
<div
v-for="advanceInvoice in openAdvanceInvoices"
:key="advanceInvoice.id"
class="flex flex-row justify-between my-2"
>
<span>{{advanceInvoice.documentNumber}}</span>
<span>Summe: {{renderCurrency(advanceInvoice.rows[0].price * (1 + advanceInvoice.rows[0].taxPercent/100))}}</span>
<InputGroup>
<UButton
@click="addAdvanceInvoiceToInvoice(advanceInvoice.id)"
:disabled="itemInfo.usedAdvanceInvoices.includes(advanceInvoice.id)"
class="mr-2"
>
Verwenden
</UButton>
<UButton
@click="itemInfo.usedAdvanceInvoices = itemInfo.usedAdvanceInvoices.filter(i => i !== advanceInvoice.id)"
:disabled="!itemInfo.usedAdvanceInvoices.includes(advanceInvoice.id)"
color="rose"
variant="outline"
>
X
</UButton>
</InputGroup>
</div>
<UDivider class="my-3" v-if="Object.keys(documentTotal.titleSums).length > 0">Überschriften</UDivider>
<table>
<tr v-for="sumKey in Object.keys(documentTotal.titleSums) ">
<td>{{sumKey}}</td>
<td>{{useCurrency(documentTotal.titleSums[sumKey])}}</td>
</tr>
</table>
<UDivider class="my-3" v-if="itemInfo.rows.length > 0">Gesamt</UDivider>
<div class="w-full flex justify-end" v-if="itemInfo.type !== 'deliveryNotes'">
<table class="w-1/3" v-if="itemInfo.rows.length > 0">
<tr>
<td class="font-bold">Netto:</td>
<td class="text-right">{{renderCurrency(documentTotal.totalNet)}}</td>
</tr>
<tr>
<td class="font-bold">zzgl. 19 % USt:</td>
<td class="text-right">{{renderCurrency(documentTotal.total19)}}</td>
</tr>
<tr>
<td class="font-bold">Brutto:</td>
<td class="text-right">{{renderCurrency(documentTotal.totalGross)}}</td>
</tr>
<tr v-if="documentTotal.totalGrossAlreadyPaid !== '0,00 €'">
<td class="font-bold">Bereits bezahlt:</td>
<td class="text-right">{{renderCurrency(documentTotal.totalGrossAlreadyPaid)}}</td>
</tr>
<tr v-if="documentTotal.totalGrossAlreadyPaid !== '0,00 €'">
<td class="font-bold">Offene Summe:</td>
<td class="text-right">{{renderCurrency(documentTotal.totalSumToPay)}}</td>
</tr>
</table>
</div>
<UDivider
class="my-3"
/>
<UFormGroup
label="Vorlage auswählen"
>
<USelectMenu
:options="getTextTemplateByType(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 && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? dataStore.texttemplates.find(i => i.text === itemInfo.endText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).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>
<UProgress animation="carousel" v-else/>
</UDashboardPanelContent>
</template>
<style scoped>
th {
text-align: left;
}
td {
padding: .4em;
}
/*tr:hover {
border: 1px solid #69c350;
}*/
.previewDocument {
height: 80vh;
}
</style>