feat: create validated ZUGFeRD invoices in one console command
This commit is contained in:
1 parent
e0c15fc7f2
commit
a643cf5227
30 files changed
+1509
-207
No files matched your search
+177
-79
@@ -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.
|
||||
Reference in new issue
Block a user