Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions .forge/features/admin-member-dashboard/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,14 @@ manage current dues status, and remove invalid member accounts.
- `Edit member access`: `EDIT_MEMBERS` or officer access.
- `Effective permissions`: The union of permission bits granted by all roles
linked to the signed-in user.
- `Current dues status`: Whether an active dues record currently counts for the
member under the academic-school-year rules.
- `Current dues status`: Whether the member has an active entitlement for the
current academic school year.
- `Payment availability`: Whether unpaid members may start a new Stripe dues
payment. It does not change any member's paid status or history.
- `Revoke dues`: Mark every active row that would keep the member effectively
paid as inactive while preserving it for future history.
- `Mass invalidate dues`: Mark the dues records covered by the officer action
inactive until every affected member is unpaid, while preserving them.
- `Revoke dues`: Deactivate the member's current academic-year entitlement while
preserving recorded payment history.
- `Mass invalidate dues`: Deactivate every active current academic-year
entitlement while preserving recorded payment history.

## Acceptance criteria

Expand All @@ -136,9 +136,8 @@ manage current dues status, and remove invalid member accounts.
- An editor can update a selected member and sees the updated row/detail state.
- An editor can delete a selected member after confirmation, and the deleted
account no longer appears in results.
- An editor can mark an unpaid member paid for the payable academic school year
and can click the status to revoke every record that would keep the member
paid without deleting history.
- An editor can grant or revoke the selected member's current academic-year
entitlement without creating, rewriting, or deleting payment history.
- An editor can pause or enable member-initiated payments; a read-only member
admin can see but cannot change that state.
- Search covers name, email, Discord username, and company.
Expand Down
36 changes: 17 additions & 19 deletions .forge/features/admin-member-dashboard/srd.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,21 @@ reusable inputs live in `@forge/validators`.
ID plus member profile values, updates the selected member rather than the
caller, and keeps the code-owned signup response consistent.
- `memberAdmin.deleteAdminMember`: requires edit-member access and deletes the
selected Member row, signup response, dues rows, and unreferenced member
uploads. It retains the User, roles, permissions, sessions, and Hacker data.
- `memberAdmin.setAdminDuesStatus`: requires edit-member access. Marking paid creates
a manual active record for the payable year with the configured dues amount
and no Stripe intent, or reactivates an existing payable-year record without
rewriting its payment metadata. Revoking repeatedly resolves effective rows
and marks them inactive until the member is unpaid; it does not delete
history.
selected Member row, signup response, dues payment and entitlement rows, and
unreferenced member uploads. It retains the User, roles, permissions,
sessions, and Hacker data.
- `memberAdmin.setAdminDuesStatus`: requires edit-member access. Marking paid
creates or reactivates the current academic-year entitlement without creating
or rewriting a payment. Revoking deactivates that entitlement; neither action
changes recorded payment history.
- `memberAdmin.getDuesPaymentConfiguration`: requires read-member access and
returns the persisted global payment-availability setting.
- `memberAdmin.setDuesPaymentsEnabled`: requires edit-member access, upserts the
singleton setting transactionally, and records the before/after value in the
durable admin audit log.
- `memberAdmin.invalidateEffectiveDues`: requires officer access and, in one
transaction, repeatedly resolves and invalidates rows until every affected
member is unpaid. The returned count is distinct affected members rather than
modified rows.
transaction, deactivates every active current academic-year entitlement. The
returned count is distinct affected members.
- `memberAdmin.exportAdminMembers`: requires read-member access and returns an
escaped CSV for every result matching the current filters and fuzzy search.
- Admin profile-picture and resume procedures resolve the target User from the
Expand All @@ -111,17 +109,17 @@ reusable inputs live in `@forge/validators`.
## Data / migration / compatibility

