# 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= ``` `` 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=&step= ``` 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 `` Interaktive Tasks (Formulare, Genehmigungen, Abstimmungen) erhalten ein ``-Element, das bestimmt, wer den Schritt bearbeiten soll: ```xml max.mustermann@example.org ... ``` 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 ### `` — Wurzelelement Jede Datei beginnt mit dem ``-Wurzelelement. Direkt darunter steht genau ein `` als Haupt-Container: ```xml ``` Jedes `id`-Attribut muss innerhalb der Datei eindeutig sein — die Engine merkt sich anhand der IDs, welche Tasks bereits erledigt sind. ### `` — 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. ### `` mit `` — parallele Zweige Führt mehrere unabhängige Zweige aus. **Der Parallel-Block gilt erst als abgeschlossen, wenn alle Zweige fertig sind.** ```xml ... ... ``` 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: ```xml tester@example.org ``` 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 {{ICH.mail}} betreff,begruendung

Antrag stellen

]]>
chefin@example.org Antrag prüfen: {{betreff}} Antrag von {{ICH.vorname}} {{ICH.nachname}} decision {{ICH.mail}} Ihr Antrag wurde abgelehnt
Ihr Antrag "{{betreff}}" vom {{DATE}} wurde abgelehnt.
Begründung: {{decision_reason}} ]]>
Der Antrag wurde abgelehnt.
{{ICH.mail}} Ihr Antrag wurde genehmigt
Ihr Antrag "{{betreff}}" vom {{DATE}} wurde genehmigt. ]]>
``` 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/`](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](#struktur-elemente) beschrieben. ## Steuerung, Logik und Zeit | Task | Was er kann | |---|---| | [`aktenzeichen_vergabe`](tasks/aktenzeichen_vergabe.md) — Aktenzeichen vergeben | Kollisionssicher die nächste Nummer aus einem jahresweisen Nummernkreis ziehen und formatieren. | | [`beleg_abgleich`](tasks/beleg_abgleich.md) — Belege abgleichen | Felder zweier Datensätze mit Toleranzen vergleichen (z. B. Bestellung ↔ Rechnung). | | [`calc`](tasks/calc.md) — Berechnung | Wertet arithmetische/logische Ausdrücke sicher aus und schreibt die Ergebnisse als Kontextvariablen. | | [`decision_table`](tasks/decision_table.md) — Entscheidungstabelle | Mehrspaltige Entscheidungstabelle (DMN-light) statt verschachtelter if-Kaskaden. | | [`escalate`](tasks/escalate.md) — 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`](tasks/event_race.md) — Ereignis-Wettrennen | Ereignisbasiertes Gateway: die zuerst abgeschlossene Warte-Option gewinnt, der Rest wird storniert. | | [`external_trigger`](tasks/external_trigger.md) — 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`](tasks/foreach_parallel.md) — Parallele Multi-Instanz | Kind-Tasks je Listenelement unabhängig ausführen, optional mit Abschlussbedingung. | | [`frist_rechner`](tasks/frist_rechner.md) — Fristen berechnen | Verwaltungsfristen mit Bekanntgabefiktion, Feiertagen und Werktagsregel berechnen. | | [`if`](tasks/if.md) — Bedingte Verzweigung | Führt abhängig von einer Bedingung die Tasks im ``- oder im optionalen ``-Zweig aus. | | [`json_transform`](tasks/json_transform.md) — JSON umformen | Werte per Pfadausdruck aus JSON ziehen und als Kontextvariablen ablegen. | | [`log_step`](tasks/log_step.md) — Audit-Log-Eintrag | Schreibt einen unveränderlichen Eintrag in das Audit-Log des Workflows (mit Task-ID, Aktion, Benutzer, Details und Zeitstempel). | | [`loop_foreach`](tasks/loop_foreach.md) — Schleife über eine Liste | Iteriert über ein Array aus dem Kontext und führt die direkt enthaltenen Kind-Tasks pro Element aus. | | [`map_lookup`](tasks/map_lookup.md) — Wertetabelle / Mapping | Bildet einen Eingabewert über eine Inline-Tabelle auf einen Zielwert ab — spart lange ``-Kaskaden für Zuordnungen wie Status → Empfänger oder Typ → Feldname. | | [`return_to`](tasks/return_to.md) — 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`](tasks/schedule_resume.md) — 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`](tasks/set_var.md) — Kontextvariablen setzen | Setzt eine oder mehrere Kontextvariablen ohne Benutzerinteraktion. | | [`signal_fire`](tasks/signal_fire.md) — 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`](tasks/signal_wait.md) — Auf Signal warten | Pausieren, bis ein anderes Ereignis ein passendes Signal (Name + Korrelation) hinterlegt. | | [`stop`](tasks/stop.md) — Workflow abbrechen | Bricht den gesamten Workflow sofort ab; die Engine setzt den Gesamtstatus auf `CANCELLED`. | | [`subworkflow`](tasks/subworkflow.md) — 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`](tasks/try_catch.md) — Fehlergrenze mit Retry | Fehler abfangen, mit Backoff wiederholen und im catch-Zweig weiterlaufen statt den Workflow abzubrechen. | | [`wait_until`](tasks/wait_until.md) — Bis Zeitpunkt warten | Pausiert den Workflow bis zu einem festen oder relativen Zeitpunkt. | | [`wiedervorlage`](tasks/wiedervorlage.md) — Wiedervorlage | Vorgang bis zu einem Termin parken und dem Bearbeiter mit Notiz wieder vorlegen. | ## Interaktion und Kommunikation | Task | Was er kann | |---|---| | [`approve_reject`](tasks/approve_reject.md) — Genehmigen/Ablehnen | Zeigt dem Bearbeiter eine JA/NEIN-Entscheidungsseite, optional mit eingebetteter PDF-Vorschau im Vollbild. | | [`assign_group`](tasks/assign_group.md) — Gruppen-Freigabe | Genehmigungsschritt an eine Gruppe zustellen; wer zuerst entscheidet, übernimmt (claim). | | [`calendar_event`](tasks/calendar_event.md) — Kalendereintrag anlegen | Legt vollautomatisch (ohne Benutzerinteraktion) einen Ereignis-Eintrag an — Termin, Frist oder Vermerk — und verknüpft ihn mit einem Bezugsobjekt (z. | | [`cloud_link`](tasks/cloud_link.md) — Öffentlichen Cloud-Freigabelink erzeugen | Gibt eine Datei im angebundenen Cloud-Speicher per öffentlichem Link frei und schreibt die Link-URL in den Kontext. | | [`email`](tasks/email.md) — E-Mail versenden | Versendet eine E-Mail (ohne Benutzerinteraktion) mit Betreff, Text und optionalen Anhängen. | | [`html_form`](tasks/html_form.md) — Freies HTML-Formular | Rendert ein beliebiges, selbst definiertes HTML-Formular und blockiert den Workflow, bis der Benutzer es absendet; `` bestimmt Bearbeiter und Benachrichtigung. | | [`load_person`](tasks/load_person.md) — 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`](tasks/mkz_pick.md) — Maßnahmen auswählen | Interaktive (Mehrfach-)Auswahl von Maßnahmen (MKZ) eines Verfahrens aus einer filterbaren Liste. | | [`person_pick`](tasks/person_pick.md) — Person auswählen | Interaktive Auswahl einer oder mehrerer Personen über ein durchsuchbares Dropdown. | | [`quorum`](tasks/quorum.md) — Abstimmung (N von M) | Mehrpersonen-Abstimmung: Jede in `` genannte Person darf eine Stimme (Zustimmen/Ablehnen, optional mit Kommentar) abgeben. | | [`tg_pick`](tasks/tg_pick.md) — Verfahren auswählen | Interaktive Auswahl eines Verfahrens (Teilnehmergemeinschaft) über ein durchsuchbares Dropdown (Suche nach VKZ oder Name). | | [`webhook`](tasks/webhook.md) — HTTP-Aufruf an externe URL | Sendet einen HTTP-Request an eine beliebige URL (z. | ## KI und Fachdienste | Task | Was er kann | |---|---| | [`ki_auftrag`](tasks/ki_auftrag.md) — KI-Auftrag | Freitext-Auftrag (klassifizieren/zusammenfassen/entwerfen) an die zentrale KI-Kette, optional mit Datei/Variable als Material. | | [`ki_dok_extrakt`](tasks/ki_dok_extrakt.md) — KI-Feldextraktion | Schema-basierte Feldextraktion aus PDF/Text per KI mit Pflichtfeld-Validierung. | | [`paperless_ablage`](tasks/paperless_ablage.md) — Archiv-Ablage | Datei mit Metadaten ins Dokumentenarchiv (Paperless-ngx) übergeben. | | [`rag_recherche`](tasks/rag_recherche.md) — RAG-Recherche | Belegte Antwort mit Quellenangaben aus dem hausinternen Vektor-RAG. | | [`route_dienstfahrt`](tasks/route_dienstfahrt.md) — 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`](tasks/pdf_from_template.md) — PDF aus HTML-Vorlage | Erzeugt ein neues PDF aus HTML — entweder direkt inline im XML oder aus einer hinterlegten HTML-Vorlagendatei. | | [`pdf_merge`](tasks/pdf_merge.md) — PDFs zusammenführen | Fügt mehrere PDF-Dateien in der angegebenen Reihenfolge zu einem Gesamtdokument zusammen. | | [`pdf_rotate`](tasks/pdf_rotate.md) — Interaktive Seitendrehung | Zeigt dem Bearbeiter eine Vorschau aller Seiten mit Dreh-Buttons (0°/90°/180°/270° je Seite, plus „Alle gleich drehen"). | | [`pdf_sign`](tasks/pdf_sign.md) — 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`](tasks/pdf_sign_at_text.md) — Stempeln/Signieren an gefundener Textstelle | Sucht eine Phrase im PDF-Text (z. | | [`pdf_split`](tasks/pdf_split.md) — PDF aufteilen | Teilt ein PDF in Einzelseiten oder benannte Seitenbereiche auf. | | [`pdf_stamp_interactive`](tasks/pdf_stamp_interactive.md) — Interaktive Stempelplatzierung | Bearbeiter platziert einen Stempel (Vorlage, Bild oder Text) per Klick frei auf der PDF-Seite; danach wird er dauerhaft eingebrannt. | | [`pdf_text`](tasks/pdf_text.md) — Text an feste Koordinate schreiben | Schreibt einen (dynamischen) Text an eine feste Koordinate in ein bestehendes PDF — z. | | [`pdf_text_at_text`](tasks/pdf_text_at_text.md) — 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`](tasks/vorlage_docx.md) — 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. |