This PR updates how the codebase evaluates user membership during overlapping terms, since the codebase previously supported only having one active term at a time.
I wrote some new utilities allowing each call site to be a bit more explicit about which terms we are evaluating a user for membership for. Since different behavior is desirable for different use cases, we can't evaluate user membership by implicitly assuming which term the call site is wanting to evaluate membership for now that we have multiple active terms at any given point in time. Most of those utilities should all have docstrings that I hope pretty clearly explain their use cases and why they're necessary. If not, please do let me know, because I want those utilities to be clear to other readers.
After writing those utilities in the model files, I updated the rest of the application to use those new utilities and deprecated the old utilities, which should get removed in a future PR.
Please start review in:
core/models.pypayments/models.py
Those two files contain the core utility changes and behavior updates. Most of the remaining files are call-site updates to use those utilities.
tested this, looks like it is good to go
test results from codex
Membership Overlap Staging — Execution Record
Preflight evidence
RoboBotapplication account was visibly online.Fixture state
http://localhost:8000; its check-in page requires a staff login before recording the interactive scenarios.Event check-in evidence
Membership Renewal Neededand named the later-ending renewal term. One attendance row exists.Successfully Checked In!with no renewal prompt. One attendance row exists.Already Signed In; the attendance count remains one.Member has not paid dues yet! Checked in:and identified the Colin fixture account. Attendance was still created.Successfully Checked In!with no renewal prompt.Regression coverage
The plan-required focused renewal coverage passed:
Both tests passed: the old-term-only member receives the renewal prompt and the renewal-term-only member succeeds without it. The wider
events.testsmodule is not an authoritative result yet because its unrelated lookup tests make optional UTD directory requests and log local TLS verification warnings. The Python runtime also logs unavailableblake2bandblake2shash implementations; neither warning affected the two required assertions.Remote Discord command evidence and data-boundary finding
#bot-commands, the operator's visible/profileresponse reported the account as Not a member, with historical terms and a Fall 2026 dues link. This does not match the local fixture's intended old-term-only membership./payresponse offered Fall 2026 and Spring 2027, rather than the two clearly labelled local overlap terms.Local RoboBot Beta evidence
discord_bot.pyprocess is now running against the same local database as the fixture and has connected to RoboBot Staging Ground as the separate RoboBot Beta application. Its local HTTP endpoint is listening on port 2468./profile: RoboBot Beta reported the expiring term as the current membership and offered payment for the later-ending renewal term./pay: RoboBot Beta listed both active terms, marked the expiring term as already paid, and presented the renewal-term payment link. No payment link was opened./profileand/pay: Colin supplied a RoboBot Beta screenshot showing only the renewal term as active, with no renewal-needed prompt;/paylists both active terms and marks the renewal term as already paid./profileand/pay: Colin supplied a RoboBot Beta screenshot showing Not a member, Dues needed for the renewal term, and neither/payterm marked already paid.Initial role and export evidence
/givememberrolesresponse reported that it added the staging member role to two active fixture users. This includes the old-term-only operator and renewal-term-only Colin. No before-and-after recording was retained; the operator accepted the direct command and role-state checks as the evidence variance./purgememberrolesreported processing 11 non-active members. The actual holder count fell to one: the active operator retained the role and unpaid Colin lost it. The command's 11 is its candidate count, not a literal before/after role-holder count.Intentionally skipped coverage
/link's email-backed flow. Replacing or renaming a fixture user would require a new account-link email path and must not be substituted by directly editing the Discord ID. The operator elected to skip this scenario.