feat(expense): add expense category entities + seed (11 groups / 38 subs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Chen
2026-05-29 18:08:12 -07:00
parent f6f06d841c
commit b3eb9d297a
4 changed files with 90 additions and 0 deletions
+41
View File
@@ -29,6 +29,22 @@ public static class DbSeeder
("Catering", "餐飲", 10),
];
// (GroupEn, GroupZh, Sort, SubItems[(SubEn, SubZh)])
private static readonly (string En, string Zh, int Sort, (string En, string Zh)[] Subs)[] ExpenseCategorySeed =
[
("Equipment", "設備", 1, [("Purchase","購置"),("Rental","租借"),("Maintenance & Repair","維修")]),
("Consumables", "消耗品", 2, [("Batteries","電池"),("Accessories","配件"),("Cleaning Supplies","清潔用品"),("Office Supplies","文具")]),
("Food & Beverage", "餐飲", 3, [("Catering","出餐費用"),("Food Ingredients","食材採購"),("Utensils","器具"),("Consumables","消耗品")]),
("Training", "培訓", 4, [("Course Fees","課程費用"),("Books","書籍"),("Conference","研討會"),("Travel","差旅")]),
("Materials", "教材", 5, [("Printing","印刷費用"),("Craft Supplies","手工材料"),("Copyright & Licensing","版權購買")]),
("Facility", "場地", 6, [("Rent","場地租金"),("Utilities","水電"),("Property Insurance","財產保險"),("Decoration","裝飾")]),
("Printing", "印刷", 7, [("Bulletins","週報"),("Order of Service","程序單"),("Posters","海報")]),
("Missions", "宣教", 8, [("Offering Transfer","奉獻轉帳"),("Missionary Support","宣教士支援"),("Travel","差旅")]),
("Benevolence", "關懷救助", 9, [("Emergency Aid","急難救助"),("Condolence Gifts","慰問禮品"),("Visit Expenses","探訪費用")]),
("Other", "其他", 10, [("Miscellaneous","雜支")]),
("Personnel", "人事", 11, [("Salary & Wages","薪資"),("Payroll Taxes","薪資稅費"),("Employee Benefits","員工福利"),("Workers Compensation","勞工保險"),("Honorarium","酬庸"),("Staff Training","同工進修"),("Contract Labor","外包勞務")]),
];
private static readonly (string Name, string Description)[] Roles =
[
("super_admin", "System administrator — full access"),
@@ -90,6 +106,30 @@ public static class DbSeeder
await db.SaveChangesAsync();
}
public static async Task SeedExpenseCategoriesAsync(AppDbContext db)
{
foreach (var (gEn, gZh, gSort, subs) in ExpenseCategorySeed)
{
var group = await db.ExpenseCategoryGroups.FirstOrDefaultAsync(g => g.Name_en == gEn);
if (group is null)
{
group = new ExpenseCategoryGroup { Name_en = gEn, Name_zh = gZh, SortOrder = gSort, IsActive = true };
db.ExpenseCategoryGroups.Add(group);
await db.SaveChangesAsync(); // assign group.Id
}
var sub = 1;
foreach (var (sEn, sZh) in subs)
{
if (!await db.ExpenseSubCategories.AnyAsync(s => s.GroupId == group.Id && s.Name_en == sEn))
db.ExpenseSubCategories.Add(new ExpenseSubCategory
{ GroupId = group.Id, Name_en = sEn, Name_zh = sZh, SortOrder = sub, IsActive = true });
sub++;
}
}
await db.SaveChangesAsync();
}
/// <summary>
/// Seeds roles and (in Development) the default admin account.
/// Called once on application startup after migrations have been applied.
@@ -105,6 +145,7 @@ public static class DbSeeder
var db = services.GetRequiredService<AppDbContext>();
await SeedGivingCategoriesAsync(db);
await SeedMinistriesAsync(db);
await SeedExpenseCategoriesAsync(db);
if (env.IsDevelopment())
await SeedAdminUserAsync(userManager);