feat(accounting): Ausgangsrechnungen als ZUGFeRD-E-Rechnung (EN 16931) erzeugen

Rechnungen, Abschlags-, Storno- und Serienrechnungen erhalten beim
Fertigstellen ein eingebettetes factur-x.xml (CII, Profil EN 16931) samt
Factur-X-XMP-Metadaten. Das XML wird serverseitig aus dem gespeicherten
Beleg erzeugt; fehlende Pflichtangaben werden als Hinweis gemeldet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 10:15:23 +01:00
parent bd768e404d
commit 549b469a41
9 changed files with 726 additions and 8 deletions

View File

@@ -18,7 +18,8 @@ import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js"
import duration from "dayjs/plugin/duration.js";
import timezone from "dayjs/plugin/timezone.js";
import {generateTimesEvaluation} from "../modules/time/evaluation.service";
import {citys, files} from "../../db/schema";
import {citys, createddocuments, files} from "../../db/schema";
import { attachZugferdToPdf } from "../modules/einvoice/outgoingZugferd.service";
import {and, eq, isNull, not} from "drizzle-orm";
import {cancelManualGeneration, executeManualGeneration, finishManualGeneration} from "../modules/serialexecution.service";
import { updateOutgoingDocumentCostCentres } from "../modules/outgoing-document-cost-centres.service";
@@ -110,6 +111,8 @@ export default async function functionRoutes(server: FastifyInstance) {
const body = req.body as {
data: any
backgroundPath?: string
// Gespeicherter Beleg, aus dem die eingebettete E-Rechnung (ZUGFeRD) erzeugt wird
createdDocumentId?: number
}
const {type} = req.params as {type:string}
@@ -125,6 +128,17 @@ export default async function functionRoutes(server: FastifyInstance) {
body.data,
body.backgroundPath
)
if (body.createdDocumentId) {
const tenantId = (req as any).user.tenant_id
const [document] = await server.db
.select()
.from(createddocuments)
.where(and(eq(createddocuments.id, Number(body.createdDocumentId)), eq(createddocuments.tenant, tenantId)))
.limit(1)
if (!document) return reply.code(404).send({ error: "Beleg nicht gefunden" })
const result = await attachZugferdToPdf(server, tenantId, document, Buffer.from(pdf.base64, "base64"))
pdf = { ...pdf, base64: result.pdf.toString("base64"), einvoice: result.einvoice, einvoiceWarnings: result.warnings }
}
} else if(type === "timesheet") {
pdf = await createTimeSheetPDF(
server,