Process Flow Integration

1. Overview

The Registration Portal integrates with the Process entity system to provide flexible, multi-step workflows for membership registration. The process engine enables dynamic question sequences, conditional branching, and state management for complex registration scenarios.

Key Integration Points:

  • Membership Registration - Process-driven question workflow

  • Event Registration - Currently direct registration (no process used)

  • Future Extensions - Any multi-step workflow can leverage process system

2. Process Architecture

2.1. Conceptual Model

process-architecture

2.2. Entity Integration

entity-integration

3. Form DTO

3.1. FormDTO Structure

The FormDTO is the primary data transfer object for process workflows:

export interface FormDTO {
  processId: string;
  step: number;
  processState: ProcessInstanceDTO.StatusEnum;
  question?: string;
  questionType?: string;
  required?: boolean;
  options?: string[];
  people?: Set<FormPersonDTO>;
  paymentUrl?: string;
}

export interface FormPersonDTO {
  id?: number;
  firstName?: string;
  lastName?: string;
  dateOfBirth?: string;
  answer?: string;
  hide?: boolean;
  selected?: boolean;
  canSelect?: boolean;
  status?: string;
  paymentUrl?: string;
}

FormDTO Properties:

Property Type Description

processId

string

Unique process instance identifier

step

number

Current step number (1-indexed)

processState

enum

NONE, RUNNING, RESUME, DONE, etc.

question

string

Question text for current step

questionType

string

TXT, NUM, BCB, ITC, ONE, etc.

required

boolean

Whether answer is mandatory

options

string[]

Options for dropdown/radio questions

people

Set<FormPersonDTO>

People selected for registration

paymentUrl

string

Payment gateway URL (when complete)

3.2. Process States

process-states

Status Enum:

export enum ProcessState {
  NONE = 'NONE',           // No process started
  RUNNING = 'RUNNING',     // Process executing
  RESUME = 'RESUME',       // Resuming from suspended
  SUSPENDED = 'SUSPENDED', // Paused (browser closed)
  DONE = 'DONE',          // Questions complete
  PENDING_PAYMENT = 'PENDING_PAYMENT',
  COMPLETED = 'COMPLETED', // Fully complete
  CANCELLED = 'CANCELLED', // User cancelled
  FAILED = 'FAILED'        // Error occurred
}

4. Process Lifecycle

4.1. Initialization

1. User Accesses Membership Registration

URL: /membership/register/42?u=abc123&h=5d41402a…​

2. Load Membership Form State

this.formService.getFormMembership(this.membershipPeriodId, this.userKey)
  .subscribe(formState => {
    this.formDTO = formState;
    this.linkedPersons = Array.from(formState.people || []);
    this.processId = formState.processId;

    if (formState.processState === ProcessInstanceDTO.StatusEnum.Running ||
        formState.processState === ProcessInstanceDTO.StatusEnum.Resume) {
      this.showProcessDialog(); // Ask to resume or start fresh
    }
  });

API Endpoint: GET /api/forms/membership/{periodId}?userKey={key}

Initial Response (No Process):

{
  "processId": "proc-12345",
  "step": 0,
  "processState": "NONE",
  "people": [
    {
      "id": 1,
      "firstName": "John",
      "lastName": "Smith",
      "selected": false,
      "canSelect": true,
      "status": "Available"
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "selected": false,
      "canSelect": true,
      "status": "Available"
    }
  ]
}

4.2. Person Selection

User Selects Persons:

User checks boxes for John and Billy Smith, then clicks "Register".

Submit Selection:

this.formService.next({
  processId: this.processId,
  step: this.formDTO.step,
  people: [
    { id: 1 },
    { id: 3 }
  ]
}).subscribe(response => {
  this.formDTO = response;
  this.showQuestionBox = true;
});

API Endpoint: POST /api/forms/next

Request:

{
  "processId": "proc-12345",
  "step": 0,
  "people": [
    { "id": 1 },
    { "id": 3 }
  ]
}

Response (First Question):

{
  "processId": "proc-12345",
  "step": 1,
  "processState": "RUNNING",
  "question": "Does anyone have any medical conditions?",
  "questionType": "BCB",
  "required": true,
  "people": [
    {
      "id": 1,
      "firstName": "John",
      "lastName": "Smith",
      "answer": null,
      "hide": false
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "answer": null,
      "hide": false
    }
  ]
}

