I. Le pied à l'étrier▲
Le défi de cet article, c'est d'apprendre Access à un néophyte, en le guidant pour réaliser un outil qui l'aide dans sa gestion quotidienne.
Puisqu'il s'agissait d'un premier contact avec le logiciel, j'ai conseillé de lire d'abord ces deux tutoriels :
II. Contexte de l'outil à mettre en place▲
Il s'agit d'un laboratoire qui reçoit des échantillons de produit dans des tubes.
À la réception, ces tubes portent le nom d'un « lot » et d'un « stade » de production indiqués par le client.
Le laborantin ajoute une étiquette personnelle sur chaque tube, le « N° du tube » et le range dans un endroit réfrigéré.
À diverses époques, le client va demander de procéder à des analyses du produit… qu'il faudra éventuellement décongeler. Nous parlerons de « manipulations ».
S'il reste du liquide dans un tube manipulé, on le restockera alors avec son reliquat ou, peut-être qu'on préfèrera répartir ce reliquat dans des nouveaux tubes. On parle alors de « réaliquotage ».
Pour des raisons techniques, il est impératif de mémoriser le nombre de fois qu'un contenu a été décongelé.
Le but de l'application est de tenir l'inventaire des tubes : savoir où chacun se trouve, la quantité qu'il contient et combien de fois son liquide a été décongelé.
III. Le modèle de données▲
Toutes les relations sont du type 1 à plusieurs et appliquent l'intégrité référentielle
Voir Comprendre les jointures dans Access de Maxence Hubiche.
IV. Un mot sur les tables▲
IV-A. tTubes▲
Ne pas confondre tTubesPK qui est un N° attribué automatiquement par Access (et qui servira de clé) avec TubeNum qui est le N° de tube attribué par l'utilisateur.
TubeSelect est un drapeau (flag) que nous utiliserons pour signaler que ce tube est sélectionné dans une manipulation en cours.
TubeRecu est un drapeau qui sera mis à Oui, s'il s'agit d'un tube reçu du fournisseur (donc quand le drapeau est à Non, il s'agit d'un tube « réaliquoté »).
IV-B. tPrelevements▲
Dans cette table, on stockera la date et le motif du prélèvement.
N.B. Dans l'état actuel du développement, il y a maximum un prélèvement par enregistrement.
Si un tube intervient dans une manip et qu'il subsiste un reliquat, un nouvel enregistrement sera créé dans tTubes (donc avec un autre tTubesPK), ce « nouveau » hérite des caractéristiques de l'original (NumTube,TubeDateEntree, NbreDecongel…).
IV-C. tEncodageEntree et tRealiquot▲
Il s'agit de tables techniques qui servent à encoder les items d'une entrée ou d'un réaliquotage. Elles sont vidangées à l'issue du processus.
N.B. Pour empêcher l'encodage de N° de tube en doublon :
Les autres tables n'appellent pas de commentaires particuliers.
Maintenant que nous avons examiné quoi montrer (les tables), nous allons voir comment le montrer (les formulaires).
Pour apprendre à créer un formulaire en Access, je suggère de lire les paragraphes 2—1—1 et 2—1—2 de Mise en surbrillance d'un enregistrement dans un formulaire de Jean-Philippe Ambrosino
N.B. Formulaire et état, même combat !
Pour un autre exemple et préparer les premiers contacts avec la syntaxe du langage VBA (Visual Basic for Applications), cette autopub :
V. Un scénario de test pour illustrer nos propos▲
Imaginez la mise au point de la production d'un produit : 12345.
La production passe par plusieurs stades : Initial, […] , chromatographie, […] , final.
Au fil du temps, le labo reçoit des tubes d'échantillons prélevés aux différents stades.
Pour le stade final, le 15/3 le labo reçoit 50 ml de liquide réparti en 13 tubes :
3 tubes de 10 ml à stocker à -20 °C
2 tubes de 5 ml à stocker à -20 °C
1 tube de 3 ml à stocker à -20 °C
3 tubes de 1 ml à stocker à -20 °C
4 tubes de 1 ml à stocker à +4 °C.
Le 30/6, on demande au labo de réaliser un dosage des protéines.
On prélève dans le stock, le tube de 3 ml, on le décongèle, on fait la manipulation.
Reste dans le tube 2 ml. Pour un usage futur éventuel, on décide de répartir ce reliquat dans 4 tubes : celui d'origine et 3 nouveaux (c'est un « réaliquotage »). On restocke les 4 tubes à -20 °C.
Le 3/7, on demande au labo de réaliser une ELISA.
On prélève dans le stock 3 tubes de 1 ml à +4 °C.
Le 13/7, les résultats sont corrects. On décide d'éliminer le dernier tube de 1 ml stocké à +4 °C.
VI. L'encodage d'une entrée en stock▲
xxxxxxxxxxxxxxxxxxxIntégrer modif du code (contrôle pertinence N° de tube et présence Lot et stade)
La zone de liste modifiable cboLot
Sa propriété Contenu correspond à cette requête :
Deux colonnes, ce que confirme la propriété Nbre Colonnes.
Mais seule, la seconde sera visible dans le formulaire, car la propriété Largeurs Colonnes stipule une largeur de 0 cm pour la première !
Par contre, Access attribuera la valeur de la 1re colonne (propriété Colonne liée = 1)
En d'autres mots, sur le formulaire on voit le contenu de la 2e colonne (l'appellation : 12345), mais le champ vaut tLotsPK : 1.
Faites l'expérience.
Affichez le formulaire et choisissez le lot « 789 ».
Ouvrez la fenêtre d'exécution (<CTRL + G>) et saisissez « ? Forms!fEncoEntree!cboLot » suivi de Enter
Traduction en français de cette instruction : « Affichez (?) le contrôle cboLot qui appartient au formulaire fEncoEntree dans la collection des formulaires actuellement ouverts (Forms) ».
Et Access répond « 2 ».
La propriété Limiter à liste est positionnée à Oui, cela entraîne que l'utilisateur pourra seulement choisir des Lots qui ont été définis a priori. (Intégrité référentielle oblige !)
Pour se documenter sur les propriétés d'un formulaire ou d'un état, ou de leurs contrôles :
- afficher l'objet en mode création ;
- cliquer sur la propriété, elle se met alors en surbrillance ;
- enfoncer la touche <F1>.
L'aide Access s'ouvre à la bonne page.
On peut aussi :
- ouvrir l'aide <F1>, choisir l'onglet « Aide intuitive » et suivre les instructions ;
- ouvrir la fenêtre d'exécution (<Ctrl> + G), saisir un mot-clé, y placer le curseur de la souris et presser <F1>. Idem dans le code d'un module.
Un clic sur ce bouton ouvrira le formulaire pour encoder un nouveau Lot si nécessaire.
Affichez les propriétés du bouton BtLot, onglet Événement et cliquez sur
Une nouvelle fenêtre affiche le module du formulaire (le VBA qui lui est associé)
Private
Sub
BtLot_Click
(
)
DoCmd.OpenForm
"fLots"
End
Sub
On opère un contrôle de cohérence : la date de fabrication ne peut pas être postérieure à la date d'entrée.
Si vous suivez le processus décrit au point vous voyez le code associé à l'événement « Avant mise à jour »
Private
Sub
txtDateEntree_BeforeUpdate
(
Cancel As
Integer
)
If
Me.txtDateFabrication
>
Me.txtDateEntree
Then
MsgBox
"La date de fabrication doit être antérieure ou égale à la date d'entrée !"
, vbCritical
Cancel =
True
Me.Undo
End
If
End
Sub
Intuitivement, vous devriez deviner ce qui se cache derrière ce mauvais anglais, voici le résultat si la règle n'est pas respectée :
… et l'utilisateur ne pourra plus rien faire tant qu'une date plausible ne soit encodée.
Un clic sur ce bouton permet d'afficher les tubes déjà en stock pour ce lot (fTubesDuLot)
Le code associé au bouton est un peu plus élaboré :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
Private
Sub
BtVoir_Click
(
)
'Vérifier qu'un lot est désigné
If
IsNull
(
Me.cboLot
) Then
MsgBox
"Vous n'avez pas désigné un N° de lot"
, vbCritical
Exit
Sub
End
If
'Fermer fTubesDuLot
If
CurrentProject.AllForms
(
"fTubesDuLot"
).IsLoaded
Then
DoCmd.Close
acForm, "fTubesDuLot"
'routine Arkham
DoCmd.OpenForm
"fTubesDuLot"
, , , , , acHidden, Me.Name
PositionForm Forms
(
"fTubesDuLot"
), Me.BtVoir
End
Sub
Commentaire du code
2-6 : on réagit si l'utilisateur demande la liste des tubes, mais qu'il n'a pas encore spécifié le lot.
8 : on s'assure que le formulaire fTubesDuLot, n'est pas déjà ouvert. Et le cas échéant, on le ferme.
10-11 : on utilise ici une procédure écrite par Arkham46 et qui permet d'ouvrir un formulaire à proximité d'un contrôle du formulaire appelant.
Faites l'expérience : déplacez le formulaire fEncoEntree et cliquez sur le bouton. Quelle que soit la position de fEncoEntree, le formulaire fTubesDuLot s'ouvre à proximité du bouton (juste en dessous ou juste au-dessus selon la place disponible à l'écran).
Nous utilisons le code tel quel : on ne comprend pas tout, mais on est sûr que ça marche ! (Comme quand on conduit une voiture, sans trop comprendre comment fonctionne le carburateur.)
Si le sujet vous passionne, une autre autopub : Positionner un formulaire par rapport au contrôle d'un autre formulaire.
Ici, il va falloir s'accrocher ! Le but, c'est
1° quand l'utilisateur a encodé un tube, un clic sur ce bouton va proposer l'encodage du tube suivant en copiant la quantité, le lieu et le N° de tube suivant ;
2° la hauteur du formulaire va s'adapter en conséquence.
Expliquons d'abord comment ça marche pour proposer les données du suivant
En tête de module, on définit trois variables. Elles seront visibles (c.-à-d. on pourra utiliser leur contenu) tant que le formulaire reste ouvert.
Chaque fois que l'utilisateur met à jour le N°, la quantité ou le lieu, on copie les valeurs dans les variables respectives. Ce sera nécessairement le cas pour le premier enregistrement et éventuellement dans l'un ou l'autre des suivants. (Lorsque l'utilisateur décide de modifier une valeur proposée.)
Quand on clique sur le bouton « Copier pour suivant »,
on crée un nouvel enregistrement,
on ajoute une unité au contenu de la variable
et on copie le contenu des variables dans les contrôles respectifs.
Pour adapter la hauteur du formulaire
On l'ouvre à bonne dimension pour faire apparaître une première ligne vierge
… et quand on ajoute une ligne, on augmente d'une hauteur de section détail sans toucher au reste
Quand le dernier tube de la série est encodé, un clic sur ce bouton déclenche la comptabilisation de l'entrée.
Voici le code associé au bouton :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
Private
Sub
BtEnregistrer_Click
(
)
Me.Refresh
DoCmd.SetWarnings
False
DoCmd.OpenQuery
"rEncoEntree"
DoCmd.SetWarnings
True
'Réinitialiser le processus pour une autre entrée éventuelle
Me.cboLot
=
Null
Me.txtDateFabrication
=
Null
Me.txtStade
=
Null
Call
Form_Open
(
0
)
End
Sub
On exécute donc cette requête :
N.B. Access n'accepte pas une syntaxe du genre
Formulaires!fEncoEntree!cboLot
en tant que champ (sur la 1re ligne) par contre, il accepte n'importe quelle fonction.
Qu'à cela ne tienne, nous contournons le problème avec une fonction passe-partout qui donne l'équivalent.
Public
Function
ValForm
(
NomDuFormulaire As
String
, NomDuChamp As
String
) As
Variant
On
Error
GoTo
GestionErreur
ValForm =
Forms
(
NomDuFormulaire)(
NomDuChamp)
Exit
Function
GestionErreur
:
ValForm =
Null
End
Function
VII. Un formulaire pour faire une recherche multicritère▲
VII-A. La technique▲
Elle est décrite dans ce tutoriel Formulaire de recherche sur la base d'une requête
VII-B. Particularité : filtrer avec un groupe d'options▲
Il s'agit de vouloir montrer ou cacher les tubes vides. (Ceux-ci ont une contenance de 0 ml.)
Puisque la valeur par défaut du groupe est 0, à l'ouverture du formulaire, c'est l'option opCacher qui est sélectionnée. Si l'utilisateur coche l'option opMontrer, le groupe prend la valeur -1.
La requête rRecherche réagit comme ceci
VII-C. Commentaires du code▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
Option
Compare Database
Option
Explicit
Public
Sub
TubeSelectFaux
(
)
'Réinitialiser [TubeSelect]
DoCmd.SetWarnings
False
DoCmd.RunSQL
"UPDATE tTubes SET tTubes.TubeSelect =False;"
DoCmd.SetWarnings
True
End
Sub
Private
Sub
Form_Open
(
Cancel As
Integer
)
DoCmd.MoveSize
100
, 100
Call
TubeSelectFaux
End
Sub
Private
Sub
FiltreLot_AfterUpdate
(
)
Call
TubeSelectFaux
'Réactualiser le choix du stade
Me.FiltreStade
=
Null
Me.FiltreStade.RowSource
=
Me.FiltreStade.RowSource
Me.Requery
Me.FiltreStade.SetFocus
Me.FiltreStade.Dropdown
End
Sub
Private
Sub
FiltreStade_AfterUpdate
(
)
Call
TubeSelectFaux
Me.Requery
End
Sub
Private
Sub
BtManip_Click
(
)
Dim
NbreA0 As
Integer
Dim
Reponse As
Integer
'Vérifier que la sélection concerne le même lot au même stade
If
IsNull
(
Me.FiltreLot
) Or
IsNull
(
Me.FiltreStade
) Then
MsgBox
"Vous devez d'abord choisir un lot et un stade !"
, vbCritical
If
IsNull
(
Me.FiltreLot
) Then
Me.FiltreLot.SetFocus
Me.FiltreLot.Dropdown
Else
Me.FiltreStade.SetFocus
Me.FiltreStade.Dropdown
End
If
Exit
Sub
End
If
'Vérifier qu'un choix a été opéré
If
DCount
(
"*"
, "rRecherche"
, "TubeSelect=True"
) =
0
Then
MsgBox
"Vous devez d'abord cocher des cases"
, vbCritical
Exit
Sub
End
If
'Signaler la présence de tubes vides
NbreA0 =
DCount
(
"*"
, "rRecherche"
, "TubeSelect=True And ContenanceActuelle=0"
)
If
NbreA0 <>
0
Then
Reponse =
MsgBox
(
"Votre sélection comporte "
&
NbreA0 &
" tube(s) vide(s), voulez-vous continuer ?"
, _
vbDefaultButton2
+
vbInformation
+
vbOKCancel
)
If
Reponse =
vbCancel
Then
Exit
Sub
End
If
End
If
'Ouvrir fManip
DoCmd.OpenForm
"fManip"
End
Sub
Private
Sub
cadreContenances_AfterUpdate
(
)
Me.Requery
End
Sub
Private
Sub
btTout_Click
(
)
Dim
ctl As
Control
For
Each
ctl In
Me.Controls
If
ctl.Name
Like "Filtre*"
Then
ctl =
Null
Next
ctl
Me.cadreContenances
=
-
1
Me.Requery
End
Sub
Private
Sub
bTubeSelect_AfterUpdate
(
)
Me.Requery
End
Sub
4-9 : une routine pour réinitialiser à « Non » le drapeau « TubeSelect ». Elle sera appelée à plusieurs reprises dans la suite du code (Call TubeSelectFaux).
Ce code équivaut à exécuter cette requête :
11-14 : ce code est associé à l'événement « Sur ouverture » du formulaire.
L'instruction 12 vise à positionner le formulaire en haut à droite. Le but : organiser l'écran pour pouvoir afficher simultanément les formulaires fRecherche, fManip et fRealiquot.
Puisque les paramètres 3 et 4 de DoCmd.MoveSize 100, 100 sont omis, la largeur et la hauteur seront celles que le formulaire présentait lors de sa fermeture précédente.
16-29 : chaque fois que l'utilisateur modifiera la sélection du lot ou celle du stade, le drapeau TubeSelect est réinitialisé (17 et 27).
Le choix d'un lot provoque le réaménagement du choix des stades possibles (20)
31-62 : ce code est associé au clic sur le bouton « Manipuler la sélection ».
De 35 à 45, on s'assure que les contrôles FiltreLot et FiltreStade ont été complétés. Sinon, on invite l'utilisateur à le faire et on interrompt la procédure.
De 47 à 50, on vérifie que l'utilisateur a sélectionné des tubes à traiter.
De 52 à 59, si des tubes actuellement vides ont été sélectionnés, on le signale et on invite l'utilisateur à confirmer le choix.
En 61, tout semble OK, on ouvre le formulaire fManip.
65-fin : code relatif au fonctionnement des filtres. (Voir le tutoriel déjà signalé.)
VIII. Le formulaire fManip▲
VIII-A. Présentation et fonctionnement▲
Ce formulaire n'est pas conçu pour fonctionner de manière autonome.
Il est censé s'ouvrir au clic sur le bouton « Manipuler la sélection » de fRecherche. Ce dernier doit obligatoirement rester ouvert toute la durée d'utilisation.
Sa source est la requête rRecherche, mais en ne retenant que les enregistrements avec TubeSelect = OUI(c'est-à-dire les tubes que l'utilisateur a cochés dans fRecherche
L'utilisateur spécifie une manipulation en choisissant dans la liste déroulante.
Xxxxxxxxxxxxxxxxxparler ici de l'ajout d'une manip
À ce stade, de deux choses l'une :
- ou bien la manipulation va consommer tout le liquide ;
- ou bien il subsistera un reliquat qui sera remis en stock après un éventuel réaliquotage.
Si le reliquat est zéro, un clic sur le bouton « Comptabiliser la manipulation » déclenchera la mise à jour et c'est terminé.
S'il y a un reliquat, l'utilisateur en indique la quantité ce qui déclenche l'ouverture d'un autre formulaire (fRealiquot) qui permettra de décrire le sort réservé au produit restant.
VIII-B. Le code et ses commentaires▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
Option
Compare Database
Option
Explicit
Private
Sub
Form_Open
(
Cancel As
Integer
)
Dim
hauteur As
Integer
hauteur =
Me.Section
(
acHeader).Height
+
Me.Section
(
acDetail).Height
*
Me.Recordset.recordcount
+
650
'bordures
DoCmd.MoveSize
3000
, 1750
, 6600
, hauteur
End
Sub
Private
Sub
TxtReliquat_BeforeUpdate
(
Cancel As
Integer
)
'Vérifier que le reliquat n'est pas négatif
If
Me.TxtReliquat
<
0
Then
MsgBox
"Le reliquat est négatif !"
, vbCritical
Cancel =
True
Me.Undo
Exit
Sub
End
If
'Vérifier que le reliquat n'est pas supérieur à la somme des contenus
If
Me.TxtReliquat
>
DSum
(
"ContenanceActuelle"
, "rRecherche"
, "TubeSelect= yes"
) Then
MsgBox
"Le reliquat est supérieur au contenu !"
, vbCritical
Cancel =
True
Me.Undo
End
If
End
Sub
Private
Sub
TxtReliquat_AfterUpdate
(
)
If
TxtReliquat =
0
Then
Me.BtCompta.Visible
=
True
'FermerfRealiquot s'il est ouvert
If
CurrentProject.AllForms
(
"fRealiquot"
).IsLoaded
Then
DoCmd.Close
acForm, "fRealiquot"
End
If
Else
Me.BtCompta.Visible
=
False
'Lancer la procédure de réaliquotage
'-----------------------------------
'Vérifier la présence d'une cause
If
IsNull
(
Me.cboCause
) Then
MsgBox
"La cause de la manipulation manque !"
, vbCritical
Me.cboCause.SetFocus
Me.cboCause.Dropdown
Exit
Sub
End
If
'Créer les enregistrements de fRealiquot
DoCmd.SetWarnings
False
DoCmd.RunSQL
"DELETE * FROM tRealiquot;"
'pour la vider
DoCmd.OpenQuery
"rRealiquot"
' pour la remplir à nouveau
DoCmd.SetWarnings
True
'Fermer fRealiquot s'il était déjà ouvert
If
CurrentProject.AllForms
(
"fRealiquot"
).IsLoaded
Then
DoCmd.Close
acForm, "fRealiquot"
'routine Arkham
DoCmd.OpenForm
"fRealiquot"
, , , , , acHidden
PositionForm Forms
(
"fRealiquot"
), Me.TxtReliquat
End
If
End
Sub
Private
Sub
BtCompta_Click
(
)
'Vérifier qu'une cause de manipulation a été choisie
If
IsNull
(
Me.cboCause
) Then
MsgBox
"Une cause de manipulation doit être spécifiée !"
, vbCritical
Me.cboCause.SetFocus
Me.cboCause.Dropdown
Exit
Sub
End
If
DoCmd.SetWarnings
False
DoCmd.OpenQuery
"rComptaManip"
DoCmd.OpenQuery
"rComptaDecongel"
DoCmd.SetWarnings
True
If
CurrentProject.AllForms
(
"fRecherche"
).IsLoaded
Then
DoCmd.Close
acForm, "fRecherche"
End
If
DoCmd.Close
acForm, Me.Name
End
Sub
4-8 : à l'ouverture du formulaire, on calcule sa hauteur totale : elle sera égale à la hauteur de la section En-tête + autant de fois la hauteur de la section Détail qu'il y a d'enregistrements à afficher + 650 twips pour compter les bordures supérieure et inférieure.
10-26 : si l'utilisateur tente d'indiquer un reliquat, on vérifie la pertinence avant de l'acter. Le reliquat ne peut être négatif ni supérieur au contenu d'origine.
28-57 : si le reliquat est modifié, la réaction est différente selon que le reliquat est égal à zéro ou positif.
29-34 : si le reliquat est égal à zéro, on rend visible le bouton « Comptabiliser la manipulation » (30) et s'il est ouvert, on referme le formulaire fRealiquot (32-34). (fRealiquot serait ouvert par exemple, si l'utilisateur a d'abord inscrit un reliquat positif et qu'ensuite, il se ravise et inscrit zéro.)
36-55 : si le reliquat est positif, la suite va se dérouler dans la procédure de réaliquotage. On cache le bouton « Comptabiliser la manipulation » (36), on vérifie ensuite que l'utilisateur a mentionné le type de manipulation (40-45) et on prépare la table tRealiquot, source de fRealiquot.
On la vide de ses enregistrements précédents éventuels (48) et on y loge les tubes qui interviennent dans la manipulation en cours. L'instruction 49 exécute la requête rRealiquot :
On ajoute donc dans la tRealiquot autant de lignes que de tubes dans la manipulation, avec zéro comme quantité.
51-57 : on déclenche l'ouverture du formulaire fRealiquot juste en dessous du montant du reliquat.
59-75 : au clic sur le bouton « Comptabiliser la manipulation », on s'assure que l'utilisateur a spécifié le type de manipulation (61-65).
Si OK, on exécute la requête rComptaManip (68)
On ajoute donc dans la tPrelevements, pour chaque tube manipulé, un enregistrement qui va amener sa contenance actuelle à zéro (puisque le prélèvement ajouté est égal à la contenance avant manip).
Dans la foulée, on exécute la requête rComptaDecongel (69)
Fin du processus à reliquat zéro, on ferme les formulaires (71-74).
IX. Le formulaire fRealiquot▲
IX-A. Présentation et fonctionnement▲
IX-B. ▲
Tout comme le précédent, ce formulaire n'est pas conçu pour fonctionner de manière autonome.
Il est censé s'ouvrir lorsque l'utilisateur inscrit un reliquat positif dans fManip. Ce dernier doit obligatoirement rester ouvert toute la durée d'utilisation.
Sa source est la table tRealiquot.
Le fait d'indiquer un reliquat dans fManip a prérempli cette table. (Voir les instructions 47 à 50 du code de fManip.)
L'utilisateur corrige les données préremplies des tubes qui ont été manipulés.
S'il y a réaliquotage dans des nouveaux tubes supplémentaires, l'utilisateur clique sur « Copier pour suivant » et une nouvelle ligne est ajoutée et préremplie sur le modèle du dernier tube de la liste (avec N° +1, pour autant que ce N° soit admissible).
Quand le compte est bon, l'utilisateur clique sur « Enregistrer », pour comptabiliser l'ensemble des opérations et refermer les formulaires.
IX-C. Le code et ses commentaires▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
Option
Compare Database
Option
Explicit
Dim
Numero As
Integer
Dim
Quantite As
Single
Dim
Lieu As
Long
Private
Function
VeriPertiNum
(
) As
Boolean
'Vérifier que ce N° n'est pas déjà attribué à un tube du même lot et même stade
'sauf si c'est l'un des tubes de la manipulation
If
DLookup
(
"TubeNum"
, "tTubes"
, _
"TubeSelect=Yes And TubeNum = "
&
Me.txtRealiNum
) Then
'c'est un des tubes de la manip qui est remployé
VeriPertiNum =
True
Exit
Function
Else
If
DLookup
(
"TubeNum"
, "tTubes"
, "TubeNum = "
&
Me.txtRealiNum
_
&
" And tLotsFK ="
&
Forms!fRecherche!FiltreLot _
&
" And Stade='"
&
Forms!fRecherche!FiltreStade &
"'"
) Then
'Le N° est déjà utilisé dans ce contexte
VeriPertiNum =
False
Else
'Le N° est OK
VeriPertiNum =
True
End
If
End
If
End
Function
Private
Sub
Form_Open
(
Cancel As
Integer
)
Dim
hauteur As
Integer
hauteur =
Me.Section
(
acHeader).Height
+
Me.Section
(
acDetail).Height
*
Me.Recordset.recordcount
+
Me.Section
(
acFooter).Height
+
650
'bordures
DoCmd.MoveSize
, , , hauteur
End
Sub
Private
Sub
Form_Close
(
)
'Purger la table
DoCmd.SetWarnings
False
DoCmd.RunSQL
" Delete * from tRealiquot;"
DoCmd.SetWarnings
True
End
Sub
Private
Sub
txtRealiNum_BeforeUpdate
(
Cancel As
Integer
)
'Vérifier la pertinence du N°
If
VeriPertiNum =
False
Then
MsgBox
"Ce N° est déjà affecté à un autre tube de ce lot/stade !"
, vbCritical
Cancel =
True
Me.Undo
End
If
End
Sub
Private
Sub
BtSuivant_Click
(
)
On
Error
GoTo
GestionErreurs
' Dim Position As rect
Me.Refresh
'Se positionner sur le dernier tube pour le prendre comme modèle
DoCmd.GoToRecord
, , acLast
'Rafraîchir la valeur des variables
Numero =
Me.txtRealiNum
Quantite =
Me.txtRealiContenance
Lieu =
Me.cboCasier
'Passer à l'enregistrement suivant
DoCmd.GoToRecord
, , acNext
'Préremplir les contrôles de cet enregistrement
Numero =
Numero +
1
Me.txtRealiNum
=
Numero
Me.txtRealiContenance
=
Quantite
Me.cboCasier
=
Lieu
'Vérifier que ce N° est acceptable
If
VeriPertiNum =
False
Then
MsgBox
Me.txtRealiNum
&
" ferait double-emploi, veuillez spécifier un autre N° pour ce tube."
Me.txtRealiNum
=
Null
End
If
'Ajuster la hauteur du formulaire pour laisser voir cette nouvelle ligne
DoCmd.MoveSize
, , , Me.WindowHeight
+
Me.Section
(
"Détail"
).Height
Exit
Sub
GestionErreurs
:
MsgBox
"Une anomalie empêche d'accepter un tel tube. Par exemple, une donnée manquante ou causant double-emploi."
, vbCritical
End
Sub
Private
Sub
BtEnregistrer_Click
(
)
Me.Refresh
'Vérifier la cohérence entre reliquat et contenances réaliquotés
If
Forms!fManip!TxtReliquat <>
DSum
(
"RealiContenance"
, "tRealiquot"
) Then
MsgBox
"Il y a discordance entre le reliquat : "
&
Forms!fManip!TxtReliquat _
&
" et la somme des contenances réaliquotées : "
&
DSum
(
"RealiContenance"
, "tRealiquot"
) _
&
" !"
, vbExclamation
Exit
Sub
End
If
'Comptabiliser la manipulation
DoCmd.SetWarnings
False
DoCmd.OpenQuery
"rComptaManip"
DoCmd.OpenQuery
"rComptaDecongel"
'comptabiliser le réaliquotage
DoCmd.OpenQuery
"rComptaRealiquot"
'Désélectionner (remettre TubeSelect à la valeur Faux
DoCmd.RunSQL
"UPDATE tTubes SET TubeSelect = False;"
DoCmd.SetWarnings
True
'Fermer les formulaires
DoCmd.Close
acForm, "fManip"
DoCmd.Close
acForm, Me.Name
If
CurrentProject.AllForms
(
"fRecherche"
).IsLoaded
Then
DoCmd.Close
acForm, "fRecherche"
End
If
End
Sub
Private
Sub
BtVoir_Click
(
)
'Vérifier que fManip est ouvert
If
Not
CurrentProject.AllForms
(
"fManip"
).IsLoaded
Then
MsgBox
"Le formulaire fManip n'est pas ouvert"
, vbCritical
Exit
Sub
End
If
'Fermer fTubesDuLot
If
CurrentProject.AllForms
(
"fTubesDuLot"
).IsLoaded
Then
DoCmd.Close
acForm, "fTubesDuLot"
'Routine Arkham pour ouvrir à proximité du bouton
DoCmd.OpenForm
"fTubesDuLot"
, , , , , acHidden, Me.Name
PositionForm Forms
(
"fTubesDuLot"
), Me.BtVoir
End
Sub
4-6 : on définit les trois variables qui serviront au préremplissage des contrôles du « suivant ».
Puisque ces variables sont définies en tête du module du formulaire, elles seront visibles dans l'environnement du formulaire tant qu'il restera ouvert.
8-26 : une fonction qui va vérifier la pertinence d'un N° de tube attribué. En l'occurrence, si on veut éviter toute confusion dans l'inventaire du stock, le N° du tube doit être l'un des N° des tubes entrant dans la manipulation ou alors un N° qui n'a pas déjà été attribué à un autre tube du lot/stade.
En d'autres mots, on doit empêcher que deux tubes d'un même lot/stade portent le même N°.
Dans le reste du code, chaque fois qu'un N° de tube sera attribué par l'utilisateur ou proposé par le système, la fonction sera appelée pour vérifier que ce N° respecte la règle.
VeriPertiNum = True quand la règle est respectée, VeriPertiNum = False quand elle est violée.
Remarquez que la table tRealiquot empêche déjà de spécifier deux N° de tube identiques
Ceci permet de rattraper une lacune de VeriPertinum : si l'utilisateur introduit par mégarde deux fois un même N° « acceptable », VeriPertiNum ne réagira pas. Mais cet encodage erroné sera arrêté au moment de l'enregistrement. (Voir instruction 78 ci-après.)
11-15 : on utilise la fonction de domaine DLookup pour trouver dans la table tTubes un enregistrement sélectionné (TubeSlect = Oui, puisque l'utilisateur l'a sélectionné dans fRecherche) qui porte un N° identique à celui inscrit dans fRealiquot.
Piqûre de rappel
Pour vous documenter sur DLookup, dans le texte du code, cliquez sur « Dlookup » et enfoncez la touche <F1>.
17-27 : ici, on vérifie si le N° n'est pas déjà présent dans les tubes du même Lot/Stade.
29-33 : à l'ouverture du formulaire, on fixe sa hauteur pour laisser apparaître les lignes utiles.
On ajoute la hauteur des sections En-tête et Pied, et autant de fois la section Détail qu'il y a d'enregistrements. Plus une rawette(1) de 650 twips pour les deux bordures.
35-40 : à la fermeture du formulaire, on vidange sa table source tRealiquot. (On fait place nette pour le réaliquotage suivant.)
42-49 : on vérifie ici la pertinence d'un N° de tube attribué par l'utilisateur.
50-78 : au clic sur « Copier pour suivant », on fait apparaître une ligne supplémentaire préremplie de quantité, lieu et N°+1 de l'actuel dernier tube de la liste.
56 : on se positionne sur l'actuel dernier enregistrement (pour accéder à ses données).
58-60 : on mémorise dans les variables Numero, Quantite et Lieu les valeurs retenues pour ce dernier.
62 : on se positionne sur un nouvel enregistrement.
65-72 : on propose le N°+1, la quantité et le lieu. On passe la main à l'utilisateur si on constate que ce N° n'est pas pertinent. Le processus est interrompu tant que l'utilisateur n'a pas saisi un N° acceptable.
74 : on ajuste la taille pour montrer la nouvelle ligne.
77-78 : le système réagit si l'utilisateur a introduit deux fois le même N° de tube, ou s'il essaie de passer au suivant alors que la ligne n'est pas complète.
80-104 : le clic sur « Enregistrer » va déclencher la comptabilisation de la manipulation et du réaliquotage.
83-88 : on vérifie que la contenance des tubes à restocker correspond au reliquat annoncé.
91-92 : on comptabilise la manipulation et les décongélations (voir instructions 68-69 de fManip).
94 : on comptabilise le réaliquotage
Les tubes renseignés avec une quantité 0, ne sont pas pris en compte.
Les tubes restockés recevront un nouveau tTubesPK.
Pour illustrer le mécanisme par un exemple,
si on part d'un tube N° 3 avec un tTubesPK = 2 d'une quantité de 50 ml et
qu'on le restocke après manipulation avec 12 ml,
on aura dans tTubes :
- N° 3 (tTubesPK =2) - Quantité 50
- N° 3 (tTubesPK =X) - Quantité 12 (X étant le NuméroAuto attribué par Access)
et dans tPrelevements :
- tTubesPK = 2 - Prélèvement 50
96-104 : on termine le processus en refermant les formulaires.
106-117 : pour le clic sur le bouton « Voir les tubes de ce lot déjà en stock », le code est identique à celui décrit plus haut pour le formulaire fEncoEntree.
X. Le formulaire fTubesDuLot▲
Ce formulaire est censé être ouvert tantôt au départ de fEncoEntree, tantôt au départ fRealiquot
X-A. La source ▲
On va créer cette requête à la volée, à l'ouverture du formulaire.
Comment déterminer à quel formulaire, il faut se référer (soit fEncoEntree, soit fRealiquot) ?
X-B. La propriété OpenArgs ▲
Rappelons-nous comment l'ouverture est déclenchée
On ouvre donc le formulaire fTubesDuLot, en lui envoyant un argument d'ouverture (OpenArgs). Selon le cas : la chaîne de caractères « fEncoEntre » ou « fRealiquot ».
C'est l'occasion d'aller faire un tour dans l'aide Access : voir ce qu'on raconte à l'article DoCmd.openForm et OpenArgs
X-C. Le code à l'ouverture▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
Option
Compare Database
Option
Explicit
Private
Sub
Form_Open
(
Cancel As
Integer
)
Dim
sClauseSelect As
String
Dim
sClauseOrder As
String
'Créer les clauses Select et Order By communes aux appelants
sClauseSelect =
"SELECT tTubes.TubeNum, tTubes.Stade, tTubes.TubeDateEntree, "
_
&
"[LieuNom] & "" "" & [CasierNom] AS [Local] "
_
&
"FROM tLots INNER JOIN (tLieuxStock "
_
&
"INNER JOIN (tCasiers "
_
&
"INNER JOIN tTubes "
_
&
"ON tCasiers.tCasiersPK = tTubes.tCasiersFK) "
_
&
"ON tLieuxStock.tLieuxStockPK = tCasiers.tLieuxStockFK) "
_
&
"ON tLots.tLotsPK = tTubes.tLotsFK "
sClauseOrder =
"ORDER BY tTubes.Stade, tTubes.TubeNum;"
'Confection de la source et du titre selon les appelants (OpenArgs)
Select
Case
Me.OpenArgs
Case
"fEncoEntree"
Me.RecordSource
=
sClauseSelect _
&
"WHERE (((tLots.tLotsPK)="
&
Forms!fEncoEntree!cboLot &
")) "
_
&
sClauseOrder
Me.txtTitre
=
"Tubes déjà en stock pour le lot "
&
Forms!fEncoEntree!cboLot.Column
(
1
)
Case
"fRealiquot"
Me.RecordSource
=
sClauseSelect _
&
"WHERE (((tLots.tLotsPK)="
&
Forms!fManip!TXTtLotsPK &
"))"
_
&
sClauseOrder
Me.txtTitre
=
"Tubes déjà en stock pour le lot "
&
Forms!fManip!txtLotAppellation
End
Select
End
Sub
XI. Le formulaire fModif▲
On doit en rediscuter.
Ce formulaire est uniquement basé sur tTubes => quid des manipulations.
Pour changer quoi ? Sauf si elle porte uniquement sur le lieu de stockage, une modification brutale risque d'introduire des incohérences.
Pour se documenter sur la construction d'un état avec Access, voyez ce tutoriel de Jean Ballat : Description de la création d'un état
XII. L'état eInventaire▲
XII-A. But▲
Disposer d'une liste du contenu des lieux de stockage pour vérifier que l'inventaire selon l'application représente bien la réalité « physique » des tubes sur le terrain.
XII-B. La source▲
XII-C. La mise en forme conditionnelle des contrôles▲
Pour accéder à cette fonctionnalité :
- dans l'état en construction ;
- clic droit sur le contrôle à formater ;
- dans la barre des menus : Format, Mise en forme conditionnelle…
Traduction française de l'expression : si la fonction Realiquote(2) répond « Vrai » pour ce tube, allumer en jaune ce contrôle.
Astuce : tous les contrôles se touchent et ont la même mise en forme conditionnelle, cela donnera l'illusion que la ligne change de couleur lorsqu'il s'agit d'un tube réaliquoté.
XIII. L'état eHistoire▲
XIII-A. But▲
En quelque sorte, le bilan d'un couple Lot/Stade. On y voit :
- les tubes reçus au départ ;
- ce qu'on en a fait ;
- ce qu'il en reste et où ils sont.
XIII-B. Le mécanisme▲
Il s'agit en fait de quatre états : un père (la surface non colorée) et trois fils (en couleur).
Si la technique des formulaires ou états pères/fils ne vous est pas familière, une autre autopub : Comment classer les données dans des tables liées et construire un formulaire père/fils.
La source du formulaire père (eHistoire) est très rudimentaire
Elle sert à amorcer la pompe pour que les fils affichent chacun leur détail
Dans le formulaire eHistoire, les trois conteneurs des sous-états ont les propriétés « Champs fils » et « Champs pères » suivantes.
Cela suffit à synchroniser le père et les fils.
XIV. Le module standard mFonctions▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
Option
Compare Database
Option
Explicit
Public
Function
ContenanceActu
(
TubePk As
Long
) As
Single
'Recherche contenance initiale
ContenanceActu =
DLookup
(
"TubeContini"
, "tTubes"
, "tTubesPK ="
&
TubePk)
'Soustraire la somme de prélèvements
ContenanceActu =
ContenanceActu -
Nz
(
DSum
(
"PrelevQuantite"
, "tPrelevements"
, "tTubesFK="
&
TubePk), 0
)
End
Function
Public
Function
ValForm
(
NomDuFormulaire As
String
, NomDuChamp As
String
) As
Variant
On
Error
GoTo
GestionErreur
ValForm =
Forms
(
NomDuFormulaire)(
NomDuChamp)
Exit
Function
GestionErreur
:
ValForm =
Null
End
Function
Public
Function
Realiquote
(
idTube As
Long
) As
Boolean
Dim
rst As
DAO.Recordset
'Sélectionner l'enregistrement pour accéder à ses données
Set
rst =
CurrentDb.OpenRecordset
(
"Select * From tTubes Where tTubesPk="
&
idTube &
";"
)
'Vérifier qu'on a bien 1 enregistrement pour ce idTube
If
rst.RecordCount
<>
1
Then
MsgBox
"Le tTubesPK N° "
&
idTube &
" n'existe pas !"
, vbCritical
Exit
Function
End
If
'Analyse des données
If
rst
(
"TubeRecu"
) =
-
1
Then
Exit
Function
'c'est un tube d'origine => Realiquote = Faux
Else
If
DSum
(
"TubeRecu"
, "tTubes"
, "TubeNum ="
&
rst
(
"TubeNum"
) _
&
" And tLotsFK ="
&
rst
(
"tLotsFK"
) _
&
" And Stade='"
&
rst
(
"Stade"
) &
"'"
) =
0
Then
' => réaliquoté
Realiquote =
True
Else
Exit
Function
End
If
End
If
rst.Close
Set
rst =
Nothing
End
Function
XIV-A. La fonction ContenanceActu▲
XIV-A-1. But▲
Connaissant la clé d'un tube (tTubesPK), obtenir la contenance actuelle.
XIV-A-2. Processus▲
On va d'abord chercher la contenance initiale du tube (la colonne TubeContini dans tTubes) et ensuite défalquer la somme des prélèvements opérés sur ce tube (les enregistrements de ce tube dans tPrelevements.
XIV-A-3. Instructions▲
6 : un Dlookup() dans tTubes.
8 : un Dsum() dans tPrelevements encadré de Nz() pour remplacer par zéro le résultat Null obtenu s'il n'y a pas eu de prélèvement pour ce tube.
XIV-B. La fonction ValForm▲
XIV-B-1. But▲
Solution de remplacement, lorsque Access refuse la syntaxe Formulaires!NomDuFormulaire!NomDuControle, par exemple en tant que valeur d'un champ dans une requête :
XIV-B-2. Processus▲
Simplement lire la valeur dans le formulaire et la restituer.
XIV-B-3. Instructions▲
16 : la gestion de l'erreur qui pourrait survenir en raison d'une orthographe fautive ou du formulaire fermé.
XIV-C. La fonction Realiquote▲
XIV-C-1. But▲
Connaissant la clé d'un tube (tTubesPK) savoir si c'est un tube réaliquoté (un tube autre que ceux reçus du client, dans lequel on a transvasé une partie d'un tube d'origine).
XIV-C-2. Processus▲
S'il s'agit d'un tube entré via fEncoEntree, la colonne « TubeRecu » de tTubes vaut -1(3).
Si c'est le cas, ce n'est pas un tube réaliquoté.
Si « TubeRecu » = 0, c'est peut-être un tube d'origine qui contient le reliquat d'une manipulation. Si c'est le cas, il y a pour ce lot/stade un autre tube portant le même N° (TubeNum) et qui lui a « TubeRecu » = -1.
L'algorithme consiste à dire, si un tube a « TubeRecu » = 0, voyons la somme des « TubeRecu » pour tous les tubes du couple Lot/Stade qui portent le même TubeNul. Si la somme est -1, il s'agit d'un tube d'origine. Sinon il est réaliquoté.
Le tTubesPK 16 est un tube d'origine, car la somme des TubeRecu pour tous les tubes N° 6 du lot 12345/Stade Final vaut -1.
XIV-C-3. Instructions▲
20 : on définit une variable pour y loger un jeu d'enregistrements (RecordSet).
22 : on crée le jeu. En fait, ici il n'y aura qu'un seul enregistrement (tTubesPK est unique puisque c'est la clé !).
24-27 : on traite l'anomalie éventuelle (idTube erroné par exemple).
29-38 : on procède à l'analyse décrite plus haut.
40-41 : on sort proprement.