Skip to content

feat(admin): track organization donations over time #1971

@cdcore09

Description

@cdcore09

Summary

Track financial contributions from organizations across time, separate from the recurring-membership tier system. Donations are one-time or campaign-attributed contributions that don't fit org_memberships (annual recurring) or event_sponsorships (event-tied).

Requirements

  • New org_donations table: id, organization_id (FK → organizations.id ON DELETE restrict), amount_cents (bigint), currency (text, ISO 4217, default 'USD'), donation_date (date NOT NULL), campaign (text, nullable — free-form for now), fiscal_year (int, nullable), notes (text), created_at/updated_at/recorded_by_user_id (audit columns)
  • Migration adds the table + index on (organization_id, donation_date DESC) for fast per-org history queries
  • API: GET/POST/PATCH/DELETE /admin/organizations/:id/donations; list returns rows sorted by donation_date desc with per-currency totals as aggregates
  • Org detail page: new "Donations" tab listing the org's contribution history with running totals by year
  • Permissions: donation writes gated on staff tier (canEditOrganizations); consider a canRecordDonations policy if we want to lock this down further later
  • Audit middleware captures all donation mutations (organizations.donation.create, .update, .delete)

Context

Why not extend org_memberships? Memberships are recurring annual tiers with one active row per org; donations are arbitrarily many transactions across time, often campaign-attributed. Why not extend event_sponsorships? Donations aren't event-bound. The clean split is three independent tables sharing only the organization_id FK.

Implementation Notes

amount_cents as bigint keeps us currency-precision-safe without a numeric type. Storing currency per-row instead of org-level lets a single org's history span currency changes (e.g., GBP → USD after a UK org's US subsidiary takes over donations).

Fiscal year is nullable because US-RSE doesn't have one defined yet — leaving the column means we can backfill it later via a single UPDATE keyed off donation_date once the policy is set.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions