Workflow Structure
The DTOs that make up a workflow definition — transitions, end states, actions, and authorisation.
A workflow is an array of WorkflowTransitionDto objects forming a state machine.
WorkflowTransitionDto
| Field | Type | Required | Description |
|---|---|---|---|
startState | string | Yes | Current state (UPPER_SNAKE_CASE) |
event | string | Yes | Trigger event (UPPER_SNAKE_CASE verb) |
endStateOne | WorkflowEndStateDto | Yes | Primary destination state |
endStateTwo | WorkflowEndStateDto | No | Alternate destination (conditional) |
endStateCondition | WorkflowEndStateConditionDto | No | Condition for choosing endStateTwo |
authorisation | WorkflowAuthorisationDto | Yes | Who can trigger this event |
position | {x, y} | No | Visual editor position |
eventNames | Map<string, string> | No | Localized event names (en, fr, rw) |
eventConfigs | WorkflowEventConfigsDto | No | Event configuration |
WorkflowEndStateDto
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | Yes | UPPER_SNAKE_CASE state identifier |
stateName | string | Yes | Default display name |
stateNames | Map<string, string> | No | Localized names {en, fr, rw} |
breakingAction | WorkflowActionDto | No | Single breaking action (legacy, prefer breakingActions) |
breakingActions | WorkflowActionDto[] | No | Breaking actions (rejection/cancellation notifications) |
nonBreakingActions | WorkflowActionDto[] | No | Non-breaking actions (certificates, integrations) |
nextEvent | string | No | Auto-trigger next event after reaching this state |
position | {x, y} | No | Visual editor position |
WorkflowActionDto
| Field | Type | Required | Description |
|---|---|---|---|
actionType | string | Yes | Must be a valid EWorkflowActionType value |
args | WorkflowArgumentsDto | No | Action-specific arguments |
EWorkflowActionType Values
| Action Type | Purpose |
|---|---|
NOTIFICATION | Send notification (email/SMS) to applicant |
INTERNAL_NOTIFICATION | Send notification to internal staff |
FORM_FIELD_NOTIFICATION | Send notification to address from a form field |
OFFICE_ASSIGNMENT_NOTIFICATION | Notify assigned office |
INTEGRATION | Call external integration endpoint |
INTEGRATION_UPDATE | Update via integration |
GENERATE_CERTIFICATE | Generate output document/certificate |
BILL_ID_GENERATION | Generate payment bill ID |
MASTER_BILL_ID_GENERATION | Generate master bill ID |
SERVICE_CENTER_MASTER_BILL_ID_GENERATION | Service center master bill |
EXECUTE_PRICING | Execute pricing calculation |
OFFICE_ASSIGNMENT | Assign application to office |
FETCH_ID_PHOTO | Fetch ID photo from registry |
SLOT_BOOKING | Book appointment slot |
REDEEM_SLOT | Redeem a booked slot |
SLOT_REBOOKING | Rebook an appointment slot |
APPLY_SERVICE | Apply for a related service |
APPLY_NORMAL_SERVICE | Apply for a normal service |
TRIGGER_PARENT | Trigger parent service event |
TRIGGER_PARENT_SERVICE_EVENT | Trigger specific parent event |
TRIGGER_CHILD_SERVICE_EVENT | Trigger child service event |
CREATE_CHILD_APPLICATION | Create a child application |
APPLY_OR_RESUBMIT_CHILD_APPLICATION | Apply or resubmit child |
UPDATE_DATASET | Update a dataset |
SYSTEM_REQUEST_FOR_ACTION | System-generated RFA |
URL_CALL | Call an external URL |
VALIDATE_UNIQUE_APPLICATION_PROPERTY | Validate unique property |
WorkflowArgumentsDto (Key Fields)
For NOTIFICATION / INTERNAL_NOTIFICATION
notificationType:DYNAMIC|STATICdynamicTemplate:{ "en": { "templateCode": "CODE" } }staticTemplate:{ "en": { "templateCode": "CODE" } }attachment: array of attachment configsinternalEmailNotificationRecipients: string[]internalSmsNotificationRecipients: string[]formFieldsSmsNotificationRecipients: string[]formFieldsEmailNotificationRecipients: string[]
For INTEGRATION
endpointCode: stringasync: boolean
For GENERATE_CERTIFICATE
certificateName: stringcertificateNames:{ "en": "Name" }certificateTemplateCode:{ "en": "TEMPLATE_CODE" }certificateExpirationDays: numbercertificateWithList: booleanmergeDocuments: string[]
For BILL_ID_GENERATION / EXECUTE_PRICING
invoiceDescription: stringcurrency: string
For OFFICE_ASSIGNMENT
officeAssignmentType: stringofficeLevel: stringformFieldKey: stringformFieldKeys: string[]fixedOfficeCode: stringofficeAssignmentExpression: stringofficeId: string
For SLOT_BOOKING / REDEEM_SLOT
groupIdField: stringtimeRangeIdField: stringseatsField: string
For SYSTEM_REQUEST_FOR_ACTION
fieldNames: string[]feedbackSection: stringfeedbackReason: stringcomment: string
For APPLY_SERVICE / CREATE_CHILD_APPLICATION
serviceCode: stringapplyPayload:{ "sourceField": "targetField" }event: stringparametersToRetain:{ "key": "value" }
For UPDATE_DATASET
datasetCode: stringdatasetUpdateType: stringdatasetFieldsMapping:{ "formField": "datasetField" }
For VALIDATE_UNIQUE_APPLICATION_PROPERTY
propertyKey: stringpropertyValuePath: stringapplicationStates: string[]errorMessageCode: stringexcludeCurrentApplication: boolean (default true)
For URL_CALL
source: stringsourceDocumentType: stringsourceDocumentCode: string
WorkflowAuthorisationDto
| Field | Type | Description |
|---|---|---|
authorisedRoles | string[] | Role codes (e.g., REVIEWER, APPROVER) |
authorisedUsers | UUID[] | Specific user IDs |
systemAuthorised | boolean | System-triggered (no human actor) |
At least one of authorisedRoles, authorisedUsers, or systemAuthorised must be set.
WorkflowEndStateConditionDto
| Field | Type | Description |
|---|---|---|
endStateTwo | WorkflowSingleEndStateConditionDto[] | Conditions for routing to endStateTwo |
validationStrategy | string | Strategy identifier |
validationStrategyIdentifierField | string | Field for strategy lookup |
childReadyStates | string[] | Child states to wait for |
duplicateCheckApplicationStates | string[] | States to check for duplicates |
WorkflowSingleEndStateConditionDto
| Field | Type | Required | Description |
|---|---|---|---|
parameter | string | Yes | Form field key to evaluate |
value | string | Yes | Expected value |
comparator | EFieldComparator | Yes | GT, LT, EQ, GTE, LTE, NEQ, CONTAINS |
WorkflowEventConfigsDto
| Field | Type | Description |
|---|---|---|
eventDueDate | string | Due date expression |
State Machine Rules
- Exactly one initial state (typically
SUBMITTED) - Every state must be reachable from the initial state
- Terminal states have no outgoing transitions
- State names:
UPPER_SNAKE_CASEnouns (SUBMITTED, APPROVED, REJECTED) - Event names:
UPPER_SNAKE_CASEverbs (APPROVE, REJECT, FORWARD) - Breaking actions = failure path (rejection, cancellation)
- Non-breaking actions = success path (certificates, integrations, notifications)
Complete Example -- Simple Approval
[
{
"startState": "SUBMITTED",
"event": "APPROVE",
"endStateOne": {
"stateCode": "APPROVED",
"stateName": "Approved",
"stateNames": { "en": "Approved", "rw": "Byemejwe" },
"breakingAction": null,
"breakingActions": [],
"nonBreakingActions": [
{
"actionType": "NOTIFICATION",
"args": {
"notificationType": "DYNAMIC",
"dynamicTemplate": {
"en": { "templateCode": "TODO: approval_template_en" }
}
}
},
{
"actionType": "GENERATE_CERTIFICATE",
"args": {
"certificateName": "Certificate",
"certificateTemplateCode": { "en": "TODO: cert_template_code" }
}
}
],
"nextEvent": null,
"position": { "x": 400, "y": 100 }
},
"endStateTwo": null,
"endStateCondition": null,
"authorisation": {
"authorisedRoles": ["REVIEWER"],
"authorisedUsers": null,
"systemAuthorised": null
},
"position": { "x": 200, "y": 100 },
"eventNames": { "en": "Approve", "rw": "Emeza" },
"eventConfigs": null
},
{
"startState": "SUBMITTED",
"event": "REJECT",
"endStateOne": {
"stateCode": "REJECTED",
"stateName": "Rejected",
"stateNames": { "en": "Rejected", "rw": "Byanzwe" },
"breakingAction": null,
"breakingActions": [
{
"actionType": "NOTIFICATION",
"args": {
"notificationType": "DYNAMIC",
"dynamicTemplate": {
"en": { "templateCode": "TODO: rejection_template_en" }
}
}
}
],
"nonBreakingActions": [],
"nextEvent": null,
"position": { "x": 400, "y": 300 }
},
"endStateTwo": null,
"endStateCondition": null,
"authorisation": {
"authorisedRoles": ["REVIEWER"],
"authorisedUsers": null,
"systemAuthorised": null
},
"position": { "x": 200, "y": 300 },
"eventNames": { "en": "Reject", "rw": "Hagarika" },
"eventConfigs": null
}
]