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,202 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoneyMap.Data;
|
||||
using MoneyMap.Models;
|
||||
|
||||
namespace MoneyMap.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for account management including retrieval, validation, and deletion.
|
||||
/// </summary>
|
||||
public interface IAccountService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an account by ID with optional related data.
|
||||
/// </summary>
|
||||
Task<Account?> GetAccountByIdAsync(int id, bool includeRelated = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all accounts with optional statistics.
|
||||
/// </summary>
|
||||
Task<List<AccountWithStats>> GetAllAccountsWithStatsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets account details with cards and transaction count.
|
||||
/// </summary>
|
||||
Task<AccountDetails?> GetAccountDetailsAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an account can be deleted (no transactions exist).
|
||||
/// </summary>
|
||||
Task<DeleteValidationResult> CanDeleteAccountAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an account if it has no associated transactions.
|
||||
/// </summary>
|
||||
Task<DeleteResult> DeleteAccountAsync(int id);
|
||||
}
|
||||
|
||||
public class AccountService : IAccountService
|
||||
{
|
||||
private readonly MoneyMapContext _db;
|
||||
|
||||
public AccountService(MoneyMapContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<Account?> GetAccountByIdAsync(int id, bool includeRelated = false)
|
||||
{
|
||||
var query = _db.Accounts.AsQueryable();
|
||||
|
||||
if (includeRelated)
|
||||
{
|
||||
query = query
|
||||
.Include(a => a.Cards)
|
||||
.Include(a => a.Transactions);
|
||||
}
|
||||
|
||||
return await query.FirstOrDefaultAsync(a => a.Id == id);
|
||||
}
|
||||
|
||||
public async Task<List<AccountWithStats>> GetAllAccountsWithStatsAsync()
|
||||
{
|
||||
var accounts = await _db.Accounts
|
||||
.Include(a => a.Transactions)
|
||||
.OrderBy(a => a.Owner)
|
||||
.ThenBy(a => a.Institution)
|
||||
.ThenBy(a => a.Last4)
|
||||
.ToListAsync();
|
||||
|
||||
return accounts.Select(a => new AccountWithStats
|
||||
{
|
||||
Id = a.Id,
|
||||
Institution = a.Institution,
|
||||
AccountType = a.AccountType,
|
||||
Last4 = a.Last4,
|
||||
Owner = a.Owner,
|
||||
Nickname = a.Nickname,
|
||||
TransactionCount = a.Transactions.Count
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<AccountDetails?> GetAccountDetailsAsync(int id)
|
||||
{
|
||||
var account = await _db.Accounts.FindAsync(id);
|
||||
if (account == null)
|
||||
return null;
|
||||
|
||||
// Single query with projection to avoid N+1
|
||||
var cardStats = await _db.Cards
|
||||
.Where(c => c.AccountId == id)
|
||||
.OrderBy(c => c.Owner)
|
||||
.ThenBy(c => c.Last4)
|
||||
.Select(c => new CardWithStats
|
||||
{
|
||||
Card = c,
|
||||
TransactionCount = c.Transactions.Count
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
// Get transaction count for this account
|
||||
var accountTransactionCount = await _db.Transactions.CountAsync(t => t.AccountId == id);
|
||||
|
||||
return new AccountDetails
|
||||
{
|
||||
Account = account,
|
||||
Cards = cardStats,
|
||||
TransactionCount = accountTransactionCount
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<DeleteValidationResult> CanDeleteAccountAsync(int id)
|
||||
{
|
||||
var account = await _db.Accounts
|
||||
.Include(a => a.Transactions)
|
||||
.FirstOrDefaultAsync(a => a.Id == id);
|
||||
|
||||
if (account == null)
|
||||
return new DeleteValidationResult
|
||||
{
|
||||
CanDelete = false,
|
||||
Reason = "Account not found."
|
||||
};
|
||||
|
||||
if (account.Transactions.Any())
|
||||
return new DeleteValidationResult
|
||||
{
|
||||
CanDelete = false,
|
||||
Reason = $"Cannot delete account. It has {account.Transactions.Count} transaction(s) associated with it."
|
||||
};
|
||||
|
||||
return new DeleteValidationResult { CanDelete = true };
|
||||
}
|
||||
|
||||
public async Task<DeleteResult> DeleteAccountAsync(int id)
|
||||
{
|
||||
var validation = await CanDeleteAccountAsync(id);
|
||||
if (!validation.CanDelete)
|
||||
{
|
||||
return new DeleteResult
|
||||
{
|
||||
Success = false,
|
||||
Message = validation.Reason ?? "Cannot delete account."
|
||||
};
|
||||
}
|
||||
|
||||
var account = await _db.Accounts.FindAsync(id);
|
||||
if (account == null)
|
||||
{
|
||||
return new DeleteResult
|
||||
{
|
||||
Success = false,
|
||||
Message = "Account not found."
|
||||
};
|
||||
}
|
||||
|
||||
_db.Accounts.Remove(account);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return new DeleteResult
|
||||
{
|
||||
Success = true,
|
||||
Message = $"Deleted account {account.Institution} {account.Last4}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// DTOs
|
||||
public class AccountWithStats
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Institution { get; set; } = "";
|
||||
public AccountType AccountType { get; set; }
|
||||
public string Last4 { get; set; } = "";
|
||||
public string Owner { get; set; } = "";
|
||||
public string? Nickname { get; set; }
|
||||
public int TransactionCount { get; set; }
|
||||
}
|
||||
|
||||
public class AccountDetails
|
||||
{
|
||||
public Account Account { get; set; } = null!;
|
||||
public List<CardWithStats> Cards { get; set; } = new();
|
||||
public int TransactionCount { get; set; }
|
||||
}
|
||||
|
||||
public class CardWithStats
|
||||
{
|
||||
public Card Card { get; set; } = null!;
|
||||
public int TransactionCount { get; set; }
|
||||
}
|
||||
|
||||
public class DeleteValidationResult
|
||||
{
|
||||
public bool CanDelete { get; set; }
|
||||
public string? Reason { get; set; }
|
||||
}
|
||||
|
||||
public class DeleteResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = "";
|
||||
}
|
||||
Reference in New Issue
Block a user