Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
Accueil Access Forum Access F.A.Q Access F.A.Q VBA Tutoriels Sources Outils Livres Access TV Access 2007

Application:bibliothèque

28/07/2004

Par Stephane Eyskens (Autres articles)
 

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.


1. Introduction
1.1. Petit aperçu de l'application
2. Principales règles de gestion de notre bibliothèque
3. Brève explication de l'utilisation
3.1. L'écran des clients, éditeurs, auteurs,catégories, villes
3.2. L'écran des livres
3.3. L'écran de la gestion du stock
3.4. L'écran des réservations
3.5. L'écran des remises
3.6. L'arborescence
3.7. L'écran des recherches
3.8. Note générale concernant les écrans
4. Schéma relationnel
4.1. Les tables et leur structure
4.2. Les relations
5. Le VBA (Visual Basic for Application)
5.1. Les modules de classe
5.2. Les modules génériques
5.3. Le module générique de notre bibliothèque
5.4. Les modules de classe de notre bibliothèque
6. Téléchargement et utilisation


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 colonneTypeDescription
ID_AuteurNuméroAutoclé primaire, identifiant auteur
Nom_AuteurTexteNom de l'auteur
Prenom_AuteurTextePrénom de l'auteur
Note: une signalétique plus poussée de l'auteur peut être envisageable
Table CATEGORIES
Nom de colonneTypeDescription
ID_CategorieNuméroAutoclé primaire, identifiant des catégories
Nom_CategorieTexteNom de la catégorie
Table CLIENTS
Nom de colonneTypeDescription
ID_ClientNuméroAutoclé primaire, identifiant client
Nom_ClientTexteNom du client
Prenom_ClientTextePrénom du client
Adresse_ClientTexteadresse du client
CP_ClientTextecode 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 colonneTypeDescription
ID_EditeurNuméroAutoclé primaire, identifiant des éditeurs
Nom_EditeurTexteNom de l'éditeur
Table ETATS
Nom de colonneTypeDescription
ID_EtatNuméroAutoclé primaire, identifiant des états
EtatTexteType d'état des livres "RENDU, NON RENDU, Abîmé..."
Table LIVRES
Nom de colonneTypeDescription
ID_LivreTexteclé primaire, identifiant des livres. L'identifiant sera le n° ISBN du livre
ID_AuteurNumériqueN° de l'auteur du livre, clé étrangère de la table auteurs
ID_CategorieNumériqueN° de la catégorie, clé étrangère de la table categories
ID_EditeurNumériqueN° de l'éditeur, clé étrangère de la table editeurs
Titre_LivreTexteTitre du livre
Quantite_StockNumériqueQuantité en stock
Table REMISE_ENTETE
Nom de colonneTypeDescription
ID_RemiseNuméroAutoclé primaire, identifiant des remises.
ID_ReservationNumériqueN°de la réservation liée, clé étrangère de la table reservations_entete
Date_RemiseDateDate de la remise
ClotureeYES/NOFlag indiquant si la mise à jour du stock est réalisée ou non
Table REMISES_LIGNES
Nom de colonneTypeDescription
ID_RemiseNumériqueclé étrangère de la table remise_entete, identifiant des remises.
ID_LigneNuméroAutoN°de ligne, clé primaire
ID_Reservation_LigneNumériqueN° de ligne réservation, clé étrangère de la table reservations_lignes
QuantiteNumériqueNombre de livres remis
EtatNumériqueID de l'état, état du livre (bon, abîmé...)! clé étrangère de la table etats
Table RESERVATIONS_ENTETE
Nom de colonneTypeDescription
ID_ReservationNuméroAutoclé primaire, identifiant des réservations.
ID_ClientNumériqueN°de client, clé étrangère de la table clients
Date_ReservationDateDate de la réservation
MAJYES/NOFlag indiquant si la mise à jour du stock a été effectuée
clotureeYES/NOFlag indiquant si une remise est liée et a été confirmée par rapport à la réservation
Table RESERVATIONS_LIGNES
Nom de colonneTypeDescription
ID_ReservationNumériqueclé étrangère de la table reservations_entete, identifiant des réservations.
ID_LigneNuméroAutoclé primaire, N°de ligne
ID_LivreTexteclé étrangère de la table livres, n° du livre
Quantite_ReserveeNumériqueQuantité de livre(s) réservée
Table VILLES
Nom de colonneTypeDescription
Code_PostalNumériqueclé primaire, N° de code postal
VilleTexteNom 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 type
Dans 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 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:

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 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



Cet article est la propriété de www.developpez.com en tant qu'hebergeur ainsi que celle de Stephaneey en tant que redacteur, ce texte est donc protégé par le code de la propriété intellectuelle et est soumis à la réglementation en vigueur.
www.developpez.com ou son auteur se reserve le droit d'apporter des modifications sans préavis. Vous pouvez utiliser cet article comme bon vous semble, faire un lien depuis votre site Web, ou le copier en spécifiant l'auteur et la provenance (www.developpez.com) Le non respect de cette règle equivaudrait à faire une contrefaçon. La responsabilité de www.developpez.com, de l'un de ses membres, ou de la direction ne pourra etre engagé en cas de destruction partielle ou totale des données ou de l'architecture système ou logicielle inhérente à l'utilisation des ses logiciels.
Les logiciels decrits ici sont la propriété de leurs auteurs respectifs.
Responsable bénévole de la rubrique Access : Christophe Lessirard - Contacter par EMail :
Vos questions techniques : forum d'entraide Access - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.