@@ -67,14 +67,20 @@ public class ExpenseServiceTests
|
||||
|
||||
private static CreateExpenseRequest Reimb() => new()
|
||||
{
|
||||
Type = "StaffReimbursement", MinistryId = 1, CategoryGroupId = 1, SubCategoryId = 1,
|
||||
Amount = 45.50m, Description = "Batteries", ExpenseDate = new DateOnly(2026, 5, 28),
|
||||
Type = "StaffReimbursement", MinistryId = 1,
|
||||
Lines = { new ExpenseLineInput { CategoryGroupId = 1, SubCategoryId = 1, Amount = 45.50m } },
|
||||
Description = "Batteries", ExpenseDate = new DateOnly(2026, 5, 28),
|
||||
};
|
||||
|
||||
private static UpdateExpenseRequest CloneToUpdate(CreateExpenseRequest r) => new()
|
||||
{
|
||||
Type = r.Type, MinistryId = r.MinistryId, CategoryGroupId = r.CategoryGroupId,
|
||||
SubCategoryId = r.SubCategoryId, Amount = r.Amount, Description = r.Description,
|
||||
Type = r.Type, MinistryId = r.MinistryId,
|
||||
Lines = r.Lines.Select(l => new ExpenseLineInput
|
||||
{
|
||||
CategoryGroupId = l.CategoryGroupId, SubCategoryId = l.SubCategoryId,
|
||||
Amount = l.Amount, FunctionalClass = l.FunctionalClass, Description = l.Description,
|
||||
}).ToList(),
|
||||
Description = r.Description,
|
||||
VendorName = r.VendorName, MemberId = r.MemberId, CheckNumber = r.CheckNumber,
|
||||
ExpenseDate = r.ExpenseDate, Notes = r.Notes,
|
||||
};
|
||||
@@ -207,7 +213,7 @@ public class ExpenseServiceTests
|
||||
Assert.Equal("PendingApproval", (await db.Expenses.FindAsync(id))!.Status);
|
||||
|
||||
var edit = CloneToUpdate(Reimb());
|
||||
edit.Amount = 99.99m;
|
||||
edit.Lines[0].Amount = 99.99m;
|
||||
await svc.UpdateAsync(id, edit, isFinance: false);
|
||||
|
||||
var e = await db.Expenses.FindAsync(id);
|
||||
@@ -260,13 +266,70 @@ public class ExpenseServiceTests
|
||||
|
||||
var id = await svc.CreateAsync(new CreateExpenseRequest
|
||||
{
|
||||
Type = "VendorPayment", MinistryId = 1, CategoryGroupId = 1, SubCategoryId = 1,
|
||||
Amount = 50m, Description = "x", ExpenseDate = new DateOnly(2026, 5, 1),
|
||||
FunctionalClass = "ManagementGeneral",
|
||||
Type = "VendorPayment", MinistryId = 1,
|
||||
Lines = { new ExpenseLineInput { CategoryGroupId = 1, SubCategoryId = 1, Amount = 50m, FunctionalClass = "ManagementGeneral" } },
|
||||
Description = "x", ExpenseDate = new DateOnly(2026, 5, 1),
|
||||
}, isFinance: true);
|
||||
|
||||
var dto = await svc.GetByIdAsync(id);
|
||||
Assert.Equal("ManagementGeneral", dto!.FunctionalClass);
|
||||
Assert.Equal("ManagementGeneral", dto!.Lines.Single().FunctionalClass);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_MultiLine_SetsHeaderTotal_AndRoundTripsLines()
|
||||
{
|
||||
var (svc, db, _) = Build("u1");
|
||||
db.ExpenseCategoryGroups.Add(new ExpenseCategoryGroup { Id = 2, Name_en = "Food & Beverage" });
|
||||
db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 2, GroupId = 2, Name_en = "Snacks" });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var r = new CreateExpenseRequest
|
||||
{
|
||||
Type = "VendorPayment", MinistryId = 1, VendorName = "Costco",
|
||||
Description = "Mixed invoice", ExpenseDate = new DateOnly(2026, 5, 1),
|
||||
Lines =
|
||||
{
|
||||
new ExpenseLineInput { CategoryGroupId = 1, SubCategoryId = 1, Amount = 30m },
|
||||
new ExpenseLineInput { CategoryGroupId = 2, SubCategoryId = 2, Amount = 12.50m },
|
||||
},
|
||||
};
|
||||
var id = await svc.CreateAsync(r, isFinance: true);
|
||||
|
||||
Assert.Equal(42.50m, (await db.Expenses.FindAsync(id))!.Amount);
|
||||
var dto = await svc.GetByIdAsync(id);
|
||||
Assert.Equal(2, dto!.Lines.Count);
|
||||
Assert.Equal(42.50m, dto.Amount);
|
||||
Assert.Equal(2, dto.LineCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_WithNoLines_Throws()
|
||||
{
|
||||
var (svc, _, _) = Build("u1");
|
||||
var r = Reimb(); r.Lines.Clear();
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => svc.CreateAsync(r, isFinance: false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Update_ReplacesLines_AndRecomputesTotal()
|
||||
{
|
||||
var (svc, db, _) = Build("alice");
|
||||
db.ExpenseCategoryGroups.Add(new ExpenseCategoryGroup { Id = 2, Name_en = "Food & Beverage" });
|
||||
db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 2, GroupId = 2, Name_en = "Snacks" });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var id = await svc.CreateAsync(Reimb(), isFinance: false);
|
||||
|
||||
var edit = CloneToUpdate(Reimb());
|
||||
edit.Lines = new()
|
||||
{
|
||||
new ExpenseLineInput { CategoryGroupId = 1, SubCategoryId = 1, Amount = 10m },
|
||||
new ExpenseLineInput { CategoryGroupId = 2, SubCategoryId = 2, Amount = 5m },
|
||||
};
|
||||
await svc.UpdateAsync(id, edit, isFinance: false);
|
||||
|
||||
Assert.Equal(15m, (await db.Expenses.FindAsync(id))!.Amount);
|
||||
Assert.Equal(2, await db.ExpenseLines.CountAsync(l => l.ExpenseId == id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user