feat: add declined status to ContractStatus enum #380
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api!380
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "379-add-declined-status-to-contractstatus-en"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add
declinedto theContractStatusenum so players who explicitly decline their contract offer are tracked separately fromnone(never offered) andoffered(pending). Alembic migration 038 adds the enum value and updates three specific players (Boston Greenhalgh #98, Patrick Kirschman #94, Stein Kunic #134) todeclined. Email blast queries are updated to exclude declined players.Changes
src/basketball_api/models.py— Addeddeclined = "declined"toContractStatusenumalembic/versions/038_add_declined_contract_status.py— New migration: ALTER TYPE + UPDATE 3 playerssrc/basketball_api/routes/admin.py— Contract reminder endpoint now excludes bothsignedanddeclinedplayers (was only excludingsigned)src/basketball_api/services/email_queries.py—query_incomplete_profilesnow filters outdeclinedplayers so they don't receive profile completion emailsTest Plan
alembic upgrade headon staging to verify migration applies cleanlycontract_status = 'declined'in DBnone,offered,signedstatuses still work correctlyReview Checklist
Related
Closes #379
Related Notes
None
QA Review -- PR #380
Diff Summary (4 files, +33/-1)
src/basketball_api/models.py--declined = "declined"added toContractStatusenum. Correct placement aftersigned.alembic/versions/038_add_declined_contract_status.py-- Migration usesALTER TYPE contractstatus ADD VALUE IF NOT EXISTS 'declined'with explicitCOMMITfor the enum transaction boundary, thenUPDATEfor player IDs 98, 94, 134. Downgrade reverts data tonone. Postgres cannot remove enum values, so this is the correct approach.src/basketball_api/routes/admin.py-- Contract reminder query upgraded from!= ContractStatus.signedto.notin_([ContractStatus.signed, ContractStatus.declined]). This prevents declined players from receiving contract reminder emails.src/basketball_api/services/email_queries.py--query_incomplete_profilesnow filters!= ContractStatus.declinedso declined players are excluded from profile completion blasts. Thequery_unsigned_contractsfunction already filters== ContractStatus.offered, so declined players are naturally excluded there without changes.Checklist
offeredonly)Nits
None.
VERDICT: APPROVED
PR #380 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / Alembic
The PR adds a
declinedvalue to theContractStatusenum, creates Alembic migration 038 to ALTER TYPE and update 4 players, and modifies two query paths to exclude declined players from email blasts.Migration (038)
ALTER TYPE ... ADD VALUE IF NOT EXISTSfollowed by explicitCOMMITis the correct Postgres pattern for enum additions inside Alembic.IF NOT EXISTSguard makes re-runs safe.Admin route (
admin.py)!= signedto.notin_([signed, declined]). This is the right approach since both statuses should be excluded.Email queries (
email_queries.py)!= ContractStatus.declinedwhich is functionally correct for excluding declined players. Note this does NOT also excludesignedplayers from incomplete profile emails -- verify this is intentional (signed players with incomplete profiles should still get nagged?).Sign contract endpoint (
players.py, line 294)ContractStatus.signed. A player withcontract_status = 'declined'CAN still sign their contract. This is likely intentional (player changes their mind), but worth confirming since it is a business logic decision.BLOCKERS
1. No test coverage for the new enum value
This is new functionality (
declinedstatus) with zero test coverage. The existingtest_contract.pyhas thorough tests fornone,offered, andsignedstatuses, but nothing fordeclined. At minimum:contract_status = declinedis excluded from the contract reminder querycontract_status = declinedis excluded fromquery_incomplete_profilesContractStatus.declinedexists and has value"declined"Per review criteria: "New functionality with zero test coverage" is a blocker.
2. PR body says "3 players" but migration updates 4 (player ID 89 undocumented)
The PR summary lists players
#98 (Boston Greenhalgh), #94 (Patrick Kirschman), #134 (Stein Kunic)but the migration SQL updatesWHERE id IN (98, 94, 134, 89). Player ID 89 is present in the code but not mentioned in the PR body. The migration docstring correctly says "4 players." The PR description must accurately document which players are being modified -- this is a data migration touching production records.NITS
Filter style inconsistency:
admin.pyuses.notin_([signed, declined])whileemail_queries.pyuses!= declined. If future statuses are added (e.g.,expired), the!=pattern will need updating in each location independently. Consider using.notin_()consistently for maintainability, or at minimum add a comment explaining why onlydeclinedis excluded in the email query path.Migration docstring minor: The docstring says "migrate 4 players" but could name them for traceability, matching the level of detail in the PR body.
SOP COMPLIANCE
379-add-declined-status-to-contractstatus-enfollows{issue-number}-{kebab-case-purpose}convention (truncated but acceptable)PROCESS OBSERVATIONS
email_queries.pyfile appears to be new (not present on main), suggesting this PR depends on another unmerged PR. Confirm the merge order is correct.VERDICT: NOT APPROVED
Two blockers must be resolved:
declinedcontract status (enum value exists, query exclusion behavior).PR #380 Re-Review
Re-review after fixes for 3 blockers flagged in initial QA (missing tests, filter inconsistency, PR body mismatch).
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy / Alembic
Enum addition --
declined = "declined"added toContractStatusinmodels.py. Clean, follows existing pattern.Migration 038 -- Uses
ALTER TYPE ... ADD VALUE IF NOT EXISTSwith an explicitCOMMITbefore theUPDATE. This is the correct Postgres pattern (enum additions cannot run inside a transaction with DML).IF NOT EXISTSmakes the migration idempotent. Downgrade correctly reverts data tononeand documents that Postgres cannot remove enum values. Solid.Filter updates -- Both
admin.py(contract reminder) andemail_queries.py(incomplete profiles) now use.notin_([ContractStatus.signed, ContractStatus.declined]). This is consistent and correct. No other blast query functions referencecontract_statusin a way that would need updating.Sign-contract endpoint -- Only blocks
signedstatus (409). Adeclinedplayer can still sign later. This is correct product behavior.Tests added -- 3 new tests covering the declined exclusion:
test_skips_declined_playersintest_contract_reminder.py-- sets player to declined, verifiessent_count == 0test_excludes_declined_playersintest_email_blast.py-- sets player to declined, verifies empty resultstest_excludes_signed_playersintest_email_blast.py-- sets player to signed, verifies empty results (regression guard for the new.notin_filter)All three tests follow existing fixture patterns (
parent_with_unsigned_player,parent_with_incomplete_player), mock the right dependencies, and assert the expected behavior.BLOCKERS
None. All three previous blockers are resolved:
.notin_()pattern.NITS
SOP COMPLIANCE
379-add-declined-status-to-contractstatus-en(truncated but follows convention)Closes #379PROCESS OBSERVATIONS
test_excludes_signed_playerstest is a nice addition -- it guards against regression if someone changes the filter back to!=instead of.notin_().VERDICT: APPROVED