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

Alle Task-Typen im Überblick, gruppiert nach Einsatzzweck. Konventionen:

  • Pflicht? bezieht sich auf das jeweilige XML-Element innerhalb von <config> (sofern nicht anders vermerkt).
  • Alle Werte unterstützen Mustache-Platzhalter {{...}}.
  • Interaktive Tasks blockieren den Workflow (WAITING), bis der zugewiesene Bearbeiter reagiert; <assign_to> steht direkt unter <task>, nicht in <config>.

Steuerung, Logik und Zeit

calc — Berechnung

Zweck: Wertet einen oder mehrere arithmetische bzw. logische Ausdrücke aus und schreibt die Ergebnisse als Kontextvariablen — über einen sicheren eigenen Parser (kein eval).

Parameter

Name Pflicht? Default Beschreibung
<var name="…" expr="…"/> ja (min. 1) Wiederholbar. name = Ziel-Kontextvariable, expr = Ausdruck (Fallback: Element-Text statt expr-Attribut).

Unterstützte Operatoren: + - * / %, == != < > <= >=, and/or/not (XML-freundliche Alternative zu &&/||/!). Funktionen: round(x, stellen), ceil, floor, abs, min, max, len, upper, lower, concat(...), ifelse(bedingung, dann, sonst). true/false werden zu 1/0. Dezimalkomma (1,50) wird toleriert.

Eingangswerte: Beliebige Kontextvariablen via {{platzhalter}} — sie werden vor dem Parsen aufgelöst.

Ausgangswerte: Jede <var> wird unter ihrem name in den Kontext geschrieben. Vorherige <var>-Zeilen sind in nachfolgenden sofort verfügbar. Bei Auswertungsfehler bricht der Task mit error ab.

<task type="calc" id="berechne">
  <var name="brutto"     expr="{{netto}} * 1.19" />
  <var name="rabatt"     expr="round({{brutto}} * {{rabatt_pct}} / 100, 2)" />
  <var name="endsumme"   expr="{{brutto}} - {{rabatt}}" />
  <var name="grosskunde" expr="{{endsumme}} > 10000" />
  <var name="info"       expr="concat('Endsumme: ', {{endsumme}}, ' EUR')" />
</task>

escalate — Eskalation bei Fristüberschreitung

Zweck: Hält den Workflow bis zum Ablauf einer Frist im Wartezustand; solange bleibt der ursprüngliche Bearbeiter zuständig. Nach Fristablauf wird ein Eskalationsziel per E-Mail benachrichtigt und der Workflow läuft weiter. Typischer Einsatz: parallel zu einem Formular-Task (Watchdog-Zweig), damit liegengebliebene Aufgaben an die nächste Ebene gehen.

Parameter

Name Pflicht? Default Beschreibung
after_days eines von beiden 0 Frist in Tagen (Dezimalwerte erlaubt).
after_hours eines von beiden 0 Frist in Stunden; wird mit after_days addiert.
escalate_to ja E-Mail-Adresse des Eskalationsziels.
original_assignee nein leer Bearbeiter, der bis zur Eskalation zuständig bleibt (assign_to während der Wartezeit).
message nein „Eskalation: Frist verstrichen." Text der Eskalations-Mail.
flag_var nein Kontextvariable, die bei Eskalation auf "1" gesetzt wird.

Eingangswerte: Platzhalter in escalate_to, original_assignee, message (z. B. {{chef_mail}}).

Ausgangswerte: Bei Eskalation assign_to = Eskalationsziel; optional <flag_var> = "1". Der Fristbeginn wird beim ersten Lauf eingefroren, erneutes Rendern startet die Frist nicht neu. Für vollautomatisches Aufwachen sorgt ein regelmäßiger Hintergrund-Prozess, der wartende Workflows reaktiviert.

<task type="escalate" id="esc_vorsitz">
  <config>
    <after_days>3</after_days>
    <original_assignee>{{vorsitz_mail}}</original_assignee>
    <escalate_to>{{chef_mail}}</escalate_to>
    <message>Frist verstrichen — bitte stellvertretend genehmigen.</message>
    <flag_var>eskaliert</flag_var>
  </config>
</task>

external_trigger — Auf externen Callback warten

Zweck: Pausiert den Workflow, bis ein externes System (Signatur-Dienst, Webhook, Pipeline, manueller Klick) eine generierte Callback-URL mit einmaligem Token aufruft. Die mitgesendete Payload landet im Workflow-Kontext. Optional wird das externe System beim Pausieren aktiv per HTTP benachrichtigt (Gegenstück zum ausgehenden webhook-Task).

Parameter

Name Pflicht? Default Beschreibung
notify_url nein URL, die beim Pausieren einmalig aufgerufen wird (z. B. um dem externen System die Callback-URL mitzuteilen).
notify_method nein POST HTTP-Methode für den Notify-Aufruf.
notify_body nein leer Request-Body (Content-Type application/json), Platzhalter erlaubt.
notify_header name="…" nein Wiederholbar; zusätzliche HTTP-Header für den Notify-Aufruf.
url_var nein trigger_url Name der Kontextvariable, in der die Callback-URL abgelegt wird.
store_payload_var nein Kontextvariable, unter der die eingehende Callback-Payload gespeichert wird.
allowed_ips nein CSV erlaubter Absender-IPs (exakter Match, kein CIDR).
hmac_secret nein HMAC-Secret; Callback muss dann Header X-Signature: sha256=<hex(hmac(secret, body))> mitsenden.
timeout_days nein 0 (kein Timeout) Hard-Timeout in Tagen; danach endet der Task mit error.

Eingangswerte: Platzhalter in allen Notify-Feldern; {{trigger_url}} ist im notify_body bereits verfügbar.

Ausgangswerte: <url_var> (Default trigger_url) = Callback-URL mit Token; nach Empfang optional <store_payload_var> = Payload des Callbacks.

<task type="external_trigger" id="warte_auf_signatur">
  <config>
    <notify_url>https://sign.example.org/api/sign</notify_url>
    <notify_body>{"document":"{{pdf_url}}","callback":"{{trigger_url}}"}</notify_body>
    <notify_header name="Authorization">Bearer {{api_token}}</notify_header>
    <store_payload_var>signatur_antwort</store_payload_var>
    <timeout_days>7</timeout_days>
  </config>
</task>

if — Bedingte Verzweigung

Zweck: Führt abhängig von einer Bedingung die Tasks im <then>- oder im optionalen <else>-Zweig aus. Auch innerhalb der Zweige funktionieren wartende Tasks — der Workflow setzt beim nächsten Trigger an derselben Stelle fort.

Parameter

Name Pflicht? Default Beschreibung
condition (Attribut) ja Bedingung mit Platzhaltern. Operatoren: ==, !=, >, < (numerisch). Ohne Operator: leerer Wert = falsch, nicht-leerer Wert = wahr. Anführungszeichen um Werte werden entfernt.
<then> ja Container mit beliebigen Tasks für den Wahr-Fall.
<else> nein Container mit Tasks für den Falsch-Fall.

Eingangswerte: Kontextvariablen in der Bedingung (z. B. {{decision}}).

Ausgangswerte: Keine eigenen; Ausgaben der Kind-Tasks werden in den Kontext übernommen.

<task type="if" condition="{{decision}} == 'approved'">
  <then>
    <task type="email" id="mail_ok"></task>
  </then>
  <else>
    <task type="email" id="mail_nok"></task>
    <task type="stop" id="abbruch" />
  </else>
</task>

log_step — Audit-Log-Eintrag

