Gestion de stock d'échantillons

Un prétexte pour apprendre à utiliser Access

Claude Leloup

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.

- Titre : Gestion de stock d'échantillons

- Auteur : Claude Leloup

- Parution : 9 septembre 2014

- Mise à jour : 12 janvier 2018

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

Sommaire

- I. Le pied à l'étrier

- II. Contexte de l'outil à mettre en place

- III. Le modèle de données

- IV. Un mot sur les tables

- IV-A. tTubes

- IV-B. tPrelevements

- IV-C. tEncodageEntree et tRealiquot

- V. Un scénario de test pour illustrer nos propos

- VI. L'encodage d'une entrée en stock

- VII. Un formulaire pour faire une recherche multicritère

- VII-A. La technique

- VII-B. Particularité : filtrer avec un groupe d'options

- VII-C. Commentaires du code

- VIII. Le formulaire fManip

- VIII-A. Présentation et fonctionnement

- VIII-B. Le code et ses commentaires

- IX. Le formulaire fRealiquot

- IX-A. Présentation et fonctionnement

- IX-B.

- IX-C. Le code et ses commentaires

- X. Le formulaire fTubesDuLot

- X-A. La source

- X-B. La propriété OpenArgs

- X-C. Le code à l'ouverture

- XI. Le formulaire fModif

- XII. L'état eInventaire

- XII-A. But

- XII-B. La source

- XII-C. La mise en forme conditionnelle des contrôles

- XIII. L'état eHistoire

- XIII-A. But

- XIII-B. Le mécanisme

- XIV. Le module standard mFonctions

- XIV-A. La fonction ContenanceActu

- XIV-A-1. But

- XIV-A-2. Processus

- XIV-A-3. Instructions

- XIV-B. La fonction ValForm

- XIV-B-1. But

- XIV-B-2. Processus

- XIV-B-3. Instructions

- XIV-C. La fonction Realiquote

- XIV-C-1. But

- XIV-C-2. Processus

- XIV-C-3. Instructions

- XV. Téléchargement

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

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 :

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)

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

1. Private Sub BtVoir_Click()

2. 'Vérifier qu'un lot est désigné

3. If IsNull(Me.cboLot) Then

4. MsgBox "Vous n'avez pas désigné un N° de lot", vbCritical

5. Exit Sub

6. End If

7. 'Fermer fTubesDuLot

8. If CurrentProject.AllForms("fTubesDuLot").IsLoaded Then DoCmd.Close acForm, "fTubesDuLot"

9. 'routine Arkham

10. DoCmd.OpenForm "fTubesDuLot", , , , , acHidden, Me.Name

11. PositionForm Forms("fTubesDuLot"), Me.BtVoir

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

1. Private Sub BtEnregistrer_Click()

2. Me.Refresh

3. DoCmd.SetWarnings False

4. DoCmd.OpenQuery "rEncoEntree"

5. DoCmd.SetWarnings True

6. 'Réinitialiser le processus pour une autre entrée éventuelle

7. Me.cboLot = Null

8. Me.txtDateFabrication = Null

9. Me.txtStade = Null

10. Call Form_Open(0)

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

1. Option Compare Database

2. Option Explicit

4. Public Sub TubeSelectFaux()

5. 'Réinitialiser [TubeSelect]

6. DoCmd.SetWarnings False

7. DoCmd.RunSQL "UPDATE tTubes SET tTubes.TubeSelect =False;"

8. DoCmd.SetWarnings True

9. End Sub

11. Private Sub Form_Open(Cancel As Integer)

12. DoCmd.MoveSize 100, 100

13. Call TubeSelectFaux

14. End Sub

16. Private Sub FiltreLot_AfterUpdate()

17. Call TubeSelectFaux

18. 'Réactualiser le choix du stade

19. Me.FiltreStade = Null

20. Me.FiltreStade.RowSource = Me.FiltreStade.RowSource

21. Me.Requery

22. Me.FiltreStade.SetFocus

23. Me.FiltreStade.Dropdown

24. End Sub

26. Private Sub FiltreStade_AfterUpdate()

27. Call TubeSelectFaux

28. Me.Requery

29. End Sub

31. Private Sub BtManip_Click()

32. Dim NbreA0 As Integer

33. Dim Reponse As Integer

34. 'Vérifier que la sélection concerne le même lot au même stade

35. If IsNull(Me.FiltreLot) Or IsNull(Me.FiltreStade) Then

36. MsgBox "Vous devez d'abord choisir un lot et un stade !", vbCritical

37. If IsNull(Me.FiltreLot) Then

38. Me.FiltreLot.SetFocus

39. Me.FiltreLot.Dropdown

40. Else

41. Me.FiltreStade.SetFocus

42. Me.FiltreStade.Dropdown

