Files
FEDEO/frontend/components/displayTaxSummary.vue
2026-03-21 17:57:23 +01:00

184 lines
4.8 KiB
Vue

<script setup lang="ts">
import dayjs from "dayjs"
import customParseFormat from "dayjs/plugin/customParseFormat"
import {
formatTaxEvaluationPeriodLabel,
formatTaxEvaluationPeriodRange,
getCreatedDocumentTaxBreakdown,
getIncomingInvoiceTaxBreakdown,
getTaxEvaluationPeriodBounds,
normalizeTaxEvaluationPeriod
} from "~/composables/useTaxEvaluation"
dayjs.extend(customParseFormat)
const auth = useAuthStore()
const loading = ref(true)
const summary = ref({
label: "",
range: "",
outputTax: 0,
inputTax: 0,
balance: 0,
outputCount: 0,
inputCount: 0,
})
const formatCurrency = (value: number) => {
return new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR"
}).format(Number(value || 0))
}
const loadSummary = async () => {
loading.value = true
try {
const periodType = normalizeTaxEvaluationPeriod(auth.activeTenantData?.taxEvaluationPeriod)
const bounds = getTaxEvaluationPeriodBounds(dayjs(), periodType)
const [docs, incoming] = await Promise.all([
useEntities("createddocuments").select(),
useEntities("incominginvoices").select()
])
const outputDocs = (docs || []).filter((doc: any) => {
if (doc?.state !== "Gebucht") return false
if (!["invoices", "advanceInvoices", "cancellationInvoices"].includes(doc?.type)) return false
const date = dayjs(doc.documentDate)
return date.isValid() && !date.isBefore(bounds.start, "day") && !date.isAfter(bounds.end, "day")
})
const inputDocs = (incoming || []).filter((invoice: any) => {
if (invoice?.state !== "Gebucht" || !invoice?.date) return false
const date = dayjs(invoice.date)
return date.isValid() && !date.isBefore(bounds.start, "day") && !date.isAfter(bounds.end, "day")
})
const outputTax = outputDocs.reduce((sum: number, doc: any) => {
const breakdown = getCreatedDocumentTaxBreakdown(doc)
return sum + breakdown.tax19 + breakdown.tax7
}, 0)
const inputTax = inputDocs.reduce((sum: number, invoice: any) => {
const breakdown = getIncomingInvoiceTaxBreakdown(invoice)
return sum + breakdown.tax19 + breakdown.tax7
}, 0)
summary.value = {
label: formatTaxEvaluationPeriodLabel(bounds.start, periodType),
range: formatTaxEvaluationPeriodRange(bounds.start, periodType),
outputTax: Number(outputTax.toFixed(2)),
inputTax: Number(inputTax.toFixed(2)),
balance: Number((outputTax - inputTax).toFixed(2)),
outputCount: outputDocs.length,
inputCount: inputDocs.length,
}
} finally {
loading.value = false
}
}
onMounted(loadSummary)
</script>
<template>
<div class="space-y-3">
<div class="tax-summary-top">
<div>
<p class="tax-summary-period">{{ summary.label }}</p>
<p class="tax-summary-range">{{ summary.range }}</p>
</div>
<UButton
size="xs"
variant="soft"
color="gray"
icon="i-heroicons-arrow-top-right-on-square"
@click="navigateTo('/accounting/tax')"
>
Details
</UButton>
</div>
<div class="tax-summary-row">
<span class="tax-summary-label">USt Rechnungen</span>
<span class="tax-summary-value text-amber-600 dark:text-amber-400">
{{ loading ? "..." : formatCurrency(summary.outputTax) }}
</span>
</div>
<div class="tax-summary-row">
<span class="tax-summary-label">Vorsteuer</span>
<span class="tax-summary-value text-sky-600 dark:text-sky-400">
{{ loading ? "..." : formatCurrency(summary.inputTax) }}
</span>
</div>
<div class="tax-summary-row">
<span class="tax-summary-label">Ergebnis</span>
<span
class="tax-summary-value"
:class="summary.balance >= 0 ? 'text-rose-600 dark:text-rose-400' : 'text-emerald-600 dark:text-emerald-400'"
>
{{ loading ? "..." : formatCurrency(summary.balance) }}
</span>
</div>
<div class="tax-summary-meta">
{{ summary.outputCount }} Ausgangsbelege | {{ summary.inputCount }} Eingangsbelege
</div>
</div>
</template>
<style scoped>
.tax-summary-top {
display: flex;
justify-content: space-between;
gap: 0.75rem;
align-items: flex-start;
}
.tax-summary-period {
margin: 0;
font-weight: 700;
color: rgb(17 24 39);
}
.tax-summary-range,
.tax-summary-meta {
margin: 0;
font-size: 0.875rem;
color: rgb(107 114 128);
}
.tax-summary-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.75rem;
align-items: center;
}
.tax-summary-label {
color: rgb(55 65 81);
}
.tax-summary-value {
font-weight: 700;
text-align: right;
}
:deep(.dark) .tax-summary-period {
color: rgb(243 244 246);
}
:deep(.dark) .tax-summary-range,
:deep(.dark) .tax-summary-meta,
:deep(.dark) .tax-summary-label {
color: rgb(156 163 175);
}
</style>