feat(seed): seed Form 990 line catalog and default subcategory mappings
This commit is contained in:
@@ -49,6 +49,74 @@ public static class DbSeeder
|
||||
("Finance & Banking", "財務與銀行", 14, [("Interest","利息支出"),("Bank & Processing Fees","銀行/金流手續費")]),
|
||||
];
|
||||
|
||||
// (LineCode, Name_en, Name_zh, Sort)
|
||||
private static readonly (string Code, string En, string Zh, int Sort)[] Form990LineSeed =
|
||||
[
|
||||
("1", "Grants to domestic organizations", "對國內機構之捐贈", 1),
|
||||
("2", "Grants to domestic individuals", "對國內個人之捐贈", 2),
|
||||
("3", "Grants to foreign organizations/individuals", "對國外之捐贈", 3),
|
||||
("5", "Compensation of current officers / key employees", "主要職員/負責人薪酬", 4),
|
||||
("7", "Other salaries and wages", "薪資", 5),
|
||||
("8", "Pension plan accruals and contributions", "退休金提撥", 6),
|
||||
("9", "Other employee benefits", "員工福利", 7),
|
||||
("10", "Payroll taxes", "薪資稅", 8),
|
||||
("11b", "Legal fees", "法律服務費", 9),
|
||||
("11c", "Accounting fees", "會計與審計費", 10),
|
||||
("11g", "Other fees for services (non-employee)", "其他勞務報酬(非員工)", 11),
|
||||
("12", "Advertising and promotion", "廣告與推廣", 12),
|
||||
("13", "Office expenses", "辦公費用", 13),
|
||||
("14", "Information technology", "資訊科技", 14),
|
||||
("16", "Occupancy", "場地佔用", 15),
|
||||
("17", "Travel", "差旅", 16),
|
||||
("19", "Conferences, conventions, and meetings", "會議與研習", 17),
|
||||
("20", "Interest", "利息", 18),
|
||||
("22", "Depreciation", "折舊", 19),
|
||||
("23", "Insurance", "保險", 20),
|
||||
("24", "Other expenses", "其他費用", 21),
|
||||
];
|
||||
|
||||
// (GroupEn, SubEn, LineCode) — default natural-category → 990 line mapping.
|
||||
private static readonly (string GroupEn, string SubEn, string Code)[] Form990SubMappingSeed =
|
||||
[
|
||||
("Personnel", "Officer / Key Employee Compensation", "5"),
|
||||
("Personnel", "Salary & Wages", "7"),
|
||||
("Personnel", "Payroll Taxes", "10"),
|
||||
("Personnel", "Employee Benefits", "9"),
|
||||
("Personnel", "Retirement / Pension","8"),
|
||||
("Personnel", "Workers Compensation","9"),
|
||||
("Personnel", "Honorarium", "11g"),
|
||||
("Personnel", "Contract Labor", "11g"),
|
||||
("Personnel", "Staff Training", "19"),
|
||||
("Facility", "Rent", "16"),
|
||||
("Facility", "Utilities", "16"),
|
||||
("Facility", "Property Insurance", "23"),
|
||||
("Facility", "Decoration", "24"),
|
||||
("Training", "Course Fees", "19"),
|
||||
("Training", "Conference", "19"),
|
||||
("Training", "Books", "24"),
|
||||
("Training", "Travel", "17"),
|
||||
("Missions", "Travel", "17"),
|
||||
("Missions", "Offering Transfer", "1"),
|
||||
("Missions", "Missionary Support", "1"),
|
||||
("Missions", "Foreign Missions Support", "3"),
|
||||
("Benevolence", "Emergency Aid", "2"),
|
||||
("Benevolence", "Condolence Gifts", "2"),
|
||||
("Benevolence", "Visit Expenses", "2"),
|
||||
("Consumables", "Office Supplies", "13"),
|
||||
("Printing", "Bulletins", "13"),
|
||||
("Printing", "Order of Service", "13"),
|
||||
("Printing", "Posters", "12"),
|
||||
("Printing", "Advertising & Promotion", "12"),
|
||||
("Materials", "Curriculum Printing", "13"),
|
||||
("Professional Services", "Legal", "11b"),
|
||||
("Professional Services", "Accounting & Audit", "11c"),
|
||||
("Professional Services", "Other Professional", "11g"),
|
||||
("Information Technology", "Software & Subscriptions", "14"),
|
||||
("Information Technology", "Website & Hosting", "14"),
|
||||
("Information Technology", "Internet & Telecom", "14"),
|
||||
("Finance & Banking", "Interest", "20"),
|
||||
];
|
||||
|
||||
private static readonly (string Name, string Description)[] Roles =
|
||||
[
|
||||
("super_admin", "System administrator — full access"),
|
||||
@@ -227,6 +295,34 @@ public static class DbSeeder
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public static async Task SeedForm990ExpenseLinesAsync(AppDbContext db)
|
||||
{
|
||||
foreach (var (code, en, zh, sort) in Form990LineSeed)
|
||||
{
|
||||
if (!await db.Form990ExpenseLines.AnyAsync(l => l.LineCode == code))
|
||||
db.Form990ExpenseLines.Add(new Form990ExpenseLine
|
||||
{ LineCode = code, Name_en = en, Name_zh = zh, SortOrder = sort, IsActive = true });
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var linesByCode = await db.Form990ExpenseLines.ToDictionaryAsync(l => l.LineCode, l => l.Id);
|
||||
var fallbackId = linesByCode["24"];
|
||||
|
||||
// Every group defaults to line 24 (safety net); precise mapping lives on subcategories.
|
||||
foreach (var group in await db.ExpenseCategoryGroups.ToListAsync())
|
||||
group.Form990LineId ??= fallbackId;
|
||||
|
||||
// Subcategory default mappings — only set when not already mapped (never clobber an admin edit).
|
||||
var subsByKey = await db.ExpenseSubCategories.Include(s => s.Group).ToListAsync();
|
||||
foreach (var (groupEn, subEn, code) in Form990SubMappingSeed)
|
||||
{
|
||||
var sub = subsByKey.FirstOrDefault(s => s.Group!.Name_en == groupEn && s.Name_en == subEn);
|
||||
if (sub is not null && sub.Form990LineId is null && linesByCode.TryGetValue(code, out var lineId))
|
||||
sub.Form990LineId = lineId;
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public static async Task SeedChurchProfileAsync(AppDbContext db)
|
||||
{
|
||||
// Singleton row used by the disbursement module (issuer info + check counter).
|
||||
@@ -305,6 +401,7 @@ public static class DbSeeder
|
||||
await SeedGivingCategoriesAsync(db);
|
||||
await SeedMinistriesAsync(db);
|
||||
await SeedExpenseCategoriesAsync(db);
|
||||
await SeedForm990ExpenseLinesAsync(db);
|
||||
await SeedChurchProfileAsync(db);
|
||||
await SeedSiteSettingAsync(db);
|
||||
await SeedNotificationSettingAsync(db, config);
|
||||
|
||||
Reference in New Issue
Block a user