4.3. Question Workflow

Backend Process Flow:

backend-process-flow

Sequential Question Answering:

// Question 1: Medical conditions
next({
  processId: "proc-12345",
  step: 1,
  people: [
    { id: 1, answer: "0" },  // John: No
    { id: 3, answer: "1" }   // Billy: Yes
  ]
})

// Question 2: Emergency contact (shown because step 1 answered)
next({
  processId: "proc-12345",
  step: 2,
  people: [
    { id: 1, answer: "0821234567" },
    { id: 3, answer: "0821234567" }
  ]
})

// Question 3: Terms and conditions
next({
  processId: "proc-12345",
  step: 3,
  people: [
    { id: 1, answer: "1" },  // Accepted
    { id: 3, answer: "1" }   // Accepted
  ]
})

4.4. Completion

Final Response:

{
  "processId": "proc-12345",
  "step": 3,
  "processState": "DONE",
  "people": [
    {
      "id": 1,
      "firstName": "John",
      "lastName": "Smith",
      "status": "Pending Payment",
      "paymentUrl": null
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "status": "Pending Payment",
      "paymentUrl": null
    }
  ],
  "paymentUrl": "https://payment.gateway.com/pay?reference=ORD-892&amount=800.00"
}

Frontend Payment Redirect:

if (this.questionData.paymentUrl) {
  this.messageService.add({
    severity: 'info',
    summary: 'Payment Portal',
    detail: 'Redirecting to payment portal...',
    life: 5000
  });

  setTimeout(() => {
    window.location.href = this.questionData.paymentUrl;
  }, 5000);
}

5. Process Actions

5.1. Reset Process

User Action: User clicks "Start Fresh" when prompted to resume.

Frontend:

this.formService.reset(this.formDTO.processId)
  .subscribe(_formDTO => {
    if (_formDTO.processState === ProcessInstanceDTO.StatusEnum.Running) {
      this.updateFormWithData(_formDTO);
    }
  });

API Endpoint: POST /api/forms/processes/{processId}/reset

Backend Operations:

  1. Load ProcessInstance

  2. Delete all ProcessData for instance

  3. Reset current_step to first step

  4. Set status to RUNNING

  5. Clear related Membership records (if any)

  6. Return initial FormDTO

5.2. Resume Process

User Action: User clicks "Resume" when prompted.

Frontend:

this.formService.resume(this.formDTO.processId)
  .subscribe(_formDTO => {
    if (_formDTO.processState === ProcessInstanceDTO.StatusEnum.Running) {
      this.updateFormWithData(_formDTO);
      this.showQuestionBox = true; // Jump to questions
    }
  });

API Endpoint: POST /api/forms/processes/{processId}/resume

Backend Operations:

  1. Load ProcessInstance

  2. Load ProcessData for current step

  3. Reconstruct FormDTO with saved answers

  4. Set status to RUNNING (from SUSPENDED)

  5. Return FormDTO for current question

Resume Behavior:

  • Resumes from last completed step

  • Pre-populates previous answers

  • Allows back navigation through completed steps

  • Preserves person selection

6. Question Types

6.1. Text Input (TXT)

Example Question: "What is your emergency contact number?"

Question DTO:

{
  "question": "What is your emergency contact number?",
  "questionType": "TXT",
  "required": true,
  "people": [
    { "id": 1, "firstName": "John", "lastName": "Smith", "answer": null }
  ]
}

Answer Submission:

{
  "people": [
    { "id": 1, "answer": "0821234567" }
  ]
}

Validation:

  • Required: Answer must not be empty

  • Format: No specific format (free text)

6.2. Binary Checkbox (BCB)

Example Question: "Does anyone have any medical conditions?"

Question DTO:

{
  "question": "Does anyone have any medical conditions?",
  "questionType": "BCB",
  "required": false,
  "people": [
    { "id": 1, "firstName": "John", "lastName": "Smith", "answer": null },
    { "id": 3, "firstName": "Billy", "lastName": "Smith", "answer": null }
  ]
}

UI:

Does anyone have any medical conditions?

☐ John Smith
☐ Billy Smith

Answer Submission:

