feat: create validated ZUGFeRD invoices in one console command

This commit is contained in:
admin committed 2026-10-16 09:29:08 +02:00
1 parent e0c15fc7f2
commit a643cf5227
30 files changed
+1509 -207

No files matched your search

+177 -79
View File
@@ -1,120 +1,218 @@
# Documentation
# Bedienung und technische Dokumentation
## Resilienz & Logs
- YAML wird strikt validiert (Pydantic) -> frühzeitige, klare Fehler.
- Pro Run: `out/logs/run.log` (DEBUG-level File Log, inkl. Stacktraces).
- Mustang stdout/stderr wird ebenfalls geloggt.
Stand: 16.10.2026 · Version 1.0
## PDF Rendering
Priorität:
1) WeasyPrint (wenn installiert)
2) wkhtmltopdf (wenn vorhanden)
## Einrichtung und persönliche Vorlagen
Wenn beides fehlt -> klarer Fehler.
Die README enthält den sofort nutzbaren Ein-Aufruf-Ablauf mit `einvoice --create`. Installation aus dem Repository
mit `pip install -e '.[pdf]'`; Java wird für Mustang benötigt. `setup-mustang`
installiert die geprüfte Referenzversion 2.26.0 aus Maven Central mit SHA1-Abgleich.
Der Abgleich prüft die Downloadintegrität; er ist keine Signaturprüfung.
`mustang-update` zeigt neuere Versionen, `mustang-update --apply` installiert sie.
Danach eine Testrechnung erneut prüfen. `JR_EINVOICE_MUSTANG_JAR` überschreibt den
Standardpfad. Downloads erfolgen nur bei Setup/Update; Generierung und Validierung
laufen lokal ohne Upload der Rechnungsdaten.
## Combine + Validate (Mustang) – standardmäßig aktiv
Combine:
`java -jar Mustang-CLI.jar --action combine --source invoice.pdf --source-xml factur-x.xml --out invoice.facturx.pdf --profile E`
Vorlagen werden in dieser Reihenfolge zusammengeführt:
Validate:
`java -jar Mustang-CLI.jar --no-notices --action validate --source invoice.facturx.pdf`
1. `examples/invoice.init.yaml`.
2. Versionierte `presets/seller.default.yaml` mit Beispieldaten.
3. Lokale `private/presets/seller.default.yaml`.
4. Lokale `private/presets/customers/<kunde>.yaml`, sonst die versionierte Kunden-Datei,
sonst `presets/customers.yaml`.
5. Rechnungsnummer, Datum, Zahlungsziel und Monats-/Stundenangaben des Befehls.
## YAML Schema
Siehe `examples/invoice.sample.yaml`
`JR_EINVOICE_HOME=/absoluter/pfad` setzt das private Datenverzeichnis.
`JR_EINVOICE_PRESETS=/absoluter/pfad` setzt ein anderes Verzeichnis für die lokalen
Vorlagen (gleiche Unterstruktur). Persönliche Daten nicht in die versionierten
Beispieldateien eintragen. `private/`, `invoices/`, die virtuelle Umgebung und
Ausgabeordner sind gitignoriert. Externe Rechnungsordner separat gegen versehentliches
Committen sichern. Die lokale IDV-Vorlage ist nur historischer Bestand; keine Empfängeradresse ist dafür erforderlich.
## Hinweis zum XML
Aktuell erzeugt `facturx.py` ein **minimal stabiles** EN16931 XML (Nummer/Datum/Währung/Gesamt).
Für maximale Empfänger-Kompatibilität erweitere `facturx.py` um Seller/Buyer/Lines/Tax breakdown etc.
## Befehle
`jr-einvoice --help` und `<befehl> --help` zeigen alle Parameter.
## Init Command
```bash
# Vollständig erzeugen und validieren (normaler Ablauf):
einvoice --create --Customer "Kundenfirma GmbH" \
--Entry "Geleistete Stunden Oktober 2026" --Volume 120 --Price "85,00 €"
`jr-einvoice init <invoice_dir>` erstellt:
# Expertenablauf für mehrere Positionen / manuell bearbeitete YAML-Dateien:
jr-einvoice init private/invoices --auto-folder --open
jr-einvoice generate /pfad/zum/rechnungsordner --json
jr-einvoice email /pfad/zum/rechnungsordner --to rechnung@empfaenger.example
```
- `invoice.yaml` (Vorlage)
- optional `assets/` Ordner (für Logos etc.)
- `.gitkeep` Dateien damit Git die Ordner behält
### Ein-Aufruf-Erstellung
Damit kannst du pro Rechnung einen neuen Ordner anlegen und direkt editieren.
`einvoice` ist ein eigener Konsolenbefehl. Die Optionen `--Customer`, `--Entry`,
`--Volume` und `--Price` sind erforderlich. Der Kunde wird über den Namen oder einen
angezeigten Schlüssel aus `einvoice --customers` gefunden. Für neue Kunden erfasst
der Befehl einmalig eine vollständige Rechnungsanschrift. Alternativ können diese
Daten mit `--Street`, `--PostalCode`, `--City` und optional `--Country`, `--Email`,
`--VatId` übergeben werden. In Skripten führen fehlende Daten zu einer konkreten
Fehlermeldung; es gibt keine erfundenen Adressen oder hängenden Eingabeaufforderungen.
Adressoptionen überschreiben keine bereits gespeicherten Kunden. Deren Stammdaten
gezielt in der lokalen Kundendatei bearbeiten. Der Kunde IDV ist ein ehemaliger
Kunde und hat keine besondere Rolle im Ablauf.
`--Price` ist der Nettopreis pro Einheit. Deutsche Schreibweise (`85,00 €`,
`1.234,56 EUR`) und ungegliederte Dezimalpunkt-Schreibweise (`85.00`) sind zulässig.
`--Volume` und `--Tax` verstehen dieselben Dezimalzahlen. Ohne Dezimalkomma gilt ein
Punkt als Dezimalpunkt; `1.234` bedeutet daher 1,234 und nicht 1234. Gemischte oder
ungültige Angaben werden abgewiesen. Standardwerte: `--Unit HUR`, `--Tax 19`,
`--DueDays 30`, Rechnungsdatum heute. Preis, Menge und Steuer werden ausdrücklich
angegeben bzw. verwenden diese dokumentierten Standardwerte; ein alter
kundenspezifischer Stundensatz wird nicht unbemerkt übernommen.
## Presets
Ein deutscher Monatsname plus Jahr in `--Entry`, z. B. „Oktober 2026“, ergibt den
vollständigen Leistungsmonat. `--Month YYYY-MM` setzt ihn ausdrücklich;
`--ServiceDate YYYY-MM-DD` überschreibt die automatische Erkennung durch ein
Einzeldatum. Beide expliziten Optionen zusammen sind nicht zulässig. Ohne Monat
oder Leistungsdatum gilt das Rechnungsdatum als Leistungsdatum. Die Konsole zeigt
den verwendeten Zeitraum; tatsächliche Leistungsdaten vor Versand prüfen.
- `presets/seller.default.yaml`: deine Standard-Seller- und Payment-Daten
- `presets/customers.yaml`: Kunden-Vorlagen (case-insensitive Keys)
Nummern werden unter Dateisperre im gewählten `--root` automatisch als
`JR-JJJJ-NNNN` vergeben. Alle gespeicherten YAML-Rechnungen auf der ersten Ebene
werden auf bereits verwendete Nummern geprüft; `--Number` kann eine eigene freie
Nummer setzen. Bei fehlgeschlagenem PDF-Build bleibt der Entwurf erhalten und
reserviert seine Nummer. Der Befehl meldet einen Fehler und veröffentlicht kein
fertiges Anhang-PDF. In `out/logs/run.log` stehen Details. Zum Korrigieren und
Wiederholen den betreffenden Entwurf mit `jr-einvoice generate` aufbauen; keinen
bereits versandten Datensatz verändern.
Init merge order:
1) `examples/invoice.init.yaml` (Basis)
2) `presets/seller.default.yaml`
3) `presets/customers.yaml` (wenn --customer gesetzt)
4) Smart defaults (heutiges Datum, due_days, reference)
Nach erfolgreicher PDF/A- und XML-Validierung wird das vollständige hybride PDF
zusätzlich als `Rechnung_<Nummer>.pdf` im Rechnungsordner bereitgestellt. Diese
Datei direkt an eine E-Mail anhängen. `out/invoice.pdf` ist lediglich die Sichtkopie.
`out/manifest.json` hält SHA-256-Prüfsummen einschließlich des Anhang-PDFs fest.
`--json` schreibt nur ein JSON-Ergebnis mit Nummer, Ordner, PDF, Prüfbericht, Log
und `technical_validation: valid` nach stdout. Keine automatische Mailübertragung.
## Assets / Logo
Die Konsole bestätigt „PDF/A und EN16931-XML technisch gültig“. Sie behauptet
keine vollständige rechtliche oder steuerliche Prüfung der Geschäftsdaten;
siehe [BMF-FAQ zur Validierung](https://www.bundesfinanzministerium.de/Content/DE/FAQ/e-rechnung.html).
`jr-einvoice init` kopiert standardmäßig `assets/logo.svg` nach `<invoice_dir>/assets/logo.svg`.
Das Template referenziert `assets/logo.svg` relativ – WeasyPrint bekommt dafür `base_url=<invoice_dir>`.
### Älterer Monatsentwurf
## Setup Mustang
Der ergänzende `jr-einvoice monthly`-Befehl bleibt für gespeicherte
Stundenvorlagen verfügbar; der neue Standard ist `einvoice --create`.
`jr-einvoice setup-mustang` lädt das Jar von Maven Central (repo1.maven.org) und prüft optional die `.sha1` Datei.
`monthly` übernimmt genau eine Stundenposition (`HUR`) aus der Kundenvorlage,
setzt deren Menge und deutschen Monatsnamen sowie Anfang/Ende des Monats. `--date`
setzt ein abweichendes Rechnungsdatum, ansonsten gilt heute. `--rate` überschreibt
den Nettostundensatz. Ungültige Monatsentwürfe hinterlassen keinen Rechnungsordner.
Explizite Nummern dürfen noch nicht vergeben sein. `monthly` prüft YAML-Rechnungen
auf der ersten Ordnerebene desselben Stamms, auch bei anderen Kunden, und verhindert
Überschreiben. Das optionale Nummernvorschlagen von `init --auto-folder` verwendet
weiter das Schema `YYYY-NNNN` aus Ordnernamen. Es kennt die bisherigen `YYYY-MM`-PDFs
nicht. Für den Wechsel daher eine freie Nummer ausdrücklich setzen; keine
bereits versandte Rechnung unter gleicher Nummer neu ausstellen. Die lokale
Ordneranlage ist per Dateisperre serialisiert; kein verteiltes Rechnungsregister.
## Auto-Rechnungsnummer
`init --print-path-only`, `generate --print-artifacts-only` und `generate --json`
schreiben maschinenlesbare Ergebnisse nach stdout; Logs laufen nach stderr.
`--open` verwendet `$EDITOR`, danach verfügbare Editoren. Ein unbekannter Kunde
führt zu einem Fehler statt zu einer Rechnung mit generischem Empfänger.
Wenn du bei `init` **keine** `--invoice-number` angibst, aber `--invoices-root` setzt, dann wird die nächste Nummer vorgeschlagen.
## YAML und Beträge
Standard-Pattern: `YYYY-NNNN` (z.B. 2026-0007). Gesucht wird in Ordnernamen unter `--invoices-root`.
Die vollständige Ausgangsvorlage steht in `examples/invoice.sample.yaml`.
Zusätzlich werden folgende optionale Meta-Felder unterstützt:
Implementierung: `jr_einvoice/invoice_number.py`.
```yaml
meta:
# ... invoice_number, invoice_date, service_date, currency ...
service_period_start: '2026-10-01'
service_period_end: '2026-10-31'
buyer_reference: 'Kundenreferenz'
order_reference: 'Bestellnummer'
```
## Customer Presets (Datei pro Kunde)
Alle Pflichtangaben einschließlich Leistungsbeschreibung und Zeitraum gelangen
in PDF und XML. Für Monatsrechnungen steht `service_date` auf dem Monatsende.
Bei Teilmonaten den tatsächlichen Zeitraum setzen. `payment.reference` ist der
Verwendungszweck, keine Bestellnummer. Kunden- und Bestellreferenzen stehen in den
eigenen Meta-Feldern. Ein B2B-Empfänger benötigt keine erfundene Leitweg-ID.
Bevorzugt: `presets/customers/<customer>.yaml`
Geld, Mengen und Steuersätze verwenden `Decimal`. Jede Nettoposition wird kaufmännisch
auf Cent gerundet; je Steuersatz wird die Steuer aus der Summe der gerundeten
Nettopositionen berechnet und auf Cent gerundet. PDF und XML verwenden dieselbe
Berechnung. Dezimalpunkt verwenden; besonders genaue Mengen/Preise in YAML als
Zeichenkette angeben (`quantity: '72.5'`). Leere Positionen, nicht endliche Zahlen,
negative Preise, Nullsteuersätze, unbekannte YAML-Felder und ungültige IBAN-Prüfsummen
werden abgewiesen. Der Validator prüft zusätzlich Format und Geschäftsregeln.
Fallback: `presets/customers.yaml` (Map `customers:`)
## PDF/A, XML und Validierung
## Mustang Update Check
WeasyPrint erzeugt PDF/A-3b. Mustang kombiniert diese Sichtkopie mit dem CII-D16B-XML,
Profil EN16931 (`--format fx --version 1 --profile E`). Die konkrete Kombination
wurde mit Mustang 2.26.0 einschließlich PDF/A und eingebettetem XML erfolgreich
geprüft. Die CLI-Formatversion `1` bezeichnet Factur-X, nicht ZUGFeRD 1.
Es wird keine Behauptung einer Zertifizierung oder vollständigen Unterstützung
aller neuen ZUGFeRD-2.5.2-Funktionen abgeleitet.
`jr-einvoice mustang-update` liest Maven metadata und vergleicht mit `tools/mustang/VERSION.txt`.
Mit `--apply` wird die neueste Version geladen und installiert (inkl. optional SHA1-Check).
Der frühere wkhtmltopdf-Fallback bleibt für eine Sichtkopie verfügbar, garantiert
aber kein PDF/A. Ohne gültiges PDF/A darf die abschließende Prüfung nicht bestehen.
Für den produktiven Weg WeasyPrint verwenden.
`generate` kombiniert und validiert immer. Die Python-Pipeline kann für interne
Anwendungsfälle auch nur Sichtkopie und XML erzeugen. Ein Erfolg erfordert einen
parsebaren Mustang-Bericht mit XML-Prüfabschnitt und ausschließlich gültigen
Zusammenfassungen einschließlich Gesamtergebnis. Ein Exitcode 0 allein genügt nicht.
Bei fehlgeschlagener Validierung bleibt der Bericht erhalten und das nicht freigegebene
hybride PDF wird entfernt. Alte Mailentwürfe werden bei einem neuen Build verworfen.
Pro Lauf stehen Details und Mustang-Ausgaben in `out/logs/run.log`.
## Auto Folder Name
Für Eingänge kann `validate` XML und hybride PDF prüfen. Eine normale PDF-Datei
wird nicht als strukturierte E-Rechnung akzeptiert. Quellen bleiben unverändert;
Berichte an einem eigenen Pfad aufbewahren. Inhaltliche Prüfung (Leistung, Parteien,
Betrag, Steuersatz, Doppelrechnung) bleibt zusätzlich erforderlich.
Mit `jr-einvoice init invoices --auto-folder --customer unicredit` wird ein Unterordner erzeugt:
## Versand und Aufbewahrung
`Rechnung_<invoice_number>_<customer>`
`email` erzeugt die Standardausgabe erneut aus `invoice.yaml` und legt erst nach
erfolgreicher Prüfung `invoice.eml` an. Absender kommt aus `seller.email`, Empfänger
aus `buyer.email` oder `--to`. Die Mail enthält nur das hybride PDF als Rechnungsanhang;
XML ist darin eingebettet. `X-Unsent: 1` kennzeichnet den Entwurf. Kein SMTP-Zugang,
kein Passwort und kein automatischer Versand sind nötig. Das konkrete Öffnen als
Entwurf hängt vom Mailprogramm ab. Versand und Zustellnachweis erfolgen dort.
Sanitizing/slugging in `jr_einvoice/folder_naming.py`.
`manifest.json` enthält SHA-256-Prüfsummen von YAML, XML, hybridem PDF, Bericht und
EML. Es unterstützt die Integritätskontrolle, schafft aber allein kein unveränderbares
Archiv. Nach Versand den vollständigen Rechnungsordner und die gesendete Originalmail
samt Versandnachweis in die bestehende gesicherte Ablage übernehmen. Versandte
Rechnungen nicht durch erneutes `generate` überschreiben. Für Berichtigungen einen
fachlich abgestimmten Prozess verwenden. Das Tool verwaltet keine Stornos.
## Open Editor
## Prüfung der Überarbeitung
Mit `--open` wird `invoice.yaml` nach dem Init in `$EDITOR` geöffnet.
Fallback-Reihenfolge: `code`, `codium`, `nano`, `vim`, `vi`.
```bash
python -m unittest discover -s tests -v
# Einschließlich echter PDF-/XML-/Mail-Prüfung durch Mustang:
JR_EINVOICE_INTEGRATION=1 python -m unittest discover -s tests -v
```
Die Tests decken deutschen Preisparser, neue/wiederverwendete Kunden, Ein-Aufruf-Nummernvergabe,
Abbruch ohne fertigen PDF-Anhang sowie dezimale Rundung, mehrere Steuersätze, Pflichtdaten und ungültige
Zahlen/IBAN, XML-Inhalte, HTML-Escaping, Monatsgrenzen einschließlich Schaltjahr,
Nummernkollisionen, unbrauchbare oder widersprüchliche Validierungsberichte sowie
veraltete Mailentwürfe ab. Der echte Mustang-Lauf ist ein zusätzlicher Integrationstest
und erfordert Java und das lokale Jar.
## Script output
Lokal wurde eine Testrechnung mit den Juli-Beispielwerten erstellt: 75 Stunden zu
85 EUR → 6.375,00 EUR netto + 1.211,25 EUR USt = 7.586,25 EUR. Nummer `TEST-2026-07`,
keine neue echte Rechnung. Der Mailtest verwendet die nicht zustellbare Adresse
`rechnung@example.invalid`. Die alten PDF-Rechnungen wurden nicht verändert.
- `--print-path` prints the created folder path after the panel
- `--print-path-only` prints only the path (no rich output)
## Bewusster Funktionsumfang
## Auto open
- `--open` opens unconditionally (needs an editor available)
- `--open-auto` opens only when a GUI session is detected (DISPLAY/WAYLAND_DISPLAY) and stdout is a TTY
## Generate artifact output
Options:
- `--print-artifacts`: prints paths after the rich OK output
- `--print-artifacts-only`: prints only the paths (one per line) in this order:
1) invoice.pdf
2) factur-x.xml
3) invoice.facturx.pdf
4) validation/validate_report.xml
5) out/logs/run.log
- `--json`: prints a JSON object with keys:
- invoice_dir, out_dir, invoice_pdf, facturx_xml, facturx_pdf, validate_report, log
Normale positive Dienstleistungsrechnungen in EUR mit positiver Umsatzsteuer und mehreren
Steuersätzen sind unterstützt. Nicht implementiert sind steuerfreie Positionen,
Kleinunternehmer, Reverse Charge, Abschläge/Anzahlungen, Skonto/Zuschläge/Rabatte,
Gutschriften/Storno, Fremdwährungs-Steuerumrechnung, XRechnung-CIUS-Ausgabe,
Peppol, DATEV-API und automatischer Postfacheingang. Diese Fälle nicht durch bloße
Freitexthinweise nachbilden. Das vorhandene CII-XML ist keine XRechnung, auch wenn
beide Formate EN16931 verwenden. Aufbewahrung/Backups und fachliche Freigabe müssen
in den betrieblichen Ablauf eingebunden werden.