Zweck: Schreibt einen unveränderlichen Eintrag in das Audit-Log des Workflows (mit Task-ID, Aktion, Benutzer, Details und Zeitstempel). Nützlich zur Nachvollziehbarkeit nach Genehmigungen, Ablehnungen oder anderen wichtigen Schritten.

Parameter

Name Pflicht? Default Beschreibung
aktion nein „Schritt ausgeführt" Kurzbezeichnung der protokollierten Aktion.
benutzer nein aktueller Bearbeiter bzw. system Wer die Aktion ausgeführt hat.
details nein leer Freitext mit Zusatzinformationen.

Eingangswerte: Platzhalter in allen Feldern (z. B. {{current_assignee}}, {{decision}}).

Ausgangswerte: Keine Kontextvariablen; der Eintrag wird ausschließlich ans Audit-Log angehängt.

<task type="log_step" id="log_genehmigung">
  <config>
    <aktion>Antrag genehmigt</aktion>
    <benutzer>{{current_assignee}}</benutzer>
    <details>Entscheidung: {{decision}}, Grund: {{reason}}</details>
  </config>
</task>

loop_foreach — Schleife über eine Liste

Zweck: Iteriert über ein Array aus dem Kontext und führt die direkt enthaltenen Kind-Tasks pro Element aus. Voll wartefähig: Pausiert der Workflow mitten in einer Iteration (z. B. für eine Benutzeraktion), geht es beim nächsten Trigger exakt dort weiter. Jede Iteration hat einen eigenen Sub-State, damit sich Abschluss-Marker nicht vermischen.

Parameter

Name Pflicht? Default Beschreibung
items ja Quelle der Elemente: Kontext-Array ({{anlagen}}), JSON-String oder kommaseparierte Liste.
as nein item Variablenname des aktuellen Elements im Kontext.
index_var nein Variable für den 0-basierten Index.
total_var nein Variable für die Gesamtanzahl.
Kind-Tasks ja Beliebige <task>-Elemente direkt im Schleifen-Element (analog zu einer Sequenz). assign_to an Kind-Tasks wird bei Wartezuständen automatisch übernommen.

Eingangswerte: Die referenzierte Liste unter items.

Ausgangswerte: Pro Durchlauf {{<as>}} (aktuelles Element); bei Objekt-Elementen zusätzlich flache Felder {{<as>_feld}} bzw. {{<as>.feld}}; optional {{<index_var>}} und {{<total_var>}}. Ausgaben der Kind-Tasks landen im Kontext. Leere Liste = Schleife wird übersprungen.

<task type="loop_foreach" id="alle_anlagen">
  <items>{{anlagen}}</items>
  <as>anlage</as>
  <index_var>i</index_var>
  <total_var>n</total_var>

  <task type="pdf_stamp_interactive" id="stempel">
    <assign_to>{{ICH.mail}}</assign_to>
    <config>
      <file>{{anlage.pdf_pfad}}</file>
    </config>
  </task>
</task>

map_lookup — Wertetabelle / Mapping

Zweck: 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. Bei fehlendem Treffer greift ein Default.

Parameter

Name Pflicht? Default Beschreibung
input ja leer Zu vergleichender Wert (Platzhalter erlaubt).
target_var ja Name der Ziel-Kontextvariable.
default nein leer Wert bei fehlendem Treffer.
case_sensitive nein false true = Groß-/Kleinschreibung beachten.
<map><entry from="…" to="…"/></map> ja Wiederholbare Einträge; erste Übereinstimmung gewinnt. Statt to-Attribut auch Element-Form <entry from="…"><to>{{…}}</to></entry>. Platzhalter in from/to erlaubt.

Eingangswerte: Der Wert aus input (z. B. {{subart}}).

Ausgangswerte: <target_var> = gemappter Wert bzw. Default; <target_var>_matched = "1" (Treffer) oder "0" (Default) — praktisch für nachfolgende <if>-Blöcke.

<task type="map_lookup" id="status_zu_feld">
  <input>{{subart}}</input>
  <target_var>ziel_feld</target_var>
  <default>Sonstiges</default>
  <map>
    <entry from="Planung abgeschlossen" to="PlanungEnde" />
    <entry from="Schlussabnahme"        to="IstBE" />
  </map>
</task>

return_to — Zur Überarbeitung zurückgeben

Zweck: 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. Die ursprüngliche Person wird benachrichtigt; der Workflow bleibt aktiv (kein Abbruch). Typischer Einsatz in einem <if>-Block nach einer Genehmigungsentscheidung „zurückgeben".

Parameter

Name Pflicht? Default Beschreibung
clear_tasks nein leer Kommaseparierte Task-IDs, deren Abschluss-Status gelöscht wird (zugehörige Formular-Token werden mit zurückgesetzt).
assign_to nein leer Empfänger der Rückgabe-Benachrichtigung.
message nein „Der Antrag wurde zur Überarbeitung zurückgegeben." Nachricht an den Empfänger.

Eingangswerte: Platzhalter in assign_to und message (z. B. {{comment}}).

Ausgangswerte: assign_to und return_message (falls assign_to gesetzt). Der Task wird bewusst nie als abgeschlossen markiert und läuft bei jedem Trigger erneut, solange die Bedingung ihn erreicht.

<task type="if" condition="{{decision}} == 'return'">
  <then>
    <task type="return_to" id="zurueck_zu_schritt1">
      <config>
        <clear_tasks>schritt_1_upload,schritt_2_sign_in</clear_tasks>
        <assign_to>{{antragsteller_mail}}</assign_to>
        <message>Bitte überarbeiten: {{comment}}</message>
      </config>
    </task>
  </then>
</task>

schedule_resume — Geplante automatische Fortsetzung

Zweck: 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. Optional wird beim Pausieren eine Info-Mail versendet.

Parameter (eine Zeitangabe genügt; Priorität: at > after > weekday+time)

Name Pflicht? Default Beschreibung
at eines von A/B/C Absoluter Zeitpunkt, z. B. 2026-05-20 08:00.
after eines von A/B/C Relativ zu jetzt (strtotime-Syntax), z. B. +3 days 09:00.
weekday eines von A/B/C Nächster Wochentag (mondaysunday, englisch).
time nein Uhrzeit zum weekday, z. B. 08:00.
pause_notify_to nein Empfänger einer Info-Mail beim Pausieren.
message nein automatischer Text Text der Info-Mail.
store_var nein Kontextvariable mit dem Zielzeitpunkt (Format d.m.Y H:i), z. B. für Templates.

Eingangswerte: Platzhalter in allen Zeit- und Mail-Feldern.

Ausgangswerte: Optional <store_var> = geplanter Zeitpunkt. Der Zielzeitpunkt wird beim ersten Lauf eingefroren. Während der Wartezeit wird bewusst keine Aufgaben-Mail versendet (kein assign_to).

<task type="schedule_resume" id="weckruf_montag">
  <config>
    <weekday>monday</weekday>
    <time>08:00</time>
    <pause_notify_to>{{antragsteller_mail}}</pause_notify_to>
    <message>Antrag wird {{resume_at}} weiterverarbeitet.</message>
    <store_var>resume_at</store_var>
  </config>
</task>

set_var — Kontextvariablen setzen

Zweck: Setzt eine oder mehrere Kontextvariablen ohne Benutzerinteraktion. Alle Werte unterstützen Platzhalter; nachfolgende Tasks und E-Mail-Templates können auf die Variablen zugreifen.

Parameter

Name Pflicht? Default Beschreibung
<var name="…">wert</var> ja (min. 1) Wiederholbar. name = Variablenname, Element-Text = Wert (Platzhalter erlaubt).

Eingangswerte: Beliebige Kontextvariablen in den Werten (z. B. {{antragsteller_name}}, {{DATE}}).

