From 93374c3c0a7930b458101cc34acc57607e35e25c Mon Sep 17 00:00:00 2001 From: Chris Chen Date: Thu, 25 Jun 2026 16:16:04 -0700 Subject: [PATCH] =?UTF-8?q?docs(1099):=20design=20spec=20for=20sub-project?= =?UTF-8?q?=20B=20=E2=80=94=201099=20recipient=20tracking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- ...26-06-25-1099-recipient-tracking-design.md | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-25-1099-recipient-tracking-design.md diff --git a/docs/superpowers/specs/2026-06-25-1099-recipient-tracking-design.md b/docs/superpowers/specs/2026-06-25-1099-recipient-tracking-design.md new file mode 100644 index 0000000..09b1118 --- /dev/null +++ b/docs/superpowers/specs/2026-06-25-1099-recipient-tracking-design.md @@ -0,0 +1,154 @@ +# 子專案 B — 1099 收款人追蹤(1099 Recipient Tracking)設計 + +**日期:** 2026-06-25 +**狀態:** Approved(user 已核可,待轉 implementation plan) +**範圍:** 僅子專案 B。支出 Part IX(A)已上線;收入端 Part VIII(C)為獨立 spec,不在此。 + +--- + +## 1. 目標與背景 + +教會依 IRC §6033(a)(3)(A) 免於申報 990,但對**獨立承攬人/廠商**的付款仍須在年底產出 **1099-NEC**(非員工報酬)記錄。本子專案讓系統能:辨識收款人身分、保存 W-9/TIN、依**已付**金額按年彙總、標示 $600 門檻與缺漏 W-9,並產出可交付的收款人聯(Copy B)PDF 與申報用資料檔。 + +**現況缺口:** 系統沒有收款人身分。廠商付款只存自由文字 `Expense.VendorName`(nullable, max 200);出納工作清單以該**字串**分組。沒有任何 W-9/TIN 資料,也無法把一整年付款依收款人加總。 + +**實際驅動案例:** 一位**兼職同工同時也是 Member**,以獨立承攬人身分受款,需開立 1099-NEC。 + +### 設計原則 +- **資料驅動、疊在現有分類軸之上**,沿用子專案 A 的「映射欄位 + 參考表」風格,不重寫分類樹。 +- 收款人身分以**獨立 master** 表達,與 Member **可選關聯**(不強耦合)。 +- 1099 應報與否需**兩個條件同時成立**:收款人被追蹤 + 該筆科目映射到 1099 box。 +- 員工(W-2/薪資)**不在範圍**(本系統無 payroll 模組)。 +- 向後相容:新增欄位皆 nullable,既有資料不破。 + +--- + +## 2. 資料模型變更 + +### 2.1 新表 `Payee1099`(收款人 master)— 繼承 `SoftDeleteEntity, IAuditable` +檔案:`API/ROLAC.API/Entities/Payee1099.cs` + +| 欄位 | 型別 | 說明 | +|---|---|---| +| Id | int PK | | +| LegalName | varchar(200) NOT NULL | W-9 上的法定名稱 | +| DisplayName | varchar(200)? | 友善 / DBA 名稱 | +| MemberId | int? FK→Member (SetNull) | 收款人同時是 Member 時連結(兼職同工案例) | +| TaxClassification | varchar(40) | Individual/SoleProprietor、Partnership、CCorp、SCorp、LLC、Other — 決定 `Is1099Tracked` 預設 | +| Is1099Tracked | bool NOT NULL DEFAULT true | 可覆寫;公司(C/S Corp)預設 false | +| TinType | varchar(10)? | "SSN" \| "EIN" | +| TinEncrypted | text? | 經 Data Protection API 加密的 TIN | +| TinLast4 | varchar(4)? | 遮罩顯示 / 搜尋用,免解密 | +| AddressLine1/2, City, State, Zip | varchar | 1099 表單用地址 | +| Email, Phone | varchar? | W-9 催收用 | +| W9Status | varchar(20) DEFAULT 'Missing' | Missing \| Requested \| OnFile \| Expired | +| W9ReceivedDate | DateOnly? | | +| W9BlobPath | text? | 上傳的 W-9 PDF/影像(比照 `Expense.ReceiptBlobPath`) | +| IsActive | bool DEFAULT true | | +| Notes | text? | | +| + audit + soft-delete | | 由 `SoftDeleteEntity` 提供 | + +### 2.2 `Expense` 新增 `PayeeId int?` FK → Payee1099 (SetNull) +檔案:`API/ROLAC.API/Entities/Expense.cs`。**表頭層**(一筆支出/一張支票 = 一位收款人,與 `Check.PayeeName` 一致)。與 `Type` 無關 — 外部廠商與「同工承攬人」皆適用。`VendorName` 仍保留為自由文字 fallback/snapshot。 + +### 2.3 新參考表 `Form1099Box` — 繼承 `AuditableEntity, IAuditable`(比照 `Form990ExpenseLine`) +檔案:`API/ROLAC.API/Entities/Form1099Box.cs` +- Id、BoxCode(unique,如 `"NEC-1"`、`"MISC-1"`)、Name_en、Name_zh?、FormType(`"1099-NEC"` | `"1099-MISC"`)、SortOrder、IsActive。 +- **seed 子集:** `NEC-1` Nonemployee compensation 非員工報酬;`MISC-1` Rents 租金。目錄可擴充。 + +### 2.4 映射欄位(完全比照 990-line 模式) +- `ExpenseSubCategory.Form1099BoxId int?` FK → Form1099Box (SetNull)— **主要映射** +- `ExpenseCategoryGroup.Form1099BoxId int?` FK — 大類 fallback + +**有效 box = `sub ?? group ?? null`。** 與 990 不同(990 fallback 為 line 24,人人有歸屬);此處 **null = 不列入 1099** 才是預設 — 只有勞務性科目才給 box。 + +**預設 seed 映射(子項目 → box),僅列可報者:** +- Personnel ▸ Honorarium → NEC-1 +- Personnel ▸ Contract Labor → NEC-1 +- Professional Services ▸ Legal / Accounting & Audit / Other Professional → NEC-1 +- Facility ▸ Rent → MISC-1 +- **其餘一律 unmapped(排除)。** Salary & Wages / Officer Compensation 維持 unmapped(那是 W-2 薪資,永不入 1099)— 即使被追蹤的收款人記在這些科目,box gate 也會擋下。 + +--- + +## 3. 報表層 + +新服務 `Form1099ReportService`(讀取為主,與 `Form990ReportService` 並列)。 +檔案:`API/ROLAC.API/Services/{IForm1099ReportService,Form1099ReportService}.cs`、DTOs `API/ROLAC.API/DTOs/Finance/Form1099ReportDtos.cs`、controller `API/ROLAC.API/Controllers/Form1099ReportController.cs`。 + +```csharp +Task GetAnnualSummaryAsync(int taxYear); +Task GetRecipientDetailAsync(int payeeId, int taxYear); +Task> GetBoxesAsync(); +``` + +**現金基礎查詢(與 990 報表不同):** `Status == "Paid"` 且 `PaidAt` 年份 == taxYear。(1099 報的是**該曆年實際支付**的金額,而非 990 報表採用的 Approved/ExpenseDate。`Expense.PaidAt` 為支付日;`Check.CheckDate` 為日後若要更精準的替代基準。) + +**彙總邏輯:** +1. Join 已付支出(PaidAt 落在該年、`PayeeId` 非 null)→ `ExpenseLines` → SubCategory/Group → 有效 box。 +2. 只保留 **有效 box ≠ null 且 `payee.Is1099Tracked`** 的行。 +3. 依 `(PayeeId, BoxCode)` 加總。 +4. 每位收款人:各 box 小計;`MeetsThreshold`(每 box ≥ **$600**,常數 `Form1099.ReportingThreshold`);`W9Missing`(`W9Status != "OnFile"`)。 + +**DTOs:** +- `Form1099SummaryDto { TaxYear, Rows:[Form1099RecipientRowDto], TotalReportable, RecipientsAtThreshold, RecipientsMissingW9 }` +- `Form1099RecipientRowDto { PayeeId, LegalName, TinLast4, W9Status, NecTotal, RentsTotal, GrandTotal, MeetsThreshold, W9Missing }` +- `Form1099RecipientDetailDto { 收款人表頭 + 構成付款明細: [date, description, categoryName, boxCode, amount] }` + +--- + +## 4. TIN 加密 + +採用 ASP.NET Core **Data Protection API**(`IDataProtectionProvider.CreateProtector("Payee1099.Tin")`)— 可逆、由框架管理金鑰、不引入新加密相依。寫入時加密;另存 `TinLast4` 供顯示/搜尋。完整 TIN 解密僅透過專屬 endpoint,並以本模組 **Write** action 把關;其餘一律遮罩(`***-**-1234`)。 + +## 5. 1099-NEC Copy B PDF + 申報資料匯出 + +新服務 `I1099FormService`,沿用 DevExpress 管道(`ICheckPrintService` / `DevExpress.Document.Processor`;授權檔已設定,見 [[project-devexpress-check-printing]])。產出**收款人聯 Copy B** 1099-NEC(payer = `ChurchProfile`、recipient = `Payee1099`、box 1 = NEC 合計),純白紙列印。另產出供 IRIS/會計師用的**申報資料 CSV/試算表**。不含 IRS 傳輸。 + +## 6. 權限 + +`API/ROLAC.API/Authorization/Modules.cs`(+ `Modules.All`)新增模組 `Form1099`,並同步前端 `PermissionModules`(`APP/src/app/core/models/permission.model.ts`)。Actions:Read(收款人 + 報表)、Write(編輯收款人、連結 payee、顯示完整 TIN)、Delete。seed 財務角色之 RolePermission;super_admin 自動 bypass。 + +## 7. 前端(Angular,admin) + +慣例:`UserPortalComponent` 財務導覽群組 + `app.routes.ts` 路由 data(`title/titleZh/section` + `PermissionGuard`)、unified header(`appPageHeaderActions`)、Kendo UI、Tailwind 表單版面、行動裝置 `hidden md:block` + `md:hidden` 卡片。([[project-real-sidebar-nav]]、[[project-unified-system-header]]、[[feedback-mobile-friendly-all-screens]]、[[feedback-form-layout-tailwind]]) + +1. **1099 收款人維護頁**(`features/payee1099/pages/payee-1099-page`)— Kendo Grid(LegalName、member 連結、分類、TIN 末四碼遮罩、W-9 狀態徽章、Tracked 開關、Active);右鍵 context menu Edit/Deactivate;編輯對話框含 W-9 欄位 + Member 選擇器 + 遮罩 TIN 輸入 + W-9 上傳;行動卡片。比照 `expense-categories-page`。 +2. **科目 → box 映射** — 擴充現有 `expense-categories-page`,在既有 990-line 下拉旁加一個「1099 Box」下拉(大類/子項目皆可設 `Form1099BoxId`)。`[valuePrimitive]="true"`([[feedback-kendo-value-primitive]])。 +3. **支出表單**(`expense-form-dialog`)— 新增可選「1099 收款人」payee 選擇器(DropdownList、`valuePrimitive`)。 +4. **1099 年度報表頁**(`features/finance-report/pages/form1099-report-page`)— 年度選擇器;收款人 grid(NEC/Rents 合計、門檻旗標、缺 W-9 旗標);下鑽收款人明細(構成付款,[[feedback-kendo-table-select-via-row-click]]);header actions「匯出申報資料」+「產生 Copy B PDF」。行動卡片。比照 `form990-report-page`。 + +## 8. Migration / 落地 + +- EF migration:新表 `Payee1099s`、`Form1099Boxes`;新欄 `Expenses.PayeeId`、`ExpenseSubCategories.Form1099BoxId`、`ExpenseCategoryGroups.Form1099BoxId`(FK、SetNull)。 +- `DbSeeder`:seed `Form1099Box` 目錄 + 子項目→box 映射(**只填 NULL**,比照 `SeedForm990ExpenseLinesAsync` 的冪等性;不得覆蓋 admin 編輯)。無 catch-all fallback(unmapped = 不列入)。 +- 同步更新 `docs/DB_SCHEMA.md`(新表 + 新欄)。 +- v1 **不**自動把既有自由文字 `VendorName` 回填成 master(教會規模小,手動連結即可)。列為已知後續。 + +--- + +## 9. 測試 + +沿用既有測試模式(`ExpenseServiceTests` 等;Release build,見 [[project-build-run-env]]): +- `EffectiveBox` 解析:子項目 ?? 大類 ?? null。 +- 報表現金基礎:Paid + PaidAt 年份;每收款人/每 box 加總正確。 +- 門檻:恰 $600 觸發旗標;缺 W-9 旗標正確。 +- `Is1099Tracked` gate;員工薪資科目被排除;同工(member-linked)收款人正確加總。 +- TIN 加解密 round-trip + 末四碼 + 遮罩。 + +## 10. 不在此範圍(已知缺口) + +- IRS 電子申報(IRIS/FIRE)整合。 +- 官方 Copy A / 1096 表單(v1 僅 Copy B + 資料匯出)。 +- payroll / W-2(員工)。 +- 既有 `VendorName` → master 自動回填。 +- 可設定門檻(v1 以常數)。 + +## 11. 驗收標準 + +1. 可在收款人維護頁建立 `Payee1099`(含 W-9/TIN,TIN 遮罩),並可連結 Member。 +2. 支出可選填 1099 收款人;科目可設 1099 box;映射採子項目優先、大類 fallback、否則不列入。 +3. 年度報表依**已付**金額按收款人 × box 加總,正確標示 $600 門檻與缺 W-9;可下鑽明細。 +4. 完整 TIN 僅在具 Write 權限時可揭示;其餘遮罩為末四碼。 +5. 可產出收款人聯 Copy B 1099-NEC PDF(無 DevExpress 浮水印)與申報資料 CSV。 +6. 員工薪資科目即使付給被追蹤收款人,也不出現在 1099 報表;既有支出資料不受影響。