KI-AGENT: Kassenbucheintrag vereinfacht anlegen
This commit is contained in:
@@ -260,7 +260,7 @@ export default async function bankingRoutes(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Number.isFinite(cashbookId)) return reply.code(400).send({ error: "Ungültige Kasse." })
|
if (!Number.isFinite(cashbookId)) return reply.code(400).send({ error: "Ungültige Kasse." })
|
||||||
if (!body.date || !dayjs(body.date).isValid()) return reply.code(400).send({ error: "Bitte ein gültiges Buchungsdatum angeben." })
|
const bookingDate = body.date && dayjs(body.date).isValid() ? dayjs(body.date) : dayjs()
|
||||||
if (!Number.isFinite(Number(body.amount)) || Number(body.amount) <= 0) return reply.code(400).send({ error: "Der Betrag muss größer als 0 sein." })
|
if (!Number.isFinite(Number(body.amount)) || Number(body.amount) <= 0) return reply.code(400).send({ error: "Der Betrag muss größer als 0 sein." })
|
||||||
if (body.direction !== "income" && body.direction !== "expense") return reply.code(400).send({ error: "Bitte Einnahme oder Ausgabe auswählen." })
|
if (body.direction !== "income" && body.direction !== "expense") return reply.code(400).send({ error: "Bitte Einnahme oder Ausgabe auswählen." })
|
||||||
|
|
||||||
@@ -273,8 +273,11 @@ export default async function bankingRoutes(server: FastifyInstance) {
|
|||||||
|
|
||||||
if (!cashbook[0]) return reply.code(404).send({ error: "Kasse nicht gefunden." })
|
if (!cashbook[0]) return reply.code(404).send({ error: "Kasse nicht gefunden." })
|
||||||
|
|
||||||
const counterPayload = buildCashbookCounterPayload(String(body.counterType || ""), body.counterId)
|
const hasCounterInput = Boolean(body.counterType || body.counterId)
|
||||||
if (!counterPayload) return reply.code(400).send({ error: "Bitte ein Gegenkonto auswählen." })
|
const counterPayload = hasCounterInput
|
||||||
|
? buildCashbookCounterPayload(String(body.counterType || ""), body.counterId)
|
||||||
|
: null
|
||||||
|
if (hasCounterInput && !counterPayload) return reply.code(400).send({ error: "Bitte ein gültiges Gegenkonto auswählen." })
|
||||||
|
|
||||||
const signedAmount = body.direction === "income"
|
const signedAmount = body.direction === "income"
|
||||||
? Math.abs(Number(body.amount))
|
? Math.abs(Number(body.amount))
|
||||||
@@ -284,8 +287,8 @@ export default async function bankingRoutes(server: FastifyInstance) {
|
|||||||
const created = await server.db.transaction(async (tx) => {
|
const created = await server.db.transaction(async (tx) => {
|
||||||
const insertedStatements = await tx.insert(bankstatements).values({
|
const insertedStatements = await tx.insert(bankstatements).values({
|
||||||
account: cashbookId,
|
account: cashbookId,
|
||||||
date: dayjs(body.date).format("YYYY-MM-DD"),
|
date: bookingDate.format("YYYY-MM-DD"),
|
||||||
valueDate: dayjs(body.date).format("YYYY-MM-DD"),
|
valueDate: bookingDate.format("YYYY-MM-DD"),
|
||||||
amount: signedAmount,
|
amount: signedAmount,
|
||||||
tenant: req.user.tenant_id,
|
tenant: req.user.tenant_id,
|
||||||
text: description,
|
text: description,
|
||||||
@@ -296,18 +299,20 @@ export default async function bankingRoutes(server: FastifyInstance) {
|
|||||||
}).returning()
|
}).returning()
|
||||||
|
|
||||||
const statement = insertedStatements[0]
|
const statement = insertedStatements[0]
|
||||||
const insertedAllocations = await tx.insert(statementallocations).values({
|
const insertedAllocations = counterPayload
|
||||||
bankstatement: statement.id,
|
? await tx.insert(statementallocations).values({
|
||||||
amount: signedAmount,
|
bankstatement: statement.id,
|
||||||
tenant: req.user.tenant_id,
|
amount: signedAmount,
|
||||||
description,
|
tenant: req.user.tenant_id,
|
||||||
datevTaxKey: body.datevTaxKey ? String(body.datevTaxKey).trim() : null,
|
description,
|
||||||
...counterPayload,
|
datevTaxKey: body.datevTaxKey ? String(body.datevTaxKey).trim() : null,
|
||||||
}).returning()
|
...counterPayload,
|
||||||
|
}).returning()
|
||||||
|
: []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statement,
|
statement,
|
||||||
allocation: insertedAllocations[0],
|
allocation: insertedAllocations[0] || null,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,8 @@ const route = useRoute()
|
|||||||
|
|
||||||
const bankstatements = ref([])
|
const bankstatements = ref([])
|
||||||
const bankaccounts = ref([])
|
const bankaccounts = ref([])
|
||||||
const accounts = ref([])
|
|
||||||
const customers = ref([])
|
const customers = ref([])
|
||||||
const vendors = ref([])
|
const vendors = ref([])
|
||||||
const ownaccounts = ref([])
|
|
||||||
const entitybankaccounts = ref([])
|
const entitybankaccounts = ref([])
|
||||||
const createddocuments = ref([])
|
const createddocuments = ref([])
|
||||||
const incominginvoices = ref([])
|
const incominginvoices = ref([])
|
||||||
@@ -31,8 +29,6 @@ const savingCashbookBooking = ref(false)
|
|||||||
const loadingDocs = ref(true) // Startet im Ladezustand
|
const loadingDocs = ref(true) // Startet im Ladezustand
|
||||||
const suggestionsModalOpen = ref(false)
|
const suggestionsModalOpen = ref(false)
|
||||||
const selectedSuggestionRowId = ref(null)
|
const selectedSuggestionRowId = ref(null)
|
||||||
const counterSearch = ref("")
|
|
||||||
const expandedGroups = ref([])
|
|
||||||
|
|
||||||
const CASHBOOK_BANK_ID = "fedeo-cashbook"
|
const CASHBOOK_BANK_ID = "fedeo-cashbook"
|
||||||
|
|
||||||
@@ -62,22 +58,10 @@ const dateRange = ref({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const cashbookForm = reactive({
|
const cashbookForm = reactive({
|
||||||
date: $dayjs().format("YYYY-MM-DD"),
|
|
||||||
direction: "expense",
|
direction: "expense",
|
||||||
amount: null,
|
amount: null
|
||||||
counter: "",
|
|
||||||
datevTaxKey: "__none__",
|
|
||||||
description: ""
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const DATEV_TAX_KEY_ITEMS = [
|
|
||||||
{ value: "__none__", label: "Ohne Steuerschlüssel" },
|
|
||||||
{ value: "9", label: "9 - Vorsteuer 19 %" },
|
|
||||||
{ value: "8", label: "8 - Vorsteuer 7 %" },
|
|
||||||
{ value: "19", label: "19 - EU Vorsteuer 19 %" },
|
|
||||||
{ value: "18", label: "18 - EU Vorsteuer 7 %" }
|
|
||||||
]
|
|
||||||
|
|
||||||
const getCalendarValue = (value) => {
|
const getCalendarValue = (value) => {
|
||||||
if (!value) return undefined
|
if (!value) return undefined
|
||||||
|
|
||||||
@@ -98,13 +82,11 @@ const setDateRangeFieldToToday = (field) => {
|
|||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
loadingDocs.value = true
|
loadingDocs.value = true
|
||||||
try {
|
try {
|
||||||
const [statements, bankAccountItems, accountItems, customerItems, vendorItems, ownAccountItems, entityBankItems, documentItems, invoiceItems] = await Promise.all([
|
const [statements, bankAccountItems, customerItems, vendorItems, entityBankItems, documentItems, invoiceItems] = await Promise.all([
|
||||||
useEntities("bankstatements").select("*, statementallocations(*)", "valueDate", false),
|
useEntities("bankstatements").select("*, statementallocations(*)", "valueDate", false),
|
||||||
useEntities("bankaccounts").select(),
|
useEntities("bankaccounts").select(),
|
||||||
useEntities("accounts").selectSpecial("*", "number", true),
|
|
||||||
useEntities("customers").select(),
|
useEntities("customers").select(),
|
||||||
useEntities("vendors").select(),
|
useEntities("vendors").select(),
|
||||||
useEntities("ownaccounts").select(),
|
|
||||||
useEntities("entitybankaccounts").select(),
|
useEntities("entitybankaccounts").select(),
|
||||||
useEntities("createddocuments").select("*, statementallocations(*), customer(id,name)"),
|
useEntities("createddocuments").select("*, statementallocations(*), customer(id,name)"),
|
||||||
useEntities("incominginvoices").select("*, statementallocations(*), vendor(*)")
|
useEntities("incominginvoices").select("*, statementallocations(*), vendor(*)")
|
||||||
@@ -115,10 +97,8 @@ const setupPage = async () => {
|
|||||||
...account,
|
...account,
|
||||||
displayLabel: getBankAccountLabel(account)
|
displayLabel: getBankAccountLabel(account)
|
||||||
}))
|
}))
|
||||||
accounts.value = accountItems
|
|
||||||
customers.value = customerItems
|
customers.value = customerItems
|
||||||
vendors.value = vendorItems
|
vendors.value = vendorItems
|
||||||
ownaccounts.value = ownAccountItems
|
|
||||||
entitybankaccounts.value = entityBankItems
|
entitybankaccounts.value = entityBankItems
|
||||||
createddocuments.value = documentItems
|
createddocuments.value = documentItems
|
||||||
incominginvoices.value = invoiceItems.filter(i => i.state === "Gebucht")
|
incominginvoices.value = invoiceItems.filter(i => i.state === "Gebucht")
|
||||||
@@ -276,118 +256,25 @@ const currentCashbookBalance = computed(() => {
|
|||||||
.reduce((sum, statement) => sum + Number(statement.amount || 0), 0)
|
.reduce((sum, statement) => sum + Number(statement.amount || 0), 0)
|
||||||
return openingBalance + movementSum
|
return openingBalance + movementSum
|
||||||
})
|
})
|
||||||
const getIncomingInvoiceGross = (invoice) => Number((invoice.accounts || []).reduce((sum, account) => {
|
|
||||||
return sum + Number(account.amountNet || 0) + Number(account.amountTax || 0)
|
|
||||||
}, 0))
|
|
||||||
const getIncomingInvoiceOpenAmount = (invoice) => {
|
|
||||||
const gross = getIncomingInvoiceGross(invoice)
|
|
||||||
const allocated = Number((invoice.statementallocations || []).reduce((sum, allocation) => sum + Number(allocation.amount || 0), 0))
|
|
||||||
return Math.abs(gross) - Math.abs(allocated)
|
|
||||||
}
|
|
||||||
const buildCashbookEntries = (rows, type, labelBuilder) =>
|
|
||||||
(rows || []).map((item) => ({
|
|
||||||
key: `${type}:${item.id}`,
|
|
||||||
id: item.id,
|
|
||||||
type,
|
|
||||||
number: item.number || item.vendorNumber || item.customerNumber || item.reference || "",
|
|
||||||
name: item.label || item.name || item.vendor?.name || "",
|
|
||||||
label: labelBuilder(item),
|
|
||||||
typeLabel:
|
|
||||||
type === "account"
|
|
||||||
? "Sachkonten"
|
|
||||||
: type === "vendor"
|
|
||||||
? "Kreditoren"
|
|
||||||
: type === "customer"
|
|
||||||
? "Debitoren"
|
|
||||||
: type === "incominginvoice"
|
|
||||||
? "Eingangsbelege"
|
|
||||||
: "Zusätzliche Konten"
|
|
||||||
}))
|
|
||||||
const cashbookEntryGroups = computed(() => ([
|
|
||||||
{
|
|
||||||
key: "account",
|
|
||||||
label: "Sachkonten",
|
|
||||||
entries: buildCashbookEntries(accounts.value, "account", (item) => `${item.number} - ${item.label}`)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "vendor",
|
|
||||||
label: "Kreditoren",
|
|
||||||
entries: buildCashbookEntries(vendors.value, "vendor", (item) => `${item.vendorNumber || "ohne Nr."} - ${item.name}`)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "customer",
|
|
||||||
label: "Debitoren",
|
|
||||||
entries: buildCashbookEntries(customers.value, "customer", (item) => `${item.customerNumber || "ohne Nr."} - ${item.name}`)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "ownaccount",
|
|
||||||
label: "Zusätzliche Konten",
|
|
||||||
entries: buildCashbookEntries(ownaccounts.value, "ownaccount", (item) => `${item.number} - ${item.name}`)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "incominginvoice",
|
|
||||||
label: "Eingangsbelege",
|
|
||||||
entries: buildCashbookEntries(
|
|
||||||
incominginvoices.value.filter((invoice) => !invoice.archived && getIncomingInvoiceOpenAmount(invoice) > 0.004),
|
|
||||||
"incominginvoice",
|
|
||||||
(item) => `${item.reference || "Ohne Referenz"} - ${item.vendor?.name || "Ohne Lieferant"} - Offen ${displayCurrency(getIncomingInvoiceOpenAmount(item))}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]))
|
|
||||||
const groupedCashbookCounterEntries = computed(() =>
|
|
||||||
cashbookEntryGroups.value.map((group) => ({
|
|
||||||
...group,
|
|
||||||
entries: group.entries.filter((entry) => {
|
|
||||||
const search = normalizeSuggestionText(counterSearch.value)
|
|
||||||
if (!search) return true
|
|
||||||
return [entry.number, entry.name, entry.label, entry.typeLabel]
|
|
||||||
.some((value) => normalizeSuggestionText(value).includes(search))
|
|
||||||
})
|
|
||||||
})).filter((group) => group.entries.length > 0)
|
|
||||||
)
|
|
||||||
const selectedCashbookCounter = computed(() => cashbookEntryGroups.value.flatMap((group) => group.entries).find((entry) => entry.key === cashbookForm.counter))
|
|
||||||
const isCashbookGroupExpanded = (groupKey) => expandedGroups.value.includes(groupKey)
|
|
||||||
const toggleCashbookGroupExpanded = (groupKey) => {
|
|
||||||
if (expandedGroups.value.includes(groupKey)) {
|
|
||||||
expandedGroups.value = expandedGroups.value.filter((item) => item !== groupKey)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
expandedGroups.value = [...expandedGroups.value, groupKey]
|
|
||||||
}
|
|
||||||
const visibleCashbookEntries = (group) => {
|
|
||||||
if (group.entries.length <= 5 || isCashbookGroupExpanded(group.key)) return group.entries
|
|
||||||
return group.entries.slice(0, 5)
|
|
||||||
}
|
|
||||||
const saveCashbookBooking = async () => {
|
const saveCashbookBooking = async () => {
|
||||||
if (!selectedCashbookAccount.value || !cashbookForm.date || !cashbookForm.amount || !cashbookForm.counter) {
|
if (!selectedCashbookAccount.value || !cashbookForm.amount) {
|
||||||
toast.add({ title: "Bitte Kasse, Datum, Betrag und Gegenkonto auswählen.", color: "warning" })
|
toast.add({ title: "Bitte Kasse und Betrag auswählen.", color: "warning" })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const [counterType, counterId] = String(cashbookForm.counter).split(":")
|
|
||||||
savingCashbookBooking.value = true
|
savingCashbookBooking.value = true
|
||||||
try {
|
try {
|
||||||
await $api(`/api/banking/cashbooks/${selectedCashbookAccount.value.id}/bookings`, {
|
const created = await $api(`/api/banking/cashbooks/${selectedCashbookAccount.value.id}/bookings`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
date: cashbookForm.date,
|
|
||||||
direction: cashbookForm.direction,
|
direction: cashbookForm.direction,
|
||||||
amount: Number(cashbookForm.amount),
|
amount: Number(cashbookForm.amount)
|
||||||
counterType,
|
|
||||||
counterId,
|
|
||||||
datevTaxKey: cashbookForm.datevTaxKey === "__none__" ? null : cashbookForm.datevTaxKey,
|
|
||||||
description: cashbookForm.description
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
toast.add({ title: "Kassenbuchung erstellt." })
|
toast.add({ title: "Kassenbuchung erstellt." })
|
||||||
cashbookForm.amount = null
|
cashbookForm.amount = null
|
||||||
cashbookForm.counter = ""
|
|
||||||
cashbookForm.datevTaxKey = "__none__"
|
|
||||||
cashbookForm.description = ""
|
|
||||||
counterSearch.value = ""
|
|
||||||
expandedGroups.value = []
|
|
||||||
cashbookBookingModalOpen.value = false
|
cashbookBookingModalOpen.value = false
|
||||||
await setupPage()
|
await router.push(`/banking/statements/edit/${created.statement.id}`)
|
||||||
} finally {
|
} finally {
|
||||||
savingCashbookBooking.value = false
|
savingCashbookBooking.value = false
|
||||||
}
|
}
|
||||||
@@ -876,7 +763,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<PageLeaveGuard :when="isSyncing"/>
|
<PageLeaveGuard :when="isSyncing"/>
|
||||||
|
|
||||||
<UModal v-model:open="cashbookBookingModalOpen" :ui="{ width: 'sm:max-w-5xl' }">
|
<UModal v-model:open="cashbookBookingModalOpen" :ui="{ width: 'sm:max-w-lg' }">
|
||||||
<template #content>
|
<template #content>
|
||||||
<UCard>
|
<UCard>
|
||||||
<template #header>
|
<template #header>
|
||||||
@@ -896,27 +783,12 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-4">
|
||||||
<div class="grid gap-3 sm:grid-cols-3">
|
<div class="space-y-3">
|
||||||
<UFormField label="Buchungsdatum">
|
|
||||||
<UInput v-model="cashbookForm.date" type="date" />
|
|
||||||
</UFormField>
|
|
||||||
<UFormField label="Betrag">
|
<UFormField label="Betrag">
|
||||||
<UInput v-model="cashbookForm.amount" type="number" min="0" step="0.01" placeholder="0,00" />
|
<UInput v-model="cashbookForm.amount" type="number" min="0" step="0.01" placeholder="0,00" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
<UFormField label="DATEV-Steuerschlüssel">
|
<div class="grid gap-3 sm:grid-cols-2">
|
||||||
<USelect
|
|
||||||
v-model="cashbookForm.datevTaxKey"
|
|
||||||
:items="DATEV_TAX_KEY_ITEMS"
|
|
||||||
value-key="value"
|
|
||||||
label-key="label"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</UFormField>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid gap-6 xl:grid-cols-[220px_minmax(0,1fr)]">
|
|
||||||
<div class="space-y-3">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="w-full rounded-lg border px-4 py-3 text-left transition"
|
class="w-full rounded-lg border px-4 py-3 text-left transition"
|
||||||
@@ -940,60 +812,6 @@ onMounted(() => {
|
|||||||
<div class="text-sm text-gray-500">Bargeld wird aus der Kasse entnommen.</div>
|
<div class="text-sm text-gray-500">Bargeld wird aus der Kasse entnommen.</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="font-semibold text-gray-900 dark:text-white">Gegenkonto</h3>
|
|
||||||
<p class="text-sm text-gray-500">Sachkonto, Kreditor, Debitor, Eingangsbeleg oder zusätzliches Konto.</p>
|
|
||||||
</div>
|
|
||||||
<UInput v-model="counterSearch" icon="i-heroicons-magnifying-glass" placeholder="Gegenkonto durchsuchen..." class="max-w-sm" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="max-h-[360px] space-y-4 overflow-y-auto pr-1">
|
|
||||||
<div v-for="group in groupedCashbookCounterEntries" :key="group.key" class="space-y-2">
|
|
||||||
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500">{{ group.label }}</div>
|
|
||||||
<div class="grid gap-2 md:grid-cols-2">
|
|
||||||
<button
|
|
||||||
v-for="entry in visibleCashbookEntries(group)"
|
|
||||||
:key="entry.key"
|
|
||||||
type="button"
|
|
||||||
class="rounded-lg border px-3 py-2 text-left transition"
|
|
||||||
:class="cashbookForm.counter === entry.key
|
|
||||||
? 'border-primary-500 bg-primary-50 dark:bg-primary-950/30'
|
|
||||||
: 'border-gray-200 bg-white hover:border-gray-300 dark:border-gray-800 dark:bg-gray-900'"
|
|
||||||
@click="cashbookForm.counter = entry.key"
|
|
||||||
>
|
|
||||||
<div class="flex min-w-0 items-center gap-2">
|
|
||||||
<span class="font-mono text-sm">{{ entry.number }}</span>
|
|
||||||
<span class="truncate text-sm">{{ entry.name }}</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<UButton
|
|
||||||
v-if="group.entries.length > 5"
|
|
||||||
size="xs"
|
|
||||||
color="neutral"
|
|
||||||
variant="ghost"
|
|
||||||
:label="isCashbookGroupExpanded(group.key) ? 'Weniger anzeigen' : `${group.entries.length - 5} weitere anzeigen`"
|
|
||||||
@click="toggleCashbookGroupExpanded(group.key)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<UAlert
|
|
||||||
v-if="selectedCashbookCounter"
|
|
||||||
color="primary"
|
|
||||||
variant="soft"
|
|
||||||
icon="i-heroicons-arrows-right-left"
|
|
||||||
:title="cashbookForm.direction === 'income' ? `${selectedCashbookAccount?.datevNumber} an ${selectedCashbookCounter.number}` : `${selectedCashbookCounter.number} an ${selectedCashbookAccount?.datevNumber}`"
|
|
||||||
:description="`${selectedCashbookCounter.typeLabel.slice(0, -1)} ${selectedCashbookCounter.name} wird als Gegenkonto verwendet.`"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<UFormField label="Beschreibung">
|
|
||||||
<UTextarea v-model="cashbookForm.description" placeholder="z. B. Bareinkauf Büromaterial" autoresize class="w-full" />
|
|
||||||
</UFormField>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1003,7 +821,7 @@ onMounted(() => {
|
|||||||
Abbrechen
|
Abbrechen
|
||||||
</UButton>
|
</UButton>
|
||||||
<UButton color="primary" icon="i-heroicons-check" :loading="savingCashbookBooking" @click="saveCashbookBooking">
|
<UButton color="primary" icon="i-heroicons-check" :loading="savingCashbookBooking" @click="saveCashbookBooking">
|
||||||
Eintrag speichern
|
Anlegen und bearbeiten
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user