Application Status & Workflow
Custom statuses (template-scoped)
ApplicationStatus is a custom status defined per ApplicationTemplate — not a global enum. Each template defines its own set of statuses and transitions.
Fields:
| Field | Type | Notes |
|---|---|---|
| applicationTemplateId | UUID | The template this status belongs to |
| statusId | String | Human-readable slug (e.g. credit-check-done) |
| internalName | String | Shown to staff only |
| externalName | String | Shown to clients |
| color | String | Hex or Tailwind token for UI badge |
| isInitial | Boolean | Exactly one status should be the starting point |
| isFinal | Boolean | Marks end states (approved, rejected) |
| manuallySettable | Boolean | Whether staff can manually assign this status |
ApplicationStatus is not the old ApplicationStatusEnum (DRAFT/SUBMITTED). That enum was deleted. Do not recreate it. See Backend Rules: Application Status for the full rename history.
Setting status on an application
Application.currentApplicationStatus is a read-only JPA join. Never set it through ApplicationInput.
// Correct — set the FK directly
application.setCurrentApplicationStatusId(newStatusId);
// Wrong — ApplicationMapper ignores this
// @Mapping(target = "currentApplicationStatus", ignore = true)
ApplicationInput does not contain a status field. Status is managed exclusively by the workflow engine.
Status transitions
ApplicationStatusTransition defines allowed transitions between statuses on a template (from-status → to-status):
| Field | Type | Notes |
|---|---|---|
| fromStatusId | UUID | Source status |
| toStatusId | UUID | Target status |
| transitionType | Enum | USER, SYSTEM, or BOTH |
Transition types:
| Type | Triggered by |
|---|---|
| USER | Manual action by staff |
| SYSTEM | Workflow engine (system handler) |
| BOTH | Either manual or automated |
Workflow engine & system handlers
The workflow engine automatically advances applications through steps based on conditions.
Known system handler:
client-approval-check— triggers whenApplication.clientApprovalConfirmedAtis set (client approves via portal)
System handlers are evaluated by WorkflowService / WorkflowController, which inject ApplicationStatusRepository.
WorkflowStep
Each step in the workflow definition (for a template):
| Field | Notes |
|---|---|
| applicationStatus | The status this step represents (field name: applicationStatus, not templateStatus) |
| systemHandler | Optional — name of the handler to auto-advance |
| positionX, positionY | ReactFlow canvas coordinates |
Frontend field name: step.applicationStatus (was step.templateStatus in old code — use new name only).
Status history (audit log)
ApplicationStatusHistory records every status change on an application:
previousInternalStatus/previousExternalStatusinternalStatus/externalStatus(new values)workflowStepIdtriggeredBy(userId or SYSTEM)transitionedAt(timestamp)
This is the audit trail — displayed on the application detail Workflow tab under the history section.
GraphQL queries & mutations
# Queries
applicationStatuses(templateId: ID!): [ApplicationStatus!]!
applicationStatusTransitions(templateId: ID!): [ApplicationStatusTransition!]!
applicationStatusHistory(applicationId: ID!): [ApplicationStatusHistory!]!
# Mutations
createApplicationStatus(templateId: ID!, input: ApplicationStatusInput!): ApplicationStatus!
updateApplicationStatus(id: ID!, input: ApplicationStatusInput!): ApplicationStatus!
deleteApplicationStatus(id: ID!): Boolean!
createApplicationStatusTransition(...): ApplicationStatusTransition!
updateApplicationStatusTransition(...): ApplicationStatusTransition!
deleteApplicationStatusTransition(id: ID!): Boolean!
Frontend
Application detail page → Workflow tab:
- Read-only ReactFlow visualization of the template's workflow
- History tab: list of
ApplicationStatusHistoryentries
Current status display:
// Use currentApplicationStatus — the old status.internal/external fields no longer exist
application.currentApplicationStatus?.internalName // for staff
application.currentApplicationStatus?.externalName // for clients
isDraft detection (no status assigned yet):
const isDraft = !application.currentApplicationStatus
See also
- Backend Rules: Application Status — full rename reference, pitfalls
- Agent: Workflow Statuses Feature