43. End If

44. Exit Sub

45. End If

46. 'Vérifier qu'un choix a été opéré

47. If DCount("*", "rRecherche", "TubeSelect=True") = 0 Then

48. MsgBox "Vous devez d'abord cocher des cases", vbCritical

49. Exit Sub

50. End If

51. 'Signaler la présence de tubes vides

52. NbreA0 = DCount("*", "rRecherche", "TubeSelect=True And ContenanceActuelle=0")

53. If NbreA0 <> 0 Then

54. Reponse = MsgBox("Votre sélection comporte " & NbreA0 & " tube(s) vide(s), voulez-vous continuer ?", _

55. vbDefaultButton2 + vbInformation + vbOKCancel)

56. If Reponse = vbCancel Then

57. Exit Sub

58. End If

59. End If

60. 'Ouvrir fManip

61. DoCmd.OpenForm "fManip"

62. End Sub

65. Private Sub cadreContenances_AfterUpdate()

66. Me.Requery

67. End Sub

69. Private Sub btTout_Click()

70. Dim ctl As Control

71. For Each ctl In Me.Controls

72. If ctl.Name Like "Filtre*" Then ctl = Null

73. Next ctl

74. Me.cadreContenances = -1

75. Me.Requery

76. End Sub

78. Private Sub bTubeSelect_AfterUpdate()

79. Me.Requery

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

1. Option Compare Database

2. Option Explicit

4. Private Sub Form_Open(Cancel As Integer)

5. Dim hauteur As Integer

6. hauteur = Me.Section(acHeader).Height + Me.Section(acDetail).Height * Me.Recordset.recordcount + 650 'bordures

7. DoCmd.MoveSize 3000, 1750, 6600, hauteur

8. End Sub

10. Private Sub TxtReliquat_BeforeUpdate(Cancel As Integer)

11. 'Vérifier que le reliquat n'est pas négatif

12. If Me.TxtReliquat < 0 Then

13. MsgBox "Le reliquat est négatif !", vbCritical

14. Cancel = True

15. Me.Undo

16. Exit Sub

17. End If

19. 'Vérifier que le reliquat n'est pas supérieur à la somme des contenus

20. If Me.TxtReliquat > DSum("ContenanceActuelle", "rRecherche", "TubeSelect= yes") Then

21. MsgBox "Le reliquat est supérieur au contenu !", vbCritical

22. Cancel = True

23. Me.Undo

24. End If

26. End Sub

28. Private Sub TxtReliquat_AfterUpdate()

29. If TxtReliquat = 0 Then

30. Me.BtCompta.Visible = True

31. 'FermerfRealiquot s'il est ouvert

32. If CurrentProject.AllForms("fRealiquot").IsLoaded Then

33. DoCmd.Close acForm, "fRealiquot"

34. End If

35. Else

36. Me.BtCompta.Visible = False

37. 'Lancer la procédure de réaliquotage

38. '-----------------------------------

39. 'Vérifier la présence d'une cause

40. If IsNull(Me.cboCause) Then

41. MsgBox "La cause de la manipulation manque !", vbCritical

42. Me.cboCause.SetFocus

43. Me.cboCause.Dropdown

44. Exit Sub

45. End If

46. 'Créer les enregistrements de fRealiquot

47. DoCmd.SetWarnings False

48. DoCmd.RunSQL "DELETE * FROM tRealiquot;" 'pour la vider

49. DoCmd.OpenQuery "rRealiquot" ' pour la remplir à nouveau

50. DoCmd.SetWarnings True

51. 'Fermer fRealiquot s'il était déjà ouvert

52. If CurrentProject.AllForms("fRealiquot").IsLoaded Then DoCmd.Close acForm, "fRealiquot"

53. 'routine Arkham

54. DoCmd.OpenForm "fRealiquot", , , , , acHidden

55. PositionForm Forms("fRealiquot"), Me.TxtReliquat

56. End If

57. End Sub

59. Private Sub BtCompta_Click()

60. 'Vérifier qu'une cause de manipulation a été choisie

61. If IsNull(Me.cboCause) Then

62. MsgBox "Une cause de manipulation doit être spécifiée !", vbCritical

63. Me.cboCause.SetFocus

64. Me.cboCause.Dropdown

65. Exit Sub

66. End If

67. DoCmd.SetWarnings False

68. DoCmd.OpenQuery "rComptaManip"

69. DoCmd.OpenQuery "rComptaDecongel"

70. DoCmd.SetWarnings True

71. If CurrentProject.AllForms("fRecherche").IsLoaded Then

72. DoCmd.Close acForm, "fRecherche"

73. End If

74. DoCmd.Close acForm, Me.Name

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

1. Option Compare Database

2. Option Explicit

4. Dim Numero As Integer

5. Dim Quantite As Single

