Feature: Add alumni flag to block membership eligibility #503

Open
opened 2026-04-28 07:15:33 +00:00 by jjalangtry · 0 comments
jjalangtry commented 2026-04-28 07:15:33 +00:00 (Migrated from github.com)

Problem

Once a user is alumni, they should no longer be able to earn or receive active SSE memberships.

Right now membership is represented by rows in Memberships, and isMember is computed from membership count in several places. There is no user-level alumni flag that membership-grant paths can check before creating new membership rows.

This creates two risks:

  1. Alumni can accidentally receive new memberships through automatic systems like profile completion or event attendance reconciliation
  2. Alumni with historical membership rows may still be treated as current members in places that only check Memberships: { some: {} } or _count.Memberships >= 1

Proposed Solution

Add an alumni boolean/state on the user record and use it as a hard block for membership eligibility.

Suggested schema shape:

model User {
  // ...existing fields
  isAlumni Boolean @default(false)
}

When a user becomes alumni, set isAlumni = true. From that point forward, the user cannot receive new memberships and should not be treated as an active member even if historical Memberships rows remain in the database.

Scope

In scope:

  • Add a persisted alumni flag to User
  • Set the flag when an AlumniCandidate is approved into an Alumni record
  • Set the flag when an officer manually creates/links an Alumni record to a user
  • Clear or review the flag if an alumni record is deleted/unlinked, with explicit product behavior documented
  • Block new membership creation for alumni in all membership grant paths
  • Update active membership checks so alumni are not considered current members
  • Preserve historical Memberships rows for audit/history; do not delete old membership records just because someone becomes alumni
  • Surface a clear error when an officer attempts to manually grant membership to an alumni user

Membership paths to audit include:

  • Profile completion grants in next/lib/services/profileCompletionService.ts
  • Manual membership grants in next/app/api/memberships/route.ts
  • Event attendance membership grants/reconciliation in next/app/api/event/[id]/attendance/route.ts
  • Invitation/acceptance membership grants in next/app/api/invitations/accept/route.ts
  • Officer creation flows that currently create membership rows in next/app/api/officer/route.ts
  • Auth/member computations in next/lib/services/authLevelService.ts and next/lib/authLevelResolver.ts
  • Election/amendment recipient/member queries that use raw Memberships existence

Out of scope:

  • Deleting historical membership records
  • Automatically approving alumni requests
  • Replacing the alumni review workflow

Acceptance Criteria

  • User has an alumni flag or equivalent persisted boolean state
  • Approving an alumni candidate marks the linked user as alumni
  • Creating or linking an alumni record for a user marks that user as alumni
  • Profile-completion membership grants do not create memberships for alumni
  • Event attendance membership grants/reconciliation do not create memberships for alumni
  • Manual membership creation returns a clear 4xx error for alumni users
  • Invitation/officer flows do not silently grant membership to alumni users
  • isMember and membershipCount-based access checks do not treat alumni as current active members
  • Historical membership rows remain visible for records/auditing
  • Tests cover alumni blocked from each membership grant path and non-alumni users still receiving memberships normally

Additional Context

Related new alumni automation issue: #502

## Problem Once a user is alumni, they should no longer be able to earn or receive active SSE memberships. Right now membership is represented by rows in `Memberships`, and `isMember` is computed from membership count in several places. There is no user-level alumni flag that membership-grant paths can check before creating new membership rows. This creates two risks: 1. Alumni can accidentally receive new memberships through automatic systems like profile completion or event attendance reconciliation 2. Alumni with historical membership rows may still be treated as current members in places that only check `Memberships: { some: {} }` or `_count.Memberships >= 1` ## Proposed Solution Add an alumni boolean/state on the user record and use it as a hard block for membership eligibility. Suggested schema shape: ```prisma model User { // ...existing fields isAlumni Boolean @default(false) } ``` When a user becomes alumni, set `isAlumni = true`. From that point forward, the user cannot receive new memberships and should not be treated as an active member even if historical `Memberships` rows remain in the database. ## Scope In scope: - Add a persisted alumni flag to `User` - Set the flag when an `AlumniCandidate` is approved into an `Alumni` record - Set the flag when an officer manually creates/links an `Alumni` record to a user - Clear or review the flag if an alumni record is deleted/unlinked, with explicit product behavior documented - Block new membership creation for alumni in all membership grant paths - Update active membership checks so alumni are not considered current members - Preserve historical `Memberships` rows for audit/history; do not delete old membership records just because someone becomes alumni - Surface a clear error when an officer attempts to manually grant membership to an alumni user Membership paths to audit include: - Profile completion grants in `next/lib/services/profileCompletionService.ts` - Manual membership grants in `next/app/api/memberships/route.ts` - Event attendance membership grants/reconciliation in `next/app/api/event/[id]/attendance/route.ts` - Invitation/acceptance membership grants in `next/app/api/invitations/accept/route.ts` - Officer creation flows that currently create membership rows in `next/app/api/officer/route.ts` - Auth/member computations in `next/lib/services/authLevelService.ts` and `next/lib/authLevelResolver.ts` - Election/amendment recipient/member queries that use raw `Memberships` existence Out of scope: - Deleting historical membership records - Automatically approving alumni requests - Replacing the alumni review workflow ## Acceptance Criteria - [ ] `User` has an alumni flag or equivalent persisted boolean state - [ ] Approving an alumni candidate marks the linked user as alumni - [ ] Creating or linking an alumni record for a user marks that user as alumni - [ ] Profile-completion membership grants do not create memberships for alumni - [ ] Event attendance membership grants/reconciliation do not create memberships for alumni - [ ] Manual membership creation returns a clear 4xx error for alumni users - [ ] Invitation/officer flows do not silently grant membership to alumni users - [ ] `isMember` and `membershipCount`-based access checks do not treat alumni as current active members - [ ] Historical membership rows remain visible for records/auditing - [ ] Tests cover alumni blocked from each membership grant path and non-alumni users still receiving memberships normally ## Additional Context Related new alumni automation issue: #502
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
SSE/WebsiteTheSSEquel#503
No description provided.