feat(api): add MerchantsController with list and merge endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<IActionResult> 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<IActionResult> 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; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user