USt-Auswertung in Liquiditätsprognose integrieren

This commit is contained in:
2026-04-23 21:44:59 +02:00
parent 4783971000
commit 35ef3a7cf8
2 changed files with 399 additions and 5 deletions

View File

@@ -11,7 +11,7 @@ const loading = ref(false)
const error = ref("")
const cacheLoaded = ref(false)
const CACHE_KEY = "fedeo:liquidityForecast:v1"
const CACHE_KEY = "fedeo:liquidityForecast:v2"
const dismissedRecurringKeys = computed(() => {
return tempStore.settings?.liquidityForecast?.dismissedRecurringKeys || []
@@ -28,7 +28,8 @@ const sourceLabels = {
open_createddocument: "Offene Ausgangsrechnung",
open_incominginvoice: "Offener Eingangsbeleg",
recurring_bankstatement: "Regelmäßige Bankbewegung",
draft_createddocument: "Rechnungsentwurf"
draft_createddocument: "Rechnungsentwurf",
tax_settlement: "USt-Zahlung"
}
const intervalLabels = {
@@ -156,6 +157,7 @@ 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}`
if (event.source === "tax_settlement") return "/accounting/tax"
return null
}
@@ -245,7 +247,8 @@ 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 }
recurring_bankstatement: { label: sourceLabels.recurring_bankstatement, amount: 0, count: 0 },
tax_settlement: { label: sourceLabels.tax_settlement, amount: 0, count: 0 }
}
;(forecast.value?.events || []).forEach((event) => {
@@ -277,6 +280,24 @@ const draftIncomeDrivers = computed(() => {
.slice(0, 12)
})
const taxPeriodTypeLabel = computed(() => {
if (forecast.value?.tax?.periodType === "quarterly") return "quartalsweise"
if (forecast.value?.tax?.periodType === "yearly") return "jährlich"
return "monatlich"
})
const taxForecastPeriods = computed(() => {
return [...(forecast.value?.tax?.periods || [])]
.filter((period) => Math.abs(Number(period.balance || 0)) > 0.01)
.slice(0, 6)
})
const taxEventDrivers = computed(() => {
return [...(forecast.value?.events || [])]
.filter((event) => event.source === "tax_settlement")
.sort((a, b) => a.date.localeCompare(b.date))
})
const detailedDays = computed(() => {
let previousBalance = Number(forecast.value?.startingBalance || 0)
@@ -478,6 +499,79 @@ onMounted(() => {
</UCard>
</div>
<UCard v-if="taxForecastPeriods.length">
<template #header>
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h2 class="font-semibold">USt in der Prognose</h2>
<p class="text-sm text-gray-500">Aus der USt-Auswertung mit {{ taxPeriodTypeLabel }} Fälligkeit abgeleitet</p>
</div>
<UButton
size="sm"
variant="ghost"
icon="i-heroicons-arrow-top-right-on-square"
@click="navigateTo('/accounting/tax')"
>
USt-Auswertung öffnen
</UButton>
</div>
</template>
<div class="mb-4 rounded-lg border border-dashed border-gray-300 p-4 dark:border-gray-700">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<p class="text-sm text-gray-500">Im Prognosezeitraum berücksichtigte USt-Salden</p>
<p class="text-xs text-gray-500">Positive Salden werden als Auszahlung an das Finanzamt eingeplant, negative als Erstattung.</p>
</div>
<span
class="text-lg font-semibold"
:class="Number(forecast.tax?.totalBalance || 0) > 0 ? 'text-rose-600' : 'text-primary-600'"
>
{{ useCurrency(Number(forecast.tax?.totalBalance || 0) * -1) }}
</span>
</div>
</div>
<div class="space-y-3">
<div
v-for="period in taxForecastPeriods"
:key="period.key"
class="grid gap-3 rounded-lg border border-gray-200 p-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,1fr)_auto] dark:border-gray-800"
>
<div>
<p class="font-medium">{{ period.label }}</p>
<p class="text-xs text-gray-500">{{ period.range }} · Fällig am {{ formatDate(period.dueDate) }}</p>
</div>
<div class="grid gap-1 text-sm text-gray-500">
<div class="flex items-center justify-between gap-3">
<span>USt Rechnungen</span>
<span>{{ useCurrency(period.outputTax) }}</span>
</div>
<div class="flex items-center justify-between gap-3">
<span>Vorsteuer</span>
<span>{{ useCurrency(period.inputTax) }}</span>
</div>
<div class="flex items-center justify-between gap-3 font-medium">
<span>Saldo</span>
<span :class="period.balance > 0 ? 'text-rose-600' : 'text-primary-600'">
{{ useCurrency(period.balance) }}
</span>
</div>
</div>
<div class="text-right">
<p class="text-xs text-gray-500">{{ period.outputCount }} Ausgangsbelege · {{ period.inputCount }} Eingangsbelege</p>
<p
class="mt-2 text-lg font-semibold"
:class="period.balance > 0 ? 'text-rose-600' : 'text-primary-600'"
>
{{ useCurrency(period.balance * -1) }}
</p>
<p class="text-xs text-gray-500">Liquiditätseffekt</p>
</div>
</div>
</div>
</UCard>
<UCard v-if="draftIncomeDrivers.length">
<template #header>
<div>
@@ -634,6 +728,43 @@ onMounted(() => {
</UCard>
</div>
<UCard v-if="taxEventDrivers.length">
<template #header>
<div>
<h2 class="font-semibold">Geplante USt-Zahlungen und Erstattungen</h2>
<p class="text-sm text-gray-500">Direkt aus den berücksichtigten USt-Zeiträumen abgeleitet</p>
</div>
</template>
<div class="space-y-2">
<div
v-for="event in taxEventDrivers"
:key="`tax-${event.date}-${event.sourceId}`"
class="grid gap-3 rounded-lg border border-gray-200 p-3 sm:grid-cols-[110px_minmax(0,1fr)_auto_auto] dark:border-gray-800"
>
<span class="text-sm text-gray-500">{{ formatDate(event.date) }}</span>
<div class="min-w-0">
<p class="truncate font-medium">{{ event.label }}</p>
<p class="text-xs text-gray-500">{{ sourceLabels[event.source] }}</p>
</div>
<span
class="text-right font-semibold"
:class="event.amount < 0 ? 'text-rose-600' : 'text-primary-600'"
>
{{ useCurrency(event.amount) }}
</span>
<UButton
size="xs"
variant="ghost"
icon="i-heroicons-arrow-top-right-on-square"
@click="openEvent(event)"
>
Öffnen
</UButton>
</div>
</div>
</UCard>
<div class="grid gap-4 xl:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)]">
<UCard>
<template #header>