refactor: move services and AITools to MoneyMap.Core
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
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)
|
||||
{
|
||||
// Calculate stats at database level instead of loading all transactions into memory
|
||||
var stats = await query
|
||||
.GroupBy(_ => 1) // Group all into one group to aggregate
|
||||
.Select(g => new TransactionStats
|
||||
{
|
||||
Count = g.Count(),
|
||||
TotalDebits = g.Where(t => t.Amount < 0).Sum(t => t.Amount),
|
||||
TotalCredits = g.Where(t => t.Amount > 0).Sum(t => t.Amount),
|
||||
NetAmount = g.Sum(t => t.Amount)
|
||||
})
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
return stats ?? new TransactionStats();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Single query with projection to avoid N+1
|
||||
return await _db.Cards
|
||||
.Where(c => c.AccountId == accountId)
|
||||
.OrderBy(c => c.Owner)
|
||||
.ThenBy(c => c.Last4)
|
||||
.Select(c => new CardStats
|
||||
{
|
||||
Card = c,
|
||||
TransactionCount = c.Transactions.Count
|
||||
})
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// 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; }
|
||||
}
|
||||
Reference in New Issue
Block a user