Status-Maschine & Transitions
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:
- Ü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.
- Über regulären
PATCHauf dasstatus-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 from → to, 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
- Berechtigungen & Scopes — wer Transitions ausführen darf
- Audit-Historie — Status-Wechsel im Patch-Trail
- Workflows & Automationen — der nächst-höhere Layer
- Transitions-API — Reference