6. Dim Lieu As Long

8. Private Function VeriPertiNum() As Boolean

9. 'Vérifier que ce N° n'est pas déjà attribué à un tube du même lot et même stade

10. 'sauf si c'est l'un des tubes de la manipulation

11. If DLookup("TubeNum", "tTubes", _

12. "TubeSelect=Yes And TubeNum = " & Me.txtRealiNum) Then

13. 'c'est un des tubes de la manip qui est remployé

14. VeriPertiNum = True

15. Exit Function

16. Else

17. If DLookup("TubeNum", "tTubes", "TubeNum = " & Me.txtRealiNum _

18. & " And tLotsFK =" & Forms!fRecherche!FiltreLot _

19. & " And Stade='" & Forms!fRecherche!FiltreStade & "'") Then

20. 'Le N° est déjà utilisé dans ce contexte

21. VeriPertiNum = False

22. Else

23. 'Le N° est OK

24. VeriPertiNum = True

25. End If

26. End If

27. End Function

29. Private Sub Form_Open(Cancel As Integer)

30. Dim hauteur As Integer

31. hauteur = Me.Section(acHeader).Height + Me.Section(acDetail).Height * Me.Recordset.recordcount + Me.Section(acFooter).Height + 650 'bordures

32. DoCmd.MoveSize , , , hauteur

33. End Sub

35. Private Sub Form_Close()

36. 'Purger la table

37. DoCmd.SetWarnings False

38. DoCmd.RunSQL " Delete * from tRealiquot;"

39. DoCmd.SetWarnings True

40. End Sub

42. Private Sub txtRealiNum_BeforeUpdate(Cancel As Integer)

43. 'Vérifier la pertinence du N°

44. If VeriPertiNum = False Then

45. MsgBox "Ce N° est déjà affecté à un autre tube de ce lot/stade !", vbCritical

46. Cancel = True

47. Me.Undo

48. End If

49. End Sub

51. Private Sub BtSuivant_Click()

52. On Error GoTo GestionErreurs

53. ' Dim Position As rect

54. Me.Refresh

55. 'Se positionner sur le dernier tube pour le prendre comme modèle

56. DoCmd.GoToRecord , , acLast

57. 'Rafraîchir la valeur des variables

58. Numero = Me.txtRealiNum

59. Quantite = Me.txtRealiContenance

60. Lieu = Me.cboCasier

61. 'Passer à l'enregistrement suivant

62. DoCmd.GoToRecord , , acNext

63. 'Préremplir les contrôles de cet enregistrement

64. Numero = Numero + 1

65. Me.txtRealiNum = Numero

66. Me.txtRealiContenance = Quantite

67. Me.cboCasier = Lieu

68. 'Vérifier que ce N° est acceptable

69. If VeriPertiNum = False Then

70. MsgBox Me.txtRealiNum & " ferait double-emploi, veuillez spécifier un autre N° pour ce tube."

71. Me.txtRealiNum = Null

72. End If

73. 'Ajuster la hauteur du formulaire pour laisser voir cette nouvelle ligne

74. DoCmd.MoveSize , , , Me.WindowHeight + Me.Section("Détail").Height

75. Exit Sub

76. GestionErreurs:

77. MsgBox "Une anomalie empêche d'accepter un tel tube. Par exemple, une donnée manquante ou causant double-emploi.", vbCritical

78. End Sub

80. Private Sub BtEnregistrer_Click()

81. Me.Refresh

82. 'Vérifier la cohérence entre reliquat et contenances réaliquotés

83. If Forms!fManip!TxtReliquat <> DSum("RealiContenance", "tRealiquot") Then

84. MsgBox "Il y a discordance entre le reliquat : " & Forms!fManip!TxtReliquat _

85. & " et la somme des contenances réaliquotées : " & DSum("RealiContenance", "tRealiquot") _

86. & " !", vbExclamation

87. Exit Sub

88. End If

89. 'Comptabiliser la manipulation

90. DoCmd.SetWarnings False

91. DoCmd.OpenQuery "rComptaManip"

92. DoCmd.OpenQuery "rComptaDecongel"

93. 'comptabiliser le réaliquotage

94. DoCmd.OpenQuery "rComptaRealiquot"

95. 'Désélectionner (remettre TubeSelect à la valeur Faux

96. DoCmd.RunSQL "UPDATE tTubes SET TubeSelect = False;"

97. DoCmd.SetWarnings True

98. 'Fermer les formulaires

99. DoCmd.Close acForm, "fManip"

100. DoCmd.Close acForm, Me.Name

101. If CurrentProject.AllForms("fRecherche").IsLoaded Then

102. DoCmd.Close acForm, "fRecherche"

103. End If

104. End Sub

106. Private Sub BtVoir_Click()

