const hasValue = (value: unknown) => value !== null && value !== undefined && value !== "" const stringArg = (args: Record, key: string) => { const value = args[key] return typeof value === "string" && value.trim() ? value.trim() : null } const statementAllocationTargetFields = [ "createddocument", "incominginvoice", "account", "ownaccount", "customer", "vendor", ] as const const manualDebitFields = ["account", "customer", "vendor", "ownaccount"] as const const manualCreditFields = ["contraAccount", "contraCustomer", "contraVendor", "contraOwnaccount"] as const const bookingModes = new Set(["expense", "depreciation_single", "depreciation_bundle"]) const depreciationMethods = new Set(["linear", "degressive"]) const requiredNumber = (args: Record, key: string) => { const value = Number(args[key]) if (!hasValue(args[key]) || !Number.isFinite(value)) throw new Error(`${key} muss eine gültige Zahl sein`) return value } const optionalNumericId = (args: Record, key: string) => { if (!hasValue(args[key])) return undefined const value = Number(args[key]) if (!Number.isInteger(value) || value <= 0) throw new Error(`${key} muss eine gültige ID sein`) return value } export const statementAllocationUuidArg = (args: Record, key: string) => { if (!hasValue(args[key])) return undefined const value = String(args[key]).trim() if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) { throw new Error(`${key} muss eine gültige UUID sein`) } return value } const validDate = (value: string | null) => Boolean(value && /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(`${value}T00:00:00Z`))) export const prepareStatementAllocationInput = (args: Record) => { const payload: Record = {} const amount = requiredNumber(args, "amount") const bankstatement = optionalNumericId(args, "bankstatement") const bookingMode = stringArg(args, "bookingMode") || "expense" if (!bookingModes.has(bookingMode)) throw new Error(`Ungültige Aufwandsart: ${bookingMode}`) payload.amount = amount if (args.description !== undefined) payload.description = stringArg(args, "description") payload.bookingMode = bookingMode if (args.datevTaxKey !== undefined) payload.datevTaxKey = stringArg(args, "datevTaxKey") for (const field of ["createddocument", "incominginvoice", "account", "customer", "vendor"] as const) { const value = optionalNumericId(args, field) if (value !== undefined) payload[field] = value } for (const field of ["ownaccount", "contraOwnaccount"] as const) { const value = statementAllocationUuidArg(args, field) if (value !== undefined) payload[field] = value } for (const field of ["contraAccount", "contraCustomer", "contraVendor"] as const) { const value = optionalNumericId(args, field) if (value !== undefined) payload[field] = value } if (bankstatement !== undefined) { const selectedTargets = statementAllocationTargetFields.filter((field) => hasValue(payload[field])) if (selectedTargets.length !== 1) { throw new Error("Für eine Bankzuweisung muss genau ein Ziel ausgewählt werden") } if (amount === 0) throw new Error("Der Zuweisungsbetrag darf nicht 0 sein") if (manualCreditFields.some((field) => hasValue(payload[field]))) { throw new Error("Haben-Konten sind nur bei manuellen Buchungen zulässig") } payload.bankstatement = bankstatement } else { const manualBookingDate = stringArg(args, "manualBookingDate") if (!validDate(manualBookingDate)) { throw new Error("Für manuelle Buchungen ist ein gültiges Buchungsdatum erforderlich") } if (amount <= 0) throw new Error("Für manuelle Buchungen muss der Betrag größer als 0 sein") const manualInvoiceSide = stringArg(args, "manualInvoiceSide") const hasInvoice = hasValue(payload.incominginvoice) if (hasInvoice && manualInvoiceSide !== "debit" && manualInvoiceSide !== "credit") { throw new Error("Für zugewiesene Eingangsbelege muss Soll oder Haben ausgewählt sein") } if (!hasInvoice && manualInvoiceSide) { throw new Error("manualInvoiceSide ist nur zusammen mit incominginvoice zulässig") } const debitCount = manualDebitFields.filter((field) => hasValue(payload[field])).length + (hasInvoice && manualInvoiceSide === "debit" ? 1 : 0) const creditCount = manualCreditFields.filter((field) => hasValue(payload[field])).length + (hasInvoice && manualInvoiceSide === "credit" ? 1 : 0) if (debitCount !== 1 || creditCount !== 1) { throw new Error("Für manuelle Buchungen muss genau ein Soll- und ein Haben-Konto ausgewählt werden") } payload.bankstatement = null payload.manualBookingDate = manualBookingDate if (hasInvoice) payload.manualInvoiceSide = manualInvoiceSide } if (bookingMode === "expense") return payload const depreciationMonths = requiredNumber(args, "depreciationMonths") const depreciationStartDate = stringArg(args, "depreciationStartDate") const depreciationMethod = stringArg(args, "depreciationMethod") || "linear" const residualValue = args.residualValue === undefined ? 0 : requiredNumber(args, "residualValue") if (!Number.isInteger(depreciationMonths) || depreciationMonths <= 0) { throw new Error("Die Abschreibungsdauer muss eine positive Anzahl Monate sein") } if (!validDate(depreciationStartDate)) throw new Error("Ein gültiger Abschreibungsbeginn ist erforderlich") if (!depreciationMethods.has(depreciationMethod)) { throw new Error(`Ungültige Abschreibungsmethode: ${depreciationMethod}`) } if (residualValue < 0) throw new Error("Der Restwert darf nicht negativ sein") payload.depreciationMonths = depreciationMonths payload.depreciationStartDate = depreciationStartDate payload.depreciationMethod = depreciationMethod if (args.depreciationLabel !== undefined) payload.depreciationLabel = stringArg(args, "depreciationLabel") payload.depreciationGroup = bookingMode === "depreciation_bundle" ? stringArg(args, "depreciationGroup") : null payload.residualValue = residualValue if (bookingMode === "depreciation_bundle" && !payload.depreciationGroup) { throw new Error("Für eine Sammelabschreibung ist eine Abschreibungsgruppe erforderlich") } return payload }