125 lines
3.3 KiB
TypeScript
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];
|
|
}
|
|
} |