From c085b1e4d5df138312087e158b8157c7f713e4a6 Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Thu, 23 Apr 2026 21:21:15 +0200 Subject: [PATCH] =?UTF-8?q?Liquidit=C3=A4tsprognose=20besser=20nachvollzie?= =?UTF-8?q?hbar=20machen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ergänzt Herleitung, Einflussfaktoren, größte Treiber und eine Tagesaufschlüsselung, damit die Prognosewerte Schritt für Schritt nachvollzogen werden können. --- frontend/pages/accounting/liquidity.vue | 205 ++++++++++++++++++++++++ 1 file changed, 205 insertions(+) diff --git a/frontend/pages/accounting/liquidity.vue b/frontend/pages/accounting/liquidity.vue index eea6991..8345faf 100644 --- a/frontend/pages/accounting/liquidity.vue +++ b/frontend/pages/accounting/liquidity.vue @@ -240,6 +240,53 @@ const upcomingEvents = computed(() => { .slice(0, 18) }) +const groupedEventSummary = computed(() => { + const summary = { + open_createddocument: { label: sourceLabels.open_createddocument, amount: 0, count: 0 }, + open_incominginvoice: { label: sourceLabels.open_incominginvoice, amount: 0, count: 0 }, + recurring_bankstatement: { label: sourceLabels.recurring_bankstatement, amount: 0, count: 0 } + } + + ;(forecast.value?.events || []).forEach((event) => { + if (!summary[event.source]) return + summary[event.source].amount = roundMoney(summary[event.source].amount + Number(event.amount || 0)) + summary[event.source].count += 1 + }) + + return Object.values(summary) +}) + +const topIncomeDrivers = computed(() => { + return [...(forecast.value?.events || [])] + .filter((event) => Number(event.amount || 0) > 0) + .sort((a, b) => Number(b.amount || 0) - Number(a.amount || 0)) + .slice(0, 12) +}) + +const topExpenseDrivers = computed(() => { + return [...(forecast.value?.events || [])] + .filter((event) => Number(event.amount || 0) < 0) + .sort((a, b) => Math.abs(Number(b.amount || 0)) - Math.abs(Number(a.amount || 0))) + .slice(0, 12) +}) + +const detailedDays = computed(() => { + let previousBalance = Number(forecast.value?.startingBalance || 0) + + return (forecast.value?.points || []) + .filter((point) => (point.events || []).length > 0) + .map((point) => { + const startBalance = previousBalance + previousBalance = Number(point.balance || 0) + + return { + ...point, + startBalance: roundMoney(startBalance) + } + }) + .slice(0, 24) +}) + const riskTone = computed(() => { if (!forecast.value) return "neutral" if (forecast.value.lowestBalance < 0) return "danger" @@ -359,6 +406,67 @@ onMounted(() => { :description="`Die gespeicherte Prognose kombiniert aktuelle Kontostände, offene Belege und erkannte regelmäßige Bankbewegungen bis ${formatDate(dayjs().add(forecast.horizonDays, 'day'))}. Neu berechnet wird sie nur über den Refresh-Button.`" /> +
+ + + +
+
+ Startbestand + {{ useCurrency(forecast.startingBalance) }} +
+
+ Geplante Einzahlungen + + {{ useCurrency(forecast.totalIncome) }} +
+
+ Geplante Auszahlungen + {{ useCurrency(forecast.totalExpense) }} +
+
+
+ Erwarteter Endstand + {{ useCurrency(forecast.endingBalance) }} +
+
+
+
+ + + + +
+
+
+

{{ row.label }}

+

{{ row.count }} Positionen

+
+ {{ row.count }}x + + {{ useCurrency(row.amount) }} + +
+
+
+
+