2050 lines
72 KiB
Vue
2050 lines
72 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",
|
|
customer: null,
|
|
contact: null,
|
|
address: {
|
|
street: null,
|
|
special: null,
|
|
zip: null,
|
|
city: null,
|
|
},
|
|
project: null,
|
|
documentNumber: null,
|
|
documentNumberTitle: "Rechnungsnummer",
|
|
documentDate: dayjs(),
|
|
deliveryDate: dayjs(),
|
|
deliveryDateType: "Lieferdatum",
|
|
dateOfPerformance: null,
|
|
paymentDays: 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 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","*"))
|
|
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()
|
|
|
|
linkedDocuments.forEach(doc => {
|
|
let lastId = 0
|
|
itemInfo.value.rows.forEach(row => {
|
|
if(row.id > lastId) lastId = row.id
|
|
})
|
|
|
|
itemInfo.value.rows.push(...[
|
|
{
|
|
id:uuidv4(),
|
|
mode: "title",
|
|
text: doc.title
|
|
},
|
|
...doc.rows
|
|
])
|
|
|
|
})
|
|
|
|
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.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) => {
|
|
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 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(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)
|
|
}
|
|
}
|
|
})
|
|
|
|
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 {
|
|
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.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,
|
|
nachname: contactData && contactData.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)
|
|
|
|
const returnData = {
|
|
type: itemInfo.value.type,
|
|
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"),
|
|
deliveryDateType: itemInfo.value.deliveryDateType,
|
|
contactPerson: contactPerson.fullName,
|
|
contactTel: contactPerson.fixedTel || contactPerson.mobileTel,
|
|
contactEMail: contactPerson.email,
|
|
project: dataStore.getProjectById(itemInfo.value.project) ? dataStore.getProjectById(itemInfo.value.project).name : null
|
|
},
|
|
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)
|
|
},
|
|
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"
|
|
}
|
|
})
|
|
|
|
|
|
|
|
//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
|
|
}
|
|
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,
|
|
state: itemInfo.value.state || "Entwurf",
|
|
customer: itemInfo.value.customer,
|
|
contact: itemInfo.value.contact,
|
|
address: itemInfo.value.address,
|
|
project: itemInfo.value.project,
|
|
documentNumber: itemInfo.value.documentNumber,
|
|
documentDate: itemInfo.value.documentDate,
|
|
deliveryDate: itemInfo.value.deliveryDate,
|
|
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 #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'] : ['text-white']]">
|
|
{{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="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
|
|
label="Datum:"
|
|
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="mt-3 w-80"
|
|
label="Lieferdatumsart:"
|
|
v-if="itemInfo.type !== 'serialInvoices'"
|
|
>
|
|
<USelectMenu
|
|
:options="['Lieferdatum'/*,'Lieferzeitraum'*/,'Leistungsdatum'/*,'Leistungszeitraum'*/,'Kein Lieferdatum anzeigen']"
|
|
v-model="itemInfo.deliveryDateType"
|
|
class="mb-2"
|
|
/>
|
|
|
|
|
|
</UFormGroup>
|
|
<UFormGroup
|
|
:label="itemInfo.deliveryDateType"
|
|
v-if="itemInfo.type !== 'serialInvoices'"
|
|
>
|
|
<UPopover :popper="{ placement: 'bottom-start' }">
|
|
<UButton
|
|
icon="i-heroicons-calendar-days-20-solid"
|
|
:label="itemInfo.deliveryDate ? dayjs(itemInfo.deliveryDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
|
variant="outline"
|
|
/>
|
|
|
|
<template #panel="{ close }">
|
|
<LazyDatePicker v-model="itemInfo.deliveryDate" @close="close" />
|
|
</template>
|
|
</UPopover>
|
|
</UFormGroup>
|
|
|
|
<UFormGroup
|
|
class="w-full"
|
|
>
|
|
<template #label>
|
|
<span class="truncate">
|
|
Zahlungsziel in Tagen:
|
|
</span>
|
|
</template>
|
|
<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="Projekt:"
|
|
>
|
|
<InputGroup>
|
|
<USelectMenu
|
|
:options="projects.filter(i => i.customer === itemInfo.customer)"
|
|
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"
|
|
>
|
|
<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="itemInfo.rows.length > 0"/>
|
|
|
|
|
|
<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> |