feat(accounting): Pflichtangaben der E-Rechnung vor dem Fertigstellen durchsetzen
All checks were successful
Build and Release FEDEO / verify (push) Successful in 59s
Build and Release FEDEO / build-and-release (push) Successful in 6m27s

Fehlende Pflichtangaben (Firmendaten, Steuer-ID, Kundenanschrift, IBAN,
§ 13b-USt-Id, Lastschriftmandat) erscheinen im Editor als blockierende
Fehler und verhindern Vorschau und Fertigstellung. Serienrechnungslauf,
MCP-Finalisierung und PDF-Erzeugung prüfen serverseitig vor der
Nummernvergabe bzw. Ablage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 11:24:28 +01:00
parent 549b469a41
commit e9697ef197
7 changed files with 224 additions and 55 deletions

View File

@@ -14,11 +14,34 @@ import {
tenants,
units,
} from "../../../db/schema"
import { buildZugferdInvoiceXml, ZUGFERD_DOCUMENT_TYPES, ZugferdInvoiceContext } from "./buildZugferdInvoice"
import { buildZugferdInvoiceXml, checkZugferdRequirements, ZUGFERD_DOCUMENT_TYPES, ZugferdInvoiceContext, ZugferdIssue } from "./buildZugferdInvoice"
import { embedZugferdXml } from "./embedZugferd"
export const isZugferdDocument = (document: any) =>
Boolean(document && ZUGFERD_DOCUMENT_TYPES.includes(document.type) && document.documentNumber)
export const isZugferdDocumentType = (type: unknown) => ZUGFERD_DOCUMENT_TYPES.includes(String(type))
export const isZugferdDocument = (document: any) => Boolean(document && isZugferdDocumentType(document.type) && document.documentNumber)
export class EInvoiceRequirementsError extends Error {
statusCode = 422
code = "EINVOICE_REQUIREMENTS"
constructor(public issues: ZugferdIssue[]) {
super(`Die E-Rechnung kann nicht erstellt werden: ${issues.map((issue) => issue.message.replace(/^E-Rechnung: /, "")).join("; ")}`)
}
}
export const breakingIssues = (issues: ZugferdIssue[]) => issues.filter((issue) => issue.type === "breaking")
/** Prüft die Pflichtangaben eines (auch ungespeicherten) Belegs. Andere Belegarten liefern keine Hinweise. */
export async function checkOutgoingEInvoice(server: FastifyInstance, tenantId: number, document: any, options: { requireNumber?: boolean } = {}) {
if (!document || !isZugferdDocumentType(document.type)) return [] as ZugferdIssue[]
const context = await loadZugferdContext(server, tenantId, document)
return checkZugferdRequirements(document, context, options)
}
/** Wirft EInvoiceRequirementsError, wenn Pflichtangaben fehlen. */
export async function assertOutgoingEInvoiceRequirements(server: FastifyInstance, tenantId: number, document: any, options: { requireNumber?: boolean } = {}) {
const blocking = breakingIssues(await checkOutgoingEInvoice(server, tenantId, document, options))
if (blocking.length) throw new EInvoiceRequirementsError(blocking)
}
const byId = async (server: FastifyInstance, table: any, id: unknown, tenantId: number) => {
if (id === null || id === undefined || id === "") return null
@@ -69,19 +92,15 @@ export async function loadZugferdContext(server: FastifyInstance, tenantId: numb
}
/**
* Macht aus der gerenderten Rechnungs-PDF eine ZUGFeRD-Rechnung. Fehler bei der E-Rechnung blockieren
* die Belegerstellung nicht: dann wird die unveränderte PDF mit einer Warnung zurückgegeben.
* Macht aus der gerenderten Rechnungs-PDF eine ZUGFeRD-Rechnung. Fehlende Pflichtangaben führen zu
* EInvoiceRequirementsError, damit keine Rechnung ohne gültige E-Rechnung abgelegt wird.
*/
export async function attachZugferdToPdf(server: FastifyInstance, tenantId: number, document: any, pdf: Buffer) {
if (!isZugferdDocument(document)) return { pdf, warnings: [] as string[], einvoice: false }
try {
const context = await loadZugferdContext(server, tenantId, document)
const { xml, warnings } = buildZugferdInvoiceXml(document, context)
const result = await embedZugferdXml(pdf, xml, { title: document.title || `Rechnung ${document.documentNumber}` })
if (warnings.length) server.log.warn({ documentId: document.id, warnings }, "E-Rechnung mit Hinweisen erzeugt")
return { pdf: result, warnings, einvoice: true }
} catch (error) {
server.log.error({ err: error, documentId: document.id }, "E-Rechnung konnte nicht erzeugt werden")
return { pdf, warnings: ["E-Rechnung konnte nicht erzeugt werden"], einvoice: false }
}
if (!isZugferdDocument(document)) return { pdf, issues: [] as ZugferdIssue[], einvoice: false }
const context = await loadZugferdContext(server, tenantId, document)
const { xml, issues } = buildZugferdInvoiceXml(document, context)
const blocking = breakingIssues(issues)
if (blocking.length) throw new EInvoiceRequirementsError(blocking)
const result = await embedZugferdXml(pdf, xml, { title: document.title || `Rechnung ${document.documentNumber}` })
return { pdf: result, issues, einvoice: true }
}