3b01efd8a6
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
124 lines
3.3 KiB
C#
124 lines
3.3 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using MoneyMap.Data;
|
|
using MoneyMap.Models;
|
|
|
|
namespace MoneyMap.Services;
|
|
|
|
/// <summary>
|
|
/// Service for card management including retrieval, validation, and deletion.
|
|
/// </summary>
|
|
public interface ICardService
|
|
{
|
|
/// <summary>
|
|
/// Gets a card by ID with optional related data.
|
|
/// </summary>
|
|
Task<Card?> GetCardByIdAsync(int id, bool includeRelated = false);
|
|
|
|
/// <summary>
|
|
/// Gets all cards with transaction statistics.
|
|
/// </summary>
|
|
Task<List<CardWithStats>> GetAllCardsWithStatsAsync();
|
|
|
|
/// <summary>
|
|
/// Checks if a card can be deleted (no transactions exist).
|
|
/// </summary>
|
|
Task<DeleteValidationResult> CanDeleteCardAsync(int id);
|
|
|
|
/// <summary>
|
|
/// Deletes a card if it has no associated transactions.
|
|
/// </summary>
|
|
Task<DeleteResult> DeleteCardAsync(int id);
|
|
}
|
|
|
|
public class CardService : ICardService
|
|
{
|
|
private readonly MoneyMapContext _db;
|
|
|
|
public CardService(MoneyMapContext db)
|
|
{
|
|
_db = db;
|
|
}
|
|
|
|
public async Task<Card?> GetCardByIdAsync(int id, bool includeRelated = false)
|
|
{
|
|
var query = _db.Cards.AsQueryable();
|
|
|
|
if (includeRelated)
|
|
{
|
|
query = query
|
|
.Include(c => c.Account)
|
|
.Include(c => c.Transactions);
|
|
}
|
|
|
|
return await query.FirstOrDefaultAsync(c => c.Id == id);
|
|
}
|
|
|
|
public async Task<List<CardWithStats>> GetAllCardsWithStatsAsync()
|
|
{
|
|
// Single query with projection to avoid N+1
|
|
return await _db.Cards
|
|
.Include(c => c.Account)
|
|
.OrderBy(c => c.Owner)
|
|
.ThenBy(c => c.Last4)
|
|
.Select(c => new CardWithStats
|
|
{
|
|
Card = c,
|
|
TransactionCount = c.Transactions.Count
|
|
})
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<DeleteValidationResult> CanDeleteCardAsync(int id)
|
|
{
|
|
var card = await _db.Cards.FindAsync(id);
|
|
if (card == null)
|
|
return new DeleteValidationResult
|
|
{
|
|
CanDelete = false,
|
|
Reason = "Card not found."
|
|
};
|
|
|
|
var transactionCount = await _db.Transactions.CountAsync(t => t.CardId == id);
|
|
if (transactionCount > 0)
|
|
return new DeleteValidationResult
|
|
{
|
|
CanDelete = false,
|
|
Reason = $"Cannot delete card. It has {transactionCount} transaction(s) associated with it."
|
|
};
|
|
|
|
return new DeleteValidationResult { CanDelete = true };
|
|
}
|
|
|
|
public async Task<DeleteResult> DeleteCardAsync(int id)
|
|
{
|
|
var validation = await CanDeleteCardAsync(id);
|
|
if (!validation.CanDelete)
|
|
{
|
|
return new DeleteResult
|
|
{
|
|
Success = false,
|
|
Message = validation.Reason ?? "Cannot delete card."
|
|
};
|
|
}
|
|
|
|
var card = await _db.Cards.FindAsync(id);
|
|
if (card == null)
|
|
{
|
|
return new DeleteResult
|
|
{
|
|
Success = false,
|
|
Message = "Card not found."
|
|
};
|
|
}
|
|
|
|
_db.Cards.Remove(card);
|
|
await _db.SaveChangesAsync();
|
|
|
|
return new DeleteResult
|
|
{
|
|
Success = true,
|
|
Message = "Card deleted successfully."
|
|
};
|
|
}
|
|
}
|