From 47839710007018403e0498ae68d3e12ad627ee64 Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Thu, 23 Apr 2026 21:29:14 +0200 Subject: [PATCH] =?UTF-8?q?Rechnungsentw=C3=BCrfe=20optional=20in=20Progno?= =?UTF-8?q?se=20anzeigen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zeigt Rechnungsentwürfe eingeklammert als optionalen Einfluss in der Liquiditätsprognose an, ohne den eigentlichen Endstand zu verändern. --- backend/src/utils/liquidityForecast.ts | 27 ++++++++++++- frontend/pages/accounting/liquidity.vue | 53 ++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/backend/src/utils/liquidityForecast.ts b/backend/src/utils/liquidityForecast.ts index e9d8b3d..a5d2da3 100644 --- a/backend/src/utils/liquidityForecast.ts +++ b/backend/src/utils/liquidityForecast.ts @@ -15,7 +15,7 @@ import { } from "../../db/schema"; import { secrets } from "./secrets"; -type ForecastEventSource = "open_createddocument" | "open_incominginvoice" | "recurring_bankstatement"; +type ForecastEventSource = "open_createddocument" | "open_incominginvoice" | "recurring_bankstatement" | "draft_createddocument"; type ForecastEvent = { date: string; @@ -372,6 +372,7 @@ export const generateLiquidityForecast = async ( const cancelledDocumentIds = findCancellationDocumentIds(activeDocuments); const openEvents: ForecastEvent[] = []; + const draftEvents: ForecastEvent[] = []; activeDocuments .filter((document) => ["invoices", "advanceInvoices"].includes(document.type)) @@ -395,6 +396,27 @@ export const generateLiquidityForecast = async ( }); }); + activeDocuments + .filter((document) => ["invoices", "advanceInvoices"].includes(document.type)) + .filter((document) => document.state === "Entwurf" && !cancelledDocumentIds.has(document.id)) + .forEach((document) => { + const total = getCreatedDocumentGrossAmount(document, activeDocuments); + if (total <= 0.01) return; + + const dueDate = dayjs(document.documentDate).isValid() + ? dayjs(document.documentDate).add(Number(document.paymentDays || 0), "day") + : today; + + draftEvents.push({ + date: dueDate.isBefore(today, "day") ? today.format("YYYY-MM-DD") : dueDate.format("YYYY-MM-DD"), + amount: total, + label: document.documentNumber || document.title || `Rechnungsentwurf ${document.id}`, + source: "draft_createddocument", + sourceId: document.id, + confidence: 0.35, + }); + }); + activeIncomingInvoices .filter((invoice) => invoice.state === "Gebucht" || invoice.state === "Vorbereitet") .filter((invoice) => !invoice.paid) @@ -459,6 +481,7 @@ export const generateLiquidityForecast = async ( const lowestPoint = points.reduce((lowest, point) => point.balance < lowest.balance ? point : lowest, points[0]); const totalIncome = roundMoney(events.filter((event) => event.amount > 0).reduce((sum, event) => sum + event.amount, 0)); const totalExpense = roundMoney(events.filter((event) => event.amount < 0).reduce((sum, event) => sum + event.amount, 0)); + const draftIncome = roundMoney(draftEvents.filter((event) => event.amount > 0).reduce((sum, event) => sum + event.amount, 0)); return { generatedAt: new Date().toISOString(), @@ -479,6 +502,8 @@ export const generateLiquidityForecast = async ( })), recurring, events: events.sort((a, b) => a.date.localeCompare(b.date)), + draftEvents: draftEvents.sort((a, b) => a.date.localeCompare(b.date)), + draftIncome, points, ai: { enabled: Boolean(secrets.OPENAI_API_KEY), diff --git a/frontend/pages/accounting/liquidity.vue b/frontend/pages/accounting/liquidity.vue index 8345faf..9dc854e 100644 --- a/frontend/pages/accounting/liquidity.vue +++ b/frontend/pages/accounting/liquidity.vue @@ -27,7 +27,8 @@ const storeDismissedRecurringKeys = (keys) => { const sourceLabels = { open_createddocument: "Offene Ausgangsrechnung", open_incominginvoice: "Offener Eingangsbeleg", - recurring_bankstatement: "Regelmäßige Bankbewegung" + recurring_bankstatement: "Regelmäßige Bankbewegung", + draft_createddocument: "Rechnungsentwurf" } const intervalLabels = { @@ -270,6 +271,12 @@ const topExpenseDrivers = computed(() => { .slice(0, 12) }) +const draftIncomeDrivers = computed(() => { + return [...(forecast.value?.draftEvents || [])] + .sort((a, b) => Number(b.amount || 0) - Number(a.amount || 0)) + .slice(0, 12) +}) + const detailedDays = computed(() => { let previousBalance = Number(forecast.value?.startingBalance || 0) @@ -424,6 +431,10 @@ onMounted(() => { Geplante Einzahlungen + {{ useCurrency(forecast.totalIncome) }} +
+ Rechnungsentwürfe optional + (+ {{ useCurrency(forecast.draftIncome) }}) +
Geplante Auszahlungen {{ useCurrency(forecast.totalExpense) }} @@ -467,6 +478,46 @@ onMounted(() => {
+ + + +
+
+ Optionale Zusatzliquidität aus Entwürfen + (+ {{ useCurrency(forecast.draftIncome) }}) +
+
+ +
+
+ {{ formatDate(event.date) }} +
+

{{ event.label }}

+

Noch nicht gebucht · {{ sourceLabels[event.source] }}

+
+ (+ {{ useCurrency(event.amount) }}) + + Öffnen + +
+
+
+