/** * Domain Entity: Prize * * Represents a prize awarded to a driver for a specific position in a season. */ import { RacingDomainValidationError, RacingDomainInvariantError } from '../../errors/RacingDomainError'; import type { IEntity } from '@core/shared/domain'; import type { Money } from '../../value-objects/Money'; import { Position } from '../championship/Position'; import { PrizeId } from './PrizeId'; import { PrizeStatus } from './PrizeStatus'; import { SeasonId } from '../SeasonId'; import { DriverId } from '../DriverId'; export interface PrizeProps { id: PrizeId; seasonId: SeasonId; position: Position; amount: Money; driverId: DriverId | undefined; status: PrizeStatus; createdAt: Date; awardedAt: Date | undefined; paidAt: Date | undefined; description: string | undefined; } export class Prize implements IEntity { readonly id: PrizeId; readonly seasonId: SeasonId; readonly position: Position; readonly amount: Money; readonly driverId: DriverId | undefined; readonly status: PrizeStatus; readonly createdAt: Date; readonly awardedAt: Date | undefined; readonly paidAt: Date | undefined; readonly description: string | undefined; private constructor(props: PrizeProps) { this.id = props.id; this.seasonId = props.seasonId; this.position = props.position; this.amount = props.amount; this.driverId = props.driverId; this.status = props.status; this.createdAt = props.createdAt; this.awardedAt = props.awardedAt; this.paidAt = props.paidAt; this.description = props.description; } static create(props: Omit & { id: string; seasonId: string; position: number; createdAt?: Date; status?: string; driverId?: string; awardedAt?: Date; paidAt?: Date; description?: string; }): Prize { const fullProps: Omit = { id: PrizeId.create(props.id), seasonId: SeasonId.create(props.seasonId), position: Position.create(props.position), amount: props.amount, driverId: props.driverId ? DriverId.create(props.driverId) : undefined, awardedAt: props.awardedAt, paidAt: props.paidAt, description: props.description, }; this.validate(fullProps); return new Prize({ ...fullProps, createdAt: props.createdAt ?? new Date(), status: PrizeStatus.create(props.status ?? 'pending'), }); } private static validate(props: Omit): void { if (!props.amount) { throw new RacingDomainValidationError('Prize amount is required'); } if (props.amount.amount <= 0) { throw new RacingDomainValidationError('Prize amount must be greater than zero'); } } /** * Award prize to a driver */ awardTo(driverId: string): Prize { if (!driverId || driverId.trim().length === 0) { throw new RacingDomainValidationError('Driver ID is required to award prize'); } if (this.status.toString() !== 'pending') { throw new RacingDomainInvariantError('Only pending prizes can be awarded'); } return new Prize({ ...this, driverId: DriverId.create(driverId), status: PrizeStatus.create('awarded'), awardedAt: new Date(), }); } /** * Mark prize as paid */ markAsPaid(): Prize { if (this.status.toString() !== 'awarded') { throw new RacingDomainInvariantError('Only awarded prizes can be marked as paid'); } if (!this.driverId) { throw new RacingDomainInvariantError('Prize must have a driver to be paid'); } return new Prize({ ...this, status: PrizeStatus.create('paid'), paidAt: new Date(), }); } /** * Cancel prize */ cancel(): Prize { if (this.status.toString() === 'paid') { throw new RacingDomainInvariantError('Cannot cancel a paid prize'); } return new Prize({ ...this, status: PrizeStatus.create('cancelled'), }); } /** * Check if prize is pending */ isPending(): boolean { return this.status.toString() === 'pending'; } /** * Check if prize is awarded */ isAwarded(): boolean { return this.status.toString() === 'awarded'; } /** * Check if prize is paid */ isPaid(): boolean { return this.status.toString() === 'paid'; } }