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>
|
||||
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'"
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user