121 lines
2.8 KiB
TypeScript
121 lines
2.8 KiB
TypeScript
/**
|
|
* Domain Value Object: ParticipantCount
|
|
*
|
|
* Represents the number of participants in a league or race.
|
|
* Enforces constraints based on league visibility and other business rules.
|
|
*/
|
|
|
|
import type { IValueObject } from '@core/shared/domain';
|
|
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
|
|
|
export interface ParticipantCountProps {
|
|
value: number;
|
|
}
|
|
|
|
export class ParticipantCount implements IValueObject<ParticipantCountProps> {
|
|
readonly value: number;
|
|
|
|
private constructor(value: number) {
|
|
this.value = value;
|
|
}
|
|
|
|
static create(value: number): ParticipantCount {
|
|
if (!Number.isInteger(value) || value < 0) {
|
|
throw new RacingDomainValidationError('Participant count must be a non-negative integer');
|
|
}
|
|
return new ParticipantCount(value);
|
|
}
|
|
|
|
/**
|
|
* Validate against minimum requirements for ranked leagues
|
|
*/
|
|
validateForRankedLeague(): { valid: boolean; error?: string } {
|
|
if (this.value < 10) {
|
|
return {
|
|
valid: false,
|
|
error: 'Ranked leagues require at least 10 participants'
|
|
};
|
|
}
|
|
return { valid: true };
|
|
}
|
|
|
|
/**
|
|
* Validate against minimum requirements for unranked leagues
|
|
*/
|
|
validateForUnrankedLeague(): { valid: boolean; error?: string } {
|
|
if (this.value < 2) {
|
|
return {
|
|
valid: false,
|
|
error: 'Unranked leagues require at least 2 participants'
|
|
};
|
|
}
|
|
return { valid: true };
|
|
}
|
|
|
|
/**
|
|
* Validate against maximum capacity
|
|
*/
|
|
validateAgainstMax(maxParticipants: number): { valid: boolean; error?: string } {
|
|
if (this.value > maxParticipants) {
|
|
return {
|
|
valid: false,
|
|
error: `Participant count (${this.value}) exceeds maximum capacity (${maxParticipants})`
|
|
};
|
|
}
|
|
return { valid: true };
|
|
}
|
|
|
|
/**
|
|
* Check if count meets minimum for given visibility type
|
|
*/
|
|
meetsMinimumForVisibility(isRanked: boolean): boolean {
|
|
return isRanked ? this.value >= 10 : this.value >= 2;
|
|
}
|
|
|
|
/**
|
|
* Increment count by 1
|
|
*/
|
|
increment(): ParticipantCount {
|
|
return new ParticipantCount(this.value + 1);
|
|
}
|
|
|
|
/**
|
|
* Decrement count by 1 (if > 0)
|
|
*/
|
|
decrement(): ParticipantCount {
|
|
if (this.value === 0) {
|
|
throw new RacingDomainValidationError('Cannot decrement below zero');
|
|
}
|
|
return new ParticipantCount(this.value - 1);
|
|
}
|
|
|
|
/**
|
|
* Check if count is zero
|
|
*/
|
|
isZero(): boolean {
|
|
return this.value === 0;
|
|
}
|
|
|
|
/**
|
|
* Check if count is at least the given minimum
|
|
*/
|
|
isAtLeast(min: number): boolean {
|
|
return this.value >= min;
|
|
}
|
|
|
|
get props(): ParticipantCountProps {
|
|
return { value: this.value };
|
|
}
|
|
|
|
equals(other: IValueObject<ParticipantCountProps>): boolean {
|
|
return this.value === other.props.value;
|
|
}
|
|
|
|
toString(): string {
|
|
return this.value.toString();
|
|
}
|
|
|
|
toNumber(): number {
|
|
return this.value;
|
|
}
|
|
} |