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.
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▲
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▲
3-3. L'écran de la gestion du stock▲
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▲
3-5. L'écran des remises▲
3-6. L'arborescence▲
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▲
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
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
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:
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:
Dim une_variable As typeDans l'en-tête du module. Exemple complet:
'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.
'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 Function5-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:
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, "N°" & 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, "N°" & 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 Sub6. 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







