Rechnungsautomatisierung mit Azure Functions: Vom SharePoint-Stammdatensatz zur fertigen E-Rechnung

TL;DR
- Mitglieds- und Zertifizierungsrechnungen für den SMI e.V. entstanden jahrelang von Hand. Das kostete Zeit und war fehleranfällig.
- Heute liest ein zeitgesteuerter Dienst auf Azure Functions die Stammdaten aus Microsoft 365 / SharePoint und legt die Rechnungen automatisch als Entwurf in Lexware Office an.
- Doppelbuchungen verhindere ich ohne zweite Datenbank, über einen deterministischen Marker in jeder Rechnung. Der Preis: Die Idempotenz hängt damit an einer organisatorischen Konvention, denn der Marker im
remark-Feld ist für die Buchhaltung tabu. - Vorübergehende API-Fehler, das Rate Limit von Lexware und unzuverlässige Server-Filter fängt der Dienst mit Retries, Throttling und einem Fallback ab.
- Heute läuft die Rechnungsstellung für rund 30 Mitgliedsunternehmen. Vertical Meters beziffert die Zeitersparnis der Geschäftsstelle auf rund 80 Prozent.
Wiederkehrende Rechnungen sind ein dankbares Ziel für Automatisierung. Sie folgen klaren Regeln, fallen in festen Intervallen an und kosten von Hand trotzdem erstaunlich viel Zeit. Genau diese Lage hatte der SMI e.V. (Standard Motor Interface), ein Industrieverein, den ich gemeinsam mit der Agentur Vertical Meters technisch betreue. Aus derselben Zusammenarbeit stammt die Power-Platform-Lösung für das Zertifizierungsmanagement, auf deren Daten dieser Rechnungslauf aufsetzt.
In diesem Bericht geht es weniger um die Funktionsliste als um eine Entscheidung: wie der Lauf erkennt, dass eine Rechnung bereits existiert, ohne dafür eine zweite Datenquelle zu pflegen. An diesem Punkt steht oder fällt eine Rechnungsautomatisierung.
Zwei Rechnungsarten, zwei getrennte Welten
Die Geschäftsstelle stellte zwei Arten von Rechnungen immer wieder neu aus: jährliche Mitgliedsbeiträge, gestaffelt nach Kategorie, und monatliche Rechnungen für Produktzertifizierungen und Registrierungen. Beides folgte festen Regeln, lag aber in zwei getrennten Welten. Die Stammdaten der Mitglieder pflegt der Verein in SharePoint, die Buchhaltung läuft in Lexware Office. Dazwischen gab es keine Verbindung, also übertrug jemand die Zahlen von Hand.
Das war nicht nur Fleißarbeit. Mitglieder im In- und Ausland, verschiedene Beitragskategorien und Netto-Konditionen machen jede Rechnung ein Stück weit zum Einzelfall, und Einzelfälle sind die natürliche Heimat von Tippfehlern. Dazu kam die gesetzliche Umstellung auf die strukturierte E-Rechnung, die einen sauberen, API-gestützten Weg ohnehin nahelegte. Das Ziel war deshalb nicht allein Zeitersparnis, sondern ein Prozess, der reproduzierbar und prüfbar ist.
Drei Stationen, ein Lauf
Der Dienst ist eine in Python geschriebene Azure Function mit zeitgesteuertem Trigger. Es läuft kein Server durch: Die Funktion wacht nach Zeitplan auf, erledigt ihren Lauf und schläft wieder ein. Zwei solche Funktionen takten den Betrieb, eine für den monatlichen und eine für den jährlichen Lauf, bewusst auf verschiedene Tage gelegt, damit nie beide gleichzeitig starten.
Der Lauf beginnt bei SharePoint. Über Microsoft Graph liest der Dienst die Listen mit Unternehmen, Registrierungen und Zertifizierungen, ausschließlich lesend. Er authentifiziert sich über eine Managed Identity, sodass kein Passwort und kein Token im Code liegt; die wenigen Geheimnisse, die er braucht, holt er zur Laufzeit aus dem Azure Key Vault.
Aus diesen Daten baut der Dienst die Rechnungspositionen und legt sie über die Lexware-API an, und zwar absichtlich als Entwurf, nicht als finale Rechnung. So bleibt die fachliche Kontrolle vor dem Versand bei der Geschäftsstelle. Zum Schluss verschickt der Lauf über Graph eine Zusammenfassung per E-Mail: was angelegt, was übersprungen wurde und wo es Fehler gab. Damit ist jeder Lauf nachvollziehbar, ohne dass jemand Logs durchsuchen muss.
Dass die Zielstation ausgerechnet Lexware ist, ist dabei austauschbar. Weil die gesamte Erstellung über eine öffentliche API läuft, ließe sich ein anderes Buchhaltungssystem mit eigener API anbinden, ohne die Logik auf der SharePoint-Seite anzufassen.
Das Kernproblem: keine Rechnung doppelt
Der schwierigste Teil einer Rechnungsautomatisierung ist nicht das Anlegen, sondern das Nicht-doppelt-Anlegen. Eine Function kann neu starten, ein überfälliger Timer einen verpassten Lauf nachholen, ein Netzwerkfehler eine Wiederholung auslösen. Keiner dieser Fälle darf am Ende eine zweite Rechnung erzeugen.
Der naheliegende Weg wäre eine eigene Liste, die jeden erledigten Lauf protokolliert. Dagegen habe ich mich bewusst entschieden. Eine zweite Datenquelle, die dauerhaft mit dem tatsächlichen Stand in Lexware übereinstimmen muss, ist selbst eine neue Fehlerquelle. Stattdessen ist Lexware selbst die Single Source of Truth.
Dafür schreibt der Dienst einen deterministischen Marker in das remark-Feld jeder Rechnung, etwa:
Ref: AUTO-SP|MONTHLY|2026-05|<contactId>
Vor dem Anlegen fragt er über die Voucherlist von Lexware alle Rechnungen ab, die für diesen Kunden an diesem Tag existieren, und prüft, ob eine davon genau diesen Marker trägt:
def already_invoiced(lex, contact_id, invoice_date_ymd, marker):
"""True, wenn für diesen Kontakt am Stichtag bereits eine
Rechnung mit unserem Marker existiert."""
listing = lex.voucherlist_invoices_for_day(contact_id, invoice_date_ymd)
for voucher in listing.get("content", []):
invoice = lex.get_invoice(voucher["id"])
if marker in (invoice.get("remark") or ""):
return True # schon von der Automatisierung erzeugt, also überspringen
return False
Entscheidend ist die Aufgabenteilung: Die Voucherlist grenzt nur nach Kunde und Datum vor, die eigentliche Entscheidung fällt am Marker. Legt die Buchhaltung am selben Tag von Hand eine Rechnung für dasselbe Unternehmen an, trägt diese den Marker nicht und löst deshalb keinen falschen Treffer aus. Der Lauf ist damit idempotent, ohne manuelle Buchungen zu verdrängen oder zu verdoppeln.
Der Preis dieser Entscheidung
Diese Eleganz hat eine Bedingung, und sie steht nicht im Code, sondern in der Betriebsanweisung: Der Marker im remark-Feld ist tabu. Entfernt oder überschreibt jemand in der Buchhaltung die Ref:-Zeile einer automatisch erzeugten Rechnung, erkennt ein späterer Lauf sie nicht wieder und legt sie ein zweites Mal an. Die Idempotenz hängt also an einer menschlichen Konvention. Das ist der Preis dafür, auf eine eigene Log-Datenbank zu verzichten: Ich tausche eine technische Fehlerquelle gegen eine organisatorische, die ich dokumentieren und mit der Geschäftsstelle absprechen muss.
Der zweite Preis ist die Zahl der Abfragen. Jede Prüfung kostet einen Voucherlist-Aufruf plus je einen weiteren für jede gefundene Kandidaten-Rechnung, und das pro Unternehmen und Lauf. Bei einem Verein mit überschaubarer Mitgliederzahl passt das bequem in das Anfrage-Budget der API. Bei Tausenden Kontakten wäre dieser Ansatz zu gesprächig und müsste anders gedacht werden, etwa über einen gezielteren Index auf dem Marker. Für den Maßstab des SMI e.V. ist er genau richtig, und diese Einordnung gehört zur Entscheidung dazu.
Was den Lauf unbeaufsichtigt durchhält
Zwischen „funktioniert auf meinem Rechner“ und „läuft jeden Monat ohne Aufsicht durch“ liegen vor allem die unangenehmen Randfälle. Drei davon habe ich gezielt abgefangen.
Fremde APIs antworten nicht immer beim ersten Versuch. Auf die üblichen vorübergehenden Fehler, also Timeouts, überschrittene Rate Limits und Server-Fehler (408, 429, 5xx), reagieren beide Clients, der für Graph und der für Lexware, mit einem Retry. Liefert die API einen Retry-After-Header, hält sich der Client daran; sonst wartet er mit exponentiell wachsendem, gedeckeltem Abstand.
Lexware erlaubt nur rund zwei Anfragen pro Sekunde. Statt blind ins Limit zu laufen und sich an 429-Fehlern abzuarbeiten, hält der Client von sich aus einen Mindestabstand zwischen zwei Aufrufen ein. Throttling, bevor die API es erzwingt, ist ruhiger als hinterher aufzuräumen.
Und die serverseitigen Filter von Graph sind bei manchen SharePoint-Feldtypen unzuverlässig und quittieren eine eigentlich gültige Abfrage mit einem 400. Schlägt der Filter fehl, lädt der Dienst die Liste ungefiltert und sortiert clientseitig nach. Das Ergebnis stimmt, der Lauf bricht nicht ab.
Dazu kommt ein Schalter, der im Betrieb Gold wert ist: Ein Dry-Run-Modus spielt den kompletten Ablauf durch, ohne eine einzige Rechnung anzulegen. So lässt sich vor jedem echten Lauf prüfen, was passieren würde.
Was es gebracht hat
Heute trägt der Dienst die Rechnungsstellung für rund 30 nationale und internationale Mitgliedsunternehmen: monatlich für Zertifizierungen und Registrierungen, jährlich für die Beiträge. Die Geschäftsstelle überträgt keine Zahlen mehr von Hand, sondern prüft Entwürfe und gibt sie frei. Vertical Meters beziffert die Zeitersparnis der Geschäftsstelle auf rund 80 Prozent, und weil die Rechnungen ausschließlich über die Lexware-API entstehen, sind sie durchgehend E-Rechnungs-fähig, ohne dass dafür ein zusätzlicher Schritt nötig wäre.
Der Teil, der sich nicht in einer Zahl ausdrücken lässt, ist die geschlossene Lücke: Zertifizierungsverwaltung und Buchhaltung hängen jetzt an denselben Stammdaten. Ein Lauf lässt sich wiederholen, ohne dass jemand nachrechnet, was beim letzten Mal schon erledigt war.
Woran sich eine Automatisierung beweist
Eine gute Automatisierung beweist sich nicht im Normalfall, sondern in den Ausnahmen: beim Neustart, beim nachgeholten Lauf, neben der manuell erfassten Rechnung. Genau dort steckt die eigentliche Arbeit, und genau dort entscheidet sich, ob die Geschäftsstelle dem Lauf vertraut oder doch wieder selbst nachzählt.
FAQ
Warum eine Azure Function und kein durchgehender Server?
Der Rechnungslauf fällt nur zu festen Zeitpunkten an. Eine zeitgesteuerte Funktion startet nach Plan, erledigt den Lauf und verursacht dazwischen keine Kosten. Das passt zum Lastprofil besser als ein Dauerbetrieb.
Wie werden Doppelbuchungen verhindert?
Über einen festen Marker im remark-Feld jeder Rechnung, kombiniert mit einer Abfrage pro Kunde und Stichtag. Trägt bereits eine Rechnung diesen Marker, überspringt der Lauf das Anlegen. Ein separates Protokoll ist dafür nicht nötig.
Lässt sich der Ansatz auf ein anderes Buchhaltungssystem übertragen?
Ja. Die Erstellung läuft vollständig über eine öffentliche API, deshalb ist die Zielstation austauschbar. Solange ein System eine API zum Anlegen und Abfragen von Rechnungen bietet, bleibt der Rest der Logik gleich.
Was passiert bei einem API-Fehler mitten im Lauf?
Vorübergehende Fehler fängt der Dienst mit Retries und wachsendem Abstand ab und hält dabei das Rate Limit der API ein. Bleibt ein Fehler bestehen, landet er in der Zusammenfassungs-Mail, statt den ganzen Lauf still scheitern zu lassen.
Wiederkehrende Prozesse automatisieren?
Ob Rechnungsstellung, Datenabgleich oder Auswertungen: Ich unterstütze Sie von der Architektur bis zur robusten, wartbaren Umsetzung.