Ausgangswerte: Jede <var> unter ihrem Namen im Kontext; frühere <var>-Zeilen sind in späteren sofort nutzbar.

<task type="set_var" id="vars_setzen">
  <var name="betreff">Antrag von {{antragsteller_name}} vom {{DATE}}</var>
  <var name="empfaenger">registratur@example.org</var>
  <var name="betrag_formatiert">{{betrag}} EUR</var>
</task>

signal_fire — Internes Ereignis auslösen

Zweck: Feuert ein Ereignis auf dem internen Event-Bus der Anwendung, sodass registrierte Listener reagieren — ohne Umweg über die HTTP-API. Als Argumente können skalare Werte oder geladene Fachobjekte übergeben werden. Ist der Event-Bus nicht verfügbar, wird der Task mit Warnhinweis übersprungen.

Parameter

Name Pflicht? Default Beschreibung
event ja Ereignisname.
<arg> nein Wiederholbar; skalares Argument (Platzhalter erlaubt).
<load type="massnahme" md5="…"/> nein Lädt eine Maßnahme als Argument.
<load type="person" id="…"/> nein Lädt eine Person als Argument.
<load type="ereignis" id="…"/> nein Lädt ein Ereignis als Argument.

Die Argumente werden in XML-Reihenfolge übergeben; nicht ladbare <load>-Objekte werden übersprungen.

Eingangswerte: Platzhalter in event, <arg> und <load>-Attributen (z. B. {{massnahme_md5}}, {{ICH.id}}).

Ausgangswerte: Keine Kontextvariablen. Wirft ein Listener eine Exception, endet der Task mit error.

<task type="signal_fire" id="emit_done">
  <config>
    <event>massnahme.saved</event>
    <load type="massnahme" md5="{{massnahme_md5}}" />
    <arg>{{ICH.id}}</arg>
  </config>
</task>

stop — Workflow abbrechen

Zweck: Bricht den gesamten Workflow sofort ab; die Engine setzt den Gesamtstatus auf CANCELLED. Typischer Einsatz nach einer Ablehnung.

Parameter

Name Pflicht? Default Beschreibung
message nein Standard-Abbruchseite Abbruch-Nachricht (Platzhalter erlaubt).

Eingangswerte: Platzhalter in message.

Ausgangswerte: Keine; der Task wird nie als abgeschlossen markiert, da der Workflow endet.

<task type="stop" id="abort_process">
  <message>Der Antrag wurde abgelehnt.</message>
</task>

subworkflow — Anderen Workflow einbetten

Zweck: 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. Sub- und Parent-Workflow teilen sich keinen Ausführungsstatus, dieselbe XML kann also mehrfach eingebettet werden. Variablen werden per <pass> hinein- und per <return>/merge_prefix herausgereicht.

Parameter

Name Pflicht? Default Beschreibung
xml ja Name der Sub-Workflow-Definition (ohne .xml).
<pass><var name="…">wert</var></pass> nein Eingangsvariablen für den Sub-Workflow. Ohne <pass> erbt er den gesamten Parent-Kontext.
<return><var name="…" from="…"/></return> nein Rückgabevariablen: from = Name im Sub-Kontext (Default: wie name), name = Zielname im Parent-Kontext.
merge_prefix nein Hängt alternativ/zusätzlich den kompletten Sub-Kontext mit diesem Präfix in den Parent-Kontext.

Eingangswerte: Parent-Kontext (komplett geerbt) plus explizite <pass>-Variablen.

Ausgangswerte: Die <return>-Variablen bzw. alle Sub-Variablen unter <merge_prefix>…. Wartezustände (inkl. assign_to) werden aus dem Sub-Workflow durchgereicht. Hinweis: Der Sub-Workflow erscheint nicht als eigener Eintrag im Dashboard — dafür stattdessen einen regulären Workflow-Start nutzen.

<task type="subworkflow" id="freigabe">
  <config>
    <xml>freigabe-modul</xml>
    <pass>
      <var name="betrag">{{summe}}</var>
    </pass>
    <return>
      <var name="freigabe_decision" from="decision" />
      <var name="freigabe_comment"  from="comment" />
    </return>
  </config>
</task>

wait_until — Bis Zeitpunkt warten

Zweck: Pausiert den Workflow bis zu einem festen oder relativen Zeitpunkt. Der Task ist passiv: Er prüft die Uhrzeit beim nächsten Trigger (erneutes Öffnen des Links oder automatischer Weckruf) und schließt sich ab, sobald der Zeitpunkt erreicht ist. Für vollautomatisches Wecken zum Zeitpunkt selbst eignet sich schedule_resume.

Parameter

Name Pflicht? Default Beschreibung
until eines von beiden Absoluter Zeitpunkt, z. B. 2026-06-01 09:00 (Vorrang vor relative).
relative eines von beiden Relativ ab jetzt (strtotime-Syntax), z. B. +14 days.
assign_to nein Optionale Zuständigkeit/Benachrichtigung während der Wartezeit.
store_var nein Kontextvariable mit dem Zielzeitpunkt (Format Y-m-d H:i:s).

Eingangswerte: Platzhalter in until, relative, assign_to.

Ausgangswerte: Optional <store_var> = eingefrorener Zielzeitpunkt. Der Zeitpunkt wird beim ersten Lauf berechnet und fixiert — +14 days zählt also ab dem ersten Lauf, nicht ab jedem Wiederaufruf.

<task type="wait_until" id="frist_2_wochen">
  <config>
    <relative>+14 days</relative>
    <assign_to>{{antragsteller_mail}}</assign_to>
    <store_var>frist_zeitpunkt</store_var>
  </config>
</task>

Interaktion und Kommunikation

approve_reject — Genehmigen/Ablehnen

Zweck: Zeigt dem Bearbeiter eine JA/NEIN-Entscheidungsseite, optional mit eingebetteter PDF-Vorschau im Vollbild. Der Workflow blockiert, bis der Benutzer entscheidet; <assign_to> bestimmt den Bearbeiter und wer benachrichtigt wird. Bei Ablehnung wird (standardmäßig verpflichtend) eine Begründung über ein Dialogfenster abgefragt.

Parameter

Name Pflicht? Default Beschreibung
pdf nein Pfad-Variable zu einer PDF-Datei; wenn gesetzt und vorhanden, wird sie als Vorschau eingebettet
title nein Genehmigung erforderlich Überschrift der Seite
subtitle nein (leer) Untertitel
approve_label nein Genehmigen Beschriftung des Zustimmen-Buttons
reject_label nein Ablehnen Beschriftung des Ablehnen-Buttons
require_reason_on_reject nein true Begründung bei Ablehnung erzwingen
require_reason_on_approve nein false Begründung auch bei Genehmigung erzwingen
output_var nein entscheidung Prefix der Ausgabevariablen

Eingangswerte: Alle Parameter unterstützen Platzhalter, z. B. {{latest_pdf_path}} in <pdf>.

Ausgangswerte:

  • <prefix>approved oder rejected
  • <prefix>_reason — Begründung (Plain Text, ggf. leer)
  • <prefix>_at — ISO-8601-Zeitstempel der Entscheidung
<task type="approve_reject" id="freigabe">
  <assign_to>{{fbl_mail}}</assign_to>
  <config>
    <pdf>{{latest_pdf_path}}</pdf>
    <title>Antrag freigeben</title>
    <output_var>entscheidung</output_var>
  </config>
</task>

calendar_event — Kalendereintrag anlegen

Zweck: Legt vollautomatisch (ohne Benutzerinteraktion) einen Ereignis-Eintrag an — Termin, Frist oder Vermerk — und verknüpft ihn mit einem Bezugsobjekt (z. B. Maßnahme, Person, Verfahren, Vertrag). Optional können Erinnerungen für Personen hinterlegt werden.

