Compare commits

...

4 Commits

Author SHA1 Message Date
e5fab70056 Gebuchte Eingangsbelege bearbeitbar machen
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 35s
Build and Push Docker Images / build-central-services-api (push) Successful in 35s
Build and Push Docker Images / build-docs (push) Successful in 33s
Build and Push Docker Images / build-frontend (push) Successful in 1m54s
Build and Push Docker Images / build-website (push) Successful in 35s
Build and Push Docker Images / build-central-services-admin (push) Successful in 34s
2026-08-09 17:47:20 +02:00
e7fe98da56 Eingangsbelege ohne Bankzuweisung archivieren 2026-08-09 17:42:44 +02:00
e12ec4557d Reduce duplicate dashboard data loading 2026-08-09 17:39:41 +02:00
17717e8a1b Speed up initial file listing 2026-08-09 17:38:17 +02:00
6 changed files with 85 additions and 20 deletions

View File

@@ -6,9 +6,13 @@ let unallocatedStatements = ref(0)
let bankaccounts = ref([]) let bankaccounts = ref([])
const setupPage = async () => { const setupPage = async () => {
let bankstatements = (await useEntities("bankstatements").select("*, statementallocations(*)","date",true)).filter(i => !i.archived) const [statementItems, accountItems] = await Promise.all([
useEntities("bankstatements").select("*, statementallocations(*)", "date", true),
useEntities("bankaccounts").select()
])
let bankstatements = statementItems.filter(i => !i.archived)
unallocatedStatements.value = bankstatements.filter(i => Number(calculateOpenSum(i)) !== 0).length unallocatedStatements.value = bankstatements.filter(i => Number(calculateOpenSum(i)) !== 0).length
bankaccounts.value = await useEntities("bankaccounts").select() bankaccounts.value = accountItems
} }
setupPage() setupPage()
@@ -51,4 +55,4 @@ const calculateOpenSum = (statement) => {
<style scoped> <style scoped>
</style> </style>

View File

@@ -10,9 +10,11 @@ let draftInvoicesSum = ref(0)
let draftInvoicesCount = ref(0) let draftInvoicesCount = ref(0)
let countPreparedOpenIncomingInvoices = ref(0) let countPreparedOpenIncomingInvoices = ref(0)
const { loadCoreData } = useDashboardData()
const setupPage = async () => { const setupPage = async () => {
let items = (await useEntities("createddocuments").select("*, statementallocations(*), customer(id,name), linkedDocument(*)")).filter(i => !i.archived) const { createdDocuments, incomingInvoices } = await loadCoreData()
let items = createdDocuments.filter(i => !i.archived)
let documents = items.filter(i => i.type === "invoices" ||i.type === "advanceInvoices") let documents = items.filter(i => i.type === "invoices" ||i.type === "advanceInvoices")
let draftDocuments = documents.filter(i => i.state === "Entwurf") let draftDocuments = documents.filter(i => i.state === "Entwurf")
@@ -36,7 +38,7 @@ const setupPage = async () => {
}) })
draftInvoicesCount.value = draftDocuments.length draftInvoicesCount.value = draftDocuments.length
countPreparedOpenIncomingInvoices.value = (await useEntities("incominginvoices").select("id, state")).filter(i => i.state === "Vorbereitet" && !i.archived).length countPreparedOpenIncomingInvoices.value = incomingInvoices.filter(i => i.state === "Vorbereitet" && !i.archived).length
} }
setupPage() setupPage()

View File