{
  "people": [
    { "id": 1, "answer": "0" },  // No (unchecked)
    { "id": 3, "answer": "1" }   // Yes (checked)
  ]
}

Answer Values:

  • "0" = No / Unchecked / False

  • "1" = Yes / Checked / True

6.3. I Accept Terms (ITC)

Example Question: "I accept the terms and conditions of membership"

Question DTO:

{
  "question": "I accept the terms and conditions of membership",
  "questionType": "ITC",
  "required": true,
  "people": [
    { "id": 1, "firstName": "John", "lastName": "Smith", "answer": null },
    { "id": 3, "firstName": "Billy", "lastName": "Smith", "answer": null }
  ]
}

UI Features:

  • Master checkbox: "Accept for all members"

  • Individual checkboxes per person

  • All must accept if required

Validation:

isValid(): boolean {
  if (this.questionData.questionType === 'ITC') {
    // All people must have answer '1' (accepted)
    return this.people.every(person => person.answer === '1');
  }
  return true;
}

6.4. Select One (ONE)

Example Question: "Who will be the primary contact person?"

Question DTO:

{
  "question": "Who will be the primary contact person?",
  "questionType": "ONE",
  "required": true,
  "people": [
    { "id": 1, "firstName": "John", "lastName": "Smith", "answer": null },
    { "id": 3, "firstName": "Billy", "lastName": "Smith", "answer": null }
  ]
}

UI:

Who will be the primary contact person?

(○) John Smith
(○) Billy Smith

Answer Submission:

{
  "people": [
    { "id": 1, "answer": "1" },  // Selected
    { "id": 3, "answer": "0" }   // Not selected
  ]
}

Validation:

if (this.questionData.questionType === 'ONE') {
  // Exactly one person should have answer '1'
  return this.people.some(p => p.answer === '1');
}

6.5. Dropdown (DRP)

Example Question: "What is your T-shirt size?"

Question DTO:

{
  "question": "What is your T-shirt size?",
  "questionType": "DRP",
  "required": true,
  "options": ["XS", "S", "M", "L", "XL", "XXL"],
  "people": [
    { "id": 1, "firstName": "John", "lastName": "Smith", "answer": null },
    { "id": 3, "firstName": "Billy", "lastName": "Smith", "answer": null }
  ]
}

Answer Submission:

{
  "people": [
    { "id": 1, "answer": "L" },
    { "id": 3, "answer": "M" }
  ]
}

7. Process Data Storage

7.1. Database Schema

ProcessInstance Table:

Column Type Description

id

BIGINT

Primary key

definition_id

BIGINT

FK to ProcessDefinition

current_step_id

BIGINT

FK to ProcessStep

status

VARCHAR

NONE, RUNNING, DONE, etc.

start_time

TIMESTAMP

When process started

end_time

TIMESTAMP

When process completed

related_entity_type

VARCHAR

"MEMBERSHIP", "EVENT", etc.

related_entity_id

BIGINT

FK to Membership, Event, etc.

user_key

VARCHAR

Security context

ProcessData Table:

Column Type Description

id

BIGINT

Primary key

instance_id

BIGINT

FK to ProcessInstance

step_id

BIGINT

FK to ProcessStep

person_id

BIGINT

FK to Person (nullable)

data_key

VARCHAR

"answer", "selected", etc.

data_value

TEXT

JSON or string value

created_at

TIMESTAMP

When data was saved

7.2. Data Storage Example

After Question 1 (Medical conditions):

INSERT INTO process_data (instance_id, step_id, person_id, data_key, data_value)
VALUES
  (12345, 1, 1, 'answer', '0'),
  (12345, 1, 3, 'answer', '1');

After Question 2 (Emergency contact):

INSERT INTO process_data (instance_id, step_id, person_id, data_key, data_value)
VALUES
  (12345, 2, 1, 'answer', '0821234567'),
  (12345, 2, 3, 'answer', '0821234567');

Query All Answers:

SELECT
  ps.name AS step_name,
  ps.question_text,
  p.first_name || ' ' || p.last_name AS person_name,
  pd.data_value AS answer
FROM process_data pd
JOIN process_step ps ON pd.step_id = ps.id
JOIN person p ON pd.person_id = p.id
WHERE pd.instance_id = 12345
ORDER BY ps.order_index, p.id;

