Application:bibliothèque

Nous vous avons concocté une petite application permettant de gérer une bibliothèque. Je remercie Maxence HUBICHE pour sa participation à l'élaboration de cet article.

Article lu   fois.

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1. Introduction

Cet article va décrire les mécanismes utilisés afin de réaliser cette petite application gérant une bibliothèque.
Nous y décrirons quelques procédures VBA ainsi que le schéma relationnel. L'application a été réalisée avec Access 2000.
Vous devez donc disposer de la même version de MS Access ou d'une version supérieure pour pouvoir l'utiliser.
Cette application est complètement libre de droits, vous pouvez donc l'utiliser en l'état ou la modifier selon vos désirs.

Note: il est à noter que cette application ainsi que cet article ont été réalisés par pur souci pédagogique. Je n'ai pas rencontré de bibliothéquaire pour réaliser cette application. Il est évident que pour un développement professionnel, la première chose à faire est de rencontrer son client et de faire une analyse approfondie des besoins, cette analyse débouchera sur la réalisation du modèle relationnel et sur le développement de l'application. Il est donc plus que probable que cette gestion de bibliothèque ne conviendrait pas à un bibliothéquaire en l'état.

1.1. Petit aperçu de l'application

Afin de faciliter l'utilisation de cette application, j'ai décidé de tout centraliser sur un seul écran.
Celui-ci est composé d'un contrôle onglet facilitant la navigation. Voici les différents flux pris en charge par l'application:

  • La gestion des livres et de leurs stocks
  • La gestion des clients
  • La gestion des différents auteurs et éditeurs
  • La gestion des codes postaux et des villes
  • La gestion des réservations de livres
  • La gestion des remises (livres rendus, abîmés, non rendu)
  • Un module de recherche permettant des recherches poussées
  • Un contrôle treeview permettant de naviguer de client en client et de visualiser leurs réservations/remises
  • Une personnalisation de l'affichage permettant de définir l'ordre dans lequel les onglets doivent s'afficher
  • Aucun système d'amende n'a été créé. Vous pourrez bien sûr ajouter cette fonctionalité vous-même

Voici un aperçu de l'écran principal.

Image non disponible

2. Principales règles de gestion de notre bibliothèque

  • Un client peut effectuer de 0 à plusieurs réservations
  • Un livre est identifié par son N° ISBN et est lié à une catégorie,un auteur et un éditeur préalablement définis
  • Une remise doit impérativement être liée à une réservation
  • La date de la remise doit être postérieure ou égale à la date de la réservation
  • Lors d'une réservation, la quantité du livre réservé ne peut être supérieure à la quantité du livre en stock.
  • Lorsqu'une réservation est confirmée, la mise à jour du stock est effectuée
  • Tant qu'une réservation n'est pas liée à une remise, elle peut-être annulée
  • Lorsqu'une remise est confirmée, la mise à jour du stock est effectuée
  • Pour être confirmée et provoquer la mise à jour du stock, une remise doit impérativement reprendre tous les livres réservés même
    si certains n'ont pas été rendus. Une gestion d'état permettra de stipuler "non rendu" pour les livres non rendus.

Ces règles de gestion devraient vous donner une idée de la manière dont la bibliothèque a été conçue.

3. Brève explication de l'utilisation

3.1. L'écran des clients, éditeurs, auteurs,catégories, villes

Image non disponible

Comme indiqué dans le schéma, pour pouvoir encoder un client, il faut que le code postal et la ville de résidence de celui-ci ait été préalablement encodés dans l'onglet "Villes".

Le numéro de client étant un numéroAuto, il n'est bien sûr pas proposé à l'encodage. Les écrans "Auteurs", "Catégories","Villes" et "Editeurs" étant très similaires à l'écran client. Je ne vous les détaillerai pas.

3.2. L'écran des livres

Image non disponible

3.3. L'écran de la gestion du stock

Image non disponible

Cet écran vous permet à tout moment de visualiser le nombre de livres en stock. Vous pouvez affiner l'affichage en recherchant un livre précis. Tous les champs sont en lecture seule. Vous pouvez uniquement incrémenter ou décrémenter la quantité en stock via les boutons "+" et "-"

3.4. L'écran des réservations

Image non disponible

3.5. L'écran des remises

Image non disponible

3.6. L'arborescence

Image non disponible

Cet écran vous permet en un rapide coup d'oeil de visualiser tous les clients ainsi que les réservations et remises qu'ils ont effectué. De plus, la navigation est assez agréable puisqu'elle fait appel à la technique d'affichage en arborescence. La liste "rechercher un client" referme tous les noeuds ouverts de l'arbre et n'ouvre que celui correspondant au client recherché.

