Event Registration

1. Overview

The Event Registration workflow enables users to register participants for events such as races, tournaments, and competitions. It follows the Phase 2 Registration Design with implementation-specific details for event scenarios.

Key Features:

  • Multi-participant Registration - Register multiple people in a single transaction

  • LinkedPerson Integration - Select from existing linked persons

  • Order-based System - Creates Order entity for payment tracking

  • Payment Gateway Integration - Redirect to payment portal after registration

  • Cancellation Support - Cancel registrations with refund rules (Phase 2)

2. Event Concepts

2.1. Event Entities

event-entities

Event

Defines the overall competition or activity:

  • Name, description, dates, timezone

  • Location and venue

  • Organizer (Organisation)

  • enrolProcess — links to ProcessDefinition for the registration question workflow

  • productDefault — fallback product for pricing when EventCategory has no product

  • customList1/2/3 — configurable custom lists (e.g., clubs, teams, sponsors)

EventCategory

Age/gender grouping within an event:

  • Name (e.g., "U15 Boys", "Senior Men", "Elite Women")

  • minAge, maxAge — age eligibility range

  • gender — gender eligibility (M/F or null for mixed)

  • product — pricing product for this category (primary pricing source)

  • Can inherit from sourceCategory (organisation-level standard categories)

EventRaceType

Race type configuration per event:

  • Links a RaceType (e.g., Scratch, Elimination, Match Sprint) to an Event

  • Optional name override; falls back to RaceType.name

Race (Matrix Intersection)

Specific race within an event — created at each valid EventCategory × EventRaceType combination:

  • Links one EventCategory and one EventRaceType

  • Not all combinations are valid (e.g., U15 may not race Match Sprint)

  • Contains StartGroup records for participant assignment

StartGroup / StartGroupParticipant

Race participation:

  • StartGroup belongs to a Race; multiple groups possible (warm-up, qualification, final)

  • StartGroupParticipant links an EventParticipant to a StartGroup

  • One EventParticipant can have multiple StartGroupParticipant records (one per selected race type)

EventParticipant

Individual participant registration:

  • Links Person to Event with exactly one EventCategory (single-select)

  • Many-to-many with EventRaceType (multi-select)

  • custom1/2/3 — CustomListValue references mapped from Event.customList1/2/3

  • enrolInstance — links to the ProcessInstance that created this participant

  • Stores mirrored personal data from Person (override capability)

CustomList / CustomListValue

Event-configurable dropdown lists:

  • Event supports 3 custom list slots (customList1/2/3)

  • Each CustomList contains multiple CustomListValues

  • metaKey on CustomList corresponds to UserMeta key for "last used" persistence

  • Selected values populate EventParticipant.custom1/2/3

Order / OrderLineItem

Payment transaction container:

  • Created during EventFormController.onDone()

  • Groups line items for all participants in a registration

  • Product/price sourced from EventCategory.product with Event.productDefault fallback

  • Tracks payment status (UNPAID → PENDING → PAID)

3. Registration Flow by Step

This section details each step from the Phase 2 Registration Design as it applies to event registration.

3.1. Step 1: Identity Selection

Phase 2 Enhancement: Identity Selection is a new capability being introduced. Current implementation uses URL parameters for user identification.

3.1.1. Current Implementation (URL-Based Access)

Route Pattern:

/register

Query Parameters:

Parameter Description Required Example

eventId

Event ID to register for

Yes

42

orgId

Organisation ID (organizer)

Yes

8

userKey

User key (security context)

Optional

abc123xyz

Example URLs:

https://app.example.com/register?eventId=42&orgId=8&userKey=abc123xyz
https://app.example.com/register?eventId=42&orgId=8

Session vs URL userKey:

  • If userKey provided in URL → Use URL parameter (external link scenario)

  • If no userKey in URL → Use session userKey (logged-in user scenario)

3.1.2. Phase 2: OIDC Authentication

When OIDC authentication is configured for the tenant, users will be presented with a choice:

  • Authenticate - Sign in via OIDC provider to access existing profile

  • Continue Anonymously - Proceed without authentication

