import { FastifyInstance } from "fastify" import dayjs from "dayjs" import { getInvoiceDataFromGPT } from "../../utils/gpt" // Drizzle schema import { tenants, files, filetags, incominginvoices, } from "../../../db/schema" import { eq, and, isNull, not } from "drizzle-orm" export function prepareIncomingInvoices(server: FastifyInstance) { const processInvoices = async (tenantId:number) => { console.log("▶ Starting Incoming Invoice Preparation") const tenantsRes = await server.db .select() .from(tenants) .where(eq(tenants.id, tenantId)) .orderBy(tenants.id) if (!tenantsRes.length) { console.log("No tenants with autoPrepareIncomingInvoices = true") return } console.log(`Processing tenants: ${tenantsRes.map(t => t.id).join(", ")}`) // ------------------------------------------------------------- // 2️⃣ Jeden Tenant einzeln verarbeiten // ------------------------------------------------------------- for (const tenant of tenantsRes) { const tenantId = tenant.id // 2.1 Datei-Tags holen für incoming invoices const tagRes = await server.db .select() .from(filetags) .where( and( eq(filetags.tenant, tenantId), eq(filetags.incomingDocumentType, "invoices") ) ) .limit(1) const invoiceFileTag = tagRes?.[0]?.id if (!invoiceFileTag) { server.log.error(`❌ Missing filetag 'invoices' for tenant ${tenantId}`) continue } // 2.2 Alle Dateien laden, die als Invoice markiert sind aber NOCH keine incominginvoice haben const filesRes = await server.db .select() .from(files) .where( and( eq(files.tenant, tenantId), eq(files.type, invoiceFileTag), isNull(files.incominginvoice), eq(files.archived, false), not(isNull(files.path)) ) ) if (!filesRes.length) { console.log(`No invoice files for tenant ${tenantId}`) continue } // ------------------------------------------------------------- // 3️⃣ Jede Datei einzeln durch GPT jagen & IncomingInvoice erzeugen // ------------------------------------------------------------- for (const file of filesRes) { console.log(`Processing file ${file.id} for tenant ${tenantId}`) const data = await getInvoiceDataFromGPT(server,file, tenantId) if (!data) { server.log.warn(`GPT returned no data for file ${file.id}`) continue } // --------------------------------------------------------- // 3.1 IncomingInvoice-Objekt vorbereiten // --------------------------------------------------------- let itemInfo: any = { tenant: tenantId, state: "Vorbereitet" } if (data.invoice_number) itemInfo.reference = data.invoice_number if (data.invoice_date) itemInfo.date = dayjs(data.invoice_date).toISOString() if (data.issuer?.id) itemInfo.vendor = data.issuer.id if (data.invoice_duedate) itemInfo.dueDate = dayjs(data.invoice_duedate).toISOString() // Payment terms mapping const mapPayment: any = { "Direct Debit": "Einzug", "Transfer": "Überweisung", "Credit Card": "Kreditkarte", "Other": "Sonstiges", } if (data.terms) itemInfo.paymentType = mapPayment[data.terms] ?? data.terms // 3.2 Positionszeilen konvertieren if (data.invoice_items?.length > 0) { itemInfo.accounts = data.invoice_items.map(item => ({ account: item.account_id, description: item.description, amountNet: item.total_without_tax, amountTax: Number((item.total - item.total_without_tax).toFixed(2)), taxType: String(item.tax_rate), amountGross: item.total, costCentre: null, quantity: item.quantity, })) } // 3.3 Beschreibung generieren let description = "" if (data.delivery_note_number) description += `Lieferschein: ${data.delivery_note_number}\n` if (data.reference) description += `Referenz: ${data.reference}\n` if (data.invoice_items) { for (const item of data.invoice_items) { description += `${item.description} - ${item.quantity} ${item.unit} - ${item.total}\n` } } itemInfo.description = description.trim() // --------------------------------------------------------- // 4️⃣ IncomingInvoice erstellen // --------------------------------------------------------- const inserted = await server.db .insert(incominginvoices) .values(itemInfo) .returning() const newInvoice = inserted?.[0] if (!newInvoice) { server.log.error(`Failed to insert incoming invoice for file ${file.id}`) continue } // --------------------------------------------------------- // 5️⃣ Datei mit incominginvoice-ID verbinden // --------------------------------------------------------- await server.db .update(files) .set({ incominginvoice: newInvoice.id }) .where(eq(files.id, file.id)) console.log(`IncomingInvoice ${newInvoice.id} created for file ${file.id}`) } } return } return { run: async (tenant:number) => { await processInvoices(tenant) console.log("Incoming Invoice Preparation Completed.") } } }