La question de développer son propre agenda de rendez-vous ou d'en payer un se tranche presque toujours sur le prix, qui est justement le critère le moins pertinent. Ce qui décide, c'est ce que vous planifiez et l'endroit où vous voulez que ces données atterrissent. Vous verrez ici les deux cas que l'on confond en permanence, ce que vous gagnez exactement à garder les rendez-vous dans votre base et les deux problèmes difficiles que personne n'anticipe. Les exemples sont en Laravel, l'une de nos spécialités chez AndorraDev, mais le modèle de disponibilité et les deux problèmes difficiles sont les mêmes quel que soit le stack.
Calendly résout un problème qui n'est pas toujours le vôtre
Avant de décider, il vaut mieux séparer deux cas qui se ressemblent beaucoup et n'ont rien à voir.
Un : vous êtes commercial et vous devez coordonner votre agenda réel avec celui de personnes extérieures, avec des reprogrammations, des rappels et une synchronisation avec le calendrier où vous vivez déjà. Là, Calendly fait exactement ce dont vous avez besoin et le monter vous-même serait du temps jeté.
Deux : vous avez une page "demander une démo" et vous voulez que le visiteur choisisse un créneau sans quitter votre site. Ici, l'agenda de réservation est un formulaire de captation avec calendrier, et la donnée qui entre est un lead, pas un événement d'agenda. C'est le cas où cela vaut la peine, et pour une raison qui n'est pas le prix : le lead reste dans votre base de données, il arrive dans la même boîte que le reste de vos formulaires et il n'oblige pas le visiteur à sauter sur un domaine tiers juste au moment de convertir.
Cet article parle du second cas.
Un agenda de réservation, c'est la disponibilité moins les réservations
Cette phrase est littéralement le modèle tout entier, et c'est pour cela qu'il tient en si peu de code.
Vous déclarez les plages où vous recevez, la durée de chaque rendez-vous, la pause entre rendez-vous, le préavis minimum pour réserver et jusqu'à combien de jours à l'avance. Avec cela, vous générez les créneaux possibles d'une journée et vous en retirez ceux qui sont déjà réservés. Ce qui reste est ce qui s'affiche.
'demo' => [
'duration' => 30, // minutes par réunion
'buffer' => 0, // pause entre réunions
'days_ahead' => 21, // jusqu'où on peut réserver
'min_notice' => 120, // préavis minimum en minutes
// 1 = lundi ... 7 = dimanche
'availability' => [
1 => [['10:00', '14:00'], ['16:00', '20:30']],
5 => [['10:00', '14:00']],
],
],
Le déclarer dans la configuration plutôt que dans une interface d'administration est une décision, et pour ce cas c'est la bonne : votre disponibilité commerciale change deux fois par an, pas tous les jours. Construire un panneau pour l'éditer, c'est plus de code à maintenir que la fonctionnalité elle-même.
Les créneaux se calculent, ils ne se stockent pas
Si la disponibilité est une soustraction, le membre de gauche n'a pas besoin d'être stocké. Cela supprime d'un coup la table de créneaux, le processus qui la remplit et tous les problèmes de synchronisation que les deux entraînent.
Il n'y a pas de table de créneaux à remplir à l'avance ni de traitement nocturne pour générer le calendrier du mois prochain. Seules les réservations sont enregistrées. Les créneaux libres sont une fonction de la configuration et de cette table, évaluée à la volée.
Cela veut dire que changer votre disponibilité prend effet immédiatement, qu'il n'y a rien à resynchroniser et que l'état incohérent classique du type "la table de créneaux dit une chose et les réservations en disent une autre" n'existe pas.
Deux réservations simultanées, c'est le seul problème difficile
Calculer des créneaux, soustraire des réservations et dessiner un calendrier sont des opérations déterministes : elles donnent la même chose aujourd'hui que dans un an. Il n'y a qu'un seul point dans tout le système où le résultat dépend de la vitesse à laquelle les choses arrivent.
Deux visiteurs peuvent regarder le même écran et cliquer sur le même créneau à la même seconde. La solution qui fonctionne a deux couches, et les deux sont nécessaires.
La première est un index unique en base de données sur le couple (type de réunion, heure de début). C'est la garantie dure : même si toute la logique échoue, la base de données n'autorise pas deux réservations sur le même créneau.
La seconde est une revérification au moment de confirmer, juste avant d'enregistrer, pour que le second utilisateur reçoive un message compréhensible au lieu d'une erreur :
if (! $this->generator()->isFree($this->selectedSlot)) {
$this->addError('selectedSlot', __('Ese hueco acaba de ocuparse.'));
$this->selectedSlot = null;
return;
}
Sans l'index, la vérification seule laisse une fenêtre de compétition. Sans la vérification, l'index ne produit qu'une erreur disgracieuse. Avec les deux, le cas est couvert et, en plus, c'est aimable.
L'index unique sur (type de réunion, heure de début) est la garantie dure contre la double réservation, et il vaut mieux qu'elle le reste quand vous étendez. Si vous ajoutez plus tard les annulations, décidez alors si le statut entre dans l'index ou si annuler supprime la ligne. Prise au moment d'étendre, la décision est triviale ; prise avec des données dedans, beaucoup moins.
Le fuseau horaire, c'est là que ces systèmes cassent
Presque tous les agendas qui échouent le font pour la même raison, et ce n'est pas la concurrence. C'est qu'à un endroit quelqu'un a enregistré une heure sans dire d'où elle venait, et l'erreur voyage ensuite sans que personne ne la voie jusqu'au jour où un client se présente avec une heure d'écart.
Un agenda de réservation manie trois horloges à la fois : celle de l'organisateur, où les plages sont déclarées ; celle du visiteur, où il se trouvera au moment du rendez-vous ; et UTC, où il faut tout enregistrer pour pouvoir comparer.
La règle qui évite quatre-vingt-dix pour cent des problèmes est simple : enregistrez toujours en UTC et affichez toujours en précisant dans quel fuseau vous affichez. Un sélecteur qui affiche "10:00" sans dire d'où il parle est un piège : le visiteur d'un autre pays choisit en croyant qu'il s'agit de dix heures chez lui et découvre l'heure réelle dans le courriel de confirmation.
Cette petite mention "horaire d'Andorre (CET)" à côté du sélecteur coûte une ligne et évite des réunions manquées.
Où s'arrête un composant et où commence le produit
C'est ici que la décision se joue vraiment, alors autant l'avoir sous les yeux. Un agenda de réservation léger couvre tout le cas de la captation et laisse dehors, à dessein, quatre choses qui relèvent de la fonctionnalité produit.
La synchronisation avec votre calendrier externe. L'agenda connaît ses propres réservations, pas les réunions que vous avez dans Google Calendar. Pour la captation, cela se règle en déclarant des plages un peu plus conservatrices que votre agenda réel, et c'est la différence de fond avec un outil sur abonnement.
La reprogrammation autonome. Le courriel de confirmation invite à répondre, ce qui suffit pour un volume de démos commerciales et ouvre en plus la conversation. Si votre métier ce sont des rendez-vous de service avec du volume, là oui, vous voudrez un lien et un flux dédiés.
Les rappels et l'invitation de calendrier. Une tâche planifiée et un fichier .ics. Ce n'est pas difficile et cela s'ajoute quand les absences commencent à faire mal, ce qui arrive en général bien après le lancement.
L'envoi en file d'attente. Les courriels partent sur le moment, ce qui est le bon choix avec un volume faible et fait une pièce de moins à maintenir. Quand le volume l'exigera, mettre cet appel en file tient en une ligne.
Quand chaque option vaut la peine
Avec tout ce qui précède sur la table, la décision devient assez nette.
Le monter vous-même vaut la peine si l'agenda est une page de captation, si votre disponibilité est stable, si vous tenez à ce que le visiteur ne quitte pas votre domaine et si la donnée doit rester dans votre base parce qu'elle va alimenter votre CRM. Cela représente environ sept cents lignes et une table.
Payer l'abonnement vaut la peine s'il vous faut une vraie synchronisation avec le calendrier où vous vivez, une reprogrammation autonome, des rappels automatiques, un encaissement au moment de réserver ou une répartition entre plusieurs personnes. Tout cela relève de la fonctionnalité produit, pas du composant, et le reconstruire est un projet.
Et il y a une troisième réponse, celle que nous donnons le plus souvent : commencer par le plus simple et ajouter. Ajouter des rappels quand les absences font mal, et la synchronisation quand vous aurez vraiment des conflits d'agenda, coûte bien moins cher que de vous défaire d'une dépendance externe à laquelle vous avez déjà branché trois intégrations.
Et si vous ne travaillez pas avec Laravel
Les décisions se transposent telles quelles :
- N'enregistrez que les réservations et calculez les créneaux. Une table de créneaux précalculés, c'est de l'état qui se désynchronise.
- Mettez l'index unique en base de données, pas seulement la vérification dans le code.
- Tout en UTC, et dites toujours dans quel fuseau vous affichez.
- Repartez de zéro et calculez de façon paresseuse, au moment de la requête, plutôt qu'avec des tâches planifiées.
- Et décidez tôt s'il y aura des annulations, parce que cela conditionne le schéma.
Où se trouve le code
Le paquet est sur Packagist, le code sur GitHub et la documentation du paquet recense toutes les options. Il est sous licence MIT, une seule table et sans dépendances tierces.
Il est en production sur la page de démo de Crowd Legal, où chaque réservation arrive dans la même boîte que le reste des formulaires de contact, parce qu'une démo planifiée est un lead de plus.
Si votre activité vit des rendez-vous et que cela devient trop juste, c'est autre chose et c'est exactement ce que nous faisons en plateformes de réservations. Si vous nous décrivez le volume et le flux, nous vous dirons si un composant vous suffit ou s'il vous faut un produit.