docs: spec for sub-project A — expense 990 functional expenses (Part IX)
Audit-readiness at Form 990 Part IX level: functional class (Program/M&G/ Fundraising) via Ministry default + per-expense override, Form990ExpenseLine catalog + subcategory/group mapping, duplicate-category cleanup, and the Part IX functional-expense matrix report. 1099 (B) and revenue Part VIII (C) are separate specs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,243 @@
|
|||||||
|
# 子專案 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 | 對國外之捐贈 |
|
||||||
|
| 7 | Other salaries and wages | 薪資 |
|
||||||
|
| 9 | Other employee benefits | 員工福利 |
|
||||||
|
| 10 | Payroll taxes | 薪資稅 |
|
||||||
|
| 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 | 會議與研習 |
|
||||||
|
| 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` 不受影響。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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. 既有支出資料不因本次變更而遺失或錯置。
|
||||||
Reference in New Issue
Block a user