Files
ROLAC/API/ROLAC.API/Services/ExpenseCategoryService.cs
T
Chris Chen 015f689d9b feat(expense): add ExpenseCategoryService + tests
TDD cycle: wrote 3 xUnit tests first (red), then implemented
IExpenseCategoryService + ExpenseCategoryService (green).
2026-05-29 18:24:07 -07:00

87 lines
3.5 KiB
C#

using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data;
using ROLAC.API.DTOs.Expense;
using ROLAC.API.Entities;
namespace ROLAC.API.Services;
public class ExpenseCategoryService : IExpenseCategoryService
{
private readonly AppDbContext _db;
public ExpenseCategoryService(AppDbContext db) => _db = db;
public async Task<List<ExpenseCategoryGroupDto>> GetAllAsync(bool includeInactive)
{
var groups = await _db.ExpenseCategoryGroups.AsNoTracking()
.Where(g => includeInactive || g.IsActive)
.OrderBy(g => g.SortOrder).ThenBy(g => g.Name_en)
.ToListAsync();
var subs = await _db.ExpenseSubCategories.AsNoTracking()
.Where(s => includeInactive || s.IsActive)
.OrderBy(s => s.SortOrder).ThenBy(s => s.Name_en)
.ToListAsync();
return groups.Select(g => new ExpenseCategoryGroupDto
{
Id = g.Id, Name_en = g.Name_en, Name_zh = g.Name_zh,
SortOrder = g.SortOrder, IsActive = g.IsActive,
SubCategories = subs.Where(s => s.GroupId == g.Id).Select(s => new ExpenseSubCategoryDto
{
Id = s.Id, GroupId = s.GroupId, Name_en = s.Name_en, Name_zh = s.Name_zh,
SortOrder = s.SortOrder, IsActive = s.IsActive,
}).ToList(),
}).ToList();
}
public async Task<int> CreateGroupAsync(CreateExpenseGroupRequest r)
{
var g = new ExpenseCategoryGroup { Name_en = r.Name_en, Name_zh = r.Name_zh, SortOrder = r.SortOrder, IsActive = true };
_db.ExpenseCategoryGroups.Add(g);
await _db.SaveChangesAsync();
return g.Id;
}
public async Task UpdateGroupAsync(int id, UpdateExpenseGroupRequest r)
{
var g = await _db.ExpenseCategoryGroups.FindAsync(id)
?? throw new KeyNotFoundException($"ExpenseCategoryGroup {id} not found.");
g.Name_en = r.Name_en; g.Name_zh = r.Name_zh; g.SortOrder = r.SortOrder; g.IsActive = r.IsActive;
await _db.SaveChangesAsync();
}
public async Task DeactivateGroupAsync(int id)
{
var g = await _db.ExpenseCategoryGroups.FindAsync(id)
?? throw new KeyNotFoundException($"ExpenseCategoryGroup {id} not found.");
g.IsActive = false;
await _db.SaveChangesAsync();
}
public async Task<int> CreateSubCategoryAsync(CreateExpenseSubCategoryRequest r)
{
var exists = await _db.ExpenseCategoryGroups.AnyAsync(g => g.Id == r.GroupId);
if (!exists) throw new KeyNotFoundException($"ExpenseCategoryGroup {r.GroupId} not found.");
var s = new ExpenseSubCategory { GroupId = r.GroupId, Name_en = r.Name_en, Name_zh = r.Name_zh, SortOrder = r.SortOrder, IsActive = true };
_db.ExpenseSubCategories.Add(s);
await _db.SaveChangesAsync();
return s.Id;
}
public async Task UpdateSubCategoryAsync(int id, UpdateExpenseSubCategoryRequest r)
{
var s = await _db.ExpenseSubCategories.FindAsync(id)
?? throw new KeyNotFoundException($"ExpenseSubCategory {id} not found.");
s.GroupId = r.GroupId; s.Name_en = r.Name_en; s.Name_zh = r.Name_zh; s.SortOrder = r.SortOrder; s.IsActive = r.IsActive;
await _db.SaveChangesAsync();
}
public async Task DeactivateSubCategoryAsync(int id)
{
var s = await _db.ExpenseSubCategories.FindAsync(id)
?? throw new KeyNotFoundException($"ExpenseSubCategory {id} not found.");
s.IsActive = false;
await _db.SaveChangesAsync();
}
}