feat(mcp): Belegvorbereitung für Eingangsrechnungen anstoßen
- accounting.incoming_invoices.prepare startet die Vorbereitung für alle offenen oder ausgewählte Dateien und liefert angelegte Entwürfe samt Validierung sowie übersprungene Dateien - Vorbereitungslauf meldet sein Ergebnis und läuft je Mandant nacheinander, damit parallele Aufrufe keine doppelten Belege anlegen Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { outgoingAddressSchema, outgoingRowsSchema, outgoingReportSchema } from "../documentSchemas"
|
||||
import { limitFromArgs, offsetFromArgs, paginatedResult, paginationProperties } from "../pagination"
|
||||
import { and, desc, eq, ilike, or } from "drizzle-orm"
|
||||
import { and, desc, eq, ilike, inArray, or } from "drizzle-orm"
|
||||
import { randomUUID } from "node:crypto"
|
||||
import { validateBankAllocationCostCentre } from "../../modules/bank-allocation-details"
|
||||
import {
|
||||
@@ -1154,6 +1154,62 @@ export const accountingTools: McpTool[] = [
|
||||
return validateIncomingInvoiceData(rows[0] as Record<string, any>)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "accounting.incoming_invoices.prepare",
|
||||
title: "Belegvorbereitung starten",
|
||||
description: "Startet die Belegvorbereitung wie der Button „Belege vorbereiten“: Dateien vom Typ Eingangsrechnung ohne verknüpften Beleg werden ausgelesen (strukturierte E-Rechnung, sonst KI) und als Eingangsbeleg-Entwurf angelegt. Optional nur für bestimmte Datei-IDs. Kann je Datei einige Sekunden dauern.",
|
||||
requiredPermissions: ["accounting.incoming_invoices.write"],
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
fileIds: {
|
||||
type: "array",
|
||||
items: { type: "string", format: "uuid" },
|
||||
minItems: 1,
|
||||
maxItems: 50,
|
||||
description: "Optionale Datei-IDs; ohne Angabe alle offenen Eingangsrechnungsdateien.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
async handler(context, args) {
|
||||
let fileIds: string[] | undefined
|
||||
if (args.fileIds !== undefined) {
|
||||
if (!Array.isArray(args.fileIds) || !args.fileIds.length || args.fileIds.length > 50) {
|
||||
throw new Error("fileIds muss 1 bis 50 Datei-IDs enthalten")
|
||||
}
|
||||
if (args.fileIds.some((id) => typeof id !== "string" || !UUID_PATTERN.test(id))) {
|
||||
throw new Error("fileIds darf nur gültige UUIDs enthalten")
|
||||
}
|
||||
fileIds = [...new Set((args.fileIds as string[]).map((id) => id.toLowerCase()))]
|
||||
}
|
||||
|
||||
const result = await context.server.services.prepareIncomingInvoices.run(context.tenantId, { fileIds })
|
||||
if (result.error && !result.filesFound) throw new Error(result.error)
|
||||
|
||||
const invoiceIds = result.created.map((entry) => entry.invoiceId)
|
||||
const invoices = invoiceIds.length
|
||||
? await context.server.db
|
||||
.select()
|
||||
.from(incominginvoices)
|
||||
.where(and(eq(incominginvoices.tenant, context.tenantId), inArray(incominginvoices.id, invoiceIds)))
|
||||
: []
|
||||
const handled = new Set([...result.created, ...result.skipped].map((entry) => entry.fileId))
|
||||
|
||||
return {
|
||||
filesFound: result.filesFound,
|
||||
created: result.created.map((entry) => {
|
||||
const invoice = invoices.find((row) => row.id === entry.invoiceId) || null
|
||||
return { ...entry, invoice, validation: invoice ? validateIncomingInvoiceData(invoice as Record<string, any>) : null }
|
||||
}),
|
||||
skipped: [
|
||||
...result.skipped,
|
||||
...(fileIds || []).filter((id) => !handled.has(id))
|
||||
.map((fileId) => ({ fileId, reason: "Keine offene Eingangsrechnungsdatei (nicht gefunden, archiviert, anderer Dateityp oder bereits vorbereitet)" })),
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "accounting.incoming_invoices.create",
|
||||
title: "Eingangsbeleg erstellen",
|
||||
|
||||
Reference in New Issue
Block a user