Clubs et effectifs
Cette page décrit les règles vérifiées côté backend (Convex) concernant les clubs et la gestion d’effectif.
Sources principales :
convex/social/_shared.ts
convex/social/clubs.ts
convex/social/memberships.ts
convex/competition/match_lineups.ts
convex/social/announcements.ts
Modèle club (données de base)
La table clubs contient notamment :
gmUserId, name, isPremium, eaClubId, eaPlatform, logoUrl, premiumLogoUrl, bannerUrl, primaryColor, secondaryColor, description, abbreviation, isActive, ownerId, country, searchText, createdAt, updatedAt.
Le module normalise les couleurs et nettoie les URLs optionnelles : une chaîne vide est transformée en valeur absente.
Rôles d’effectif
Les rôles valides côté membership sont :
MANAGER
CO_MANAGER
COACH
MEMBER
Un membership actif est défini par status = IN_CLUB et leftAt absent (isMembershipActive).
La création/mise à jour d’un membership passe par des helpers qui :
- empêchent qu’un même club ait déjà un
MANAGER, CO_MANAGER ou COACH actif (assertSingletonRoleAvailable),
- vérifient les autorisations d’acteur pour les actions de management.
Matrice d’autorisation (club / effectif)
assertActorCanManageClub et canActorCanManageClub encapsulent les règles :
- GM du club : toujours autorisé.
- Rôles de gestion (
MANAGER, CO_MANAGER) : autorisés pour les actions management par défaut.
COACH : autorisé uniquement quand le call backend passe includeCoach: true.
ADMIN : autorisé par défaut.
- Anonyme : refusé, sauf si l’appel explicite
allowAnonymous: true (rare, dépend du flux).
Matrice (résumé d’usage) :
- Gestion club classique (
clubs / recrutement / annonces) : MANAGER, CO_MANAGER, GM, ADMIN.
- Actions demandant explicitement recrutement élargi :
COACH inclus via includeCoach: true.
- Consultations non sensibles selon flux : peuvent être plus permissives (selon les fonctions appelantes), non couvertes ici.
Règles de cap effectif (roster)
Le calcul de cap effectif est centralisé dans _shared.ts :
resolveLeagueRosterLimit(league, club) :
- cap base =
league.maxActiveRoster,
- +10 si
club.isPremium === true.
assertClubRosterCapacityAllowsJoin vérifie que, avant invitation ou acceptation, le nombre de membres actifs existants (hors joueur en cours d’acceptation, via exclusion sur playerProfileId) reste en dessous du cap.
Quand la limite est atteinte :
- code :
CLUB_ROSTER_LIMIT_REACHED
- message avec
(activeRosterCount/rosterLimit)
- surcharge premium exposée dans
premiumBonus (10 pour club premium, 0 sinon).
Consommation frontend vérifiée
useClubs transporte rosterLimit depuis les queries club.
TeamCard affiche le ratio effectif/cap et le nombre de places disponibles.
- Le mercato recalcule le cap depuis
mainLeague.maxActiveRoster avec le bonus
premium de 10, puis l’utilise dans la jauge d’effectif et pour désactiver les
invitations quand le roster est plein.
- Le helper canonique
resolveClubDisplayLogo donne priorité à
premiumLogoUrl pour un club premium, puis retombe sur logoUrl.
- Les éditeurs club et admin écrivent
logoUrl; l’onglet branding premium écrit
premiumLogoUrl. Les URLs remplacées sont nettoyées côté backend lorsqu’elles
ne sont plus utilisées.
Les composants historiques qui reçoivent déjà un champ logo continuent à
l’afficher directement ; la règle métier de priorité premium reste portée par
resolveClubDisplayLogo pour les surfaces qui disposent des deux URLs.
Effets connexes (effet de recrutement)
Dans le flux de transfert premium/open-to-offers :
- un joueur actif dans un autre club peut encore être traité via invitation si le joueur est premium +
openToOffers,
- l’arrivée déclenche la fermeture de ses memberships actifs précédents et la création d’un membership
IN_CLUB dans le club cible après acceptation.
Ces règles sont détaillées côté transferts dans docs/transferts-mercato-premium.md.
Invariants d’ops
- Un membership actif doit rester cohérent (
status=IN_CLUB + leftAt absent).
- Les autorisations sensibles passent par les helpers
_shared.
- Les modifications de membership à fort impact déclenchent audits, événements et/ou annonces selon le flux.
Last modified on July 16, 2026