Ersetzt ungültige UTable-Empty-Props durch einen gemeinsamen Empty-State-Slot, damit leere Tabellen keine Objekt-/JSON-Ausgabe mehr anzeigen.
682 lines
24 KiB
Vue
682 lines
24 KiB
Vue
<script setup lang="ts">
|
|
import dayjs from "dayjs"
|
|
import {
|
|
getCreatedDocumentTaxBreakdown,
|
|
getIncomingInvoiceTaxBreakdown
|
|
} from "~/composables/useTaxEvaluation"
|
|
import {
|
|
getIncomingInvoiceDepreciationRows,
|
|
getIncomingInvoiceImmediateExpenseGross,
|
|
getIncomingInvoiceImmediateExpenseNet,
|
|
isDepreciationBookingMode,
|
|
normalizeIncomingInvoiceAccount,
|
|
getStatementAllocationImmediateExpenseAmount
|
|
} from "~/composables/useDepreciation"
|
|
|
|
const router = useRouter()
|
|
|
|
const loading = ref(true)
|
|
const createdDocuments = ref<any[]>([])
|
|
const incomingInvoices = ref<any[]>([])
|
|
const accounts = ref<any[]>([])
|
|
const ownAccounts = ref<any[]>([])
|
|
const statementAllocations = ref<any[]>([])
|
|
|
|
const selectedYear = ref(String(dayjs().year()))
|
|
const selectedMonth = ref("all")
|
|
|
|
const monthItems = [
|
|
{ label: "Ganzes Jahr", value: "all" },
|
|
{ label: "Januar", value: "1" },
|
|
{ label: "Februar", value: "2" },
|
|
{ label: "Maerz", value: "3" },
|
|
{ label: "April", value: "4" },
|
|
{ label: "Mai", value: "5" },
|
|
{ label: "Juni", value: "6" },
|
|
{ label: "Juli", value: "7" },
|
|
{ label: "August", value: "8" },
|
|
{ label: "September", value: "9" },
|
|
{ label: "Oktober", value: "10" },
|
|
{ label: "November", value: "11" },
|
|
{ label: "Dezember", value: "12" }
|
|
]
|
|
|
|
const accountColumns = [
|
|
{ accessorKey: "gross", header: "Brutto" },
|
|
{ accessorKey: "net", header: "Netto" },
|
|
{ accessorKey: "tax", header: "Steuer" },
|
|
{ accessorKey: "number", header: "Nummer" },
|
|
{ accessorKey: "label", header: "Konto" },
|
|
{ accessorKey: "bookings", header: "Buchungen" }
|
|
]
|
|
|
|
const ownAccountColumns = [
|
|
{ accessorKey: "balance", header: "Saldo" },
|
|
{ accessorKey: "expenses", header: "Ausgaben" },
|
|
{ accessorKey: "income", header: "Einnahmen" },
|
|
{ accessorKey: "number", header: "Nummer" },
|
|
{ accessorKey: "label", header: "Konto" },
|
|
{ accessorKey: "bookings", header: "Buchungen" }
|
|
]
|
|
|
|
const depreciationColumns = [
|
|
{ accessorKey: "label", header: "Abschreibung" },
|
|
{ accessorKey: "groupLabel", header: "Gruppe" },
|
|
{ accessorKey: "modeLabel", header: "Art" },
|
|
{ accessorKey: "amount", header: "Betrag" },
|
|
{ accessorKey: "bookings", header: "Positionen" }
|
|
]
|
|
|
|
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 sameId = (left: any, right: any) => String(left ?? "") === String(right ?? "")
|
|
|
|
const getStatementDate = (allocation: any) => {
|
|
return allocation?.bankstatement?.date || allocation?.bankstatement?.valueDate || allocation?.date || allocation?.created_at || null
|
|
}
|
|
|
|
const matchesSelectedPeriod = (dateValue: any) => {
|
|
const parsed = dayjs(dateValue)
|
|
|
|
if (!parsed.isValid()) {
|
|
return false
|
|
}
|
|
|
|
if (String(parsed.year()) !== selectedYear.value) {
|
|
return false
|
|
}
|
|
|
|
if (selectedMonth.value !== "all" && parsed.month() + 1 !== Number(selectedMonth.value)) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
const computeDocumentNet = (doc: any) => {
|
|
return Number((doc?.rows || []).reduce((sum: number, row: any) => {
|
|
if (!row || ["pagebreak", "title", "text"].includes(row.mode)) {
|
|
return sum
|
|
}
|
|
|
|
const quantity = Number(row.quantity || 0)
|
|
const price = Number(row.price || 0)
|
|
const discountPercent = Number(row.discountPercent || 0)
|
|
|
|
return sum + (quantity * price * (1 - discountPercent / 100))
|
|
}, 0).toFixed(2))
|
|
}
|
|
|
|
const yearItems = computed(() => {
|
|
const years = new Set<string>([String(dayjs().year())])
|
|
|
|
createdDocuments.value.forEach((doc) => {
|
|
const parsed = dayjs(doc.documentDate)
|
|
if (parsed.isValid()) {
|
|
years.add(String(parsed.year()))
|
|
}
|
|
})
|
|
|
|
incomingInvoices.value.forEach((invoice) => {
|
|
const parsed = dayjs(invoice.date)
|
|
if (parsed.isValid()) {
|
|
years.add(String(parsed.year()))
|
|
}
|
|
})
|
|
|
|
statementAllocations.value.forEach((allocation) => {
|
|
const parsed = dayjs(getStatementDate(allocation))
|
|
if (parsed.isValid()) {
|
|
years.add(String(parsed.year()))
|
|
}
|
|
})
|
|
|
|
return Array.from(years)
|
|
.sort((a, b) => Number(b) - Number(a))
|
|
.map((year) => ({ label: year, value: year }))
|
|
})
|
|
|
|
const filteredDocuments = computed(() => {
|
|
return createdDocuments.value.filter((doc) => matchesSelectedPeriod(doc.documentDate))
|
|
})
|
|
|
|
const filteredIncomingInvoices = computed(() => {
|
|
return incomingInvoices.value.filter((invoice) => matchesSelectedPeriod(invoice.date))
|
|
})
|
|
|
|
const filteredStatementAllocations = computed(() => {
|
|
return statementAllocations.value.filter((allocation) => matchesSelectedPeriod(getStatementDate(allocation)))
|
|
})
|
|
|
|
const filteredAccountStatementAllocations = computed(() => {
|
|
return filteredStatementAllocations.value.filter((allocation) => {
|
|
if (allocation.account === null || allocation.account === undefined) {
|
|
return false
|
|
}
|
|
|
|
return getStatementAllocationImmediateExpenseAmount(allocation) > 0
|
|
})
|
|
})
|
|
|
|
const selectedPeriodBounds = computed(() => {
|
|
const start = selectedMonth.value === "all"
|
|
? dayjs(`${selectedYear.value}-01-01`).startOf("day")
|
|
: dayjs(`${selectedYear.value}-${String(selectedMonth.value).padStart(2, "0")}-01`).startOf("month")
|
|
|
|
const end = selectedMonth.value === "all"
|
|
? dayjs(`${selectedYear.value}-12-31`).endOf("day")
|
|
: start.endOf("month")
|
|
|
|
return { start, end }
|
|
})
|
|
|
|
const incomeTotal = computed(() => {
|
|
return Number(filteredDocuments.value.reduce((sum, doc) => sum + computeDocumentNet(doc), 0).toFixed(2))
|
|
})
|
|
|
|
const expenseNetTotal = computed(() => {
|
|
const invoiceExpenses = filteredIncomingInvoices.value.reduce((sum, invoice) => {
|
|
return sum + getIncomingInvoiceImmediateExpenseNet(invoice)
|
|
}, 0)
|
|
|
|
const directAccountExpenses = filteredAccountStatementAllocations.value.reduce((sum, allocation) => {
|
|
return sum + Number(getStatementAllocationImmediateExpenseAmount(allocation) || 0)
|
|
}, 0)
|
|
|
|
const depreciations = depreciationTotal.value
|
|
|
|
return Number((invoiceExpenses + directAccountExpenses + depreciations).toFixed(2))
|
|
})
|
|
|
|
const expenseGrossTotal = computed(() => {
|
|
const invoiceExpenses = filteredIncomingInvoices.value.reduce((sum, invoice) => sum + getIncomingInvoiceImmediateExpenseGross(invoice), 0)
|
|
|
|
const directAccountExpenses = filteredAccountStatementAllocations.value.reduce((sum, allocation) => {
|
|
return sum + Number(getStatementAllocationImmediateExpenseAmount(allocation) || 0)
|
|
}, 0)
|
|
|
|
const depreciations = depreciationTotal.value
|
|
|
|
return Number((invoiceExpenses + directAccountExpenses + depreciations).toFixed(2))
|
|
})
|
|
|
|
const taxSummary = computed(() => {
|
|
const output = filteredDocuments.value.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 = filteredIncomingInvoices.value.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))
|
|
|
|
return {
|
|
output,
|
|
input,
|
|
outputTax,
|
|
inputTax,
|
|
balance: Number((outputTax - inputTax).toFixed(2))
|
|
}
|
|
})
|
|
|
|
const operatingResult = computed(() => {
|
|
return Number((incomeTotal.value - expenseNetTotal.value).toFixed(2))
|
|
})
|
|
|
|
const incomeDocumentCount = computed(() => filteredDocuments.value.length)
|
|
const expenseDocumentCount = computed(() => {
|
|
const directExpenseCount = filteredAccountStatementAllocations.value.filter((allocation) => getStatementAllocationImmediateExpenseAmount(allocation) > 0).length
|
|
return filteredIncomingInvoices.value.filter((invoice) => getIncomingInvoiceImmediateExpenseNet(invoice) > 0).length + directExpenseCount
|
|
})
|
|
|
|
const depreciationRows = computed(() => {
|
|
const invoiceRows = filteredIncomingInvoices.value.flatMap((invoice) => getIncomingInvoiceDepreciationRows(invoice, selectedPeriodBounds.value.start, selectedPeriodBounds.value.end))
|
|
|
|
const grouped = new Map<string, any>()
|
|
|
|
;invoiceRows.forEach((row: any) => {
|
|
const key = row.group || `${row.mode}:${row.label}`
|
|
const current = grouped.get(key) || {
|
|
id: key,
|
|
label: row.group || row.label,
|
|
groupLabel: row.group || "-",
|
|
modeLabel: row.mode === "depreciation_bundle" ? "Sammelposten" : "Einzeln",
|
|
amount: 0,
|
|
bookings: 0
|
|
}
|
|
|
|
current.amount += Number(row.amount || 0)
|
|
current.bookings += 1
|
|
grouped.set(key, current)
|
|
})
|
|
|
|
return Array.from(grouped.values())
|
|
.map((row) => ({ ...row, amount: Number(row.amount.toFixed(2)) }))
|
|
.sort((left, right) => Number(right.amount) - Number(left.amount))
|
|
})
|
|
|
|
const depreciationTotal = computed(() => {
|
|
return Number(depreciationRows.value.reduce((sum, row) => sum + Number(row.amount || 0), 0).toFixed(2))
|
|
})
|
|
|
|
const accountRows = computed(() => {
|
|
return accounts.value
|
|
.map((account) => {
|
|
const invoiceBookings = filteredIncomingInvoices.value.flatMap((invoice) => {
|
|
return (invoice.accounts || [])
|
|
.filter((invoiceAccount: any) => !isDepreciationBookingMode(normalizeIncomingInvoiceAccount(invoiceAccount, invoice?.date).bookingMode))
|
|
.filter((invoiceAccount: any) => sameId(invoiceAccount.account?.id || invoiceAccount.account, account.id))
|
|
.map((invoiceAccount: any) => ({
|
|
type: "incominginvoice",
|
|
amountNet: Number(invoiceAccount.amountNet || 0),
|
|
amountTax: Number(invoiceAccount.amountTax || 0),
|
|
amountGross: Number.isFinite(Number(invoiceAccount.amountGross))
|
|
? Number(invoiceAccount.amountGross)
|
|
: Number(invoiceAccount.amountNet || 0) + Number(invoiceAccount.amountTax || 0)
|
|
}))
|
|
})
|
|
|
|
const directBookings = filteredAccountStatementAllocations.value
|
|
.filter((allocation) => getStatementAllocationImmediateExpenseAmount(allocation) > 0)
|
|
.filter((allocation) => sameId(allocation.account?.id || allocation.account, account.id))
|
|
.map((allocation) => {
|
|
const amount = Number(allocation.amount || 0)
|
|
|
|
return {
|
|
type: "statementallocation",
|
|
amountNet: amount,
|
|
amountTax: 0,
|
|
amountGross: amount
|
|
}
|
|
})
|
|
|
|
const bookings = [...invoiceBookings, ...directBookings]
|
|
|
|
if (bookings.length === 0) {
|
|
return null
|
|
}
|
|
|
|
const net = bookings.reduce((sum, booking: any) => sum + Number(booking.amountNet || 0), 0)
|
|
const tax = bookings.reduce((sum, booking: any) => sum + Number(booking.amountTax || 0), 0)
|
|
const gross = bookings.reduce((sum, booking: any) => {
|
|
const amountGross = Number(booking.amountGross)
|
|
return sum + (Number.isFinite(amountGross) ? amountGross : Number(booking.amountNet || 0) + Number(booking.amountTax || 0))
|
|
}, 0)
|
|
|
|
return {
|
|
id: account.id,
|
|
number: account.number || "-",
|
|
label: account.label || account.name || "-",
|
|
bookings: bookings.length,
|
|
net: Number(net.toFixed(2)),
|
|
tax: Number(tax.toFixed(2)),
|
|
gross: Number(gross.toFixed(2))
|
|
}
|
|
})
|
|
.filter(Boolean)
|
|
.sort((left: any, right: any) => Math.abs(Number(right.gross)) - Math.abs(Number(left.gross)))
|
|
})
|
|
|
|
const ownAccountRows = computed(() => {
|
|
return ownAccounts.value
|
|
.map((account) => {
|
|
const bookings = filteredStatementAllocations.value.filter((allocation) => sameId(allocation.ownaccount?.id || allocation.ownaccount, account.id))
|
|
|
|
if (bookings.length === 0) {
|
|
return null
|
|
}
|
|
|
|
const income = bookings.reduce((sum, booking) => {
|
|
const amount = Number(booking.amount || 0)
|
|
return amount > 0 ? sum + amount : sum
|
|
}, 0)
|
|
|
|
const expenses = bookings.reduce((sum, booking) => {
|
|
const amount = Number(booking.amount || 0)
|
|
return amount < 0 ? sum + Math.abs(amount) : sum
|
|
}, 0)
|
|
|
|
const balance = bookings.reduce((sum, booking) => sum + Number(booking.amount || 0), 0)
|
|
|
|
return {
|
|
id: account.id,
|
|
number: account.number || "-",
|
|
label: account.name || account.label || "-",
|
|
bookings: bookings.length,
|
|
income: Number(income.toFixed(2)),
|
|
expenses: Number(expenses.toFixed(2)),
|
|
balance: Number(balance.toFixed(2))
|
|
}
|
|
})
|
|
.filter(Boolean)
|
|
.sort((left: any, right: any) => Math.abs(Number(right.balance)) - Math.abs(Number(left.balance)))
|
|
})
|
|
|
|
const setupPage = async () => {
|
|
loading.value = true
|
|
|
|
try {
|
|
const [docs, invoices, accountItems, ownAccountItems, allocationItems] = await Promise.all([
|
|
useEntities("createddocuments").select(),
|
|
useEntities("incominginvoices").select("*, vendor(*)"),
|
|
useEntities("accounts").selectSpecial(),
|
|
useEntities("ownaccounts").select(),
|
|
useEntities("statementallocations").select("*, bankstatement(*), createddocument(*), incominginvoice(*)")
|
|
])
|
|
|
|
createdDocuments.value = (docs || []).filter(isRelevantOutputDocument)
|
|
incomingInvoices.value = (invoices || []).filter(isRelevantInputInvoice)
|
|
accounts.value = accountItems || []
|
|
ownAccounts.value = ownAccountItems || []
|
|
statementAllocations.value = allocationItems || []
|
|
|
|
const firstYear = yearItems.value[0]?.value
|
|
if (firstYear && !yearItems.value.some((item) => item.value === selectedYear.value)) {
|
|
selectedYear.value = firstYear
|
|
}
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const openAccount = (rowLike: any) => {
|
|
const row = rowLike?.original || rowLike
|
|
if (row?.id) {
|
|
router.push(`/accounts/show/${row.id}`)
|
|
}
|
|
}
|
|
|
|
const openOwnAccount = (rowLike: any) => {
|
|
const row = rowLike?.original || rowLike
|
|
if (row?.id) {
|
|
router.push(`/standardEntity/ownaccounts/show/${row.id}`)
|
|
}
|
|
}
|
|
|
|
onMounted(setupPage)
|
|
</script>
|
|
|
|
<template>
|
|
<UDashboardNavbar title="BWA">
|
|
|
|
</UDashboardNavbar>
|
|
|
|
<UDashboardPanelContent class="min-w-0 space-y-6 overflow-x-hidden overflow-y-auto p-4 md:p-6">
|
|
<div class="flex flex-col gap-3 md:flex-row md:items-end">
|
|
<UFormField label="Jahr" class="w-full md:w-48">
|
|
<USelectMenu
|
|
v-model="selectedYear"
|
|
:items="yearItems"
|
|
value-key="value"
|
|
label-key="label"
|
|
class="w-full"
|
|
/>
|
|
</UFormField>
|
|
|
|
<UFormField label="Monat" class="w-full md:w-56">
|
|
<USelectMenu
|
|
v-model="selectedMonth"
|
|
:items="monthItems"
|
|
value-key="value"
|
|
label-key="label"
|
|
class="w-full"
|
|
/>
|
|
</UFormField>
|
|
</div>
|
|
|
|
<div class="grid min-w-0 gap-4 md:grid-cols-2 xl:grid-cols-5">
|
|
<UCard class="min-w-0">
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">Einnahmen netto</div>
|
|
<div class="mt-2 text-2xl font-semibold">{{ useCurrency(incomeTotal) }}</div>
|
|
<div class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
{{ incomeDocumentCount }} gebuchte Ausgangsbelege
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard class="min-w-0">
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">Ausgaben netto</div>
|
|
<div class="mt-2 text-2xl font-semibold">{{ useCurrency(expenseNetTotal) }}</div>
|
|
<div class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
Brutto: {{ useCurrency(expenseGrossTotal) }}
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard class="min-w-0">
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">Abschreibungen</div>
|
|
<div class="mt-2 text-2xl font-semibold text-amber-600 dark:text-amber-400">{{ useCurrency(depreciationTotal) }}</div>
|
|
<div class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
{{ depreciationRows.length }} periodisierte Buchungen
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard class="min-w-0">
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">Einnahmen Belege</div>
|
|
<div class="mt-2 text-2xl font-semibold">{{ incomeDocumentCount }}</div>
|
|
<div class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
Ausgangsbelege im Zeitraum
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard class="min-w-0">
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">Ausgaben Belege</div>
|
|
<div class="mt-2 text-2xl font-semibold">{{ expenseDocumentCount }}</div>
|
|
<div class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
Eingangsbelege plus direkte Buchungen
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
|
|
<div class="grid min-w-0 gap-4 md:grid-cols-2">
|
|
<UCard class="min-w-0">
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">Betriebsergebnis</div>
|
|
<div class="mt-2 text-2xl font-semibold" :class="operatingResult >= 0 ? 'text-primary-500' : 'text-error'">
|
|
{{ useCurrency(operatingResult) }}
|
|
</div>
|
|
<div class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
Einnahmen minus Ausgaben netto
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard class="min-w-0">
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">USt-Saldo</div>
|
|
<div class="mt-2 text-2xl font-semibold" :class="taxSummary.balance >= 0 ? 'text-amber-600 dark:text-amber-400' : 'text-primary-500'">
|
|
{{ useCurrency(taxSummary.balance) }}
|
|
</div>
|
|
<div class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
|
USt {{ useCurrency(taxSummary.outputTax) }} | Vorsteuer {{ useCurrency(taxSummary.inputTax) }}
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
|
|
<div class="grid min-w-0 gap-4 xl:grid-cols-2">
|
|
<UCard class="min-w-0">
|
|
<template #header>
|
|
<div class="font-semibold">USt-Details</div>
|
|
</template>
|
|
|
|
<div class="space-y-3 text-sm">
|
|
<div class="flex items-center justify-between">
|
|
<span>Netto 19% Ausgangsbelege</span>
|
|
<span>{{ useCurrency(taxSummary.output.net19) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span>USt 19%</span>
|
|
<span>{{ useCurrency(taxSummary.output.tax19) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span>Netto 7% Ausgangsbelege</span>
|
|
<span>{{ useCurrency(taxSummary.output.net7) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span>USt 7%</span>
|
|
<span>{{ useCurrency(taxSummary.output.tax7) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span>Steuerfrei</span>
|
|
<span>{{ useCurrency(taxSummary.output.net0 + taxSummary.input.net0) }}</span>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard class="min-w-0">
|
|
<template #header>
|
|
<div class="font-semibold">Vorsteuer-Details</div>
|
|
</template>
|
|
|
|
<div class="space-y-3 text-sm">
|
|
<div class="flex items-center justify-between">
|
|
<span>Netto 19% Eingangsbelege</span>
|
|
<span>{{ useCurrency(taxSummary.input.net19) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span>Vorsteuer 19%</span>
|
|
<span>{{ useCurrency(taxSummary.input.tax19) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span>Netto 7% Eingangsbelege</span>
|
|
<span>{{ useCurrency(taxSummary.input.net7) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span>Vorsteuer 7%</span>
|
|
<span>{{ useCurrency(taxSummary.input.tax7) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span>Steuerfrei</span>
|
|
<span>{{ useCurrency(taxSummary.input.net0) }}</span>
|
|
</div>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
|
|
<div class="grid min-w-0 gap-4 xl:grid-cols-2">
|
|
<UCard class="min-w-0">
|
|
<template #header>
|
|
<div class="flex items-center justify-between gap-3">
|
|
<span class="font-semibold">Buchungskonten</span>
|
|
<UBadge color="neutral" variant="soft">{{ accountRows.length }}</UBadge>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="min-w-0">
|
|
<UTable
|
|
:data="accountRows"
|
|
:columns="normalizeTableColumns(accountColumns)"
|
|
:loading="loading"
|
|
:on-select="openAccount"
|
|
>
|
|
<template #label-cell="{ row }">
|
|
<div class="truncate font-medium">{{ row.original.label }}</div>
|
|
</template>
|
|
|
|
<template #bookings-cell="{ row }">
|
|
<div class="text-right">{{ row.original.bookings }}</div>
|
|
</template>
|
|
|
|
<template #net-cell="{ row }">
|
|
<div class="text-right tabular-nums">{{ useCurrency(row.original.net) }}</div>
|
|
</template>
|
|
|
|
<template #tax-cell="{ row }">
|
|
<div class="text-right tabular-nums">{{ useCurrency(row.original.tax) }}</div>
|
|
</template>
|
|
|
|
<template #gross-cell="{ row }">
|
|
<div class="text-right font-medium tabular-nums">{{ useCurrency(row.original.gross) }}</div>
|
|
</template>
|
|
<template #empty>
|
|
<TableEmptyState label="Keine Buchungskonten im ausgewählten Zeitraum" />
|
|
</template>
|
|
</UTable>
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard class="min-w-0">
|
|
<template #header>
|
|
<div class="flex items-center justify-between gap-3">
|
|
<span class="font-semibold">Eigene Buchungskonten</span>
|
|
<UBadge color="neutral" variant="soft">{{ ownAccountRows.length }}</UBadge>
|
|
</div>
|
|
</template>
|
|
|
|
<div class="min-w-0">
|
|
<UTable
|
|
:data="ownAccountRows"
|
|
:columns="normalizeTableColumns(ownAccountColumns)"
|
|
:loading="loading"
|
|
:on-select="openOwnAccount"
|
|
>
|
|
<template #label-cell="{ row }">
|
|
<div class="truncate font-medium">{{ row.original.label }}</div>
|
|
</template>
|
|
|
|
<template #bookings-cell="{ row }">
|
|
<div class="text-right">{{ row.original.bookings }}</div>
|
|
</template>
|
|
|
|
<template #income-cell="{ row }">
|
|
<div class="text-right text-primary-500 tabular-nums">{{ useCurrency(row.original.income) }}</div>
|
|
</template>
|
|
|
|
<template #expenses-cell="{ row }">
|
|
<div class="text-right text-error tabular-nums">{{ useCurrency(row.original.expenses) }}</div>
|
|
</template>
|
|
|
|
<template #balance-cell="{ row }">
|
|
<div class="text-right font-medium tabular-nums" :class="row.original.balance >= 0 ? 'text-primary-500' : 'text-error'">
|
|
{{ useCurrency(row.original.balance) }}
|
|
</div>
|
|
</template>
|
|
<template #empty>
|
|
<TableEmptyState label="Keine eigenen Buchungen im ausgewählten Zeitraum" />
|
|
</template>
|
|
</UTable>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
|
|
<UCard class="min-w-0" v-if="depreciationRows.length > 0">
|
|
<template #header>
|
|
<div class="flex items-center justify-between gap-3">
|
|
<span class="font-semibold">Abschreibungen</span>
|
|
<UBadge color="warning" variant="soft">{{ depreciationRows.length }}</UBadge>
|
|
</div>
|
|
</template>
|
|
|
|
<UTable
|
|
:data="depreciationRows"
|
|
:columns="normalizeTableColumns(depreciationColumns)"
|
|
:loading="loading"
|
|
>
|
|
<template #amount-cell="{ row }">
|
|
<div class="text-right text-amber-600 dark:text-amber-400 tabular-nums">{{ useCurrency(row.original.amount) }}</div>
|
|
</template>
|
|
<template #empty>
|
|
<TableEmptyState label="Keine Abschreibungen im ausgewählten Zeitraum" />
|
|
</template>
|
|
</UTable>
|
|
</UCard>
|
|
</UDashboardPanelContent>
|
|
</template>
|