Skip to content

Workflow Template Configuration

Back to Regnify Product Docs

Previous


Overview

Workflow templates define the approval chains for declarations within an organisation. Each template specifies an ordered list of approvers who must sequentially approve a declaration before it reaches Approved status. Templates are stored in the workflow_templates table and are materialised into workflow_instances when a declaration is submitted.

Templates are managed via the Workflow Management page (/admin/workflows) and through GraphQL mutations. They are per-organisation and per-form-type: the unique key is (repId, formType) on the workflow_templates table.

Template Data Model

workflow_templates

Column Type Description
id Int (auto-increment) Primary key
formType String (default: "DECLARATION_FORM_3A") Which declaration type this template applies to
repId String Scoping key -- either an org-level sentinel (e.g. "ORG-dbs") or a rep-specific identifier
organisationId Int? FK to organisations table
name String? Human-readable name (e.g. "DBS Bank Ltd - Form 3A Approval Chain")
approverUserIds Json Ordered JSON array of user ID strings representing the approval chain

The approverUserIds column defines the approval order. At materialisation time (WorkflowService.createInstance()), each array entry becomes a WorkflowStep row with stepIndex = array_position - 1.

workflow_instances

Created when a declaration is submitted. One instance per declaration (linked via declarationId).

Column Type Description
id Int Primary key
declarationId Int FK to declarations
repId String Representative identifier
templateId Int? FK to the template that defined this instance
status String (default: "pending") pending, approved, rejected, sent_back
currentStepIndex Int (default: 0) Which step in the chain is currently active

workflow_steps

Individual approval steps within an instance.

Column Type Description
id Int Primary key
workflowInstanceId Int FK to workflow_instances
stepIndex Int 0-based position in the chain
approverUserId String FK to users -- the assigned approver for this step
approverName String? Cached approver name at instance creation
approverEmail String? Cached approver email at instance creation
status String (default: "pending") pending, approved, rejected, sent_back
comment String? Approver's comment when acting on the step
actedAt DateTime? When the approver acted

Approver Rank Hierarchy

Approvers are assigned to users who have isApprover = true and an approvalRank value. The hierarchy:

Rank Role Description
1 Representative (FI_USER) Submits the declaration; NOT an approver but the first actor in the chain
2 Supervisor First-line approver; must be same domain, higher approval_rank, and isApprover = true
3 Manager Second-line approver
4 CEO Highest approval rank

This rank hierarchy is stored in the users table (approvalRank column) and applies to the reporting-officer relationship, not directly to workflow template configuration. Template approvers are pre-selected by ORG_ADMIN and do not need to follow strict rank ordering through the chain -- the rank is a user attribute, not a template constraint.

Sequential Approval Constraint

Workflow steps execute in strict order. Step N+1 cannot be acted on until step N is approved. This is enforced by WorkflowService.approveStep():

  1. Validates the instance is in pending status (not already approved or rejected).
  2. Validates instance.currentStepIndex === stepIndex -- only the current step can be approved.
  3. Validates the calling user matches the step's approverUserId (unless approverUserId is not provided, in which case this check is skipped).
  4. Marks the step as approved, sets actedAt.
  5. If this is NOT the last step: advances currentStepIndex to stepIndex + 1, instance stays pending.
  6. If this IS the last step: sets instance status to approved AND auto-transitions the declaration to Approved (status = "Approved", statusLabel = "APPROVED", approvedAt = now()).

Rejection path: rejectWorkflowStep marks the step as rejected, sets instance status to rejected, and auto-transitions the declaration to Rejected.

Send-back path: sendBackWorkflowStep marks the step as sent_back, sets instance status to sent_back, and auto-transitions the declaration to Revision Requested. The rep then revises and calls resubmitDeclaration, which resets all workflow steps to pending and restarts from stepIndex = 0 (prior approval history is preserved in actedAt / comment but all steps restart as pending).

Template CRUD Operations

createWorkflowTemplate

mutation CreateWorkflowTemplate($data: CreateWorkflowTemplateInput!) {
  createWorkflowTemplate(data: $data) { id name formType approverUserIds }
}

