Emit invoices as validated ZUGFeRD (DATEV) and XRechnung (B2G)
Playwright Tests / test (pull_request) Has been cancelled

Completes the eRechnung output path: finalized invoices are emitted as a
ZUGFeRD/Factur-X EN 16931 hybrid (DATEV) or, when a Leitweg-ID is present, as
XRechnung 3.0 for B2G. Both are externally validated as ACCEPTED (0 errors) and
PDF/A-3B COMPLIANT against the ProcessWeb eInvoice service.

- ERechnungMapper: FdsInvoiceData -> EN 16931 model. Seller master data now from
  Fuchs:ERechnung:Seller config (VAT id BT-31, Steuernummer BT-32, optional
  Handelsregister BT-30, IBAN/BIC, contact). Adds payment terms BT-20/BT-9
  (BR-CO-25), buyer VAT id, §13b reverse charge, and the service date/period
  (BT-72 / BG-14) parsed from ProvisionPeriod (BR-DE-TMP-32). B2G -> XRechnung
  profile with the required electronic addresses/contact.
- ERechnungValidator: client for POST /validatepdf (EN 16931 XML + PDF/A-3 in
  one call). A pure "scenario not matched" with zero errors is not treated as a
  hard failure; real errors optionally withhold the hybrid (FailOnError).
- ERechnungSettings: seller + validation config; wired in Program.cs; ServiceUrl
  in appsettings.
- Online editor: structured German-only dialogs for the recipient address
  (incl. Leitweg-ID) and the service date/period (single date or range).
- Bumps the eRechnungLib submodule to the CII rsm-namespace / PDF-A subtype fix.

