I. Introduction

Sur le forum Access de nombreuses questions sont relatives à la planification.

Voici donc un cas relativement simple de planification de rendez-vous des patients dans un cabinet médical, facile à adapter à vos besoins :

Nous considérons des horaires journaliers qui s'étalent de 08:00 à 18:00 avec des créneaux horaires de 15 minutes.

Dans ce planning hebdomadaire, nous aurons donc en lignes les horaires, et en colonnes les jours de la semaine : nous décrirons dans cet article une technique de création dynamique du planning qui pourra être réutilisée dans d'autres applications du même type.

En plus du numéro du patient, il nous faudra aussi sauvegarder dans une table la date du jour et l'horaire de début et de fin du rendez-vous. Ainsi le mieux sera de considérer l'horaire de début et l'horaire de fin du RDV comme des champs au format date générale (Date/Heure).

En outre, pour simplifier la saisie des rendez-vous, il devra être possible de choisir l'horaire et donc d'ajouter un RDV par un simple clic (ou double-clic) sur le label (étiquette) situé à l'intersection d'une ligne (horaire) et d'une colonne (jour).

Enfin, pour faciliter son développement et assurer sa portabilité, à l'exception du contrôle DTPicker (normalement disponible dans Autres contrôles de la boîte à outils), l'application n'utilise pas de composants externes à Access (ActiveX ou autre).

II. Rendu final de l'application

Image non disponible
Voici le rendu du planning hebdomadaire qui présente les rendez-vous des patients sur un ou plusieurs créneaux horaires et sur les 7 jours de la semaine.
Image non disponible
Un double-clic sur un créneau horaire ouvre le formulaire des rendez-vous pour la saisie.

III. Les tables nécessaires

Nous allons tout d'abord définir les tables et les champs qui contiendront les données :

L'application réduite au maximum se contentera de 2 tables, la table T_RendezVous contenant les rendez-vous des patients et la table T_Patient contenant les informations relatives aux patients.

III-A. La table "T_RendezVous"

Elle sert à enregistrer les rendez-vous des patients.

Nom du champ Type de données Description
NR Numéro auto Numéro d'ordre du rendez-vous.
NP Entier long Numéro d'ordre du patient ayant le RDV.
HoraireDebut Date/Heure Jour et horaire de début du RDV.
HoraireFin Date/Heure Jour et horaire de fin du RDV.
Memo Mémo Commentaire du médecin sur le patient.

III-B. La table "T_Patient"

Elle contient les informations relatives aux patients.

Nom du champ Type de données Description
NP Numéro Auto Numéro d'ordre du patient.
Nom Texte Nom du patient.
Prénom Texte Prénom du patient.
DNaiss Date Date de naissance.
Rue Mémo Rue de la ville où réside le patient.
CP Texte Code postal de la ville où réside le patient.
Ville Texte Ville où réside le patient.
Tel Texte Téléphone du patient.
Email Texte Email.

Dans notre cas, seuls les 2 premiers champs de la table sont indispensables.

IV. Les formulaires

La deuxième étape consiste à créer les formulaires.

IV-A. Le sous-formulaire "SF_Planning"

Il contient le tableau de labels (40x7), plus les en-têtes de lignes (les horaires journaliers) et les en-têtes de colonnes (les jours de la semaine).

IV-A-1. Les labels "creneau.._."

Les labels sont créés manuellement dans le formulaire en mode création, de la dernière ligne (creneau40_.) à la première ligne (creneau1_.). Ceci permet aux premiers labels de se superposer aux suivants quand on les redimensionne.

Image non disponible
Apercu du formulaire SF_Planning en mode création

La création des 40x7 labels, plus les en-têtes, peut être fastidieuse, c'est pourquoi j'ai conçu un outil permettant d'automatiser le tout et de créer dynamiquement ces labels.

IV-A-1-a. Description de la technique de création dynamique des labels

