feat: jersey number selection in checkout flow #143

Closed
opened 2026-03-21 21:34:34 +00:00 by forgejo_admin · 0 comments

Type

Feature

Lineage

plan-wkq → Phase 11 (Girls Tryout) → discovered during jersey email blast 2026-03-21

Repo

forgejo_admin/basketball-api

User Story

As a parent, I want to pick a jersey number during checkout so that my child's jersey is personalized
As an admin, I want no duplicate numbers within a division so that game officials can identify players

Context

Jersey checkout has no number selection. Per AAU rules (NFHS 2025-2026), all numbers 0, 00, and 1-99 are legal. Numbers must be unique per division (boys/girls) — two girls can't share a number, but a boy and girl can. Duplicate validation checks against all players in the same division who already have a jersey_number set.

File Targets

Files the agent should modify:

  • src/basketball_api/models.py — add jersey_number column (String, nullable) to Player model
  • src/basketball_api/routes/jersey.py — accept number param in POST /jersey/checkout, validate 0/00/1-99, check uniqueness within division, pass as Stripe metadata jersey_number. Add GET /jersey/taken-numbers?division=girls endpoint returning already-claimed numbers.
  • src/basketball_api/routes/webhooks.py — read jersey_number from Stripe metadata, save to player
  • alembic/versions/ — new migration adding jersey_number column

Files the agent should NOT touch:

  • src/basketball_api/services/email.py — unrelated
  • src/basketball_api/routes/admin.py — unrelated

Acceptance Criteria

  • POST /jersey/checkout accepts number param (string: "0", "00", "1"-"99")
  • POST /jersey/checkout rejects number outside valid range
  • POST /jersey/checkout rejects duplicate number within same division (query players where division matches AND jersey_number matches)
  • POST /jersey/checkout does not require number for opt-out
  • GET /jersey/taken-numbers?division=girls returns list of numbers already claimed in that division
  • Webhook reads jersey_number from Stripe metadata and saves to player
  • New alembic migration adds jersey_number column

Test Expectations

  • Unit test: checkout with valid number passes number in Stripe metadata
  • Unit test: checkout with duplicate number in same division returns 409
  • Unit test: same number in different division succeeds
  • Unit test: checkout without number returns 422 (except opt-out)
  • Unit test: taken-numbers endpoint returns claimed numbers
  • Unit test: webhook saves jersey_number from metadata
  • Run command: pytest tests/ -k jersey

Constraints

  • Valid numbers: "0", "00", "1" through "99" (string field — "00" is distinct from "0")
  • Uniqueness scope: per division (boys/girls), NOT per team (teams not assigned for girls yet)
  • jersey_number stored as String (not Int) because "00" is valid and distinct from "0"
  • Number only required for non-opt-out checkout

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • plan-wkq — Phase 11
  • project-westside-basketball
  • basketball-api #141 — jersey size (same pattern)
### Type Feature ### Lineage `plan-wkq` → Phase 11 (Girls Tryout) → discovered during jersey email blast 2026-03-21 ### Repo `forgejo_admin/basketball-api` ### User Story As a parent, I want to pick a jersey number during checkout so that my child's jersey is personalized As an admin, I want no duplicate numbers within a division so that game officials can identify players ### Context Jersey checkout has no number selection. Per AAU rules (NFHS 2025-2026), all numbers 0, 00, and 1-99 are legal. Numbers must be unique per division (boys/girls) — two girls can't share a number, but a boy and girl can. Duplicate validation checks against all players in the same division who already have a jersey_number set. ### File Targets Files the agent should modify: - `src/basketball_api/models.py` — add `jersey_number` column (String, nullable) to Player model - `src/basketball_api/routes/jersey.py` — accept `number` param in `POST /jersey/checkout`, validate 0/00/1-99, check uniqueness within division, pass as Stripe metadata `jersey_number`. Add `GET /jersey/taken-numbers?division=girls` endpoint returning already-claimed numbers. - `src/basketball_api/routes/webhooks.py` — read `jersey_number` from Stripe metadata, save to player - `alembic/versions/` — new migration adding `jersey_number` column Files the agent should NOT touch: - `src/basketball_api/services/email.py` — unrelated - `src/basketball_api/routes/admin.py` — unrelated ### Acceptance Criteria - [ ] `POST /jersey/checkout` accepts `number` param (string: "0", "00", "1"-"99") - [ ] `POST /jersey/checkout` rejects number outside valid range - [ ] `POST /jersey/checkout` rejects duplicate number within same division (query players where division matches AND jersey_number matches) - [ ] `POST /jersey/checkout` does not require number for opt-out - [ ] `GET /jersey/taken-numbers?division=girls` returns list of numbers already claimed in that division - [ ] Webhook reads `jersey_number` from Stripe metadata and saves to player - [ ] New alembic migration adds `jersey_number` column ### Test Expectations - [ ] Unit test: checkout with valid number passes number in Stripe metadata - [ ] Unit test: checkout with duplicate number in same division returns 409 - [ ] Unit test: same number in different division succeeds - [ ] Unit test: checkout without number returns 422 (except opt-out) - [ ] Unit test: taken-numbers endpoint returns claimed numbers - [ ] Unit test: webhook saves jersey_number from metadata - Run command: `pytest tests/ -k jersey` ### Constraints - Valid numbers: "0", "00", "1" through "99" (string field — "00" is distinct from "0") - Uniqueness scope: per division (boys/girls), NOT per team (teams not assigned for girls yet) - jersey_number stored as String (not Int) because "00" is valid and distinct from "0" - Number only required for non-opt-out checkout ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes ### Related - `plan-wkq` — Phase 11 - `project-westside-basketball` - basketball-api #141 — jersey size (same pattern)
forgejo_admin 2026-03-21 21:44:28 +00:00
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
forgejo_admin/basketball-api#143
No description provided.