premiere iteration de la gestion des documents
CI - Security, Lint & Tests / validate (push) Failing after 1m20s
CI - Security, Lint & Tests / validate (push) Failing after 1m20s
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
"""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_team_ids, manager_org_team_ids
|
||||
|
||||
if isinstance(user, Player):
|
||||
return [tp.org_team_id for tp in user.team_placements]
|
||||
if isinstance(user, Coach):
|
||||
return coach_org_team_ids(user)
|
||||
if isinstance(user, Manager):
|
||||
return manager_org_team_ids(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
|
||||
Reference in New Issue
Block a user