/** * Domain Entity: SeasonSponsorship * * Represents a sponsorship relationship between a Sponsor and a Season. * Aggregate root for managing sponsorship slots and pricing. */ import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError'; import type { IEntity } from '@gridpilot/shared/domain'; import type { Money } from '../value-objects/Money'; export type SponsorshipTier = 'main' | 'secondary'; export type SponsorshipStatus = 'pending' | 'active' | 'ended' | 'cancelled'; export interface SeasonSponsorshipProps { id: string; seasonId: string; /** * Optional denormalized leagueId for fast league-level aggregations. * Must always match the owning Season's leagueId when present. */ leagueId?: string; sponsorId: string; tier: SponsorshipTier; pricing: Money; status: SponsorshipStatus; createdAt: Date; activatedAt?: Date; endedAt?: Date; cancelledAt?: Date; description?: string; } export class SeasonSponsorship implements IEntity { readonly id: string; readonly seasonId: string; readonly leagueId: string | undefined; readonly sponsorId: string; readonly tier: SponsorshipTier; readonly pricing: Money; readonly status: SponsorshipStatus; readonly createdAt: Date; readonly activatedAt: Date | undefined; readonly endedAt: Date | undefined; readonly cancelledAt: Date | undefined; readonly description: string | undefined; private constructor(props: SeasonSponsorshipProps) { this.id = props.id; this.seasonId = props.seasonId; this.leagueId = props.leagueId; this.sponsorId = props.sponsorId; this.tier = props.tier; this.pricing = props.pricing; this.status = props.status; this.createdAt = props.createdAt; this.activatedAt = props.activatedAt; this.endedAt = props.endedAt; this.cancelledAt = props.cancelledAt; this.description = props.description; } static create(props: Omit & { createdAt?: Date; status?: SponsorshipStatus; }): SeasonSponsorship { this.validate(props); return new SeasonSponsorship({ id: props.id, seasonId: props.seasonId, ...(props.leagueId !== undefined ? { leagueId: props.leagueId } : {}), sponsorId: props.sponsorId, tier: props.tier, pricing: props.pricing, status: props.status ?? 'pending', createdAt: props.createdAt ?? new Date(), ...(props.activatedAt !== undefined ? { activatedAt: props.activatedAt } : {}), ...(props.endedAt !== undefined ? { endedAt: props.endedAt } : {}), ...(props.cancelledAt !== undefined ? { cancelledAt: props.cancelledAt } : {}), ...(props.description !== undefined ? { description: props.description } : {}), }); } private static validate(props: Omit): void { if (!props.id || props.id.trim().length === 0) { throw new RacingDomainValidationError('SeasonSponsorship ID is required'); } if (!props.seasonId || props.seasonId.trim().length === 0) { throw new RacingDomainValidationError('SeasonSponsorship seasonId is required'); } if (!props.sponsorId || props.sponsorId.trim().length === 0) { throw new RacingDomainValidationError('SeasonSponsorship sponsorId is required'); } if (!props.tier) { throw new RacingDomainValidationError('SeasonSponsorship tier is required'); } if (!props.pricing) { throw new RacingDomainValidationError('SeasonSponsorship pricing is required'); } if (props.pricing.amount <= 0) { throw new RacingDomainValidationError('SeasonSponsorship pricing must be greater than zero'); } } /** * Activate the sponsorship */ activate(): SeasonSponsorship { if (this.status === 'active') { throw new RacingDomainInvariantError('SeasonSponsorship is already active'); } if (this.status === 'cancelled') { throw new RacingDomainInvariantError('Cannot activate a cancelled SeasonSponsorship'); } if (this.status === 'ended') { throw new RacingDomainInvariantError('Cannot activate an ended SeasonSponsorship'); } const base: SeasonSponsorshipProps = { id: this.id, seasonId: this.seasonId, ...(this.leagueId !== undefined ? { leagueId: this.leagueId } : {}), sponsorId: this.sponsorId, tier: this.tier, pricing: this.pricing, status: 'active', createdAt: this.createdAt, activatedAt: new Date(), ...(this.endedAt !== undefined ? { endedAt: this.endedAt } : {}), ...(this.cancelledAt !== undefined ? { cancelledAt: this.cancelledAt } : {}), }; const next: SeasonSponsorshipProps = this.description !== undefined ? { ...base, description: this.description } : base; return new SeasonSponsorship(next); } /** * Mark the sponsorship as ended (completed term) */ end(): SeasonSponsorship { if (this.status === 'cancelled') { throw new RacingDomainInvariantError('Cannot end a cancelled SeasonSponsorship'); } if (this.status === 'ended') { throw new RacingDomainInvariantError('SeasonSponsorship is already ended'); } const base: SeasonSponsorshipProps = { id: this.id, seasonId: this.seasonId, ...(this.leagueId !== undefined ? { leagueId: this.leagueId } : {}), sponsorId: this.sponsorId, tier: this.tier, pricing: this.pricing, status: 'ended', createdAt: this.createdAt, ...(this.activatedAt !== undefined ? { activatedAt: this.activatedAt } : {}), endedAt: new Date(), ...(this.cancelledAt !== undefined ? { cancelledAt: this.cancelledAt } : {}), }; const next: SeasonSponsorshipProps = this.description !== undefined ? { ...base, description: this.description } : base; return new SeasonSponsorship(next); } /** * Cancel the sponsorship */ cancel(): SeasonSponsorship { if (this.status === 'cancelled') { throw new RacingDomainInvariantError('SeasonSponsorship is already cancelled'); } const base: SeasonSponsorshipProps = { id: this.id, seasonId: this.seasonId, ...(this.leagueId !== undefined ? { leagueId: this.leagueId } : {}), sponsorId: this.sponsorId, tier: this.tier, pricing: this.pricing, status: 'cancelled', createdAt: this.createdAt, ...(this.activatedAt !== undefined ? { activatedAt: this.activatedAt } : {}), ...(this.endedAt !== undefined ? { endedAt: this.endedAt } : {}), cancelledAt: new Date(), }; const next: SeasonSponsorshipProps = this.description !== undefined ? { ...base, description: this.description } : base; return new SeasonSponsorship(next); } /** * Update pricing/terms when allowed */ withPricing(pricing: Money): SeasonSponsorship { if (pricing.amount <= 0) { throw new RacingDomainValidationError('SeasonSponsorship pricing must be greater than zero'); } if (this.status === 'cancelled' || this.status === 'ended') { throw new RacingDomainInvariantError('Cannot update pricing for ended or cancelled SeasonSponsorship'); } const base: SeasonSponsorshipProps = { id: this.id, seasonId: this.seasonId, ...(this.leagueId !== undefined ? { leagueId: this.leagueId } : {}), sponsorId: this.sponsorId, tier: this.tier, pricing, status: this.status, createdAt: this.createdAt, ...(this.activatedAt !== undefined ? { activatedAt: this.activatedAt } : {}), ...(this.endedAt !== undefined ? { endedAt: this.endedAt } : {}), ...(this.cancelledAt !== undefined ? { cancelledAt: this.cancelledAt } : {}), }; const next: SeasonSponsorshipProps = this.description !== undefined ? { ...base, description: this.description } : base; return new SeasonSponsorship(next); } /** * Check if sponsorship is active */ isActive(): boolean { return this.status === 'active'; } /** * Get the platform fee for this sponsorship */ getPlatformFee(): Money { return this.pricing.calculatePlatformFee(); } /** * Get the net amount after platform fee */ getNetAmount(): Money { return this.pricing.calculateNetAmount(); } }