# 子專案 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
             ?? <line "24 Other expenses">   // 保底,確保無漏列
```

> 映射**必須能下到子項目層**: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<FunctionalExpenseStatementDto> 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. 既有支出資料不因本次變更而遺失或錯置。