See Identity Selection in the Phase 2 design for details.

3.2. Step 2: Registration Overview

The Registration Overview displays the event details and linked persons for selection.

Component: RegistrationComponent

File: src/main/webapp/app/entities/registration/list/registration.component.ts

3.2.1. Component Structure

export class RegistrationComponent implements OnInit, OnDestroy {
  persons: IPersonSelect[] = [];
  selectForm!: FormGroup;
  selectCount = 0;
  isLoading = false;
  isSaving = false;

  _eventId: number | undefined;
  event: IEvent | undefined;
  _organisationId: number | undefined;
  organisation: IOrganisation | undefined;
  _userKey: string | undefined;
  principal: IPerson | undefined;
  sessionUserId: boolean = true;
}

3.2.2. Initialization

ngOnInit(): void {
  this.selectForm = this.fb.group({
    selection: this.fb.array([]),
  });

  // Subscribe to checkbox changes for count
  this.selectionChangeSubscription = this.selectForm.valueChanges
    .subscribe(data => {
      const arr: boolean[] = data.selection;
      this.selectCount = arr.reduce((n, checkbox) =>
        (checkbox ? n + 1 : n), 0);
    });

  this.handleNavigation();
}

3.2.3. Navigation Handler

protected handleNavigation(): void {
  combineLatest([this.activatedRoute.data, this.activatedRoute.queryParamMap])
    .subscribe(([data, params]) => {
      const eventId = params.get('eventId');
      if (eventId) {
        this.eventId = +eventId;
      }

      const organisationId = params.get('orgId');
      if (organisationId) {
        this.organisationId = +organisationId;
      }

      const userKey = params.get('userKey');
      if (userKey) {
        this.userKey = userKey;
        this.sessionUserId = false;
      } else {
        this.userKey = this.sessionService.getUserKey();
        this.sessionUserId = true;
      }

      this.loadPage(pageNumber, true);
    });
}

API Endpoints Called:

  • GET /api/events/{id} - Load event details

  • GET /api/organisations/{id} - Load organisation details

  • GET /api/people/userkey/{userkey} - Load or create principal person

3.2.4. Loading LinkedPersons

loadPage(page?: number, dontNavigate?: boolean): void {
  const pageToLoad: number = page ?? this.page ?? 1;

  if (this.organisationId && this.userKey) {
    this.isLoading = true;
    this.personService.getAllLinkedOrgUsersByPrincipal(
      this.userKey,
      this.organisationId
    ).subscribe(
      (res: EmbeddedLinkedPersonDTO[]) => {
        this.isLoading = false;
        this.persons = res;
        this.selectForm.setControl('selection', this.buildSelection());
      },
      () => {
        this.isLoading = false;
        this.onError();
      }
    );
  }
}

API Endpoint: GET /api/people/linked?userKey={key}&organisationId={orgid}

Response:

[
  {
    "id": 1,
    "firstName": "John",
    "lastName": "Smith",
    "dateOfBirth": "1985-03-15",
    "gender": "MALE",
    "email": "[email protected]",
    "contactNumber": "0821234567"
  },
  {
    "id": 3,
    "firstName": "Billy",
    "lastName": "Smith",
    "dateOfBirth": "2010-11-03",
    "gender": "MALE",
    "email": null,
    "contactNumber": null
  }
]

3.2.5. Person Selection UI

person-selection-ui

3.2.6. Existing Registration Status

Phase 2 Enhancement: The Registration Overview will clearly show if any person is already registered for this event. See Registration Overview in the Phase 2 design.

For persons already registered:

  • Display "Registered" status

  • Show bib number if assigned

  • If current user was the payer, show link to view order/payment/invoice details

3.2.7. Event Cancellation

Phase 2 Enhancement: Event cancellation with refund rules is a new capability. See Registration Overview in the Phase 2 design.

Outstanding Requirements:

  • REQ-001: Define cancellation windows and refund percentages

  • REQ-002: Specify processing fee handling

  • REQ-003: Define refund mechanism

  • REQ-004: Determine notification flow for cancellation

