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