3.7. L'écran des recherches

Image non disponible

L'écran des recherches construit dynamiquement une requête SQL simple. C'est à dire une requête mono-critère et mono-table. Malgré cette limitation, vous pouvez à peu près effectuer des recherches sur tout ce que vous voulez. Les 4 types de colonnes utilisés dans notre bibliothèque sont pris en charge, à savoir:

  • Le type date
  • Le type numérique
  • Le type texte
  • Le type Oui/non

3.8. Note générale concernant les écrans

Comme vous l'aurez probablement déjà constaté, les boutons apparaîssent parfois en grisé et d'autres fois non. Sur tous les écrans, j'ai essayé autant que possible de ne laisser apparaître un bouton en mode "activé" que si vous avez réellement le droit de l'utiliser. Par exemple, dans l'écran remise, vous n'aurez pas accès au bouton "Confirmer" si la remise a déjà été confirmée. Il en va de même pour d'autres boutons. Le comportement des boutons est donc "intelligent"

4. Schéma relationnel

4.1. Les tables et leur structure

Nous allons parcourir ensemble la structure des tables.

Table AUTEURS
Nom de colonne Type Description
ID_Auteur NuméroAuto clé primaire, identifiant auteur
Nom_Auteur Texte Nom de l'auteur
Prenom_Auteur Texte Prénom de l'auteur
Note: une signalétique plus poussée de l'auteur peut être envisageable
Table CATEGORIES
Nom de colonne Type Description
ID_Categorie NuméroAuto clé primaire, identifiant des catégories
Nom_Categorie Texte Nom de la catégorie
Table CLIENTS
Nom de colonne Type Description
ID_Client NuméroAuto clé primaire, identifiant client
Nom_Client Texte Nom du client
Prenom_Client Texte Prénom du client
Adresse_Client Texte adresse du client
CP_Client Texte code postal du client. Ce code est l'id cp se trouvant dans la table "villes"
Note: une signalétique plus poussée du client peut être envisageable! D'autre part, si vous avez la possibilité d'identifier vos clients autrement qu'avec un numéro auto, n'hésitez pas! Selon les cas de figure, une table "adresses" séparées peut s'avérer utile lorsque les clients doivent être joignable à plusieurs adresses (adresse livraison, facturation, domicile...)
Table EDITEURS
Nom de colonne Type Description
ID_Editeur NuméroAuto clé primaire, identifiant des éditeurs
Nom_Editeur Texte Nom de l'éditeur
Table ETATS
Nom de colonne Type Description
ID_Etat NuméroAuto clé primaire, identifiant des états
Etat Texte Type d'état des livres "RENDU, NON RENDU, Abîmé..."
Table LIVRES
Nom de colonne Type Description
ID_Livre Texte clé primaire, identifiant des livres. L'identifiant sera le n° ISBN du livre
ID_Auteur Numérique N° de l'auteur du livre, clé étrangère de la table auteurs
ID_Categorie Numérique N° de la catégorie, clé étrangère de la table categories
ID_Editeur Numérique N° de l'éditeur, clé étrangère de la table editeurs
Titre_Livre Texte Titre du livre
Quantite_Stock Numérique Quantité en stock
Table REMISE_ENTETE
Nom de colonne Type Description
ID_Remise NuméroAuto clé primaire, identifiant des remises.
ID_Reservation Numérique N°de la réservation liée, clé étrangère de la table reservations_entete
Date_Remise Date Date de la remise
Cloturee YES/NO Flag indiquant si la mise à jour du stock est réalisée ou non
Table REMISES_LIGNES
Nom de colonne Type Description
ID_Remise Numérique clé étrangère de la table remise_entete, identifiant des remises.
ID_Ligne NuméroAuto N°de ligne, clé primaire
ID_Reservation_Ligne Numérique N° de ligne réservation, clé étrangère de la table reservations_lignes
Quantite Numérique Nombre de livres remis
Etat Numérique ID de l'état, état du livre (bon, abîmé...)! clé étrangère de la table etats
Table RESERVATIONS_ENTETE
Nom de colonne Type Description
ID_Reservation NuméroAuto clé primaire, identifiant des réservations.
ID_Client Numérique N°de client, clé étrangère de la table clients
Date_Reservation Date Date de la réservation
MAJ YES/NO Flag indiquant si la mise à jour du stock a été effectuée
cloturee YES/NO Flag indiquant si une remise est liée et a été confirmée par rapport à la réservation
Table RESERVATIONS_LIGNES
Nom de colonne Type Description
ID_Reservation Numérique clé étrangère de la table reservations_entete, identifiant des réservations.
ID_Ligne NuméroAuto clé primaire, N°de ligne
ID_Livre Texte clé étrangère de la table livres, n° du livre
Quantite_Reservee Numérique Quantité de livre(s) réservée
Table VILLES
Nom de colonne Type Description
Code_Postal Numérique clé primaire, N° de code postal
Ville Texte Nom de la ville, village...

