Add four new services to extract business logic from PageModels: - AccountService: Account retrieval, stats, and deletion with validation - CardService: Card retrieval, stats, and deletion with validation - ReferenceDataService: Centralized reference data for dropdowns (categories, merchants, cards, accounts) - TransactionStatisticsService: Transaction statistics and aggregate calculations All services follow the established service layer pattern with interfaces for DI and improved testability. Includes comprehensive DTOs for stats and validation results. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
109 lines
3.1 KiB
C#
109 lines
3.1 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using MoneyMap.Data;
|
|
using MoneyMap.Models;
|
|
|
|
namespace MoneyMap.Services;
|
|
|
|
/// <summary>
|
|
/// Service for calculating transaction statistics and aggregates.
|
|
/// </summary>
|
|
public interface ITransactionStatisticsService
|
|
{
|
|
/// <summary>
|
|
/// Calculates statistics for a filtered set of transactions.
|
|
/// </summary>
|
|
Task<TransactionStats> CalculateStatsAsync(IQueryable<Transaction> query);
|
|
|
|
/// <summary>
|
|
/// Gets categorization statistics for the entire database.
|
|
/// </summary>
|
|
Task<CategorizationStats> GetCategorizationStatsAsync();
|
|
|
|
/// <summary>
|
|
/// Gets card statistics for a specific account.
|
|
/// </summary>
|
|
Task<List<CardStats>> GetCardStatsForAccountAsync(int accountId);
|
|
}
|
|
|
|
public class TransactionStatisticsService : ITransactionStatisticsService
|
|
{
|
|
private readonly MoneyMapContext _db;
|
|
|
|
public TransactionStatisticsService(MoneyMapContext db)
|
|
{
|
|
_db = db;
|
|
}
|
|
|
|
public async Task<TransactionStats> CalculateStatsAsync(IQueryable<Transaction> query)
|
|
{
|
|
var allFilteredTransactions = await query.ToListAsync();
|
|
|
|
return new TransactionStats
|
|
{
|
|
Count = allFilteredTransactions.Count,
|
|
TotalDebits = allFilteredTransactions.Where(t => t.Amount < 0).Sum(t => t.Amount),
|
|
TotalCredits = allFilteredTransactions.Where(t => t.Amount > 0).Sum(t => t.Amount),
|
|
NetAmount = allFilteredTransactions.Sum(t => t.Amount)
|
|
};
|
|
}
|
|
|
|
public async Task<CategorizationStats> GetCategorizationStatsAsync()
|
|
{
|
|
var totalTransactions = await _db.Transactions.CountAsync();
|
|
var uncategorized = await _db.Transactions
|
|
.CountAsync(t => string.IsNullOrWhiteSpace(t.Category));
|
|
var categorized = totalTransactions - uncategorized;
|
|
|
|
return new CategorizationStats
|
|
{
|
|
TotalTransactions = totalTransactions,
|
|
Categorized = categorized,
|
|
Uncategorized = uncategorized
|
|
};
|
|
}
|
|
|
|
public async Task<List<CardStats>> GetCardStatsForAccountAsync(int accountId)
|
|
{
|
|
var cards = await _db.Cards
|
|
.Where(c => c.AccountId == accountId)
|
|
.OrderBy(c => c.Owner)
|
|
.ThenBy(c => c.Last4)
|
|
.ToListAsync();
|
|
|
|
var cardStats = new List<CardStats>();
|
|
foreach (var card in cards)
|
|
{
|
|
var transactionCount = await _db.Transactions.CountAsync(t => t.CardId == card.Id);
|
|
cardStats.Add(new CardStats
|
|
{
|
|
Card = card,
|
|
TransactionCount = transactionCount
|
|
});
|
|
}
|
|
|
|
return cardStats;
|
|
}
|
|
}
|
|
|
|
// DTOs
|
|
public class TransactionStats
|
|
{
|
|
public int Count { get; set; }
|
|
public decimal TotalDebits { get; set; }
|
|
public decimal TotalCredits { get; set; }
|
|
public decimal NetAmount { get; set; }
|
|
}
|
|
|
|
public class CategorizationStats
|
|
{
|
|
public int TotalTransactions { get; set; }
|
|
public int Categorized { get; set; }
|
|
public int Uncategorized { get; set; }
|
|
}
|
|
|
|
public class CardStats
|
|
{
|
|
public Card Card { get; set; } = null!;
|
|
public int TransactionCount { get; set; }
|
|
}
|