Files
gridpilot.gg/core/racing/domain/value-objects/SeasonStewardingConfig.ts
2025-12-16 11:52:26 +01:00

142 lines
4.9 KiB
TypeScript

import { RacingDomainValidationError } from '../errors/RacingDomainError';
import type { IValueObject } from '@core/shared/domain';
import type { StewardingDecisionMode } from '../entities/League';
export interface SeasonStewardingConfigProps {
decisionMode: StewardingDecisionMode;
requiredVotes?: number | undefined;
requireDefense: boolean;
defenseTimeLimit: number;
voteTimeLimit: number;
protestDeadlineHours: number;
stewardingClosesHours: number;
notifyAccusedOnProtest: boolean;
notifyOnVoteRequired: boolean;
}
/**
* Value Object: SeasonStewardingConfig
*
* Encapsulates stewarding configuration owned by a Season.
* Shape intentionally mirrors LeagueStewardingFormDTO used by the wizard.
*/
export class SeasonStewardingConfig
implements IValueObject<SeasonStewardingConfigProps>
{
readonly decisionMode: StewardingDecisionMode;
readonly requiredVotes?: number;
readonly requireDefense: boolean;
readonly defenseTimeLimit: number;
readonly voteTimeLimit: number;
readonly protestDeadlineHours: number;
readonly stewardingClosesHours: number;
readonly notifyAccusedOnProtest: boolean;
readonly notifyOnVoteRequired: boolean;
constructor(props: SeasonStewardingConfigProps) {
if (!props.decisionMode) {
throw new RacingDomainValidationError(
'SeasonStewardingConfig.decisionMode is required',
);
}
if (
(props.decisionMode === 'steward_vote' ||
props.decisionMode === 'member_vote' ||
props.decisionMode === 'steward_veto' ||
props.decisionMode === 'member_veto') &&
(props.requiredVotes === undefined ||
!Number.isInteger(props.requiredVotes) ||
props.requiredVotes <= 0)
) {
throw new RacingDomainValidationError(
'SeasonStewardingConfig.requiredVotes must be a positive integer for voting/veto modes',
);
}
// For non-voting modes, requiredVotes should not be provided
if (props.decisionMode !== 'steward_vote' &&
props.decisionMode !== 'member_vote' &&
props.decisionMode !== 'steward_veto' &&
props.decisionMode !== 'member_veto' &&
props.requiredVotes !== undefined) {
throw new RacingDomainValidationError(
'SeasonStewardingConfig.requiredVotes should only be provided for voting/veto modes',
);
}
if (!Number.isInteger(props.defenseTimeLimit) || props.defenseTimeLimit < 0) {
throw new RacingDomainValidationError(
'SeasonStewardingConfig.defenseTimeLimit must be a non-negative integer (hours)',
);
}
if (!Number.isInteger(props.voteTimeLimit) || props.voteTimeLimit <= 0) {
throw new RacingDomainValidationError(
'SeasonStewardingConfig.voteTimeLimit must be a positive integer (hours)',
);
}
if (
!Number.isInteger(props.protestDeadlineHours) ||
props.protestDeadlineHours <= 0
) {
throw new RacingDomainValidationError(
'SeasonStewardingConfig.protestDeadlineHours must be a positive integer (hours)',
);
}
if (
!Number.isInteger(props.stewardingClosesHours) ||
props.stewardingClosesHours <= 0
) {
throw new RacingDomainValidationError(
'SeasonStewardingConfig.stewardingClosesHours must be a positive integer (hours)',
);
}
this.decisionMode = props.decisionMode;
if (props.requiredVotes !== undefined) {
this.requiredVotes = props.requiredVotes;
}
this.requireDefense = props.requireDefense;
this.defenseTimeLimit = props.defenseTimeLimit;
this.voteTimeLimit = props.voteTimeLimit;
this.protestDeadlineHours = props.protestDeadlineHours;
this.stewardingClosesHours = props.stewardingClosesHours;
this.notifyAccusedOnProtest = props.notifyAccusedOnProtest;
this.notifyOnVoteRequired = props.notifyOnVoteRequired;
}
get props(): SeasonStewardingConfigProps {
return {
decisionMode: this.decisionMode,
...(this.requiredVotes !== undefined
? { requiredVotes: this.requiredVotes }
: {}),
requireDefense: this.requireDefense,
defenseTimeLimit: this.defenseTimeLimit,
voteTimeLimit: this.voteTimeLimit,
protestDeadlineHours: this.protestDeadlineHours,
stewardingClosesHours: this.stewardingClosesHours,
notifyAccusedOnProtest: this.notifyAccusedOnProtest,
notifyOnVoteRequired: this.notifyOnVoteRequired,
};
}
equals(other: IValueObject<SeasonStewardingConfigProps>): boolean {
const a = this.props;
const b = other.props;
return (
a.decisionMode === b.decisionMode &&
a.requiredVotes === b.requiredVotes &&
a.requireDefense === b.requireDefense &&
a.defenseTimeLimit === b.defenseTimeLimit &&
a.voteTimeLimit === b.voteTimeLimit &&
a.protestDeadlineHours === b.protestDeadlineHours &&
a.stewardingClosesHours === b.stewardingClosesHours &&
a.notifyAccusedOnProtest === b.notifyAccusedOnProtest &&
a.notifyOnVoteRequired === b.notifyOnVoteRequired
);
}
}