API · v1 · stabil
CODEMETA OSDeveloper Center
Konsole öffnen
Konzept

Status-Maschine & Transitions

Lesedauer · 9 Min.Aktualisiert · 2026-05-01

Praktisch jede Codemeta-Entity mit einem status-Feld hat eine Status-Maschine — eine Liste erlaubter Statuswerte plus Regeln, welcher Wechsel von welchem Status aus zulässig ist. Diese Maschine ist serverseitig durchgesetzt: ein direktes PATCH /api/v1/<resource>/:id mit einem unzulässigen Statuswechsel wird abgelehnt.

Die Mechanik ist über alle Module hinweg identisch — Tickets, Verträge, Assets, DMS-Dokumente, Opportunities, Aufgaben, Callback-Requests und tenant-eigene Custom-Workflows verwenden denselben Engine.

Bestandteile einer Status-Maschine

Begriff Bedeutung
Status Diskreter Zustand (z. B. OPEN, IN_PROGRESS, RESOLVED)
Transition Erlaubter Wechsel von einem Status in einen anderen
Guard Bedingung, die vor der Transition erfüllt sein muss (z. B. „Assignee da“)
Hook Aktion, die beim Eintritt/Austritt ausgeführt wird (z. B. „Notification“)
Dialog UI-Interaktion, die der Server vor der Transition anfordert
Approval Externe Freigabe, die die Transition vorübergehend pausiert

System- vs. Custom-Workflows

Codemeta OS bringt für Kern-Entities System-Workflows mit (die genauen Regeln stehen in packages/shared/src/types/transitions.ts). Beispiel ticket:

OPEN → IN_PROGRESS   (Guard: assigneeId muss gesetzt sein)
IN_PROGRESS → WAITING
IN_PROGRESS → RESOLVED
WAITING → IN_PROGRESS
RESOLVED → CLOSED
* → OPEN              (Reopen aus jedem Status)

Tenants können diese Defaults überschreiben und eigene Statuswerte + Transitions definieren — pro Schema und pro Mandant. Die Engine zieht den mandanten-spezifischen Workflow vor, wenn vorhanden, sonst die System-Defaults.

Auslöse-Wege

Status lassen sich auf zwei Wegen ändern:

  1. Über die Transitions-API — der saubere Weg. Der Server wertet Guards aus, fragt Dialoge an, legt ggf. einen Approval-Request an, und führt Hooks aus.
  2. Über regulären PATCH auf das status-Feld. Auch hier prüft die Engine, ob der Wechsel im Workflow erlaubt ist — Guards/Hooks laufen ebenfalls. Nutzen Sie das nur für triviale Fälle ohne Dialog/Approval.

Ein Versuch, ein „nicht verbundenes“ Statuspaar zu setzen, ergibt 400 Bad Request bzw. 422 Unprocessable Entity mit einem RFC-7807-Problem-Body.

Guards — was den Wechsel blockieren kann

Guards sind benannte Bedingungen am Edge zwischen zwei Status. Eine Auswahl der eingebauten Guard-Typen:

Guard Bedeutung
requiredFields Bestimmte Felder dürfen nicht leer sein
fieldValue Feld muss eq/ne/gt/… einen Wert haben
requireRole Ausführer muss eine bestimmte Rolle haben
timeInStatus Mindestaufenthalt im aktuellen Status (Minuten)
requireComment Mindestens ein Kommentar/INTERNAL_NOTE muss am Ticket hängen
requireTimeEntry Mindestens ein Time-Entry mit Mindestdauer
noOpenSubtasks Kein offener Sub-Task
checklist_complete Verknüpfte Checkliste muss done sein
requireApproval Externer Approver muss freigeben
requireAudit Audit-Catalog muss erfolgreich abgeschlossen sein (ADR 0045a)
confirmDialog Server fragt vor Ausführung einen Confirm-Dialog an
inputDialog Server fragt einen Texteingabe-Dialog an
formDialog Server fragt ein dynamisches Form an
signatureDialog Server fragt eine Acknowledgement-/Signatur an
customExpression Frei definierbarer Ausdruck

Schlägt ein Guard fehl, antwortet die API mit 422 und einem failedGuards[]-Array — die UI rendert daraus eine verständliche Fehlermeldung.

Hooks — was beim Wechsel passiert

Hooks sind Aktionen, die onEnter (Eintritt in den neuen Status) oder onExit (Verlassen des alten Status) feuern:

Hook Wirkung
setTimestamp Timestamp-Feld setzen (z. B. resolvedAt)
clearTimestamp Timestamp-Feld leeren
setField / clearField Beliebiges Feld setzen/leeren
copyField Source-Feld → Target-Feld
incrementField Numerisches Feld inkrementieren
addComment System-Kommentar anhängen
sendNotification In-App-Notification an User/Rolle/Field-Empfänger
sendEmail Transaktionsmail aus Template oder freier Body
webhook Outbound-HTTP-Call (eigene Empfänger)
assignToUser / assignToRole Verantwortlichkeit setzen
escalatePriority Priorität erhöhen
tagEntity Tags add/remove/set
createChecklist Aus Template eine Checkliste anlegen und verknüpfen
createFollowUpTask Folge-Aufgabe erzeugen
triggerWorkflow Anderen Workflow anstoßen
createAudit Audit-Assessment-Entwurf erzeugen (ADR 0045a)

Async-Hooks (sendEmail, sendNotification, webhook) laufen vor dem Statuswrite — wenn sie scheitern, scheitert die Transition.

Dialog-Gates — 428 Precondition Required

Manche Guards (confirmDialog, inputDialog, formDialog, signatureDialog) sind interaktiv. Ein erster Aufruf der Transition ohne Dialog-Antwort liefert 428 zurück, mit dialogConfig im Body. Der Client rendert das Dialog, sammelt die Antwort und ruft die Transition erneut auf — diesmal mit dialogResponse im Body.

HTTP/1.1 428 Precondition Required
{
  "type": "dialog_required",
  "title": "Dialog Required",
  "dialogConfig": {
    "type": "input",
    "title": "Closing reason",
    "inputLabel": "Reason",
    "inputRequired": true,
    "targetField": "closeReason"
  }
}

Dialog-targetField wird automatisch in das Entity geschrieben — der Client muss es nicht in fields doppelt mitschicken.

Approval-Gates — 202 Accepted

Der requireApproval-Guard öffnet einen Approval-Workflow: Plattform legt einen approval_requests-Datensatz an, schickt ggf. eine E-Mail mit Approve/Reject-Link, und gibt 202 zurück. Der Status wechselt nicht sofort — er wechselt erst, wenn der Approver freigibt (oder bei Ablehnung in den rejectionTargetStatus).

HTTP/1.1 202 Accepted
{
  "type": "approval_required",
  "approvalRequestId": "0192f2c0-…",
  "approverType": "role",
  "approverValue": "manager"
}

Audit-Trail von Statuswechseln

Jede Transition hängt einen Eintrag an die Audit-Historie mit fromto, ausführendem User, Zeitstempel und Begründung (sofern aus einem Dialog erfasst). Der transition_history-Eintrag bleibt auch dann bestehen, wenn der Status später zurückgenommen wird.

SSE — Statuswechsel live mitlesen

Wer eine Detail-Ansicht offen hält, bekommt Statuswechsel über Server-Sent-Events ausgeliefert (/api/v1/<resource>/<id>/stream). Das Event hat type: 'entity-updated' und payload.action: 'transitioned' mit from und to.

Verwandte Konzepte

Suche