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 = { "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 } const addDaysToDate = (date: string, days: number) => { const parsed = new Date(`${String(date).slice(0, 10)}T00:00:00Z`) if (Number.isNaN(parsed.getTime())) return null parsed.setUTCDate(parsed.getUTCDate() + days) return parsed.toISOString().slice(0, 10) } 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, cashDiscountPercent: invoice.cashDiscount?.percent ?? null, cashDiscountDueDate: invoice.cashDiscount && invoice.invoiceDate ? addDaysToDate(invoice.invoiceDate, invoice.cashDiscount.days) : null, 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, }, } }