budget/BudgetApp/Domain/Models/LedgerEntry.cs
2026-01-03 10:29:03 -06:00

114 lines
3.4 KiB
C#

namespace BudgetApp.Domain.Models;
/// <summary>
/// Represents a ledger entry for tracking money in/out of budgets.
/// </summary>
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<Guid, Money> BudgetAllocations { get; protected set; }
protected LedgerEntry()
{
BudgetAllocations = new Dictionary<Guid, Money>();
}
public LedgerEntry(
Guid id,
DateTime date,
string description,
Money amount,
EntryType type,
Dictionary<Guid, Money> 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<Guid, Money>(budgetAllocations);
Validate();
}
public LedgerEntry(
DateTime date,
string description,
Money amount,
EntryType type,
Dictionary<Guid, Money> budgetAllocations)
: this(Guid.NewGuid(), date, description, amount, type, budgetAllocations)
{
}
/// <summary>
/// Validates that budget allocations sum to the entry amount.
/// </summary>
public void Validate()
{
if (Type == EntryType.Expense)
{
ValidateExpenseBalance();
}
else if (Type == EntryType.Income)
{
ValidateIncomeBalance();
}
}
/// <summary>
/// Validates that expense budget allocations sum to the expense amount.
/// </summary>
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}).");
}
/// <summary>
/// Validates that income budget allocations sum to the income amount.
/// </summary>
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}).");
}
}
/// <summary>
/// Type of ledger entry.
/// </summary>
public enum EntryType
{
Income,
Expense
}