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

85 lines
4.7 KiB
Markdown

---
status: Accepted
date: 2026-07-05
applyTo:
- "Fuchs/code/FuchsPdf.cs"
- "Fuchs/Services/FuchsPdfService.cs"
- "Fuchs/Services/InvoiceService.cs"
- "Fuchs/Services/ReminderService.cs"
- "eRechnungLib/**"
supersededBy: ""
---
# 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`/`InvoiceRegistration``eRechnungLib.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.