For events, users will be able to cancel existing registrations:

  1. Select registered person

  2. Request cancellation

  3. System applies configured refund rules

  4. Refund processed (if applicable)

  5. Registration status updated to cancelled

The Link Person step allows users to add participants to their profile.

See LinkedPerson Management for complete details on the person search, matching, and linking workflow.

Add Person Button:

<a routerLink="/linked-person/search">
  <button type="button" class="btn btn-primary">
    <fa-icon icon="plus"></fa-icon>
    Add Person
  </button>
</a>

Flow:

  1. User clicks "Add Person"

  2. Navigate to /linked-person/search

  3. User searches/creates person

  4. Person is linked to principal

  5. Return to /register with updated person list

3.3.1. Form Array for Selection

buildSelection(clear?: boolean): FormArray<FormControl<boolean | null>> {
  const arr = this.persons.map(person => {
    return this.fb.control(!clear ? !!person.selected : false);
  });
  return this.fb.array(arr);
}

get selectionControl(): FormArray<FormControl<boolean | null | undefined>> {
  return this.selectForm.get('selection') as FormArray;
}

Validation:

get canRegister(): boolean {
  return !!this.event &&
         this.selectForm.valid &&
         this.selectCount > 0 &&
         !this.isLoading &&
         !this.isSaving;
}

3.4. Step 4: Registration Details (Question Workflow)

Event registration uses the ProcessDefinition-driven question workflow, managed by EventFormController. The Event.enrolProcess field links to a ProcessDefinition containing ordered ProcessStep records, each mapped to a FormField type.

3.4.1. Question Flow

The EventFormController iterates through ProcessSteps in sequence order. For each step, the corresponding FormField is instantiated, options loaded, and the user’s answer validated before advancing. If all steps pass validation, onDone() is called to create backend records.

3.4.2. Event-Specific FormField Types

ECA — Event Category Selection (single-select)

  • Presents eligible EventCategories for the event

  • Filters by participant’s age and gender (minAge, maxAge, gender)

  • Matrix filtering: If ERT was already answered, further filters to categories that have valid Race records for the selected race types

  • Auto-selects and skips if only one valid category remains

  • Stores selected category ID in ProcessData (key: eventCategory)

ERT — Event Race Type Selection (multi-select)

  • Presents available EventRaceTypes for the event

  • Matrix filtering: If ECA was already answered, filters to race types that have valid Race records for the selected category

  • Auto-selects and skips if only one valid race type remains

  • Stores selected race type IDs in ProcessData (key: eventRaceTypes)

  • UI renders as multi-select (UI_CODE: MRT)

ECA and ERT can appear in either order in the ProcessDefinition. The matrix filtering adapts: whichever is answered first constrains the options for the second.

CLV — Custom List Value (dropdown)

  • When ProcessStep.customListSlot is set (1, 2, or 3):

    • Loads options from Event.customList{N}CustomListValue entities

    • Default value resolution: existing EventParticipant.custom{N}UserMeta via personDataKey → none

    • On save: persists to ProcessData AND updates UserMeta for "last used" recall

  • When customListSlot is null: falls back to pipe-delimited listOptions (backward compatibility)

3.4.3. Generic FormField Types

The following generic fields are shared with membership registration:

Code Type Purpose

TXT

Free Text

Text input with min/max/regex validation

DRP

Dropdown

Select from ProcessStepOption records

RBO

Radio Buttons

Radio group from ProcessStepOption records

ROO

Pick One

Single radio option selection

CHK

Checkbox

Boolean checkbox

IND

Indemnity

Terms and conditions acceptance

COM

Communication

Communication preference

EMG

Emergency Contact

Emergency contact details

PHO

Phone Number

Phone number with validation

See Process Flow Integration for details on the ProcessDefinition system.

3.5. Step 5: Order Creation (EventFormController.onDone)

When all ProcessSteps pass validation, EventFormController.onDone() executes to create all backend records. This follows the same pattern as MembershipFormController.onDone().

