diff --git a/MoneyMap/Pages/AccountDetails.cshtml.cs b/MoneyMap/Pages/AccountDetails.cshtml.cs
index 97e936b..fb89d1b 100644
--- a/MoneyMap/Pages/AccountDetails.cshtml.cs
+++ b/MoneyMap/Pages/AccountDetails.cshtml.cs
@@ -3,16 +3,19 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using MoneyMap.Data;
using MoneyMap.Models;
+using MoneyMap.Services;
namespace MoneyMap.Pages
{
public class AccountDetailsModel : PageModel
{
- private readonly MoneyMapContext _db;
+ private readonly IAccountService _accountService;
+ private readonly ICardService _cardService;
- public AccountDetailsModel(MoneyMapContext db)
+ public AccountDetailsModel(IAccountService accountService, ICardService cardService)
{
- _db = db;
+ _accountService = accountService;
+ _cardService = cardService;
}
public Account Account { get; set; } = null!;
@@ -27,67 +30,35 @@ namespace MoneyMap.Pages
public async Task OnGetAsync(int id)
{
- var account = await _db.Accounts.FindAsync(id);
- if (account == null)
+ var accountDetails = await _accountService.GetAccountDetailsAsync(id);
+ if (accountDetails == null)
return NotFound();
- Account = account;
-
- // Get cards linked to this account
- var cards = await _db.Cards
- .Where(c => c.AccountId == id)
- .OrderBy(c => c.Owner)
- .ThenBy(c => c.Last4)
- .ToListAsync();
-
- var cardStats = new List();
- foreach (var card in cards)
- {
- var transactionCount = await _db.Transactions.CountAsync(t => t.CardId == card.Id);
- cardStats.Add(new CardWithStats
- {
- Card = card,
- TransactionCount = transactionCount
- });
- }
-
- Cards = cardStats;
-
- // Get transaction count for this account
- TransactionCount = await _db.Transactions.CountAsync(t => t.AccountId == id);
+ Account = accountDetails.Account;
+ Cards = accountDetails.Cards;
+ TransactionCount = accountDetails.TransactionCount;
return Page();
}
public async Task OnPostDeleteCardAsync(int cardId)
{
- var card = await _db.Cards.FindAsync(cardId);
- if (card == null)
+ // Get card to retrieve account ID before deletion
+ var card = await _cardService.GetCardByIdAsync(cardId);
+ var accountId = card?.AccountId;
+
+ var result = await _cardService.DeleteCardAsync(cardId);
+
+ if (result.Success)
{
- ErrorMessage = "Card not found.";
- return RedirectToPage(new { id = card?.AccountId });
+ SuccessMessage = result.Message;
+ }
+ else
+ {
+ ErrorMessage = result.Message;
}
- var accountId = card.AccountId;
-
- var transactionCount = await _db.Transactions.CountAsync(t => t.CardId == card.Id);
- if (transactionCount > 0)
- {
- ErrorMessage = $"Cannot delete card. It has {transactionCount} transaction(s) associated with it.";
- return RedirectToPage(new { id = accountId });
- }
-
- _db.Cards.Remove(card);
- await _db.SaveChangesAsync();
-
- SuccessMessage = "Card deleted successfully.";
return RedirectToPage(new { id = accountId });
}
-
- public class CardWithStats
- {
- public Card Card { get; set; } = null!;
- public int TransactionCount { get; set; }
- }
}
}
diff --git a/MoneyMap/Pages/Accounts.cshtml.cs b/MoneyMap/Pages/Accounts.cshtml.cs
index 6bd6e78..ff975f4 100644
--- a/MoneyMap/Pages/Accounts.cshtml.cs
+++ b/MoneyMap/Pages/Accounts.cshtml.cs
@@ -3,16 +3,17 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using MoneyMap.Data;
using MoneyMap.Models;
+using MoneyMap.Services;
namespace MoneyMap.Pages
{
public class AccountsModel : PageModel
{
- private readonly MoneyMapContext _db;
+ private readonly IAccountService _accountService;
- public AccountsModel(MoneyMapContext db)
+ public AccountsModel(IAccountService accountService)
{
- _db = db;
+ _accountService = accountService;
}
public List Accounts { get; set; } = new();
@@ -22,14 +23,9 @@ namespace MoneyMap.Pages
public async Task OnGetAsync()
{
- var accounts = await _db.Accounts
- .Include(a => a.Transactions)
- .OrderBy(a => a.Owner)
- .ThenBy(a => a.Institution)
- .ThenBy(a => a.Last4)
- .ToListAsync();
+ var accountsWithStats = await _accountService.GetAllAccountsWithStatsAsync();
- Accounts = accounts.Select(a => new AccountRow
+ Accounts = accountsWithStats.Select(a => new AccountRow
{
Id = a.Id,
Institution = a.Institution,
@@ -37,30 +33,25 @@ namespace MoneyMap.Pages
Last4 = a.Last4,
Owner = a.Owner,
Nickname = a.Nickname,
- TransactionCount = a.Transactions.Count
+ TransactionCount = a.TransactionCount
}).ToList();
}
public async Task OnPostDeleteAsync(int id)
{
- var account = await _db.Accounts
- .Include(a => a.Transactions)
- .FirstOrDefaultAsync(a => a.Id == id);
+ var result = await _accountService.DeleteAccountAsync(id);
- if (account == null)
- return NotFound();
-
- if (account.Transactions.Any())
+ if (result.Success)
{
- ModelState.AddModelError(string.Empty, "Cannot delete account with existing transactions.");
+ SuccessMessage = result.Message;
+ }
+ else
+ {
+ ModelState.AddModelError(string.Empty, result.Message);
await OnGetAsync();
return Page();
}
- _db.Accounts.Remove(account);
- await _db.SaveChangesAsync();
-
- SuccessMessage = $"Deleted account {account.Institution} {account.Last4}";
return RedirectToPage();
}
diff --git a/MoneyMap/Pages/Cards.cshtml.cs b/MoneyMap/Pages/Cards.cshtml.cs
index 09f974c..cf0fd21 100644
--- a/MoneyMap/Pages/Cards.cshtml.cs
+++ b/MoneyMap/Pages/Cards.cshtml.cs
@@ -3,16 +3,17 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using MoneyMap.Data;
using MoneyMap.Models;
+using MoneyMap.Services;
namespace MoneyMap.Pages
{
public class CardsModel : PageModel
{
- private readonly MoneyMapContext _db;
+ private readonly ICardService _cardService;
- public CardsModel(MoneyMapContext db)
+ public CardsModel(ICardService cardService)
{
- _db = db;
+ _cardService = cardService;
}
public List Cards { get; set; } = new();
@@ -25,55 +26,23 @@ namespace MoneyMap.Pages
public async Task OnGetAsync()
{
- var cards = await _db.Cards
- .Include(c => c.Account)
- .OrderBy(c => c.Owner)
- .ThenBy(c => c.Last4)
- .ToListAsync();
-
- var cardStats = new List();
-
- foreach (var card in cards)
- {
- var transactionCount = await _db.Transactions.CountAsync(t => t.CardId == card.Id);
-
- cardStats.Add(new CardWithStats
- {
- Card = card,
- TransactionCount = transactionCount
- });
- }
-
- Cards = cardStats;
+ Cards = await _cardService.GetAllCardsWithStatsAsync();
}
public async Task OnPostDeleteAsync(int id)
{
- var card = await _db.Cards.FindAsync(id);
- if (card == null)
+ var result = await _cardService.DeleteCardAsync(id);
+
+ if (result.Success)
{
- ErrorMessage = "Card not found.";
- return RedirectToPage();
+ SuccessMessage = result.Message;
+ }
+ else
+ {
+ ErrorMessage = result.Message;
}
- var transactionCount = await _db.Transactions.CountAsync(t => t.CardId == card.Id);
- if (transactionCount > 0)
- {
- ErrorMessage = $"Cannot delete card. It has {transactionCount} transaction(s) associated with it.";
- return RedirectToPage();
- }
-
- _db.Cards.Remove(card);
- await _db.SaveChangesAsync();
-
- SuccessMessage = "Card deleted successfully.";
return RedirectToPage();
}
-
- public class CardWithStats
- {
- public Card Card { get; set; } = null!;
- public int TransactionCount { get; set; }
- }
}
}
\ No newline at end of file
diff --git a/MoneyMap/Pages/EditTransaction.cshtml.cs b/MoneyMap/Pages/EditTransaction.cshtml.cs
index d7d856a..245d613 100644
--- a/MoneyMap/Pages/EditTransaction.cshtml.cs
+++ b/MoneyMap/Pages/EditTransaction.cshtml.cs
@@ -12,12 +12,16 @@ namespace MoneyMap.Pages
private readonly MoneyMapContext _db;
private readonly IReceiptManager _receiptManager;
private readonly IReceiptParser _receiptParser;
+ private readonly IReferenceDataService _referenceDataService;
+ private readonly IMerchantService _merchantService;
- public EditTransactionModel(MoneyMapContext db, IReceiptManager receiptManager, IReceiptParser receiptParser)
+ public EditTransactionModel(MoneyMapContext db, IReceiptManager receiptManager, IReceiptParser receiptParser, IReferenceDataService referenceDataService, IMerchantService merchantService)
{
_db = db;
_receiptManager = receiptManager;
_receiptParser = receiptParser;
+ _referenceDataService = referenceDataService;
+ _merchantService = merchantService;
}
[BindProperty]
@@ -124,22 +128,9 @@ namespace MoneyMap.Pages
// Update merchant
if (!string.IsNullOrWhiteSpace(Transaction.MerchantName))
{
- // Create new merchant if custom name was entered
- var merchantName = Transaction.MerchantName.Trim();
- var existingMerchant = await _db.Merchants
- .FirstOrDefaultAsync(m => m.Name == merchantName);
-
- if (existingMerchant != null)
- {
- transaction.MerchantId = existingMerchant.Id;
- }
- else
- {
- var newMerchant = new Merchant { Name = merchantName };
- _db.Merchants.Add(newMerchant);
- await _db.SaveChangesAsync();
- transaction.MerchantId = newMerchant.Id;
- }
+ // Create or get merchant if custom name was entered
+ var merchantId = await _merchantService.GetOrCreateIdAsync(Transaction.MerchantName);
+ transaction.MerchantId = merchantId;
}
else if (Transaction.MerchantId.HasValue && Transaction.MerchantId.Value > 0)
{
@@ -252,19 +243,12 @@ namespace MoneyMap.Pages
private async Task LoadAvailableCategoriesAsync()
{
- AvailableCategories = await _db.Transactions
- .Select(t => t.Category ?? "")
- .Where(c => !string.IsNullOrWhiteSpace(c))
- .Distinct()
- .OrderBy(c => c)
- .ToListAsync();
+ AvailableCategories = await _referenceDataService.GetAvailableCategoriesAsync();
}
private async Task LoadAvailableMerchantsAsync()
{
- AvailableMerchants = await _db.Merchants
- .OrderBy(m => m.Name)
- .ToListAsync();
+ AvailableMerchants = await _referenceDataService.GetAvailableMerchantsAsync();
}
public class TransactionEditModel
diff --git a/MoneyMap/Pages/Merchants.cshtml.cs b/MoneyMap/Pages/Merchants.cshtml.cs
index f75b706..efbd9ae 100644
--- a/MoneyMap/Pages/Merchants.cshtml.cs
+++ b/MoneyMap/Pages/Merchants.cshtml.cs
@@ -3,17 +3,18 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using MoneyMap.Data;
using MoneyMap.Models;
+using MoneyMap.Services;
using System.ComponentModel.DataAnnotations;
namespace MoneyMap.Pages
{
public class MerchantsModel : PageModel
{
- private readonly MoneyMapContext _db;
+ private readonly IMerchantService _merchantService;
- public MerchantsModel(MoneyMapContext db)
+ public MerchantsModel(IMerchantService merchantService)
{
- _db = db;
+ _merchantService = merchantService;
}
public List Merchants { get; set; } = new();
@@ -40,9 +41,7 @@ namespace MoneyMap.Pages
}
// Check if merchant already exists
- var existing = await _db.Merchants
- .FirstOrDefaultAsync(m => m.Name == model.Name.Trim());
-
+ var existing = await _merchantService.FindByNameAsync(model.Name);
if (existing != null)
{
ErrorMessage = $"Merchant '{model.Name}' already exists.";
@@ -50,13 +49,7 @@ namespace MoneyMap.Pages
return Page();
}
- var merchant = new Merchant
- {
- Name = model.Name.Trim()
- };
-
- _db.Merchants.Add(merchant);
- await _db.SaveChangesAsync();
+ var merchant = await _merchantService.GetOrCreateAsync(model.Name);
SuccessMessage = $"Added merchant '{merchant.Name}'.";
return RedirectToPage();
@@ -71,68 +64,48 @@ namespace MoneyMap.Pages
return Page();
}
- var merchant = await _db.Merchants.FindAsync(model.Id);
- if (merchant == null)
+ var result = await _merchantService.UpdateMerchantAsync(model.Id, model.Name);
+
+ if (result.Success)
{
- ErrorMessage = "Merchant not found.";
- return RedirectToPage();
+ SuccessMessage = result.Message;
}
-
- // Check if another merchant with the same name exists
- var existing = await _db.Merchants
- .FirstOrDefaultAsync(m => m.Name == model.Name.Trim() && m.Id != model.Id);
-
- if (existing != null)
+ else
{
- ErrorMessage = $"Merchant '{model.Name}' already exists.";
+ ErrorMessage = result.Message;
await LoadDataAsync();
return Page();
}
- merchant.Name = model.Name.Trim();
- await _db.SaveChangesAsync();
-
- SuccessMessage = "Merchant updated successfully.";
return RedirectToPage();
}
public async Task OnPostDeleteMerchantAsync(int id)
{
- var merchant = await _db.Merchants
- .Include(m => m.Transactions)
- .Include(m => m.CategoryMappings)
- .FirstOrDefaultAsync(m => m.Id == id);
+ var result = await _merchantService.DeleteMerchantAsync(id);
- if (merchant == null)
+ if (result.Success)
{
- ErrorMessage = "Merchant not found.";
- return RedirectToPage();
+ SuccessMessage = result.Message;
+ }
+ else
+ {
+ ErrorMessage = result.Message;
}
- var transactionCount = merchant.Transactions.Count;
- var mappingCount = merchant.CategoryMappings.Count;
-
- _db.Merchants.Remove(merchant);
- await _db.SaveChangesAsync();
-
- SuccessMessage = $"Deleted merchant '{merchant.Name}'. {transactionCount} transactions and {mappingCount} category mappings are now unlinked.";
return RedirectToPage();
}
private async Task LoadDataAsync()
{
- var merchants = await _db.Merchants
- .Include(m => m.Transactions)
- .Include(m => m.CategoryMappings)
- .OrderBy(m => m.Name)
- .ToListAsync();
+ var merchantsWithStats = await _merchantService.GetAllMerchantsWithStatsAsync();
- Merchants = merchants.Select(m => new MerchantRow
+ Merchants = merchantsWithStats.Select(m => new MerchantRow
{
Id = m.Id,
Name = m.Name,
- TransactionCount = m.Transactions.Count,
- MappingCount = m.CategoryMappings.Count
+ TransactionCount = m.TransactionCount,
+ MappingCount = m.MappingCount
}).ToList();
TotalMerchants = Merchants.Count;
diff --git a/MoneyMap/Pages/Recategorize.cshtml.cs b/MoneyMap/Pages/Recategorize.cshtml.cs
index 7dd76d5..c5acae9 100644
--- a/MoneyMap/Pages/Recategorize.cshtml.cs
+++ b/MoneyMap/Pages/Recategorize.cshtml.cs
@@ -10,11 +10,13 @@ namespace MoneyMap.Pages
{
private readonly MoneyMapContext _db;
private readonly ITransactionCategorizer _categorizer;
+ private readonly ITransactionStatisticsService _statsService;
- public RecategorizeModel(MoneyMapContext db, ITransactionCategorizer categorizer)
+ public RecategorizeModel(MoneyMapContext db, ITransactionCategorizer categorizer, ITransactionStatisticsService statsService)
{
_db = db;
_categorizer = categorizer;
+ _statsService = statsService;
}
public RecategorizeStats Stats { get; set; } = new();
@@ -47,16 +49,13 @@ namespace MoneyMap.Pages
private async Task LoadStatsAsync()
{
- var totalTransactions = await _db.Transactions.CountAsync();
- var uncategorized = await _db.Transactions
- .CountAsync(t => string.IsNullOrWhiteSpace(t.Category));
- var categorized = totalTransactions - uncategorized;
+ var categorizationStats = await _statsService.GetCategorizationStatsAsync();
Stats = new RecategorizeStats
{
- TotalTransactions = totalTransactions,
- Categorized = categorized,
- Uncategorized = uncategorized
+ TotalTransactions = categorizationStats.TotalTransactions,
+ Categorized = categorizationStats.Categorized,
+ Uncategorized = categorizationStats.Uncategorized
};
}
diff --git a/MoneyMap/Pages/Transactions.cshtml.cs b/MoneyMap/Pages/Transactions.cshtml.cs
index 8e0c0e2..6b954e7 100644
--- a/MoneyMap/Pages/Transactions.cshtml.cs
+++ b/MoneyMap/Pages/Transactions.cshtml.cs
@@ -3,16 +3,21 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using MoneyMap.Data;
using MoneyMap.Models;
+using MoneyMap.Services;
namespace MoneyMap.Pages
{
public class TransactionsModel : PageModel
{
private readonly MoneyMapContext _db;
+ private readonly ITransactionStatisticsService _statsService;
+ private readonly IReferenceDataService _referenceDataService;
- public TransactionsModel(MoneyMapContext db)
+ public TransactionsModel(MoneyMapContext db, ITransactionStatisticsService statsService, IReferenceDataService referenceDataService)
{
_db = db;
+ _statsService = statsService;
+ _referenceDataService = referenceDataService;
}
[BindProperty(SupportsGet = true)]
@@ -147,33 +152,17 @@ namespace MoneyMap.Pages
}).ToList();
// Calculate stats for filtered results (all pages, not just current)
- var allFilteredTransactions = await query.ToListAsync();
- Stats = 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)
- };
+ Stats = await _statsService.CalculateStatsAsync(query);
// Get available categories for filter dropdown
- AvailableCategories = await _db.Transactions
- .Select(t => t.Category ?? "")
- .Distinct()
- .OrderBy(c => c)
- .ToListAsync();
+ AvailableCategories = await _referenceDataService.GetAvailableCategoriesAsync();
// Get available merchants for filter dropdown
- AvailableMerchants = await _db.Merchants
- .OrderBy(m => m.Name)
- .Select(m => m.Name)
- .ToListAsync();
+ var merchants = await _referenceDataService.GetAvailableMerchantsAsync();
+ AvailableMerchants = merchants.Select(m => m.Name).ToList();
// Get available cards for filter dropdown
- AvailableCards = await _db.Cards
- .OrderBy(c => c.Owner)
- .ThenBy(c => c.Last4)
- .ToListAsync();
+ AvailableCards = await _referenceDataService.GetAvailableCardsAsync(includeAccount: false);
}
public class TransactionRow
@@ -189,13 +178,5 @@ namespace MoneyMap.Pages
public string AccountLabel { get; set; } = "";
public int ReceiptCount { get; set; }
}
-
- public class TransactionStats
- {
- public int Count { get; set; }
- public decimal TotalDebits { get; set; }
- public decimal TotalCredits { get; set; }
- public decimal NetAmount { get; set; }
- }
}
}
\ No newline at end of file