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"
></UButton>
<UButton
v-if="props.fileId"
@click="handleDownloadFile"
variant="outline"
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) {
errors.push({message: "Es sind keine Positionen angegeben", type: "breaking"})
} else {
itemInfo.value.rows.forEach(row => {
itemInfo.value.rows.forEach((row,index) => {
if (itemInfo.value.type !== "quotes" && row.optional) {
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 accounts = ref([])
const mode = ref(route.params.mode)
const setup = async () => {
let filetype = (await useEntities("filetags").select()).find(i=> i.incomingDocumentType === "invoices").id
console.log(filetype)
@@ -43,6 +45,8 @@ const setup = async () => {
itemInfo.value = await useEntities("incominginvoices").selectSingle(route.params.id, "*, files(*)")
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()
@@ -143,7 +147,7 @@ const findIncomingInvoiceErrors = computed(() => {
let errors = []
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.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"})
@@ -168,21 +172,38 @@ const findIncomingInvoiceErrors = computed(() => {
</script>
<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>
<ArchiveButton
color="rose"
variant="outline"
type="incominginvoices"
@confirmed="useEntities('incominginvoices').archive(route.params.id)"
v-if="mode !== 'show'"
/>
<UButton
@click="updateIncomingInvoice(false)"
v-if="mode !== 'show'"
>
Speichern
</UButton>
<UButton
@click="updateIncomingInvoice(true)"
v-if="mode !== 'show'"
:disabled="findIncomingInvoiceErrors.filter(i => i.type === 'breaking').length > 0"
>
Speichern & Buchen
@@ -192,6 +213,7 @@ const findIncomingInvoiceErrors = computed(() => {
<UDashboardPanelContent>
<div
class="flex justify-between mt-5 workingContainer"
v-if="loadedFile"
>
<object
v-if="loadedFile"
@@ -200,7 +222,6 @@ const findIncomingInvoiceErrors = computed(() => {
class="mx-5 documentPreview"
/>
<div class="w-3/5 mx-5">
<UAlert
class="mb-5"
title="Vorhandene Probleme und Informationen:"
@@ -218,17 +239,19 @@ const findIncomingInvoiceErrors = computed(() => {
</UAlert>
<div class=" scrollContainer">
<div class="scrollContainer">
<InputGroup class="mb-3">
<UButton
:variant="itemInfo.expense ? 'solid' : 'outline'"
@click="itemInfo.expense = true"
:disabled="mode === 'show'"
>
Ausgabe
</UButton>
<UButton
:variant="!itemInfo.expense ? 'solid' : 'outline'"
@click="itemInfo.expense = false"
:disabled="mode === 'show'"
>
Einnahme
</UButton>
@@ -237,6 +260,7 @@ const findIncomingInvoiceErrors = computed(() => {
<UFormGroup label="Lieferant:" >
<InputGroup>
<USelectMenu
:disabled="mode === 'show'"
v-model="itemInfo.vendor"
:options="vendors"
option-attribute="name"
@@ -258,12 +282,15 @@ const findIncomingInvoiceErrors = computed(() => {
type="vendors"
:id="itemInfo.vendor"
@return-data="(data) => itemInfo.vendor = data.id"
:button-edit="mode !== 'show'"
:button-create="mode !== 'show'"
/>
<UButton
icon="i-heroicons-x-mark"
variant="outline"
color="rose"
@click="itemInfo.vendor = null"
v-if="itemInfo.vendor && mode !== 'show'"
/>
</InputGroup>
@@ -276,6 +303,7 @@ const findIncomingInvoiceErrors = computed(() => {
>
<UInput
v-model="itemInfo.reference"
:disabled="mode === 'show'"
/>
</UFormGroup>
@@ -287,6 +315,7 @@ const findIncomingInvoiceErrors = computed(() => {
:label="itemInfo.date ? dayjs(itemInfo.date).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
:color="!itemInfo.date ? 'rose' : 'primary'"
:disabled="mode === 'show'"
/>
<template #panel="{ close }">
@@ -301,10 +330,11 @@ const findIncomingInvoiceErrors = computed(() => {
icon="i-heroicons-calendar-days-20-solid"
:label="itemInfo.dueDate ? dayjs(itemInfo.dueDate).format('DD.MM.YYYY') : 'Datum auswählen'"
variant="outline"
:disabled="mode === 'show'"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.dueDate" @close="close" />
<LazyDatePicker v-model="itemInfo.dueDate" @close="close"/>
</template>
</UPopover>
</UFormGroup>
@@ -316,12 +346,14 @@ const findIncomingInvoiceErrors = computed(() => {
<USelectMenu
:options="['Einzug','Kreditkarte','Überweisung','Sonstiges']"
v-model="itemInfo.paymentType"
:disabled="mode === 'show'"
/>
</UFormGroup>
<UFormGroup label="Beschreibung:" >
<UTextarea
v-model="itemInfo.description"
:disabled="mode === 'show'"
/>
</UFormGroup>
@@ -329,12 +361,14 @@ const findIncomingInvoiceErrors = computed(() => {
<UButton
:variant="!useNetMode ? 'solid' : 'outline'"
@click="changeNetMode(false)"
:disabled="mode === 'show'"
>
Brutto
</UButton>
<UButton
:variant="useNetMode ? 'solid' : 'outline'"
@click="changeNetMode(true)"
:disabled="mode === 'show'"
>
Netto
</UButton>
@@ -377,6 +411,7 @@ const findIncomingInvoiceErrors = computed(() => {
option-attribute="label"
value-attribute="id"
searchable
:disabled="mode === 'show'"
:search-attributes="['label']"
searchable-placeholder="Suche..."
v-model="item.account"
@@ -398,6 +433,7 @@ const findIncomingInvoiceErrors = computed(() => {
option-attribute="name"
value-attribute="id"
searchable
:disabled="mode === 'show'"
:search-attributes="['label']"
searchable-placeholder="Suche..."
v-model="item.costCentre"
@@ -417,7 +453,7 @@ const findIncomingInvoiceErrors = computed(() => {
<UButton
variant="outline"
color="rose"
v-if="item.costCentre"
v-if="item.costCentre && mode !== 'show'"
icon="i-heroicons-x-mark"
@click="item.costCentre = null"
/>
@@ -431,11 +467,12 @@ const findIncomingInvoiceErrors = computed(() => {
<UInput
v-model="item.description"
class="flex-auto"
:disabled="mode === 'show'"
></UInput>
<UButton
variant="outline"
color="rose"
v-if="item.description"
v-if="item.description && mode !== 'show'"
icon="i-heroicons-x-mark"
@click="item.description = null"
/>
@@ -457,7 +494,7 @@ const findIncomingInvoiceErrors = computed(() => {
step="0.01"
v-model="item.amountNet"
: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)),
item.amountGross = Number(item.amountNet) + NUmber(item.amountTax)"
>
@@ -476,7 +513,7 @@ const findIncomingInvoiceErrors = computed(() => {
<UInput
type="number"
step="0.01"
:disabled="item.taxType === null"
:disabled="item.taxType === null || mode === 'show'"
v-model="item.amountGross"
:color="!item.amountGross ? 'rose' : 'primary'"
:ui-menu="{ width: 'min-w-max' }"
@@ -496,6 +533,8 @@ const findIncomingInvoiceErrors = computed(() => {
>
<USelectMenu
:options="taxOptions"
:disabled="mode === 'show'"
:color="item.taxType === null || item.taxType === '0' ? 'rose' : 'primary'"
v-model="item.taxType"
value-attribute="key"
:ui-menu="{ width: 'min-w-max' }"
@@ -513,12 +552,13 @@ const findIncomingInvoiceErrors = computed(() => {
<UButton
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)]"
>
Betrag aufteilen
</UButton>
<UButton
v-if="index !== 0"
v-if="index !== 0 && mode !== 'show'"
class="mt-3"
variant="ghost"
color="rose"
@@ -532,6 +572,7 @@ const findIncomingInvoiceErrors = computed(() => {
</div>
</div>
</div>
<UProgress v-else animation="carousel"/>
</UDashboardPanelContent>
@@ -549,8 +590,6 @@ const findIncomingInvoiceErrors = computed(() => {
.scrollContainer {
overflow-y: scroll;
padding-left: 1em;
padding-right: 1em;
height: 70vh;
-ms-overflow-style: none; /* IE and Edge */
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 route = useRoute()
const auth = useAuthStore()
const toast = useToast()
// 🔹 State
const workingtimes = ref([])
const absencerequests = ref([])
@@ -81,10 +81,32 @@ async function generateDocument() {
uri.value = await useFunctions().useCreatePDF({
full_name: profile.value.full_name,
employee_number: profile.value.employee_number ? profile.value.employee_number : "-",
...workingTimeInfo.value}, path, "timesheet")
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) {
@@ -157,6 +179,19 @@ changeRange()
</UPopover>
</UFormGroup>
</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>
<UDashboardPanelContent>