Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
22 KiB
Workflow-System
Dieses System führt Workflows aus, die vollständig als XML-Dateien definiert sind. Eine Workflow-Datei beschreibt eine Abfolge von Schritten (Tasks) — Formulare, Genehmigungen, E-Mails, PDF-Verarbeitung, Bedingungen — und die Engine arbeitet diese Schritte sequenziell oder parallel ab.
Die wichtigsten Eigenschaften:
- XML statt Code: Neue Abläufe entstehen durch Anlegen einer XML-Datei, nicht durch Programmierung.
- Tokenbasiert und zustandslos: Jeder Schritt ist ein normaler Web-Aufruf. Der komplette Zustand (erledigte Tasks, gesammelte Variablen) wird serverseitig gespeichert — Bearbeiter können Tage später weitermachen, ohne dass etwas verloren geht.
- Benutzer-Interaktion per Weblink: Wenn ein Schritt eine Person erfordert (Formular ausfüllen, genehmigen, abstimmen), erhält diese automatisch eine E-Mail mit einem persönlichen Link.
- E-Mail-Benachrichtigungen: Zuweisungen, Rückgaben und Erinnerungen werden automatisch verschickt.
- PDF-Verarbeitung: Hochgeladene PDFs können gestempelt, mit Text befüllt, zusammengeführt und kryptografisch signiert werden.
- Parallelität und Abstimmungen: Unabhängige Zweige laufen parallel; N-von-M-Abstimmungen (Quorum) sind eingebaut.
Typischer Ablauf: Ein Mitarbeiter startet einen Workflow, füllt ein Formular aus, ein Vorgesetzter bekommt per Mail einen Genehmigungslink, das Dokument wird gestempelt und das Ergebnis per Mail zugestellt — alles definiert in einer einzigen XML-Datei.
Grundkonzepte
Workflow-Definition als XML-Datei
Jeder Workflow ist eine .xml-Datei im Workflow-Ordner der Installation. Der Dateiname (ohne .xml) ist gleichzeitig der Name des Workflow-Typs. Die Datei beschreibt alle Schritte deklarativ; die Engine liest sie bei jedem Aufruf neu ein — Änderungen an der XML wirken sofort auf neue Durchläufe.
Einen Workflow starten
Ein neuer Durchlauf wird über die Start-Seite ausgelöst:
start.php?xml=<name>
<name> ist der Dateiname der Workflow-XML ohne Endung (z. B. start.php?xml=dienstreise). Es wird ein neuer Workflow-Datensatz angelegt, der eingeloggte Benutzer als Antragsteller in den Kontext eingetragen und sofort zum ersten Schritt weitergeleitet.
Lebenszyklus und Status
Jeder Workflow-Durchlauf hat genau einen Gesamtstatus:
| Status | Bedeutung |
|---|---|
RUNNING |
Workflow läuft; die Engine arbeitet automatische Schritte ab (auch nach einer Rückgabe zur Überarbeitung) |
WAITING |
Workflow wartet auf eine Person (z. B. Formular ausfüllen, genehmigen) |
COMPLETED |
Alle Tasks erfolgreich abgeschlossen |
ERROR |
Ein Task ist mit einem technischen Fehler abgebrochen |
CANCELLED |
Workflow wurde bewusst gestoppt (z. B. Ablehnung mit stop-Task) |
Bei jedem Aufruf läuft die Engine die XML von oben durch, überspringt bereits abgeschlossene Tasks und führt den ersten offenen Task aus. Liefert dieser waiting, pausiert der Workflow und der zuständige Bearbeiter wird benachrichtigt.
Zugriffs- und Schritt-Token (Sicherheit)
Jeder Durchlauf ist über zwei Token geschützt:
- Zugriffs-Token (
token): identifiziert den Workflow-Durchlauf. Ohne gültigen Token gibt es keinen Zugriff. - Schritt-Token (
step): wird für jeden neuen interaktiven Schritt frisch erzeugt und nur per Benachrichtigungs-Mail an den zugewiesenen Bearbeiter verschickt.
Der Link in der E-Mail hat die Form:
index.php?token=<zugriffs-token>&step=<schritt-token>
Nur der aktuell zugewiesene Bearbeiter sieht das interaktive Formular — alle anderen erhalten lediglich eine Status-Ansicht. Wechselt der Bearbeiter oder beginnt ein neuer Schritt, wird ein neues Schritt-Token erzeugt und der alte Link damit für interaktive Aktionen ungültig. Formulare sind zusätzlich automatisch CSRF-geschützt.
Kontext-Variablen und Mustache-Templating
Der Kontext ist der gemeinsame Variablenspeicher eines Workflow-Durchlaufs. Jeder abgeschlossene Formular-Task schreibt seine Eingabefelder hinein, jeder Task kann Werte ergänzen — nachfolgende Tasks können alles davon nutzen.
In allen Textknoten der XML (E-Mail-Texte, Empfänger, Bedingungen, Dateiangaben, HTML) können Variablen im Mustache-Format eingesetzt werden:
{{variable}}— einfacher Wert aus dem Kontext{{objekt.feld}}— verschachtelter Zugriff, z. B.{{ICH.mail}}
Wichtige Systemvariablen, die ab dem Start automatisch verfügbar sind:
| Variable | Inhalt |
|---|---|
{{ICH.id}} |
Personen-ID des Antragstellers (der Person, die den Workflow gestartet hat) |
{{ICH.vorname}}, {{ICH.nachname}} |
Name des Antragstellers |
{{ICH.mail}} |
E-Mail-Adresse des Antragstellers |
{{ICH.funktion}} |
Funktion/Rolle des Antragstellers |
{{DATE}} |
Datum des Workflow-Starts (tt.mm.jjjj) |
{{TODAY}} |
Tagesdatum zum Zeitpunkt des jeweiligen Schritts (z. B. für Stempel) |
{{WORKFLOW_ID}} |
Eindeutige numerische ID des laufenden Durchlaufs |
{{URL_WORKFLOW}} |
Basis-Webadresse der Workflow-Anwendung (für selbstgebaute Links) |
{{temp_dir}} |
Arbeitsverzeichnis des Durchlaufs für erzeugte Dateien |
{{latest_pdf_path}} |
Pfad der zuletzt erzeugten/bearbeiteten PDF-Datei — wird von jedem PDF-Task automatisch aktualisiert (dazu passend {{latest_pdf_url}} als Web-Adresse) |
Zuweisung mit <assign_to>
Interaktive Tasks (Formulare, Genehmigungen, Abstimmungen) erhalten ein <assign_to>-Element, das bestimmt, wer den Schritt bearbeiten soll:
<task type="html_form" id="genehmigung">
<assign_to>max.mustermann@example.org</assign_to>
...
</task>
Erlaubt sind eine E-Mail-Adresse, eine Personen-ID aus der Benutzerverwaltung oder ein Namens-Präfix — natürlich auch als Variable ({{ICH.mail}}, {{chef_mail}}). Die Engine löst den Bearbeiter auf und verschickt automatisch eine Benachrichtigungs-Mail mit dem persönlichen Bearbeitungslink, inklusive persönlicher Anrede, sofern die Person im System bekannt ist.
Zwei Komfort-Regeln vermeiden Mail-Flut:
- Selbstzuweisung: Ist der nächste Bearbeiter der aktuell eingeloggte Benutzer, wird keine Mail verschickt — die Folgeseite erscheint direkt im Browser (Schritt-für-Schritt-Durchklicken).
- Unveränderter Schritt: Bleiben Bearbeiter und Schritt gleich (z. B. Seite neu geladen), wird keine erneute Mail verschickt.
Struktur-Elemente
<workflow id="..."> — Wurzelelement
Jede Datei beginnt mit dem <workflow>-Wurzelelement. Direkt darunter steht genau ein <task type="sequence"> als Haupt-Container:
<?xml version="1.0" encoding="UTF-8"?>
<workflow id="mein_workflow_v1">
<task type="sequence" id="haupt">
<!-- Schritte hier -->
</task>
</workflow>
Jedes id-Attribut muss innerhalb der Datei eindeutig sein — die Engine merkt sich anhand der IDs, welche Tasks bereits erledigt sind.
<task type="sequence"> — Reihenfolge
Führt seine Kind-Tasks strikt nacheinander aus. Ein Task startet erst, wenn der vorherige erfolgreich abgeschlossen ist. Sequenzen können beliebig verschachtelt werden.
<task type="parallel"> mit <branch> — parallele Zweige
Führt mehrere unabhängige Zweige aus. Der Parallel-Block gilt erst als abgeschlossen, wenn alle Zweige fertig sind.
<task type="parallel" id="benachrichtigungen">
<branch id="zweig_a">
<task type="email" id="mail_a">...</task>
</branch>
<branch id="zweig_b">
<task type="email" id="mail_b">...</task>
</branch>
</task>
Wartet ein Zweig auf eine Person, pausiert der Parallel-Block an dieser Stelle und wird beim nächsten Aufruf nahtlos fortgesetzt.
Watchdog-Zweige: Zweige, die ausschließlich aus Wächter-Tasks der Typen escalate, wait_until oder external_trigger bestehen (Fristen-Überwachung, Zeitpunkt abwarten, externes Signal), werden automatisch geschlossen, sobald mindestens ein Arbeits-Zweig komplett fertig ist. So blockiert z. B. ein noch laufender Eskalations-Zweig den Abschluss nicht, wenn die eigentliche Arbeit bereits erledigt wurde — typisches Muster: ein Zweig mit dem Genehmigungsformular, daneben ein Watchdog-Zweig, der nach Ablauf einer Frist eskalieren würde.
Testmodus
Für gefahrloses Testen gibt es eine einfache Konvention: Enthält der Kontext eine Variable test_modus_mail mit einer E-Mail-Adresse, werden alle E-Mails des Workflows auf diese Adresse umgeleitet — sowohl die Benachrichtigungs-Mails der Engine als auch die per email-Task verschickten Mails (CC/BCC entfallen dabei komplett). Die Zuweisungen (assign_to) bleiben unverändert, damit auch die Bearbeiter-Logik realistisch getestet werden kann.
Am einfachsten setzt man die Variable ganz am Anfang des Workflows per set_var-Task:
<task type="set_var" id="konfiguration">
<var name="test_modus_mail">tester@example.org</var>
</task>
Zum Produktivschalten den Wert leeren. Der Testmodus gilt nur für Workflows, die die Variable selbst setzen — andere Workflows laufen unverändert.
Minimalbeispiel
Ein vollständiger kleiner Workflow: Antragsteller füllt ein Formular aus, eine zweite Person genehmigt oder lehnt ab, der Antragsteller erhält das Ergebnis per Mail.
<?xml version="1.0" encoding="UTF-8"?>
<workflow id="minimal_antrag_v1">
<task type="sequence" id="haupt">
<!-- 1. Antrag: Formular für den Antragsteller (Person, die den Workflow startet) -->
<task type="html_form" id="antrag">
<assign_to>{{ICH.mail}}</assign_to>
<config>
<required>betreff,begruendung</required>
<html><![CDATA[
<!DOCTYPE html>
<html lang="de">
<head><meta charset="UTF-8"></head>
<body>
<form method="post">
<h2>Antrag stellen</h2>
<p><label>Betreff<br>
<input type="text" name="betreff"></label></p>
<p><label>Begründung<br>
<textarea name="begruendung"></textarea></label></p>
<button type="submit">Absenden</button>
</form>
</body>
</html>
]]></html>
</config>
</task>
<!-- 2. Genehmigung: die zuständige Person erhält automatisch eine Mail mit Link -->
<task type="approve_reject" id="genehmigung">
<assign_to>chefin@example.org</assign_to>
<config>
<title>Antrag prüfen: {{betreff}}</title>
<subtitle>Antrag von {{ICH.vorname}} {{ICH.nachname}}</subtitle>
<output_var>decision</output_var>
</config>
</task>
<!-- 3a. Bei Ablehnung: Mail an Antragsteller, dann Workflow beenden -->
<task type="if" condition="{{decision}} == 'rejected'">
<then>
<task type="email" id="mail_ablehnung">
<config>
<an>{{ICH.mail}}</an>
<titel>Ihr Antrag wurde abgelehnt</titel>
<text><![CDATA[
Hallo {{ICH.vorname}},<br><br>
Ihr Antrag "{{betreff}}" vom {{DATE}} wurde abgelehnt.<br>
<strong>Begründung:</strong> {{decision_reason}}
]]></text>
</config>
</task>
<task type="stop" id="ende_abgelehnt">
<message>Der Antrag wurde abgelehnt.</message>
</task>
</then>
</task>
<!-- 3b. Bei Genehmigung: Bestätigungsmail -->
<task type="email" id="mail_genehmigt">
<config>
<an>{{ICH.mail}}</an>
<titel>Ihr Antrag wurde genehmigt</titel>
<text><![CDATA[
Hallo {{ICH.vorname}},<br><br>
Ihr Antrag "{{betreff}}" vom {{DATE}} wurde genehmigt.
]]></text>
</config>
</task>
</task>
</workflow>
Ablauf: Datei z. B. als minimal_antrag.xml speichern, dann per start.php?xml=minimal_antrag starten. Nach dem Absenden des Antrags erhält chefin@example.org automatisch eine Benachrichtigungs-Mail mit dem persönlichen Genehmigungslink; nach der Entscheidung wird der Antragsteller informiert und der Workflow steht auf COMPLETED (bzw. CANCELLED bei Ablehnung).
Task-Referenz
Jeder Task-Typ ist in einer eigenen Datei unter tasks/ dokumentiert — mit Zweck, allen Parametern (Pflicht/Default), Eingangs- und Ausgangswerten und einem XML-Beispiel. Die Tabellen hier geben den Schnellüberblick: Task ansehen, Kurzbeschreibung lesen, per Klick in die Detail-Doku springen.
Die Struktur-Container sequence und parallel/branch sind oben unter Struktur-Elemente beschrieben.
Einzelne Tasks (signal_wait, aktenzeichen_vergabe) benötigen eine eigene kleine Tabelle — die zugehörigen Migrationen liegen unter sql/.
Steuerung, Logik und Zeit
| Task | Was er kann |
|---|---|
aktenzeichen_vergabe — Aktenzeichen vergeben |
Kollisionssicher die nächste Nummer aus einem jahresweisen Nummernkreis ziehen und formatieren. |
beleg_abgleich — Belege abgleichen |
Felder zweier Datensätze mit Toleranzen vergleichen (z. B. Bestellung ↔ Rechnung). |
calc — Berechnung |
Wertet arithmetische/logische Ausdrücke sicher aus und schreibt die Ergebnisse als Kontextvariablen. |
decision_table — Entscheidungstabelle |
Mehrspaltige Entscheidungstabelle (DMN-light) statt verschachtelter if-Kaskaden. |
escalate — Eskalation bei Fristüberschreitung |
Hält den Workflow bis zum Ablauf einer Frist im Wartezustand; solange bleibt der ursprüngliche Bearbeiter zuständig. |
event_race — Ereignis-Wettrennen |
Ereignisbasiertes Gateway: die zuerst abgeschlossene Warte-Option gewinnt, der Rest wird storniert. |
external_trigger — Auf externen Callback warten |
Pausiert den Workflow, bis ein externes System (Signatur-Dienst, Webhook, Pipeline, manueller Klick) eine generierte Callback-URL mit einmaligem Token aufruft. |
foreach_parallel — Parallele Multi-Instanz |
Kind-Tasks je Listenelement unabhängig ausführen, optional mit Abschlussbedingung. |
frist_rechner — Fristen berechnen |
Verwaltungsfristen mit Bekanntgabefiktion, Feiertagen und Werktagsregel berechnen. |
if — Bedingte Verzweigung |
Führt abhängig von einer Bedingung die Tasks im <then>- oder im optionalen <else>-Zweig aus. |
json_transform — JSON umformen |
Werte per Pfadausdruck aus JSON ziehen und als Kontextvariablen ablegen. |
log_step — Audit-Log-Eintrag |
Schreibt einen unveränderlichen Eintrag in das Audit-Log des Workflows (mit Task-ID, Aktion, Benutzer, Details und Zeitstempel). |
loop_foreach — Schleife über eine Liste |
Iteriert über ein Array aus dem Kontext und führt die direkt enthaltenen Kind-Tasks pro Element aus. |
map_lookup — Wertetabelle / Mapping |
Bildet einen Eingabewert über eine Inline-Tabelle auf einen Zielwert ab — spart lange <if>-Kaskaden für Zuordnungen wie Status → Empfänger oder Typ → Feldname. |
return_to — Zur Überarbeitung zurückgeben |
Setzt frühere Schritte zurück, indem die angegebenen Task-IDs aus dem Ausführungsstatus gelöscht werden — sie laufen beim nächsten Trigger erneut. |
schedule_resume — Geplante automatische Fortsetzung |
Pausiert den Workflow und plant einen Weck-Zeitpunkt, zu dem der Workflow automatisch fortgesetzt wird — im Gegensatz zu wait_until, das passiv auf den nächsten Trigger wartet. |
set_var — Kontextvariablen setzen |
Setzt eine oder mehrere Kontextvariablen ohne Benutzerinteraktion. |
signal_fire — Internes Ereignis auslösen |
Feuert ein Ereignis auf dem internen Event-Bus der Anwendung, sodass registrierte Listener reagieren — ohne Umweg über die HTTP-API. |
signal_wait — Auf Signal warten |
Pausieren, bis ein anderes Ereignis ein passendes Signal (Name + Korrelation) hinterlegt. |
stop — Workflow abbrechen |
Bricht den gesamten Workflow sofort ab; die Engine setzt den Gesamtstatus auf CANCELLED. |
subworkflow — Anderen Workflow einbetten |
Lädt eine zweite Workflow-XML (Name ohne .xml aus dem Workflow-Verzeichnis) und führt sie synchron im laufenden Workflow aus — inklusive Wartezuständen. |
try_catch — Fehlergrenze mit Retry |
Fehler abfangen, mit Backoff wiederholen und im catch-Zweig weiterlaufen statt den Workflow abzubrechen. |
wait_until — Bis Zeitpunkt warten |
Pausiert den Workflow bis zu einem festen oder relativen Zeitpunkt. |
wiedervorlage — Wiedervorlage |
Vorgang bis zu einem Termin parken und dem Bearbeiter mit Notiz wieder vorlegen. |
Interaktion und Kommunikation
| Task | Was er kann |
|---|---|
approve_reject — Genehmigen/Ablehnen |
Zeigt dem Bearbeiter eine JA/NEIN-Entscheidungsseite, optional mit eingebetteter PDF-Vorschau im Vollbild. |
assign_group — Gruppen-Freigabe |
Genehmigungsschritt an eine Gruppe zustellen; wer zuerst entscheidet, übernimmt (claim). |
calendar_event — Kalendereintrag anlegen |
Legt vollautomatisch (ohne Benutzerinteraktion) einen Ereignis-Eintrag an — Termin, Frist oder Vermerk — und verknüpft ihn mit einem Bezugsobjekt (z. |
cloud_link — Öffentlichen Cloud-Freigabelink erzeugen |
Gibt eine Datei im angebundenen Cloud-Speicher per öffentlichem Link frei und schreibt die Link-URL in den Kontext. |
email — E-Mail versenden |
Versendet eine E-Mail (ohne Benutzerinteraktion) mit Betreff, Text und optionalen Anhängen. |
html_form — Freies HTML-Formular |
Rendert ein beliebiges, selbst definiertes HTML-Formular und blockiert den Workflow, bis der Benutzer es absendet; <assign_to> bestimmt Bearbeiter und Benachrichtigung. |
load_person — Personendaten nachladen |
Lädt einen Personen-Stammdatensatz anhand einer ID, E-Mail-Adresse oder Login-Kennung aus der Personenverwaltung und stellt ihn strukturiert im Kontext bereit — typisch, um Anrede oder Mail-Adresse einer über eine ID referenzierten Person zu ermitteln. |
mkz_pick — Maßnahmen auswählen |
Interaktive (Mehrfach-)Auswahl von Maßnahmen (MKZ) eines Verfahrens aus einer filterbaren Liste. |
person_pick — Person auswählen |
Interaktive Auswahl einer oder mehrerer Personen über ein durchsuchbares Dropdown. |
quorum — Abstimmung (N von M) |
Mehrpersonen-Abstimmung: Jede in <assign_to> genannte Person darf eine Stimme (Zustimmen/Ablehnen, optional mit Kommentar) abgeben. |
tg_pick — Verfahren auswählen |
Interaktive Auswahl eines Verfahrens (Teilnehmergemeinschaft) über ein durchsuchbares Dropdown (Suche nach VKZ oder Name). |
webhook — HTTP-Aufruf an externe URL |
Sendet einen HTTP-Request an eine beliebige URL (z. |
KI und Fachdienste
| Task | Was er kann |
|---|---|
ki_auftrag — KI-Auftrag |
Freitext-Auftrag (klassifizieren/zusammenfassen/entwerfen) an die zentrale KI-Kette, optional mit Datei/Variable als Material. |
ki_dok_extrakt — KI-Feldextraktion |
Schema-basierte Feldextraktion aus PDF/Text per KI mit Pflichtfeld-Validierung. |
paperless_ablage — Archiv-Ablage |
Datei mit Metadaten ins Dokumentenarchiv (Paperless-ngx) übergeben. |
rag_recherche — RAG-Recherche |
Belegte Antwort mit Quellenangaben aus dem hausinternen Vektor-RAG. |
route_dienstfahrt — Routing/Kilometer |
Distanz/Fahrzeit über das hausinterne Routing berechnen und Kilometerangaben plausibilisieren. |
PDF- und Dokument-Verarbeitung
Konvention: Signatur-Tasks (
pdf_sign,pdf_sign_at_text) gehören ans Ende einer PDF-Bearbeitungskette — Text-/Stempel-Tasks verwerfen beim Re-Import vorhandene Signaturen. Details in den jeweiligen Task-Dokus.
| Task | Was er kann |
|---|---|
pdf_from_template — PDF aus HTML-Vorlage |
Erzeugt ein neues PDF aus HTML — entweder direkt inline im XML oder aus einer hinterlegten HTML-Vorlagendatei. |
pdf_merge — PDFs zusammenführen |
Fügt mehrere PDF-Dateien in der angegebenen Reihenfolge zu einem Gesamtdokument zusammen. |
pdf_rotate — Interaktive Seitendrehung |
Zeigt dem Bearbeiter eine Vorschau aller Seiten mit Dreh-Buttons (0°/90°/180°/270° je Seite, plus „Alle gleich drehen"). |
pdf_sign — PDF stempeln und signieren (feste Position) |
Bringt einen aus Text erzeugten Sichtvermerk-Stempel an einer festen Koordinate auf und versieht das Dokument mit einer kryptografischen Signatur (systemweit hinterlegtes Zertifikat). |
pdf_sign_at_text — Stempeln/Signieren an gefundener Textstelle |
Sucht eine Phrase im PDF-Text (z. |
pdf_split — PDF aufteilen |
Teilt ein PDF in Einzelseiten oder benannte Seitenbereiche auf. |
pdf_stamp_interactive — Interaktive Stempelplatzierung |
Bearbeiter platziert einen Stempel (Vorlage, Bild oder Text) per Klick frei auf der PDF-Seite; danach wird er dauerhaft eingebrannt. |
pdf_text — Text an feste Koordinate schreiben |
Schreibt einen (dynamischen) Text an eine feste Koordinate in ein bestehendes PDF — z. |
pdf_text_at_text — Text an gefundener Textstelle schreiben |
Sucht eine Phrase im PDF und schreibt relativ dazu einen Text — typischer Anwendungsfall: ein „x" in eine Checkbox neben einem gefundenen Formular-Label setzen. |
vorlage_docx — DOCX aus Vorlagen-Skript erzeugen |
Ruft serverseitig eine Vorlage des zentralen Vorlagen-Dienstes auf (dieselbe, die auch interaktiv im Browser genutzt wird), befüllt deren Formularfelder aus dem Workflow-Kontext und übernimmt das erzeugte DOCX in den Workflow. |