DAF Contributions Report
DAF Contributions Report
Component File: src/features/reports/components/DafContributionsReport.tsx Route / navigation: Path /reports, Zustand reportTool = daf-contributions-report. See 00-REPORTS-HUB.md. Access Level: Parent Org and Fund Users with Reports access (gated by daf-contributions-report in the admin permission tree) Last Updated: April 24, 2026
Overview
The DAF Contributions Report is a year-end compliance report that rolls up every Donor Advised Fund (DAF) gift received during a fiscal year, grouped by sponsoring organization. It is the year-round, standalone equivalent of the DAF/Schedule B aggregation that lives inside the Form 990 Builder, and it is intended for accountants, auditors, and board reports — not donor-facing receipts.
Why a standalone report (not part of Donor Reporting): DonorReporting is a per-donor tax-receipt workflow, while DAF gifts are not tax-deductible to the soft-credit donor and must be re-attributed to the sponsoring organization for IRS Schedule B. This report's audience and grouping axis are different, so it gets its own tile in the Reports hub.
UI Features
Header
- Breadcrumb:
Reports → DAF Contributions - Page title: "DAF Contributions"
- Subtitle: "Roll up Donor Advised Fund gifts by sponsor for end-of-year reporting"
- Top-right Export button (opens
DataExportDialog) - Inline action: Open Form 990 Builder (deep-links into Tools via
navigateTo('tools', 'form-990-builder'))
Stat Tiles (4)
1. Total DAF Contributions — sum of all DAF gift amounts for the selected fiscal year 2. DAF Gifts — number of DAF gift records 3. Sponsors — distinct sponsoring organizations 4. % of Total Contributions — DAF total as a share of all completed donations for the same period
All tiles use useCountUp(value, 900) and inherit the standard finance neutral color (no text-primary).
Filter Bar
- Year selector — current year and 5 prior years (persisted via
useReportSettings) - Search — sponsor name, soft-credit donor, fund, or check / reference number (300 ms debounce)
- Page size — 100 / 250 / 500
- Schedule B reportable only checkbox — keeps only sponsors whose annual DAF total is $5,000 or more (the IRS Schedule B threshold)
- Clear Filters button when any filter is active
Tabs
1. By Sponsor (default) — sponsor name, EIN, gift count, total amount, and a Schedule B eligibility badge. Each row is expandable into the underlying gifts. 2. All Gifts — flat list (date, sponsor, soft-credit donor, fund, reference number, amount). Sortable by date / sponsor / amount.
Table Standards
SkeletonTableon first load;opacity-60dim while background-refetchingtable-fixed w-fulllayoutPaginationControlsrendered above and below the table (only whentotalPages > 1)EmptyState(Landmark icon) with two-state copy:noDafGiftsYet— no DAF gifts exist for the selected yearnoDafGiftsFound— gifts exist but filters hide them all
Export
Reuses the shared DataExportDialog and produces CSV / XLSX / PDF using exportToCSV, exportToExcel, and exportToPDF. Excel uses EXCEL_DATE_FORMAT and EXCEL_CURRENCY_FORMAT. PDF includes the parent organization name + EIN in the header (resolved via fetchParentOrgInfo). Exports respect the active Schedule B / search filter and the fund scope chosen in the dialog.
Data Layer
Function
fetchDafContributionsReport(
entityId: EntityId,
year: number,
fiscalYearEndMonth: number = 12,
): Promise<DafContributionsReportResult>Located in src/lib/db/daf-sponsors.ts next to getDafDonationSummary.
Result shape
interface DafContributionsReportResult {
rows: DafContributionRow[];
totals: {
total_amount: number; // sum of DAF gift amounts
gift_count: number; // count of DAF gift rows
sponsor_count: number; // distinct sponsors
total_contributions_amount: number; // all completed donations (for % tile)
};
by_sponsor: {
sponsor_id: string | null;
sponsor_name: string;
ein: string | null;
amount: number;
count: number;
}[];
}Query rules (per Developer Playbook §17.5)
- Explicit columns (no
select('*')) .limit(50000)— same ceiling used by Schedule B inform990-schedules.tsgetActualOrgId+getDonationScopeFilterfor entity scoping (parent org aggregates across child funds; single-fund views scope correctly)payment_status = 'completed',status != 'voided',is_daf_gift = true- Embedded join:
donors:donor_id(id, name, first_name, last_name)only (this is the onlydonationscolumn that has a real FK constraint to its target table). - Sponsor and fund names are resolved via separate `id IN (...)` lookups against
daf_sponsorsandorganizationsafter the donations result comes back, then joined in memory. See "Why two lookups instead of embedded joins" below. - A second small query without the
is_daf_giftfilter is used to compute the "% of Total Contributions" denominator
Why two lookups instead of embedded joins
PostgREST embedded selects (daf_sponsors:daf_sponsor_id(...), organizations!fund_id(...)) require declared foreign-key constraints on donations.daf_sponsor_id and donations.fund_id. Those FKs were missing for a long time, so the embed silently returned HTTP 400 ("could not find a relationship") and the report rendered empty for every parent-org user. The FKs are now declared (supabase/migrations/20260424120000_add_donation_daf_sponsor_and_fund_fk.sql, both ON DELETE SET NULL), but the data layer keeps the separate-lookup pattern because:
1. It is resilient to PostgREST schema-cache misses (the same workaround already lives in getDafDonationSummary). 2. It avoids round-tripping nested embed payloads when only name / ein columns are needed. 3. Adding/removing nullable FK columns in the future cannot silently break the report.
The unit test src/lib/db/daf-sponsors.fetchDafContributionsReport.test.ts pins this behavior and will fail if anyone reintroduces an embedded daf_sponsors: or organizations!fund_id select in the donations query.
Fiscal year handling
The function accepts a fiscalYearEndMonth (default 12). For calendar years it uses YYYY-01-01 … YYYY-12-31; for non-calendar fiscal years it spans (year-1)-(end+1)-01 … year-end-lastDay. The current UI is calendar-year only — the parameter is plumbed through so a future fiscal-year selector will not require a data-layer change.
State Management
Persisted (via useReportSettings('daf-contributions', …))
reportYear,pageSize,activeTab,scheduleBOnly
Local
searchQuery/debouncedSearchexpandedSponsorIds(By Sponsor row expansion)giftSort({ key, dir }for the All Gifts tab)bySponsorPage,allGiftsPageexportDialogOpen
Global (Zustand)
selectedEntity,setReportTool,navigateTo
Routing & Registration
| Surface | File | Change |
|---|---|---|
| Tool slug | `src/store/types.ts` | Adds `'daf-contributions-report'` to `ReportTool` |
| Hub tile | `src/features/reports/components/ReportsHub.tsx` | Inserted between `donor-reporting` and `mileage-report` (Landmark icon) |
| Page routing | `src/components/PageRouter.tsx` | Lazy import + switch branch next to `DonorReporting` |
| Navigation URL | `src/lib/navigationUrl.ts` | Added to the `reports` tool list |
| Permissions | `src/lib/permissionConstants.ts` + `auth.json` (`admin.permTree.dafContributions`) | Org admins can gate it like other reports |
| Search index | `src/lib/searchIndex/items.ts` | Searchable by `daf`, `donor advised fund`, `990`, `schedule b`, `sponsor`, `fidelity`, `schwab`, `vanguard` |
Relationship to the Form 990 Builder
The Form 990 Builder (../tools/08-FORM-990-BUILDER.md) owns Schedule B aggregation via getTopDonorsForScheduleB, which is the canonical Schedule B source. The DAF Contributions Report intentionally does not duplicate that logic; instead, it provides:
1. A year-round, hub-discoverable DAF roll-up for accountants, auditors, and board reports 2. A jump link to the Form 990 Builder for finalized Schedule B preparation 3. CPA-ready CSV / XLSX exports with EIN, fund attribution, and check / reference numbers
Use Cases
1. 990 Schedule B prep — pull the full DAF roll-up before opening the Form 990 Builder 2. Audit support — provide auditors with sponsor-level totals plus per-gift detail 3. Board reporting — quantify DAF dependence as a percentage of total contributions 4. Sponsor reconciliation — verify check / reference numbers against sponsor records
Related Documentation
Synced from IFMmvp-Frontend documentation: pages/reports/DAF-CONTRIBUTIONS-REPORT.md
Ready to Get Started?
See how Alignmint can simplify your nonprofit's operations. Schedule a free demo with our team and we'll walk you through everything.
Questions? Email us at steven@getalignmint.org