4.2. Les relations

Les relations entre les différentes tables d'une base de données permettent d'assurer une intégrité des données Pour bien comprendre le mécanisme des relations, voici un petit exemple très explicite:

Nous allons établir une relation entre les tables "Clients" et "Reservations_entete" dont les structures sont décrites en section 3.1

Image non disponible

Nous établissons une relation entres les colonnes Clients.ID_Client et Reservations_entete.ID_Client.
Cette relation est de type 1,n ce qui signifie que dans la table client, on ne peut avoir qu'une seule occurrence d'un même ID_Client
et dans la table Reservation_entete, on peut en avoir plusieurs. Pour créer une relation, il suffit simplement de faire un glisser-déplacer d'une colonne depuis la table source vers la table cible. Ensuite, il faut appliquer l'intégrité référentielle. Dans notre cas de figure, l'intégrité référentielle va faire en sorte qu'il sera impossible lors de l'encodage d'une réservation, de spécifier un numéro de client ne se trouvant pas dans la table "Clients". Lors de l'établissement d'une relation, deux options sont disponibles

  • Mettre à jour en cascade les champs correspondants : si le numéro de client est modifié, toutes les réservations correspondant à l'ancien numéro de client seront mises à jour avec la nouvelle valeur
  • Effacer en cascade les champs correspondants: si ce client est supprimé de la table Clients, toutes les réservations de ce client seront supprimées aussi

Je n'ai pas coché ces deux options! C'est une question de choix et de besoin. Si on décide qu'un client ne peut en aucun cas être supprimé/modifié (N°), ne serait-ce que pour garder une historique, il est inutile de cocher ces cases. Pour toutes les tables dont les enregistrements sont susceptibles d'être supprimés, il est évident qu'il faut alors cocher l'option "suppression en cascade".

Suivant exactement le même principe que celui expliqué ci-dessus, voici le schéma relationnel global

Image non disponible

Je n'ai adopté aucune convention de nom pour définir les clés primaires et étrangères. Certaines règles peuvent être mise en oeuvre concernant la nomination des colonnes, par exemple:

  • On peut décider de nommer les clés primaires comme ceci: PK_....(Primary Key+nom de la colonne)
  • On peut décider de nommer les clés étrangères comme ceci:FK_...(Foreign Key+nom de la colonne)

Toute autre convention peut bien sûr être adoptée! Adopter ce genre de convention peut éclaircir le modèle relationnel. Avec Access, ce n'est pas forcément nécessaire puisqu'il suffit d'ouvrir l'écran des relations pour voir tout ce qui a été fait. Avec d'autres SGBD, le modèle relationnel ne saute pas toujours aux yeux et donc, une convention de nom peut aider.

5. Le VBA (Visual Basic for Application)

5.1. Les modules de classe

Les modules de classes que nous utiliserons sont attachés aux formulaires et aux états. Ils sont propres à ceux-ci. Ces modules contiennent du code associés aux évènements liés au formulaire et à tous les contrôles contenus dans ce formulaire.
Un module de classe ressemble typiquement à ceci:

 
Sélectionnez

Private Sub Form_Delete(Cancel As Integer)
' procédure déclenchée lorsqu'un enregistrement est supprimé
End Sub

Private Sub Form_Open(Cancel As Integer)
' procédure déclenchée lorsque le formulaire s'ouvre
End Sub

Private Sub Quantite_AfterUpdate()
'procédure déclenchée lorsque le contrôle "Quantite" a été mis à jour
End Sub

			 

Comme vous pouvez le constater, toutes les procédures se trouvant dans un module de classe sont liées à un évènement particulier. C'est ce qu'on appelle des "procédures évènementielles". On retrouve d'ailleurs des modules similaires dans d'autres langages.
L'intérêt de ces modules est, vous l'aurez compris, de gérer tous les évènements pouvant survenir dans un formulaire et/ou un état. Les variables déclarées au niveau d'une procédure évènementielle sont locale à cette procédure et sont donc inaccessible par les autres. Vous pouvez déclarer des variables globales au niveau du module de classe tout entier. Pour cela, il suffit de faire:

 
Sélectionnez

