169 lines
4.4 KiB
TypeScript
169 lines
4.4 KiB
TypeScript
/**
|
|
* Domain Entity: Prize
|
|
*
|
|
* Represents a prize awarded to a driver for a specific position in a season.
|
|
*/
|
|
|
|
import { Entity } from '@core/shared/domain/Entity';
|
|
import { RacingDomainInvariantError, RacingDomainValidationError } from '../../errors/RacingDomainError';
|
|
|
|
import type { Money } from '../../value-objects/Money';
|
|
import { Position } from '../championship/Position';
|
|
import { DriverId } from '../DriverId';
|
|
import { SeasonId } from '../season/SeasonId';
|
|
import { PrizeId } from './PrizeId';
|
|
import { PrizeStatus } from './PrizeStatus';
|
|
|
|
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 extends Entity<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) {
|
|
super(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<PrizeProps, 'createdAt' | 'status' | 'driverId' | 'awardedAt' | 'paidAt' | 'description' | 'id' | 'seasonId' | 'position'> & {
|
|
id: string;
|
|
seasonId: string;
|
|
position: number;
|
|
createdAt?: Date;
|
|
status?: string;
|
|
driverId?: string;
|
|
awardedAt?: Date;
|
|
paidAt?: Date;
|
|
description?: string;
|
|
}): Prize {
|
|
const fullProps: Omit<PrizeProps, 'createdAt' | 'status'> = {
|
|
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<PrizeProps, 'createdAt' | 'status'>): 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';
|
|
}
|
|
} |