fix: per-player contract rows and working action.mjml #306
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!306
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "303-fix-query-and-template"
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
Fixes two bugs blocking the contract reminder email blast: the unsigned_contracts query silently dropped contracts for multi-child parents, and action.mjml used MJML v5-incompatible features that broke rendering.
Changes
src/basketball_api/services/email_queries.py— Removed parent_map grouping. Query now emits one row per player instead of one row per parent. Each contract is a separate legal document needing its own email with a direct sign link. Removed unusedParentimport.templates/email/action.mjml— Replaced broken template that usedmj-groupleft-border trick andcss-class/mj-include(all broken in MJML v5 beta) with working version usingmj-tablefor the red left-border card and inline attributes.tests/test_email_blast.py— Addedtest_multi_player_parent_returns_per_player_rowscovering the exact bug scenario (Sandra Apaisa with 3 unsigned children).Test Plan
ruff formatandruff checkcleanReview Checklist
player_count/playerskeysRelated Notes
None.
Related
Closes #303
QA Review -- PR #306
Query change (email_queries.py)
Parentimport properly removedplayer_count/playerskeys (verified via grep across src/)Template change (action.mjml)
mj-group(broken in MJML v5 beta) withmj-tablefor the red left-border cardcss-classandmj-includereferences (both broken in v5 beta)mj-attributesblock{{headline}},{{body}},{{cta_url}},{{cta_text}},{{footer_note}}background-coloron headline/body/CTA sections (#141414) where old template relied on inherited/undefined backgroundTest addition (test_email_blast.py)
Nits
None.
VERDICT: APPROVED
PR #306 Review
DOMAIN REVIEW
Stack: Python / FastAPI / SQLAlchemy (query layer), MJML (email templates), pytest (tests).
Query logic (
email_queries.py):parent_mapapproach collapsed N children into one entry, meaning only the first child'scontract_urlwas used as the primary link. Parents with multiple unsigned children would receive one email with one link -- the other contracts were buried in aplayerslist that the action template had no mechanism to render per-row.contract_url. This matches the stated requirement: each contract is a separate legal document needing its own sign link.test_emailfiltering moved from post-grouping dict comprehension to inlinecontinue-- functionally equivalent, slightly simpler.Parentimport is correct --player.parentis accessed via the ORM relationship, no directParentquery needed.player_count,playerslist) were only consumed internally by this query function's output shape. The PR checklist confirms no downstream consumers depend on these keys. The blast endpoint consumes the flat dict keys (to,parent_name,player_name,team_name,contract_url) which are all preserved.Template (
action.mjml):mj-group(deprecated/broken in MJML v5 beta),css-classattribute (not supported without custom CSS head), andmj-include(requires build tooling).mj-tablewith inlinestylefor the red left-border card -- this is the standard workaround for left-border cards in MJML and renders reliably across email clients.background-colorattributes moved frommj-sectiondefault to explicit per-section --#141414for content sections,#0a0a0afor header/footer. Consistent with brand.{{headline}},{{body}},{{cta_url}},{{cta_text}},{{footer_note}}-- no contract breakage with consumers.Test (
test_email_blast.py):test_multi_player_parent_returns_per_player_rowsdirectly exercises the bug scenario: Sandra Apaisa with 3 unsigned children.contract_tokenvalues, and junction table entries.db.flush()between creates for FK integrity -- correct pattern for SQLAlchemy test fixtures.BLOCKERS
None.
NITS
team_namefallback:player.teams[0].name if player.teams else "Westside"-- the hardcoded"Westside"fallback was inherited from the old code, not introduced here. Consider extracting this to a constant (DEFAULT_TEAM_NAME) in a future cleanup ticket. Not blocking.Test assertion style: The contract URL check
assert any(tok in url for url in result_urls)works but is slightly indirect. A more direct assertion would beassert f"{_CONTRACT_BASE_URL}/{tok}" in result_urls-- though this would require importing the constant. Minor preference, not blocking.SOP COMPLIANCE
303-fix-query-and-templatereferences issue #303Closes #303fix: per-player contract rows and working action.mjml)One minor gap: the Related section says "None" for plan slug references, which is acceptable since this is a bug fix, not plan-driven work.
PROCESS OBSERVATIONS
player_count,players) that were unused by the template consumer. The blast endpoint passes individual dict values into template placeholders -- the flat per-player shape aligns better with this pattern.VERDICT: APPROVED