All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 50s
Build and Push Docker Images / build-website (push) Successful in 22s
Build and Push Docker Images / build-central-services-api (push) Successful in 22s
Build and Push Docker Images / build-central-services-admin (push) Successful in 22s
Build and Push Docker Images / build-docs (push) Successful in 21s
Build and Push Docker Images / build-frontend (push) Successful in 22s
142 lines
6.6 KiB
TypeScript
142 lines
6.6 KiB
TypeScript
const hasValue = (value: unknown) => value !== null && value !== undefined && value !== ""
|
|
|
|
const stringArg = (args: Record<string, unknown>, 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<string, unknown>, 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<string, unknown>, 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<string, unknown>, 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<string, unknown>) => {
|
|
const payload: Record<string, any> = {}
|
|
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
|
|
}
|