Mon approche consiste à pré-générer dans une table la liste de tous les créneaux (table slots) possibles. C'est beaucoup plus efficace et facile ensuite à exploiter car chercher un créneau disponible est une simple requête SQL (jointure LEFT JOIN). Cette pré-génération doit être exécutée en cron (ou au moins en CLI), ce n'est pas grave si elle devait mettre une heure à s'exécuter, surtout que c'est une tâche qui est faite d'avance, l'important c'est que l'application ne soit pas lente pour vos clients à trouver un créneau disponible (sinon elle risque de tomber à la moindre charge - utilisateurs simultanés). Imaginez, sans ce prémâchage sur lequel se baser, comment trouver un rendez-vous disponible ? En tenant compte des jours + horaires d'ouverture ainsi que des jours fériés : ça demanderait beaucoup de calculs et possiblement beaucoup d'allers/retours PHP/base de données pour chaque recherche :/
Avantages de ces slots précalculés :
- trouver un créneau libre est très rapide (tant que la base de données est correctement indexée)
- dynamicité et souplesse : vous devez exceptionnellement fermer un jour, une heure ou peu importe, il est aisé de désallouer (supprimer) les slots concernés pour qu'ils ne soient définitivement plus disponibles. Inversement pour une ouverture exceptionnelle qui ne requerra qu'une création de ces slots
- portable : ça fonctionne même avec MySQL qui ne dispose malheureusement pas de fonction équivalente à
generate_series
de PostgreSQL
Note : le script pour générer ces slots peut être exécuté à intervalle régulier. Il n'est pas requis de l'exécuter pour un an voir plus. Il peut parfaitement l'être tous les mois pour une période de 3 mois si les rendez-vous n'ont pas à être pris plus tôt à l'avance.
Exemple d'exécution du script pour des slots commençant au 1^er mai 2021 (option --debut) pour une période de 3 mois (option --fin) (au-delà, il faudra le réexécuter), des créneaux de 30 minutes (option --duree) et des horaires d'ouverture tels que :
- Lundi de 14H à 18H
- Mardi de 9H30 à 12H
- Mardi de 14H à 18H
- Jeudi de 9H à 12H
- Jeudi de 14H à 18H
php slots_generation.php -d --debut=2021-05-01 --fin="3 months" --duree="30 minutes" --lundi=14-18 --mardi=9:30-12 --mardi=14-18 --jeudi=9-12 --jeudi=14-18
Disons qu'une personne travaille 40 heures par semaine, avec des slots de 5 minutes seulement et en considérant qu'il y a 53 semaines par an, cela représente 40 * (60 / 5) * 53 = 25440 slots et lignes par personne (ou ressource) par an, ce qui est loin d'être énorme pour un SGBD. S'il n'est pas nécessaire de conserver les données des rendez-vous passés, ils peuvent être supprimés sinon éventuellement être archivés (déplacés vers une table annexe) au fur et à mesure (par une tâche périodique/cron par exemple). Le tout, pour avoir de bonnes performances, c'est que les colonnes, à commencer par slots.debut, soient bien indexées.
Il faudrait générer des slots d'au moins le plus grand facteur commun de celles-ci (exemple : 20 minutes et 30 minutes = 10 minutes) sinon moins (5 minutes) et alors attribuer autant de slots consécutifs que nécessaires à la réalisation de la prestation désirée.
Il faudra générer les slots pour chacune d'elles en ajoutant une clé étrangère vers cette ressource dans la table slots (et ne pas oublier de l'intégrer aux contraintes uniques - UNIQUE KEY
). Pour des personnes, vous n'avez alors plus des horaires d'ouverture globaux mais individuel de travail.
On aurait pu se contenter d'une relation unidirectionnelle créneaux vers rendez-vous (en d'autres termes, une clé étrangère dans la table slots vers le rendez-vous le pourvoyant - sinon NULL si le slot est libre). Toutefois l'avantage d'avoir une relation (facultative) inverse, c'est-à-dire associer le rendez-vous à un créneau permet de retrouver les rendez-vous à réassigner, ceux que vous auriez explicitement mis de côté en assignant NULL comme slot ou suite à la suppression d'un créneau qui était utilisé (via l'attribut ON DELETE SET NULL
de la FK). Ainsi, par la requête :
SELECT *
FROM appointments
LEFT JOIN appointments_slots
ON appointments.id = appointments_slots.appointment_id
WHERE appointments_slots.slot_id IS NULL
;
Vous retrouvez tous les rendez-vous qui sont en attente ou à reporter.
- Requiert PHP >= 8.1.0
- Prévu pour MySQL (écrit et testé sur une version 8)
- Ce n'est qu'une base, il est impossible de faire quelque chose qui corresponde aux besoins de chacun, vous aurez sans doute des choses à ajouter et adapter (des tables, des clés étrangères, modifier les contraintes, prendre en charge une session PHP pour savoir à qui ça correspond, etc)
- C'est le fruit de ma propre réflexion, c'est une approche possible (avec PostgreSQL, il aurait sans doute possible de faire mieux à certains égards et possiblement autrement) et si vous voulez en discuter vous savez où et comment me trouver
Ci-dessous un rendu sur la semaine du 10 au 16 mai 2021 suite à la commande :
php slots_generation.php -d --debut=2021-05-01 --fin="3 months" --duree="30 minutes" --lundi=14-18 --mardi=9:30-12 --mardi=14-18 --jeudi=9-12 --jeudi=14-18 --vendredi=9-12 --vendredi=14-17
Soit, pour horaires :
- lundi : 14H00 - 18H00
- mardi :
- 09H30 - 12H00
- 14H00 - 18H00
- jeudi (qui n'apparaît pas car le 13/05/2021 était férié) :
- 09H00 - 12H00
- 14H00 - 18H00
- vendredi :
- 09H00 - 12H00
- 14H00 - 17H00
Avec :
- quelques RDV de tests en vert (2 d'un client "xxx" et 1 d'un client "test")
- en violet, le créneau actuel (j'ai artificiellement bloqué now à "2021-05-11 11:38:00")
- en blanc, tous les créneaux libres
Et le formulaire (sommaire) de réservation :