Skip to content

Documentation : Reservation Admin (adminReservation)

Voir aussi : Matching instantané — réservation système (1 jeune + 3 bénévoles, 15 min), affichage identique (rouge).

Vue d'ensemble

La reservation admin est un mécanisme de verrouillage temporaire qui permet à un administrateur de "réserver" un jeune ou un bénévole pendant qu'il travaille dessus (notamment lors du matching). Cela évite que plusieurs admins ne travaillent simultanément sur le même profil et ne créent des conflits.


Objectifs métier

  1. Éviter les conflits : Quand un admin ouvre la page de matching d'un jeune (ou bénévole), il réserve ce profil pour lui seul pendant 30 minutes.
  2. Blocage des autres admins : Pendant cette période, les autres admins ne peuvent pas :
  3. Voir la liste des candidats au matching pour ce profil
  4. Matcher ce profil avec quelqu'un
  5. Accéder à certaines actions sur la fiche
  6. Libération explicite : L'admin peut libérer sa réservation via le bouton "Arrêter le matching".
  7. Expiration automatique : La réservation expire automatiquement après 30 minutes si l'admin ne libère pas.

Modèle de données

Entité AdminReservation

Champ Type Description
id bigint Identifiant unique
reservationDate timestamp Date/heure de la réservation (utilisée pour calculer l'expiration)
deletedAt timestamp (soft delete) Date de suppression (null = réservation active)
benevoleId varchar ID du bénévole réservé (nullable)
jeuneId varchar ID du jeune réservé (nullable)
adminId varchar ID de l'admin qui a réservé

Note : Une entrée AdminReservation lie soit un bénévole soit un jeune à un admin. Les champs benevoleId et jeuneId sont mutuellement exclusifs selon le contexte.

Relations

  • Benevole ↔ AdminReservation : OneToMany (un bénévole peut avoir plusieurs réservations dans l'historique)
  • Jeune ↔ AdminReservation : OneToMany
  • User (admin) ↔ AdminReservation : OneToMany

Logique de durée : 30 minutes

La réservation est considérée active si :

reservationDate + 30 minutes > NOW()
  • Active : L'admin a l'exclusivité, les autres sont bloqués
  • Expirée : Tout le monde peut à nouveau réserver

Flux fonctionnels

1. Réservation lors du matching (bénévole → jeunes)

Contexte : Un admin ouvre la page de matching d'un bénévole pour lui trouver des jeunes.

  1. GET /rating/jeuneList/:benevoleId (RatingController)
  2. Vérification : le bénévole est-il déjà réservé par un autre admin ?
  3. Si oui et réservation active → retourne { success: false, message: 'reserved' }
  4. Sinon → appelle reserveBenevole(benevoleId, adminId)
  5. reserveBenevole crée ou met à jour une entrée AdminReservation (voir section "Logique de reserve")
  6. Retourne la liste des jeunes matchables (filtrés des jeunes déjà réservés par d'autres admins)

2. Réservation lors du matching (jeune → bénévoles)

Contexte : Un admin ouvre la page de matching d'un jeune pour lui trouver des bénévoles.

  1. GET /rating/benevoleList/:jeuneId (RatingController)
  2. Vérification : le jeune est-il déjà réservé par un autre admin ?
  3. Si oui et réservation active → retourne { success: false, message: 'reserved' }
  4. Sinon → appelle reserveJeune(jeuneId, admin.id)
  5. Retourne la liste des bénévoles matchables (filtrés des bénévoles déjà réservés)

3. Libération de la réservation ("Arrêter le matching")

  1. GET /rating/stopResa/benevole/:id ou /rating/stopResa/jeune/:id
  2. Appelle stopReserveBenevole ou stopReserveJeune
  3. Soft delete de la dernière réservation de cet admin pour ce profil

4. Réservation multiple (jeunes uniquement)

Contexte : Actions en masse sur plusieurs jeunes (ex. changement de statut).

  • POST /jeunes/multiple/resa avec { jeuneIds: [...] }
  • Pour chaque jeune non réservé par un autre admin, crée une réservation
  • Retourne { success: true, errors: [ids des jeunes déjà réservés] }

Logique détaillée de reserveBenevole / reserveJeune

1. Récupérer les réservations actives (deletedAt IS NULL) pour ce benevole/jeune + cet admin

2. Si aucune réservation existante :
   → Créer une nouvelle AdminReservation (benevoleId/jeuneId, adminId, reservationDate = NOW)

3. Si réservation(s) existante(s) pour CET admin :
   - Dernière réservation : dateReservation = reservationDate + 30 min
   - Si today < dateReservation (réservation encore valide) :
     → Mettre à jour reservationDate = NOW() (prolonge la fenêtre de 30 min)
   - Sinon (expirée) :
     → Créer une nouvelle AdminReservation

4. Si réservation existante pour UN AUTRE admin :
   → Créer une nouvelle AdminReservation (pour cet admin)
   (Note : la vérification "bloquant" est faite AVANT l'appel, au niveau du controller)

Points d'entrée frontend

Store Vuex

Store État Description
benevoleAdmin isBinomableB true si le bénévole peut être matché (non réservé par un autre ou réservé par moi)
benevoleAdmin isReservedToMeB true si le bénévole est réservé par l'admin connecté
jeuneAdmin isBinomable idem pour jeune
jeuneAdmin isReservedToMe idem pour jeune

Ces états sont calculés dans getBenevole / getJeune à partir de adminReservation :

  • isReservedToMe : dernière réservation par moi ET encore dans les 30 min
  • isBinomable : pas de réservation active par un autre admin (ou réservé par moi)

Composants

  • ButtonsAdmin.vue : Affiche "Arrêter le matching" si isReservedToMe, désactive les boutons si !isBinomable
  • Pages matching (/bo/jeunes/:id/matching, /bo/benevoles/:id/matching) : Gèrent reserved (message "réservé par un autre admin") et appellent stopResa pour libérer

Services API

  • ratingService.getJeuneList(benevoleId) → réserve le bénévole + retourne les jeunes
  • ratingService.getBenevoleList(jeuneId) → réserve le jeune + retourne les bénévoles
  • ratingService.stopResaBenevole(id) / ratingService.stopResaJeune(id) → libère la réservation

Filtrage dans les requêtes SQL

Chargement des réservations actives uniquement

Pour éviter de charger des lignes inutiles, seules les réservations actives (30 min) sont récupérées à chaque usage :

  • findWithRelationsForList (listes BO) : leftJoinAndSelect avec condition deletedAt IS NULL AND DATE_ADD(reservationDate, INTERVAL 30 MINUTE) > NOW()
  • findJeunesWithRelationForMatching / findBenevolesWithRelationForMatching : idem
  • findJeune / findBenevole avec relation adminReservation : chargement séparé via AdminReservationRepository.findActiveForJeune/ForBenevole

BenevoleRepository.findBenevolesWithRelationForMatching

Les bénévoles retournés pour le matching d'un jeune sont filtrés via un LEFT JOIN sur adminReservation avec les conditions :

adminReservation.deletedAt IS NULL
AND adminReservation.adminId != :adminId
AND DATE_ADD(adminReservation.reservationDate, INTERVAL 30 MINUTE) > NOW()

→ Seules les réservations bloquantes (par un autre admin, encore actives) sont jointes. Si adminReservation.id est non null, le bénévole est exclu (filtrage en mémoire dans le controller).

JeuneRepository.findMatchable

Les jeunes sont filtrés en mémoire après la requête : on exclut ceux qui ont une réservation active par un autre admin.


Endpoints API

Méthode Route Rôle Réservation
GET /rating/jeuneList/:benevoleId Liste des jeunes matchables pour un bénévole Réserve le bénévole
GET /rating/benevoleList/:jeuneId Liste des bénévoles matchables pour un jeune Réserve le jeune
GET /rating/stopResa/benevole/:id Libère la réservation d'un bénévole Soft delete
GET /rating/stopResa/jeune/:id Libère la réservation d'un jeune Soft delete
POST /jeunes/multiple/resa Réserve plusieurs jeunes en masse Crée des réservations

Checklist de conformité aux besoins

Besoin Implémenté ? Détail
Empêcher 2 admins de matcher le même profil en même temps Blocage via reserved + isBinomable
Durée de réservation limitée 30 minutes
Prolongation automatique en restant sur la page ⚠️ Non : la prolongation ne se fait qu'à chaque nouvel appel API (ex. changement de filtre). Pas de heartbeat périodique.
Libération manuelle Bouton "Arrêter le matching"
Réservation multiple (jeunes) Endpoint multiple/resa
Réservation multiple (bénévoles) Pas d'équivalent pour les bénévoles
Superadmin bypass ⚠️ Le superadmin peut bypasser adminAssocieId et certaines restrictions, mais la réservation s'applique aussi à lui (il peut réserver et être bloqué par une réservation d'un autre admin).
Affichage "réservé par un autre" reserved + message sur les pages matching
Désactivation des boutons quand réservé isBinomable → désactive Matcher, etc.

Points d'attention / limites

  1. Pas de heartbeat : Si l'admin reste 30 min sur la page sans interagir, sa réservation expire. Un autre admin pourra alors réserver. Pas de renouvellement automatique.

  2. Réservation multiple bénévoles : Seuls les jeunes ont un endpoint multiple/resa. Les bénévoles n'ont pas d'équivalent pour des actions en masse.

  3. Soft delete : Les anciennes réservations sont soft-deleted, pas supprimées. L'historique reste en base.

  4. Cohérence front/back : La logique des 30 min est dupliquée (front dans les stores, back dans les controllers/services). Une dérive est possible si l'un est modifié sans l'autre.

  5. Race conditions : Entre la vérification "pas réservé" et l'appel à reserveBenevole/reserveJeune, un autre admin pourrait théoriquement réserver entre-temps. En pratique, la fenêtre est très courte.


Fichiers clés

Fichier Rôle
back/src/binomes/entities/AdminReservation.entity.ts Entité
back/src/binomes/services/benevoles.service.ts reserveBenevole, stopReserveBenevole
back/src/binomes/services/jeunes.service.ts reserveJeune, stopReserveJeune
back/src/binomes/controllers/rating.controller.ts Endpoints rating + stopResa
back/src/binomes/controllers/jeune.controller.ts multiple/resa
back/src/binomes/repositories/benevole.repository.ts findBenevolesWithRelationForMatching (filtre adminReservation)
front/store/benevoleAdmin.js État isBinomableB, isReservedToMeB
front/store/jeuneAdmin.js État isBinomable, isReservedToMe
front/components/bo/widgets/ButtonsAdmin.vue Bouton "Arrêter le matching"
front/services/rating.js Appels API rating