Merge branch 'devCorrected' into 'beta'

Introduced Employee Number

See merge request fedeo/software!37
This commit is contained in:
2025-11-14 19:01:20 +00:00
6 changed files with 107 additions and 178 deletions

View File

@@ -168,6 +168,7 @@ watch(downloadControl, (downloadCtrl) => {
variant="outline" variant="outline"
></UButton> ></UButton>
<UButton <UButton
v-if="props.fileId"
@click="handleDownloadFile" @click="handleDownloadFile"
variant="outline" variant="outline"
icon="i-heroicons-arrow-down-on-square" icon="i-heroicons-arrow-down-on-square"

View File

@@ -124,6 +124,19 @@ export const useFiles = () => {
} }
const dataURLtoFile = (dataurl:string, filename:string) => {
let arr = dataurl.split(","),
//@ts-ignore
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});
}
return {uploadFiles, selectDocuments, selectSomeDocuments, selectDocument, downloadFile}
return {uploadFiles, selectDocuments, selectSomeDocuments, selectDocument, downloadFile, dataURLtoFile}
} }

View File

@@ -661,7 +661,7 @@ const findDocumentErrors = computed(() => {
if (itemInfo.value.rows.length === 0) { if (itemInfo.value.rows.length === 0) {
errors.push({message: "Es sind keine Positionen angegeben", type: "breaking"}) errors.push({message: "Es sind keine Positionen angegeben", type: "breaking"})
} else { } else {
itemInfo.value.rows.forEach(row => { itemInfo.value.rows.forEach((row,index) => {
if (itemInfo.value.type !== "quotes" && row.optional) { if (itemInfo.value.type !== "quotes" && row.optional) {
errors.push({ errors.push({
@@ -717,6 +717,10 @@ const findDocumentErrors = computed(() => {
} }
} }
if (index === itemInfo.value.rows.length - 1 && row.mode === "pagebreak") {
errors.push({message: `Die letze Position darf kein Seitenumbruch sein`, type: "breaking"})
}
}) })
} }

View File

@@ -32,6 +32,8 @@ const costcentres = ref([])
const vendors = ref([]) const vendors = ref([])
const accounts = ref([]) const accounts = ref([])
const mode = ref(route.params.mode)
const setup = async () => { const setup = async () => {
let filetype = (await useEntities("filetags").select()).find(i=> i.incomingDocumentType === "invoices").id let filetype = (await useEntities("filetags").select()).find(i=> i.incomingDocumentType === "invoices").id
console.log(filetype) console.log(filetype)
@@ -43,6 +45,8 @@ const setup = async () => {
itemInfo.value = await useEntities("incominginvoices").selectSingle(route.params.id, "*, files(*)") itemInfo.value = await useEntities("incominginvoices").selectSingle(route.params.id, "*, files(*)")
await loadFile(itemInfo.value.files[itemInfo.value.files.length-1].id) await loadFile(itemInfo.value.files[itemInfo.value.files.length-1].id)
if(itemInfo.value.date && !itemInfo.value.dueDate) itemInfo.value.dueDate = itemInfo.value.date
} }
setup() setup()
@@ -143,7 +147,7 @@ const findIncomingInvoiceErrors = computed(() => {
let errors = [] let errors = []
if(itemInfo.value.vendor === null) errors.push({message: "Es ist kein Lieferant ausgewählt", type: "breaking"}) if(itemInfo.value.vendor === null) errors.push({message: "Es ist kein Lieferant ausgewählt", type: "breaking"})
if(itemInfo.value.reference === null) errors.push({message: "Es ist keine Referenz angegeben", type: "breaking"}) if(itemInfo.value.reference === null || itemInfo.value.reference.length === 0) errors.push({message: "Es ist keine Referenz angegeben", type: "breaking"})
if(itemInfo.value.date === null) errors.push({message: "Es ist kein Datum ausgewählt", type: "breaking"}) if(itemInfo.value.date === null) errors.push({message: "Es ist kein Datum ausgewählt", type: "breaking"})
if(itemInfo.value.dueDate === null) errors.push({message: "Es ist kein Fälligkeitsdatum ausgewählt", type: "breaking"}) if(itemInfo.value.dueDate === null) errors.push({message: "Es ist kein Fälligkeitsdatum ausgewählt", type: "breaking"})
if(itemInfo.value.paymentType === null) errors.push({message: "Es ist keine Zahlart ausgewählt", type: "breaking"}) if(itemInfo.value.paymentType === null) errors.push({message: "Es ist keine Zahlart ausgewählt", type: "breaking"})
@@ -168,21 +172,38 @@ const findIncomingInvoiceErrors = computed(() => {
</script> </script>
<template> <template>
<UDashboardNavbar :title="'Eingangsbeleg erstellen'"> <UDashboardNavbar>
<template #left>
<UButton
icon="i-heroicons-chevron-left"
@click="navigateTo(`/incomingInvoices`)"
variant="outline"
>
Eingangsbelege
</UButton>
</template>
<template #center>
<h1
class="text-xl font-medium"
>{{`Eingangsbeleg ${mode === 'show' ? 'anzeigen' : 'bearbeiten'}`}}</h1>
</template>
<template #right> <template #right>
<ArchiveButton <ArchiveButton
color="rose" color="rose"
variant="outline" variant="outline"
type="incominginvoices" type="incominginvoices"
@confirmed="useEntities('incominginvoices').archive(route.params.id)" @confirmed="useEntities('incominginvoices').archive(route.params.id)"
v-if="mode !== 'show'"
/> />
<UButton <UButton
@click="updateIncomingInvoice(false)" @click="updateIncomingInvoice(false)"
v-if="mode !== 'show'"
> >
Speichern Speichern
</UButton> </UButton>
<UButton <UButton
@click="updateIncomingInvoice(true)" @click="updateIncomingInvoice(true)"
v-if="mode !== 'show'"
:disabled="findIncomingInvoiceErrors.filter(i => i.type === 'breaking').length > 0" :disabled="findIncomingInvoiceErrors.filter(i => i.type === 'breaking').length > 0"
> >
Speichern & Buchen Speichern & Buchen
@@ -192,6 +213,7 @@ const findIncomingInvoiceErrors = computed(() => {
<UDashboardPanelContent> <UDashboardPanelContent>
<div <div
class="flex justify-between mt-5 workingContainer" class="flex justify-between mt-5 workingContainer"
v-if="loadedFile"
> >
<object <object
v-if="loadedFile" v-if="loadedFile"
@@ -200,7 +222,6 @@ const findIncomingInvoiceErrors = computed(() => {
class="mx-5 documentPreview" class="mx-5 documentPreview"
/> />
<div class="w-3/5 mx-5"> <div class="w-3/5 mx-5">
<UAlert <UAlert
class="mb-5" class="mb-5"
title="Vorhandene Probleme und Informationen:" title="Vorhandene Probleme und Informationen:"
@@ -218,17 +239,19 @@ const findIncomingInvoiceErrors = computed(() => {
</UAlert> </UAlert>
<div class=" scrollContainer"> <div class="scrollContainer">
<InputGroup class="mb-3"> <InputGroup class="mb-3">
<UButton <UButton
:variant="itemInfo.expense ? 'solid' : 'outline'" :variant="itemInfo.expense ? 'solid' : 'outline'"
@click="itemInfo.expense = true" @click="itemInfo.expense = true"
:disabled="mode === 'show'"
> >
Ausgabe Ausgabe
</UButton> </UButton>
<UButton <UButton
:variant="!itemInfo.expense ? 'solid' : 'outline'" :variant="!itemInfo.expense ? 'solid' : 'outline'"
@click="itemInfo.expense = false" @click="itemInfo.expense = false"
:disabled="mode === 'show'"
> >
Einnahme Einnahme
</UButton> </UButton>
@@ -237,6 +260,7 @@ const findIncomingInvoiceErrors = computed(() => {
<UFormGroup label="Lieferant:" > <UFormGroup label="Lieferant:" >
<InputGroup> <InputGroup>
<USelectMenu <USelectMenu
:disabled="mode === 'show'"
v-model="itemInfo.vendor" v-model="itemInfo.vendor"
:options="vendors" :options="vendors"
option-attribute="name" option-attribute="name"
@@ -258,12 +282,15 @@ const findIncomingInvoiceErrors = computed(() => {
type="vendors" type="vendors"
:id="itemInfo.vendor" :id="itemInfo.vendor"
@return-data="(data) => itemInfo.vendor = data.id" @return-data="(data) => itemInfo.vendor = data.id"
:button-edit="mode !== 'show'"
:button-create="mode !== 'show'"
/> />
<UButton <UButton
icon="i-heroicons-x-mark" icon="i-heroicons-x-mark"
variant="outline" variant="outline"
color="rose" color="rose"
@click="itemInfo.vendor = null" @click="itemInfo.vendor = null"
v-if="itemInfo.vendor && mode !== 'show'"
/> />
</InputGroup> </InputGroup>
@@ -276,6 +303,7 @@ const findIncomingInvoiceErrors = computed(() => {
> >
<UInput <UInput
v-model="itemInfo.reference" v-model="itemInfo.reference"
:disabled="mode === 'show'"
/> />
</UFormGroup> </UFormGroup>
@@ -287,6 +315,7 @@ const findIncomingInvoiceErrors = computed(() => {
:label="itemInfo.date ? dayjs(itemInfo.date).format('DD.MM.YYYY') : 'Datum auswählen'" :label="itemInfo.date ? dayjs(itemInfo.date).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline" variant="outline"
:color="!itemInfo.date ? 'rose' : 'primary'" :color="!itemInfo.date ? 'rose' : 'primary'"
:disabled="mode === 'show'"
/> />
<template #panel="{ close }"> <template #panel="{ close }">
@@ -301,10 +330,11 @@ const findIncomingInvoiceErrors = computed(() => {
icon="i-heroicons-calendar-days-20-solid" icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.dueDate ? dayjs(itemInfo.dueDate).format('DD.MM.YYYY') : 'Datum auswählen'" :label="itemInfo.dueDate ? dayjs(itemInfo.dueDate).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline" variant="outline"
:disabled="mode === 'show'"
/> />
<template #panel="{ close }"> <template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.dueDate" @close="close" /> <LazyDatePicker v-model="itemInfo.dueDate" @close="close"/>
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormGroup>
@@ -316,12 +346,14 @@ const findIncomingInvoiceErrors = computed(() => {
<USelectMenu <USelectMenu
:options="['Einzug','Kreditkarte','Überweisung','Sonstiges']" :options="['Einzug','Kreditkarte','Überweisung','Sonstiges']"
v-model="itemInfo.paymentType" v-model="itemInfo.paymentType"
:disabled="mode === 'show'"
/> />
</UFormGroup> </UFormGroup>
<UFormGroup label="Beschreibung:" > <UFormGroup label="Beschreibung:" >
<UTextarea <UTextarea
v-model="itemInfo.description" v-model="itemInfo.description"
:disabled="mode === 'show'"
/> />
</UFormGroup> </UFormGroup>
@@ -329,12 +361,14 @@ const findIncomingInvoiceErrors = computed(() => {
<UButton <UButton
:variant="!useNetMode ? 'solid' : 'outline'" :variant="!useNetMode ? 'solid' : 'outline'"
@click="changeNetMode(false)" @click="changeNetMode(false)"
:disabled="mode === 'show'"
> >
Brutto Brutto
</UButton> </UButton>
<UButton <UButton
:variant="useNetMode ? 'solid' : 'outline'" :variant="useNetMode ? 'solid' : 'outline'"
@click="changeNetMode(true)" @click="changeNetMode(true)"
:disabled="mode === 'show'"
> >
Netto Netto
</UButton> </UButton>
@@ -377,6 +411,7 @@ const findIncomingInvoiceErrors = computed(() => {
option-attribute="label" option-attribute="label"
value-attribute="id" value-attribute="id"
searchable searchable
:disabled="mode === 'show'"
:search-attributes="['label']" :search-attributes="['label']"
searchable-placeholder="Suche..." searchable-placeholder="Suche..."
v-model="item.account" v-model="item.account"
@@ -398,6 +433,7 @@ const findIncomingInvoiceErrors = computed(() => {
option-attribute="name" option-attribute="name"
value-attribute="id" value-attribute="id"
searchable searchable
:disabled="mode === 'show'"
:search-attributes="['label']" :search-attributes="['label']"
searchable-placeholder="Suche..." searchable-placeholder="Suche..."
v-model="item.costCentre" v-model="item.costCentre"
@@ -417,7 +453,7 @@ const findIncomingInvoiceErrors = computed(() => {
<UButton <UButton
variant="outline" variant="outline"
color="rose" color="rose"
v-if="item.costCentre" v-if="item.costCentre && mode !== 'show'"
icon="i-heroicons-x-mark" icon="i-heroicons-x-mark"
@click="item.costCentre = null" @click="item.costCentre = null"
/> />
@@ -431,11 +467,12 @@ const findIncomingInvoiceErrors = computed(() => {
<UInput <UInput
v-model="item.description" v-model="item.description"
class="flex-auto" class="flex-auto"
:disabled="mode === 'show'"
></UInput> ></UInput>
<UButton <UButton
variant="outline" variant="outline"
color="rose" color="rose"
v-if="item.description" v-if="item.description && mode !== 'show'"
icon="i-heroicons-x-mark" icon="i-heroicons-x-mark"
@click="item.description = null" @click="item.description = null"
/> />
@@ -457,7 +494,7 @@ const findIncomingInvoiceErrors = computed(() => {
step="0.01" step="0.01"
v-model="item.amountNet" v-model="item.amountNet"
:color="!item.amountNet ? 'rose' : 'primary'" :color="!item.amountNet ? 'rose' : 'primary'"
:disabled="item.taxType === null" :disabled="item.taxType === null || mode === 'show'"
@keyup="item.amountTax = Number((item.amountNet * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)), @keyup="item.amountTax = Number((item.amountNet * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2)),
item.amountGross = Number(item.amountNet) + NUmber(item.amountTax)" item.amountGross = Number(item.amountNet) + NUmber(item.amountTax)"
> >
@@ -476,7 +513,7 @@ const findIncomingInvoiceErrors = computed(() => {
<UInput <UInput
type="number" type="number"
step="0.01" step="0.01"
:disabled="item.taxType === null" :disabled="item.taxType === null || mode === 'show'"
v-model="item.amountGross" v-model="item.amountGross"
:color="!item.amountGross ? 'rose' : 'primary'" :color="!item.amountGross ? 'rose' : 'primary'"
:ui-menu="{ width: 'min-w-max' }" :ui-menu="{ width: 'min-w-max' }"
@@ -496,6 +533,8 @@ const findIncomingInvoiceErrors = computed(() => {
> >
<USelectMenu <USelectMenu
:options="taxOptions" :options="taxOptions"
:disabled="mode === 'show'"
:color="item.taxType === null || item.taxType === '0' ? 'rose' : 'primary'"
v-model="item.taxType" v-model="item.taxType"
value-attribute="key" value-attribute="key"
:ui-menu="{ width: 'min-w-max' }" :ui-menu="{ width: 'min-w-max' }"
@@ -513,12 +552,13 @@ const findIncomingInvoiceErrors = computed(() => {
<UButton <UButton
class="mt-3" class="mt-3"
v-if="mode !== 'show'"
@click="itemInfo.accounts = [...itemInfo.accounts.slice(0,index+1),{account:null, amountNet: null, amountTax:null, taxType: '19'} , ...itemInfo.accounts.slice(index+1)]" @click="itemInfo.accounts = [...itemInfo.accounts.slice(0,index+1),{account:null, amountNet: null, amountTax:null, taxType: '19'} , ...itemInfo.accounts.slice(index+1)]"
> >
Betrag aufteilen Betrag aufteilen
</UButton> </UButton>
<UButton <UButton
v-if="index !== 0" v-if="index !== 0 && mode !== 'show'"
class="mt-3" class="mt-3"
variant="ghost" variant="ghost"
color="rose" color="rose"
@@ -532,6 +572,7 @@ const findIncomingInvoiceErrors = computed(() => {
</div> </div>
</div> </div>
</div> </div>
<UProgress v-else animation="carousel"/>
</UDashboardPanelContent> </UDashboardPanelContent>
@@ -549,8 +590,6 @@ const findIncomingInvoiceErrors = computed(() => {
.scrollContainer { .scrollContainer {
overflow-y: scroll; overflow-y: scroll;
padding-left: 1em;
padding-right: 1em;
height: 70vh; height: 70vh;
-ms-overflow-style: none; /* IE and Edge */ -ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */ scrollbar-width: none; /* Firefox */

View File

@@ -1,163 +0,0 @@
<script setup>
import dayjs from "dayjs";
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
//Working
const mode = ref(route.params.mode || "show")
const itemInfo = ref({
vendor: 0,
expense: true,
reference: "",
date: null,
dueDate: null,
paymentType: "Überweisung",
description: "",
state: "Entwurf",
accounts: [
{
account: null,
amountNet: null,
amountTax: null,
taxType: "19",
costCentre: null
}
]
})
//Functions
const currentDocument = ref(null)
const loading = ref(true)
const setupPage = async () => {
if((mode.value === "show") && route.params.id){
itemInfo.value = await useEntities("incominginvoices").selectSingle(route.params.id,"*, files(*), vendor(*)")
if(process.dev) console.log(itemInfo.value)
currentDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
}
loading.value = false
}
const setState = async (newState) => {
let item = itemInfo.value
delete item.files
if(item.vendor.id) item.vendor = item.vendor.id
item.state = newState
await useEntities('incominginvoices').update(route.params.id,item)
await router.push("/incomingInvoices")
}
setupPage()
</script>
<template>
<UDashboardNavbar :title="'Eingangsbeleg anzeigen'">
<template #left>
<UButton
to="/incominginvoices"
icon="i-heroicons-chevron-left"
variant="outline"
>
Übersicht
</UButton>
</template>
<template #right>
<UButton
@click="router.push(`/incomingInvoices/edit/${itemInfo.id}`)"
v-if="itemInfo.state !== 'Gebucht'"
>
Bearbeiten
</UButton>
<UButton
@click="setState('Gebucht')"
v-if="itemInfo.state !== 'Gebucht'"
color="rose"
>
Status auf Gebucht
</UButton>
</template>
</UDashboardNavbar>
<UDashboardPanelContent v-if="!loading">
<div
class="flex justify-between mt-5"
>
<object
v-if="currentDocument ? currentDocument.url : false"
:data="currentDocument.url + '#toolbar=0&navpanes=0&scrollbar=0&statusbar=0&messages=0&scrollbar=0'"
type="application/pdf"
class="mx-5 w-2/5 documentPreview"
/>
<div class="w-1/2 mx-5">
<UCard class="truncate mb-5">
<p>Status: {{itemInfo.state}}</p>
<p>Datum: {{dayjs(itemInfo.date).format('DD.MM.YYYY')}}</p>
<p>Fälligkeitsdatum: {{dayjs(itemInfo.dueDate).format('DD.MM.YYYY')}}</p>
<p v-if="itemInfo.vendor">Lieferant: <nuxt-link :to="`/standardEntity/vendors/show/${itemInfo.vendor.id}`">{{itemInfo.vendor.name}}</nuxt-link></p>
<p>Bezahlt: {{itemInfo.paid ? "Ja" : "Nein"}}</p>
<p>Beschreibung: {{itemInfo.description}}</p>
<!-- TODO: Buchungszeilen darstellen -->
</UCard>
<UCard class="scrollContainer">
<HistoryDisplay
type="incomingInvoice"
v-if="itemInfo"
:element-id="itemInfo.id"
render-headline
/>
</UCard>
</div>
</div>
</UDashboardPanelContent>
<UProgress class="mt-3 mx-3" v-else animation="carousel"/>
</template>
<style scoped>
.documentPreview {
aspect-ratio: 1 / 1.414;
}
.scrollContainer {
overflow-y: scroll;
padding-left: 1em;
padding-right: 1em;
height: 75vh;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.scrollContainer::-webkit-scrollbar {
display: none;
}
.lineItemRow {
display: flex;
flex-direction: row;
}
</style>

View File

@@ -3,7 +3,7 @@ const { $dayjs } = useNuxtApp()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const auth = useAuthStore() const auth = useAuthStore()
const toast = useToast()
// 🔹 State // 🔹 State
const workingtimes = ref([]) const workingtimes = ref([])
const absencerequests = ref([]) const absencerequests = ref([])
@@ -81,10 +81,32 @@ async function generateDocument() {
uri.value = await useFunctions().useCreatePDF({ uri.value = await useFunctions().useCreatePDF({
full_name: profile.value.full_name, full_name: profile.value.full_name,
employee_number: profile.value.employee_number ? profile.value.employee_number : "-",
...workingTimeInfo.value}, path, "timesheet") ...workingTimeInfo.value}, path, "timesheet")
showDocument.value = true showDocument.value = true
}
const fileSaved = ref(false)
async function saveFile() {
try {
let fileData = {
auth_profile: profile.value.id,
tenant: auth.activeTenant
}
let file = useFiles().dataURLtoFile(uri.value, `${profile.value.full_name}-${$dayjs(selectedStartDay.value).format("YYYY-MM-DD")}-${$dayjs(selectedEndDay.value).format("YYYY-MM-DD")}.pdf`)
await useFiles().uploadFiles(fileData, [file])
toast.add({title:"Auswertung erfolgreich gespeichert"})
fileSaved.value = true
} catch (error) {
toast.add({title:"Fehler beim Speichern der Auswertung", color: "rose"})
}
} }
async function onTabChange(index: number) { async function onTabChange(index: number) {
@@ -157,6 +179,19 @@ changeRange()
</UPopover> </UPopover>
</UFormGroup> </UFormGroup>
</template> </template>
<template #right>
<UTooltip
:text="fileSaved ? 'Bericht bereits gespeichert' : 'Bericht speichern'"
v-if="openTab === 1 && uri"
>
<UButton
icon="i-mdi-content-save"
:disabled="fileSaved"
@click="saveFile"
>Bericht</UButton>
</UTooltip>
</template>
</UDashboardToolbar> </UDashboardToolbar>
<UDashboardPanelContent> <UDashboardPanelContent>