Entries & Eligibility
Player entries, divisions, eligibility rules, team events, and withdrawal management.
Entry workflow
┌──────────┐ ┌────────────┐ ┌───────────┐ ┌───────────┐
│ OPEN │───▶│ ENTERED │───▶│ PLAYING │───▶│ COMPLETED │
│ (window) │ │ (snapshot) │ │ (scoring) │ │ (results) │
└──────────┘ └────────────┘ └───────────┘ └───────────┘
│
▼
┌───────────┐
│ WITHDRAWN │
└───────────┘
Creating entries
Basic entry
await entriesService.create({
competitionId: 'comp-123',
playerId: 'player-456',
});
Entry with division
await entriesService.create({
competitionId: 'comp-123',
playerId: 'player-456',
divisionId: 'div-a', // A Division
});
Entry with handicap provider
await entriesService.create({
competitionId: 'comp-123',
playerId: 'player-456',
handicapProvider: 'GOLFRSA',
membershipNumber: 'RSA123456',
});
Guest entry (manual handicap)
await entriesService.create({
competitionId: 'comp-123',
playerId: 'guest-789',
handicapIndexOverride: 18.5,
});
Entry linked to tee time
await entriesService.create({
competitionId: 'comp-123',
playerId: 'player-456',
teeTimeSlotId: 'slot-abc', // Existing booking
});
Entry data
interface CompetitionEntry {
id: string;
competitionId: string;
playerId: string;
divisionId: string | null;
teeTimeSlotId: string | null;
enteredAt: Date;
withdrawnAt: Date | null;
withdrawalReason: string | null;
// Relationships
player: Player;
division: CompetitionDivision | null;
handicapSnapshot: HandicapSnapshot | null;
scorecards: Scorecard[];
teamMembers: CompetitionTeamMember[];
}
Divisions
Divisions segment players by handicap, gender, or age.
Creating divisions
await competitionsService.create({
// ...competition details
divisions: [
{
name: 'A Division',
sortOrder: 1,
maxHandicap: 12,
},
{
name: 'B Division',
sortOrder: 2,
minHandicap: 13,
maxHandicap: 24,
},
{
name: 'C Division',
sortOrder: 3,
minHandicap: 25,
},
{
name: 'Ladies',
sortOrder: 4,
gender: 'F',
},
{
name: 'Seniors',
sortOrder: 5,
minAge: 55,
},
],
});
Division configuration
interface CreateDivisionInput {
name: string;
sortOrder?: number; // Display order
minHandicap?: number; // Minimum handicap index
maxHandicap?: number; // Maximum handicap index
gender?: 'M' | 'F'; // Gender restriction
minAge?: number; // Minimum age
maxAge?: number; // Maximum age
teeSet?: number; // Tee set for this division
}
Division auto-assignment
When divisionId is not provided, the system can auto-assign based on:
- Player's handicap index (from snapshot)
- Player's gender (from profile)
- Player's age (calculated from DOB)
Eligibility rules
Competition-level eligibility
interface CompetitionEligibilityConfig {
minHandicap?: number;
maxHandicap?: number;
allowedGenders?: Array<'M' | 'F'>;
minAge?: number;
maxAge?: number;
requireHomeClub?: boolean; // Must be member of hosting club
allowGuests?: boolean; // Allow non-members
allowedMembershipStatuses?: string[]; // e.g., ['FULL', 'ASSOCIATE']
allowedAssociationProviders?: string[]; // e.g., ['GOLFRSA', 'DOTGOLF']
}
Usage
await competitionsService.create({
// ...
eligibility: {
maxHandicap: 24,
allowedGenders: ['M'],
minAge: 18,
requireHomeClub: true,
allowGuests: false,
},
});
Eligibility policy (reusable)
For common eligibility configurations:
{
eligibilityPolicyId: 'policy-club-champs', // Reference to stored policy
}
Eligibility check flow
- Check competition-level rules
- Check division-specific rules
- Validate handicap snapshot available
- Verify membership status (if required)
- Verify age (if min/max specified)
Team events
Configure team competition
await competitionsService.create({
clubId: 'club-123',
name: 'Better Ball Championship',
format: 'FOUR_BALL_BETTER_BALL',
isTeamEvent: true,
teamSize: 2,
// ...rounds
});
Create team entry
await entriesService.create({
competitionId: 'comp-123',
playerId: 'player-a', // Team captain
teamMembers: [
{ playerId: 'player-b', position: 2 },
],
});
4-person team
await entriesService.create({
competitionId: 'comp-123',
playerId: 'player-a',
teamMembers: [
{ playerId: 'player-b', position: 2 },
{ playerId: 'player-c', position: 3 },
{ playerId: 'player-d', position: 4 },
],
});
Team member data
interface CompetitionTeamMember {
id: string;
entryId: string;
playerId: string;
position: number; // 1 = captain, 2-n = members
player: Player;
}
Withdrawals
Withdraw an entry
await entriesService.withdraw({
entryId: 'entry-123',
reason: 'Injury',
});
Withdrawal data
{
withdrawnAt: Date; // Timestamp of withdrawal
withdrawalReason: string; // Optional reason
}
Withdrawal rules
- Cannot withdraw after round is locked
- Withdrawal reason is optional but recommended
- Entry fee refund handled separately (not in tournaments module)
- Withdrawn entries excluded from leaderboard by default
Include withdrawn in results
const leaderboard = await resultsService.getLeaderboard({
competitionId: 'comp-123',
includeWithdrawn: true, // Include withdrawn entries
});
Entry limits
Max entries
{
maxEntries: 120, // Competition limit
}
Entry creation fails if limit reached.
Entry windows
{
entryOpens: new Date('2025-12-01T00:00:00'),
entryCloses: new Date('2025-12-14T18:00:00'),
}
- Entries rejected before
entryOpens - Entries rejected after
entryCloses - Admin can override windows
Entry fees
Configure fees
{
entryFee: 15000, // R150.00 (cents)
entryFee9Holes: 8000, // R80.00 for 9-hole option
}
Fee tracking
Entry fee payment tracked separately via payments module. The entryFee field is informational for the competition.
Tee sheet sync
Automatically create entries from tee sheet bookings for a linked round:
// Round must have teeSheetId set
await teeSheetSyncService.syncRound('round-123');
Sync behavior
- Load round (and
teeSheetId); skip if none. - Read tee times + slots for the sheet.
- For each slot with
playerId, create an entry if one does not already exist for the competition. - Link the entry to the slot (
teeTimeSlotId).
Querying entries
List entries for competition
const entries = await entriesService.findByCompetition('comp-123');
List entries for player
const entries = await entriesService.findByPlayer('player-456', {
clubId: 'club-123',
seasonId: 'season-2025',
});
Get entry detail
const entry = await entriesService.findById('entry-123', {
includeScores: true,
includeTeamMembers: true,
});