using Asentrafin.Api.Contracts.Transactions; using Asentrafin.Api.Data; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Asentrafin.Api.Controllers; [ApiController] [Route("api/transactions")] public class TransactionAnalyticsController(AppDbContext dbContext) : ControllerBase { [HttpGet("categories")] public async Task>> GetCategories( CancellationToken cancellationToken) { var categories = await dbContext.TransactionCategories .OrderBy(category => category.SortOrder) .ThenBy(category => category.Name) .Select(category => new TransactionCategoryResponse( category.Id, category.ParentId, category.Name, category.Slug, category.PlaidPrimary, category.PlaidDetailed, category.IsSystem)) .ToListAsync(cancellationToken); return Ok(categories); } [HttpGet("category-summaries")] public async Task>> GetCategorySummaries( [FromQuery] DateOnly? startDate, [FromQuery] DateOnly? endDate, [FromQuery] Guid? accountId, [FromQuery] bool includePending = false, CancellationToken cancellationToken = default) { var query = dbContext.PlaidTransactions .AsNoTracking() .Where(transaction => transaction.RemovedAt == null); if (!includePending) { query = query.Where(transaction => !transaction.Pending); } if (startDate is not null) { query = query.Where(transaction => transaction.Date >= startDate.Value); } if (endDate is not null) { query = query.Where(transaction => transaction.Date <= endDate.Value); } if (accountId is not null) { query = query.Where(transaction => transaction.PlaidAccountId == accountId.Value); } var summaries = await query .Where(transaction => transaction.Classification != null) .GroupBy(transaction => new { transaction.Classification!.Category.Id, transaction.Classification.Category.Name, transaction.Classification.Category.Slug }) .Select(group => new CategorySummaryResponse( group.Key.Id, group.Key.Name, group.Key.Slug, group.Count(), group.Sum(transaction => transaction.Amount))) .OrderByDescending(summary => Math.Abs(summary.TotalAmount)) .ThenBy(summary => summary.CategoryName) .ToListAsync(cancellationToken); return Ok(summaries); } }