Refactor: migrate PageModels to use new service layer
Refactor 7 PageModels to delegate business logic to services: - AccountDetails: Use AccountService for account details retrieval - Accounts: Use AccountService for listing and deletion - Cards: Use CardService for listing and deletion - EditTransaction: Use ReferenceDataService for dropdowns - Merchants: Use MerchantService for CRUD operations - Recategorize: Use ReferenceDataService and TransactionStatisticsService - Transactions: Use ReferenceDataService and TransactionStatisticsService Significantly simplifies PageModels (229 lines removed, 97 added) by extracting data access and business logic into testable services. Pages now focus solely on HTTP request/response handling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,16 +3,19 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoneyMap.Data;
|
using MoneyMap.Data;
|
||||||
using MoneyMap.Models;
|
using MoneyMap.Models;
|
||||||
|
using MoneyMap.Services;
|
||||||
|
|
||||||
namespace MoneyMap.Pages
|
namespace MoneyMap.Pages
|
||||||
{
|
{
|
||||||
public class AccountDetailsModel : PageModel
|
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!;
|
public Account Account { get; set; } = null!;
|
||||||
@@ -27,67 +30,35 @@ namespace MoneyMap.Pages
|
|||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync(int id)
|
public async Task<IActionResult> OnGetAsync(int id)
|
||||||
{
|
{
|
||||||
var account = await _db.Accounts.FindAsync(id);
|
var accountDetails = await _accountService.GetAccountDetailsAsync(id);
|
||||||
if (account == null)
|
if (accountDetails == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
Account = account;
|
Account = accountDetails.Account;
|
||||||
|
Cards = accountDetails.Cards;
|
||||||
// Get cards linked to this account
|
TransactionCount = accountDetails.TransactionCount;
|
||||||
var cards = await _db.Cards
|
|
||||||
.Where(c => c.AccountId == id)
|
|
||||||
.OrderBy(c => c.Owner)
|
|
||||||
.ThenBy(c => c.Last4)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var cardStats = new List<CardWithStats>();
|
|
||||||
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);
|
|
||||||
|
|
||||||
return Page();
|
return Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostDeleteCardAsync(int cardId)
|
public async Task<IActionResult> OnPostDeleteCardAsync(int cardId)
|
||||||
{
|
{
|
||||||
var card = await _db.Cards.FindAsync(cardId);
|
// Get card to retrieve account ID before deletion
|
||||||
if (card == null)
|
var card = await _cardService.GetCardByIdAsync(cardId);
|
||||||
|
var accountId = card?.AccountId;
|
||||||
|
|
||||||
|
var result = await _cardService.DeleteCardAsync(cardId);
|
||||||
|
|
||||||
|
if (result.Success)
|
||||||
{
|
{
|
||||||
ErrorMessage = "Card not found.";
|
SuccessMessage = result.Message;
|
||||||
return RedirectToPage(new { id = card?.AccountId });
|
}
|
||||||
|
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 });
|
return RedirectToPage(new { id = accountId });
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CardWithStats
|
|
||||||
{
|
|
||||||
public Card Card { get; set; } = null!;
|
|
||||||
public int TransactionCount { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoneyMap.Data;
|
using MoneyMap.Data;
|
||||||
using MoneyMap.Models;
|
using MoneyMap.Models;
|
||||||
|
using MoneyMap.Services;
|
||||||
|
|
||||||
namespace MoneyMap.Pages
|
namespace MoneyMap.Pages
|
||||||
{
|
{
|
||||||
public class AccountsModel : PageModel
|
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<AccountRow> Accounts { get; set; } = new();
|
public List<AccountRow> Accounts { get; set; } = new();
|
||||||
@@ -22,14 +23,9 @@ namespace MoneyMap.Pages
|
|||||||
|
|
||||||
public async Task OnGetAsync()
|
public async Task OnGetAsync()
|
||||||
{
|
{
|
||||||
var accounts = await _db.Accounts
|
var accountsWithStats = await _accountService.GetAllAccountsWithStatsAsync();
|
||||||
.Include(a => a.Transactions)
|
|
||||||
.OrderBy(a => a.Owner)
|
|
||||||
.ThenBy(a => a.Institution)
|
|
||||||
.ThenBy(a => a.Last4)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
Accounts = accounts.Select(a => new AccountRow
|
Accounts = accountsWithStats.Select(a => new AccountRow
|
||||||
{
|
{
|
||||||
Id = a.Id,
|
Id = a.Id,
|
||||||
Institution = a.Institution,
|
Institution = a.Institution,
|
||||||
@@ -37,30 +33,25 @@ namespace MoneyMap.Pages
|
|||||||
Last4 = a.Last4,
|
Last4 = a.Last4,
|
||||||
Owner = a.Owner,
|
Owner = a.Owner,
|
||||||
Nickname = a.Nickname,
|
Nickname = a.Nickname,
|
||||||
TransactionCount = a.Transactions.Count
|
TransactionCount = a.TransactionCount
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostDeleteAsync(int id)
|
public async Task<IActionResult> OnPostDeleteAsync(int id)
|
||||||
{
|
{
|
||||||
var account = await _db.Accounts
|
var result = await _accountService.DeleteAccountAsync(id);
|
||||||
.Include(a => a.Transactions)
|
|
||||||
.FirstOrDefaultAsync(a => a.Id == id);
|
|
||||||
|
|
||||||
if (account == null)
|
if (result.Success)
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
if (account.Transactions.Any())
|
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(string.Empty, "Cannot delete account with existing transactions.");
|
SuccessMessage = result.Message;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(string.Empty, result.Message);
|
||||||
await OnGetAsync();
|
await OnGetAsync();
|
||||||
return Page();
|
return Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
_db.Accounts.Remove(account);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
SuccessMessage = $"Deleted account {account.Institution} {account.Last4}";
|
|
||||||
return RedirectToPage();
|
return RedirectToPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoneyMap.Data;
|
using MoneyMap.Data;
|
||||||
using MoneyMap.Models;
|
using MoneyMap.Models;
|
||||||
|
using MoneyMap.Services;
|
||||||
|
|
||||||
namespace MoneyMap.Pages
|
namespace MoneyMap.Pages
|
||||||
{
|
{
|
||||||
public class CardsModel : PageModel
|
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<CardWithStats> Cards { get; set; } = new();
|
public List<CardWithStats> Cards { get; set; } = new();
|
||||||
@@ -25,55 +26,23 @@ namespace MoneyMap.Pages
|
|||||||
|
|
||||||
public async Task OnGetAsync()
|
public async Task OnGetAsync()
|
||||||
{
|
{
|
||||||
var cards = await _db.Cards
|
Cards = await _cardService.GetAllCardsWithStatsAsync();
|
||||||
.Include(c => c.Account)
|
|
||||||
.OrderBy(c => c.Owner)
|
|
||||||
.ThenBy(c => c.Last4)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var cardStats = new List<CardWithStats>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostDeleteAsync(int id)
|
public async Task<IActionResult> OnPostDeleteAsync(int id)
|
||||||
{
|
{
|
||||||
var card = await _db.Cards.FindAsync(id);
|
var result = await _cardService.DeleteCardAsync(id);
|
||||||
if (card == null)
|
|
||||||
|
if (result.Success)
|
||||||
{
|
{
|
||||||
ErrorMessage = "Card not found.";
|
SuccessMessage = result.Message;
|
||||||
return RedirectToPage();
|
}
|
||||||
|
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();
|
return RedirectToPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CardWithStats
|
|
||||||
{
|
|
||||||
public Card Card { get; set; } = null!;
|
|
||||||
public int TransactionCount { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,12 +12,16 @@ namespace MoneyMap.Pages
|
|||||||
private readonly MoneyMapContext _db;
|
private readonly MoneyMapContext _db;
|
||||||
private readonly IReceiptManager _receiptManager;
|
private readonly IReceiptManager _receiptManager;
|
||||||
private readonly IReceiptParser _receiptParser;
|
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;
|
_db = db;
|
||||||
_receiptManager = receiptManager;
|
_receiptManager = receiptManager;
|
||||||
_receiptParser = receiptParser;
|
_receiptParser = receiptParser;
|
||||||
|
_referenceDataService = referenceDataService;
|
||||||
|
_merchantService = merchantService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BindProperty]
|
[BindProperty]
|
||||||
@@ -124,22 +128,9 @@ namespace MoneyMap.Pages
|
|||||||
// Update merchant
|
// Update merchant
|
||||||
if (!string.IsNullOrWhiteSpace(Transaction.MerchantName))
|
if (!string.IsNullOrWhiteSpace(Transaction.MerchantName))
|
||||||
{
|
{
|
||||||
// Create new merchant if custom name was entered
|
// Create or get merchant if custom name was entered
|
||||||
var merchantName = Transaction.MerchantName.Trim();
|
var merchantId = await _merchantService.GetOrCreateIdAsync(Transaction.MerchantName);
|
||||||
var existingMerchant = await _db.Merchants
|
transaction.MerchantId = merchantId;
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (Transaction.MerchantId.HasValue && Transaction.MerchantId.Value > 0)
|
else if (Transaction.MerchantId.HasValue && Transaction.MerchantId.Value > 0)
|
||||||
{
|
{
|
||||||
@@ -252,19 +243,12 @@ namespace MoneyMap.Pages
|
|||||||
|
|
||||||
private async Task LoadAvailableCategoriesAsync()
|
private async Task LoadAvailableCategoriesAsync()
|
||||||
{
|
{
|
||||||
AvailableCategories = await _db.Transactions
|
AvailableCategories = await _referenceDataService.GetAvailableCategoriesAsync();
|
||||||
.Select(t => t.Category ?? "")
|
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c))
|
|
||||||
.Distinct()
|
|
||||||
.OrderBy(c => c)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadAvailableMerchantsAsync()
|
private async Task LoadAvailableMerchantsAsync()
|
||||||
{
|
{
|
||||||
AvailableMerchants = await _db.Merchants
|
AvailableMerchants = await _referenceDataService.GetAvailableMerchantsAsync();
|
||||||
.OrderBy(m => m.Name)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TransactionEditModel
|
public class TransactionEditModel
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoneyMap.Data;
|
using MoneyMap.Data;
|
||||||
using MoneyMap.Models;
|
using MoneyMap.Models;
|
||||||
|
using MoneyMap.Services;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace MoneyMap.Pages
|
namespace MoneyMap.Pages
|
||||||
{
|
{
|
||||||
public class MerchantsModel : PageModel
|
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<MerchantRow> Merchants { get; set; } = new();
|
public List<MerchantRow> Merchants { get; set; } = new();
|
||||||
@@ -40,9 +41,7 @@ namespace MoneyMap.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if merchant already exists
|
// Check if merchant already exists
|
||||||
var existing = await _db.Merchants
|
var existing = await _merchantService.FindByNameAsync(model.Name);
|
||||||
.FirstOrDefaultAsync(m => m.Name == model.Name.Trim());
|
|
||||||
|
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
ErrorMessage = $"Merchant '{model.Name}' already exists.";
|
ErrorMessage = $"Merchant '{model.Name}' already exists.";
|
||||||
@@ -50,13 +49,7 @@ namespace MoneyMap.Pages
|
|||||||
return Page();
|
return Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
var merchant = new Merchant
|
var merchant = await _merchantService.GetOrCreateAsync(model.Name);
|
||||||
{
|
|
||||||
Name = model.Name.Trim()
|
|
||||||
};
|
|
||||||
|
|
||||||
_db.Merchants.Add(merchant);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
SuccessMessage = $"Added merchant '{merchant.Name}'.";
|
SuccessMessage = $"Added merchant '{merchant.Name}'.";
|
||||||
return RedirectToPage();
|
return RedirectToPage();
|
||||||
@@ -71,68 +64,48 @@ namespace MoneyMap.Pages
|
|||||||
return Page();
|
return Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
var merchant = await _db.Merchants.FindAsync(model.Id);
|
var result = await _merchantService.UpdateMerchantAsync(model.Id, model.Name);
|
||||||
if (merchant == null)
|
|
||||||
|
if (result.Success)
|
||||||
{
|
{
|
||||||
ErrorMessage = "Merchant not found.";
|
SuccessMessage = result.Message;
|
||||||
return RedirectToPage();
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
ErrorMessage = $"Merchant '{model.Name}' already exists.";
|
ErrorMessage = result.Message;
|
||||||
await LoadDataAsync();
|
await LoadDataAsync();
|
||||||
return Page();
|
return Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
merchant.Name = model.Name.Trim();
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
SuccessMessage = "Merchant updated successfully.";
|
|
||||||
return RedirectToPage();
|
return RedirectToPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostDeleteMerchantAsync(int id)
|
public async Task<IActionResult> OnPostDeleteMerchantAsync(int id)
|
||||||
{
|
{
|
||||||
var merchant = await _db.Merchants
|
var result = await _merchantService.DeleteMerchantAsync(id);
|
||||||
.Include(m => m.Transactions)
|
|
||||||
.Include(m => m.CategoryMappings)
|
|
||||||
.FirstOrDefaultAsync(m => m.Id == id);
|
|
||||||
|
|
||||||
if (merchant == null)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
ErrorMessage = "Merchant not found.";
|
SuccessMessage = result.Message;
|
||||||
return RedirectToPage();
|
}
|
||||||
|
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();
|
return RedirectToPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadDataAsync()
|
private async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
var merchants = await _db.Merchants
|
var merchantsWithStats = await _merchantService.GetAllMerchantsWithStatsAsync();
|
||||||
.Include(m => m.Transactions)
|
|
||||||
.Include(m => m.CategoryMappings)
|
|
||||||
.OrderBy(m => m.Name)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
Merchants = merchants.Select(m => new MerchantRow
|
Merchants = merchantsWithStats.Select(m => new MerchantRow
|
||||||
{
|
{
|
||||||
Id = m.Id,
|
Id = m.Id,
|
||||||
Name = m.Name,
|
Name = m.Name,
|
||||||
TransactionCount = m.Transactions.Count,
|
TransactionCount = m.TransactionCount,
|
||||||
MappingCount = m.CategoryMappings.Count
|
MappingCount = m.MappingCount
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
TotalMerchants = Merchants.Count;
|
TotalMerchants = Merchants.Count;
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ namespace MoneyMap.Pages
|
|||||||
{
|
{
|
||||||
private readonly MoneyMapContext _db;
|
private readonly MoneyMapContext _db;
|
||||||
private readonly ITransactionCategorizer _categorizer;
|
private readonly ITransactionCategorizer _categorizer;
|
||||||
|
private readonly ITransactionStatisticsService _statsService;
|
||||||
|
|
||||||
public RecategorizeModel(MoneyMapContext db, ITransactionCategorizer categorizer)
|
public RecategorizeModel(MoneyMapContext db, ITransactionCategorizer categorizer, ITransactionStatisticsService statsService)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_categorizer = categorizer;
|
_categorizer = categorizer;
|
||||||
|
_statsService = statsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecategorizeStats Stats { get; set; } = new();
|
public RecategorizeStats Stats { get; set; } = new();
|
||||||
@@ -47,16 +49,13 @@ namespace MoneyMap.Pages
|
|||||||
|
|
||||||
private async Task LoadStatsAsync()
|
private async Task LoadStatsAsync()
|
||||||
{
|
{
|
||||||
var totalTransactions = await _db.Transactions.CountAsync();
|
var categorizationStats = await _statsService.GetCategorizationStatsAsync();
|
||||||
var uncategorized = await _db.Transactions
|
|
||||||
.CountAsync(t => string.IsNullOrWhiteSpace(t.Category));
|
|
||||||
var categorized = totalTransactions - uncategorized;
|
|
||||||
|
|
||||||
Stats = new RecategorizeStats
|
Stats = new RecategorizeStats
|
||||||
{
|
{
|
||||||
TotalTransactions = totalTransactions,
|
TotalTransactions = categorizationStats.TotalTransactions,
|
||||||
Categorized = categorized,
|
Categorized = categorizationStats.Categorized,
|
||||||
Uncategorized = uncategorized
|
Uncategorized = categorizationStats.Uncategorized
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,21 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MoneyMap.Data;
|
using MoneyMap.Data;
|
||||||
using MoneyMap.Models;
|
using MoneyMap.Models;
|
||||||
|
using MoneyMap.Services;
|
||||||
|
|
||||||
namespace MoneyMap.Pages
|
namespace MoneyMap.Pages
|
||||||
{
|
{
|
||||||
public class TransactionsModel : PageModel
|
public class TransactionsModel : PageModel
|
||||||
{
|
{
|
||||||
private readonly MoneyMapContext _db;
|
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;
|
_db = db;
|
||||||
|
_statsService = statsService;
|
||||||
|
_referenceDataService = referenceDataService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BindProperty(SupportsGet = true)]
|
[BindProperty(SupportsGet = true)]
|
||||||
@@ -147,33 +152,17 @@ namespace MoneyMap.Pages
|
|||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
// Calculate stats for filtered results (all pages, not just current)
|
// Calculate stats for filtered results (all pages, not just current)
|
||||||
var allFilteredTransactions = await query.ToListAsync();
|
Stats = await _statsService.CalculateStatsAsync(query);
|
||||||
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get available categories for filter dropdown
|
// Get available categories for filter dropdown
|
||||||
AvailableCategories = await _db.Transactions
|
AvailableCategories = await _referenceDataService.GetAvailableCategoriesAsync();
|
||||||
.Select(t => t.Category ?? "")
|
|
||||||
.Distinct()
|
|
||||||
.OrderBy(c => c)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
// Get available merchants for filter dropdown
|
// Get available merchants for filter dropdown
|
||||||
AvailableMerchants = await _db.Merchants
|
var merchants = await _referenceDataService.GetAvailableMerchantsAsync();
|
||||||
.OrderBy(m => m.Name)
|
AvailableMerchants = merchants.Select(m => m.Name).ToList();
|
||||||
.Select(m => m.Name)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
// Get available cards for filter dropdown
|
// Get available cards for filter dropdown
|
||||||
AvailableCards = await _db.Cards
|
AvailableCards = await _referenceDataService.GetAvailableCardsAsync(includeAccount: false);
|
||||||
.OrderBy(c => c.Owner)
|
|
||||||
.ThenBy(c => c.Last4)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TransactionRow
|
public class TransactionRow
|
||||||
@@ -189,13 +178,5 @@ namespace MoneyMap.Pages
|
|||||||
public string AccountLabel { get; set; } = "";
|
public string AccountLabel { get; set; } = "";
|
||||||
public int ReceiptCount { 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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user