3.5.1. Processing Flow

ondone-flow

3.5.2. Data Flow

ProcessData Key Source Step Destination

eventCategory

ECA

EventParticipant.category (EventCategory ID)

eventRaceTypes

ERT

Race lookup per category → StartGroupParticipant per Race

CLV field key

CLV (slot 1/2/3)

EventParticipant.custom1/2/3 (CustomListValue ID)

3.5.3. Order Structure

  • Order: Draft with status UNPAID, buyer = ProcessInstance.startedBy

  • OrderLineItem: One per person, product from EventCategory.product (fallback: Event.productDefault)

  • orderId returned in FormDTO response — frontend uses this to navigate to payment

See Order Creation in the Phase 2 design for payment callback details.

3.6. Step 6: Payment

3.6.1. Form Completion → Payment Redirect

When the participant completes all ProcessSteps, the frontend submits the form via the Form API. This triggers EventFormController.onDone() (documented in Step 5), which creates all backend records and returns the orderId in the FormDTO response.

The frontend then redirects to the payment URL:

// After form completion, FormDTO contains orderId
if (formResponse.orderId) {
  // Navigate to payment page with order reference
  this.router.navigate(['/registration/payment', formResponse.orderId]);
}

API Endpoints:

  • POST /api/form/{processInstanceId}/submit — Submit completed form (triggers onDone())

  • Response includes orderId for payment redirect

3.6.2. Backend Processing (FormController-based)

The backend processing for event registration is handled entirely by the FormController architecture:

  1. Form Validation (FormController base)

    • Each ProcessStep validated by its FormField implementation

    • All PersonData keys resolved and stored as ProcessData

    • Invalid steps returned to frontend for correction

  2. Record Creation (EventFormController.onDone() — see Step 5)

    • EventParticipant created with category, custom values, and enrolInstance

    • StartGroupParticipant records created per Race (EventCategory × EventRaceType)

    • Order with OrderLineItems created (status UNPAID)

  3. Registration State (EventFormController.setRegistrationState())

    • Checks for existing EventParticipant for each person/event pair

    • Sets FormPersonStatus.REGISTERED if already active

    • Sets FormPersonStatus.PENDING_PAYMENT if unpaid Order exists from same buyer (with paymentUrl)

    • Sets FormPersonStatus.NOT_REGISTERED otherwise

  4. Payment Integration

    • If Order amount > 0, frontend redirects to payment URL using orderId

    • Payment callback handling is out of scope for EventFormController (see below)

  5. Free Registration

    • If amount = 0 (no product on EventCategory or Event), Order created with zero value

    • Participants registered immediately without payment redirect

3.6.3. Registration Response

The FormDTO response after successful form submission:

{
  "processInstanceId": 456,
  "status": "DONE",
  "orderId": 892,
  "people": [
    {
      "personId": 3,
      "name": "Billy Smith",
      "status": "PENDING_PAYMENT"
    }
  ]
}

3.6.4. Online Payment (WooCommerce)

Payment Redirect:

if (res.body?.infoURL) {
  location.href = res.body.infoURL;
}

Payment Gateway URL Example:

https://payment.gateway.com/pay?
  reference=ORD-892-2024&
  amount=350.00&
  return=https://app.example.com/registration/payment-return&
  cancel=https://app.example.com/registration/payment-cancel

3.6.5. Manual Payment

Phase 2 Enhancement: Manual payment is a new capability being introduced. See Payment in the Phase 2 design.

Manual payment will be available for registration desk scenarios at events:

  1. System generates reference number with QR code

  2. User presents QR code to staff at registration desk

  3. Staff scans/enters reference on registration system

  4. Staff captures: amount, payment method, outcome

  5. Registration completes upon payment confirmation

3.6.6. Payment Return

Return URL: /registration/payment-return

Query Parameters:

Parameter Description Example

reference

Order reference

ORD-892-2024

status

Payment status

success, failed, cancelled

transactionId

Gateway transaction ID