Result:

step_name              | question_text                        | person_name  | answer
-----------------------|--------------------------------------|--------------|------------
Medical Condition      | Does anyone have medical conditions? | John Smith   | 0
Medical Condition      | Does anyone have medical conditions? | Billy Smith  | 1
Emergency Contact      | Emergency contact number?            | John Smith   | 0821234567
Emergency Contact      | Emergency contact number?            | Billy Smith  | 0821234567

8. Process Definition Structure

8.1. Membership Application Process

ProcessDefinition:

{
  "id": 42,
  "name": "Standard Membership Application",
  "category": "MEMBERSHIP",
  "version": 1,
  "active": true,
  "organisationId": 8
}

ProcessSteps:

[
  {
    "id": 1,
    "definitionId": 42,
    "orderIndex": 1,
    "name": "Medical Conditions",
    "questionText": "Does anyone have any medical conditions?",
    "questionType": "BCB",
    "required": false
  },
  {
    "id": 2,
    "definitionId": 42,
    "orderIndex": 2,
    "name": "Emergency Contact",
    "questionText": "What is your emergency contact number?",
    "questionType": "TXT",
    "required": true
  },
  {
    "id": 3,
    "definitionId": 42,
    "orderIndex": 3,
    "name": "Primary Contact",
    "questionText": "Who will be the primary contact person?",
    "questionType": "ONE",
    "required": true
  },
  {
    "id": 4,
    "definitionId": 42,
    "orderIndex": 4,
    "name": "Terms and Conditions",
    "questionText": "I accept the terms and conditions of membership",
    "questionType": "ITC",
    "required": true
  }
]

8.2. Conditional Steps

Future Enhancement: Process engine can support conditional branching:

Step 1: "Does anyone have medical conditions?"
  Answer = "0" (No) → Skip to Step 3
  Answer = "1" (Yes) → Continue to Step 2

Step 2: "Please describe medical conditions"
  Type: TXT
  Required: true

Step 3: "Emergency contact number"
  ...

Implementation:

  • ProcessStepOption entities define branches

  • Backend evaluates conditions based on answers

  • FormDTO returns appropriate next step

9. Form Service API

9.1. Service Interface

@Injectable({ providedIn: 'root' })
export class FormService {
  /**
   * Get initial form state for membership
   */
  getFormMembership(membershipPeriodId: number, userKey: string):
    Observable<FormDTO>

  /**
   * Submit current step and get next
   */
  next(data: { processId: string, step: number, people: any[] }):
    Observable<FormDTO>

  /**
   * Reset process to beginning
   */
  reset(processId: string): Observable<FormDTO>

  /**
   * Resume suspended process
   */
  resume(processId: string): Observable<FormDTO>
}

9.2. REST Endpoints

Method Endpoint Purpose Response

GET

/api/forms/membership/{periodId}?userKey={key}

Get initial form state

FormDTO with people, processId

POST

/api/forms/next

Submit step, get next

FormDTO with next question or completion

POST

/api/forms/processes/{id}/reset

Reset to start

FormDTO with initial state

POST

/api/forms/processes/{id}/resume

Resume from suspension

FormDTO with current question

9.3. Detailed API Examples

9.3.1. GET /api/forms/membership/{periodId}

Purpose: Initialize a membership registration workflow

URL Parameters:

  • periodId - MembershipPeriod ID (e.g., 42)

  • userKey - User security token (query param)

  • organisationId - Organization ID (query param)

Example Request:

GET /api/forms/membership/42?userKey=abc123xyz&organisationId=8
Accept: application/json

Example Response - New Process:

{
  "processId": "proc-12345-67890",
  "step": 0,
  "processState": "INIT",
  "question": null,
  "questionType": null,
  "people": [
    {
      "id": 1,
      "firstName": "Sarah",
      "lastName": "Smith",
      "dateOfBirth": "1985-03-15",
      "selected": false,
      "canSelect": true,
      "status": "Available"
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "dateOfBirth": "2010-11-03",
      "selected": false,
      "canSelect": true,
      "status": "Available"
    },
    {
      "id": 5,
      "firstName": "Emma",
      "lastName": "Smith",
      "dateOfBirth": "2013-07-20",
      "selected": false,
      "canSelect": false,
      "status": "Already Registered"
    }
  ],
  "paymentUrl": null
}

