diff --git a/API/ROLAC.API.Tests/Services/ExpenseServiceTests.cs b/API/ROLAC.API.Tests/Services/ExpenseServiceTests.cs index cac275a..ed7d225 100644 --- a/API/ROLAC.API.Tests/Services/ExpenseServiceTests.cs +++ b/API/ROLAC.API.Tests/Services/ExpenseServiceTests.cs @@ -248,6 +248,27 @@ public class ExpenseServiceTests Assert.Null(await db.Expenses.FirstOrDefaultAsync(e => e.Id == id)); } + [Fact] + public async Task Create_PersistsFunctionalClass_AndGetReturnsIt() + { + var db = BuildDb("u1"); + db.Ministries.Add(new ROLAC.API.Entities.Ministry { Id = 1, Name_en = "Admin" }); + db.ExpenseCategoryGroups.Add(new ROLAC.API.Entities.ExpenseCategoryGroup { Id = 1, Name_en = "Other" }); + db.ExpenseSubCategories.Add(new ROLAC.API.Entities.ExpenseSubCategory { Id = 1, GroupId = 1, Name_en = "Misc" }); + await db.SaveChangesAsync(); + var svc = SvcAs(db, new FakeStorage(), "u1"); + + 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", + }, isFinance: true); + + var dto = await svc.GetByIdAsync(id); + Assert.Equal("ManagementGeneral", dto!.FunctionalClass); + } + [Fact] public async Task Receipt_SaveThenOpen_RoundTrips() { diff --git a/API/ROLAC.API/DTOs/Expense/ExpenseDtos.cs b/API/ROLAC.API/DTOs/Expense/ExpenseDtos.cs index ec1f434..c4ef865 100644 --- a/API/ROLAC.API/DTOs/Expense/ExpenseDtos.cs +++ b/API/ROLAC.API/DTOs/Expense/ExpenseDtos.cs @@ -20,6 +20,7 @@ public class ExpenseListItemDto public string ExpenseDate { get; set; } = ""; // yyyy-MM-dd public bool HasReceipt { get; set; } public string? CheckNumber { get; set; } + public string? FunctionalClass { get; set; } } public class ExpenseDto : ExpenseListItemDto @@ -45,6 +46,7 @@ public class CreateExpenseRequest [MaxLength(50)] public string? CheckNumber { get; set; } [Required] public DateOnly ExpenseDate { get; set; } public string? Notes { get; set; } + [MaxLength(20)] public string? FunctionalClass { get; set; } } public class UpdateExpenseRequest : CreateExpenseRequest { } diff --git a/API/ROLAC.API/Data/AppDbContext.cs b/API/ROLAC.API/Data/AppDbContext.cs index ffec6ab..738c3f0 100644 --- a/API/ROLAC.API/Data/AppDbContext.cs +++ b/API/ROLAC.API/Data/AppDbContext.cs @@ -246,6 +246,7 @@ public class AppDbContext : IdentityDbContext entity.Property(e => e.Type).HasMaxLength(30).IsRequired(); entity.Property(e => e.Status).HasMaxLength(30).HasDefaultValue("Draft"); + entity.Property(e => e.FunctionalClass).HasMaxLength(20); entity.Property(e => e.Amount).HasColumnType("decimal(18,2)"); entity.Property(e => e.Description).HasMaxLength(500).IsRequired(); entity.Property(e => e.VendorName).HasMaxLength(200); diff --git a/API/ROLAC.API/Entities/Expense.cs b/API/ROLAC.API/Entities/Expense.cs index 6975628..1944aef 100644 --- a/API/ROLAC.API/Entities/Expense.cs +++ b/API/ROLAC.API/Entities/Expense.cs @@ -9,6 +9,7 @@ public class Expense : SoftDeleteEntity, IAuditable public int SubCategoryId { get; set; } public string Type { get; set; } = "StaffReimbursement"; // VendorPayment | StaffReimbursement public string Status { get; set; } = "Draft"; // see state machine + public string? FunctionalClass { get; set; } // null = inherit Ministry.DefaultFunctionalClass public decimal Amount { get; set; } public string Description { get; set; } = null!; public string? VendorName { get; set; } diff --git a/API/ROLAC.API/Services/ExpenseService.cs b/API/ROLAC.API/Services/ExpenseService.cs index e81eefa..2416453 100644 --- a/API/ROLAC.API/Services/ExpenseService.cs +++ b/API/ROLAC.API/Services/ExpenseService.cs @@ -97,6 +97,7 @@ public class ExpenseService : IExpenseService ExpenseDate = e.ExpenseDate.ToString("yyyy-MM-dd"), HasReceipt = e.ReceiptBlobPath != null, CheckNumber = e.CheckNumber, + FunctionalClass = e.FunctionalClass, }).ToList(); return new PagedResult { Items = items, TotalCount = total, Page = page, PageSize = pageSize }; @@ -122,6 +123,7 @@ public class ExpenseService : IExpenseService ExpenseDate = e.ExpenseDate.ToString("yyyy-MM-dd"), HasReceipt = e.ReceiptBlobPath != null, CheckNumber = e.CheckNumber, Notes = e.Notes, ReviewNotes = e.ReviewNotes, SubmittedBy = e.SubmittedBy, SubmittedAt = e.SubmittedAt, ReviewedAt = e.ReviewedAt, PaidAt = e.PaidAt, + FunctionalClass = e.FunctionalClass, }; } @@ -132,6 +134,7 @@ public class ExpenseService : IExpenseService MinistryId = r.MinistryId, CategoryGroupId = r.CategoryGroupId, SubCategoryId = r.SubCategoryId, Type = r.Type, Amount = r.Amount, Description = r.Description, VendorName = r.VendorName, CheckNumber = r.CheckNumber, ExpenseDate = r.ExpenseDate, Notes = r.Notes, + FunctionalClass = r.FunctionalClass, }; if (r.Type == "VendorPayment") @@ -179,7 +182,7 @@ public class ExpenseService : IExpenseService e.MinistryId = r.MinistryId; e.CategoryGroupId = r.CategoryGroupId; e.SubCategoryId = r.SubCategoryId; e.Amount = r.Amount; e.Description = r.Description; e.CheckNumber = r.CheckNumber; - e.ExpenseDate = r.ExpenseDate; e.Notes = r.Notes; + e.ExpenseDate = r.ExpenseDate; e.Notes = r.Notes; e.FunctionalClass = r.FunctionalClass; if (e.Type == "VendorPayment") e.VendorName = r.VendorName; await _db.SaveChangesAsync(); }