# 子專案 A — 支出 990 化(Functional Expenses / Part IX)設計 **日期:** 2026-06-24 **狀態:** Draft(待 user review) **範圍:** 僅子專案 A。1099 收款人(B)、收入端 Part VIII(C)為獨立 spec,不在此。 --- ## 1. 目標與背景 教會依 IRC §6033(a)(3)(A) **免於申報** Form 990,但本系統要做到 **990 查帳就緒(audit-readiness)**:在 IRS 檢查時,能依需求產出等同 **Form 990 Part IX — Statement of Functional Expenses** 的功能性費用表及佐證明細。 Part IX 本質是一個矩陣: ``` Program | Mgmt & General | Fundraising | Total 自然費用行(990 line) 7 Salaries & wages ... ... ... ... 9 Employee benefits ... ... ... ... 10 Payroll taxes ... ... ... ... 16 Occupancy ... ... ... ... ... 24 Other expenses ... ... ... ... ───────────────────────────────────────────────────────────── Total ... ... ... ... ``` 現況:`FinanceDashboardService` 已能按 `Ministry → CategoryGroup → SubCategory` 彙總(Paid+Approved 口徑),但**沒有功能別維度**,自然類別也**未對應 990 行**。本子專案在現有兩條軸之上疊一層「990 對照」,不重寫分類樹。 ### 設計原則 - 維持現有兩條軸:`Ministry` = 功能別來源;`ExpenseCategoryGroup → ExpenseSubCategory` = 自然科目。 - 990 對照以**映射欄位 + 參考表**實現(資料驅動,與 RolePermission / Categories 同風格)。 - 單筆支出**單一功能別**(direct-charge),不做跨功能比例分攤。 - 向後相容:新增欄位皆 nullable,既有資料不破。 --- ## 2. 資料模型變更 ### 2.1 功能別(Part IX 三欄) 功能別值域(字串,沿用 codebase 用 `string` 表 `Type`/`Status` 的慣例): `"Program"` | `"ManagementGeneral"` | `"Fundraising"` **`Ministry`**(新增欄位) | 欄位 | 型別 | 說明 | |------|------|------| | DefaultFunctionalClass | varchar(20) NOT NULL DEFAULT 'Program' | 該事工支出的預設功能別 | **`Expense`**(新增欄位) | 欄位 | 型別 | 說明 | |------|------|------| | FunctionalClass | varchar(20)? | 覆寫;null = 繼承 Ministry | **有效功能別**(報表計算用): ``` EffectiveFunctionalClass = Expense.FunctionalClass ?? Ministry.DefaultFunctionalClass ?? "Program" // 最終保底 ``` Ministry 預設 seed:`Administration → ManagementGeneral`,其餘 9 個事工 → `Program`。目前無 Fundraising 事工(教會少見),需要時用單筆覆寫。 ### 2.2 990 Part IX 行目錄 + 映射 **新表 `Form990ExpenseLine`**(參考表 / 資料驅動) | 欄位 | 型別 | 說明 | |------|------|------| | Id | int PK | | | LineCode | varchar(10) NOT NULL UNIQUE | 990 行編號,如 `"7"`、`"11g"`、`"16"`、`"24"` | | Name_en | varchar(200) NOT NULL | 如 "Other salaries and wages" | | Name_zh | varchar(200)? | | | SortOrder | int NOT NULL | 報表列順序 | | IsActive | bool NOT NULL DEFAULT true | | 繼承 `AuditableEntity`(與其他類別表一致)。 **映射欄位**(兩處,nullable): - `ExpenseSubCategory.Form990LineId`(int? FK → Form990ExpenseLine.Id)— **主要映射** - `ExpenseCategoryGroup.Form990LineId`(int? FK)— 大類預設(便利用) **有效 990 行**: ``` EffectiveLine = SubCategory.Form990LineId ?? Group.Form990LineId ?? // 保底,確保無漏列 ``` > 映射**必須能下到子項目層**:Personnel 大類底下 Salary→line 7、Payroll Taxes→line 10、Benefits→line 9 是三個不同 990 行,大類層無法表達。 **seed 的 990 行子集**(教會常用): | LineCode | Name_en | Name_zh | |----------|---------|---------| | 1 | Grants to domestic organizations | 對國內機構之捐贈 | | 2 | Grants to domestic individuals | 對國內個人之捐贈 | | 3 | Grants to foreign organizations/individuals | 對國外之捐贈 | | 5 | Compensation of current officers / key employees | 主要職員/負責人薪酬 | | 7 | Other salaries and wages | 薪資 | | 8 | Pension plan accruals and contributions | 退休金提撥 | | 9 | Other employee benefits | 員工福利 | | 10 | Payroll taxes | 薪資稅 | | 11b| Legal fees | 法律服務費 | | 11c| Accounting fees | 會計與審計費 | | 11g| Other fees for services (non-employee) | 其他勞務報酬(非員工) | | 12 | Advertising and promotion | 廣告與推廣 | | 13 | Office expenses | 辦公費用 | | 14 | Information technology | 資訊科技 | | 16 | Occupancy | 場地佔用 | | 17 | Travel | 差旅 | | 19 | Conferences, conventions, and meetings | 會議與研習 | | 20 | Interest | 利息 | | 22 | Depreciation | 折舊(本子專案不映射,留行供未來資本化使用) | | 23 | Insurance | 保險 | | 24 | Other expenses | 其他費用 | **現有子項目 → 990 行的預設映射 seed**: | 子項目(大類) | 990 行 | |---|---| | Personnel > Salary & Wages | 7 | | Personnel > Payroll Taxes | 10 | | Personnel > Employee Benefits | 9 | | 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 | | Benevolence > Emergency Aid | 2 | | Benevolence > Condolence Gifts | 2 | | Benevolence > Visit Expenses | 2 | | Consumables > Office Supplies | 13 | | Consumables > Batteries / Accessories / Cleaning Supplies | 24 | | Printing > Bulletins / Order of Service | 13 | | Printing > Posters | 12 | | Materials > Curriculum Printing(見 §2.3) | 13 | | Materials > Craft Supplies / Copyright & Licensing | 24 | | Food & Beverage > 全部子項目 | 24 | | Equipment > 全部子項目 | 24 | | Other > Miscellaneous | 24 | 大類層 `Form990LineId` 一律 seed 為 `24`(保底),確保未細映的子項目仍落在 line 24。 ### 2.3 類別清理(互斥化) 只用**改名**解決真正的歧義(不需搬移既有支出),把同名收斂成唯一含義: | 現況 | 問題 | 解法 | |---|---|---| | `Food & Beverage > Consumables` | 與大類 `Consumables` 同名 | 改名 → **"Disposable Tableware" / 一次性餐具** | | `Materials > Printing` | 與大類 `Printing` 同名 | 改名 → **"Curriculum Printing" / 教材印刷** | | `Training > Travel`、`Missions > Travel` | 同名但**父類不同** | **不算衝突**,維持原樣;兩者都映射到 990 line 17,報表自動合併 | 改名落地方式: - **新安裝**:更新 `DbSeeder` 的 seed 字串。 - **既有 DB**:一次性資料 migration,依 `(GroupId, 舊 Name_en)` 定位後更新 `Name_en`/`Name_zh`(seed 採 insert-if-not-exists,不會自動改既有列,故需 migration)。 無 `SubCategoryId` 搬移,既有 `Expense` 不受影響。 ### 2.4 新增類別(補齊 990 行覆蓋) 現有 11 大類對某些 990 行無對應(會讓那些行恆為 0 或被硬塞 line 24)。補上以下類別讓自然樹真正覆蓋常用 990 行。所有新增皆走 `DbSeeder`,並更新 DB_SCHEMA.md。 **加進現有大類** | 大類 | 新子項目 | 990 行 | |---|---|---| | Personnel 人事 | Officer / Key Employee Compensation 主要職員薪酬 | **5** | | Personnel 人事 | Retirement / Pension 退休金 | **8** | | Missions 宣教 | Foreign Missions Support 國外宣教支援 | **3** | | Printing 印刷 | Advertising & Promotion 廣告推廣 | **12** | > Personnel 既有 `Salary & Wages` 維持 line 7(一般員工);牧師等 officer/key employee 薪酬改記新子項目 → line 5。 > Missions 既有 `Missionary Support` / `Offering Transfer` 視為國內/未分 → line 1;國外走新子項目 → line 3。 **新增大類** | 新大類 | 子項目 | 990 行 | |---|---|---| | **Professional Services 專業服務** | Legal 法律服務 | **11b** | | | Accounting & Audit 會計與審計 | **11c** | | | Other Professional 其他專業服務 | 11g | | **Information Technology 資訊科技** | Software & Subscriptions 軟體與訂閱 | **14** | | | Website & Hosting 網站與主機 | **14** | | | Internet & Telecom 網路與電信 | **14** | | **Finance & Banking 財務與銀行** | Interest 利息支出 | **20** | | | Bank & Processing Fees 銀行/金流手續費 | 24 | 大類數由 11 → **14**(新增 Professional Services、Information Technology、Finance & Banking)。新大類 `Form990LineId` 預設仍 seed 為 24(保底),實際映射在子項目層。 --- ## 3. 報表層 **新服務 `Form990ReportService`**(與 `FinanceDashboardService` 並列,讀取為主)。 ```csharp Task GetFunctionalExpenseStatementAsync( DateOnly? from, DateOnly? to); ``` - 支出口徑沿用 `FinanceDashboardService` 既有約定:`Status == "Paid" || "Approved"`,選用 `ExpenseDate` 區間。 - 對每筆支出計算 `EffectiveFunctionalClass` 與 `EffectiveLine`,彙總成矩陣。 **`FunctionalExpenseStatementDto`** ``` Rows: [ { LineCode, Name_en, Name_zh, Program, ManagementGeneral, Fundraising, Total } ] // 依 SortOrder ColumnTotals: { Program, ManagementGeneral, Fundraising, GrandTotal } UnmappedExpenseCount: int // 落到保底 line 24 的「未明確映射」筆數,提示待補映射 ``` `UnmappedExpenseCount` 讓財務知道哪些還沒細映(治理用),但金額仍計入 line 24,不漏帳。 **匯出:** 沿用既有報表/DevExpress 管道,輸出可交付會計師的表格(PDF/試算表)。 --- ## 4. 前端(Angular,admin) 沿用既有 portal 慣例(`UserPortalComponent` 導覽、unified header、Kendo UI、表單版面用 Tailwind utilities、行動裝置友善 `hidden md:block` + `md:hidden` 卡片)。 1. **Part IX 報表頁**:Kendo Grid 矩陣(列=990 行,欄=三功能別+Total),年度/區間篩選,雙語,行動裝置卡片版;`UnmappedExpenseCount` 以提示列顯示;匯出鈕。 2. **支出表單**(`expense-form-dialog`):新增 `FunctionalClass` 下拉(可空=繼承事工);Kendo DropdownList 設 `[valuePrimitive]="true"`。 3. **類別維護頁**(`expense-categories-page`):每個大類/子項目可設 `Form990LineId`(990 行下拉)。 4. **事工維護**:`DefaultFunctionalClass` 下拉。 --- ## 5. 測試 沿用既有測試模式(`ExpenseServiceTests` 等;受測元件用 inline template,Edge via `CHROME_BIN`,以 `--include` 縮限)。 - `EffectiveFunctionalClass` 解析:覆寫優先、否則繼承事工、再保底 Program。 - `EffectiveLine` 解析:子項目優先、否則大類、再保底 line 24。 - 矩陣彙總:多筆跨功能別/跨行正確加總;欄合計與總計一致。 - 保底行為:未映射子項目進 line 24 且 `UnmappedExpenseCount` 正確。 - 狀態口徑:僅 Paid+Approved 計入;區間篩選正確。 --- ## 6. Migration / 落地 EF Core code-first: 1. 新表 `Form990ExpenseLines`。 2. 新欄 `Ministries.DefaultFunctionalClass`、`Expenses.FunctionalClass`、`ExpenseSubCategories.Form990LineId`、`ExpenseCategoryGroups.Form990LineId`(後二者建 FK)。 3. 資料 migration:類別改名(§2.3)。 4. `DbSeeder`:seed `Form990ExpenseLine`、子項目→行的預設映射、Ministry 預設功能別、更新改名後的 seed 字串。 DB_SCHEMA.md 同步更新(新表 + 新欄 + §8 備注)。 --- ## 7. 不在此範圍(已知缺口) - **資本化 / 折舊(line 22)**:line 已 seed 但不映射;Equipment 購置暫入 line 24。需要時另開。 - **1099 收款人追蹤**:子專案 B。 - **收入端 Part VIII**:子專案 C。 - **跨功能比例分攤**:本系統維持單筆單一功能別,不支援拆分。 --- ## 8. 驗收標準 1. 可在報表頁選定年度,產出 Part IX 矩陣(三功能別 × 990 行),欄合計與總計正確,且金額等於同口徑(Paid+Approved)的支出總額。 2. 變更某事工 `DefaultFunctionalClass` 或某筆 `FunctionalClass` 後,報表對應欄位即時反映。 3. 未細映的子項目金額落在 line 24,且 `UnmappedExpenseCount` 正確顯示。 4. 類別樹中不再有「同父同名」歧義;`Training/Missions > Travel` 維持並正確合併至 line 17。 5. 既有支出資料不因本次變更而遺失或錯置。