feat(finance): Form 990 Part IX functional-expense aggregation service
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
using ROLAC.API.Data;
|
||||
using ROLAC.API.Data.Interceptors;
|
||||
using ROLAC.API.Entities;
|
||||
using ROLAC.API.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace ROLAC.API.Tests.Services;
|
||||
|
||||
public class Form990ReportServiceTests
|
||||
{
|
||||
private static AppDbContext BuildDb()
|
||||
{
|
||||
var ctx = new DefaultHttpContext { User = new(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "t") })) };
|
||||
var mock = new Mock<IHttpContextAccessor>();
|
||||
mock.Setup(x => x.HttpContext).Returns(ctx);
|
||||
return new AppDbContext(new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.AddInterceptors(new AuditSaveChangesInterceptor(new ROLAC.API.Services.Logging.CurrentUserAccessor(mock.Object))).Options);
|
||||
}
|
||||
|
||||
private static async Task SeedAsync(AppDbContext db)
|
||||
{
|
||||
db.Form990ExpenseLines.Add(new Form990ExpenseLine { Id = 7, LineCode = "7", Name_en = "Salaries", SortOrder = 5 });
|
||||
db.Form990ExpenseLines.Add(new Form990ExpenseLine { Id = 24, LineCode = "24", Name_en = "Other", SortOrder = 21 });
|
||||
db.Ministries.Add(new Ministry { Id = 1, Name_en = "Admin", DefaultFunctionalClass = "ManagementGeneral" });
|
||||
db.Ministries.Add(new Ministry { Id = 2, Name_en = "Worship", DefaultFunctionalClass = "Program" });
|
||||
db.ExpenseCategoryGroups.Add(new ExpenseCategoryGroup { Id = 1, Name_en = "Personnel", Form990LineId = 24 });
|
||||
db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 1, GroupId = 1, Name_en = "Salary", Form990LineId = 7 });
|
||||
db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 2, GroupId = 1, Name_en = "Misc", Form990LineId = null });
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static Expense Exp(int min, int sub, decimal amt, string status, string? fc = null) => new()
|
||||
{
|
||||
MinistryId = min, CategoryGroupId = 1, SubCategoryId = sub, Type = "VendorPayment",
|
||||
Status = status, Amount = amt, Description = "x", ExpenseDate = new DateOnly(2026, 5, 10),
|
||||
FunctionalClass = fc,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task Statement_AggregatesByLineAndFunction_WithFallbackAndUnmappedCount()
|
||||
{
|
||||
using var db = BuildDb();
|
||||
await SeedAsync(db);
|
||||
db.Expenses.Add(Exp(2, 1, 100m, "Paid"));
|
||||
db.Expenses.Add(Exp(1, 1, 40m, "Approved"));
|
||||
db.Expenses.Add(Exp(2, 2, 25m, "Paid"));
|
||||
db.Expenses.Add(Exp(2, 1, 999m, "Draft"));
|
||||
db.Expenses.Add(Exp(1, 1, 10m, "Paid", fc: "Program"));
|
||||
await db.SaveChangesAsync();
|
||||
var svc = new Form990ReportService(db);
|
||||
|
||||
var stmt = await svc.GetFunctionalExpenseStatementAsync(null, null);
|
||||
|
||||
var line7 = stmt.Rows.Single(r => r.LineCode == "7");
|
||||
Assert.Equal(110m, line7.Program);
|
||||
Assert.Equal(40m, line7.ManagementGeneral);
|
||||
Assert.Equal(150m, line7.Total);
|
||||
var line24 = stmt.Rows.Single(r => r.LineCode == "24");
|
||||
Assert.Equal(25m, line24.Program);
|
||||
Assert.Equal(1, stmt.UnmappedExpenseCount);
|
||||
Assert.Equal(175m, stmt.GrandTotal);
|
||||
Assert.Equal(135m, stmt.ProgramTotal);
|
||||
Assert.Equal(40m, stmt.ManagementGeneralTotal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Statement_RespectsDateRange()
|
||||
{
|
||||
using var db = BuildDb();
|
||||
await SeedAsync(db);
|
||||
db.Expenses.Add(Exp(2, 1, 100m, "Paid"));
|
||||
var older = Exp(2, 1, 500m, "Paid"); older.ExpenseDate = new DateOnly(2026, 1, 1);
|
||||
db.Expenses.Add(older);
|
||||
await db.SaveChangesAsync();
|
||||
var svc = new Form990ReportService(db);
|
||||
|
||||
var stmt = await svc.GetFunctionalExpenseStatementAsync(new DateOnly(2026, 5, 1), new DateOnly(2026, 5, 31));
|
||||
Assert.Equal(100m, stmt.GrandTotal);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user