TXN-12345678

Backend Processing on Return:

  1. Verify payment status with gateway

  2. Update Order status

  3. Update EventParticipant status

  4. Send confirmation email

  5. Display success/failure message

3.6.7. Payment States

payment-states

3.7. Step 7: Confirmation & Notifications

3.7.1. Immediate Confirmation

Upon successful payment:

  • EventParticipant records marked as REGISTERED

  • Bib numbers assigned (if applicable)

  • Confirmation displayed with registration details

  • Confirmation email sent

Confirmation Display:

Registration Successful!

Billy Smith - Bib Number: B1523

You will receive your race pack at:
Race Expo: March 14, 2024, 10:00-18:00
Centurion Mall

Race Start: March 15, 2024, 07:00

Good luck!

3.7.2. Scheduled Notifications

Phase 2 Enhancement: Scheduled notifications are managed by long-running processes. See Confirmation & Notifications in the Phase 2 design.

For events, scheduled notifications include:

  • Registration confirmation with event details

  • Event location and directions (closer to event date)

  • Program updates and changes

  • Pre-event reminders (day before, morning of)

  • Post-event results and certificates

4. Error Handling

4.1. Validation Errors

Client-Side:

  • No event loaded → Disable register button

  • No persons selected → Disable register button

  • Form invalid → Display validation messages

Server-Side:

{
  "phase": "VALIDATION_ERROR",
  "title": "Registration Error",
  "message": "One or more participants are not eligible for this event",
  "participants": [
    {
      "id": null,
      "person": { "id": 3, "firstName": "Billy", "lastName": "Smith" },
      "valid": false,
      "message": "Participant is under minimum age requirement (18 years)"
    }
  ]
}

Error Display:

<div *ngIf="registration.phase === 'VALIDATION_ERROR'" class="alert alert-danger">
  <h4>{{ registration.title }}</h4>
  <p>{{ registration.message }}</p>
  <ul>
    <li *ngFor="let p of registration.participants">
      <span *ngIf="!p.valid">
        {{ p.person.firstName }} {{ p.person.lastName }}: {{ p.message }}
      </span>
    </li>
  </ul>
</div>

4.2. Registration Failures

Common Failure Scenarios:

Scenario Cause Resolution

Event Full

Maximum participants reached

Show waitlist option

Registration Closed

Outside registration window

Display event information only

Duplicate Registration

Person already registered

Show existing registration

Payment Gateway Down

External service unavailable

Allow registration, mark as pending payment

Network Error

Connection timeout

Show retry button, preserve selection

4.3. Loading States

<div *ngIf="isLoading" class="text-center">
  <div class="spinner-border" role="status">
    <span class="sr-only">Loading persons...</span>
  </div>
</div>

<div *ngIf="isSaving" class="text-center">
  <div class="spinner-border" role="status">
    <span class="sr-only">Processing registration...</span>
  </div>
</div>

5. Integration Points

5.1. LinkedPerson Workflow

Event registration integrates with LinkedPerson management:

  1. User accesses registration URL

  2. System loads linked persons for user

  3. User can add more persons via LinkedPerson workflow

  4. User selects participants from linked persons

  5. Registration creates EventParticipant records

5.2. Order System

Event registration creates Order entities:

  • Order - Container for transaction

  • OrderLine - Individual line items (one per participant)

  • Payment - Payment record after gateway processing

See Financial Entities for details.

5.3. Email Notifications

Registration Confirmation:

  • Sent after successful registration (free events)

  • Sent after successful payment (paid events)

  • Includes participant details, event information

  • Includes payment receipt (if applicable)

Payment Receipt:

  • Sent after successful payment

  • Includes order reference, amount paid

  • Itemized list of participants

6. Interactive UI Prototypes

Interactive HTML prototypes are available for event registration screens. See Screen Designs - Interactive UI Prototypes for links to:

  • Registration Overview (Event) - Participant selection with event details

  • Payment Selection (Event) - Order summary with individual line items

  • Manual Payment QR Handoff - Staff capture screen