From 004f99c2b493fc17599870ac7078f484314b205d Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 20 Apr 2026 20:34:07 -0400 Subject: [PATCH] feat(api): add BudgetsController with status, create, update endpoints Co-Authored-By: Claude Opus 4.6 --- MoneyMap/Controllers/BudgetsController.cs | 103 ++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 MoneyMap/Controllers/BudgetsController.cs diff --git a/MoneyMap/Controllers/BudgetsController.cs b/MoneyMap/Controllers/BudgetsController.cs new file mode 100644 index 0000000..1ee8c54 --- /dev/null +++ b/MoneyMap/Controllers/BudgetsController.cs @@ -0,0 +1,103 @@ +using Microsoft.AspNetCore.Mvc; +using MoneyMap.Models; +using MoneyMap.Services; + +namespace MoneyMap.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class BudgetsController : ControllerBase +{ + private readonly IBudgetService _budgetService; + + public BudgetsController(IBudgetService budgetService) => _budgetService = budgetService; + + [HttpGet("status")] + public async Task GetStatus([FromQuery] string? asOfDate = null) + { + DateTime? date = null; + if (!string.IsNullOrWhiteSpace(asOfDate) && DateTime.TryParse(asOfDate, out var parsed)) + date = parsed; + + var statuses = await _budgetService.GetAllBudgetStatusesAsync(date); + + var result = statuses.Select(s => new + { + s.Budget.Id, + Category = s.Budget.DisplayName, + s.Budget.Amount, + Period = s.Budget.Period.ToString(), + s.PeriodStart, + s.PeriodEnd, + s.Spent, + s.Remaining, + PercentUsed = Math.Round(s.PercentUsed, 1), + s.IsOverBudget + }).ToList(); + + return Ok(result); + } + + [HttpPost] + public async Task Create([FromBody] CreateBudgetRequest request) + { + if (!Enum.TryParse(request.Period, true, out var budgetPeriod)) + return BadRequest(new { message = $"Invalid period '{request.Period}'. Must be Weekly, Monthly, or Yearly." }); + + if (!DateTime.TryParse(request.StartDate, out var startDate)) + return BadRequest(new { message = "Invalid start date format" }); + + var budget = new Budget + { + Category = request.Category, + Amount = request.Amount, + Period = budgetPeriod, + StartDate = startDate, + IsActive = true + }; + + var result = await _budgetService.CreateBudgetAsync(budget); + + return Ok(new { result.Success, result.Message, BudgetId = budget.Id }); + } + + [HttpPut("{id}")] + public async Task Update(int id, [FromBody] UpdateBudgetRequest request) + { + var budget = await _budgetService.GetBudgetByIdAsync(id); + if (budget == null) + return NotFound(new { message = "Budget not found" }); + + if (request.Amount.HasValue) + budget.Amount = request.Amount.Value; + + if (!string.IsNullOrWhiteSpace(request.Period)) + { + if (!Enum.TryParse(request.Period, true, out var budgetPeriod)) + return BadRequest(new { message = $"Invalid period '{request.Period}'. Must be Weekly, Monthly, or Yearly." }); + budget.Period = budgetPeriod; + } + + if (request.IsActive.HasValue) + budget.IsActive = request.IsActive.Value; + + var result = await _budgetService.UpdateBudgetAsync(budget); + + return Ok(new { result.Success, result.Message }); + } +} + +public class CreateBudgetRequest +{ + public string? Category { get; set; } + public decimal Amount { get; set; } + public string Period { get; set; } = ""; + public string StartDate { get; set; } = ""; +} + +public class UpdateBudgetRequest +{ + public decimal? Amount { get; set; } + public string? Period { get; set; } + public bool? IsActive { get; set; } +}