Belege und Bankmuster in Liquiditätsprognose verwalten

Ermöglicht das Öffnen von Belegen aus der Liquiditätsprognose und das Abschließen erkannter regelmäßiger Bankbewegungen, die anschließend aus der Prognose herausgerechnet werden.
This commit is contained in:
2026-04-23 16:03:37 +02:00
parent cb71e9d294
commit aaf91ea15e
4 changed files with 132 additions and 10 deletions

View File

@@ -97,8 +97,14 @@ export const useFunctions = () => {
return await useNuxtApp().$api(`/api/banking/statements/${statementId}/suggestions`)
}
const useLiquidityForecast = async () => {
return await useNuxtApp().$api("/api/functions/liquidity-forecast")
const useLiquidityForecast = async (ignoredRecurringKeys = []) => {
const query = new URLSearchParams()
if (ignoredRecurringKeys.length) {
query.set("ignoredRecurringKeys", ignoredRecurringKeys.join(","))
}
const suffix = query.toString() ? `?${query.toString()}` : ""
return await useNuxtApp().$api(`/api/functions/liquidity-forecast${suffix}`)
}
return {getWorkingTimesEvaluationData, useNextNumber, useBankingGenerateLink, useZipCheck, useBankingCheckInstitutions, useBankingListRequisitions, useBankingResolveIban, useBankingStatementSuggestions, useLiquidityForecast, useCreatePDF}

View File

@@ -3,11 +3,23 @@ import dayjs from "dayjs"
import { Line } from "vue-chartjs"
const toast = useToast()
const tempStore = useTempStore()
const forecast = ref(null)
const loading = ref(true)
const error = ref("")
const dismissedRecurringKeys = computed(() => {
return tempStore.settings?.liquidityForecast?.dismissedRecurringKeys || []
})
const storeDismissedRecurringKeys = (keys) => {
tempStore.modifySettings("liquidityForecast", {
...(tempStore.settings?.liquidityForecast || {}),
dismissedRecurringKeys: [...new Set(keys)].filter(Boolean)
})
}
const sourceLabels = {
open_createddocument: "Offene Ausgangsrechnung",
open_incominginvoice: "Offener Eingangsbeleg",
@@ -26,7 +38,7 @@ const loadForecast = async () => {
error.value = ""
try {
forecast.value = await useFunctions().useLiquidityForecast()
forecast.value = await useFunctions().useLiquidityForecast(dismissedRecurringKeys.value)
} catch (err) {
error.value = "Die Liquiditätsprognose konnte nicht geladen werden."
toast.add({
@@ -41,6 +53,39 @@ const loadForecast = async () => {
const formatDate = (value) => dayjs(value).format("DD.MM.YYYY")
const getEventRoute = (event) => {
if (event.source === "open_createddocument" && event.sourceId) return `/createDocument/show/${event.sourceId}`
if (event.source === "open_incominginvoice" && event.sourceId) return `/incomingInvoices/show/${event.sourceId}`
return null
}
const openEvent = (event) => {
const route = getEventRoute(event)
if (route) navigateTo(route)
}
const dismissRecurringKey = async (key) => {
if (!key) return
storeDismissedRecurringKeys([...dismissedRecurringKeys.value, key])
toast.add({
title: "Bankbewegung abgeschlossen",
description: "Das erkannte Muster wird aus der Liquiditätsprognose entfernt.",
color: "success"
})
await loadForecast()
}
const restoreDismissedRecurring = async () => {
storeDismissedRecurringKeys([])
toast.add({
title: "Bankbewegungen wiederhergestellt",
description: "Ausgeblendete Muster werden wieder in der Prognose berücksichtigt.",
color: "success"
})
await loadForecast()
}
const chartData = computed(() => ({
labels: (forecast.value?.points || []).map((point) => dayjs(point.date).format("DD.MM.")),
datasets: [
@@ -112,6 +157,14 @@ loadForecast()
>
Aktualisieren
</UButton>
<UButton
v-if="dismissedRecurringKeys.length"
icon="i-heroicons-eye"
variant="ghost"
@click="restoreDismissedRecurring"
>
Ausgeblendete wiederherstellen
</UButton>
</template>
</UDashboardNavbar>
@@ -203,7 +256,7 @@ loadForecast()
<div
v-for="event in upcomingEvents"
:key="`${event.source}-${event.sourceId || event.label}-${event.date}-${event.amount}`"
class="grid gap-3 py-3 sm:grid-cols-[110px_minmax(0,1fr)_auto]"
class="grid gap-3 py-3 sm:grid-cols-[110px_minmax(0,1fr)_auto_auto]"
>
<span class="text-sm text-gray-500">{{ formatDate(event.date) }}</span>
<div class="min-w-0">
@@ -216,6 +269,27 @@ loadForecast()
>
{{ useCurrency(event.amount) }}
</span>
<div class="flex justify-end gap-1">
<UButton
v-if="getEventRoute(event)"
size="xs"
variant="ghost"
icon="i-heroicons-arrow-top-right-on-square"
@click="openEvent(event)"
>
Öffnen
</UButton>
<UButton
v-if="event.source === 'recurring_bankstatement' && event.recurringKey"
size="xs"
color="gray"
variant="ghost"
icon="i-heroicons-check-circle"
@click="dismissRecurringKey(event.recurringKey)"
>
Abschließen
</UButton>
</div>
</div>
</div>
@@ -250,9 +324,20 @@ loadForecast()
{{ useCurrency(item.amount) }}
</span>
</div>
<p class="mt-2 text-xs text-gray-500">
Sicherheit {{ Math.round(item.confidence * 100) }}% · {{ item.evidence }}
</p>
<div class="mt-2 flex flex-wrap items-center justify-between gap-2">
<p class="text-xs text-gray-500">
Sicherheit {{ Math.round(item.confidence * 100) }}% · {{ item.evidence }}
</p>
<UButton
size="xs"
color="gray"
variant="soft"
icon="i-heroicons-check-circle"
@click="dismissRecurringKey(item.key)"
>
Als abgeschlossen entfernen
</UButton>
</div>
</div>
</div>