From 7d3e9a383f7ba3b122d890e1e8bd8886b1f78cd7 Mon Sep 17 00:00:00 2001 From: flfeders Date: Sun, 9 Aug 2026 17:14:53 +0200 Subject: [PATCH] fix: use corrected invoice total for bank allocation --- backend/src/routes/banking.ts | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/backend/src/routes/banking.ts b/backend/src/routes/banking.ts index 9fb60bb..ce25ef0 100644 --- a/backend/src/routes/banking.ts +++ b/backend/src/routes/banking.ts @@ -41,6 +41,60 @@ export default async function bankingRoutes(server: FastifyInstance) { const ManualInvoices = aliasedTable(incominginvoices, "manual_invoices") const ManualInvoiceVendors = aliasedTable(vendors, "manual_invoice_vendors") + const useCurrentIncomingInvoiceAmount = async (tenantId: number, allocation: any) => { + const invoiceId = Number(allocation.incominginvoice) + const statementId = Number(allocation.bankstatement) + if (!invoiceId || !statementId) return allocation + + const [[invoice], [statement], invoiceAllocations, statementAllocations] = await Promise.all([ + server.db.select().from(incominginvoices).where(and( + eq(incominginvoices.id, invoiceId), + eq(incominginvoices.tenant, tenantId) + )).limit(1), + server.db.select().from(bankstatements).where(and( + eq(bankstatements.id, statementId), + eq(bankstatements.tenant, tenantId) + )).limit(1), + server.db.select({ amount: statementallocations.amount }).from(statementallocations).where(and( + eq(statementallocations.incominginvoice, invoiceId), + eq(statementallocations.tenant, tenantId), + eq(statementallocations.archived, false) + )), + server.db.select({ amount: statementallocations.amount }).from(statementallocations).where(and( + eq(statementallocations.bankstatement, statementId), + eq(statementallocations.tenant, tenantId), + eq(statementallocations.archived, false) + )), + ]) + + if (!invoice || !statement) return allocation + + const accountTotals = (invoice.accounts as any[] || []).reduce((totals, account) => ({ + net: totals.net + Number(account.amountNet || 0), + tax: totals.tax + Number(account.amountTax || 0), + }), { net: 0, tax: 0 }) + const correctedTax = invoice.taxAmountOverride !== null && invoice.taxAmountOverride !== undefined && invoice.taxAmountOverride !== "" + ? Number(invoice.taxAmountOverride) + : accountTotals.tax + const legacyRemaining = Math.max(0, Math.abs(accountTotals.net + accountTotals.tax) + - invoiceAllocations.reduce((sum, item) => sum + Math.abs(Number(item.amount || 0)), 0)) + const correctedRemaining = Math.max(0, Math.abs(accountTotals.net + correctedTax) + - invoiceAllocations.reduce((sum, item) => sum + Math.abs(Number(item.amount || 0)), 0)) + const statementRemaining = Math.max(0, Math.abs(Number(statement.amount || 0)) + - statementAllocations.reduce((sum, item) => sum + Math.abs(Number(item.amount || 0)), 0)) + const requestedAmount = Number(allocation.amount || 0) + const legacyAssignment = Math.min(legacyRemaining, statementRemaining) + + // Nur den automatisch vorgeschlagenen Altbetrag ersetzen. Bewusst eingegebene Teilbeträge bleiben erhalten. + if (Math.abs(Math.abs(requestedAmount) - legacyAssignment) >= 0.005) return allocation + + const currentAssignment = Math.min(correctedRemaining, statementRemaining) + return { + ...allocation, + amount: Number((Math.sign(requestedAmount || (invoice.expense ? -1 : 1)) * currentAssignment).toFixed(2)), + } + } + const normalizeManualSide = (payload: any, keys: string[]) => keys.filter((key) => payload[key] !== null && payload[key] !== undefined && payload[key] !== "") @@ -1108,6 +1162,7 @@ export default async function bankingRoutes(server: FastifyInstance) { const { data: payload } = req.body as { data: any } const prepared = prepareStatementAllocationPayload(payload) if (prepared.error) return reply.code(400).send({ error: prepared.error }) + prepared.data = await useCurrentIncomingInvoiceAmount(req.user.tenant_id, prepared.data) const inserted = await server.db.insert(statementallocations).values({ ...prepared.data,