Dim une_variable As type

Dans l'en-tête du module. Exemple complet:

 
Sélectionnez

'en-tête du module
Option Compare Database
Option Explicit
Dim une_variable As Integer

'procédure du bouton nommé BoutonDVP1
Private Sub BoutonDVP1_Click()
 une_variable = 1
End Sub
'procédure du bouton nommé BoutonDVP2
Private Sub BoutonDVP2_Click()
 Debug.Print une_variable
End Sub

Comme vous le voyez en déclarant la variable en dessous des directives access, elle est connue par toutes les procédures du module de classe.
Les directives Access montrées ci-dessus signifie ceci:

  • Option Compare Database fournit des comparaisons de chaînes basées sur l'ordre de tri déterminé par l'identificateur de paramètres régionaux de la base de données dans laquelle la comparaison de chaînes est effectuée.
  • Option Explicit Force la déclaration de variable

Voilà, on a à peu près fait le tour des modules de classe. Vous trouverez plus bas, quelques procédures qui ont été écrites pour la bibliothèque.

5.2. Les modules génériques

Les modules génériques accessibles via l'onglet "Modules" servent à coder des procédures et des fonctions qui sont totalement indépendantes et susceptibles d'être appelée par n'importe quel formulaire/état/macro..., on y retrouve les concepts suivants:

  • Les mots clés "Public" et "Private" servant à définir la portée des fonctions/procédures/variables
  • Le mot clé global permettant de déclarer une variable globale à toute l'application. Nous lui préfèrerons toutefois le mot clé "public" car global n'est encore présent que par souci de compatibilité avec Access2

5.3. Le module générique de notre bibliothèque

La bibliothèque contient un seul module générique. Le code est assez simple et n'a généralement qu'un but, à savoir, effectuer des vérifications d'enregistrement dans la base de donnée.

 
Sélectionnez

'commentaire générique
'La majorité des fonctions ci-dessous utilisent ADO, le processus d'accès à la bd sera toujours comme suit
' rst.open ...-> pour les requêtes de type SELECT
' db.execute ...-> pour les ordres DML, c'est à dire update, insert et delete
'Dans ce module, nous utiliserons DAO uniquement pour récupérer des informations sur le DDL
'Directives MSAccess
Option Compare Database
Option Explicit


Dim dbDAO As Database 'Variable globale au module connexion db en DAO
Dim db As New ADODB.Connection 'Variable globale au module connexion db en ADO
Dim rst As New ADODB.Recordset 'Variable globale au module recordset ADO

'Cette fonction est appelée pour initialiser DAO
Private Function initDAO() As Boolean
On Error GoTo err

Set dbDAO = CurrentDb

initDAO = True
Exit Function

err:
    MsgBox err.Number, err.Description
    initDAO = False
    
End Function

'Cette fonction est appelée pour initialiser ADO
Private Function initADO() As Boolean
On Error GoTo err
If db.State <> adStateClosed Then db.Close
If rst.State <> adStateClosed Then rst.Close
Set db = CurrentProject.Connection

initADO = True
Exit Function

err:
    MsgBox err.Number, err.Description
    initADO = False
    
End Function

'Cette fonction ferme les ressources allouées à ADO, db et recordset
Private Function closeADO() As Boolean
If db.State <> adStateClosed Then db.Close
If rst.State <> adStateClosed Then rst.Close
End Function

'Cette fonction est appelée lors de l'ajout d'un nouvel et renvoie "vrai" si 
'un auteur avec le même nom et prénom que ceux passés en paramètre existe déjà.
'c'est juste un avertissement pour éviter des erreurs de distraction de la part des utilisateurs
Public Function verification_auteur(nomAuteur As String, prenomAuteur As String) As Boolean
Dim ado As Boolean
On Error GoTo err
If Not initADO() Then
    MsgBox "Problème d'initialisation ADO", vbCritical
    Exit Function
End If

rst.Open "select count(*) as cnt from auteurs where nom_auteur='" & nomAuteur & "' and prenom_auteur='" & _
          prenomAuteur & "'", db

If rst("cnt") > 0 Then
    verification_auteur = True
Else
    verification_auteur = False
End If

ado = closeADO()

Exit Function

err:
    MsgBox err.Number, err.Description
    
End Function

