This commit is contained in:
2025-12-12 14:23:40 +01:00
parent 6a88fe93ab
commit 2cd3bfbb47
58 changed files with 2866 additions and 260 deletions

View File

@@ -26,8 +26,8 @@ export interface DriverLiveryProps {
userDecals: LiveryDecal[];
leagueOverrides: DecalOverride[];
createdAt: Date;
updatedAt?: Date;
validatedAt?: Date;
updatedAt: Date | undefined;
validatedAt: Date | undefined;
}
export class DriverLivery implements IEntity<string> {
@@ -39,8 +39,8 @@ export class DriverLivery implements IEntity<string> {
readonly userDecals: LiveryDecal[];
readonly leagueOverrides: DecalOverride[];
readonly createdAt: Date;
readonly updatedAt?: Date;
readonly validatedAt?: Date;
readonly updatedAt: Date | undefined;
readonly validatedAt: Date | undefined;
private constructor(props: DriverLiveryProps) {
this.id = props.id;
@@ -50,7 +50,7 @@ export class DriverLivery implements IEntity<string> {
this.uploadedImageUrl = props.uploadedImageUrl;
this.userDecals = props.userDecals;
this.leagueOverrides = props.leagueOverrides;
this.createdAt = props.createdAt;
this.createdAt = props.createdAt ?? new Date();
this.updatedAt = props.updatedAt;
this.validatedAt = props.validatedAt;
}
@@ -101,9 +101,16 @@ export class DriverLivery implements IEntity<string> {
}
return new DriverLivery({
...this,
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: [...this.userDecals, decal],
leagueOverrides: this.leagueOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
@@ -118,9 +125,16 @@ export class DriverLivery implements IEntity<string> {
}
return new DriverLivery({
...this,
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: updatedDecals,
leagueOverrides: this.leagueOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
@@ -131,16 +145,23 @@ export class DriverLivery implements IEntity<string> {
const index = this.userDecals.findIndex(d => d.id === decalId);
if (index === -1) {
throw new RacingDomainError('Decal not found in livery');
throw new RacingDomainValidationError('Decal not found in livery');
}
const updatedDecals = [...this.userDecals];
updatedDecals[index] = updatedDecal;
return new DriverLivery({
...this,
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: updatedDecals,
leagueOverrides: this.leagueOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
@@ -163,9 +184,16 @@ export class DriverLivery implements IEntity<string> {
}
return new DriverLivery({
...this,
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: this.userDecals,
leagueOverrides: updatedOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
@@ -178,9 +206,16 @@ export class DriverLivery implements IEntity<string> {
);
return new DriverLivery({
...this,
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: this.userDecals,
leagueOverrides: updatedOverrides,
createdAt: this.createdAt,
updatedAt: new Date(),
validatedAt: this.validatedAt,
});
}
@@ -198,7 +233,15 @@ export class DriverLivery implements IEntity<string> {
*/
markAsValidated(): DriverLivery {
return new DriverLivery({
...this,
id: this.id,
driverId: this.driverId,
gameId: this.gameId,
carId: this.carId,
uploadedImageUrl: this.uploadedImageUrl,
userDecals: this.userDecals,
leagueOverrides: this.leagueOverrides,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
validatedAt: new Date(),
});
}

View File

@@ -17,7 +17,7 @@ export interface LiveryTemplateProps {
baseImageUrl: string;
adminDecals: LiveryDecal[];
createdAt: Date;
updatedAt?: Date;
updatedAt: Date | undefined;
}
export class LiveryTemplate implements IEntity<string> {
@@ -28,7 +28,7 @@ export class LiveryTemplate implements IEntity<string> {
readonly baseImageUrl: string;
readonly adminDecals: LiveryDecal[];
readonly createdAt: Date;
readonly updatedAt?: Date;
readonly updatedAt: Date | undefined;
private constructor(props: LiveryTemplateProps) {
this.id = props.id;
@@ -37,7 +37,7 @@ export class LiveryTemplate implements IEntity<string> {
this.carId = props.carId;
this.baseImageUrl = props.baseImageUrl;
this.adminDecals = props.adminDecals;
this.createdAt = props.createdAt;
this.createdAt = props.createdAt ?? new Date();
this.updatedAt = props.updatedAt;
}
@@ -113,9 +113,9 @@ export class LiveryTemplate implements IEntity<string> {
*/
updateDecal(decalId: string, updatedDecal: LiveryDecal): LiveryTemplate {
const index = this.adminDecals.findIndex(d => d.id === decalId);
if (index === -1) {
throw new RacingDomainError('Decal not found in template');
throw new RacingDomainValidationError('Decal not found in template');
}
const updatedDecals = [...this.adminDecals];

View File

@@ -19,9 +19,9 @@ export interface PrizeProps {
driverId?: string;
status: PrizeStatus;
createdAt: Date;
awardedAt?: Date;
paidAt?: Date;
description?: string;
awardedAt: Date | undefined;
paidAt: Date | undefined;
description: string | undefined;
}
export class Prize implements IEntity<string> {
@@ -29,12 +29,12 @@ export class Prize implements IEntity<string> {
readonly seasonId: string;
readonly position: number;
readonly amount: Money;
readonly driverId?: string;
readonly driverId: string | undefined;
readonly status: PrizeStatus;
readonly createdAt: Date;
readonly awardedAt?: Date;
readonly paidAt?: Date;
readonly description?: string;
readonly awardedAt: Date | undefined;
readonly paidAt: Date | undefined;
readonly description: string | undefined;
private constructor(props: PrizeProps) {
this.id = props.id;
@@ -43,7 +43,7 @@ export class Prize implements IEntity<string> {
this.amount = props.amount;
this.driverId = props.driverId;
this.status = props.status;
this.createdAt = props.createdAt;
this.createdAt = props.createdAt ?? new Date();
this.awardedAt = props.awardedAt;
this.paidAt = props.paidAt;
this.description = props.description;

View File

@@ -1,8 +1,20 @@
export type SeasonStatus = 'planned' | 'active' | 'completed';
import { RacingDomainValidationError } from '../errors/RacingDomainError';
export type SeasonStatus =
| 'planned'
| 'active'
| 'completed'
| 'archived'
| 'cancelled';
import {
RacingDomainInvariantError,
RacingDomainValidationError,
} from '../errors/RacingDomainError';
import type { IEntity } from '@gridpilot/shared/domain';
import type { SeasonSchedule } from '../value-objects/SeasonSchedule';
import type { SeasonScoringConfig } from '../value-objects/SeasonScoringConfig';
import type { SeasonDropPolicy } from '../value-objects/SeasonDropPolicy';
import type { SeasonStewardingConfig } from '../value-objects/SeasonStewardingConfig';
export class Season implements IEntity<string> {
readonly id: string;
readonly leagueId: string;
@@ -13,6 +25,11 @@ export class Season implements IEntity<string> {
readonly status: SeasonStatus;
readonly startDate: Date | undefined;
readonly endDate: Date | undefined;
readonly schedule: SeasonSchedule | undefined;
readonly scoringConfig: SeasonScoringConfig | undefined;
readonly dropPolicy: SeasonDropPolicy | undefined;
readonly stewardingConfig: SeasonStewardingConfig | undefined;
readonly maxDrivers: number | undefined;
private constructor(props: {
id: string;
@@ -24,6 +41,11 @@ export class Season implements IEntity<string> {
status: SeasonStatus;
startDate?: Date;
endDate?: Date;
schedule?: SeasonSchedule;
scoringConfig?: SeasonScoringConfig;
dropPolicy?: SeasonDropPolicy;
stewardingConfig?: SeasonStewardingConfig;
maxDrivers?: number;
}) {
this.id = props.id;
this.leagueId = props.leagueId;
@@ -34,6 +56,11 @@ export class Season implements IEntity<string> {
this.status = props.status;
this.startDate = props.startDate;
this.endDate = props.endDate;
this.schedule = props.schedule;
this.scoringConfig = props.scoringConfig;
this.dropPolicy = props.dropPolicy;
this.stewardingConfig = props.stewardingConfig;
this.maxDrivers = props.maxDrivers;
}
static create(props: {
@@ -46,6 +73,11 @@ export class Season implements IEntity<string> {
status?: SeasonStatus;
startDate?: Date;
endDate?: Date;
schedule?: SeasonSchedule;
scoringConfig?: SeasonScoringConfig;
dropPolicy?: SeasonDropPolicy;
stewardingConfig?: SeasonStewardingConfig;
maxDrivers?: number;
}): Season {
if (!props.id || props.id.trim().length === 0) {
throw new RacingDomainValidationError('Season ID is required');
@@ -75,6 +107,15 @@ export class Season implements IEntity<string> {
status,
...(props.startDate !== undefined ? { startDate: props.startDate } : {}),
...(props.endDate !== undefined ? { endDate: props.endDate } : {}),
...(props.schedule !== undefined ? { schedule: props.schedule } : {}),
...(props.scoringConfig !== undefined
? { scoringConfig: props.scoringConfig }
: {}),
...(props.dropPolicy !== undefined ? { dropPolicy: props.dropPolicy } : {}),
...(props.stewardingConfig !== undefined
? { stewardingConfig: props.stewardingConfig }
: {}),
...(props.maxDrivers !== undefined ? { maxDrivers: props.maxDrivers } : {}),
});
}
@@ -98,4 +139,281 @@ export class Season implements IEntity<string> {
isCompleted(): boolean {
return this.status === 'completed';
}
/**
* Check if season is planned (not yet active)
*/
isPlanned(): boolean {
return this.status === 'planned';
}
/**
* Activate the season from planned state.
*/
activate(): Season {
if (this.status !== 'planned') {
throw new RacingDomainInvariantError(
'Only planned seasons can be activated',
);
}
return Season.create({
id: this.id,
leagueId: this.leagueId,
gameId: this.gameId,
name: this.name,
...(this.year !== undefined ? { year: this.year } : {}),
...(this.order !== undefined ? { order: this.order } : {}),
status: 'active',
...(this.startDate !== undefined ? { startDate: this.startDate } : {
startDate: new Date(),
}),
...(this.endDate !== undefined ? { endDate: this.endDate } : {}),
...(this.schedule !== undefined ? { schedule: this.schedule } : {}),
...(this.scoringConfig !== undefined
? { scoringConfig: this.scoringConfig }
: {}),
...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}),
...(this.stewardingConfig !== undefined
? { stewardingConfig: this.stewardingConfig }
: {}),
...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}),
});
}
/**
* Mark the season as completed.
*/
complete(): Season {
if (this.status !== 'active') {
throw new RacingDomainInvariantError(
'Only active seasons can be completed',
);
}
return Season.create({
id: this.id,
leagueId: this.leagueId,
gameId: this.gameId,
name: this.name,
...(this.year !== undefined ? { year: this.year } : {}),
...(this.order !== undefined ? { order: this.order } : {}),
status: 'completed',
...(this.startDate !== undefined ? { startDate: this.startDate } : {}),
...(this.endDate !== undefined ? { endDate: this.endDate } : {
endDate: new Date(),
}),
...(this.schedule !== undefined ? { schedule: this.schedule } : {}),
...(this.scoringConfig !== undefined
? { scoringConfig: this.scoringConfig }
: {}),
...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}),
...(this.stewardingConfig !== undefined
? { stewardingConfig: this.stewardingConfig }
: {}),
...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}),
});
}
/**
* Archive a completed season.
*/
archive(): Season {
if (!this.isCompleted()) {
throw new RacingDomainInvariantError(
'Only completed seasons can be archived',
);
}
return Season.create({
id: this.id,
leagueId: this.leagueId,
gameId: this.gameId,
name: this.name,
...(this.year !== undefined ? { year: this.year } : {}),
...(this.order !== undefined ? { order: this.order } : {}),
status: 'archived',
...(this.startDate !== undefined ? { startDate: this.startDate } : {}),
...(this.endDate !== undefined ? { endDate: this.endDate } : {}),
...(this.schedule !== undefined ? { schedule: this.schedule } : {}),
...(this.scoringConfig !== undefined
? { scoringConfig: this.scoringConfig }
: {}),
...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}),
...(this.stewardingConfig !== undefined
? { stewardingConfig: this.stewardingConfig }
: {}),
...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}),
});
}
/**
* Cancel a planned or active season.
*/
cancel(): Season {
if (this.status === 'completed' || this.status === 'archived') {
throw new RacingDomainInvariantError(
'Cannot cancel a completed or archived season',
);
}
if (this.status === 'cancelled') {
return this;
}
return Season.create({
id: this.id,
leagueId: this.leagueId,
gameId: this.gameId,
name: this.name,
...(this.year !== undefined ? { year: this.year } : {}),
...(this.order !== undefined ? { order: this.order } : {}),
status: 'cancelled',
...(this.startDate !== undefined ? { startDate: this.startDate } : {}),
...(this.endDate !== undefined ? { endDate: this.endDate } : {
endDate: new Date(),
}),
...(this.schedule !== undefined ? { schedule: this.schedule } : {}),
...(this.scoringConfig !== undefined
? { scoringConfig: this.scoringConfig }
: {}),
...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}),
...(this.stewardingConfig !== undefined
? { stewardingConfig: this.stewardingConfig }
: {}),
...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}),
});
}
/**
* Update schedule while keeping other properties intact.
*/
withSchedule(schedule: SeasonSchedule): Season {
return Season.create({
id: this.id,
leagueId: this.leagueId,
gameId: this.gameId,
name: this.name,
...(this.year !== undefined ? { year: this.year } : {}),
...(this.order !== undefined ? { order: this.order } : {}),
status: this.status,
...(this.startDate !== undefined ? { startDate: this.startDate } : {}),
...(this.endDate !== undefined ? { endDate: this.endDate } : {}),
schedule,
...(this.scoringConfig !== undefined
? { scoringConfig: this.scoringConfig }
: {}),
...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}),
...(this.stewardingConfig !== undefined
? { stewardingConfig: this.stewardingConfig }
: {}),
...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}),
});
}
/**
* Update scoring configuration for the season.
*/
withScoringConfig(scoringConfig: SeasonScoringConfig): Season {
return Season.create({
id: this.id,
leagueId: this.leagueId,
gameId: this.gameId,
name: this.name,
...(this.year !== undefined ? { year: this.year } : {}),
...(this.order !== undefined ? { order: this.order } : {}),
status: this.status,
...(this.startDate !== undefined ? { startDate: this.startDate } : {}),
...(this.endDate !== undefined ? { endDate: this.endDate } : {}),
...(this.schedule !== undefined ? { schedule: this.schedule } : {}),
scoringConfig,
...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}),
...(this.stewardingConfig !== undefined
? { stewardingConfig: this.stewardingConfig }
: {}),
...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}),
});
}
/**
* Update drop policy for the season.
*/
withDropPolicy(dropPolicy: SeasonDropPolicy): Season {
return Season.create({
id: this.id,
leagueId: this.leagueId,
gameId: this.gameId,
name: this.name,
...(this.year !== undefined ? { year: this.year } : {}),
...(this.order !== undefined ? { order: this.order } : {}),
status: this.status,
...(this.startDate !== undefined ? { startDate: this.startDate } : {}),
...(this.endDate !== undefined ? { endDate: this.endDate } : {}),
...(this.schedule !== undefined ? { schedule: this.schedule } : {}),
...(this.scoringConfig !== undefined
? { scoringConfig: this.scoringConfig }
: {}),
dropPolicy,
...(this.stewardingConfig !== undefined
? { stewardingConfig: this.stewardingConfig }
: {}),
...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}),
});
}
/**
* Update stewarding configuration for the season.
*/
withStewardingConfig(stewardingConfig: SeasonStewardingConfig): Season {
return Season.create({
id: this.id,
leagueId: this.leagueId,
gameId: this.gameId,
name: this.name,
...(this.year !== undefined ? { year: this.year } : {}),
...(this.order !== undefined ? { order: this.order } : {}),
status: this.status,
...(this.startDate !== undefined ? { startDate: this.startDate } : {}),
...(this.endDate !== undefined ? { endDate: this.endDate } : {}),
...(this.schedule !== undefined ? { schedule: this.schedule } : {}),
...(this.scoringConfig !== undefined
? { scoringConfig: this.scoringConfig }
: {}),
...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}),
stewardingConfig,
...(this.maxDrivers !== undefined ? { maxDrivers: this.maxDrivers } : {}),
});
}
/**
* Update max driver capacity for the season.
*/
withMaxDrivers(maxDrivers: number | undefined): Season {
if (maxDrivers !== undefined && maxDrivers <= 0) {
throw new RacingDomainValidationError(
'Season maxDrivers must be greater than 0 when provided',
);
}
return Season.create({
id: this.id,
leagueId: this.leagueId,
gameId: this.gameId,
name: this.name,
...(this.year !== undefined ? { year: this.year } : {}),
...(this.order !== undefined ? { order: this.order } : {}),
status: this.status,
...(this.startDate !== undefined ? { startDate: this.startDate } : {}),
...(this.endDate !== undefined ? { endDate: this.endDate } : {}),
...(this.schedule !== undefined ? { schedule: this.schedule } : {}),
...(this.scoringConfig !== undefined
? { scoringConfig: this.scoringConfig }
: {}),
...(this.dropPolicy !== undefined ? { dropPolicy: this.dropPolicy } : {}),
...(this.stewardingConfig !== undefined
? { stewardingConfig: this.stewardingConfig }
: {}),
...(maxDrivers !== undefined ? { maxDrivers } : {}),
});
}
}

