using System.Security.Claims; using Microsoft.EntityFrameworkCore; using ROLAC.API.Data; using ROLAC.API.DTOs.Expense; using ROLAC.API.Entities; namespace ROLAC.API.Services; public class MonthlyStatementService : IMonthlyStatementService { private readonly AppDbContext _db; private readonly IHttpContextAccessor _http; public MonthlyStatementService(AppDbContext db, IHttpContextAccessor http) { _db = db; _http = http; } private string CurrentUserId => _http.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "system"; public async Task> GetAllAsync(int? year) { var query = _db.MonthlyStatements.AsNoTracking().AsQueryable(); if (year.HasValue) query = query.Where(s => s.Year == year.Value); var list = await query.OrderByDescending(s => s.Year).ThenByDescending(s => s.Month).ToListAsync(); return list.Select(Map).ToList(); } public async Task GetByIdAsync(int id) { var s = await _db.MonthlyStatements.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id); return s is null ? null : Map(s); } public async Task CreateAsync(CreateMonthlyStatementRequest r) { if (await _db.MonthlyStatements.AnyAsync(s => s.Year == r.Year && s.Month == r.Month)) throw new InvalidOperationException($"A statement for {r.Year}-{r.Month:D2} already exists."); var s = new MonthlyStatement { Year = r.Year, Month = r.Month, OpeningBalance = r.OpeningBalance, TotalOtherIncome = r.TotalOtherIncome, BankStatementBalance = r.BankStatementBalance, Notes = r.Notes, }; await RecomputeAsync(s); _db.MonthlyStatements.Add(s); await _db.SaveChangesAsync(); return s.Id; } public async Task UpdateAsync(int id, UpdateMonthlyStatementRequest r) { var s = await _db.MonthlyStatements.FindAsync(id) ?? throw new KeyNotFoundException($"MonthlyStatement {id} not found."); if (s.IsFinalized) throw new InvalidOperationException("Statement is finalized and cannot be modified."); s.OpeningBalance = r.OpeningBalance; s.TotalOtherIncome = r.TotalOtherIncome; s.BankStatementBalance = r.BankStatementBalance; s.Notes = r.Notes; await RecomputeAsync(s); await _db.SaveChangesAsync(); } public async Task FinalizeAsync(int id) { var s = await _db.MonthlyStatements.FindAsync(id) ?? throw new KeyNotFoundException($"MonthlyStatement {id} not found."); s.IsFinalized = true; s.FinalizedAt = DateTimeOffset.UtcNow; s.FinalizedBy = CurrentUserId; await _db.SaveChangesAsync(); } private async Task RecomputeAsync(MonthlyStatement s) { var first = new DateOnly(s.Year, s.Month, 1); var next = first.AddMonths(1); s.TotalGiving = await _db.Givings .Where(g => g.GivingDate >= first && g.GivingDate < next) .SumAsync(g => (decimal?)g.Amount) ?? 0m; s.TotalExpenses = await _db.Expenses .Where(e => e.Status == "Paid" && e.ExpenseDate >= first && e.ExpenseDate < next) .SumAsync(e => (decimal?)e.Amount) ?? 0m; s.CalculatedClosingBalance = s.OpeningBalance + s.TotalGiving + s.TotalOtherIncome - s.TotalExpenses; s.Difference = s.CalculatedClosingBalance - s.BankStatementBalance; } private static MonthlyStatementDto Map(MonthlyStatement s) => new() { Id = s.Id, Year = s.Year, Month = s.Month, OpeningBalance = s.OpeningBalance, TotalGiving = s.TotalGiving, TotalOtherIncome = s.TotalOtherIncome, TotalExpenses = s.TotalExpenses, CalculatedClosingBalance = s.CalculatedClosingBalance, BankStatementBalance = s.BankStatementBalance, Difference = s.Difference, Notes = s.Notes, IsFinalized = s.IsFinalized, }; }