'Cette fonction est appelée lors de la réservation d'un livre. Elle vérifie que la quantité en stock est suffisante par
'rapport à la quantité que l'utilisateur souhaite réserver.
Public Function verification_livre(IDLivre As Long, Quantite As Integer) As Boolean
Dim ado As Boolean
On Error GoTo err

If Not initADO() Then
    MsgBox "Problème d'initialisation ADO", vbCritical
    Exit Function
End If
rst.Open "select Quantite_Stock from livres where ID_Livre='" & IDLivre & "'", db
If ((rst("Quantite_Stock") - Quantite) < 0) Then
    verification_livre = False
Else
    verification_livre = True
End If

ado = closeADO()
Exit Function
err:
    MsgBox err.Number, err.Description
    verification_livre = False
End Function

'Cette fonction est appelée lors d'une remise, elle vérifie que la quantité que l'utilisateur décide de remettre
'est bien inférieure ou égale à la quantité réservée
Public Function Verification_Reservation(IDRes As Integer, IDLigne As Integer, Qte As Integer)
Dim ado As Boolean
On Error GoTo err

If Not initADO() Then
    MsgBox "Problème d'initialisation ADO", vbCritical
    Exit Function
End If
rst.Open "select Quantite_Reservee from Reservation_Lignes where ID_Reservation=" & IDRes & " and ID_Ligne=" & _
         IDLigne, db
If ((rst("Quantite_Reservee") - Qte) < 0) Then
    Verification_Reservation = False
Else
    Verification_Reservation = True
End If

ado = closeADO()
Exit Function
err:
    MsgBox err.Number, err.Description
    ado = closeADO()
    Verification_Reservation = False
End Function

'Cette fonction sert à inter-changer les numéros d'affichage des onglets.
'Si par exemple vous aviez l'onglet "Réservations" en position 1 et l'onglet "Remises" en position 2 et que vous décidez
'd'afficher les remises en 1, l'appel à switch ordre sera comme ceci: retour=SwitchOrdre(1,2)
Public Function SwitchOrdre(ordre As Integer, OrdreOld As Integer) As Boolean
On Error GoTo err
Dim ado as Boolean

If Not initADO() Then
    MsgBox "Problème d'initialisation ADO", vbCritical
    Exit Function
End If
rst.Open "select count(*) as cnt from Ordre_Onglets where ordre in (" & ordre & "," & OrdreOld & ")", db

If (rst!cnt < 2) Then
 SwitchOrdre = False
 ado = closeADO()
 Exit Function
End If
rst.Close
rst.Open "select * from Ordre_Onglets where ordre in (" & ordre & "," & OrdreOld & ")", db, adOpenDynamic, adLockOptimistic
While Not rst.EOF
 If rst!ordre = ordre Then
  rst!ordre = OrdreOld
 Else
  rst!ordre = ordre
 End If
 rst.Update
 rst.MoveNext
Wend

ado = closeADO()
SwitchOrdre = True
Exit Function
err:
    MsgBox err.Number, err.Description
    ado = closeADO()	
    SwitchOrdre = False
End Function

'Cette fonction vérifie si une remise est liée à la réservation que l'utilisateur tente d'annuler
'nous n'appelons pas initAdo ni ADOclose car RemiseOk est appelée par une autre fonction gérant elle-même ces appels.
Function RemiseOk(IDReservation As Long) As Boolean
rst.Open "select count(*) as cnt from Remise_Entete where id_reservation=" & IDReservation, db
If rst!cnt = 0 Then
    RemiseOk = True
Else
    RemiseOk = False
End If
rst.Close

End Function

'Cette fonction vérifie que la date de remise est >= à la date de réservation
Public Function Verification_Date_Remise(IDReservation As Integer, RemDate As Date) As Boolean
On Error GoTo err
Dim ado As Boolean

If Not initADO() Then
    MsgBox "Problème d'initialisation ADO", vbCritical
    Exit Function
End If
rst.Open "select Date_Reservation from Reservations_entete where ID_Reservation=" & IDReservation, db
If rst!Date_Reservation > RemDate Then
 Verification_Date_Remise = False
 ado = closeADO()
 Exit Function
End If
Verification_Date_Remise = True
Exit Function
err: MsgBox err.Number, err.Description
     ado = closeADO() 			
End Function

'Cette fonction récupère le nom de toutes les tables existant dans la bd.
Public Function getTables() As Boolean
On Error GoTo err
Dim td As TableDef

If Not initDAO() Then
 MsgBox "Problème d'initialisation de DAO", vbCritical
 getTables = False
 Exit Function
