export type SeasonStatus = | 'planned' | 'active' | 'completed' | 'archived' | 'cancelled'; import { RacingDomainInvariantError, RacingDomainValidationError, } from '../errors/RacingDomainError'; import type { IEntity } from '@gridpilot/shared/domain'; import type { SeasonSchedule } from '../value-objects/SeasonSchedule'; import type { SeasonScoringConfig } from '../value-objects/SeasonScoringConfig'; import type { SeasonDropPolicy } from '../value-objects/SeasonDropPolicy'; import type { SeasonStewardingConfig } from '../value-objects/SeasonStewardingConfig'; export class Season implements IEntity { readonly id: string; readonly leagueId: string; readonly gameId: string; readonly name: string; readonly year: number | undefined; readonly order: number | undefined; readonly status: SeasonStatus; readonly startDate: Date | undefined; readonly endDate: Date | undefined; readonly schedule: SeasonSchedule | undefined; readonly scoringConfig: SeasonScoringConfig | undefined; readonly dropPolicy: SeasonDropPolicy | undefined; readonly stewardingConfig: SeasonStewardingConfig | undefined; readonly maxDrivers: number | undefined; private constructor(props: { id: string; leagueId: string; gameId: string; name: string; year?: number; order?: number; status: SeasonStatus; startDate?: Date; endDate?: Date; schedule?: SeasonSchedule; scoringConfig?: SeasonScoringConfig; dropPolicy?: SeasonDropPolicy; stewardingConfig?: SeasonStewardingConfig; maxDrivers?: number; }) { this.id = props.id; this.leagueId = props.leagueId; this.gameId = props.gameId; this.name = props.name; this.year = props.year; this.order = props.order; this.status = props.status; this.startDate = props.startDate; this.endDate = props.endDate; this.schedule = props.schedule; this.scoringConfig = props.scoringConfig; this.dropPolicy = props.dropPolicy; this.stewardingConfig = props.stewardingConfig; this.maxDrivers = props.maxDrivers; } static create(props: { id: string; leagueId: string; gameId: string; name: string; year?: number; order?: number; status?: SeasonStatus; startDate?: Date; endDate?: Date; schedule?: SeasonSchedule; scoringConfig?: SeasonScoringConfig; dropPolicy?: SeasonDropPolicy; stewardingConfig?: SeasonStewardingConfig; maxDrivers?: number; }): Season { if (!props.id || props.id.trim().length === 0) { throw new RacingDomainValidationError('Season ID is required'); } if (!props.leagueId || props.leagueId.trim().length === 0) { throw new RacingDomainValidationError('Season leagueId is required'); } if (!props.gameId || props.gameId.trim().length === 0) { throw new RacingDomainValidationError('Season gameId is required'); } if (!props.name || props.name.trim().length === 0) { throw new RacingDomainValidationError('Season name is required'); } const status: SeasonStatus = props.status ?? 'planned'; return new Season({ id: props.id, leagueId: props.leagueId, gameId: props.gameId, name: props.name, ...(props.year !== undefined ? { year: props.year } : {}), ...(props.order !== undefined ? { order: props.order } : {}), status, ...(props.startDate !== undefined ? { startDate: props.startDate } : {}), ...(props.endDate !== undefined ? { endDate: props.endDate } : {}), ...(props.schedule !== undefined ? { schedule: props.schedule } : {}), ...(props.scoringConfig !== undefined ? { scoringConfig: props.scoringConfig } : {}), ...(props.dropPolicy !== undefined ? { dropPolicy: props.dropPolicy } : {}), ...(props.stewardingConfig !== undefined ? { stewardingConfig: props.stewardingConfig } : {}), ...(props.maxDrivers !== undefined ? { maxDrivers: props.maxDrivers } : {}), }); } /** * Domain rule: Wallet withdrawals are only allowed when season is completed */ canWithdrawFromWallet(): boolean { return this.status === 'completed'; } /** * Check if season is active */ isActive(): boolean { return this.status === 'active'; } /** * Check if season is completed */ isCompleted(): boolean { return this.status === 'completed'; } /** * Check if season is planned (not yet active) */ isPlanned(): boolean { return this.status === 'planned'; } /** * Activate the season from planned state. */ activate(): Season { if (this.status !== 'planned') { throw new RacingDomainInvariantError( 'Only planned seasons can be activated', ); } return Season.create({ id: this.id, leagueId: this.leagueId, gameId: this.gameId, name: this.name, ...(this.year !== undefined ? { year: this.year } : {}), ...(this.order !== undefined ? { order: this.order } : {}), status: 'active', ...(this.startDate !== undefined ? { startDate: this.startDate } : { startDate: new Date(), }), ...(this.endDate !== undefined ? { endDate: this.endDate } : {}), ...(this.schedule !== undefined ? { schedule: this.schedule } : {}), ...(this.scoringConfig !== undefined ? { scoringConfig: this.scoringConfig } : {}), ...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}), ...(this.stewardingConfig !== undefined ? { stewardingConfig: this.stewardingConfig } : {}), ...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}), }); } /** * Mark the season as completed. */ complete(): Season { if (this.status !== 'active') { throw new RacingDomainInvariantError( 'Only active seasons can be completed', ); } return Season.create({ id: this.id, leagueId: this.leagueId, gameId: this.gameId, name: this.name, ...(this.year !== undefined ? { year: this.year } : {}), ...(this.order !== undefined ? { order: this.order } : {}), status: 'completed', ...(this.startDate !== undefined ? { startDate: this.startDate } : {}), ...(this.endDate !== undefined ? { endDate: this.endDate } : { endDate: new Date(), }), ...(this.schedule !== undefined ? { schedule: this.schedule } : {}), ...(this.scoringConfig !== undefined ? { scoringConfig: this.scoringConfig } : {}), ...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}), ...(this.stewardingConfig !== undefined ? { stewardingConfig: this.stewardingConfig } : {}), ...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}), }); } /** * Archive a completed season. */ archive(): Season { if (!this.isCompleted()) { throw new RacingDomainInvariantError( 'Only completed seasons can be archived', ); } return Season.create({ id: this.id, leagueId: this.leagueId, gameId: this.gameId, name: this.name, ...(this.year !== undefined ? { year: this.year } : {}), ...(this.order !== undefined ? { order: this.order } : {}), status: 'archived', ...(this.startDate !== undefined ? { startDate: this.startDate } : {}), ...(this.endDate !== undefined ? { endDate: this.endDate } : {}), ...(this.schedule !== undefined ? { schedule: this.schedule } : {}), ...(this.scoringConfig !== undefined ? { scoringConfig: this.scoringConfig } : {}), ...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}), ...(this.stewardingConfig !== undefined ? { stewardingConfig: this.stewardingConfig } : {}), ...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}), }); } /** * Cancel a planned or active season. */ cancel(): Season { if (this.status === 'completed' || this.status === 'archived') { throw new RacingDomainInvariantError( 'Cannot cancel a completed or archived season', ); } if (this.status === 'cancelled') { return this; } return Season.create({ id: this.id, leagueId: this.leagueId, gameId: this.gameId, name: this.name, ...(this.year !== undefined ? { year: this.year } : {}), ...(this.order !== undefined ? { order: this.order } : {}), status: 'cancelled', ...(this.startDate !== undefined ? { startDate: this.startDate } : {}), ...(this.endDate !== undefined ? { endDate: this.endDate } : { endDate: new Date(), }), ...(this.schedule !== undefined ? { schedule: this.schedule } : {}), ...(this.scoringConfig !== undefined ? { scoringConfig: this.scoringConfig } : {}), ...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}), ...(this.stewardingConfig !== undefined ? { stewardingConfig: this.stewardingConfig } : {}), ...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}), }); } /** * Update schedule while keeping other properties intact. */ withSchedule(schedule: SeasonSchedule): Season { return Season.create({ id: this.id, leagueId: this.leagueId, gameId: this.gameId, name: this.name, ...(this.year !== undefined ? { year: this.year } : {}), ...(this.order !== undefined ? { order: this.order } : {}), status: this.status, ...(this.startDate !== undefined ? { startDate: this.startDate } : {}), ...(this.endDate !== undefined ? { endDate: this.endDate } : {}), schedule, ...(this.scoringConfig !== undefined ? { scoringConfig: this.scoringConfig } : {}), ...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}), ...(this.stewardingConfig !== undefined ? { stewardingConfig: this.stewardingConfig } : {}), ...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}), }); } /** * Update scoring configuration for the season. */ withScoringConfig(scoringConfig: SeasonScoringConfig): Season { return Season.create({ id: this.id, leagueId: this.leagueId, gameId: this.gameId, name: this.name, ...(this.year !== undefined ? { year: this.year } : {}), ...(this.order !== undefined ? { order: this.order } : {}), status: this.status, ...(this.startDate !== undefined ? { startDate: this.startDate } : {}), ...(this.endDate !== undefined ? { endDate: this.endDate } : {}), ...(this.schedule !== undefined ? { schedule: this.schedule } : {}), scoringConfig, ...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}), ...(this.stewardingConfig !== undefined ? { stewardingConfig: this.stewardingConfig } : {}), ...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}), }); } /** * Update drop policy for the season. */ withDropPolicy(dropPolicy: SeasonDropPolicy): Season { return Season.create({ id: this.id, leagueId: this.leagueId, gameId: this.gameId, name: this.name, ...(this.year !== undefined ? { year: this.year } : {}), ...(this.order !== undefined ? { order: this.order } : {}), status: this.status, ...(this.startDate !== undefined ? { startDate: this.startDate } : {}), ...(this.endDate !== undefined ? { endDate: this.endDate } : {}), ...(this.schedule !== undefined ? { schedule: this.schedule } : {}), ...(this.scoringConfig !== undefined ? { scoringConfig: this.scoringConfig } : {}), dropPolicy, ...(this.stewardingConfig !== undefined ? { stewardingConfig: this.stewardingConfig } : {}), ...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}), }); } /** * Update stewarding configuration for the season. */ withStewardingConfig(stewardingConfig: SeasonStewardingConfig): Season { return Season.create({ id: this.id, leagueId: this.leagueId, gameId: this.gameId, name: this.name, ...(this.year !== undefined ? { year: this.year } : {}), ...(this.order !== undefined ? { order: this.order } : {}), status: this.status, ...(this.startDate !== undefined ? { startDate: this.startDate } : {}), ...(this.endDate !== undefined ? { endDate: this.endDate } : {}), ...(this.schedule !== undefined ? { schedule: this.schedule } : {}), ...(this.scoringConfig !== undefined ? { scoringConfig: this.scoringConfig } : {}), ...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}), stewardingConfig, ...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}), }); } /** * Update max driver capacity for the season. */ withMaxDrivers(maxDrivers: number | undefined): Season { if (maxDrivers !== undefined && maxDrivers <= 0) { throw new RacingDomainValidationError( 'Season maxDrivers must be greater than 0 when provided', ); } return Season.create({ id: this.id, leagueId: this.leagueId, gameId: this.gameId, name: this.name, ...(this.year !== undefined ? { year: this.year } : {}), ...(this.order !== undefined ? { order: this.order } : {}), status: this.status, ...(this.startDate !== undefined ? { startDate: this.startDate } : {}), ...(this.endDate !== undefined ? { endDate: this.endDate } : {}), ...(this.schedule !== undefined ? { schedule: this.schedule } : {}), ...(this.scoringConfig !== undefined ? { scoringConfig: this.scoringConfig } : {}), ...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}), ...(this.stewardingConfig !== undefined ? { stewardingConfig: this.stewardingConfig } : {}), ...(maxDrivers !== undefined ? { maxDrivers } : {}), }); } }