@@ -32,7 +32,7 @@ export const useDashboardData = () => {
if (!force && cache.pendingRequest) return cache.pendingRequest if (!force && cache.pendingRequest) return cache.pendingRequest
cache.pendingRequest = Promise.all([ cache.pendingRequest = Promise.all([
useEntities("createddocuments").select(), useEntities("createddocuments").select("*, statementallocations(*), customer(id,name), linkedDocument(*)"),
useEntities("incominginvoices").select() useEntities("incominginvoices").select()
]) ])
.then(([createdDocuments, incomingInvoices]) => { .then(([createdDocuments, incomingInvoices]) => {

View File

@@ -77,7 +77,10 @@ export const useFiles = () => {
const selectDocument = async (id) => { const selectDocument = async (id) => {
let documentIds = [id] let documentIds = [id]
if(documentIds.length === 0) return [] if(documentIds.length === 0) return []
const fileData = await useEntities("files").selectSingle(id) const fileData = await useEntities("files").selectSingle(
id,
"*, incominginvoice(*), project(*), vendor(*), customer(*), contract(*), plant(*), createddocument(*), vehicle(*), product(*), profile(*), check(*), inventoryitem(*)"
)
const res = await useNuxtApp().$api("/api/files/presigned",{ const res = await useNuxtApp().$api("/api/files/presigned",{
method: "POST", method: "POST",
body: { body: {

View File

@@ -63,20 +63,24 @@ watch(searchString, (val) => {
const setupPage = async () => { const setupPage = async () => {
loadingDocs.value = true loadingDocs.value = true
try { try {
const [fRes, dRes, tRes] = await Promise.all([ const [fRes, dRes] = await Promise.all([
useEntities("folders").select(), useEntities("folders").select(),
files.selectDocuments(), useEntities("files").select("id, path, folder, type, createdAt")
useEntities("filetags").select()
]) ])
folders.value = fRes || [] folders.value = fRes || []
documents.value = dRes || [] documents.value = dRes || []
filetags.value = tRes || []
syncCurrentFolderFromRoute() syncCurrentFolderFromRoute()
} finally { } finally {
loadingDocs.value = false loadingDocs.value = false
loaded.value = true loaded.value = true
} }
useEntities("filetags").select()
.then((items) => {
filetags.value = items || []
})
.catch((error) => console.error("Dateitypen konnten nicht geladen werden:", error))
} }
// --- Global Drag & Drop (Auto-Open Upload Modal) --- // --- Global Drag & Drop (Auto-Open Upload Modal) ---
@@ -323,11 +327,22 @@ const updateName = async () => {
} }
} }
const showFile = (fileId) => { const showFile = async (fileId) => {
modal.open(DocumentDisplayModal, { loadingDocs.value = true
documentData: documents.value.find(i => i.id === fileId), try {
onUpdatedNeeded: () => setupPage() const documentData = await files.selectDocument(fileId)
}) if (!documentData) throw new Error("Datei nicht gefunden")
modal.open(DocumentDisplayModal, {
documentData,
onUpdatedNeeded: () => setupPage()
})
} catch (error) {
console.error(error)
toast.add({title: 'Datei konnte nicht geöffnet werden', color: 'red'})
} finally {
loadingDocs.value = false
}
} }
const openScanModal = () => { const openScanModal = () => {

View File

@@ -49,6 +49,11 @@ const loadedFileId = ref(null)
const invoiceFiles = ref([]) const invoiceFiles = ref([])
const paymentTypeItems = ['Überweisung', 'Lastschrift', 'Kreditkarte', 'PayPal', 'Bar', 'Sonstiges'] const paymentTypeItems = ['Überweisung', 'Lastschrift', 'Kreditkarte', 'PayPal', 'Bar', 'Sonstiges']
const files = useFiles() const files = useFiles()
const hasActiveBankAssignmentIn = (allocations = []) => allocations.some((allocation) => {
if (allocation?.archived) return false
return Boolean(allocation?.bankstatement || allocation?.bs_id)
})
const setup = async () => { const setup = async () => {
// 1. Daten laden // 1. Daten laden
@@ -65,6 +70,18 @@ const setup = async () => {
accounts: normalizeIncomingInvoiceAccounts(invoiceData.accounts || [], invoiceData.date) accounts: normalizeIncomingInvoiceAccounts(invoiceData.accounts || [], invoiceData.date)
} }
if (mode.value === "edit" && invoiceData.state === "Gebucht" && (invoiceData.archived || hasActiveBankAssignmentIn(invoiceData.statementallocations))) {
toast.add({
title: "Bearbeiten nicht möglich",
description: invoiceData.archived
? "Archivierte Eingangsbelege können nicht bearbeitet werden."
: "Der Eingangsbeleg ist bereits einer Bankbuchung zugewiesen.",
color: "error"
})
await navigateTo(`/incomingInvoices/show/${invoiceData.id}`)
return
}
// Fallback Accounts // Fallback Accounts
if(itemInfo.value.accounts.length === 0) { if(itemInfo.value.accounts.length === 0) {
itemInfo.value.accounts.push(createIncomingInvoiceAccount({ depreciationStartDate: itemInfo.value.date || null })) itemInfo.value.accounts.push(createIncomingInvoiceAccount({ depreciationStartDate: itemInfo.value.date || null }))
@@ -150,6 +167,20 @@ const bankBookingDateLabel = computed(() => {
return bankBookingDates.value.map(formatDate).join(", ") return bankBookingDates.value.map(formatDate).join(", ")
}) })
const hasActiveBankAssignment = computed(() => hasActiveBankAssignmentIn(itemInfo.value.statementallocations))
const isBookedIncomingInvoice = computed(() => itemInfo.value.state === "Gebucht")
const canEditIncomingInvoice = computed(() => (
mode.value === "show"
&& isBookedIncomingInvoice.value
&& !itemInfo.value.archived
&& !hasActiveBankAssignment.value
))
const canArchiveIncomingInvoice = computed(() => {
if (itemInfo.value.archived) return false
if (isBookedIncomingInvoice.value) return !hasActiveBankAssignment.value
return mode.value !== "show"
})
const vendorName = computed(() => vendors.value.find((vendor) => vendor.id === itemInfo.value.vendor)?.name || "-") const vendorName = computed(() => vendors.value.find((vendor) => vendor.id === itemInfo.value.vendor)?.name || "-")
const eInvoiceValidation = computed(() => itemInfo.value.eInvoiceValidation || null) const eInvoiceValidation = computed(() => itemInfo.value.eInvoiceValidation || null)
const eInvoiceSourceLabel = computed(() => { const eInvoiceSourceLabel = computed(() => {
@@ -248,7 +279,9 @@ const applyTotalTaxOverride = () => {
// --- Saving --- // --- Saving ---
const updateIncomingInvoice = async (setBooked = false) => { const updateIncomingInvoice = async (setBooked = false) => {
if (setBooked && hasBlockingIncomingInvoiceErrors.value) { const keepBooked = setBooked || isBookedIncomingInvoice.value
if (keepBooked && hasBlockingIncomingInvoiceErrors.value) {
toast.add({ toast.add({
title: "Buchen nicht möglich", title: "Buchen nicht möglich",
description: "Bitte beheben Sie zuerst die rot markierten Pflichtfehler.", description: "Bitte beheben Sie zuerst die rot markierten Pflichtfehler.",
@@ -260,7 +293,7 @@ const updateIncomingInvoice = async (setBooked = false) => {
let item = { ...itemInfo.value } let item = { ...itemInfo.value }
item.accounts = (item.accounts || []).map((account) => ensureDepreciationDefaults({ ...account }, item.date)) item.accounts = (item.accounts || []).map((account) => ensureDepreciationDefaults({ ...account }, item.date))
delete item.files delete item.files
item.state = setBooked ? "Gebucht" : "Entwurf" item.state = keepBooked ? "Gebucht" : "Entwurf"
await useEntities('incominginvoices').update(itemInfo.value.id, item, !setBooked) await useEntities('incominginvoices').update(itemInfo.value.id, item, !setBooked)
@@ -321,8 +354,16 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
</h1> </h1>
</template> </template>
<template #right> <template #right>
<UButton
v-if="canEditIncomingInvoice"
icon="i-heroicons-pencil-square"
variant="outline"
@click="navigateTo(`/incomingInvoices/edit/${route.params.id}`)"
>
Bearbeiten
</UButton>
<ArchiveButton <ArchiveButton
v-if="mode !== 'show'" v-if="canArchiveIncomingInvoice"
color="error" color="error"
variant="outline" variant="outline"
type="incominginvoices" type="incominginvoices"
@@ -332,7 +373,7 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
Speichern Speichern
</UButton> </UButton>
<UButton <UButton
v-if="mode !== 'show'" v-if="mode !== 'show' && !isBookedIncomingInvoice"
@click="updateIncomingInvoice(true)" @click="updateIncomingInvoice(true)"
:disabled="hasBlockingIncomingInvoiceErrors" :disabled="hasBlockingIncomingInvoiceErrors"
> >