Files
Fuchs_Intranet/Fuchs/Docs/Decisions/0005-pdf-generation-and-erechnung.md
T
Stefan 4abf81cd7d
Playwright Tests / test (push) Has been cancelled
Refactor stored procedure and update project structure
- Modified the stored procedure `fds__admin_getReportCatalog.sql` to use the correct schema for `all_objects`.
- Added new folders and projects for `eRechnungLib` in the solution file `Fuchs_Intranet.slnx`, including validation and test projects.
- Updated submodule reference for `OCORE`.
- Added new submodule `eRechnungLib` with initial commit.
2026-07-06 00:01:35 +02:00

4.7 KiB

status, date, applyTo, supersededBy
status date applyTo supersededBy
Accepted 2026-07-05
Fuchs/code/FuchsPdf.cs
Fuchs/Services/FuchsPdfService.cs
Fuchs/Services/InvoiceService.cs
Fuchs/Services/ReminderService.cs
eRechnungLib/**

0005 — PDF generation, rendering, and eRechnung output

Context

Fuchs produces letters, invoices, and reminders as PDFs. The layout is a faithful port of the legacy VB module fuchs_fds_pdf.vb (letterhead, DIN address window, admin block, four-block footer with page numbers, invoice item table, GiroCode). The port had silently drifted — wrong letterhead image filenames (image1.png instead of the shipped image1.jpeg, which AddHeaderImage skips via File.Exists), a too-small bottom margin, and a reworked footer/admin block — so generated PDFs (e.g. the sprep invoice preview) rendered broken.

Separately, German B2B/B2G invoicing now requires eRechnung (structured electronic invoices). The company direction is that all invoices are emitted as eRechnung, not just human-readable PDFs.

Rendering also depends on Spire.PDF (commercial, licensed) for PDF/A conversion and rasterising PDFs to preview images.

Decision

  • PDF layout stays a 1:1 port of the legacy fuchs_fds_pdf.vb. FuchsPdf (MigraDoc/PdfSharp) is the single source of the visual layout. When changing the letter/invoice/reminder layout, compare against the legacy module and keep the letterhead assets (Fuchs/Data/image1-3.jpeg, image4.png, overlay.png), margins, sender line, label-over-value admin block, absolutely-positioned four-block footer, and Seite X von Y page numbers aligned with it. Reference the shipped asset filenames exactly — AddHeaderImage no-ops on a missing file, so a wrong extension silently drops a logo.
  • Rendering pipeline: FuchsPdf.DocToPdfBytes renders MigraDoc → PDF and post-processes to PDF/A; DocToImageCollection / BytesToImageCollection rasterise via Spire for the on-screen invoice preview (sprep/sedit). The OCORE OCOREFontResolver must be installed before any PdfSharp rendering.
  • Spire license comes from a managed secret. FuchsPdfService reads the license from configuration key SpirePdf_License (Key Vault secret fuchs--SpirePdf-License, registered in ManagedSecretKeys) and passes it to FuchsPdf.SetLicense(key). An embedded fallback key keeps local/dev rendering working without Key Vault.
  • eRechnung via eRechnungLib. The eRechnungLib submodule is the single library for structured invoices. Invoices are to be produced as eRechnung: build an eRechnungLib.Model.Invoice from the Fuchs invoice data, then EInvoice.CreateInvoice(model).ToZugferd(ZugferdProfile.EN16931, visualPdfBytes) to embed the CII XML into the FuchsPdf-rendered visual PDF (ZUGFeRD/Factur-X hybrid PDF/A-3), or ToXRechnung(...) for pure UBL/CII XML. The visual PDF is the FuchsPdf output — the two layers stay consistent (same amounts/parties). Default ConversionOptions runs model + XSD validation; use StrictValidation when a malformed invoice must withhold output rather than ship with findings.

Consequences

  • Layout edits must be validated against the legacy reference and the shipped Data/ assets; do not invent new positions/sizes. The pipeline test Fuchs.Tests/PdfPipelineTests.cs exercises the full chain (PdfSharp visual PDF → Spire preview images → eRechnung hybrid/XML) and must stay green.
  • Do not upgrade Spire.PDF beyond 8.10.5 (see project libraries rule). The license must never be hard-coded in new code paths — read it from SpirePdf_License.
  • Wiring the app's invoice flow to emit eRechnung is the follow-up: map FdsInvoiceData/InvoiceRegistrationeRechnungLib.Model.Invoice (parties, lines, VAT breakdown, payment/IBAN, buyer reference, seller electronic address) and persist/deliver the ZUGFeRD PDF and/or XRechnung XML.
  • eRechnungLib depends only on open-source libraries (PDFsharp/MigraDoc; optional SaxonCS-HE for Schematron) — no new commercial dependency for the structured output itself.

Alternatives considered

  • Hand-rolling ZUGFeRD/XRechnung XML in Fuchs: rejected — EN 16931 + CIUS validation, multiple profiles/syntaxes, and PDF/A-3 embedding are error-prone; a dedicated, validated library is safer.
  • Rewriting the PDF layout from scratch rather than porting the legacy module: rejected — the letterhead is a fixed corporate design; the legacy VB is the authoritative spec, so faithful porting avoids visual regressions.
  • Bundling a Spire license file / hard-coding the key: rejected in favor of the managed-secret path so the production key is centrally rotated and never committed, with the embedded key only as a dev fallback.