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>
|
||||
Reference in New Issue
Block a user