Parameter

Name Pflicht? Default Beschreibung
referenz_typ ja Typ des Bezugsobjekts, z. B. Massnahme, Person, TG, Vertrag
referenz_id ja ID/Hash des Bezugsobjekts (als String)
subart nein Subkategorie, z. B. Frist
kommentar nein Text/Inhalt des Eintrags
datum nein jetzt Termin-Datum
frist nein (keine) Fristdatum
kosten nein 0 Kostenangabe (Komma oder Punkt als Dezimaltrenner)
person nein Numerische ID des Erstellers, z. B. {{ICH.id}}
zuerinnern nein CSV numerischer Personen-IDs, die erinnert werden sollen
output_var nein ereignis_id Prefix der Ausgabevariablen

Eingangswerte: Datumsangaben akzeptieren Unix-Timestamp (rein numerisch), ISO-Format (2026-05-20 oder 2026-05-20 09:00) oder relative Angaben (+14 days, monday 09:00).

Ausgangswerte:

  • <prefix> — ID des neuen Eintrags
  • <prefix>_datum — formatiertes Datum d.m.Y (z. B. für Mails)
  • <prefix>_frist — formatierte Frist d.m.Y (oder leer)
<task type="calendar_event" id="frist_setzen">
  <config>
    <referenz_typ>Massnahme</referenz_typ>
    <referenz_id>{{massnahme_id}}</referenz_id>
    <subart>Frist</subart>
    <kommentar>{{betreff}}</kommentar>
    <frist>+14 days</frist>
    <zuerinnern>{{gewaehlte_person}}</zuerinnern>
  </config>
</task>

Zweck: Gibt eine Datei im angebundenen Cloud-Speicher per öffentlichem Link frei und schreibt die Link-URL in den Kontext. Optional wird eine lokale Datei zuvor hochgeladen. Standardberechtigung ist „nur lesen"; Passwortschutz und Ablaufdatum werden unterstützt.

Parameter

Name Pflicht? Default Beschreibung
remote ja Zielpfad in der Cloud (relativ, ohne führenden /)
local nein Lokale Datei, die vor der Freigabe an remote hochgeladen wird; Upload-Fehler bricht den Task ab
password nein (keins) Passwort für den Freigabelink
expire_days nein 0 (kein Ablauf) Ablauf des Links in Tagen ab heute
permissions nein 1 Berechtigungen: 1 = nur lesen, 15 = Vollzugriff
cloud_user nein (Standardzugang) Alias eines konfigurierten Cloud-Zugangs
link_var nein share_url Name der Kontextvariablen für die öffentliche URL
token_var nein (keine) Name der Kontextvariablen für das Share-Token

Eingangswerte: Pfad-Variablen wie {{latest_pdf_path}} für <local>; alle Felder unterstützen Platzhalter.

Ausgangswerte:

  • <link_var> (Default share_url) — öffentliche Freigabe-URL
  • <token_var> — Share-Token (nur wenn konfiguriert)
<task type="cloud_link" id="share_pdf">
  <config>
    <remote>Workflow/{{WORKFLOW_ID}}/{{filename}}.pdf</remote>
    <local>{{latest_pdf_path}}</local>
    <expire_days>14</expire_days>
    <link_var>share_url</link_var>
  </config>
</task>

email — E-Mail versenden

Zweck: Versendet eine E-Mail (ohne Benutzerinteraktion) mit Betreff, Text und optionalen Anhängen. Der Versand erfolgt über den Mail-Spooler des Systems; Empfänger, Betreff, Text und Anhänge unterstützen Platzhalter. Im Testmodus (test_modus_mail gesetzt) wird der Empfänger umgeleitet und CC/BCC unterdrückt.

Parameter

Name Pflicht? Default Beschreibung
an ja Empfängeradresse(n)
titel ja Betreff
text ja Mailtext (HTML erlaubt, CDATA empfohlen)
cc nein CC-Empfänger, kommagetrennt
bcc nein BCC-Empfänger, kommagetrennt
attachments/file nein Ein <file>-Element je Anhang (Pfad); optionales Attribut name="…" setzt einen sprechenden Anzeigenamen (ebenfalls mit Platzhaltern), sonst wird der Dateiname des Pfads verwendet