Example Response - Existing Process (Suspended):

{
  "processId": "proc-12345-67890",
  "step": 2,
  "processState": "SUSPENDED",
  "question": "What is your emergency contact number?",
  "questionType": "TXT",
  "people": [
    {
      "id": 1,
      "firstName": "Sarah",
      "lastName": "Smith",
      "selected": true,
      "answer": "0821234567"
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "selected": true,
      "answer": ""
    }
  ],
  "paymentUrl": null
}

9.3.2. POST /api/forms/next

Purpose: Submit current step answers and retrieve next question

Request Body:

{
  "processId": "proc-12345-67890",
  "step": 0,
  "people": [
    {
      "id": 1,
      "selected": true
    },
    {
      "id": 3,
      "selected": true
    }
  ]
}

Response - First Question:

{
  "processId": "proc-12345-67890",
  "step": 1,
  "processState": "RUNNING",
  "question": "Does anyone have any medical conditions?",
  "questionType": "BCB",
  "required": false,
  "options": null,
  "people": [
    {
      "id": 1,
      "firstName": "Sarah",
      "lastName": "Smith",
      "answer": null,
      "hide": false
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "answer": null,
      "hide": false
    }
  ],
  "paymentUrl": null
}

Submitting Answer:

{
  "processId": "proc-12345-67890",
  "step": 1,
  "people": [
    {
      "id": 1,
      "answer": "0"
    },
    {
      "id": 3,
      "answer": "1"
    }
  ]
}

Response - Next Question:

{
  "processId": "proc-12345-67890",
  "step": 2,
  "processState": "RUNNING",
  "question": "What is your emergency contact number?",
  "questionType": "TXT",
  "required": true,
  "options": null,
  "people": [
    {
      "id": 1,
      "firstName": "Sarah",
      "lastName": "Smith",
      "answer": null,
      "hide": false
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "answer": null,
      "hide": false
    }
  ],
  "paymentUrl": null
}

Response - Last Question (Payment Required):

{
  "processId": "proc-12345-67890",
  "step": 5,
  "processState": "DONE",
  "question": null,
  "questionType": null,
  "required": null,
  "options": null,
  "people": [
    {
      "id": 1,
      "firstName": "Sarah",
      "lastName": "Smith",
      "paymentUrl": "https://payment.gateway.com/pay?ref=M2024-1523&amt=500.00"
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "paymentUrl": "https://payment.gateway.com/pay?ref=M2024-1524&amt=300.00"
    }
  ],
  "paymentUrl": "https://payment.gateway.com/pay?ref=ORDER-12345&amt=800.00"
}

9.3.3. POST /api/forms/processes/{processId}/reset

Purpose: Reset process to initial state, clear all answers

URL Parameters:

  • processId - Process instance ID

Example Request:

POST /api/forms/processes/proc-12345-67890/reset
Content-Type: application/json

Response:

{
  "processId": "proc-12345-67890",
  "step": 0,
  "processState": "INIT",
  "question": null,
  "questionType": null,
  "people": [
    {
      "id": 1,
      "firstName": "Sarah",
      "lastName": "Smith",
      "selected": false,
      "canSelect": true,
      "status": "Available"
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "selected": false,
      "canSelect": true,
      "status": "Available"
    }
  ],
  "paymentUrl": null
}

Backend Operations:

  1. Load ProcessInstance by ID

  2. Delete all ProcessData records for this instance

  3. Reset current_step_id to null

  4. Set status to INIT

  5. Return fresh FormDTO with person selection

9.3.4. POST /api/forms/processes/{processId}/resume

Purpose: Resume a suspended process from current step

URL Parameters:

  • processId - Process instance ID

Example Request:

POST /api/forms/processes/proc-12345-67890/resume
Content-Type: application/json

Response:

{
  "processId": "proc-12345-67890",
  "step": 3,
  "processState": "RESUME",
  "question": "Who will be the primary contact person?",
  "questionType": "ONE",
  "required": true,
  "options": null,
  "people": [
    {
      "id": 1,
      "firstName": "Sarah",
      "lastName": "Smith",
      "answer": null,
      "hide": false
    },
    {
      "id": 3,
      "firstName": "Billy",
      "lastName": "Smith",
      "answer": null,
      "hide": false
    }
  ],
  "paymentUrl": null
}

