using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Purrse.Core.DTOs; using Purrse.Core.Enums; using Purrse.Core.Models; using Purrse.Data; using System.Security.Claims; namespace Purrse.Api.Controllers; [Authorize] [ApiController] [Route("api/[controller]")] public class ReconciliationController : ControllerBase { private readonly PurrseDbContext _db; private Guid UserId => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); public ReconciliationController(PurrseDbContext db) => _db = db; [HttpPost("{accountId}/start")] public async Task> Start(Guid accountId, StartReconciliationRequest request) { var account = await _db.Accounts.FirstOrDefaultAsync(a => a.Id == accountId && a.UserId == UserId) ?? throw new KeyNotFoundException("Account not found"); var reconciliation = new Reconciliation { Id = Guid.NewGuid(), AccountId = accountId, StatementDate = request.StatementDate, StatementBalance = request.StatementBalance }; _db.Reconciliations.Add(reconciliation); await _db.SaveChangesAsync(); var uncleared = await _db.Transactions .Include(t => t.Category) .Where(t => t.AccountId == accountId && t.Status != TransactionStatus.Reconciled && !t.IsVoid) .OrderByDescending(t => t.Date) .Select(t => new TransactionResponse(t.Id, t.AccountId, null, t.Date, t.Amount, t.PayeeId, t.PayeeName, t.CategoryId, t.Category != null ? t.Category.Name : null, t.Memo, t.ReferenceNumber, t.CheckNumber, t.Type, t.Status, t.IsVoid, t.TransferTransactionId, t.ImportBatchId, null, t.CreatedAt)) .ToListAsync(); var clearedBalance = await _db.Transactions .Where(t => t.AccountId == accountId && t.Status == TransactionStatus.Cleared && !t.IsVoid) .SumAsync(t => t.Amount); return Ok(new ReconciliationResponse(reconciliation.Id, accountId, request.StatementDate, request.StatementBalance, clearedBalance, request.StatementBalance - clearedBalance, false, uncleared)); } [HttpPost("{accountId}/complete")] public async Task Complete(Guid accountId, [FromQuery] Guid reconciliationId) { var rec = await _db.Reconciliations.FirstOrDefaultAsync(r => r.Id == reconciliationId && r.AccountId == accountId) ?? throw new KeyNotFoundException("Reconciliation not found"); var clearedTxns = await _db.Transactions .Where(t => t.AccountId == accountId && t.Status == TransactionStatus.Cleared && !t.IsVoid) .ToListAsync(); foreach (var txn in clearedTxns) { txn.Status = TransactionStatus.Reconciled; txn.ReconciliationId = reconciliationId; } rec.IsCompleted = true; rec.CompletedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); return NoContent(); } [HttpPost("{accountId}/cancel")] public async Task Cancel(Guid accountId, [FromQuery] Guid reconciliationId) { var rec = await _db.Reconciliations.FirstOrDefaultAsync(r => r.Id == reconciliationId && r.AccountId == accountId && !r.IsCompleted) ?? throw new KeyNotFoundException("Reconciliation not found"); _db.Reconciliations.Remove(rec); await _db.SaveChangesAsync(); return NoContent(); } }