138 lines
4.4 KiB
Python
138 lines
4.4 KiB
Python
"""Document lifecycle logic: eligibility, assignment backfill, primary team.
|
|
|
|
The rules this encodes are the club's:
|
|
|
|
- Only *active* members who belong to at least one team receive an
|
|
obligation. A player not placed on a team signs nothing.
|
|
- Obligations are role-scoped. A player on two teams signs the player
|
|
contract once; ``UNIQUE(document_id, user_id)`` is the guarantee, and the
|
|
team folder below is only where the signed copy is filed.
|
|
- Publishing a version (or enabling a document) backfills pending
|
|
assignments for everyone currently eligible. Joining a team backfills
|
|
that one person across every published document matching their role.
|
|
"""
|
|
|
|
from app.extensions import db
|
|
from app.models import (
|
|
DOCUMENT_KIND_ROLES,
|
|
Coach,
|
|
Document,
|
|
DocumentAssignment,
|
|
Manager,
|
|
OrgTeam,
|
|
Player,
|
|
User,
|
|
)
|
|
|
|
|
|
def user_org_team_ids(user):
|
|
"""Ids of the organisation teams the user is a member of.
|
|
|
|
Role-appropriate: placements for a player, the coach/managers
|
|
relationships (many-to-many *and* legacy single column) for staff. An
|
|
admin or a scout belongs to none, regardless of how many teams exist.
|
|
"""
|
|
from app.permissions import coach_org_teams, manager_org_teams
|
|
|
|
if isinstance(user, Player):
|
|
return [tp.org_team_id for tp in user.team_placements]
|
|
if isinstance(user, Coach):
|
|
return [team.id for team in coach_org_teams(user)]
|
|
if isinstance(user, Manager):
|
|
return [team.id for team in manager_org_teams(user)]
|
|
return []
|
|
|
|
|
|
def user_has_any_team(user):
|
|
"""Whether the user belongs to at least one organisation team."""
|
|
return bool(user_org_team_ids(user))
|
|
|
|
|
|
def primary_team_for_user(user):
|
|
"""The canonical team used for filing a signed copy, or None.
|
|
|
|
A member can sit on several teams but every signed file is filed under
|
|
one team only. The choice is deterministic: the team with the lowest
|
|
id among the user's memberships.
|
|
"""
|
|
team_ids = user_org_team_ids(user)
|
|
if not team_ids:
|
|
return None
|
|
return db.session.get(OrgTeam, min(team_ids))
|
|
|
|
|
|
def user_eligible_for_kind(user, kind):
|
|
"""Whether *user* must complete documents of *kind*."""
|
|
if kind not in DOCUMENT_KIND_ROLES:
|
|
return False
|
|
if not user.is_active_account:
|
|
return False
|
|
if user.role not in DOCUMENT_KIND_ROLES[kind]:
|
|
return False
|
|
return user_has_any_team(user)
|
|
|
|
|
|
def eligible_user_ids(kind):
|
|
"""Ids of every user currently eligible for *kind*, ordered by id."""
|
|
roles = DOCUMENT_KIND_ROLES[kind]
|
|
users = (
|
|
User.query.filter(User.role.in_(roles), User.is_active_account.is_(True))
|
|
.order_by(User.id)
|
|
.all()
|
|
)
|
|
return [u.id for u in users if user_has_any_team(u)]
|
|
|
|
|
|
def backfill_assignments_for_document(document):
|
|
"""Create a pending assignment for every currently eligible user.
|
|
|
|
Idempotent: anyone who already has an assignment for this document is
|
|
left alone. Returns how many new assignments were created.
|
|
"""
|
|
existing = {a.user_id for a in document.assignments.all()}
|
|
created = 0
|
|
for user_id in eligible_user_ids(document.kind):
|
|
if user_id in existing:
|
|
continue
|
|
db.session.add(
|
|
DocumentAssignment(
|
|
document_id=document.id,
|
|
user_id=user_id,
|
|
status='pending',
|
|
)
|
|
)
|
|
created += 1
|
|
if created:
|
|
db.session.commit()
|
|
return created
|
|
|
|
|
|
def backfill_assignments_for_user(user):
|
|
"""Create pending assignments for *user* across published documents.
|
|
|
|
Called when a member joins (or is reactivated into) a team. Only
|
|
published documents are backfilled: an essentially invisible draft
|
|
creates no obligations. Returns how many new assignments were created.
|
|
"""
|
|
if not user.is_active_account or not user_has_any_team(user):
|
|
return 0
|
|
documents = Document.query.filter(Document.is_published.is_(True)).order_by(Document.id).all()
|
|
created = 0
|
|
for doc in documents:
|
|
if user.role not in DOCUMENT_KIND_ROLES.get(doc.kind, ()):
|
|
continue
|
|
exists = DocumentAssignment.query.filter_by(document_id=doc.id, user_id=user.id).first()
|
|
if exists:
|
|
continue
|
|
db.session.add(
|
|
DocumentAssignment(
|
|
document_id=doc.id,
|
|
user_id=user.id,
|
|
status='pending',
|
|
)
|
|
)
|
|
created += 1
|
|
if created:
|
|
db.session.commit()
|
|
return created
|