Dans le programme CreerPlanningCreerPlanning, après avoir ouvert le formulaire F_CreerPlanning, il vous suffit d'indiquer le nom du formulaire contenant le planning, le nombre de colonnes et de lignes du planning, les noms génériques des labels et leurs dimensions ainsi que le nom de la fonction qui s'exécute sur double-clic des labels (ici il s'agit de la fonction OuvrirFormRendezVous décrite plus loin) et de valider le tout :

La procédure CreerPlanning supprime alors tous les contrôles du formulaire en mode création et crée dynamiquement les labels du planning dans l'ordre inverse, du dernier au premier en affectant à l'évènement double-clic de chaque label le nom de la fonction passé en argument.

Le détail de la procédure de création du planning se trouve dans le module du programmeCreerPlanning.

IV-A-1-b. La fonction "OuvrirFormRendezVous" sur l'évènement double-clic

Elle est affectée dynamiquement à l'évènement double-clic des labels du planning et son code se trouve dans le module de l'application :

 
Sélectionnez

Public Function OuvrirFormRendezVous(i As Integer, j As Integer)
' Ouvre le formulaire F_RendezVous sur double-clic d'un label du planning.
' Prend en arguments les indices de ligne et de colonne. 

Dim DateC As Date ' Jour et Horaire choisis.
Dim db As DAO.Database
Dim LeSql As String
Dim rsRdV As DAO.Recordset ' Utilise une référence à DAO. 

DateC = IndicesToHoraire(i, j) ' renvoie la date et l'heure correspondant aux indices

Set db = CurrentDb     

LeSql = "SELECT T_RendezVous.* " & _
        "FROM T_RendezVous " & _
        "WHERE T_RendezVous.HoraireDebut<=" & FormatDateUS(DateC) & _ 
		 " And T_RendezVous.HoraireFin>" & FormatDateUS(DateC)
 
' Sélectionne dans un recordset le rendez-vous correspondant au créneau choisi par 
' double-clic.
Set rsRdV = db.OpenRecordset(LeSql, dbOpenForwardOnly)
       
 ' S'il y a un RDV, alors copier dans la variable DateC l'horaire de début du RDV.             
      If Not (rsRdV.EOF) Then DateC = rsRdV!HoraireDebut
   
 ' Ouvre le formulaire F_RendezVous.

DoCmd.OpenForm "F_RendezVous", , , "HoraireDebut=" & FormatDateUS(DateC)

Forms!F_RendezVous!DateRdV = DateC
Forms!F_RendezVous!HoraireD = Format(DateC, "hh:nn")
   
     If Not (rsRdV.EOF) Then 
          Forms!F_RendezVous!HoraireF = Format(rsRdV!HoraireFin, "hh:nn")
     Else
          Forms!F_RendezVous!HoraireF = Format(DateAdd("n", 30, DateC), "hh:nn")
     End If
         
rsRdV.Close
set rsRdV = Nothing 

Set db = Nothing
   
End Function

Et après, sur l'évènement double-clic d'un label, par exemple le premier créneau (creneau1_1), on appelle la fonction OuvrirFormRendezVous(1, 1) :

creneau1_1.OnDblClick = "=OuvrirFormRendezVous(1, 1)"

IV-B. Le formulaire principal "F_Planning"

Contient le sous-formulaire SF_Planning (le planning hebdomadaire), les boutons de commande pour avancer ou reculer d'une semaine et le contrôle DateTimePicker (DTPicker) pour choisir le 1er jour de la semaine.

IV-B-1. Le contrôle calendrier "DateD"

Il permet à l'utilisateur de choisir le jour de la semaine, et ainsi d'afficher le planning de la semaine correspondante. Par défaut on utilisera un DateTimePicker pour ce contrôle.

IV-B-1-a. Installation du contrôle DateTimePicker

Ce contrôle est utilisé pour permettre à l'utilisateur de sélectionner une date, et pour afficher cette valeur au format spécifié. Les utilisateurs peuvent ainsi choisir une date dans un calendrier.

Pour l'inscrire dans la base de registres, sous Access 2000 à 2003, choisir dans le menu Outils, l'item Contrôles ActiveX. Le contrôle doit normalement apparaître dans la liste des ActiveX :

Image non disponible
Apercu de la liste des contrôles ActiveX

Si le contrôle n'est pas présent dans la liste, appuyer sur le bouton Inscrire dans la base de registres et chercher le fichier c:\Windows\system32\MSCOMCT2.OCX.

Une fois le contrôle enregistré dans la base de registres, il ne vous reste plus qu'à le sélectionner dans Autres contrôles :

Image non disponible
Apercu de la liste des autres contrôles ActiveX

IV-B-1-b. La procédure évènementielle sur Changement

Cette procédure s'exécute quand on choisit un jour (donc une semaine) dans le calendrier : Elle permet d'afficher le planning de la semaine choisie.

 
Sélectionnez

Private Sub DateD_Change()
' Sur l'évènement "changement" du contrôle DTPicker.

DateDebut = DebutSemaine(DateD.Value) ' Renvoie le premier jour de la semaine
MajPlanning ' Met à jour le planning.

End Sub

IV-B-1-c. Les contrôles à la place du DateTimePicker

Pour ceux qui rencontrent des problèmes avec le DTPicker, je leur conseille de le remplacer par le contrôle calendrier de Thierry Gasperment.

Pour les utilisateurs d'Access 2007, vous pouvez aussi utiliser à la place du contrôle DTPicker une zone de texte et ajouter la prise en charge du calendrier à ce contrôle.

IV-B-2. Les boutons de déplacement "CmdPrecedent" et "CmdSuivant"

Ces boutons de commande situés en haut du formulaire permettent d'avancer ou de reculer d'une semaine sur le planning.

IV-B-2-a. La procédure évènementielle sur clic du bouton "CmdPrecedent"

Ce code permet de reculer d'une semaine et de mettre à jour le planning.

 
Sélectionnez

Private Sub CmdPrecedent_Click()
' Sur l'évènement "clic" du bouton de commande CmdPrecedent.

DateDebut = DateDebut - 7 ' Recule de 7 jours dans le temps.
MajPlanning ' Met à jour le planning.
End Sub

IV-B-2-b. La procédure évènementielle sur clic du bouton "CmdSuivant"

Ce code permet d'avancer d'une semaine et de mettre à jour le planning.

 
Sélectionnez

Private Sub CmdSuivant_Click()
' Sur l'évènement "clic" du bouton de commande CmdSuivant.

DateDebut = DateDebut + 7 ' Avance de 7 jours dans le temps.
MajPlanning ' Met à jour le planning.
End Sub

IV-B-3. La procédure évènementielle sur chargement du formulaire

Ce code permet de mettre à jour le planning de la semaine en cours au chargement du formulaire.

 
Sélectionnez

Private Sub Form_Load()
' Sur chargement du formulaire.

DateDebut = DebutSemaine(Date) ' Renvoie le premier jour de la semaine en cours.
MajPlanning ' Met à jour le planning.
DoCmd.Maximize
End Sub

IV-C. Le formulaire "F_RendezVous"

Il permet d'ajouter, de modifier ou de supprimer un rendez-vous. Ce formulaire s'ouvre sur l'évènement double-clic des labels du planning (SF_Planning).

Image non disponible
Aperçu du formulaire

IV-C-1. La liste déroulante "NP"

Cette liste permet de choisir un patient pour le rendez-vous. Elle est liée au champ NP de la table source T_RendezVous, relié lui-même au champ NP de la table T_Patient. Elle affiche la liste des noms de patients et leur numéro dans l'ordre croissant des noms. Pour ce faire, sa propriété Contenu contient le code SQL :

 
Sélectionnez

SELECT Nom, NP 
FROM T_Patient 
ORDER BY Nom;

Cette liste comporte donc 2 colonnes et sa colonne liée est la numéro 2 (pour le champ NP).

Image non disponible
Aperçu en mode création des propriétés de la liste NP

IV-C-1-a. La procédure évènementielle sur absence dans liste

Si le nom du patient saisi n'est pas présent dans la liste, on doit pouvoir proposer à l'utilisateur de l'ajouter. Sur l'évènement absence dans liste on va donc mettre le code suivant inspiré de celui de la faqfaq:

 
Sélectionnez

Private Sub NP_NotInList(NewData As String, Response As Integer)
' Si le patient n'est pas dans la liste alors on propose de l'ajouter.
  
  If MsgBox("Voulez-vous ajouter " & NewData & " à la liste des patients ?", _
                       vbYesNo + vbQuestion + vbDefaultButton2, "Ajout") = vbYes Then
        DoCmd.SetWarnings False
        DoCmd.RunSQL "INSERT INTO T_Patient ( Nom ) SELECT """ & NewData & """;"
        DoCmd.SetWarnings True
        Response = acDataErrAdded
    Else
        Response = acDataErrContinue
        NP.Undo
    End If

End Sub

IV-C-2. Le sous-formulaire "SF_Patient"

Ce sous-formulaire, contenu dans le formulaire F_RendezVous, permet d'afficher la signalétique du patient sélectionné dans la liste NP. Sa source de données est la table T_Patient et il est synchronisé avec son formulaire principal (F_RendezVous) sur le champ NP: Les propriétés champs fils et champs pères du contrôle sous-formulaire sont égales à NP.

Image non disponible
Aperçu en mode création des propriétés du contrôle du sous-formulaire

IV-C-3. Le bouton de commande "CmdValider"

Il sert à valider la saisie.

IV-C-3-a. La procédure évènementielle sur clic

Une fois que les choix ont été effectués sur le formulaire, il faut les valider: Si les horaires sélectionnés chevauchent d'autres RDV, ou que les zones de texte NP ou Memo sont vides alors l'utilisateur est invité à corriger sa saisie. Sur l'évènement clic du bouton de commande CmdValider, on a donc :

 
Sélectionnez

Private Sub CmdValider_Click()
' Valide les choix effectués sur le formulaire F_RendezVous.
' et met à jour le planning.
Dim db As DAO.Database
Dim LeSql As String, HD As Date, HF As Date, DateC As Date
Dim rsRdV As DAO.Recordset

DateC = CDate(Me!DateRdV)

' Si les zones de texte NP ou Memo ne sont pas vides.

   If ((Me!NP <> "") And Not IsNull(Me!NP)) Or _
      ((Me!Memo <> "") And Not IsNull(Me!Memo)) Then
   HD = CDate(Format(Me!DateRdV, "dd/mm/yy ") & Me!HoraireD)
   HF = CDate(Format(Me!DateRdV, "dd/mm/yy ") & Me!HoraireF)
   
   Set db = CurrentDb
   
' On recherche des RDV dont les horaires de début et de fin chevauchent les 
' horaires choisis sur le formulaire.
    
   LeSql = "SELECT * " & _
   "FROM T_RendezVous " & _
   "WHERE (HoraireDebut<>" & FormatDateUS(DateC) & ") And HoraireDebut<" & FormatDateUS(HF) & _
                                                      " And HoraireFin>" & FormatDateUS(HD)
   
   Set rsRdV = db.OpenRecordset(LeSql, dbOpenForwardOnly)
      
       If (Format(HF, "hh:nn") <= "18:00") And (HD < HF) Then

' Si aucun RDV n'a été trouvé, la plage horaire est donc disponible et on peut  
' enregistrer le RDV.
          If rsRdV.EOF Then
             Me!HoraireDebut = HD
             Me!HoraireFin = HF
             Me.Requery ' Actualisation de la table source T_RendezVous.
             MajPlanning ' Mise à jour du planning.
             DoCmd.Close  ' Fermeture du formulaire.    
          Else
             MsgBox ("Saisie incorrecte !")      
          End If             
      Else
         MsgBox ("Saisie incorrecte !")             
      End If
	  
   rsRdV.Close
   set rsRdV = Nothing 
   
   Set db = Nothing
   	     
   Else
      MsgBox ("Saisie incorrecte !")
   End If

End Sub

V. Le module "M_Planning" de l'application

Enfin nous présentons le module de l'application.

V-A. Description de quelques routines

Quelques routines utilisant les fonctions Date et Heurefonctions Date et Heure :

 
Sélectionnez

' Dans la partie déclaration du module on définit la date du 1er jour du planning 
' hebdomadaire.

Public DateDebut as date 

' Les fonctions du module.

Public Function FormatDateUS(laDate As Date) As String
' Formate la date passée en argument en date US.

FormatDateUS = Chr(35) & Format(laDate, "m-d-yy hh:nn:ss") & Chr(35)  ' Date au format US.
End Function

Private Function EstWeek(laDate As Date)
EstWeek = ((WeekDay(laDate) > 1) And (WeekDay(laDate) < 7)) ' est un jour de la semaine.
End Function

Function DebutSemaine(ByVal DateSemaine As Date) As Date
' Prend en argument un jour dans la semaine choisie 
' et renvoie la date du premier jour de cette semaine.
Dim i As Integer

i = WeekDay(DateSemaine, vbMonday)
DebutSemaine = DateAdd("d", -i + 1, DateSemaine)
End Function

Function NbreCreneaux(ByVal HoraireD as date, ByVal HoraireF as Date) as integer
' La fonction prend en argument l'horaire de début et de fin du RDV 
' et renvoie le nombre de créneaux de 15 minutes entre ces 2 horaires :

NbreCreneaux=DateDiff("n",HoraireD,HoraireF)/15

End function

Function PremierCreneau(ByVal HoraireD as date) as integer
' La fonction prend en argument l'horaire de début du rendez-vous et renvoie l'indice 
' du premier créneau horaire du RDV. Les horaires commencent à 8 heures.

PremierCreneau=(DateDiff("n",#08:00#,TimeValue(HoraireD))/15) + 1

End function

Function IndiceColonne(ByVal HoraireD as date) as integer
' La fonction prend en argument l'horaire de début du RDV et renvoie l'indice de la 
' colonne (le jour) correspondant sur le planning.

IndiceColonne =DateDiff("d", DateDebut, HoraireD)+1

End function

Public Function IndicesToHoraire(ByVal Ligne As Integer, ByVal Col As Integer) As Date
' La fonction prend en argument les indices de ligne (les horaires) et de 
' colonne (les jours) sur le planning et renvoie l'horaire correspondant. 

Dim DateC As Date 
Dim h As Integer
Dim m As Integer

DateC = DateAdd("d", (Col - 1), DateDebut)
h = (8)  
m = (h * 60) + 15 * (Ligne - 1)
DateC = DateAdd("n", m, DateC)

IndicesToHoraire = DateC

End Function

V-B. La procédure "InitPlanning"

La procédure InitPlanning efface le contenu du planning, redimensionne les labels à leur taille d'origine et met à jour les en-têtes de colonnes avec les jours de la semaine choisie.

 
Sélectionnez

Private Sub InitPlanning()
' Initialise le planning hebdomadaire avant sa mise à jour :
' Efface tous les rendez-vous, redimensionne les labels 
' et remplit les en-têtes de colonnes avec les jours de la semaine choisie.

Dim i As Integer, j As Integer
Dim DateC As Date ' Date courante qui prend successivement les valeurs des jours de la semaine.
For i = 1 To 40 ' on parcourt les indices des lignes.

   For j = 1 To 7 ' on parcourt les indices de colonnes (les jours) 
   Forms!F_Planning!Planning.Form("creneau" & i & "_" & j).Caption = ""
   
      If (i Mod 2) = 1 Then ' on alterne les couleurs de lignes.
         Forms!F_Planning!Planning.Form("creneau" & i & "_" & j).BackColor = -2147483624
      Else
         Forms!F_Planning!Planning.Form("creneau" & i & "_" & j).BackColor = vbWhite
      End If

' On définit la hauteur des lignes.      
   Forms!F_Planning!Planning.Form("creneau" & i & "_" & j).Height = 350
   Next j

Next i

For i = 1 To 7

DateC = DateAdd("d", (i - 1), DateDebut)
Forms!F_Planning!Planning.Form("Col" & i).Caption = Format(DateC, "ddd dd mmm yyyy")
   
   If EstWeek(DateC) Then 'on colorie les en-têtes (rouge pour les week-end)
      Forms!F_Planning!Planning.Form("Col" & i).BackColor = 16761024
   Else
      Forms!F_Planning!Planning.Form("Col" & i).BackColor = 16761087
   End If
   
Next i
   
End Sub

V-C. La procédure "MajPlanning"

La procédure de mise à jour des rendez-vous sur le planning s'effectue comme suit: Elle sélectionne, avec DAODAO, dans la table T_RendezVous, les RDV compris entre la date de début (DateDebut) et la date de début + 7 jours (DateDebut+7), puis met à jour le planning avec ces rendez-vous. Pour chaque RDV l'horaire de début et l'horaire de fin permettent de dimensionner le label correspondant au créneau horaire du début :

 
Sélectionnez

Public Sub MajPlanning()   
Dim db As DAO.Database
Dim RsPL As DAO.Recordset
Dim Ligne As Integer, Col As Integer
Dim LeSql As String
Dim i As Integer, d As Integer
Dim color As Long
Dim DateDeb As Object

Set db = CurrentDb
              
' Sélectionne les RDV compris entre DateDebut et DateDebut+7.
LeSql = "SELECT T_RendezVous.*, T_Patient.Nom as Patient " & _
"FROM T_RendezVous LEFT JOIN T_Patient ON T_RendezVous.NP = T_Patient.NP " & _
"WHERE T_RendezVous.HoraireDebut between " & FormatDateUS(DateDebut) & _
                                         " And " & FormatDateUS(DateDebut + 7)

Set RsPL = db.OpenRecordset(LeSql, dbOpenForwardOnly)

Forms!F_Planning!Titre.Caption = "PLANNING DE LA SEMAINE DU " & _ 
UCase(Format(DateDebut, "dd mmmm yyyy")) & " AU " & UCase(Format(DateDebut+6,"dd mmmm yyyy"))

Set DateDeb = Forms!F_Planning!DateD.Object
DateDeb.Value = DateDebut ' Affecte la date du 1er jour de la semaine au contrôle DateD.
Set DateDeb = Nothing

' Initialise le planning.
InitPlanning
     
   Do While Not (RsPL.EOF) ' on parcours les RDV.
   Col = IndiceColonne(RsPL!HoraireDebut)
   Ligne = PremierCreneau(RsPL!HoraireDebut)   

   ' on determine le nombre de créneaux horaires correspondant à la durée du RDV.
   d = DateDiff("n", RsPL!HoraireDebut, RsPL!HoraireFin) \ 15 
   
   ' Hauteur du label = Hauteur de la ligne multipliée par le nombre de créneaux.
   Forms!F_Planning!Planning.Form("creneau" & Ligne & "_" & Col).Height = 350 * d 
      
      ' Si le numéro du patient est nul, alors il ne s'agit pas d'un RDV.
      If IsNull(RsPL!NP) Then 
        color = 16761087
        Forms!F_Planning!Planning.Form("creneau"&Ligne& "_" &Col).Caption=CentrerTexte("Autre", d)
      
      ' sinon on remplie le label avec le nom du patient 
      ' accompagné du numéro du patient entre crochets
      Else 
        color = QBColor(RsPL!NP Mod 14 + 1) ' Définit la couleur.
        Forms!F_Planning!Planning.Form("creneau" & Ligne & "_" & Col).Caption = _
		                    CentrerTexte(RsPL!Patient & " [" & RsPL!NP & "]", d)   
      End If
   
   ' Colorie le label.
        
   Forms!F_Planning!Planning.Form("creneau" & Ligne & "_" & Col).BackColor = color
         
   RsPL.MoveNext
   Loop

' ==== Libération des variables ====

RsPL.Close
Set RsPL = Nothing

Set db = Nothing

End Sub

VI. Les applications à télécharger

La base exemple n°1agenda_med v1 est au format Access 2000, elle inclut le contrôle DTPicker.

La base exemple n°2agenda_med v2 est au format Access 2000 et comprend le contrôle calendrier de Arkham46.

La base exemple n°3agenda_med v3 est au format Access 2000 et comprend de nombreuses autres fonctionnalités (congés, jours fériés...).

VII. Remerciements

Je tiens à remercier Jean Ballat pour m'avoir guidé dans la réalisation de ce premier article et ram-0000 pour sa relecture.