Gestion de stock d'échantillons

Un prétexte pour apprendre à utiliser Access

Article en construction !

En relation avec cette Q/R du forum DVP : http://www.developpez.net/forums/d1465509/logiciels/microsoft-office/access/modelisation/gestion-stock-d-echantillons-debutant/

La démarche consiste à partir d'un besoin concret (qu'ai-je en stock et où se trouve l'objet ?) pour montrer ce qu'on peut faire « rapidement » avec Access.

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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 :

Access - Les Bases de Maxence HUBICHE

Créer des requêtes simples de Jean Ballat

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

Image non disponible

Toutes les relations sont du type 1 à plusieurs et appliquent l'intégrité référentielle

Image non disponible

Voir Comprendre les jointures dans Access de Maxence Hubiche.

IV. Un mot sur les tables

IV-A. tTubes

Image non disponible

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 :

Image non disponible

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 :

Comment positionner un formulaire à un endroit déterminé.

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)

Image non disponible

C:\MesDocuments\PrintScreen\&1.jpg La zone de liste modifiable cboLot

Image non disponible

Sa propriété Contenu correspond à cette requête :

Image non disponible

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

Image non disponible

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.

C:\MesDocuments\PrintScreen\&2.jpg 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 Image non disponible

Image non disponible

Une nouvelle fenêtre affiche le module du formulaire (le VBA qui lui est associé)

 
Sélectionnez
Private Sub BtLot_Click()
  DoCmd.OpenForm "fLots"
End Sub

C:\MesDocuments\PrintScreen\&3.jpg 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 C:\MesDocuments\PrintScreen\&2.jpg vous voyez le code associé à l'événement « Avant mise à jour » 

Image non disponible
 
Sélectionnez
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 :

Image non disponible

… et l'utilisateur ne pourra plus rien faire tant qu'une date plausible ne soit encodée.

C:\MesDocuments\PrintScreen\&4.jpgUn clic sur ce bouton permet d'afficher les tubes déjà en stock pour ce lot (fTubesDuLot)

Image non disponible

Le code associé au bouton est un peu plus élaboré :

 
Sélectionnez
1.
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  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).

Image non disponible

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.

C:\MesDocuments\PrintScreen\&5.jpgIci, 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.

Image non disponible

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.

Image non disponible

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

Image non disponible

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.

Image non disponible

Pour adapter la hauteur du formulaire

On l'ouvre à bonne dimension pour faire apparaître une première ligne vierge

Image non disponible

… et quand on ajoute une ligne, on augmente d'une hauteur de section détail sans toucher au reste

Image non disponible

C:\MesDocuments\PrintScreen\&6.jpg 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 :

 
Sélectionnez
1.
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 :

Image non disponible

N.B. Access n'accepte pas une syntaxe du genre

 
Sélectionnez
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.

 
Sélectionnez
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

Image non disponible

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

Image non disponible

Il s'agit de vouloir montrer ou cacher les tubes vides. (Ceux-ci ont une contenance de 0 ml.)

Image non disponible

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

Image non disponible

VII-C. Commentaires du code

 
Sélectionnez
1.
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 :

Image non disponible

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)

Image non disponible

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

Image non disponible

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

Image non disponible

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

 
Sélectionnez
1.
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 :

Image non disponible

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)

Image non disponible

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)

Image non disponible

Fin du processus à reliquat zéro, on ferme les formulaires (71-74).

IX. Le formulaire fRealiquot

IX-A. Présentation et fonctionnement

Image non disponible

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

 
Sélectionnez
1.
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'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  est déjà utilisé dans ce contexte
             VeriPertiNum = False
           Else
             'Le  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 
   If VeriPertiNum = False Then
       MsgBox "Ce  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  est acceptable
  If VeriPertiNum = False Then
      MsgBox Me.txtRealiNum & " ferait double-emploi, veuillez spécifier un autre  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

Image non disponible

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

Image non disponible

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.

Image non disponible

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

Image non disponible

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

Image non disponible

Ce formulaire est censé être ouvert tantôt au départ de fEncoEntree, tantôt au départ fRealiquot

Image non disponible

X-A. La source

Image non disponible
Image non disponible

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 Image non disponible

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

Image non disponible

X-C. Le code à l'ouverture

 
Sélectionnez
1.
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

Image non disponible

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

Image non disponible

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…

Image non disponible

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

Image non disponible

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

Image non disponible

Elle sert à amorcer la pompe pour que les fils affichent chacun leur détail

Image non disponible
Image non disponible
Image non disponible

Dans le formulaire eHistoire, les trois conteneurs des sous-états ont les propriétés « Champs fils » et « Champs pères » suivantes.

Image non disponible

Cela suffit à synchroniser le père et les fils.

XIV. Le module standard mFonctions

 
Sélectionnez
1.
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  " & 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 :

Image non disponible

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

Image non disponible

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.

XV. Téléchargement

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


J'assume ma belgitude !
Voir l'explication de cette fonction dans le chapitre Modules standard.
Dans Access, selon les circonstances « Vrai », « Oui », « True » et « -1 » sont synonymes). Idem pour « Faux », « Non », « False » et « 0 » (zéro).

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Claude Leloup. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.