- Existing `Roles`, `Permissions`, `Member`, `FormResponse`, `DuesPayment`,
`Event`, `EventAttendee`, `Company`, `Employment`, and Discord archive rows
remain authoritative for their existing domains.
`DuesEntitlement`, `Event`, `EventAttendee`, `Company`, `Employment`, and
Discord archive rows remain authoritative for their existing domains.
- Add one `DuesConfiguration` singleton row keyed by `global`. It defaults to
`paymentsEnabled = false`; a missing row also reads as paused.
- Manual dues records use cents, the same configured price, academic-year
calculation, unique member/year rule, and active/stale semantics as Stripe
records.
- Payment rows are immutable recorded history. New rows come from Stripe while
legacy manual rows remain preserved. Admin grants and revocations write only
the member/year entitlement and never synthesize a payment.
- Legacy Blade dues rows used the UTC calendar year instead of the academic-year
start and did not reliably distinguish manual grants from Stripe payments.
Active current-calendar-year rows remain valid compatibility inputs for
status, individual revoke, filtering, CSV, and mass invalidation.
start. The entitlement migration normalizes January-July legacy years and
backfills the corresponding entitlement before removing payment activity
state.
- Current production `main` continues to interpret the same permission
bitstrings and tables. This feature adds no permission indices and therefore
remains compatible with existing roles and the bootstrap-superadmin script.
Expand Down
14 changes: 12 additions & 2 deletions .forge/features/admin-member-dashboard/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ Current phase: Complete

## Decision log

- 2026-08-12: Issue #504 exposed that payment rows were serving as both recorded
history and mutable membership state. Payments are now immutable;
a separate member/year entitlement owns paid status. Admin grants,
revocations, and mass invalidation change entitlements only, and member
checkout always targets the current academic year.
- 2026-08-11: Human requested a persisted admin toggle on `/admin/members` to
replace the hardcoded dues-payment cutoff. Existing edit-member access owns
the mutation; readers can see the operational state without changing it.
Expand All @@ -18,7 +23,7 @@ Current phase: Complete
backed by page and tRPC authorization checks.
- 2026-06-27: The legacy production dashboard is reference material only. The
Reforge dashboard must use the current member and dues models, including
academic-year payment history and stale records.
academic-year payment history and inactive entitlements.
- 2026-06-27: Human selected member editing, deletion, individual dues
controls, first-class filtering, search, and page sizes 25/50/100/250/500;
payment history is deferred. Detailed behavior still requires reverse-prompt
Expand Down Expand Up @@ -106,6 +111,7 @@ Current phase: Complete
- [x] Complete targeted and repository validation.
- [x] Add the persisted member-payment availability control and API contract.
- [x] Validate the payment-availability follow-up and review the admin UI.
- [x] Separate immutable dues payments from current-year entitlements.

## Validation / commands

Expand Down Expand Up @@ -181,9 +187,13 @@ Current phase: Complete
- 2026-08-11 payment-availability follow-up: API and Blade test suites passed;
the 6-scenario dues Playwright run passed; desktop and 390px mobile visual
review confirmed the header control remains compact and overflow-free.
- 2026-08-12 dues entitlement redesign: admin grant/revoke now changes only the
current-year entitlement, bulk invalidation leaves payment history and other
years unchanged, and the two focused admin Playwright regressions passed
against a disposable PostgreSQL database.

## Links

- PRs:
- Issues:
- Issues: #504
- Discord/thread context:
31 changes: 16 additions & 15 deletions .forge/features/admin-member-dashboard/test-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,20 @@ Expected observations:

Setup:

- An editor views one unpaid and one paid member.
- An editor views a member with active dues for the current academic year.

Action:

- Click the unpaid status to grant dues, then click the paid status to revoke
dues.
- Revoke the member's dues, then grant them again.

Expected observations:

- Manual paid status uses the payable academic year and configured amount.
- Revoke marks every row that would keep the member paid inactive without
deleting history.
- Granting dues reactivates the same current-year entitlement without creating
a payment.
- Revoking deactivates only the current-year entitlement.
- Existing payment amount, date, Stripe PaymentIntent ID, and row count remain
unchanged.
- No next-academic-year entitlement or payment is created.
- The dashboard updates the status and no payment history UI appears.

