Von der Rechnung zur fertigen Erklärung
Extraktion, Regelwerk und Dokumenterzeugung sind getrennte Module. Deshalb lässt sich die Datenquelle austauschen — heute das Rechnungs-PDF, später die Business-Central-API — ohne dass an Regeln oder Vorlage etwas geändert werden muss.
PPWR_<Nr>_Pos<n>.pdf.
Archiv/<Jahr>/<Rechnungsnr>/.
Grundsatz: lieber abbrechen als leer ausliefern
Fehlt ein Pflichtfeld — Rechnungsnummer, Datum, Bestellnummer, Kundenanschrift — bricht der Lauf mit einer klaren Meldung ab. Es entsteht in keinem Fall eine Erklärung mit leerem Feld.
Drei Regeln entscheiden, was erzeugt wird
Eine Erklärung je Artikelposition
Zwei Artikel auf einer Rechnung ergeben zwei PDFs. Die Kopfdaten sind auf allen Erklärungen einer Rechnung identisch, nur der Produktname unterscheidet sich.
Text-Vererbung bei bloßer Abmessung
Steht in einer Position nur noch eine Abmessung („S2 17 × 48789“), übernimmt sie den Beschreibungstext der Position darüber und behält die eigene Abmessung.
Keine Erklärung für Nicht-Ware
Fracht, Verpackungskosten, Rabatte, Skonto, Zwischensummen erzeugen nichts. Beim PDF-Weg über eine erweiterbare Musterliste, über die API über den Zeilentyp.
Zeilenumbrüche im Artikeltext erkennt das Parsing daran, dass in der Pos.-Spalte keine Nummer steht — eine umbrochene Beschreibung erzeugt also keine zusätzliche Erklärung.
Rechnung prüfen, bevor etwas erzeugt wird
Der Ablauf zeigt immer zuerst, was er gelesen hat. Erst wenn die Felder stimmen, werden PDFs
geschrieben. Dieselbe Prüfung gibt es auf der Kommandozeile mit
python -m ppwr erzeugen rechnung.pdf --trocken.
Rechnung rein, Erklärungen raus — zum Nachschauen
Die drei Testrechnungen und die daraus erzeugten Erklärungen zum Öffnen. Der Fixtext der Erklärungen stammt aus einer Muster-Vorlage und ist oben rot als solche gekennzeichnet — er wird durch die Originalvorlage von Diergarten ersetzt.
| Testrechnung | Fall | Erzeugte Erklärungen |
|---|---|---|
| Rechnung 103045 | zwei Artikel, dazu eine Frachtzeile | Pos1 · Pos2 |
| Rechnung 103046 | zwei Positionen ohne Vortext (nur Abmessung), dazu Verpackungskosten und Rabatt | Pos1 · Pos2 · Pos3 |
| Rechnung 103047 | eine Position, Artikeltext über mehrere Zeilen umbrochen | Pos1 |
Einrichtung auf einem Windows-Rechner
-
Python und LibreOffice installieren
Python 3.11 oder neuer von python.org — beim Installieren „Add python.exe to PATH“ anhaken. LibreOffice von de.libreoffice.org erledigt die PDF-Umwandlung. Ist auf dem Rechner Microsoft Word installiert, geht alternativ
pip install docx2pdf. -
Projekt einrichten
:: im Projektordner, einmalig python -m venv .venv .venv\Scripts\pip install -r requirements.txt -
Vorlage vorbereiten
Die Original-Vorlage
Template.docin den Projektordner legen. Das Werkzeug ersetzt die rot markierten Beispieltexte durch Platzhalter und stellt sie auf Schwarz — der übrige Text bleibt unverändert.:: 1. ansehen, was rot markiert ist .venv\Scripts\python tools\prepare_template.py Template.doc --auflisten :: 2. umwandeln .venv\Scripts\python tools\prepare_template.py Template.doc vorlagen\template.docx :: 3. gegenprüfen .venv\Scripts\python -m ppwr pruefe-vorlage vorlagen\template.docx
Erkennt die Automatik eine Stelle nicht, trägt man den Platzhalter in Word von Hand ein:
{{KUNDENANSCHRIFT}},{{RECHNUNGSDATUM}},{{KUNDENNAME}},{{PRODUKTNAME}},{{RECHNUNGSNUMMER}},{{BESTELLNUMMER}}. Steht „Amberg,“ in der Vorlage fest, den Lauf mit--ort ""starten, dann liefert der Platzhalter nur das Datum. -
Trockenlauf mit echten Rechnungen
Zeigt die erkannten Felder als Tabelle und schreibt nichts.
.venv\Scripts\python -m ppwr erzeugen beispiele --trocken
-
Erklärungen erzeugen
.venv\Scripts\python -m ppwr --log ppwr.log erzeugen eingang --archiv Archiv
Ergebnisse liegen in
ausgabe\, die Archivkopie unterArchiv\<Jahr>\<Rechnungsnummer>\. Für den Alltag genügt ein Doppelklick aufPPWR-Erklaerungen.bat: die Datei zeigt erst die Felder und fragt dann nach, bevor sie erzeugt. -
Optional: Ordner überwachen
.venv\Scripts\python -m ppwr ueberwachen eingang --erledigt erledigt --archiv Archiv
Neue PDFs im Eingangsordner werden verarbeitet und anschließend verschoben.
Wenn etwas nicht klappt
| Meldung | Ursache | Abhilfe |
|---|---|---|
| Rechnungsnummer nicht gefunden | Rechnungslayout weicht vom Profil ab | python -m ppwr debug rechnung.pdf zeigt alle erkannten Zeilen mit Koordinaten; danach die Suchmuster in ppwr/extract/layout.py anpassen |
| Positionstabelle nicht gefunden | Andere Spaltenüberschrift als „Beschreibung“ | Überschrift in beschreibungsspalte ergänzen oder beschreibungsspalte_x fest setzen |
| kein extrahierbarer Text | Rechnung ist ein Scan/Bild-PDF | Original-PDF aus Business Central verwenden oder auf die API-Variante wechseln |
| Vorlage enthält die Platzhalter nicht | Platzhalter fehlen oder sind durch Word in mehrere Textteile zerlegt | Platzhalter in Word in einem Zug neu tippen (nicht aus Teilen zusammensetzen), dann pruefe-vorlage |
| Keine PDF-Konvertierung verfügbar | LibreOffice nicht im Suchpfad | Pfad setzen: PPWR_SOFFICE=C:\Program Files\LibreOffice\program\soffice.exe |
Fünf Wege, das Ganze zu automatisieren
Vom Doppelklick bis zur ERP-Erweiterung. Die Stufen bauen aufeinander auf — jede ist für sich vollständig, keine muss übersprungen werden.
| Stufe | Weg | Aufwand | Laufende Kosten | Braucht | Bewertung |
|---|---|---|---|---|---|
| 1 | Überwachter Ordner + Aufgabenplanung | ≈ 30 Min | 0 € | PC oder Server, der läuft | sofort startklar |
| 2 | BC-API abfragen (Pull) | 1–2 Std + IT | 0 € | App-Registrierung in Entra ID | empfohlener Endzustand |
| 3 | PDF zurück an den Beleg hängen | + 1 Std | 0 € | Stufe 2 | löst den Versand elegant |
| 4 | Webhook aus BC (Push) | 0,5–1 Tag | 0 €* | erreichbarer HTTPS-Endpunkt | nur bei Bedarf „sofort“ |
| 5 | AL-Erweiterung direkt in BC | 3–5 Tage Partner | 0 € | AL-Entwickler / BC-Partner | sauberste, aber teuerste Lösung |
* sofern ein bestehender Server genutzt wird. Power Automate und Logic Apps bleiben bewusst außen vor: beide erzeugen laufende Lizenz- bzw. Verbrauchskosten und widersprechen der Vorgabe „kein Abo, keine Cloud-Automation“.
Stufe 1 — Überwachter Ordner, gesteuert über die Windows-Aufgabenplanung
Business Central legt die Rechnungs-PDFs entweder direkt in einen Ordner (Berichtsausgabe) oder sie landen über eine Outlook-Regel dort, die Anhänge aus dem Rechnungsversand speichert. Das Skript verarbeitet, was neu ist.
:: Aufgabe anlegen: täglich, alle 15 Minuten wiederholen
schtasks /create /tn "PPWR-Erklaerungen" /sc minute /mo 15 ^
/tr "C:\ppwr-generator\.venv\Scripts\python.exe -m ppwr --log C:\ppwr-generator\ppwr.log erzeugen C:\ppwr-generator\eingang --archiv C:\ppwr-generator\Archiv --weiter-bei-fehler" ^
/ru "%USERNAME%"
Alternativ dauerhaft mitlaufen lassen: python -m ppwr ueberwachen eingang --erledigt erledigt.
Vorteil dieser Stufe: kein Eingriff in Business Central, keine Freigabe durch die IT nötig.
Nachteil: hängt am Rechnungslayout — ändert sich das Layout, müssen die Suchmuster nachgezogen werden
(die Testsuite meldet das sofort).
Stufe 2 — Daten direkt aus der Business-Central-API holen
Der saubere Endzustand: kein PDF-Parsing, kein Layout-Risiko. Zusätzlicher Gewinn — die Regel
„keine Erklärung für Fracht und Rabatte“ ergibt sich aus dem Zeilentyp
(lineType = "Item") statt aus Textmustern.
Einmalige Einrichtung
- In Entra ID (früher Azure AD) eine App-Registrierung anlegen, Client-Secret erzeugen.
- API-Berechtigung Dynamics 365 Business Central → Application permissions → API.ReadWrite.All hinzufügen und Administrator-Zustimmung erteilen.
- In Business Central die Seite Microsoft Entra-Anwendungen öffnen, die Client-ID eintragen, Status auf Aktiviert setzen und den Berechtigungssatz D365 BASIC (lesend genügt) zuweisen.
- Zugangsdaten in die Datei
.enveintragen — sie gehört nicht ins Repository.
# Token holen (Client Credentials) POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token grant_type=client_credentials&scope=https://api.businesscentral.dynamics.com/.default # Rechnungen eines Zeitraums GET .../v2.0/{environment}/api/v2.0/companies({id})/salesInvoices ?$filter=invoiceDate ge 2026-07-01 and status eq 'Open' # Positionen dazu GET .../salesInvoices({invoiceId})/salesInvoiceLines
:: Aufruf im Projekt — dieselbe Ausgabe, dieselben Regeln wie beim PDF-Weg
python -m ppwr bc --top 20 --archiv Archiv
python -m ppwr bc --filter "number eq '103046'" --trocken
Das Modul ppwr/extract/bc_api_source.py ist fertig; die Feldzuordnung steht als
Dictionary am Anfang der Klasse. Weicht euer Setup ab — etwa Rechnungsanschrift über
billTo… statt sellTo… — ändert sich nur diese Zuordnung, kein Ablauf.
Stufe 3 — Die Erklärung als Beleganhang zurück nach Business Central
Statt eine zweite E-Mail zu verschicken, wandert die erzeugte Erklärung als Anhang an die gebuchte Rechnung. Damit ist sie im ERP auffindbar, geht auf demselben Weg wie die Rechnung nach draußen und die Aufbewahrung liegt dort, wo der Beleg ohnehin liegt.
# Anhang anlegen und Inhalt hochladen POST .../companies({id})/salesInvoices({invoiceId})/attachments { "fileName": "PPWR_103046_Pos2.pdf", "parentId": "{invoiceId}" } PATCH .../attachments({attachmentId})/attachmentContent # Rohdaten des PDF
Für gebuchte Belege heißt die Route salesInvoices im API-v2.0-Endpunkt bzw.
documentAttachments in der Standard-Web-Service-Variante — welche zur Verfügung
steht, hängt an eurer BC-Version; das prüft die IT einmalig mit einem Testaufruf.
Stufe 4 — Push statt Abfrage: Webhook aus Business Central
Business Central kann Änderungen an salesInvoices aktiv melden, statt abgefragt zu
werden. Die Erklärung entsteht dann Sekunden nach dem Buchen. Nötig ist ein von außen
erreichbarer HTTPS-Endpunkt, der die Registrierung beantwortet; Abonnements laufen nach drei
Tagen ab und müssen automatisch erneuert werden.
Lohnt sich, wenn ohnehin ein Server vorhanden ist und die Erklärung zeitgleich mit der Rechnung rausgehen soll. Für den Anfang bringt die 15-Minuten-Abfrage aus Stufe 2 dasselbe Ergebnis bei deutlich weniger Technik.
Stufe 5 — AL-Erweiterung: alles bleibt in Business Central
Ein BC-Partner baut die Erklärung als Word-Layout-Bericht in BC nach und hängt einen Subscriber an das Buchen der Verkaufsrechnung. Je Artikelzeile wird der Bericht ausgeführt, als Beleganhang gespeichert und über die Berichtsauswahl mit der Rechnung versendet.
- Dafür: kein zweites System, kein PC der laufen muss, funktioniert für jeden Mandanten und aus der Cloud heraus.
- Dagegen: Entwicklungsaufwand beim Partner, jede Textänderung an der Erklärung läuft künftig über einen Entwicklungsvorgang statt über Word.
- Empfehlung: nur angehen, wenn ohnehin Partner-Budget eingeplant ist — der Nutzen gegenüber Stufe 2 + 3 ist überschaubar.
Empfehlung in einem Satz
Mit Stufe 1 sofort in den Echtbetrieb gehen, parallel bei der IT die App-Registrierung anstoßen und dann auf Stufe 2 + 3 wechseln — damit hängt nichts mehr am Rechnungslayout und die Erklärung liegt am Beleg statt in einem Ordner.
Was noch von eurer Seite kommen muss
| Punkt | Aktueller Stand | Was gebraucht wird |
|---|---|---|
| Original-Vorlage | Muster-Vorlage im Projekt, alle Platzhalter gesetzt, Fixtext nur ein Platzhaltertext | Template.doc — danach ein Aufruf von prepare_template.py |
| Echte Rechnungen | Drei erzeugte Testrechnungen im BC-Layout, alle Regeln damit abgesichert | 2–3 echte PDFs, davon eins mit mehreren Artikeln und eins mit Position ohne Vortext |
| Dateibenennung | PPWR_<Rechnungsnummer>_Pos<n>.pdf |
Freigabe — oder Wunschformat, etwa mit Kundenname |
| „Amberg,“ in der Vorlage | Ort wird mitgeliefert, per --ort "" abschaltbar |
kurze Bestätigung, ob der Ort in der Vorlage fest steht |
| Format Rechnungsnummer | 103046 vom 21.07.2026 |
Freigabe oder abweichendes Wunschformat |
| Betriebsort | läuft auf jedem Windows-Rechner mit Python | Entscheidung: Bürorechner oder Server (bestimmt Stufe 1 gegen Stufe 2) |
| Versand (Phase 2) | SMTP-Versand mit festem Text und Archivkopie ist eingebaut, standardmäßig aus | Postfach + Empfängerregel: an die Rechnungsadresse, zusammen mit oder getrennt von der Rechnung |