From 2a5071b15a27365774e03fafda4f3070ee2f88ec Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Thu, 28 May 2026 16:43:03 +0200 Subject: [PATCH] KI-AGENT: Kassenbucheintrag vereinfacht anlegen --- backend/src/routes/banking.ts | 33 ++--- frontend/pages/banking/index.vue | 206 ++----------------------------- 2 files changed, 31 insertions(+), 208 deletions(-) diff --git a/backend/src/routes/banking.ts b/backend/src/routes/banking.ts index aebe488..a7ff102 100644 --- a/backend/src/routes/banking.ts +++ b/backend/src/routes/banking.ts @@ -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 (!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 (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." }) - const counterPayload = buildCashbookCounterPayload(String(body.counterType || ""), body.counterId) - if (!counterPayload) return reply.code(400).send({ error: "Bitte ein Gegenkonto auswählen." }) + const hasCounterInput = Boolean(body.counterType || body.counterId) + 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" ? 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 insertedStatements = await tx.insert(bankstatements).values({ account: cashbookId, - date: dayjs(body.date).format("YYYY-MM-DD"), - valueDate: dayjs(body.date).format("YYYY-MM-DD"), + date: bookingDate.format("YYYY-MM-DD"), + valueDate: bookingDate.format("YYYY-MM-DD"), amount: signedAmount, tenant: req.user.tenant_id, text: description, @@ -296,18 +299,20 @@ export default async function bankingRoutes(server: FastifyInstance) { }).returning() const statement = insertedStatements[0] - const insertedAllocations = await tx.insert(statementallocations).values({ - bankstatement: statement.id, - amount: signedAmount, - tenant: req.user.tenant_id, - description, - datevTaxKey: body.datevTaxKey ? String(body.datevTaxKey).trim() : null, - ...counterPayload, - }).returning() + const insertedAllocations = counterPayload + ? await tx.insert(statementallocations).values({ + bankstatement: statement.id, + amount: signedAmount, + tenant: req.user.tenant_id, + description, + datevTaxKey: body.datevTaxKey ? String(body.datevTaxKey).trim() : null, + ...counterPayload, + }).returning() + : [] return { statement, - allocation: insertedAllocations[0], + allocation: insertedAllocations[0] || null, } }) diff --git a/frontend/pages/banking/index.vue b/frontend/pages/banking/index.vue index 11ed42b..d220bb9 100644 --- a/frontend/pages/banking/index.vue +++ b/frontend/pages/banking/index.vue @@ -14,10 +14,8 @@ const route = useRoute() const bankstatements = ref([]) const bankaccounts = ref([]) -const accounts = ref([]) const customers = ref([]) const vendors = ref([]) -const ownaccounts = ref([]) const entitybankaccounts = ref([]) const createddocuments = ref([]) const incominginvoices = ref([]) @@ -31,8 +29,6 @@ const savingCashbookBooking = ref(false) const loadingDocs = ref(true) // Startet im Ladezustand const suggestionsModalOpen = ref(false) const selectedSuggestionRowId = ref(null) -const counterSearch = ref("") -const expandedGroups = ref([]) const CASHBOOK_BANK_ID = "fedeo-cashbook" @@ -62,22 +58,10 @@ const dateRange = ref({ }) const cashbookForm = reactive({ - date: $dayjs().format("YYYY-MM-DD"), direction: "expense", - amount: null, - counter: "", - datevTaxKey: "__none__", - description: "" + amount: null }) -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) => { if (!value) return undefined @@ -98,13 +82,11 @@ const setDateRangeFieldToToday = (field) => { const setupPage = async () => { loadingDocs.value = true 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("bankaccounts").select(), - useEntities("accounts").selectSpecial("*", "number", true), useEntities("customers").select(), useEntities("vendors").select(), - useEntities("ownaccounts").select(), useEntities("entitybankaccounts").select(), useEntities("createddocuments").select("*, statementallocations(*), customer(id,name)"), useEntities("incominginvoices").select("*, statementallocations(*), vendor(*)") @@ -115,10 +97,8 @@ const setupPage = async () => { ...account, displayLabel: getBankAccountLabel(account) })) - accounts.value = accountItems customers.value = customerItems vendors.value = vendorItems - ownaccounts.value = ownAccountItems entitybankaccounts.value = entityBankItems createddocuments.value = documentItems incominginvoices.value = invoiceItems.filter(i => i.state === "Gebucht") @@ -276,118 +256,25 @@ const currentCashbookBalance = computed(() => { .reduce((sum, statement) => sum + Number(statement.amount || 0), 0) 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 () => { - if (!selectedCashbookAccount.value || !cashbookForm.date || !cashbookForm.amount || !cashbookForm.counter) { - toast.add({ title: "Bitte Kasse, Datum, Betrag und Gegenkonto auswählen.", color: "warning" }) + if (!selectedCashbookAccount.value || !cashbookForm.amount) { + toast.add({ title: "Bitte Kasse und Betrag auswählen.", color: "warning" }) return } - const [counterType, counterId] = String(cashbookForm.counter).split(":") savingCashbookBooking.value = true try { - await $api(`/api/banking/cashbooks/${selectedCashbookAccount.value.id}/bookings`, { + const created = await $api(`/api/banking/cashbooks/${selectedCashbookAccount.value.id}/bookings`, { method: "POST", body: { - date: cashbookForm.date, direction: cashbookForm.direction, - amount: Number(cashbookForm.amount), - counterType, - counterId, - datevTaxKey: cashbookForm.datevTaxKey === "__none__" ? null : cashbookForm.datevTaxKey, - description: cashbookForm.description + amount: Number(cashbookForm.amount) } }) toast.add({ title: "Kassenbuchung erstellt." }) cashbookForm.amount = null - cashbookForm.counter = "" - cashbookForm.datevTaxKey = "__none__" - cashbookForm.description = "" - counterSearch.value = "" - expandedGroups.value = [] cashbookBookingModalOpen.value = false - await setupPage() + await router.push(`/banking/statements/edit/${created.statement.id}`) } finally { savingCashbookBooking.value = false } @@ -876,7 +763,7 @@ onMounted(() => { - +