Eingangswerte: Beliebige Kontextvariablen in allen Feldern. Zwei Komfort-Anreicherungen (nur für das Rendern dieser Mail):

  • Jede Variable mit Suffix _json, die gültiges JSON enthält, steht zusätzlich ohne Suffix als Liste zur Verfügung — nutzbar als Template-Section: {{#mkz_auswahl}}<li>{{mkz}} — {{name}}</li>{{/mkz_auswahl}}
  • Ist {{vkz}} gesetzt, werden {{tg_name}} und {{tg_kurzname}} automatisch nachgeschlagen, sofern noch nicht vorhanden.

Ausgangswerte: keine.

<task type="email" id="info_mail">
  <config>
    <an>{{gewaehlte_person_mail}}</an>
    <cc>info@example.org</cc>
    <titel>Neuer Vorgang {{WORKFLOW_ID}}</titel>
    <text><![CDATA[Sehr geehrte Damen und Herren, ...]]></text>
    <attachments>
      <file name="Antrag_{{vkz}}.pdf">{{latest_pdf_path}}</file>
    </attachments>
  </config>
</task>

html_form — Freies HTML-Formular

Zweck: Rendert ein beliebiges, selbst definiertes HTML-Formular und blockiert den Workflow, bis der Benutzer es absendet; <assign_to> bestimmt Bearbeiter und Benachrichtigung. Nach dem Absenden werden alle POST-Felder unter ihrem Feldnamen in den Workflow-Kontext übernommen und stehen nachfolgenden Tasks als {{feldname}} zur Verfügung. Ein CSRF-Schutz wird automatisch in jedes <form>-Tag injiziert — dafür ist nichts zu konfigurieren.

Parameter

Name Pflicht? Default Beschreibung
html ja Vollständiger HTML-Inhalt der Seite (mit mindestens einem <form method="post">); Platzhalter werden gerendert
required nein (keine) CSV von Feldnamen, die nicht leer sein dürfen (gilt für Text- und Dateifelder); bei Verstoß wird das Formular mit Hinweis erneut angezeigt

Eingangswerte: Beliebige Kontextvariablen im HTML; zusätzlich steht {{TASK_ID}} zur Verfügung.

Ausgangswerte:

  • Jedes abgesendete Formularfeld als {{feldname}} (Strings getrimmt)
  • Datei-Uploads (<input type="file" name="feld">): pro Datei drei Variablen —
    • {{feld}} — Ablagepfad der hochgeladenen Datei (für Folgetasks wie PDF-Verarbeitung oder Mail-Anhang)
    • {{feld}}_name — ursprünglicher Dateiname
    • {{feld}}_url — abrufbare URL der Datei

Uploads sind auf 20 MB begrenzt; ausführbare Formate (Skripte, Programme) werden serverseitig abgelehnt.

<task type="html_form" id="antrag_erfassen">
  <assign_to>{{antragsteller_mail}}</assign_to>
  <config>
    <required>betreff,anlage</required>
    <html><![CDATA[
      <!DOCTYPE html><html><body>
        <form method="post" enctype="multipart/form-data">
          <input type="text" name="betreff" placeholder="Betreff">
          <textarea name="beschreibung"></textarea>
          <input type="file" name="anlage">
          <button type="submit">Absenden</button>
        </form>
      </body></html>
    ]]></html>
  </config>
</task>

load_person — Personendaten nachladen

Zweck: 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. Läuft ohne Benutzerinteraktion.

Parameter

Name Pflicht? Default Beschreibung
source_id ja Ausdruck, der die ID liefert, z. B. {{fbl_id}}; akzeptiert numerische ID, E-Mail oder Login
target_var ja Prefix der Ausgabevariablen, z. B. FBL

Eingangswerte: Die per source_id referenzierte Kontextvariable. Ist sie leer, wird der Task übersprungen (Erfolg ohne Daten); ist die Person nicht auffindbar, schlägt der Task fehl.

Ausgangswerte:

  • <target_var> — Personendatensatz als Struktur (id, Vorname, Nachname, mail, telefon, funktion)
  • Flache Einzelvariablen für einfaches Templating: <target_var>_id, <target_var>_Vorname, <target_var>_Nachname, <target_var>_mail, <target_var>_telefon, <target_var>_funktion
<task type="load_person" id="fbl_laden">
  <config>
    <source_id>{{fbl_id}}</source_id>
    <target_var>FBL</target_var>
  </config>
</task>
<!-- danach nutzbar: {{FBL_mail}}, {{FBL_Nachname}} … -->

mkz_pick — Maßnahmen auswählen

Zweck: Interaktive (Mehrfach-)Auswahl von Maßnahmen (MKZ) eines Verfahrens aus einer filterbaren Liste. Der Workflow blockiert bis zur Auswahl; <assign_to> bestimmt Bearbeiter und Benachrichtigung. Die Auswahl wird serverseitig nachgeladen und validiert (Manipulationsschutz). Optional kann der Bearbeiter direkt im Dialog eine neue („virtuelle") MKZ anlegen.

Parameter

Name Pflicht? Default Beschreibung
vkz ja Verfahrenskennziffer, deren Maßnahmen angeboten werden, z. B. {{vkz}}
multiple nein true Mehrfachauswahl erlauben
only_bau nein true Nur Bau-Maßnahmen anzeigen
allow_virtual nein false Button „virtuelle MKZ anlegen" anbieten (mit Prüfziffern-Berechnung)
required nein true Mindestens eine Auswahl erzwingen
title nein Maßnahmen auswählen Überschrift
text nein (leer) Einleitungstext
output_var nein mkz_auswahl Prefix der Ausgabevariablen

Eingangswerte: vkz (typischerweise aus einem vorangegangenen tg_pick).

Ausgangswerte:

  • <prefix>_json — JSON-Array [{uuid, mkz, name}, …]
  • <prefix>_csv"MKZ1, MKZ2, …" (lesbare Liste)
  • <prefix>_count — Anzahl

Im email-Task wird <prefix>_json automatisch als Section {{#<prefix>}}…{{/<prefix>}} nutzbar.

<task type="mkz_pick" id="massnahmen_waehlen">
  <assign_to>{{bearbeiter_mail}}</assign_to>
  <config>
    <vkz>{{vkz}}</vkz>
    <allow_virtual>true</allow_virtual>
    <output_var>mkz_auswahl</output_var>
  </config>
</task>

person_pick — Person auswählen

Zweck: Interaktive Auswahl einer oder mehrerer Personen über ein durchsuchbares Dropdown. Die Auswahlliste stammt aus in der Organisation gepflegten Gruppen (oder allen Personen, wenn keine Gruppe angegeben ist). Der Workflow blockiert bis zur Auswahl. Die gewählten IDs werden serverseitig nachgeladen und validiert (Manipulationsschutz).

Parameter

Name Pflicht? Default Beschreibung
group nein (alle Personen) Name einer Gruppe oder CSV mehrerer Gruppen (Vereinigungsmenge), z. B. GRUPPE_A,GRUPPE_B
title nein Person auswählen Überschrift
text nein (leer) Einleitungstext
multiple nein false Mehrfachauswahl erlauben
required nein true Mindestens eine Person erzwingen
output_var nein gewaehlte_person Prefix der Ausgabevariablen

Eingangswerte: keine besonderen; alle Parameter unterstützen Platzhalter.

Ausgangswerte:

  • <prefix> — Personen-ID(s), bei Mehrfachauswahl kommagetrennt
  • <prefix>_json — JSON-Array [{id, vorname, nachname, mail, funktion, name}, …]
  • <prefix>_mail — E-Mail-Adresse(n), CSV
  • <prefix>_name — „Vorname Nachname", bei Mehrfachauswahl CSV
  • <prefix>_nachname — Nachname(n), CSV — z. B. für Stempeltexte gez. {{prefix_nachname}}
  • <prefix>_count — Anzahl
  • <prefix>_groups — CSV der konfigurierten Gruppen, in denen die gewählte Person Mitglied ist (Schnittmenge mit <group>); nützlich für Verzweigungen in nachfolgenden if-Tasks
<task type="person_pick" id="pruefer_waehlen">
  <assign_to>{{ICH.mail}}</assign_to>
  <config>
    <group>GRUPPE_LEITUNG</group>
    <title>Prüfer auswählen</title>
    <output_var>pruefer</output_var>
  </config>
</task>
<!-- danach: {{pruefer_mail}}, {{pruefer_name}}, {{pruefer_nachname}} … -->

quorum — Abstimmung (N von M)

Zweck: Mehrpersonen-Abstimmung: Jede in <assign_to> genannte Person darf eine Stimme (Zustimmen/Ablehnen, optional mit Kommentar) abgeben. Der Workflow blockiert, bis required_votes Ja-Stimmen erreicht sind (→ Erfolg) oder das Quorum rechnerisch nicht mehr erreichbar ist (→ Workflow-Stopp). Benachrichtigt werden jeweils nur die noch ausstehenden Abstimmenden. Doppelte Stimmabgaben werden ignoriert.

Parameter

Name Pflicht? Default Beschreibung
assign_to ja (direkt unter <task>) kommagetrennte Liste der Stimmberechtigten (E-Mail-Adressen oder IDs)
required_votes nein 1 Anzahl benötigter Ja-Stimmen
html nein (Standard-Formular) Eigenes Abstimmungsformular; muss ein Feld quorum_vote (approved/rejected) und optional quorum_comment senden

Eingangswerte: Im eigenen <html>-Template stehen während der Abstimmung zusätzlich bereit: {{quorum_approved_count}}, {{quorum_rejected_count}}, {{quorum_required}}, {{quorum_total}}, {{quorum_pending}}, {{quorum_already_voted}}, {{quorum_my_vote}}.

Ausgangswerte (nach erreichtem Quorum):

  • quorum_approved_count — Anzahl Ja-Stimmen
  • quorum_rejected_count — Anzahl Nein-Stimmen
  • quorum_votes — Liste aller Stimmen (Abstimmender, Stimme, Kommentar)
<task type="quorum" id="vorstand_abstimmung">
  <assign_to>person1@example.org,person2@example.org,person3@example.org</assign_to>
  <config>
    <required_votes>2</required_votes>
  </config>
</task>

tg_pick — Verfahren auswählen

Zweck: Interaktive Auswahl eines Verfahrens (Teilnehmergemeinschaft) über ein durchsuchbares Dropdown (Suche nach VKZ oder Name). Der Workflow blockiert bis zur Auswahl. Die gewählte VKZ wird serverseitig validiert.

Parameter

Name Pflicht? Default Beschreibung
title nein Teilnehmergemeinschaft auswählen Überschrift
text nein (leer) Einleitungstext
vkz_range nein (kein Filter) Beschränkt die Liste auf einen VKZ-Nummernbereich, Format min-max, z. B. 10-100
required nein true Auswahl erzwingen
output_var nein vkz Prefix der Ausgabevariablen

Eingangswerte: keine besonderen.

Ausgangswerte:

  • <prefix> — die gewählte VKZ (String)
  • <prefix>_name — Langname des Verfahrens
  • <prefix>_kurzname — Kurzname des Verfahrens
<task type="tg_pick" id="tg_waehlen">
  <assign_to>{{ICH.mail}}</assign_to>
  <config>
    <title>Verfahren auswählen</title>
    <vkz_range>10-100</vkz_range>
  </config>
</task>
<!-- danach: {{vkz}}, {{vkz_name}}, {{vkz_kurzname}} -->

webhook — HTTP-Aufruf an externe URL

Zweck: Sendet einen HTTP-Request an eine beliebige URL (z. B. um ein Drittsystem zu informieren) und kann die Antwort in den Kontext übernehmen. Läuft ohne Benutzerinteraktion; alle Felder unterstützen Platzhalter.

Parameter

Name Pflicht? Default Beschreibung
url ja Ziel-URL
method nein POST GET, POST, PUT oder PATCH
body nein (leer) Request-Body (bei POST/PUT/PATCH), z. B. JSON
header nein Content-Type: application/json Beliebig viele <header name="…">Wert</header>-Elemente; Werte mit Platzhaltern
timeout nein 10 Timeout in Sekunden
response_var nein (keine) Kontextvariable für den Antwort-Body
fail_on_error nein true Bei false gilt der Task auch bei HTTP ≥ 400 oder Verbindungsfehler als erfolgreich (Workflow läuft weiter)

Eingangswerte: Beliebige Kontextvariablen in url, body und Header-Werten.

Ausgangswerte (nur wenn response_var gesetzt):

  • <response_var> — Antwort-Body als String
  • <response_var>_data — Antwort zusätzlich als Struktur, falls sie gültiges JSON ist (Felder z. B. per {{antwort_data.feld}} nutzbar)
<task type="webhook" id="archivieren">
  <config>
    <url>https://api.example.org/archiv</url>
    <method>POST</method>
    <body>{"id":"{{WORKFLOW_ID}}","file":"{{pdf_url}}"}</body>
    <header name="Authorization">Bearer {{api_token}}</header>
    <response_var>archiv_antwort</response_var>
  </config>
</task>

PDF- und Dokument-Verarbeitung

Wichtige Konvention — Signatur ans Ende der Kette: pdf_sign und pdf_sign_at_text müssen als letzte PDF-Bearbeitungsschritte einer zusammenhängenden Kette stehen. Text- und Stempel-Tasks (pdf_text, pdf_text_at_text, pdf_stamp_interactive, pdf_merge, pdf_rotate …) importieren das PDF beim Bearbeiten neu und verwerfen dabei vorhandene Annotationen — auch kryptografische Signaturen. Erst alle inhaltlichen Änderungen, dann signieren. (Mehrere Signaturen nacheinander — z. B. Antragsteller, dann später Genehmiger — sind möglich, solange zwischen ihnen kein Text-/Stempel-Task läuft bzw. die erneute Signatur bewusst die letzte Aktion des jeweiligen Abschnitts ist.)

pdf_from_template — PDF aus HTML-Vorlage

Zweck: Erzeugt ein neues PDF aus HTML — entweder direkt inline im XML oder aus einer hinterlegten HTML-Vorlagendatei. Platzhalter werden vor dem Rendern ersetzt. Erspart das manuelle Setzen von Koordinaten, wie es pdf_text erfordern würde.

Parameter

Name Pflicht? Default Beschreibung
output ja Zielpfad der erzeugten PDF-Datei (Platzhalter erlaubt, z. B. {{temp_dir}}/antrag_{{WORKFLOW_ID}}.pdf). Verzeichnis wird bei Bedarf angelegt.
html ja¹ Inline-HTML (CDATA empfohlen); wird mit Kontext-Platzhaltern gerendert.
template ja¹ Name einer hinterlegten HTML-Vorlagendatei; fehlt die Dateiendung, wird sie automatisch ergänzt.
titel nein Dokument Dokumenttitel (Kopfbereich).
untertitel nein (leer) Untertitel, z. B. Stand {{DATE}}.
orientation nein P P (Hochformat) oder L (Querformat).
format nein A4 A4 oder A3.
cover nein false true = zusätzliche Deckblatt-Seite.

¹ Genau eine der beiden Quellen html oder template angeben.

Eingangswerte: beliebige Kontext-Variablen für die Platzhalter im HTML; {{temp_dir}} für den Ausgabepfad.

Ausgangswerte: latest_pdf_path, latest_pdf_url.

<task type="pdf_from_template" id="erstelle_pdf">
  <config>
    <output>{{temp_dir}}/antrag_{{WORKFLOW_ID}}.pdf</output>
    <titel>Antrag {{antragsteller_name}}</titel>
    <untertitel>Stand {{DATE}}</untertitel>
    <html><![CDATA[
      <h1>Antrag</h1>
      <p>Ihr Antrag vom {{DATE}} wurde mit Vorgang {{WORKFLOW_ID}} aufgenommen.</p>
    ]]></html>
  </config>
</task>

pdf_merge — PDFs zusammenführen

Zweck: Fügt mehrere PDF-Dateien in der angegebenen Reihenfolge zu einem Gesamtdokument zusammen.

Parameter

Name Pflicht? Default Beschreibung
output ja Zielpfad des zusammengeführten PDFs. Verzeichnis wird bei Bedarf angelegt.
file ja (mehrfach) Eingabedatei; Element beliebig oft wiederholen. Mindestens 2 existierende Dateien nötig; nicht gefundene Dateien werden mit Warnung übersprungen. Reihenfolge im XML = Reihenfolge im Ergebnis.

Eingangswerte: Kontext-Variablen mit PDF-Pfaden, z. B. {{antrag_pdf}}, {{anlage_pdf}} oder {{latest_pdf_path}}.

Ausgangswerte: latest_pdf_path, latest_pdf_url (zeigen auf das zusammengeführte Dokument).

<task type="pdf_merge" id="zusammenfuehren">
  <config>
    <output>{{temp_dir}}/gesamt.pdf</output>
    <file>{{antrag_pdf}}</file>
    <file>{{anlage_pdf}}</file>
    <file>{{unterschrift_pdf}}</file>
  </config>
</task>

pdf_rotate — Interaktive Seitendrehung

Zweck: Zeigt dem Bearbeiter eine Vorschau aller Seiten mit Dreh-Buttons (0°/90°/180°/270° je Seite, plus „Alle gleich drehen"). Der Workflow wartet, bis der Nutzer die Drehung bestätigt; erst dann wird das gedrehte PDF erzeugt. Werden alle Winkel auf 0° belassen, wird das Original unverändert durchgereicht.

Parameter

Name Pflicht? Default Beschreibung
file ja Eingabe-PDF (z. B. {{latest_pdf_path}}).
output_var nein Zusätzlicher Variablenname für den Ergebnispfad (setzt auch <output_var>_url).

Die Winkel sind relativ zur angezeigten Vorschau — die Ausgabe zeigt exakt die Drehung, die der Nutzer in der Vorschau gewählt hat.

Eingangswerte: {{latest_pdf_path}} oder eine andere PDF-Pfad-Variable.

Ausgangswerte:

  • latest_pdf_path, latest_pdf_url (gedrehtes PDF; bei 0°-Passthrough weiterhin das Original)
  • <task_id>_rotations — gewählte Winkel als JSON, z. B. {"1":0,"2":90}
  • <task_id>_geaendert1 wenn gedreht wurde, sonst 0
  • optional <output_var> und <output_var>_url
<task type="pdf_rotate" id="drehung">
  <assign_to>{{bearbeiter_mail}}</assign_to>
  <config>
    <file>{{latest_pdf_path}}</file>
    <output_var>gedrehtes_pdf</output_var>
  </config>
</task>

pdf_sign — PDF stempeln und signieren (feste Position)

Zweck: Bringt einen aus Text erzeugten Sichtvermerk-Stempel an einer festen Koordinate auf und versieht das Dokument mit einer kryptografischen Signatur (systemweit hinterlegtes Zertifikat). Der Stempeltext wird dauerhaft „eingebrannt". Die Datei wird in-place überschrieben.

Parameter

Name Pflicht? Default Beschreibung
file ja Zu signierendes PDF; wird in-place ersetzt.
text ja Stempeltext (Platzhalter erlaubt), z. B. gez. {{ICH.nachname}}.
x nein 40 X-Position des Stempels in PDF-Punkten, gemessen vom linken Rand.
y nein 250 Y-Position in PDF-Punkten, Y=0 oben (top-down).
seite (oder page) nein 1 Seitennummer, auf der der Stempel platziert wird.

Die Stempelgröße ist fest (ca. 60 × 25); der Text wird dunkelblau, zentriert gerendert. Vor dem Signieren wird das PDF bei Bedarf automatisch in ein kompatibles Format konvertiert (verlustfrei).

Eingangswerte: {{latest_pdf_path}} bzw. eine PDF-Pfad-Variable wie {{antrag_pdf}}.

Ausgangswerte: latest_pdf_path, latest_pdf_url (zeigen auf die signierte Datei — gleicher Pfad, neuer Inhalt).

<task type="pdf_sign" id="freigabe_sig">
  <config>
    <file>{{latest_pdf_path}}</file>
    <text>Freigegeben: {{ICH.vorname}} {{ICH.nachname}}, {{DATE}}</text>
    <x>40</x>
    <y>250</y>
    <seite>1</seite>
  </config>
</task>

pdf_sign_at_text — Stempeln/Signieren an gefundener Textstelle

Zweck: Sucht eine Phrase im PDF-Text (z. B. eine Unterschriftszeile) und platziert relativ dazu einen Sichtvermerk-Stempel samt kryptografischer Signatur — ohne feste Koordinaten pflegen zu müssen. Robust gegen Layout-Verschiebungen der Vorlage. Der Stempel wird eingebrannt, die Datei in-place überschrieben. Wird die Phrase nicht gefunden, bricht der Task mit Fehler ab.

Parameter

Name Pflicht? Default Beschreibung
file ja Zu signierendes PDF (in-place).
search ja Exakte Suchphrase (ein oder mehrere Wörter, Whitespace wird normalisiert). Anker ist das erste Wort der Phrase.
text ja Stempeltext; {{TODAY}} steht hier zusätzlich bereit (Datum zum Signierzeitpunkt, nicht Workflow-Start).
seite nein auto auto = alle Seiten durchsuchen, erste Treffer-Seite gewinnt; oder konkrete Seitennummer.
case_sensitive nein false Groß-/Kleinschreibung bei der Suche beachten.
x_offset_pt nein 0 X-Versatz in PDF-Punkten relativ zur linken Kante des Fundworts.
y_offset_pt nein 0 Y-Versatz in PDF-Punkten relativ zur Oberkante des Fundworts. Y=0 oben; negativ = nach oben (über den Text).
breite_pt nein 120 Stempelbreite in PDF-Punkten.
hoehe_pt nein 30 Stempelhöhe in PDF-Punkten.
fontsize nein 12 Schriftgröße des Stempeltexts (vertikal zentriert im Stempelrechteck).

Eingangswerte: {{latest_pdf_path}} (typisch als file); Kontext-Variablen für den Stempeltext (z. B. {{pruefer_nachname}}).

Ausgangswerte:

  • latest_pdf_path, latest_pdf_url
  • Diagnose: <task_id>_matched_page (Fundseite), <task_id>_matched_x, <task_id>_matched_y_top (Fundposition in pt, Y top-down), <task_id>_stamp_x, <task_id>_stamp_y (tatsächliche Stempelposition inkl. Offsets)
<task type="pdf_sign_at_text" id="vorsitz_sig">
  <config>
    <file>{{latest_pdf_path}}</file>
    <search>Anordnungsbefugten</search>
    <seite>auto</seite>
    <x_offset_pt>0</x_offset_pt>
    <y_offset_pt>-25</y_offset_pt>
    <breite_pt>120</breite_pt>
    <hoehe_pt>30</hoehe_pt>
    <text>{{TODAY}}, gez. {{pruefer_nachname}}</text>
  </config>
</task>

pdf_split — PDF aufteilen

Zweck: Teilt ein PDF in Einzelseiten oder benannte Seitenbereiche auf. Die Ergebnis-Pfade landen als Array im Kontext — ideal als Eingabe für loop_foreach.

Parameter

Name Pflicht? Default Beschreibung
file ja Quell-PDF.
output_dir ja Zielverzeichnis für die Teil-PDFs (wird bei Bedarf angelegt). Dateiname: <Quellname>_<Bereichsname>.pdf.
range nein (mehrfach) Benannter Bereich mit Attributen name und pages. Seitenangabe: einzelne Seiten, Bereiche und offene Enden kombinierbar, z. B. 1, 2-5, 6-, 1,3-5,8-. Ohne range-Einträge wird jede Seite einzeln gespeichert (Bereichsnamen page_1, page_2, …).
files_var nein <task_id>_parts Name der Kontext-Variable für das Ergebnis-Array.

Eingangswerte: PDF-Pfad-Variable, z. B. {{antrag_pdf}}; {{temp_dir}} als Zielverzeichnis.

Ausgangswerte:

  • <files_var> — Array aller Teil-Pfade (in Bereichsreihenfolge)
  • <files_var>_named — Zuordnung Bereichsname → Pfad
  • <files_var>_count — Anzahl erzeugter Teile
  • <task_id>_part_<name> — Einzelpfad je Bereich (z. B. {{split_bereich_part_deckblatt}})

Hinweis: latest_pdf_path wird von diesem Task nicht verändert.

<task type="pdf_split" id="split_bereich">
  <config>
    <file>{{antrag_pdf}}</file>
    <output_dir>{{temp_dir}}</output_dir>
    <range name="deckblatt" pages="1" />
    <range name="inhalt"    pages="2-5" />
    <range name="anhang"    pages="6-" />
    <files_var>teile_pdfs</files_var>
  </config>
</task>

pdf_stamp_interactive — Interaktive Stempelplatzierung

Zweck: Zeigt dem Bearbeiter eine PDF-Vorschau, in der er per Doppelklick (Desktop) bzw. langem Drücken (Tablet) die Stempelposition setzt; ein Schieberegler skaliert den Stempel live (30300 %). Der Workflow wartet, bis platziert wurde; dann wird das Stempelbild dauerhaft eingebrannt. Keine kryptografische Signatur — dafür ist pdf_sign zuständig.

Parameter

Name Pflicht? Default Beschreibung
file ja Eingabe-PDF.
vorlage ja¹ Vordefinierter Stempelgenerator: posteingang, postausgang oder bau.
vorlage_params nein Query-Parameter für den Stempelgenerator (nur mit vorlage).
stempel ja¹ Pfad zu einer fertigen Stempelbild-Datei (PNG/JPG).
stempel_text ja¹ Text, aus dem automatisch ein Stempelbild erzeugt wird (dunkelblau, zentriert).
breite_mm / hoehe_mm nein auto Stempelmaße in mm. auto = aus der Bildgröße abgeleitet; ist nur eine Achse auto, bleibt das Seitenverhältnis erhalten. Text-Stempel ohne Bild: Fallback 60 × 25 mm.
output_var nein Zusätzlicher Variablenname für den Ergebnispfad (setzt auch <output_var>_url).

¹ Genau eine Stempelquelle angeben; Priorität bei Mehrfachangabe: vorlagestempelstempel_text.

Die geklickte Position ist der Mittelpunkt des Stempels; der Task rechnet auf die Seitengröße um und begrenzt den Stempel auf den Seitenrand. Das Ergebnis wird als neue Datei im Arbeitsverzeichnis abgelegt.

Eingangswerte: {{latest_pdf_path}} bzw. PDF-Pfad-Variable; {{temp_dir}}.

Ausgangswerte:

  • latest_pdf_path, latest_pdf_url (gestempeltes PDF)
  • <task_id>_page, <task_id>_x_mm, <task_id>_y_mm (Position obere linke Ecke in mm), <task_id>_breite, <task_id>_hoehe (finale Maße in mm), <task_id>_scale_pct
  • optional <output_var> und <output_var>_url
<task type="pdf_stamp_interactive" id="eingangsstempel">
  <assign_to>{{registratur_mail}}</assign_to>
  <config>
    <file>{{latest_pdf_path}}</file>
    <vorlage>posteingang</vorlage>
    <breite_mm>50</breite_mm>
    <hoehe_mm>auto</hoehe_mm>
  </config>
</task>

pdf_text — Text an feste Koordinate schreiben

Zweck: Schreibt einen (dynamischen) Text an eine feste Koordinate in ein bestehendes PDF — z. B. um Kopffelder oder Formularpositionen automatisch zu befüllen.

Parameter

Name Pflicht? Default Beschreibung
datei ja Quell-PDF.
dateiout nein = datei Ausgabepfad; ohne Angabe wird die Quelldatei in-place überschrieben.
text ja Zu schreibender Text (Platzhalter erlaubt).
position ja x,y in mm, gemessen von der oberen linken Ecke (Y=0 oben).
seite nein 1 Zielseite.

Eingangswerte: PDF-Pfad-Variable; Kontext-Variablen für den Text.

Ausgangswerte: latest_pdf_path, latest_pdf_url (zeigen auf die Ausgabedatei).

<task type="pdf_text" id="fill_header">
  <config>
    <datei>{{antrag_pdf}}</datei>
    <dateiout>{{temp_dir}}/v0.pdf</dateiout>
    <text>{{ICH.vorname}} {{ICH.nachname}}</text>
    <position>100,20</position>
    <seite>1</seite>
  </config>
</task>

pdf_text_at_text — Text an gefundener Textstelle schreiben

Zweck: Sucht eine Phrase im PDF und schreibt relativ dazu einen Text — typischer Anwendungsfall: ein „x" in eine Checkbox neben einem gefundenen Formular-Label setzen. Schwester-Task von pdf_sign_at_text, aber ohne Signatur (reine Textausgabe); kann daher beliebig mitten in der Bearbeitungskette stehen. Wird die Phrase nicht gefunden, bricht der Task mit Fehler ab.

Parameter

Name Pflicht? Default Beschreibung
file ja Zu bearbeitendes PDF (in-place).
search ja Suchphrase (1+ Wörter); Anker ist das erste Wort des Treffers.
text ja Zu schreibender Text, z. B. x.
seite nein auto auto = alle Seiten, erste Treffer-Seite; oder Seitennummer.
case_sensitive nein false Groß-/Kleinschreibung beachten.
x_offset_mm nein 0 X-Versatz in mm relativ zur linken Kante des Fundworts; negativ = links davon.
y_offset_mm nein 0 Y-Versatz in mm; Y=0 oben, positiv = nach unten.

Eingangswerte: {{latest_pdf_path}} bzw. PDF-Pfad-Variable.

Ausgangswerte:

  • latest_pdf_path, latest_pdf_url
  • Diagnose: <task_id>_matched_page, <task_id>_matched_x_pt, <task_id>_matched_y_pt (Fundposition in pt, Y top-down), <task_id>_write_x_mm, <task_id>_write_y_mm (tatsächliche Schreibposition in mm)
<task type="pdf_text_at_text" id="kreuz_a">
  <config>
    <file>{{latest_pdf_path}}</file>
    <search>Reisekostenstelle</search>
    <x_offset_mm>-5</x_offset_mm>
    <y_offset_mm>0</y_offset_mm>
    <text>x</text>
  </config>
</task>

vorlage_docx — DOCX aus Vorlagen-Skript erzeugen

Zweck: 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. Läuft einphasig durch — kein Warten auf Nutzereingaben. Parallele Workflows sind durch einen eindeutigen Vorgangs-Schlüssel und einen Zeitstempel-Check gegen veraltete Ergebnisse abgesichert.

Parameter

Name Pflicht? Default Beschreibung
vorlage ja Relativer Pfad der Vorlage innerhalb des Vorlagen-Katalogs (muss auf .php enden; .. ist nicht erlaubt).
docx_name ja Dokumentname, unter dem die Vorlage ihr DOCX ablegt.
prefill nein Block aus <var>-Einträgen: die Formularfelder der Vorlage (siehe unten).
output_var nein Zusätzlicher Variablenname für den DOCX-Pfad (setzt auch <output_var>_url).
timeout nein 30 Timeout des Aufrufs in Sekunden (min. 5).

<var>-Attribute im <prefill>-Block

Attribut Beschreibung
name Feldname im Vorlagen-Formular. Suffix [] = Array-Sammelposten (Element mehrfach wiederholen oder per from_json expandieren).
from_json JSON-String aus einer Kontext-Variable (Platzhalter erlaubt); Einträge werden expandiert.
field Bei from_json: Schlüssel innerhalb der JSON-Einträge, dessen Wert übernommen wird.
index Bei from_json: nur den Eintrag an diesem Index als Einzelwert übernehmen.
join Bei from_json ohne index: alle Werte zu einem String mit diesem Trenner verbinden (z. B. ,).
lookup_mail 1 = Wert als E-Mail-Adresse interpretieren und über das Personenverzeichnis in die Personen-ID auflösen (bei Nicht-Treffer bleibt der Originalwert erhalten).
unique 1 = Duplikate entfernen (greift nach lookup_mail, dedupliziert also auf ID-Ebene).

Eingangswerte: Kontext-Variablen für die Prefill-Werte, z. B. {{vkz}}, {{ICH.id}}, JSON-Variablen aus Auswahl-Tasks wie {{mkz_auswahl_json}}.

Ausgangswerte:

  • latest_docx_path, latest_docx_url (Kopie des DOCX im Arbeitsverzeichnis des Workflows)
  • <task_id>_uuid — Vorgangs-Schlüssel des Vorlagen-Aufrufs
  • <task_id>_quelle — Herkunft des Dokuments (Diagnose)
  • optional <output_var> und <output_var>_url
<task type="vorlage_docx" id="erstelle_bestaetigung">
  <config>
    <vorlage>Beispielbereich/Eingangsbestaetigung/Eingangsbestaetigung.php</vorlage>
    <docx_name>Eingangsbestätigung</docx_name>
    <prefill>
      <var name="vkz">{{vkz}}</var>
      <var name="bearbeiter">{{ICH.id}}</var>
      <var name="massnahmen[]" from_json="{{mkz_auswahl_json}}" field="mkz" />
      <var name="planer" from_json="{{planer_zuordnung_json}}"
           field="planer_mail" lookup_mail="1" join="," unique="1" />
    </prefill>
    <output_var>bestaetigung_docx</output_var>
  </config>
</task>
S
Description
No description provided
Readme 133 KiB
Languages
Markdown 100%