Backend Operations:

  1. Load ProcessInstance by ID

  2. Load ProcessData for current step

  3. Load previous step answers (steps 1-2 already completed)

  4. Set status to RESUME

  5. Return FormDTO with current question and people

9.4. HTTP Status Codes

Code Status Meaning

200

OK

Request successful, FormDTO returned

201

Created

New ProcessInstance created

400

Bad Request

Invalid request body or parameters

401

Unauthorized

Missing or invalid userKey/authentication

404

Not Found

ProcessInstance or MembershipPeriod not found

409

Conflict

Process already completed or in invalid state

422

Unprocessable Entity

Validation errors in submitted answers

500

Internal Server Error

Server-side error during processing

Error Response Format:

{
  "timestamp": "2024-01-31T10:15:30Z",
  "status": 422,
  "error": "Unprocessable Entity",
  "message": "Validation failed for question answers",
  "path": "/api/forms/next",
  "errors": [
    {
      "field": "people[0].answer",
      "message": "Emergency contact number is required"
    },
    {
      "field": "people[1].answer",
      "message": "Phone number must be 10 digits"
    }
  ]
}

10. Integration Scenarios

10.1. Scenario 1: New Membership Registration

  1. User accesses /membership/register/42?u=abc123&h=…​

  2. System creates ProcessInstance in NONE state

  3. User selects 2 family members

  4. System transitions to RUNNING, loads first question

  5. User answers 4 questions sequentially

  6. System transitions to DONE, generates payment URL

  7. User redirects to payment gateway

  8. After payment, system transitions to COMPLETED

  9. Membership records created for both persons

10.2. Scenario 2: Resume Registration

  1. User accesses same URL 2 days later

  2. System loads ProcessInstance in SUSPENDED state

  3. System prompts: "Resume or Start Fresh?"

  4. User clicks "Resume"

  5. System loads step 3 of 4 (last completed: step 2)

  6. User sees Question 3 with previous persons pre-selected

  7. User completes Questions 3 and 4

  8. Normal completion flow continues

10.3. Scenario 3: Free Membership

  1. User selects single person

  2. User answers all questions

  3. System calculates fee: R 0.00 (promotional period)

  4. System skips payment (paymentUrl = null)

  5. System transitions directly to COMPLETED

  6. Membership record created immediately

  7. Confirmation email sent

11. Error Handling

11.1. Process Errors

Scenarios:

Error Cause Recovery

ProcessInstance not found

Invalid processId

Create new instance

ProcessDefinition inactive

Definition was deactivated

Show error, link to support

Step validation failed

Invalid answer format

Return validation errors

Person not eligible

Age/membership restrictions

Show error, allow changes

Payment generation failed

Gateway unavailable

Complete registration, email payment link later

Error Response:

{
  "processId": "proc-12345",
  "step": 2,
  "processState": "ERROR",
  "message": "Invalid answer format for emergency contact",
  "errors": {
    "people": [
      {
        "id": 1,
        "error": "Phone number must be 10 digits"
      }
    ]
  }
}

11.2. State Recovery

Timeout Handling:

  • ProcessInstance automatically suspended after 30 minutes inactivity

  • User can resume within 7 days

  • After 7 days, process marked as CANCELLED

Browser Close:

  • State saved after each question

  • No data lost if browser closes

  • User prompted to resume on return

12. Performance Considerations

12.1. Caching

ProcessDefinition Caching:

  • Cache active definitions in memory

  • Cache ProcessSteps for each definition

  • Refresh cache on definition updates

ProcessInstance Optimization:

  • Load only current step data (not full history)

  • Use database indexes on instance_id, status

  • Archive completed instances after 90 days

12.2. Query Optimization

Efficient Queries:

-- Load current step with answer data
SELECT ps.*, pd.data_value
FROM process_step ps
LEFT JOIN process_data pd ON ps.id = pd.step_id
  AND pd.instance_id = ?
WHERE ps.id = (
  SELECT current_step_id FROM process_instance WHERE id = ?
);

Indexes:

  • process_instance(user_key, status)

  • process_data(instance_id, step_id, person_id)

  • process_step(definition_id, order_index)