Skip to main content

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:

FieldTypeNotes
applicationTemplateIdUUIDThe template this status belongs to
statusIdStringHuman-readable slug (e.g. credit-check-done)
internalNameStringShown to staff only
externalNameStringShown to clients
colorStringHex or Tailwind token for UI badge
isInitialBooleanExactly one status should be the starting point
isFinalBooleanMarks end states (approved, rejected)
manuallySettableBooleanWhether staff can manually assign this status
Naming

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):

FieldTypeNotes
fromStatusIdUUIDSource status
toStatusIdUUIDTarget status
transitionTypeEnumUSER, SYSTEM, or BOTH

Transition types:

TypeTriggered by
USERManual action by staff
SYSTEMWorkflow engine (system handler)
BOTHEither 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 when Application.clientApprovalConfirmedAt is 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):

FieldNotes
applicationStatusThe status this step represents (field name: applicationStatus, not templateStatus)
systemHandlerOptional — name of the handler to auto-advance
positionX, positionYReactFlow 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 / previousExternalStatus
  • internalStatus / externalStatus (new values)
  • workflowStepId
  • triggeredBy (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 ApplicationStatusHistory entries

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