107. 'Vérifier que fManip est ouvert

108. If Not CurrentProject.AllForms("fManip").IsLoaded Then

109. MsgBox "Le formulaire fManip n'est pas ouvert", vbCritical

110. Exit Sub

111. End If

112. 'Fermer fTubesDuLot

113. If CurrentProject.AllForms("fTubesDuLot").IsLoaded Then DoCmd.Close acForm, "fTubesDuLot"

114. 'Routine Arkham pour ouvrir à proximité du bouton

115. DoCmd.OpenForm "fTubesDuLot", , , , , acHidden, Me.Name

116. PositionForm Forms("fTubesDuLot"), Me.BtVoir

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

1. Option Compare Database

2. Option Explicit

4. Private Sub Form_Open(Cancel As Integer)

5. Dim sClauseSelect As String

6. Dim sClauseOrder As String

7. 'Créer les clauses Select et Order By communes aux appelants

8. sClauseSelect = "SELECT tTubes.TubeNum, tTubes.Stade, tTubes.TubeDateEntree, " _

9. & "[LieuNom] & "" "" & [CasierNom] AS [Local] " _

10. & "FROM tLots INNER JOIN (tLieuxStock " _

11. & "INNER JOIN (tCasiers " _

12. & "INNER JOIN tTubes " _

13. & "ON tCasiers.tCasiersPK = tTubes.tCasiersFK) " _

14. & "ON tLieuxStock.tLieuxStockPK = tCasiers.tLieuxStockFK) " _

15. & "ON tLots.tLotsPK = tTubes.tLotsFK "

16. sClauseOrder = "ORDER BY tTubes.Stade, tTubes.TubeNum;"

17. 'Confection de la source et du titre selon les appelants (OpenArgs)

18. Select Case Me.OpenArgs

19. Case "fEncoEntree"

20. Me.RecordSource = sClauseSelect _

21. & "WHERE (((tLots.tLotsPK)=" & Forms!fEncoEntree!cboLot & ")) " _

22. & sClauseOrder

23. Me.txtTitre = "Tubes déjà en stock pour le lot " & Forms!fEncoEntree!cboLot.Column(1)

24. Case "fRealiquot"

25. Me.RecordSource = sClauseSelect _

26. & "WHERE (((tLots.tLotsPK)=" & Forms!fManip!TXTtLotsPK & "))" _

27. & sClauseOrder

28. Me.txtTitre = "Tubes déjà en stock pour le lot " & Forms!fManip!txtLotAppellation

29. End Select

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

1. Option Compare Database

2. Option Explicit

4. Public Function ContenanceActu(TubePk As Long) As Single

5. 'Recherche contenance initiale

6. ContenanceActu = DLookup("TubeContini", "tTubes", "tTubesPK =" & TubePk)

7. 'Soustraire la somme de prélèvements

8. ContenanceActu = ContenanceActu - Nz(DSum("PrelevQuantite", "tPrelevements", "tTubesFK=" & TubePk), 0)

9. End Function

11. Public Function ValForm(NomDuFormulaire As String, NomDuChamp As String) As Variant

12. On Error GoTo GestionErreur

13. ValForm = Forms(NomDuFormulaire)(NomDuChamp)

14. Exit Function

15. GestionErreur:

16. ValForm = Null

17. End Function

19. Public Function Realiquote(idTube As Long) As Boolean

20. Dim rst As DAO.Recordset

21. 'Sélectionner l'enregistrement pour accéder à ses données

22. Set rst = CurrentDb.OpenRecordset("Select * From tTubes Where tTubesPk=" & idTube & ";")

23. 'Vérifier qu'on a bien 1 enregistrement pour ce idTube

24. If rst.RecordCount <> 1 Then

25. MsgBox "Le tTubesPK N° " & idTube & " n'existe pas !", vbCritical

26. Exit Function

27. End If

28. 'Analyse des données

29. If rst("TubeRecu") = -1 Then

30. Exit Function  'c'est un tube d'origine => Realiquote = Faux

31. Else

32. If DSum("TubeRecu", "tTubes", "TubeNum =" & rst("TubeNum") _

33. & " And tLotsFK =" & rst("tLotsFK") _

34. & " And Stade='" & rst("Stade") & "'") = 0 Then ' => réaliquoté

35. Realiquote = True

36. Else

37. Exit Function

38. End If

39. End If

40. rst.Close

41. Set rst = Nothing

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

XV - Téléchargement

La base de données au format Access2000 se trouve ici.

(1) J'assume ma belgitude !

(2) Voir l'explication de cette fonction dans le chapitre Modules standard.

(3) Dans Access, selon les circonstances « Vrai », « Oui », « True » et « -1 » sont synonymes). Idem pour « Faux », « Non », « False » et « 0 » (zéro).