namespace BudgetApp.Domain.Models; /// /// Represents a ledger entry for tracking money in/out of budgets. /// public class LedgerEntry { public Guid Id { get; protected set; } public DateTime Date { get; protected set; } public string Description { get; protected set; } = string.Empty; public Money Amount { get; protected set; } = null!; public EntryType Type { get; protected set; } public Dictionary BudgetAllocations { get; protected set; } protected LedgerEntry() { BudgetAllocations = new Dictionary(); } public LedgerEntry( Guid id, DateTime date, string description, Money amount, EntryType type, Dictionary budgetAllocations) { if (string.IsNullOrWhiteSpace(description)) throw new ArgumentException("Description cannot be null or empty.", nameof(description)); if (amount == null) throw new ArgumentNullException(nameof(amount)); if (budgetAllocations == null) throw new ArgumentNullException(nameof(budgetAllocations)); Id = id; Date = date; Description = description; Amount = amount; Type = type; BudgetAllocations = new Dictionary(budgetAllocations); Validate(); } public LedgerEntry( DateTime date, string description, Money amount, EntryType type, Dictionary budgetAllocations) : this(Guid.NewGuid(), date, description, amount, type, budgetAllocations) { } /// /// Validates that budget allocations sum to the entry amount. /// public void Validate() { if (Type == EntryType.Expense) { ValidateExpenseBalance(); } else if (Type == EntryType.Income) { ValidateIncomeBalance(); } } /// /// Validates that expense budget allocations sum to the expense amount. /// private void ValidateExpenseBalance() { if (BudgetAllocations.Count == 0) throw new InvalidOperationException("Expense must have at least one budget allocation."); var totalAllocated = BudgetAllocations.Values .Aggregate(new Money(0, Amount.Currency), (sum, money) => sum.Add(money)); if (totalAllocated.Amount != Amount.Amount) throw new InvalidOperationException( $"Expense amount ({Amount.Amount}) does not match sum of budget allocations ({totalAllocated.Amount})."); } /// /// Validates that income budget allocations sum to the income amount. /// private void ValidateIncomeBalance() { if (BudgetAllocations.Count == 0) throw new InvalidOperationException("Income must have at least one budget allocation."); var totalAllocated = BudgetAllocations.Values .Aggregate(new Money(0, Amount.Currency), (sum, money) => sum.Add(money)); if (totalAllocated.Amount != Amount.Amount) throw new InvalidOperationException( $"Income amount ({Amount.Amount}) does not match sum of budget allocations ({totalAllocated.Amount})."); } } /// /// Type of ledger entry. /// public enum EntryType { Income, Expense }