KI-AGENT: E-Rechnungen in der Belegvorbereitung verarbeiten

This commit is contained in:
2026-07-21 20:20:34 +02:00
parent 6c613857fa
commit 5e7a1f9aee
14 changed files with 708 additions and 79 deletions

View File

@@ -0,0 +1,93 @@
import { and, eq } from "drizzle-orm"
import type { FastifyInstance } from "fastify"
import { vendors } from "../../../db/schema"
import type { ParsedElectronicInvoice } from "./types"
const normalizeIdentifier = (value: unknown) => String(value || "").replace(/[^A-Za-z0-9]/g, "").toUpperCase()
const normalizeName = (value: unknown) => String(value || "").normalize("NFKD").replace(/[^A-Za-z0-9]/g, "").toUpperCase()
const paymentTypeByCode: Record<string, string> = {
"10": "Bar",
"30": "Überweisung",
"48": "Kreditkarte",
"49": "Lastschrift",
"57": "Sonstiges",
"58": "Überweisung",
"59": "Lastschrift",
}
export const findElectronicInvoiceVendor = async (
server: FastifyInstance,
tenantId: number,
invoice: ParsedElectronicInvoice,
) => {
const candidates = await server.db.select().from(vendors).where(and(
eq(vendors.tenant, tenantId),
eq(vendors.archived, false),
))
const vatId = normalizeIdentifier(invoice.seller.vatId)
if (vatId) {
const vatMatches = candidates.filter((vendor: any) => normalizeIdentifier(vendor.infoData?.ustid) === vatId)
if (vatMatches.length === 1) return vatMatches[0]
}
const sellerName = normalizeName(invoice.seller.name)
if (sellerName) {
const nameMatches = candidates.filter((vendor) => normalizeName(vendor.name) === sellerName)
if (nameMatches.length === 1) return nameMatches[0]
}
return null
}
export const mapElectronicInvoiceToIncomingInvoice = (
invoice: ParsedElectronicInvoice,
tenantId: number,
vendorId: number | null,
container: "pdf" | "xml",
attachmentName: string | null,
) => {
const warnings = [...invoice.validation.warnings]
if (!vendorId) warnings.push("Rechnungssteller konnte keinem Lieferanten eindeutig zugeordnet werden.")
const state = invoice.validation.errors.length > 0 || !vendorId ? "Prüfung erforderlich" : "Vorbereitet"
const descriptionParts = [
invoice.buyerReference ? `Käuferreferenz: ${invoice.buyerReference}` : null,
...invoice.items.map((item) => item.description),
].filter(Boolean)
return {
tenant: tenantId,
state,
vendor: vendorId,
reference: invoice.invoiceNumber,
date: invoice.invoiceDate,
dueDate: invoice.dueDate,
paymentType: paymentTypeByCode[invoice.paymentMeansCode || ""] || "Sonstiges",
description: descriptionParts.join("\n"),
accounts: invoice.items.map((item) => ({
account: null,
description: item.description,
amountNet: item.netAmount,
amountTax: item.taxAmount,
taxType: item.taxCategory === "AE" ? "13B" : item.taxRate === 0 ? "null" : String(item.taxRate),
amountGross: item.grossAmount,
costCentre: null,
quantity: item.quantity,
unitCode: item.unitCode,
})),
preparationSource: "e-invoice",
eInvoiceSyntax: invoice.syntax,
eInvoiceProfile: invoice.profileId,
eInvoiceValidation: {
errors: invoice.validation.errors,
warnings,
totals: invoice.totals,
seller: invoice.seller,
invoiceType: invoice.invoiceType,
container,
attachmentName,
},
}
}