### TC-007: Editor deletes a selected member
Expand Down Expand Up @@ -162,9 +164,8 @@ Expected observations:

Setup:

- An officer views members with effective, stale, historical, unrelated active,
and legacy calendar-year manual dues rows. At least one member has both a
current academic-year row and a manual calendar-year grant.
- An officer views members with active current-year, inactive current-year,
historical, and future entitlements plus retained payment history.

Action:

Expand All @@ -173,9 +174,9 @@ Action:
Expected observations:

- Paste is blocked in typed stages.
- Rows are resolved and invalidated until every affected member is unpaid,
including legacy manual grants, in one transaction.
- Historical and unrelated active rows remain unchanged.
- Every active current-year entitlement is deactivated in one transaction.
- Historical and future entitlements remain unchanged.
- Payment history remains unchanged.
- The UI reports the affected-member count.

### TC-010: Editor manages target-owned files
Expand Down Expand Up @@ -246,7 +247,7 @@ Setup:

- A Member has event check-ins by a known operator, archived human Discord
messages across channels and dates, employment rows, Guild profile data,
linked roles, and retained dues credits.
linked roles, and retained dues payment and entitlement rows.

Action:

Expand Down Expand Up @@ -363,8 +364,8 @@ Expected observations:

Setup:

- A selected member was removed, or a current/payable dues record already
exists when a manual paid action races another write.
- A selected member was removed, or their current-year entitlement changes
while a manual dues action races another write.

Action:

Expand Down
21 changes: 11 additions & 10 deletions .forge/features/club-analytics/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ separate user-account count.
counts, paid coverage rate, and the change from the comparable point in the
previous academic year.
- Dues history shows:
- recorded dues credits by week and month;
- recorded dues entitlements by week and month;
- cumulative collection curves by academic school year;
- academic-year payment-count comparisons;
- progress toward 25%, 50%, 75%, and 90% profile coverage;
- the date each reached milestone was met;
- active and stale retained dues credits;
- active and inactive retained dues entitlements;
- attendance reach and repeat attendance for paid and unpaid profiles.
- Historical academic-year coverage uses retained profiles that existed by the
end of that academic year. The report identifies that denominator and does
Expand All @@ -206,9 +206,9 @@ separate user-account count.
points. It contains no edit controls or contact fields.
- A user with separate member-administration access may follow a link to the
existing Members dashboard. Analytics access alone never grants edits.
- Copy uses `Paid`, `Unpaid`, or `No active dues credit recorded`. It does not
call a member delinquent or overdue because Blade has no dues deadline or
waiver policy.
- Copy uses `Paid`, `Unpaid`, or `No active dues credit recorded`. The
user-facing credit maps to `DuesEntitlement`; it does not call a member
delinquent or overdue because Blade has no dues deadline or waiver policy.

### Reports and exports