View File

@@ -11,40 +11,53 @@ import type { IEntity } from '@gridpilot/shared/domain';
import type { Money } from '../value-objects/Money';
export type SponsorshipTier = 'main' | 'secondary';
export type SponsorshipStatus = 'pending' | 'active' | 'cancelled';
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<string> {
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;
}
@@ -57,12 +70,15 @@ export class SeasonSponsorship implements IEntity<string> {
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 } : {}),
});
}
@@ -105,15 +121,56 @@ export class SeasonSponsorship implements IEntity<string> {
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 =
@@ -135,22 +192,55 @@ export class SeasonSponsorship implements IEntity<string> {
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 withActivated =
this.activatedAt !== undefined
? { ...base, activatedAt: this.activatedAt }
: base;
const next: SeasonSponsorshipProps =
this.description !== undefined
? { ...withActivated, description: this.description }
: withActivated;
? { ...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);
}

View File

@@ -27,9 +27,9 @@ export interface TransactionProps {
netAmount: Money;
status: TransactionStatus;
createdAt: Date;
completedAt?: Date;
description?: string;
metadata?: Record<string, unknown>;
completedAt: Date | undefined;
description: string | undefined;
metadata: Record<string, unknown> | undefined;
}
export class Transaction implements IEntity<string> {
@@ -41,9 +41,9 @@ export class Transaction implements IEntity<string> {
readonly netAmount: Money;
readonly status: TransactionStatus;
readonly createdAt: Date;
readonly completedAt?: Date;
readonly description?: string;
readonly metadata?: Record<string, unknown>;
readonly completedAt: Date | undefined;
readonly description: string | undefined;
readonly metadata: Record<string, unknown> | undefined;
private constructor(props: TransactionProps) {
this.id = props.id;