From c09a8c36a89e461b3176c91fad327edabf9a51fa Mon Sep 17 00:00:00 2001 From: AJ Date: Sat, 25 Oct 2025 23:08:13 -0400 Subject: [PATCH] Feature: expand MerchantService with full CRUD operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend MerchantService with additional operations: - GetMerchantByIdAsync: Retrieve merchant with optional related data - GetAllMerchantsWithStatsAsync: Get all merchants with transaction/mapping counts - UpdateMerchantAsync: Update merchant name with duplicate validation - DeleteMerchantAsync: Delete merchant (unlinks transactions and mappings) Includes DTOs for merchant stats, update results, and delete results. Consolidates merchant management logic previously scattered across PageModels. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- MoneyMap/Services/MerchantService.cs | 126 +++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/MoneyMap/Services/MerchantService.cs b/MoneyMap/Services/MerchantService.cs index 1f1dab1..e819ba3 100644 --- a/MoneyMap/Services/MerchantService.cs +++ b/MoneyMap/Services/MerchantService.cs @@ -9,6 +9,10 @@ namespace MoneyMap.Services Task FindByNameAsync(string name); Task GetOrCreateAsync(string name); Task GetOrCreateIdAsync(string? name); + Task GetMerchantByIdAsync(int id, bool includeRelated = false); + Task> GetAllMerchantsWithStatsAsync(); + Task UpdateMerchantAsync(int id, string newName); + Task DeleteMerchantAsync(int id); } public class MerchantService : IMerchantService @@ -54,5 +58,127 @@ namespace MoneyMap.Services var merchant = await GetOrCreateAsync(name); return merchant.Id; } + + public async Task GetMerchantByIdAsync(int id, bool includeRelated = false) + { + var query = _db.Merchants.AsQueryable(); + + if (includeRelated) + { + query = query + .Include(m => m.Transactions) + .Include(m => m.CategoryMappings); + } + + return await query.FirstOrDefaultAsync(m => m.Id == id); + } + + public async Task> GetAllMerchantsWithStatsAsync() + { + var merchants = await _db.Merchants + .Include(m => m.Transactions) + .Include(m => m.CategoryMappings) + .OrderBy(m => m.Name) + .ToListAsync(); + + return merchants.Select(m => new MerchantWithStats + { + Id = m.Id, + Name = m.Name, + TransactionCount = m.Transactions.Count, + MappingCount = m.CategoryMappings.Count + }).ToList(); + } + + public async Task UpdateMerchantAsync(int id, string newName) + { + var merchant = await _db.Merchants.FindAsync(id); + if (merchant == null) + { + return new MerchantUpdateResult + { + Success = false, + Message = "Merchant not found." + }; + } + + var trimmedName = newName.Trim(); + + // Check if another merchant with the same name exists + var existing = await _db.Merchants + .FirstOrDefaultAsync(m => m.Name == trimmedName && m.Id != id); + + if (existing != null) + { + return new MerchantUpdateResult + { + Success = false, + Message = $"Merchant '{trimmedName}' already exists." + }; + } + + merchant.Name = trimmedName; + await _db.SaveChangesAsync(); + + return new MerchantUpdateResult + { + Success = true, + Message = "Merchant updated successfully." + }; + } + + public async Task DeleteMerchantAsync(int id) + { + var merchant = await _db.Merchants + .Include(m => m.Transactions) + .Include(m => m.CategoryMappings) + .FirstOrDefaultAsync(m => m.Id == id); + + if (merchant == null) + { + return new MerchantDeleteResult + { + Success = false, + Message = "Merchant not found." + }; + } + + var transactionCount = merchant.Transactions.Count; + var mappingCount = merchant.CategoryMappings.Count; + + _db.Merchants.Remove(merchant); + await _db.SaveChangesAsync(); + + return new MerchantDeleteResult + { + Success = true, + Message = $"Deleted merchant '{merchant.Name}'. {transactionCount} transactions and {mappingCount} category mappings are now unlinked.", + TransactionCount = transactionCount, + MappingCount = mappingCount + }; + } + } + + // DTOs + public class MerchantWithStats + { + public int Id { get; set; } + public string Name { get; set; } = ""; + public int TransactionCount { get; set; } + public int MappingCount { get; set; } + } + + public class MerchantUpdateResult + { + public bool Success { get; set; } + public string Message { get; set; } = ""; + } + + public class MerchantDeleteResult + { + public bool Success { get; set; } + public string Message { get; set; } = ""; + public int TransactionCount { get; set; } + public int MappingCount { get; set; } } }