Fuchs.Tests 514/514, eRechnungLib 97/97.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 00:05:24 +02:00
co-authored by Claude Opus 4.8
parent d94974ce06
commit 00e72c96d4
19 changed files with 847 additions and 80 deletions
+18 -6
View File
@@ -46,15 +46,27 @@ Editor (structured recipient dialog)
(no VAT id) is fully valid — B2C stays effortless. The free-text `SendToAddress` is composed
from it so the PDF layout is unchanged.
- **Mapping.** `ERechnungMapper` maps the Fuchs invoice to the EN 16931 model: seller = Fuchs
(constants mirroring the FuchsPdf letterhead — name, Steuernummer BT-32, IBAN/BIC), buyer =
the structured recipient, lines from the invoice items, §13b → reverse charge (category AE +
exemption reason). VAT breakdown and totals are recomputed by the library.
(from `Fuchs:ERechnung:Seller` config — name, address, Steuernummer BT-32, **USt-IdNr BT-31**,
IBAN/BIC, contact; defaults mirror the FuchsPdf letterhead), buyer = the structured recipient,
lines from the invoice items, §13b → reverse charge (category AE + exemption reason), payment
terms BT-20/BT-9, and the service date/period (BT-72 or BG-14) parsed from the structured
`ProvisionPeriod`. VAT breakdown and totals are recomputed by the library.
- **Profile selection (B2B/B2C vs B2G).** Default is ZUGFeRD **EN 16931** (DATEV). When the
recipient carries a **Leitweg-ID** (`InvoiceRecipientAddress.LeitwegId``BuyerReference` BT-10),
the invoice is B2G and emitted as **XRechnung** (`ZugferdProfile.XRechnung`, embedded
`xrechnung.xml`); the mapper then also fills the seller electronic address/contact (BT-34/BG-6)
and buyer electronic address (BT-49) that XRechnung requires.
- **Feature flag & fallback.** Emission is gated by `Fuchs:ERechnung:Enabled` (off until fully
validated). `IERechnungService` returns `null` on disable **or any failure**, so
`RenderInvoicePdfBytesAsync` falls back to the plain Spire PDF/A — invoicing never breaks.
- **Formal verification.** `Fuchs:ERechnung:Validation:ServiceUrl` is the seam for an external
online veraPDF (PDF/A-3) + ZUGFeRD validator; until the URL is provisioned, verification is
reported as not-configured.
- **Formal verification.** `Fuchs:ERechnung:Validation` calls the ProcessWeb eInvoice service
(`POST {ServiceUrl}/validatepdf`, raw `application/pdf`) which checks the EN 16931 XML **and**
PDF/A-3 (veraPDF) in one call. Both the EN 16931-ZUGFeRD and the XRechnung 3.0 output are
externally **ACCEPTED** (0 errors) and **PDF/A-3B COMPLIANT**. Getting there required fixing two
eRechnungLib defects (CII root children must be `rsm:` not `ram:`; embedded-file `/Subtype` MIME
encoding) and completing the mapper (seller VAT id BT-31, payment terms BT-20/BT-9). The
validator's target feature scope is documented in
[`../eRechnung-Validator-Requirements.md`](../eRechnung-Validator-Requirements.md).
## Key files
- `Fuchs/Services/InvoiceRecipientAddress.cs` — structured buyer address, composition, conformity.
+3 -1
View File
@@ -95,7 +95,9 @@ payload; see `EVAL_live_invoice_editing.md` for the rationale.
is edited via a **structured dialog** (`$inv.eAddress`: name, street, PLZ, city,
country, optional VAT id) prefilled from `fds__prepInvoice`'s `invoiceaddressData`;
it drives the EN 16931 eRechnung and composes the free-text `SendToAddress` for the
PDF (see [`Concepts/erechnung-output.md`](Concepts/erechnung-output.md)).
PDF (see [`Concepts/erechnung-output.md`](Concepts/erechnung-output.md)). The **service
date/period** (Leistungsdatum/-zeitraum) is likewise a structured German-date dialog
(`$inv.eProvisionPeriod`) — a single date or a from/to range — mapped to BT-72 / BG-14.
- **§13b reverse-charge** toggle (`$inv.sp13b`) — suppresses VAT lines/columns.
- **Set-pricing display mode** (`$inv.ssetmode` / `setSetmode`) — `SetPrice`
(default) / `SetOnly`; see `INVOICE_SET_PRICING.md`. Purely presentational
@@ -0,0 +1,144 @@
# Anforderungen an den eInvoice-Validierungsdienst
**Adressat:** Betreiber des Validierungsdienstes `https://api.processweb.de/api/eInvoice`
**Kontext:** Das Fuchs-Intranet erzeugt Rechnungen als ZUGFeRD/Factur-X- bzw. XRechnung-Hybrid
(PDF/A-3 mit eingebetteter CII-XML) und ruft nach der Erzeugung `POST /validatepdf` auf, um
**formale PDF/A-3-Konformität** und **EN 16931-/XRechnung-Regelkonformität** zu prüfen.
**Zielbild:** Der Validator soll **maximalen Funktionsumfang** haben — jede in Deutschland/EU
praktisch vorkommende E-Rechnung (alle gängigen Syntaxen, Profile und Versionen) erkennen,
korrekt klassifizieren und gegen die passenden Regelwerke prüfen.
> **Status 2026-07:** Die ursprüngliche Kernlücke (CII-Dokumente wurden nicht klassifiziert) ist
> behoben — sowohl EN 16931-ZUGFeRD als auch XRechnung 3.0 (CII) werden inzwischen erkannt und
> geprüft (`scenarioMatched=true`, angereichertes Response-Schema). Dieses Dokument bleibt als
> Referenz für den angestrebten **vollen** Funktionsumfang (Abschnitt 4).
---
## 1. Referenz — was Fuchs emittiert
| Profil | Einbettung | GuidelineSpecifiedDocumentContextParameter/ID | Einsatz |
|---|---|---|---|
| ZUGFeRD EN 16931 (COMFORT) | `factur-x.xml` | `urn:cen.eu:en16931:2017` | B2B/B2C, DATEV (Standard) |
| XRechnung 3.0 (CII) | `xrechnung.xml` | `urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0` | B2G (Leitweg-ID vorhanden) |
Beide sind PDF/A-3 (veraPDF-COMPLIANT). Profilwahl automatisch: **Leitweg-ID vorhanden →
XRechnung**, sonst **EN 16931-ZUGFeRD**.
## 2. Kern-Anforderungen (umgesetzt, als Regressionsschutz dokumentiert)
### R1 — XRechnung 3.0 in CII matchen (B2G)
- Match-Kriterium (CII): `rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/`
`ram:GuidelineSpecifiedDocumentContextParameter/ram:ID` =
`urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0`
- Erwartung: `documentType` gesetzt, `scenarioMatched=true`, EN 16931- **und** XRechnung-Schematron.
### R2 — Reines EN 16931 (CII) matchen (ZUGFeRD/DATEV)
- Match-Kriterium (CII): `…/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID` =
`urn:cen.eu:en16931:2017` (ohne XRechnung-CIUS-Zusatz).
- Prüftiefe: **nur** das EN 16931-Kern-Schematron (nicht die XRechnung-CIUS-Regeln).
### R3 — Einbettung aus dem Hybrid-PDF robust extrahieren
`/validatepdf` muss die CII-Rechnungs-XML unabhängig vom Dateinamen aus dem `/AF`-Array ziehen
(`factur-x.xml`, `xrechnung.xml`, Altname `zugferd-invoice.xml`). Das Match erfolgt über den
XML-Inhalt/die CustomizationID, nicht über den Dateinamen.
> Historischer Hinweis: Solange die drei CII-Wurzelkinder fälschlich im `ram:`- statt
> `rsm:`-Namespace standen (Fuchs-seitiger Bug, behoben), konnte kein Validator die CustomizationID
> extrahieren. Der Match muss auf `rsm:ExchangedDocumentContext` greifen.
---
## 3. (frei)
## 4. Maximaler Funktionsumfang
### 4.1 Support-Matrix (Syntax × Profil × Version)
| Standard / Profil | Syntax | Versionen | Regelwerk |
|---|---|---|---|
| **EN 16931** (Kern) | CII, UBL | :2017 (+ Amdt.) | EN16931-Schematron (ConnectingEurope) |
| **XRechnung** (CIUS) | CII, UBL | 2.0 2.3, **3.0.x**, (4.0) | EN16931 + XRechnung-Schematron (KoSIT) |
| **ZUGFeRD / Factur-X** | CII | 2.0 **2.4** (Factur-X 1.0/1.07/1.08) | EN16931 (BASIC/COMFORT), EXTENDED |
| ZUGFeRD-Profile | CII | MINIMUM, BASIC WL, BASIC, EN16931, EXTENDED | profilabhängig |
| **Peppol BIS Billing 3.0** | CII, UBL | 3.0.x | EN16931 + Peppol-Schematron |
| Dokumentarten | — | Rechnung (380), Gutschrift (381), Korrektur (384), Storno | UNCL1001 |
> Mindest-Priorität: **XRechnung 2.x** und **ZUGFeRD alle 2.x-Profile**; **Peppol BIS 3.0** und
> **XRechnung 4.0** als Ausbaustufe.
### 4.2 Validierungsschichten (je Dokument, getrennt ausgewiesen)
1. **Syntax / XSD** — CII (D16B/D22B/D25A) bzw. UBL (2.1/2.5).
2. **EN 16931-Schematron** — BR-*, BR-CO-*, BR-DEC-*.
3. **CIUS-Schematron** — XRechnung (BR-DE-*) bzw. Peppol (PEPPOL-*).
4. **Codelisten** — UNCL/EAS/ISO (Währung, Land, Einheit, USt-Kategorie, Zahlungsmittel).
5. **Rechen-/Konsistenzprüfung** — Summen, USt-Aufteilung.
6. **PDF/A** (veraPDF) — Flavour wählbar (`3b` Default; auch `3u`, `2b`, `1b`).
7. **ZUGFeRD/Factur-X-Container**`/AF`-Verknüpfung, `/AFRelationship`, Subtype `text/xml`,
XMP-`fx:*`-Metadaten und **Konsistenz** zwischen XMP-ConformanceLevel und Guideline/CustomizationID.
### 4.3 Auto-Erkennung (ohne Vorgabe durch den Aufrufer)
- **Syntax** aus dem Wurzelelement (`rsm:CrossIndustryInvoice` → CII; UBL `Invoice`/`CreditNote`).
- **Profil/Version** aus `GuidelineSpecifiedDocumentContextParameter/ram:ID` (CII) bzw.
`cbc:CustomizationID` + `cbc:ProfileID` (UBL).
- **Dokumentart** aus `TypeCode` (BT-3).
- Strengstes zutreffendes CIUS automatisch wählen; reines EN 16931 nur Kernregeln.
### 4.4 Erweiterte API-Endpunkte
Bestehend: `verify`, `validate`, `validatepdf`, `validatepdfa`, `doc`, `schema`, `mcp`. Ergänzen:
- **`POST /detect`** — nur Klassifikation (Syntax/Profil/Version/Dokumentart).
- **`POST /validate` mit UBL** — `application/xml` UBL akzeptieren (nicht nur CII).
- **`POST /validatebatch`** — mehrere Dokumente (multipart oder ZIP).
- **Report-Formate** über `Accept`/`?format=`: `json` (Default), `svrl`, `html`.
- **`flavour`-Parameter** auch bei `validatepdf` durchreichen.
- Weiterhin **HTTP 200** bei fachlicher Ablehnung; echte Transportfehler als 400/415/502/504.
### 4.5 Angereichertes Response-Schema
```jsonc
"xml": {
"syntax": "CII|UBL",
"standard": "EN16931|XRechnung|ZUGFeRD|Peppol",
"profile": "EN16931|EXTENDED|XRECHNUNG|BASIC|…",
"version": "3.0.2",
"documentType": "XRechnung 3.0 (CII)",
"customizationId": "urn:…xrechnung_3.0",
"scenarioMatched": true,
"documentData": { "invoiceId","issueDate","seller","buyer","leitwegId","totalGross","currency" },
"layers": [ { "id":"xsd|en16931|cius|codelist|calc", "name","valid","errorCount","warningCount" } ],
"messages": [ { "level":"error|warning|information","code","rule","message","location","clause","businessTerm" } ]
}
```
- `level` konsequent aus dem SVRL (`fatal`→error, sonst warning).
- `location` als XPath **und**, wo möglich, als Business Term (BT-/BG-Nummer).
- `recommendation` = `accept`, wenn `errorCount=0` (Warnungen erlaubt).
### 4.6 Betrieb / Nicht-funktional
- **Aktuelle Artefakt-Stände**: KoSIT (XRechnung 2.x/3.0.x/4.0), eInvoicing-EN16931, Peppol,
veraPDF — mit ausgewiesenen Versionen im Report (`engine`, `scenarioVersion`, `rulesetVersion`).
- **Health** (`/verify`) meldet geladene Szenario-/Ruleset-Versionen + Zustand jedes Sub-Validators.
- Robuste Größen-/Timeout-Grenzen dokumentiert; klare 413/504 statt stiller Fehler.
## 5. Abnahmekriterien
1. **ZUGFeRD EN 16931** (`factur-x.xml`, `urn:cen.eu:en16931:2017`): `xml.scenarioMatched=true`,
`documentType``"EN16931 (CII)"`, `pdfa.isCompliant=true`, bei sauberem Beleg `errorCount=0`.
2. **XRechnung 3.0** (`xrechnung.xml`, `…xrechnung_3.0`, mit Leitweg-ID): `scenarioMatched=true`,
`documentType``"XRechnung 3.0 (CII)"`, `pdfa.isCompliant=true`.
3. **Auto-Erkennung** (`POST /detect`): klassifiziert beide korrekt (Syntax/Profil/Version).
4. **UBL-XRechnung** (Fremdbeleg): wird erkannt und geprüft.
5. **PDF/A**: bleibt COMPLIANT (Flavour `3b`).
## 6. Fuchs-seitiges Verhalten
Der Fuchs-Client (`ProcessWebERechnungValidator`) wertet einen reinen **Szenario-Mismatch mit
`errorCount=0` nicht als harten Fehler** (die Rechnung wird nicht zurückgehalten); echte PDF/A-
oder XML-Regelfehler dagegen schon (bei `FailOnError=true` wird der Hybrid zurückgehalten →
Fallback auf Plain-PDF/A).