Changes for docs/evenements.md: 306 added lines, 0 removed lines.
Original line number
Diff line number
Diff line
# Système d’événements planifiés
Cette documentation décrit le fonctionnement implémenté dans cette application. Le socle générique est fourni par `vendor/unicaen/evenement`; les traitements liés aux sessions d’atelier sont définis dans `module/Atelier`.
## Rôle général
Un événement représente une opération différée. Il contient un type, un état de traitement, une date de planification, des paramètres JSON et un journal. Le traitement générique identifie le service associé au type, puis lui délègue l’exécution.
L’entité générique est `UnicaenEvenement\Entity\Db\Evenement`, dans [vendor/unicaen/evenement/src/UnicaenEvenement/Entity/Db/Evenement.php](../vendor/unicaen/evenement/src/UnicaenEvenement/Entity/Db/Evenement.php). Son mapping est défini dans [vendor/unicaen/evenement/src/UnicaenEvenement/Entity/Db/Mapping/UnicaenEvenement.Entity.Db.Evenement.dcm.xml](../vendor/unicaen/evenement/src/UnicaenEvenement/Entity/Db/Mapping/UnicaenEvenement.Entity.Db.Evenement.dcm.xml) et utilise la table `unicaen_evenement_instance`.
Les principaux composants sont :
| Composant | Rôle |
| --- | --- |
| `Evenement` | Données persistées de l’événement. |
| `Etat` | Codes d’état d’un événement. |
| `Type` | Identifie le traitement à exécuter. |
| `EvenementService` | Création, recherche, mise à jour et suppression des événements. |
| `EvenementGestionService` | Vérification de l’échéance, changement d’état et délégation au service métier. |
| `TraiterEvenementsCommand` | Commande console exécutée par le cron. |
Le mapping des services est configuré dans [config/autoload/unicaen-evenement.global.php](../config/autoload/unicaen-evenement.global.php).
## États et cycle de vie
Les codes sont définis dans [vendor/unicaen/evenement/src/UnicaenEvenement/Entity/Db/Etat.php](../vendor/unicaen/evenement/src/UnicaenEvenement/Entity/Db/Etat.php) :
-`EN_ATTENTE` (`en_attente`) : événement créé et en attente de traitement ;
-`EN_COURS` (`en_cours`) : traitement commencé ;
-`SUCCES` (`succes`) : service métier terminé avec succès ;
-`ECHEC` (`echec`) : traitement impossible ou état métier incompatible ;
-`ANNULE` (`annule`) : événement annulé, notamment par la logique de péremption lorsqu’elle est activée.
Le cycle nominal est :
```text
création
→ EN_ATTENTE
→ EN_COURS
→ SUCCES
```
Une exception ou un retour métier d’échec conduit à `ECHEC`. Un événement récurrent peut donner lieu à la création d’un nouvel événement `EN_ATTENTE` après son traitement ; cette logique est dans `EvenementGestionService::traiterEvent()`.
## Création et planification
`EvenementService::createEvent()` construit un événement avec :
- son nom ;
- sa description ;
- son état initial ;
- son type ;
- ses paramètres ;
- sa date de création ;
- sa date de planification.
La méthode se trouve dans [vendor/unicaen/evenement/src/UnicaenEvenement/Service/Evenement/EvenementService.php](../vendor/unicaen/evenement/src/UnicaenEvenement/Service/Evenement/EvenementService.php), autour de la méthode `createEvent()`.
Si aucune date n’est fournie, la date de planification est initialisée à la date de création. Les services spécialisés peuvent calculer une date future avant de créer l’événement.
Pour une convocation de session, `ConvoquerSessionEvenement::creer()` stocke l’identifiant de la session dans les paramètres :
```json
{
"session":137
}
```
La méthode est dans [module/Atelier/src/Event/ConvoquerSession/ConvoquerSessionEvenement.php](../module/Atelier/src/Event/ConvoquerSession/ConvoquerSessionEvenement.php). Elle crée ensuite l’événement en état `EN_ATTENTE` et l’ajoute via le service d’événement.
La date d’une convocation automatique est calculée à partir de la première séance et du paramètre d’auto-convocation dans `ConvoquerSessionEvenement::updateEvent()`. Cette méthode supprime d’abord les convocations existantes liées à la session, puis crée la nouvelle planification.
## Commande et cron
La commande `evenement:traiter-evenements` est déclarée dans [vendor/unicaen/evenement/config/merged/command.config.php](../vendor/unicaen/evenement/config/merged/command.config.php) et implémentée par `UnicaenEvenement\Command\TraiterEvenementsCommand`, dans [vendor/unicaen/evenement/src/UnicaenEvenement/Command/TraiterEvenementsCommand.php](../vendor/unicaen/evenement/src/UnicaenEvenement/Command/TraiterEvenementsCommand.php).
La commande est donc lancée toutes les heures, de 08:00 à 18:00, du lundi au vendredi.
`TraiterEvenementsCommand::execute()` :
1. vérifie qu’un autre journal n’est pas déjà en cours ;
2. crée un journal d’exécution à l’état `EN_COURS` ;
3. récupère les événements arrivés à échéance ;
4. les traite un par un ;
5. enregistre le rapport et termine le journal en état `SUCCES`.
La sélection est effectuée par `EvenementService::getEvenementsByEtat(Etat::EN_ATTENTE)`. La requête filtre sur :
```text
etat.code = en_attente
datePlanification <= maintenant
```
La sélection de la date est dans `getEvenementsByEtat()` dans [vendor/unicaen/evenement/src/UnicaenEvenement/Service/Evenement/EvenementService.php](../vendor/unicaen/evenement/src/UnicaenEvenement/Service/Evenement/EvenementService.php). La commande impose également une durée maximale d’exécution configurée par `max_time_execution` ou sa valeur par défaut.
## Délégation selon le type
`EvenementGestionService` reçoit une table de correspondance entre les codes de type et les services. Elle est construite par `EvenementGestionServiceFactory` à partir de la configuration `unicaen-evenement.service`.
Dans cette application, les types de session sont déclarés dans [module/Atelier/src/Provider/Evenement/SessionEvenements.php](../module/Atelier/src/Provider/Evenement/SessionEvenements.php) :
-`rappel-evenement` ;
-`convoquer-evenement` ;
-`retour-evenement` ;
-`cloturer-evenement`.
Le mapping applicatif est dans [config/autoload/unicaen-evenement.global.php](../config/autoload/unicaen-evenement.global.php). Le package fournit aussi le type générique `collection`, traité par `EvenementCollectionService`.
## `EvenementGestionService::traiterEvent()`
La méthode se trouve dans [vendor/unicaen/evenement/src/UnicaenEvenement/Service/EvenementGestion/EvenementGestionService.php](../vendor/unicaen/evenement/src/UnicaenEvenement/Service/EvenementGestion/EvenementGestionService.php).
Son fonctionnement est le suivant :
1. récupérer le service correspondant au code de type ;
2. vérifier que l’événement est `EN_ATTENTE` et que `datePlanification` est échue ;
3. passer l’événement à `EN_COURS` et le flusher ;
4. appliquer éventuellement le délai de péremption ;
5. appeler `service->traiter($evenement)` ;
6. interpréter le code retourné par le service ;
7. affecter l’état final et `dateTraitement` ;
8. flusher l’événement ;
9. créer éventuellement la prochaine occurrence si le type est récurrent.
Les services spécialisés retournent généralement `Etat::SUCCES` ou `Etat::ECHEC`. Certains enregistrent eux-mêmes le message dans `Evenement::log` avant de retourner l’échec.
## Traitements applicatifs des sessions
### Convocation
`ConvoquerSessionEvenement::traiter()` décode `Evenement::parametres`, lit le paramètre `session`, puis appelle `SessionService::getSession()`.
Le traitement n’est exécuté que si la session est non historisée et que son état actif est `INSCRIPTION_FERMEE`. Il appelle ensuite :
```text
ConvoquerSessionEvenement::traiter()
→ SessionService::envoyerConvocation()
```
`envoyerConvocation()` :
1. appelle `EtatInstanceService::setEtatActif()` avec l’état `CONVOCATION` ;
2. flushe la session ;
3. parcourt les inscriptions principales ;
4. appelle `NotificationService::triggerConvocation()` pour chaque inscription non historisée.
Le type `collection` permet de regrouper plusieurs événements enfants. `EvenementCollectionService::traiter()` calcule l’état de la collection à partir des états de ses enfants :
-`SUCCES` si tous les enfants sont terminés sans échec ;
-`ECHEC` si au moins un enfant est en échec ;
-`EN_COURS` si aucun échec n’existe mais qu’un enfant est en cours ;
-`EN_ATTENTE` dans les autres cas.
Ce traitement est générique et n’est pas spécifique aux sessions.
## Paramètres et objet métier
Les paramètres sont stockés sous forme JSON dans `Evenement::parametres`. Ils ne constituent pas une relation Doctrine générique vers l’objet métier.
Chaque service spécialisé connaît la structure de ses paramètres. Pour les événements de session, le contrat applicatif est :
```json
{
"session":137
}
```
Le service décode ce JSON puis appelle `SessionService::getSession($id)`. La requête de session charge la session, son atelier, ses états, ses inscriptions et les données nécessaires au traitement.
## Succès, échec et journalisation
Lorsqu’un service retourne un code d’état, `EvenementGestionService` l’applique à l’événement et renseigne `dateTraitement`.
Lorsqu’une exception est levée :
- le handler spécialisé peut la capturer ;
- il enregistre généralement le message dans `Evenement::log` ;
- il retourne `Etat::ECHEC` ;
-`EvenementGestionService` met l’événement à l’état `ECHEC` et le flushe.
La commande consigne également l’exception dans sa sortie console et poursuit le traitement des autres événements lorsque cela est possible.
Le journal global de la commande est une entité distincte, gérée par `JournalService`. Il indique le début, la fin, le nombre d’opérations et les erreurs générales du traitement.
## Changements d’état des sessions
Les sessions implémentent `HasEtatsInterface` et utilisent `HasEtatsTrait`. Leur collection d’états est mappée dans [module/Atelier/src/Entity/Db/Mapping/Atelier.Entity.Db.Session.dcm.xml](../module/Atelier/src/Entity/Db/Mapping/Atelier.Entity.Db.Session.dcm.xml).
`EtatInstanceService::setEtatActif()` se trouve dans [vendor/unicaen/etat/src/UnicaenEtat/Service/EtatInstance/EtatInstanceService.php](../vendor/unicaen/etat/src/UnicaenEtat/Service/EtatInstance/EtatInstanceService.php). Lors d’un changement :
1. l’état actif est recherché ;
2. l’ancien état est historisé ;
3. l’ancien état est flushé ;
4. un nouvel `EtatInstance` est créé ;
5. il est ajouté à la collection de l’entité ;
6. l’entité est persistée et flushée.
Les méthodes de `SessionService` qui utilisent cette transition comprennent notamment `envoyerConvocation()`, `demanderRetour()`, `cloturer()`, `ouvrirInscription()`, `fermerInscription()`, `annuler()` et les méthodes de création ou de reprise de session.
## Exemple : convocation automatique
Une planification de convocation suit cette chaîne :
```text
ConvoquerSessionEvenement::creer()
↓
EvenementService::createEvent()
↓
EvenementService::ajouter()
↓
événement EN_ATTENTE avec datePlanification
↓
cron horaire
↓
TraiterEvenementsCommand::execute()
↓
EvenementService::getEvenementsByEtat(EN_ATTENTE)
↓
EvenementGestionService::traiterEvent()
↓
ConvoquerSessionEvenement::traiter()
↓
SessionService::getSession(sessionId)
↓
SessionService::envoyerConvocation()
↓
EtatInstanceService::setEtatActif(CONVOCATION)
↓
SessionService::update()
↓
NotificationService::triggerConvocation()
↓
événement SUCCES ou ECHEC
```
La date de traitement réelle est affectée par `EvenementGestionService` après l’appel au service spécialisé. Le traitement est considéré comme arrivé à échéance lorsque `datePlanification` est antérieure ou égale à l’heure du traitement.
## Points d’attention techniques
### Contexte CLI et utilisateur d’historisation
Le cron ne dispose normalement pas d’utilisateur Web authentifié. Le listener Doctrine `UnicaenUtilisateur\ORM\Event\Listeners\HistoriqueListener` renseigne alors les champs d’historisation avec `default-user` lorsqu’aucune identité n’est disponible.
La configuration est dans [config/autoload/unicaen-utilisateur.global.php](../config/autoload/unicaen-utilisateur.global.php). La version `6.0.9` teste cette valeur comme une valeur booléenne :
```php
if($this->defaultUser){
$user=newUser();
$user->setId($this->defaultUser);
}
```
Dans cette version, `default-user = 0` n’active donc pas le fallback, même si l’utilisateur `#0` existe. Les versions ultérieures ont corrigé ce cas en testant explicitement la présence de la valeur et en chargeant l’utilisateur avec Doctrine.
### Plusieurs `flush()` lors d’un changement d’état
`setEtatActif()` effectue plusieurs opérations persistées séparément. En l’absence de transaction englobante, une exception entre le `flush()` de l’ancien état et l’enregistrement du nouvel état peut laisser l’ancien état historisé sans nouvel état actif.
Cette propriété concerne les traitements de convocation, de retour et de clôture qui appellent `setEtatActif()` indirectement. Le code analysé ne fournit pas de transaction interne autour de toute la transition.
### Exceptions et état métier
Un événement peut être marqué `ECHEC` après qu’une partie du traitement métier a déjà été persistée. Le statut de l’événement décrit donc le résultat du traitement de l’événement, mais ne garantit pas que toutes les opérations précédentes ont été annulées.
### Limites de cette documentation
Les effets de services externes, de la base de données réelle et de configurations non présentes dans le dépôt ne peuvent pas être déduits davantage du code. Les descriptions ci-dessus correspondent aux classes, mappings et configurations présents dans ce projet.