rename to core
This commit is contained in:
172
core/racing/domain/entities/Prize.ts
Normal file
172
core/racing/domain/entities/Prize.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 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 '@gridpilot/shared/domain';
|
||||
|
||||
import type { Money } from '../value-objects/Money';
|
||||
|
||||
export type PrizeStatus = 'pending' | 'awarded' | 'paid' | 'cancelled';
|
||||
|
||||
export interface PrizeProps {
|
||||
id: string;
|
||||
seasonId: string;
|
||||
position: number;
|
||||
amount: Money;
|
||||
driverId: string;
|
||||
status: PrizeStatus;
|
||||
createdAt: Date;
|
||||
awardedAt: Date | undefined;
|
||||
paidAt: Date | undefined;
|
||||
description: string | undefined;
|
||||
}
|
||||
|
||||
export class Prize implements IEntity<string> {
|
||||
readonly id: string;
|
||||
readonly seasonId: string;
|
||||
readonly position: number;
|
||||
readonly amount: Money;
|
||||
readonly driverId: string;
|
||||
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 ?? new Date();
|
||||
this.awardedAt = props.awardedAt;
|
||||
this.paidAt = props.paidAt;
|
||||
this.description = props.description;
|
||||
}
|
||||
|
||||
static create(props: Omit<PrizeProps, 'createdAt' | 'status' | 'driverId' | 'awardedAt' | 'paidAt' | 'description'> & {
|
||||
createdAt?: Date;
|
||||
status?: PrizeStatus;
|
||||
driverId?: string;
|
||||
awardedAt?: Date;
|
||||
paidAt?: Date;
|
||||
description?: string;
|
||||
}): Prize {
|
||||
const fullProps: Omit<PrizeProps, 'createdAt' | 'status'> = {
|
||||
...props,
|
||||
driverId: props.driverId ?? '',
|
||||
awardedAt: props.awardedAt,
|
||||
paidAt: props.paidAt,
|
||||
description: props.description,
|
||||
};
|
||||
|
||||
this.validate(fullProps);
|
||||
|
||||
return new Prize({
|
||||
...fullProps,
|
||||
createdAt: props.createdAt ?? new Date(),
|
||||
status: props.status ?? 'pending',
|
||||
});
|
||||
}
|
||||
|
||||
private static validate(props: Omit<PrizeProps, 'createdAt' | 'status'>): void {
|
||||
if (!props.id || props.id.trim().length === 0) {
|
||||
throw new RacingDomainValidationError('Prize ID is required');
|
||||
}
|
||||
|
||||
if (!props.seasonId || props.seasonId.trim().length === 0) {
|
||||
throw new RacingDomainValidationError('Prize seasonId is required');
|
||||
}
|
||||
|
||||
if (!Number.isInteger(props.position) || props.position < 1) {
|
||||
throw new RacingDomainValidationError('Prize position must be a positive integer');
|
||||
}
|
||||
|
||||
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 !== 'pending') {
|
||||
throw new RacingDomainInvariantError('Only pending prizes can be awarded');
|
||||
}
|
||||
|
||||
return new Prize({
|
||||
...this,
|
||||
driverId,
|
||||
status: 'awarded',
|
||||
awardedAt: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark prize as paid
|
||||
*/
|
||||
markAsPaid(): Prize {
|
||||
if (this.status !== 'awarded') {
|
||||
throw new RacingDomainInvariantError('Only awarded prizes can be marked as paid');
|
||||
}
|
||||
|
||||
if (!this.driverId || this.driverId.trim() === '') {
|
||||
throw new RacingDomainInvariantError('Prize must have a driver to be paid');
|
||||
}
|
||||
|
||||
return new Prize({
|
||||
...this,
|
||||
status: 'paid',
|
||||
paidAt: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel prize
|
||||
*/
|
||||
cancel(): Prize {
|
||||
if (this.status === 'paid') {
|
||||
throw new RacingDomainInvariantError('Cannot cancel a paid prize');
|
||||
}
|
||||
|
||||
return new Prize({
|
||||
...this,
|
||||
status: 'cancelled',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if prize is pending
|
||||
*/
|
||||
isPending(): boolean {
|
||||
return this.status === 'pending';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if prize is awarded
|
||||
*/
|
||||
isAwarded(): boolean {
|
||||
return this.status === 'awarded';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if prize is paid
|
||||
*/
|
||||
isPaid(): boolean {
|
||||
return this.status === 'paid';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user