Added UST Auswertung
This commit is contained in:
289
frontend/pages/accounting/tax.vue
Normal file
289
frontend/pages/accounting/tax.vue
Normal file
@@ -0,0 +1,289 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from "dayjs"
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat"
|
||||
import {
|
||||
formatTaxEvaluationPeriodLabel,
|
||||
formatTaxEvaluationPeriodRange,
|
||||
getCreatedDocumentTaxBreakdown,
|
||||
getIncomingInvoiceTaxBreakdown,
|
||||
getTaxEvaluationPeriodBounds,
|
||||
normalizeTaxEvaluationPeriod,
|
||||
shiftTaxEvaluationPeriodStart
|
||||
} from "~/composables/useTaxEvaluation"
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
const auth = useAuthStore()
|
||||
|
||||
const loading = ref(true)
|
||||
const createdDocuments = ref<any[]>([])
|
||||
const incomingInvoices = ref<any[]>([])
|
||||
|
||||
const periodType = computed(() => normalizeTaxEvaluationPeriod(auth.activeTenantData?.taxEvaluationPeriod))
|
||||
|
||||
const formatCurrency = (value: number) => {
|
||||
return new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR"
|
||||
}).format(Number(value || 0))
|
||||
}
|
||||
|
||||
const isRelevantOutputDocument = (doc: any) => {
|
||||
return doc?.state === "Gebucht" && ["invoices", "advanceInvoices", "cancellationInvoices"].includes(doc?.type)
|
||||
}
|
||||
|
||||
const isRelevantInputInvoice = (invoice: any) => {
|
||||
return invoice?.state === "Gebucht" && !!invoice?.date
|
||||
}
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const [docs, incoming] = await Promise.all([
|
||||
useEntities("createddocuments").select(),
|
||||
useEntities("incominginvoices").select()
|
||||
])
|
||||
|
||||
createdDocuments.value = (docs || []).filter(isRelevantOutputDocument)
|
||||
incomingInvoices.value = (incoming || []).filter(isRelevantInputInvoice)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const periods = computed(() => {
|
||||
const currentBounds = getTaxEvaluationPeriodBounds(dayjs(), periodType.value)
|
||||
|
||||
return Array.from({ length: 8 }, (_, index) => {
|
||||
const start = shiftTaxEvaluationPeriodStart(currentBounds.start, periodType.value, -index)
|
||||
const bounds = getTaxEvaluationPeriodBounds(start, periodType.value)
|
||||
|
||||
const outputDocs = createdDocuments.value.filter((doc) => {
|
||||
const date = dayjs(doc.documentDate)
|
||||
return date.isValid() && !date.isBefore(bounds.start, "day") && !date.isAfter(bounds.end, "day")
|
||||
})
|
||||
|
||||
const inputDocs = incomingInvoices.value.filter((invoice) => {
|
||||
const date = dayjs(invoice.date)
|
||||
return date.isValid() && !date.isBefore(bounds.start, "day") && !date.isAfter(bounds.end, "day")
|
||||
})
|
||||
|
||||
const output = outputDocs.reduce((sum, doc) => {
|
||||
const breakdown = getCreatedDocumentTaxBreakdown(doc)
|
||||
return {
|
||||
net19: sum.net19 + breakdown.net19,
|
||||
tax19: sum.tax19 + breakdown.tax19,
|
||||
net7: sum.net7 + breakdown.net7,
|
||||
tax7: sum.tax7 + breakdown.tax7,
|
||||
net0: sum.net0 + breakdown.net0,
|
||||
}
|
||||
}, { net19: 0, tax19: 0, net7: 0, tax7: 0, net0: 0 })
|
||||
|
||||
const input = inputDocs.reduce((sum, invoice) => {
|
||||
const breakdown = getIncomingInvoiceTaxBreakdown(invoice)
|
||||
return {
|
||||
net19: sum.net19 + breakdown.net19,
|
||||
tax19: sum.tax19 + breakdown.tax19,
|
||||
net7: sum.net7 + breakdown.net7,
|
||||
tax7: sum.tax7 + breakdown.tax7,
|
||||
net0: sum.net0 + breakdown.net0,
|
||||
}
|
||||
}, { net19: 0, tax19: 0, net7: 0, tax7: 0, net0: 0 })
|
||||
|
||||
const outputTax = Number((output.tax19 + output.tax7).toFixed(2))
|
||||
const inputTax = Number((input.tax19 + input.tax7).toFixed(2))
|
||||
const balance = Number((outputTax - inputTax).toFixed(2))
|
||||
|
||||
return {
|
||||
key: bounds.start.format("YYYY-MM-DD"),
|
||||
label: formatTaxEvaluationPeriodLabel(bounds.start, periodType.value),
|
||||
range: formatTaxEvaluationPeriodRange(bounds.start, periodType.value),
|
||||
isCurrent: index === 0,
|
||||
outputTax,
|
||||
inputTax,
|
||||
balance,
|
||||
output,
|
||||
input,
|
||||
outputCount: outputDocs.length,
|
||||
inputCount: inputDocs.length,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const currentPeriod = computed(() => periods.value[0] || null)
|
||||
|
||||
const columns = [
|
||||
{ key: "label", label: "Zeitraum" },
|
||||
{ key: "range", label: "Datumsbereich" },
|
||||
{ key: "outputTax", label: "USt Rechnungen" },
|
||||
{ key: "inputTax", label: "Vorsteuer" },
|
||||
{ key: "balance", label: "Ergebnis" },
|
||||
{ key: "documents", label: "Belege" },
|
||||
]
|
||||
|
||||
onMounted(loadData)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UDashboardNavbar title="USt-Auswertung">
|
||||
<template #right>
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-path"
|
||||
variant="outline"
|
||||
@click="loadData"
|
||||
:loading="loading"
|
||||
>
|
||||
Aktualisieren
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardPanelContent class="p-4 md:p-6">
|
||||
<div class="mb-6 flex flex-col gap-2">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Aktueller Zeitraum: {{ currentPeriod?.label }}
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Intervall: {{ periodType === "monthly" ? "monatlich" : periodType === "quarterly" ? "quartalsweise" : "jährlich" }}.
|
||||
Berücksichtigt werden gebuchte Ausgangsrechnungen, Abschlags- und Stornorechnungen sowie gebuchte Eingangsbelege mit Datum.
|
||||
</p>
|
||||
<p v-if="currentPeriod" class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ currentPeriod.range }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="currentPeriod" class="grid gap-4 md:grid-cols-3">
|
||||
<UCard>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">Berechnete USt</div>
|
||||
<div class="mt-2 text-2xl font-semibold text-gray-900 dark:text-white">
|
||||
{{ formatCurrency(currentPeriod.outputTax) }}
|
||||
</div>
|
||||
<div class="mt-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
19%: {{ formatCurrency(currentPeriod.output.tax19) }} | 7%: {{ formatCurrency(currentPeriod.output.tax7) }}
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">Vorsteuer</div>
|
||||
<div class="mt-2 text-2xl font-semibold text-gray-900 dark:text-white">
|
||||
{{ formatCurrency(currentPeriod.inputTax) }}
|
||||
</div>
|
||||
<div class="mt-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
19%: {{ formatCurrency(currentPeriod.input.tax19) }} | 7%: {{ formatCurrency(currentPeriod.input.tax7) }}
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">Verrechnetes Ergebnis</div>
|
||||
<div
|
||||
class="mt-2 text-2xl font-semibold"
|
||||
:class="currentPeriod.balance >= 0 ? 'text-amber-600 dark:text-amber-400' : 'text-emerald-600 dark:text-emerald-400'"
|
||||
>
|
||||
{{ formatCurrency(currentPeriod.balance) }}
|
||||
</div>
|
||||
<div class="mt-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ currentPeriod.outputCount }} Ausgangsbelege | {{ currentPeriod.inputCount }} Eingangsbelege
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div v-if="currentPeriod" class="mt-6 grid gap-4 xl:grid-cols-2">
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="font-semibold">Ausgangsrechnungen</div>
|
||||
</template>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Netto 19%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.output.net19) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>USt 19%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.output.tax19) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Netto 7%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.output.net7) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>USt 7%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.output.tax7) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Netto 0%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.output.net0) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="font-semibold">Eingangsbelege</div>
|
||||
</template>
|
||||
<div class="space-y-3 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Netto 19%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.input.net19) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Vorsteuer 19%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.input.tax19) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Netto 7%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.input.net7) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Vorsteuer 7%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.input.tax7) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Netto 0%</span>
|
||||
<span>{{ formatCurrency(currentPeriod.input.net0) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<UCard class="mt-6">
|
||||
<template #header>
|
||||
<div class="font-semibold">Aktueller und vorherige Zeiträume</div>
|
||||
</template>
|
||||
|
||||
<UTable
|
||||
:columns="columns"
|
||||
:rows="periods"
|
||||
:loading="loading"
|
||||
:empty-state="{ icon: 'i-heroicons-calculator', label: 'Keine Daten für die USt-Auswertung vorhanden' }"
|
||||
>
|
||||
<template #label-data="{ row }">
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ row.label }}</span>
|
||||
<UBadge v-if="row.isCurrent" color="primary" variant="soft">Aktuell</UBadge>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #outputTax-data="{ row }">
|
||||
{{ formatCurrency(row.outputTax) }}
|
||||
</template>
|
||||
|
||||
<template #inputTax-data="{ row }">
|
||||
{{ formatCurrency(row.inputTax) }}
|
||||
</template>
|
||||
|
||||
<template #balance-data="{ row }">
|
||||
<span :class="row.balance >= 0 ? 'text-amber-600 dark:text-amber-400 font-medium' : 'text-emerald-600 dark:text-emerald-400 font-medium'">
|
||||
{{ formatCurrency(row.balance) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #documents-data="{ row }">
|
||||
{{ row.outputCount }} / {{ row.inputCount }}
|
||||
</template>
|
||||
</UTable>
|
||||
</UCard>
|
||||
</UDashboardPanelContent>
|
||||
</div>
|
||||
</template>
|
||||
@@ -7,6 +7,7 @@ import DisplayOpenBalances from "~/components/displayOpenBalances.vue"
|
||||
import DisplayBankaccounts from "~/components/displayBankaccounts.vue"
|
||||
import DisplayProjectsInPhases from "~/components/displayProjectsInPhases.vue"
|
||||
import DisplayOpenTasks from "~/components/displayOpenTasks.vue"
|
||||
import DisplayTaxSummary from "~/components/displayTaxSummary.vue"
|
||||
|
||||
setPageLayout("default")
|
||||
|
||||
@@ -68,6 +69,15 @@ const DASHBOARD_WIDGETS = [
|
||||
defaultLayout: { x: 0, y: 7, w: 4, h: 3 },
|
||||
minW: 3,
|
||||
minH: 3
|
||||
},
|
||||
{
|
||||
id: "tax-summary",
|
||||
title: "USt aktuell",
|
||||
description: "USt, Vorsteuer und Saldo des aktuellen Zeitraums",
|
||||
component: markRaw(DisplayTaxSummary),
|
||||
defaultLayout: { x: 4, y: 7, w: 4, h: 3 },
|
||||
minW: 3,
|
||||
minH: 3
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script setup>
|
||||
import {
|
||||
TAX_EVALUATION_PERIOD_OPTIONS,
|
||||
normalizeTaxEvaluationPeriod
|
||||
} from "~/composables/useTaxEvaluation"
|
||||
|
||||
const auth = useAuthStore()
|
||||
const defaultFeatures = {
|
||||
@@ -119,6 +123,7 @@ const setupPage = async () => {
|
||||
const features = ref({ ...defaultFeatures, ...(auth.activeTenantData?.features || {}) })
|
||||
const businessInfo = ref(auth.activeTenantData.businessInfo)
|
||||
const accountChart = ref(auth.activeTenantData.accountChart || "skr03")
|
||||
const taxEvaluationPeriod = ref(normalizeTaxEvaluationPeriod(auth.activeTenantData.taxEvaluationPeriod))
|
||||
const accountChartOptions = [
|
||||
{ label: "SKR 03", value: "skr03" },
|
||||
{ label: "Verein", value: "verein" }
|
||||
@@ -137,6 +142,7 @@ const updateTenant = async (newData) => {
|
||||
itemInfo.value = res
|
||||
auth.activeTenantData = res
|
||||
features.value = { ...defaultFeatures, ...(res?.features || {}) }
|
||||
taxEvaluationPeriod.value = normalizeTaxEvaluationPeriod(res?.taxEvaluationPeriod)
|
||||
}
|
||||
}
|
||||
const saveFeatures = async () => {
|
||||
@@ -226,6 +232,23 @@ setupPage()
|
||||
>
|
||||
Kontenrahmen speichern
|
||||
</UButton>
|
||||
<UFormGroup
|
||||
label="USt-Auswertung:"
|
||||
class="mt-6"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="taxEvaluationPeriod"
|
||||
:options="TAX_EVALUATION_PERIOD_OPTIONS"
|
||||
option-attribute="label"
|
||||
value-attribute="value"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UButton
|
||||
class="mt-3"
|
||||
@click="updateTenant({taxEvaluationPeriod: taxEvaluationPeriod})"
|
||||
>
|
||||
Zeitraum speichern
|
||||
</UButton>
|
||||
</UForm>
|
||||
</UCard>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user