End If
'On désactive les messages d'avertissement d'access
DoCmd.SetWarnings False
DoCmd.RunSQL ("delete from tmp_tables")
' On récupère toutes les tables non système et non temporaire
For Each td In dbDAO.TableDefs 
  If Left(td.Name, 4) <> "MSys" And Left(td.Name, 3) <> "tmp" Then
   DoCmd.RunSQL "insert into tmp_tables(table_name) values('" & td.Name & "')"
  End If
Next td
'On réactive les messages d'avertissement d'access
DoCmd.SetWarnings True
dbDAO.Close
getTables = True
Exit Function
err: MsgBox err.Number, err.Description
     DoCmd.SetWarnings True
     getTables = False
End Function

'Cette fonction récupère toutes les colonnes formant la structure de la table "tabname" donnée en paramètre
Public Function getColumns(tabname As String) As Boolean
On Error GoTo err
Dim col

If Not initDAO() Then
 MsgBox "Problème d'initialisation de DAO", vbCritical
 getColumns = False
 Exit Function
End If

DoCmd.SetWarnings False
DoCmd.RunSQL ("delete from tmp_columns")

For Each col In dbDAO.TableDefs(tabname).Fields
 DoCmd.RunSQL "insert into tmp_columns(column_name) values('" & col.Name & "')"
Next col
getColumns = True
dbDAO.Close
Exit Function
err: MsgBox err.Number, err.Description
     DoCmd.SetWarnings True
     dbDAO.Close
     getColumns = False
End Function

'Cette fonction retourne le type de la colonne "colName" de la table "tabname"
Public Function getColumnType(tabname As String, colName As String) As Integer
On Error GoTo err
Dim col

If Not initDAO() Then
 MsgBox "Problème d'initialisation de DAO", vbCritical
 getColumnType = -1
 Exit Function
End If

Set col = dbDAO.TableDefs(tabname).Fields(colName)

getColumnType = col.Type
dbDAO.Close
Exit Function
err: MsgBox err.Number, err.Description
     DoCmd.SetWarnings True
     dbDAO.Close
     getColumnType = -1
End Function

'Cette fonction s'occupe de la mise à jour du stock. Elle gère:
'- la confirmation des réservations
'- l'annulation des réservations
'- la confirmation des remises

Public Function MAJ_Stock(IDReservation As Long, TypeMAJ As Integer) As Boolean
On Error GoTo err

Dim IDRemise As Integer
Dim RemComplete As Boolean
Dim ado As Boolean


If Not initADO() Then
    MsgBox "Problème d'initialisation ADO", vbCritical
    Exit Function
End If

If (TypeMAJ = 2) Then
 If Not RemiseOk(IDReservation) Then
  MsgBox "Vous ne pouvez plus annuler cette réservation car une remise est déjà en cours pour celle-ci", vbCritical
  Exit Function
 End If
End If

If TypeMAJ <> 3 Then
 rst.Open "select id_reservation, id_ligne,quantite_stock,quantite_reservee from livres inner join reservation_lignes" _
          & " on livres.id_livre = reservation_Lignes.id_livre where reservation_lignes.Id_Reservation =" & _
          IDReservation, db, adOpenDynamic, adLockOptimistic
 While Not rst.EOF
      Select Case TypeMAJ
        Case 1: 'Il s'agit d'une confirmation de réservation, donc on décrémente la qté en stock
            rst!Quantite_Stock = rst!Quantite_Stock - rst!Quantite_Reservee
'annulation de rés. donc on incrémente la quantité en stock car elle a déjà été déduite lors de la 
'confirmation de la réservation
        Case 2: 
            rst!Quantite_Stock = rst!Quantite_Stock + rst!Quantite_Reservee
      End Select
      rst.Update
      rst.MoveNext
 Wend
Else
'il s'agit d'une remise que l'on considère complète par défaut
 RemComplete = True