Expand Down Expand Up @@ -315,9 +315,10 @@ separate user-account count.
90 days available to return before the report's observation end.
- `Representation gap`: A segment's share of attendees minus its share of all
Member profiles.
- `Current dues status`: Whether an active dues credit counts for the Member
- `Current dues status`: Whether an active dues entitlement counts for the Member
profile under the shared academic-school-year rules.
- `Dues credit`: A retained dues row that may be active or stale.
- `Dues entitlement`: A retained member/year row whose active state determines
whether the member is current for that academic year.
- `Data coverage`: Profiles with a usable value divided by the applicable
profile population.
- `Internal CSV`: A full read-only operational export for a user with
Expand Down Expand Up @@ -357,9 +358,9 @@ separate user-account count.
- Discord matched-Member rows expose only the approved analytical identity and
activity counts; full Member presentation opens only under the existing
Member-admin read policy.
- Dues reporting shows current paid/unpaid counts, payment timing, academic-year
comparisons, collection pace, milestones, and active/stale history without
showing dollars.
- Dues reporting shows current paid/unpaid counts, entitlement timing,
academic-year comparisons, collection pace, milestones, and active/inactive
history without showing dollars.
- The unpaid-member table contains names and analytical context but no contact
fields or edits.
- Internal CSVs match the active filters and include the full values and named
Expand Down
34 changes: 18 additions & 16 deletions .forge/features/club-analytics/srd.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ Additional rules:
2. Blade parses URL search parameters through shared validators and calls
`analytics.getReport` with one normalized filter object.
3. The API repeats access enforcement and selects only needed fields from
retained Member, non-hackathon Event, EventAttendee, DuesPayment,
retained Member, non-hackathon Event, EventAttendee, DuesEntitlement,
DuesPayment,
EventFeedbackConfig, and linked FormResponse records.
4. One pure report builder resolves periods, deduplicates attendance, normalizes
feedback metrics, and builds all five section DTOs from the scoped records.
Expand All @@ -114,8 +115,9 @@ population metadata are returned with every report.
- Attendance uses `EventAttendee.memberId` and `eventId`. Multiple rows for the
same pair collapse to one distinct attendance. `checkedInAt` is metadata; the
event's scheduled start selects the reporting period.
- Dues status reuses `buildDuesStatus` so active, stale, payable-year, and
legacy calendar-year compatibility match existing Member surfaces.
- Current dues status reads active `DuesEntitlement` rows for the selected
academic year. A linked `DuesPayment`, when present, supplies immutable
payment metadata without owning membership state.
- Event feedback uses the explicit `EventFeedbackConfig` -> `FormResponse`
association and the core `overall`, `fun`, `learning`, and `discovery`
answers. Invalid stored values are excluded from that metric and reflected in
Expand Down Expand Up @@ -257,25 +259,25 @@ are non-negative integers.

### Dues definitions

- Current paid/unpaid status is calculated for every current Member profile by
passing all of that member's retained dues rows through `buildDuesStatus` at
the report reference date.
- Current paid/unpaid status is calculated from each Member's active
`DuesEntitlement` for the report reference date's academic year.
- Current paid coverage is paid profiles divided by all current retained
profiles. Every current profile is expected to pay; no waiver population is
inferred.
- Recorded-credit trends deduplicate `(memberId, year)` and group by
`paymentDate`. Active and stale counts remain separate.
- Academic-year curves use the row's stored dues year and elapsed days from
August 1. They report distinct recorded credits, active credits, stale
credits, and recorded-credit coverage.
- Entitlement trends are already unique by `(memberId, year)` and group by the
linked payment date when present, otherwise the entitlement creation date.
Active and inactive counts remain separate.
- Academic-year curves use the entitlement's stored year and elapsed days from
August 1. They report distinct recorded entitlements, active entitlements,
inactive entitlements, and entitlement coverage.
- A historical academic-year denominator contains retained profiles whose
`dateCreated` is before that academic year's exclusive August 1 end. It is
labeled `retained profiles by year end`.
- Coverage milestones at 25%, 50%, 75%, and 90% use the first payment date on
which recorded-credit coverage met the threshold. An unreached milestone has
a null date.
- Previous-year pace compares recorded credits at the same elapsed day of the
academic year. It does not compare dollars.
- Coverage milestones at 25%, 50%, 75%, and 90% use the first recorded date on
which entitlement coverage met the threshold. An unreached milestone has a
null date.
- Previous-year pace compares recorded entitlements at the same elapsed day of
the academic year. It does not compare dollars.
- Paid/unpaid engagement uses current dues status crossed with selected-period
distinct attendance, reach, and repeat attendance.
- Named unpaid rows contain Member UUID, display name, graduation year,
Expand Down
3 changes: 3 additions & 0 deletions .forge/features/club-analytics/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Current phase: Complete

## Decision log

- 2026-08-12: Issue #504 replaced payment-row activity with one active/inactive
`DuesEntitlement` per member and academic year. Club analytics now reads
those entitlements; its user-facing "dues credit" copy remains unchanged.
- 2026-07-16: Created the feature bundle on `reforge/club-analytics`.
- 2026-07-16: The user selected non-hackathon Club analytics and dues
reporting; hackathon analytics remains deferred.
Expand Down
Loading
Loading