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:
@@ -15,6 +15,8 @@ public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
|
||||
public DbSet<OfferingSession> OfferingSessions => Set<OfferingSession>();
|
||||
public DbSet<Giving> Givings => Set<Giving>();
|
||||
public DbSet<Ministry> Ministries => Set<Ministry>();
|
||||
public DbSet<ExpenseCategoryGroup> ExpenseCategoryGroups => Set<ExpenseCategoryGroup>();
|
||||
public DbSet<ExpenseSubCategory> ExpenseSubCategories => Set<ExpenseSubCategory>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
@@ -150,5 +152,25 @@ public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
|
||||
entity.Property(e => e.Name_en).HasMaxLength(200).IsRequired();
|
||||
entity.Property(e => e.Name_zh).HasMaxLength(200);
|
||||
});
|
||||
|
||||
// ── ExpenseCategoryGroup ─────────────────────────────────────────────
|
||||
builder.Entity<ExpenseCategoryGroup>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Name_en).HasMaxLength(200).IsRequired();
|
||||
entity.Property(e => e.Name_zh).HasMaxLength(200);
|
||||
entity.Property(e => e.CreatedBy).HasMaxLength(450);
|
||||
entity.Property(e => e.UpdatedBy).HasMaxLength(450);
|
||||
});
|
||||
|
||||
// ── ExpenseSubCategory ───────────────────────────────────────────────
|
||||
builder.Entity<ExpenseSubCategory>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Name_en).HasMaxLength(200).IsRequired();
|
||||
entity.Property(e => e.Name_zh).HasMaxLength(200);
|
||||
entity.Property(e => e.CreatedBy).HasMaxLength(450);
|
||||
entity.Property(e => e.UpdatedBy).HasMaxLength(450);
|
||||
entity.HasOne(e => e.Group).WithMany(g => g.SubCategories)
|
||||
.HasForeignKey(e => e.GroupId).OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user