' ce recordset va permettre de déterminer si tous les livres ont été remis
 rst.Open "SELECT [Reservation_Lignes].[Quantite_Reservee], sum( [Remises_Lignes].[Quantite]) AS QuantiteRemise " _ 
          & "FROM Reservation_Lignes INNER JOIN Remises_Lignes ON [Reservation_Lignes].[ID_Ligne]=" _
          & "[Remises_Lignes].[ID_Reservation_Ligne] GROUP BY [Reservation_Lignes].[ID_Ligne], " _
          & "[Reservation_Lignes].[Quantite_Reservee]", db
 While Not rst.EOF
    If rst!QuantiteRemise > rst!Quantite_Reservee Then
     MsgBox "La quantité remise ne peut être supérieure à la quantité réservée", vbCritical
     ado = closeADO()
     MAJ_Stock = False
     Exit Function
    End If
    
    If rst!QuantiteRemise < rst!Quantite_Reservee Then
     RemComplete = False
    End If
  rst.MoveNext
 Wend
 rst.Close

 If Not RemComplete Then
  MsgBox "La remise est incomplète, veuillez la compléter! Si certains livres ont pas été remis, " _
         & "veuillez sélectionner non rendu comme état", vbCritical
  ado = closeADO()
  MAJ_Stock = False
  Exit Function
 End If
 'Ce recordset dynamique va nous permettre d'incrémenter la quantité en stock du livre remis, les jointures sont nécessaires
 ' pour assurer la cohérence des données
 rst.Open "SELECT remises_lignes.ID_Remise,remises_lignes.etat,reservation_lignes.ID_livre, remises_lignes.Quantite, " _
          & "Livres.Quantite_Stock, Reservations_entete.Id_Client FROM Reservations_entete INNER JOIN " _
          & "((Livres INNER JOIN reservation_lignes ON Livres.ID_Livre = reservation_lignes.ID_livre) INNER JOIN " _
          & "remises_lignes ON reservation_lignes.ID_Ligne = remises_lignes.ID_Reservation_Ligne) ON " _
          & "Reservations_entete.Id_Reservation = reservation_lignes.ID_Reservation WHERE " _
          & "remises_lignes.ID_Reservation_Ligne In (select ID_Ligne from Reservation_Lignes where ID_Reservation=" & _
          IDReservation & ") ORDER BY  remises_lignes.Etat", db, adOpenDynamic, adLockOptimistic
 IDRemise = rst!ID_Remise

 While Not rst.EOF 
  If rst!Etat = 1 Then
   rst!Quantite_Stock = rst!Quantite_Stock + rst!Quantite
   rst.Update
  End If
  rst.MoveNext
 Wend
 'On définit la remise comme clôturée
 db.Execute "update remise_entete set cloturee=true where ID_Remise=" & IDRemise
 'On définit la réservation comme clôturée
 db.Execute "update Reservations_entete set cloturee=true where ID_Reservation=" & IDReservation 
End If
If TypeMAJ = 1 Then 'si il s'agit d'une confirmation de réservation, on marque la rés. comme confirmée
    db.Execute "update reservations_entete set MAJ=true where id_reservation=" & IDReservation
ElseIf TypeMAJ = 2 Then 
's'il s'agit d'une annulation, on la supprime, pas besoin de supprimer les lignes car access le fera automatiquement
'grâce à la relation sur ID_Reservation entre les entêtes et les lignes.
    db.Execute "delete from reservations_entete where id_reservation=" & IDReservation
End If
ado = closeADO
MAJ_Stock = True
Exit Function
err:
    MsgBox err.Number, err.Description
    ado = closeADO
    MAJ_Stock = False
End Function

5.4. Les modules de classe de notre bibliothèque

Etant donné le nombre de modules de classe présent dans la bibliothèque, je ne vais pas détailler tout le code. Celui-ci est par ailleurs assez simple, donc il est inutile de s'étendre davantage dessus. Je ne détaillerai donc qu'une seule procédure qui est un peu plus complexe. Il s'agit de l'arborescence utilisant le contrôle treeview.Voici donc ce code:

 
Sélectionnez

Private Sub Afficher_Click()
Dim ctl As TreeView, clients As DAO.Recordset, db As DAO.Database
Dim prbar As ProgressBar
Dim res As DAO.Recordset, remise As DAO.Recordset
Dim k As Integer, i As Integer, j As Integer

Set db = CurrentDb
Set prbar = Me!prog.Object

prbar.Min = 0
Set clients = db.OpenRecordset("select count(*) as n from Clients")
If clients!N <> 0 Then prbar.Max = clients!N
clients.Close

