Files
gridpilot.gg/packages/racing/domain/entities/LeagueWallet.ts
2025-12-11 11:25:22 +01:00

125 lines
3.3 KiB
TypeScript

/**
* Domain Entity: LeagueWallet
*
* Represents a league's financial wallet.
* Aggregate root for managing league finances and transactions.
*/
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
import type { Money } from '../value-objects/Money';
import type { Transaction } from './Transaction';
export interface LeagueWalletProps {
id: string;
leagueId: string;
balance: Money;
transactionIds: string[];
createdAt: Date;
}
export class LeagueWallet {
readonly id: string;
readonly leagueId: string;
readonly balance: Money;
readonly transactionIds: string[];
readonly createdAt: Date;
private constructor(props: LeagueWalletProps) {
this.id = props.id;
this.leagueId = props.leagueId;
this.balance = props.balance;
this.transactionIds = props.transactionIds;
this.createdAt = props.createdAt;
}
static create(props: Omit<LeagueWalletProps, 'createdAt' | 'transactionIds'> & {
createdAt?: Date;
transactionIds?: string[];
}): LeagueWallet {
this.validate(props);
return new LeagueWallet({
...props,
createdAt: props.createdAt ?? new Date(),
transactionIds: props.transactionIds ?? [],
});
}
private static validate(props: Omit<LeagueWalletProps, 'createdAt' | 'transactionIds'>): void {
if (!props.id || props.id.trim().length === 0) {
throw new RacingDomainValidationError('LeagueWallet ID is required');
}
if (!props.leagueId || props.leagueId.trim().length === 0) {
throw new RacingDomainValidationError('LeagueWallet leagueId is required');
}
if (!props.balance) {
throw new RacingDomainValidationError('LeagueWallet balance is required');
}
}
/**
* Add funds to wallet (from sponsorship or membership payments)
*/
addFunds(netAmount: Money, transactionId: string): LeagueWallet {
if (this.balance.currency !== netAmount.currency) {
throw new RacingDomainInvariantError('Cannot add funds with different currency');
}
const newBalance = this.balance.add(netAmount);
return new LeagueWallet({
...this,
balance: newBalance,
transactionIds: [...this.transactionIds, transactionId],
});
}
/**
* Withdraw funds from wallet
* Domain rule: Cannot withdraw if insufficient balance
*/
withdrawFunds(amount: Money, transactionId: string): LeagueWallet {
if (this.balance.currency !== amount.currency) {
throw new RacingDomainInvariantError('Cannot withdraw funds with different currency');
}
if (!this.balance.isGreaterThan(amount) && !this.balance.equals(amount)) {
throw new RacingDomainInvariantError('Insufficient balance for withdrawal');
}
const newBalance = this.balance.subtract(amount);
return new LeagueWallet({
...this,
balance: newBalance,
transactionIds: [...this.transactionIds, transactionId],
});
}
/**
* Check if wallet can withdraw a specific amount
*/
canWithdraw(amount: Money): boolean {
if (this.balance.currency !== amount.currency) {
return false;
}
return this.balance.isGreaterThan(amount) || this.balance.equals(amount);
}
/**
* Get current balance
*/
getBalance(): Money {
return this.balance;
}
/**
* Get all transaction IDs
*/
getTransactionIds(): string[] {
return [...this.transactionIds];
}
}