> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cel-eleague.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Forfaits club

# Forfaits club

Cette page documente les règles Convex de forfaits déclarés sur une compétition.

Sources principales :

* `convex/schema.ts`
* `convex/competition/club_forfeits.ts`
* `convex/competition/club_forfeit_impacts.ts`
* `convex/competition/club_forfeit_sanctions.ts`
* `convex/competition/bet_settlement.ts`
* `convex/lib/audit_logs.ts`
* `convex/social/announcements.ts`

## `club_forfeits` et statut

Les champs clés vérifiés : `leagueId`, `season`, `clubId`, `effectiveAt`, `declaredAt`, `declaredById`, `scope`, `mercatoStartAt`, `status`, `reason`, `adminNote`, `cancelledAt`, `cancelledById`, `cancelReason`.

Statuts :

* `ACTIVE`
* `CANCELLED`

Idempotence métier : la déclaration d’un forfait actif existant retourne l’enregistrement existant.

## Détermination de scope

Deux scopes existent :

* `FULL_COMPETITION`
* `POST_MERCATO_ONLY`

Quand une fenêtre de mercato est connue, le scope est dérivé : avant mercato = `FULL_COMPETITION`, à partir de mercato = `POST_MERCATO_ONLY`.

## Application sportive (`applyClubForfeit`)

Le moteur applique le forfait sur les matchs actifs où le club est impliqué, selon le scope :

* `FULL_COMPETITION` : tous les matchs en cours de saison,
* `POST_MERCATO_ONLY` : seulement à partir de `mercatoStartAt` si défini.

Scores imposés :

* forfait simple : club forfaité perd (`0`), adversaire marque `1`,
* double forfait : score `0-0`.

Écriture d’impact :

* scores/évolution match en `resultOrigin = CLUB_FORFEIT`,
* `forfeitClubId`, `forfeitCreatedResult`,
* snapshots de restauration : `previousHomeScore`, `previousAwayScore`, `previousResultOrigin`, `previousMatchStatus`,
* `isDisputed = false`, `validatedAt` posé,
* match repassé `validated`.

## Répercussions associées

* Invalidation des stats match (`match_player_stats`, `match_team_stats`) avec `invalidatedReason = CLUB_FORFEIT`.
* Annulation paris : `cancelPendingBetsForMatch` avec `reason = MATCH_FORFEITED` (uniquement PENDING).
* Recalcul classement via `recalculateLeagueStandingsCore`.
* Sanctions automatiques via `generateForfeitSanctions`.
* Annonce globale `sourceType = CLUB_FORFEIT`.

## Réversion (`revertClubForfeitImpacts`)

La réversion re-cible uniquement les impacts signés par le forfait (`resultOrigin = CLUB_FORFEIT`, `forfeitClubId`).

* si `forfeitCreatedResult === true` : suppression du résultat puis restauration `status` depuis snapshot `previousMatchStatus`,
* sinon : restauration `homeScore` / `awayScore` et flags de forfeit,
* réactivation des stats invalidées par ce forfeit,
* recalcul des standings,
* levée des sanctions liées.

Cas complexe :

* si l’adversaire est encore en forfait actif, la restauration de score peut être ignorée avec warning.

Les paris annulés lors du forfeit ne sont pas réouverts ; seuls les PENDING ont été annulés.

## Sanctions

`generateForfeitSanctions` applique un `COMPETITION_BAN` :

* destinataires GM + `MANAGER` / `CO_MANAGER` actifs,
* scope principal : `["SPORT_ELIGIBILITY"]`,
* `banScope` par défaut `PLAYER_AND_STAFF`,
* périodes : de `baseSeason` sur `seasonCount` saisons (défaut `3`, cap `10`),
* traçage source (`sourceType`, `sourceForfeitId`, `sourceClubId`).

`liftForfeitSanctions` met fin à la sanction en mode manuel (`END_MANUAL`, `ENDED_MANUAL`) avec raison par défaut `"Forfait annulé"`.

## Limites / précisions

* Les effets exacts hors Convex (accès terrain/API externe, emails ciblés, placement visuel final des annonces) ne sont pas tous détaillés dans ce périmètre. **\[non vérifié]**
* La logique métier sur le comportement d’un forfeit en mode replay/écran front n’est pas couverte ici. **\[non vérifié]**