Set ctl = Me!tree.Object
'Recordset contenant les enregistrements de la table clients
Set clients = db.OpenRecordset("select  ID_Client, Nom_Client, Prenom_Client from Clients")
i = 1
j = 1
k = 1
'On parcours la table des clients qui est la table maître du treeview
While Not clients.EOF
 'on ajoute le noeud de 1er niveau, c'est à dire le client lui-même
 'la clé est la chaîne de caractère cli suivie de l'ID du client
 'le texte affiché est la concaténation du nom et du prénom
    ctl.Nodes.Add , , "cli" & clients!ID_Client, clients!Nom_Client & " " & clients!Prenom_Client
    'on ajoute un fils au client, ce fils c'est le noeud des réservations
    ctl.Nodes.Add "cli" & clients!ID_Client, tvwChild, "res" & i, "Réservations"
    'on ajoute un autre fils au client, le noeud des remises
    ctl.Nodes.Add "cli" & clients!ID_Client, tvwChild, "rem" & i, "Remises"
    ctl.Nodes("res" & i).BackColor = 16744448
    ctl.Nodes("rem" & i).BackColor = 8421631
    'On va chercher les infos qui nous intéresse dans les réservations du client
    'on a besoin de la jointure avec les tables livres et lignes réservations pour récupérer
    'le titre du livre ainsi que la quantité réservée
    Set res = db.OpenRecordset("SELECT Reservation_Lignes.ID_Reservation, Livres.Titre_livre, " _
    & "Reservation_Lignes.Quantite_Reservee,Reservations_entete.Id_Client FROM Reservations_entete" _
    & "INNER JOIN (Livres INNER JOIN Reservation_Lignes ON Livres.ID_Livre = " _
    & "Reservation_Lignes.ID_livre) ON Reservations_entete.Id_Reservation = Reservation_Lignes.ID_Reservation WHERE " _
    & "Reservations_entete.Id_Client=" & clients!ID_Client & " and Reservations_entete.MAJ=true")
    While Not res.EOF
    'On ajoute la ligne de réservation comme fils du noeud "réservation" qui est lui-même fils du noeud "client..."
    'Vous noterez que pour chaque noeud, la clé change. Je me sers des variables i,j,k qui sont assignées à chaque
    'type de noeud et incrémentées dès lors qu'un noeud leur correspondant est ajouté
     ctl.Nodes.Add "res" & i, tvwChild, "lres" & j, "" & res!ID_Reservation & _
     "Livre:" & res!Titre_livre & " Qté:" & res!Quantite_Reservee
     res.MoveNext
     j = j + 1
    Wend
    res.Close
    
    'On va chercher les infos qui nous intéresse dans les remises du client
    'le principe est exactement le même que pour le noeud des réservations.
    Set remise = db.OpenRecordset("SELECT Remises_Lignes.ID_Remise, Livres.Titre_livre, Remises_Lignes.Quantite, " _
    & "Reservations_entete.Id_Reservation FROM Remise_Entete INNER JOIN ((Clients INNER JOIN Reservations_entete " _
    & "ON Clients.ID_Client = Reservations_entete.Id_Client) INNER JOIN " _
    & "((Livres INNER JOIN Reservation_Lignes ON Livres.ID_Livre = Reservation_Lignes.ID_livre) INNER JOIN " _
    & "Remises_Lignes ON Reservation_Lignes.ID_Ligne=Remises_Lignes.ID_Reservation_Ligne) ON " _
    & "Reservations_entete.Id_Reservation = Reservation_Lignes.ID_Reservation) ON " _
    & "(Reservations_entete.Id_Reservation = Remise_Entete.ID_Reservation) AND (Remise_Entete.ID_Remise = " _
    & "Remises_Lignes.ID_Remise) " _
    & "WHERE Reservations_entete.Id_Client=" & clients!ID_Client & " AND Remise_Entete.cloturee=True")

    While Not remise.EOF
     ctl.Nodes.Add "rem" & i, tvwChild, "lrem" & k, "" & remise!ID_Remise & " Livre:" & _
     remise!Titre_livre & " Qté:" & remise!Quantite
     remise.MoveNext
     k = k + 1
    Wend
    remise.Close
    If prbar.Value < prbar.Max Then prbar.Value = i
    i = i + 1
    clients.MoveNext
Wend
'On remet la progress bar à 0
prbar.Value = 0
'on ferme le recordset clients
clients.Close
'on ferme la connexion DAO
db.Close
'On active le bouton qui permet de déployer l'arbre
Arbo.Enabled = True
'On lui donne le focus
Arbo.SetFocus
'On désactive le bouton qui crée l'arbre
Afficher.Enabled = False
End Sub

6. Téléchargement et utilisation

Vous pouvez télécharger la base de données ici. Comme je l'ai spécifié dans l'introduction, vous aurez besoin d'access 2000 ou d'une version supérieure pour pouvoir l'utiliser. Vous pouvez me signaler les problèmes éventuels par MP

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2004 Developpez. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.