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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions