diff --git a/MoneyMap/Controllers/MerchantsController.cs b/MoneyMap/Controllers/MerchantsController.cs new file mode 100644 index 0000000..ce11d48 --- /dev/null +++ b/MoneyMap/Controllers/MerchantsController.cs @@ -0,0 +1,97 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MoneyMap.Data; + +namespace MoneyMap.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class MerchantsController : ControllerBase +{ + private readonly MoneyMapContext _db; + + public MerchantsController(MoneyMapContext db) => _db = db; + + [HttpGet] + public async Task List([FromQuery] string? query = null) + { + var q = _db.Merchants + .Include(m => m.Transactions) + .Include(m => m.CategoryMappings) + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(query)) + q = q.Where(m => m.Name.Contains(query)); + + var merchants = await q + .OrderBy(m => m.Name) + .Select(m => new + { + m.Id, + m.Name, + TransactionCount = m.Transactions.Count, + MappingCount = m.CategoryMappings.Count, + Categories = m.CategoryMappings.Select(cm => cm.Category).Distinct().ToList() + }) + .ToListAsync(); + + return Ok(new { Count = merchants.Count, Merchants = merchants }); + } + + [HttpPost("merge")] + public async Task Merge([FromBody] MergeMerchantsRequest request) + { + if (request.SourceMerchantId == request.TargetMerchantId) + return BadRequest(new { message = "Source and target merchant cannot be the same" }); + + var source = await _db.Merchants.FindAsync(request.SourceMerchantId); + var target = await _db.Merchants.FindAsync(request.TargetMerchantId); + + if (source == null) + return NotFound(new { message = $"Source merchant {request.SourceMerchantId} not found" }); + if (target == null) + return NotFound(new { message = $"Target merchant {request.TargetMerchantId} not found" }); + + var transactions = await _db.Transactions + .Where(t => t.MerchantId == request.SourceMerchantId) + .ToListAsync(); + + foreach (var t in transactions) + t.MerchantId = request.TargetMerchantId; + + var sourceMappings = await _db.CategoryMappings + .Where(cm => cm.MerchantId == request.SourceMerchantId) + .ToListAsync(); + + var targetMappingPatterns = await _db.CategoryMappings + .Where(cm => cm.MerchantId == request.TargetMerchantId) + .Select(cm => cm.Pattern) + .ToListAsync(); + + foreach (var mapping in sourceMappings) + { + if (targetMappingPatterns.Contains(mapping.Pattern)) + _db.CategoryMappings.Remove(mapping); + else + mapping.MerchantId = request.TargetMerchantId; + } + + _db.Merchants.Remove(source); + await _db.SaveChangesAsync(); + + return Ok(new + { + Merged = true, + Source = new { source.Id, source.Name }, + Target = new { target.Id, target.Name }, + TransactionsReassigned = transactions.Count, + MappingsReassigned = sourceMappings.Count + }); + } +} + +public class MergeMerchantsRequest +{ + public int SourceMerchantId { get; set; } + public int TargetMerchantId { get; set; } +}