Belegansichten um Bankbuchungsdatum erweitern #110

Zeigt Bankbuchungsdaten in Ausgangsbelegen direkt an und ergänzt Bankdetails im Modal. Überarbeitet die Eingangsbeleg-Showansicht zu einer echten Lesedarstellung mit Bankbuchungsdaten und Positionsübersicht.
This commit is contained in:
2026-05-11 18:04:05 +02:00
parent aa162dcad3
commit 94ab3350ec
2 changed files with 236 additions and 8 deletions

View File

@@ -1,5 +1,6 @@
<script setup>
import CopyCreatedDocumentModal from "~/components/copyCreatedDocumentModal.vue";
import dayjs from "dayjs";
@@ -20,11 +21,12 @@ const itemInfo = ref({})
const linkedDocument =ref({})
const links = ref([])
const portalReleaseLoading = ref(false)
const bankBookingModalOpen = ref(false)
const portalEligibleTypes = ["invoices", "advanceInvoices", "cancellationInvoices"]
const setupPage = async () => {
if(route.params) {
if(route.params.id) itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id,"*,files(*),linkedDocument(*), statementallocations(bs_id)")
if(route.params.id) itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id,"*,files(*),linkedDocument(*), statementallocations(*, bs_id(*))")
if(itemInfo.value.type === "invoices"){
const createddocuments = await useEntities("createddocuments").select()
@@ -39,6 +41,27 @@ const setupPage = async () => {
setupPage()
const getStatementLike = (allocation) => allocation?.bankstatement || (typeof allocation?.bs_id === "object" ? allocation.bs_id : null)
const getStatementId = (allocation) => allocation?.bankstatement?.id || allocation?.bs_id?.id || allocation?.bankstatement || allocation?.bs_id || null
const getBankBookingDate = (allocation) => {
const statement = getStatementLike(allocation)
return statement?.date || statement?.valueDate || allocation?.manualBookingDate || null
}
const formatDate = (value) => value ? dayjs(value).format("DD.MM.YYYY") : "-"
const formatCurrency = (value) => `${Number(value || 0).toFixed(2).replace(".", ",")} EUR`
const bankBookingDates = computed(() => [...new Set(
(itemInfo.value.statementallocations || [])
.map(getBankBookingDate)
.filter(Boolean)
)].sort())
const bankBookingDateLabel = computed(() => {
if (bankBookingDates.value.length === 0) return "Kein Bankbuchungsdatum"
if (bankBookingDates.value.length === 1) return formatDate(bankBookingDates.value[0])
return bankBookingDates.value.map(formatDate).join(", ")
})
const openEmail = () => {
if(["invoices","advanceInvoices"].includes(itemInfo.value.type)){
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]&bcc=${encodeURIComponent(auth.activeTenantData.standardEmailForInvoices || "")}`)
@@ -48,10 +71,14 @@ const openEmail = () => {
}
const openBankstatements = () => {
if(itemInfo.value.statementallocations.length > 1) {
navigateTo(`/banking/?filter=${JSON.stringify(itemInfo.value.statementallocations.map(i => i.bankstatement))}`)
const statementIds = (itemInfo.value.statementallocations || []).map(getStatementId).filter(Boolean)
if(statementIds.length === 0) return
if(statementIds.length > 1) {
navigateTo(`/banking/?filter=${JSON.stringify(statementIds)}`)
} else {
navigateTo(`/banking/statements/edit/${itemInfo.value.statementallocations[0].bankstatement}`)
navigateTo(`/banking/statements/edit/${statementIds[0]}`)
}
}
@@ -202,9 +229,63 @@ const togglePortalRelease = async () => {
>
Bankbuchungen
</UButton>
<UBadge
v-if="itemInfo.statementallocations?.length > 0"
color="primary"
variant="soft"
class="self-center"
>
Bankbuchungsdatum: {{ bankBookingDateLabel }}
</UBadge>
<UButton
v-if="itemInfo.statementallocations?.length > 0"
icon="i-heroicons-calendar-days"
variant="outline"
@click="bankBookingModalOpen = true"
>
Bankdetails
</UButton>
</template>
</UDashboardToolbar>
<UModal v-model:open="bankBookingModalOpen" :ui="{ content: 'sm:max-w-2xl' }">
<template #content>
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3 class="font-semibold">Bankbuchungen</h3>
<UButton
icon="i-heroicons-x-mark"
color="gray"
variant="ghost"
@click="bankBookingModalOpen = false"
/>
</div>
</template>
<div class="space-y-3">
<div
v-for="allocation in itemInfo.statementallocations"
:key="allocation.id"
class="grid grid-cols-1 gap-2 rounded-md border border-gray-200 p-3 text-sm dark:border-gray-800 md:grid-cols-3"
>
<div>
<p class="text-xs text-gray-500">Bankbuchungsdatum</p>
<p class="font-medium">{{ formatDate(getBankBookingDate(allocation)) }}</p>
</div>
<div>
<p class="text-xs text-gray-500">Betrag</p>
<p class="font-medium">{{ formatCurrency(allocation.amount) }}</p>
</div>
<div>
<p class="text-xs text-gray-500">Text</p>
<p class="font-medium">{{ getStatementLike(allocation)?.text || allocation.description || "-" }}</p>
</div>
</div>
</div>
</UCard>
</template>
</UModal>
<UDashboardPanelContent>
<div
v-if="portalEligibleTypes.includes(itemInfo.type) && itemInfo.state !== 'Entwurf'"

View File

@@ -56,7 +56,7 @@ const setup = async () => {
vendors.value = await useEntities("vendors").select()
accounts.value = await useEntities("accounts").selectSpecial()
const invoiceData = await useEntities("incominginvoices").selectSingle(route.params.id, "*, files(*)")
const invoiceData = await useEntities("incominginvoices").selectSingle(route.params.id, "*, files(*), statementallocations(*, bs_id(*))")
// 2. Mapping
itemInfo.value = {
@@ -129,6 +129,34 @@ const setDateFieldToToday = (field) => {
}
const getDateButtonLabel = (value, emptyLabel = "Kein Datum") => value ? dayjs(value).format('DD.MM.YYYY') : emptyLabel
const formatDate = (value) => value ? dayjs(value).format("DD.MM.YYYY") : "-"
const formatMoney = (value) => `${Number(value || 0).toFixed(2).replace(".", ",")}`
const getStatementLike = (allocation) => allocation?.bankstatement || (typeof allocation?.bs_id === "object" ? allocation.bs_id : null)
const getBankBookingDate = (allocation) => {
const statement = getStatementLike(allocation)
return statement?.date || statement?.valueDate || allocation?.manualBookingDate || null
}
const bankBookingDates = computed(() => [...new Set(
(itemInfo.value.statementallocations || [])
.map(getBankBookingDate)
.filter(Boolean)
)].sort())
const bankBookingDateLabel = computed(() => {
if (bankBookingDates.value.length === 0) return "Nicht zugewiesen"
if (bankBookingDates.value.length === 1) return formatDate(bankBookingDates.value[0])
return bankBookingDates.value.map(formatDate).join(", ")
})
const vendorName = computed(() => vendors.value.find((vendor) => vendor.id === itemInfo.value.vendor)?.name || "-")
const getAccountLabel = (item) => {
const account = accounts.value.find((entry) => entry.id === item.account)
return account ? `${account.number || ""} ${account.label || ""}`.trim() : "-"
}
const getCostCentreLabel = (item) => costcentres.value.find((costcentre) => costcentre.id === item.costCentre)?.name || "-"
const getBookingModeLabel = (item) => EXPENSE_BOOKING_MODE_ITEMS.find((mode) => mode.value === item.bookingMode)?.label || "-"
const getTaxLabel = (item) => taxOptions.value.find((tax) => tax.key === item.taxType)?.label || "-"
const totalCalculated = computed(() => {
let totalNet = 0
@@ -329,7 +357,7 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
</UButton>
<UAlert
v-if="findIncomingInvoiceErrors.length > 0"
v-if="mode !== 'show' && findIncomingInvoiceErrors.length > 0"
title="Prüfung erforderlich"
:color="hasBlockingIncomingInvoiceErrors ? 'error' : 'orange'"
variant="soft"
@@ -363,7 +391,79 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
</template>
</UAlert>
<UCard>
<div v-if="mode === 'show'" class="space-y-6">
<div class="rounded-md border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-gray-900">
<div class="mb-4 flex items-start justify-between gap-4">
<div>
<p class="text-sm text-gray-500">Eingangsbeleg</p>
<h2 class="text-xl font-semibold">{{ itemInfo.reference || "-" }}</h2>
</div>
<UBadge :color="itemInfo.state === 'Gebucht' ? 'primary' : 'neutral'" variant="soft">
{{ itemInfo.state || "Entwurf" }}
</UBadge>
</div>
<dl class="grid grid-cols-1 gap-4 text-sm md:grid-cols-2">
<div>
<dt class="text-gray-500">Lieferant / Partner</dt>
<dd class="font-medium">{{ vendorName }}</dd>
</div>
<div>
<dt class="text-gray-500">Zahlart</dt>
<dd class="font-medium">{{ itemInfo.paymentType || "-" }}</dd>
</div>
<div>
<dt class="text-gray-500">Rechnungsdatum</dt>
<dd class="font-medium">{{ formatDate(itemInfo.date) }}</dd>
</div>
<div>
<dt class="text-gray-500">Fälligkeitsdatum</dt>
<dd class="font-medium">{{ formatDate(itemInfo.dueDate) }}</dd>
</div>
<div>
<dt class="text-gray-500">Bankbuchungsdatum</dt>
<dd class="font-medium">{{ bankBookingDateLabel }}</dd>
</div>
<div>
<dt class="text-gray-500">Art</dt>
<dd class="font-medium">{{ itemInfo.expense ? "Ausgabe" : "Einnahme" }}</dd>
</div>
<div class="md:col-span-2">
<dt class="text-gray-500">Beschreibung / Notiz</dt>
<dd class="font-medium whitespace-pre-wrap">{{ itemInfo.description || "-" }}</dd>
</div>
</dl>
</div>
<div
v-if="itemInfo.statementallocations?.length > 0"
class="rounded-md border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-gray-900"
>
<h3 class="mb-4 font-semibold">Bankbuchungen</h3>
<div class="space-y-3">
<div
v-for="allocation in itemInfo.statementallocations"
:key="allocation.id"
class="grid grid-cols-1 gap-2 rounded-md border border-gray-200 p-3 text-sm dark:border-gray-800 md:grid-cols-3"
>
<div>
<p class="text-xs text-gray-500">Bankbuchungsdatum</p>
<p class="font-medium">{{ formatDate(getBankBookingDate(allocation)) }}</p>
</div>
<div>
<p class="text-xs text-gray-500">Betrag</p>
<p class="font-medium">{{ formatMoney(allocation.amount) }}</p>
</div>
<div>
<p class="text-xs text-gray-500">Text</p>
<p class="font-medium">{{ getStatementLike(allocation)?.text || allocation.description || "-" }}</p>
</div>
</div>
</div>
</div>
</div>
<UCard v-if="mode !== 'show'">
<template #header>
<div class="flex justify-between items-center">
<h3 class="font-semibold">Stammdaten</h3>
@@ -492,7 +592,54 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
</div>
</UCard>
<div class="space-y-4">
<div v-if="mode === 'show'" class="space-y-4">
<div class="flex justify-between items-end border-b pb-2 dark:border-gray-700">
<h3 class="font-semibold text-lg">Positionen</h3>
</div>
<div
v-for="(item, index) in itemInfo.accounts"
:key="`show-${index}`"
class="rounded-md border border-gray-200 bg-white p-4 text-sm dark:border-gray-800 dark:bg-gray-900"
>
<div class="mb-3 flex items-start justify-between gap-4">
<div>
<p class="text-xs text-gray-500">Position {{ index + 1 }}</p>
<p class="font-medium">{{ item.description || "Ohne Positionstext" }}</p>
</div>
<UBadge variant="soft" color="neutral">{{ getBookingModeLabel(item) }}</UBadge>
</div>
<dl class="grid grid-cols-1 gap-3 md:grid-cols-3">
<div>
<dt class="text-gray-500">Konto / Kategorie</dt>
<dd class="font-medium">{{ getAccountLabel(item) }}</dd>
</div>
<div>
<dt class="text-gray-500">Kostenstelle</dt>
<dd class="font-medium">{{ getCostCentreLabel(item) }}</dd>
</div>
<div>
<dt class="text-gray-500">Steuerschlüssel</dt>
<dd class="font-medium">{{ getTaxLabel(item) }}</dd>
</div>
<div>
<dt class="text-gray-500">Netto</dt>
<dd class="font-medium">{{ formatMoney(item.amountNet) }}</dd>
</div>
<div>
<dt class="text-gray-500">Steuerbetrag</dt>
<dd class="font-medium">{{ formatMoney(item.amountTax) }}</dd>
</div>
<div>
<dt class="text-gray-500">Brutto</dt>
<dd class="font-medium">{{ formatMoney(item.amountGross) }}</dd>
</div>
</dl>
</div>
</div>
<div v-if="mode !== 'show'" class="space-y-4">
<div class="flex justify-between items-end border-b pb-2 dark:border-gray-700">
<h3 class="font-semibold text-lg">Positionen</h3>
<div class="flex items-center gap-2 text-sm">