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:
@@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import CopyCreatedDocumentModal from "~/components/copyCreatedDocumentModal.vue";
|
import CopyCreatedDocumentModal from "~/components/copyCreatedDocumentModal.vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -20,11 +21,12 @@ const itemInfo = ref({})
|
|||||||
const linkedDocument =ref({})
|
const linkedDocument =ref({})
|
||||||
const links = ref([])
|
const links = ref([])
|
||||||
const portalReleaseLoading = ref(false)
|
const portalReleaseLoading = ref(false)
|
||||||
|
const bankBookingModalOpen = ref(false)
|
||||||
const portalEligibleTypes = ["invoices", "advanceInvoices", "cancellationInvoices"]
|
const portalEligibleTypes = ["invoices", "advanceInvoices", "cancellationInvoices"]
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
if(route.params) {
|
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"){
|
if(itemInfo.value.type === "invoices"){
|
||||||
const createddocuments = await useEntities("createddocuments").select()
|
const createddocuments = await useEntities("createddocuments").select()
|
||||||
@@ -39,6 +41,27 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
setupPage()
|
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 = () => {
|
const openEmail = () => {
|
||||||
if(["invoices","advanceInvoices"].includes(itemInfo.value.type)){
|
if(["invoices","advanceInvoices"].includes(itemInfo.value.type)){
|
||||||
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]&bcc=${encodeURIComponent(auth.activeTenantData.standardEmailForInvoices || "")}`)
|
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]&bcc=${encodeURIComponent(auth.activeTenantData.standardEmailForInvoices || "")}`)
|
||||||
@@ -48,10 +71,14 @@ const openEmail = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const openBankstatements = () => {
|
const openBankstatements = () => {
|
||||||
if(itemInfo.value.statementallocations.length > 1) {
|
const statementIds = (itemInfo.value.statementallocations || []).map(getStatementId).filter(Boolean)
|
||||||
navigateTo(`/banking/?filter=${JSON.stringify(itemInfo.value.statementallocations.map(i => i.bankstatement))}`)
|
|
||||||
|
if(statementIds.length === 0) return
|
||||||
|
|
||||||
|
if(statementIds.length > 1) {
|
||||||
|
navigateTo(`/banking/?filter=${JSON.stringify(statementIds)}`)
|
||||||
} else {
|
} else {
|
||||||
navigateTo(`/banking/statements/edit/${itemInfo.value.statementallocations[0].bankstatement}`)
|
navigateTo(`/banking/statements/edit/${statementIds[0]}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,9 +229,63 @@ const togglePortalRelease = async () => {
|
|||||||
>
|
>
|
||||||
Bankbuchungen
|
Bankbuchungen
|
||||||
</UButton>
|
</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>
|
</template>
|
||||||
</UDashboardToolbar>
|
</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>
|
<UDashboardPanelContent>
|
||||||
<div
|
<div
|
||||||
v-if="portalEligibleTypes.includes(itemInfo.type) && itemInfo.state !== 'Entwurf'"
|
v-if="portalEligibleTypes.includes(itemInfo.type) && itemInfo.state !== 'Entwurf'"
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const setup = async () => {
|
|||||||
vendors.value = await useEntities("vendors").select()
|
vendors.value = await useEntities("vendors").select()
|
||||||
accounts.value = await useEntities("accounts").selectSpecial()
|
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
|
// 2. Mapping
|
||||||
itemInfo.value = {
|
itemInfo.value = {
|
||||||
@@ -129,6 +129,34 @@ const setDateFieldToToday = (field) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getDateButtonLabel = (value, emptyLabel = "Kein Datum") => value ? dayjs(value).format('DD.MM.YYYY') : emptyLabel
|
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(() => {
|
const totalCalculated = computed(() => {
|
||||||
let totalNet = 0
|
let totalNet = 0
|
||||||
@@ -329,7 +357,7 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
|
|||||||
</UButton>
|
</UButton>
|
||||||
|
|
||||||
<UAlert
|
<UAlert
|
||||||
v-if="findIncomingInvoiceErrors.length > 0"
|
v-if="mode !== 'show' && findIncomingInvoiceErrors.length > 0"
|
||||||
title="Prüfung erforderlich"
|
title="Prüfung erforderlich"
|
||||||
:color="hasBlockingIncomingInvoiceErrors ? 'error' : 'orange'"
|
:color="hasBlockingIncomingInvoiceErrors ? 'error' : 'orange'"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
@@ -363,7 +391,79 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
|
|||||||
</template>
|
</template>
|
||||||
</UAlert>
|
</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>
|
<template #header>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h3 class="font-semibold">Stammdaten</h3>
|
<h3 class="font-semibold">Stammdaten</h3>
|
||||||
@@ -492,7 +592,54 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
|
|||||||
</div>
|
</div>
|
||||||
</UCard>
|
</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">
|
<div class="flex justify-between items-end border-b pb-2 dark:border-gray-700">
|
||||||
<h3 class="font-semibold text-lg">Positionen</h3>
|
<h3 class="font-semibold text-lg">Positionen</h3>
|
||||||
<div class="flex items-center gap-2 text-sm">
|
<div class="flex items-center gap-2 text-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user