premiere iteration de la gestion des documents
CI - Security, Lint & Tests / validate (push) Failing after 1m20s

This commit is contained in:
cedrick2711
2026-09-11 09:51:48 -04:00
parent bd9ed4bdc7
commit abf2f06642
14 changed files with 1326 additions and 5 deletions
+137
View File
@@ -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