Skip to main content

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

  • SkeletonTable on first load; opacity-60 dim while background-refetching
  • table-fixed w-full layout
  • PaginationControls rendered above and below the table (only when totalPages > 1)
  • EmptyState (Landmark icon) with two-state copy:
  • noDafGiftsYet — no DAF gifts exist for the selected year
  • noDafGiftsFound — 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 in form990-schedules.ts
  • getActualOrgId + getDonationScopeFilter for 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 only donations column that has a real FK constraint to its target table).
  • Sponsor and fund names are resolved via separate `id IN (...)` lookups against daf_sponsors and organizations after 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_gift filter 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 / debouncedSearch
  • expandedSponsorIds (By Sponsor row expansion)
  • giftSort ({ key, dir } for the All Gifts tab)
  • bySponsorPage, allGiftsPage
  • exportDialogOpen

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

Ready to get started?Start Plus Trial