Roles: SYS_ADMIN, ORG_ADMIN

updateWorkflowTemplate

mutation UpdateWorkflowTemplate($id: Int!, $data: UpdateWorkflowTemplateInput!) {
  updateWorkflowTemplate(id: $id, data: $data) { id name approverUserIds }
}

Roles: SYS_ADMIN, ORG_ADMIN

deleteWorkflowTemplate

mutation DeleteWorkflowTemplate($id: Int!) {
  deleteWorkflowTemplate(id: $id) { id }
}

Roles: SYS_ADMIN, ORG_ADMIN

Query templates

query WorkflowTemplates($where: JSONObject) {
  workflowTemplates(where: $where) { id name formType organisationId approverUserIds }
}

No role guard on read queries -- templates are visible to all authenticated users for the org scope.

Scope Rule: New Declarations Only

Template changes apply ONLY to new declarations created after the change. In-flight declarations (those already submitted) retain their original WorkflowInstance with the approval chain that was materialised at submission time. The materialisation happens once, in DeclarationService.submitDeclaration(), which:

  1. Looks up the org's template for the declaration's formType.
  2. Creates a WorkflowInstance with steps populated from the template's approverUserIds array.
  3. The instance is permanently bound to that declaration and is NOT updated when the template changes.

This is a one-way materialisation: template -> instance at submit time, no back-propagation.

Per-Declaration-Type Templates

Different templates can exist for different formType values:

Form Type Typical Approver Chain Notes
DECLARATION_FORM_3A Supervisor -> HR -> Compliance (3-step) Standard Form 3A approval. Seed creates one per FI with sup + hr + comp as approvers.
DECLARATION_ATTESTATION_QUARTERLY Compliance only (1-step, implicit) Attestation declarations DO NOT auto-create workflow instances on submit. The single-step approval is via approveAttestation, not through the workflow engine. Attestation templates exist as FKs for the launch mutation but their approverUserIds are not used for workflow-step creation.
DECLARATION_INCIDENT_DISCLOSURE Compliance only (1-step) Auto-created by ensureIncidentDisclosureTemplate() on first filing. Single-step "Review by Compliance" with the org's first active COMP user as approver. Uses sentinel repId = "ORG-INCIDENT-{orgId}" for global uniqueness.

Admin Page

The Workflow Management page at /admin/workflows provides: - List of all templates for the org, grouped by form type - Create new template (select form type, add approver steps) - Edit existing template (reorder/change approvers) - Delete unused templates

Escalation and Reassignment

If an approver is unavailable (on leave, left the organisation): - There is currently no API mutation for reassigning an approver on an in-flight workflow instance. - The available workaround is to reject the current step via rejectWorkflowStep and re-submit with a different template configured with an available approver. - Direct database-level reassignment is technically possible but not recommended for production use.

Seed Data

Five Form 3A templates (one per FI), each with a 3-step chain: - DBS: dbs_sup -> dbs_hr -> dbs_comp - UOB: uob_sup -> uob_hr -> uob_comp - Great Eastern: ge_sup -> ge_hr -> ge_comp - Manulife: manulife_sup -> manulife_hr -> manulife_comp - AIA: aia_sup -> aia_hr -> aia_comp

Plus attestation templates per org (created via seed). Template id=7 is a RESERVED FIXTURE for chatbot test AT-001, permanently anchored as the UOB Q2 2026 Attestation Template with repId = "ATTESTATION_TEMPLATE_UOB".

Chatbot Guidance

When users ask about: - Adding/removing approvers: Direct to Workflow Management page (/admin/workflows), explain that changes apply only to new declarations. - Changing approval order: Explain sequential constraint and that reordering requires updateWorkflowTemplate. - "Why is my approval stuck?": Check workflowInstances for the declaration -- the currentStepIndex tells which step is pending. The assigned approver at that step must act. - "Can I skip an approver?": No. Sequential approval is enforced. The only workaround is to update the template to remove that step, which only affects future declarations.


Previous

Back to Regnify Product Docs