Opérations Mathématiques Inter-Enregistrements
Constat : De nombreuses personnes, moi le premier, souhaitent obtenir des totaux ou sous totaux dans un champ, un peu à la manière d'Excel où l'on peut additionner ou soustraire le contenu de la "ligne" précédente. Or Access, en règle générale, se cantonne à travailler dans l'enregistrement en cours... nous devons donc lui donner l'ordre de parcourir les enregistrements pour récupérer ou utiliser une donnée qui n'appartient pas à l'enregistrement en cours
Exemples : nous allons en décortiquer deux ...
1er But : Nous souhaitons créer un simple report d'un enregistrement à l'autre...
- KmFin de l'Enr N°1 vers KmDebut de l'Enr N°2 et ainsi de suite...
- Conditions KmFin sera toujours plus grand que KmDebut et il ne pourra pas y avoir de KmDebut = 0
Nous créons une table toute simple, on remarquera que l'on utilise un champ NumeroAuto; celui-ci nous permet de conserver l'ordre des enregistrements. En effet, si l'on venait à créer des filtres ou des tris, la cohérence de nos résultats pourraient sembler inexacts à l'écran. Il est donc important de pouvoir présenter les enregistrements dans un ordre précis...
Présenter les enregistrements dans l'ordre de la saisie est le seul usage de cette numérotation automatique, ainsi cette numérotation pourra être discontinue si nous effaçons des enregistrements, etc...comme on peut le voir sur la requête créée sur la table (cette vue n'a pas été prise au même moment que pour l'image du formulaire, ne vous inquiétez donc pas des enregistrements affichés - je vous démontre qu'il n'y a aucun entretien de la numérotation automatique).
On définit un tri ascendant
Nous allons travailler sur la propriété "Après MAJ" du champ [KmFin]
Il y a dans la procédure deux grands chapitres, soit une mise à jour d'un enregistrement déjà encodé, soit l'ajout d'un nouvel enregistrement. Si une de nos conditions n'est pas remplie la "colonne" se colore en rouge pour attirer l'attention de l'utilisateur
Private Sub KmFin_AfterUpdate()
Dim vFin As Double, rs As DAO.Recordset, decompte As Integer
Dim KmCtl As Double
'initialisation des données
vFin = 0: KmCtl = 0: decompte = 0
'ligne indispensable pour que la nouvelle valeur de KmFin soit prise
'en compte, sinon on est en mode édition et la nouvelle valeur est
'à null ce qui provoque une erreur
Me.Refresh
'initiation de la copie du recordset
'on se retrouve automatiquement sur le dernier Enr
Set rs = Me.RecordsetClone
'on se positionne sur le premier enregistrement
rs.MoveFirst
'on va boucler
Do
'on récupère la valeur du champ
vFin = rs("KmFin")
KmCtl = rs("KmDebut")
'***************************
'Procédure d'ajout Enr
'***************************
'Pour détecter fin de fichier moins un Enr
decompte = rs.RecordCount-1
If decompte = (rs.RecordCount - 1) Then
If vFin < KmCtl And vFin <> 0 Then
MsgBox "KmFin doit être plus grand que KmDebut"
vFin = 0
[KmFin].BackColor = RGB(255, 0, 0)
Exit Sub
Else
[KmFin].BackColor = RGB(255, 255, 255)
End If
'on passe sur l'enregistrement suivant
rs.MoveNext
'on ajoute
rs.AddNew
'on file la valeur au champ voulu
rs("KmDebut") = vFin
'on donne l'ordre de se mettre à jour
rs.Update
GoTo Test
End If
'********************
Pour mettre à jour le champ KmDebut
'********************
If vFin < KmCtl Then
MsgBox "KmFin doit être plus grand que KmDebut"
vFin = 0
[KmFin].BackColor = RGB(255, 0, 0)
Exit Sub
Else
[KmFin].BackColor = RGB(255, 255, 255)
End If
'mise à jour de l'enregistrement suivant
rs.MoveNext
rs.Edit
rs("KmDebut") = vFin
rs.Update
decompte = decompte + 1
'on boucle jusqu'à la fin de fichier
Loop Until rs.EOF = True
Test:
'parce qu'une telle ligne ne nous sert à rien
'en fin de fichier
rs.MoveLast
If rs("KmDebut") = 0 And rs("KmFin") = 0 Then
rs.Delete
End If
'on récupère de la mémoire
Set rs = Nothing
End Sub
2ème But : Cette fois, nous allons gérer un fichier de stock, des entrées, des sorties et au final, nous devons toujours être capable de dire combien d'articles nous possédons. Un petit dessin vaut mieux qu'un long discours...Même remarques pour le champ [Nu] que dans l'exercice précédent.
Dans l'image précédente, on constate que c'est peu lisible, les pommes et les poires sont mélangées...mais que dites-vous de ceci
L'on utilise des boutons pour afficher l'item voulu. Ceux-ci lancent le code suivant :
Private Sub Commande9_Click()
DoCmd.ApplyFilter , "Article = 'Pommes'"
End Sub
On peut faire mieux en utilisant un double clic dans le champ "Article" et l'utilisateur peut alors trier des articles pour lesquels aucun bouton n'a été prévu.
Private Sub Article_DblClick(Cancel As Integer)
DoCmd.ApplyFilter , "Article= '" & [Article] & "'"
End Sub
Le code final qui tout en recalculant tous les enregistrements parvient à provoquer une rupture lorsqu'il rencontre une valeur différente dans le champ "Article"
Sub calcul()
'Comme on peut toujours se tromper et mettre un champ à blanc
On Error Resume Next
'on réalise une copie des enregistrements en mémoire
Dim rs As DAO.Recordset
Set rs = Me.RecordsetClone
'Obligatoire, pour enregistrer la nouvelle valeur sinon
'Access considère la valeur du champ comme Null = Bug !
'ou il reprend l'ancienne valeur en compte
Me.Refresh
'Déterminons des variables correspondant à nos champs
'Ceci nous permet de glisser des valeurs issues de ceux-ci
'à un moment x
Dim vIn, vOut, vStock, vAncienStock As Double
Dim vArticle As String
'inutile de parcourir le code si la table est vide
If rs.RecordCount > 0 Then
'Au cas où un utilisateur oublie de mettre une valeur dans le champ
If vArticle = "" Then
vArticle = " "
Else
vArticle = rs("Article")
End If
vStock = 0
vAncienStock = 0
'il faut récupérer la valeur de l'enr en cours
'la comparaison avec les valeurs des autres enregistrements
'nous permettra de provoquer la rupture de l'opération mathématique (Total)
vArticle = [Article]
'se placer sur le premier enregistrement pour éviter qu'Access
'ne vienne buter dans le néant et émette des messages de protestation
rs.MoveFirst
Do While rs.EOF = False
'C'est cette comparaison qui permet de regrouper virtuellement
' les enregistrements par "Article"
If vArticle = rs("Article") Then
vIn = rs("In")
vOut = rs("Out")
'les opérations seront aussi plus simples
vStock = vIn - vOut
'Maintenant il faut mettre à jour les champs
'on doit passer en mode édition
rs.Edit
rs("Stock") = vAncienStock + vStock
'on passe en mode mise à jour
rs.Update
'Pour pouvoir se resservir de cette valeur dans l'Enr Suivant
vAncienStock = rs("Stock")
End If ' de vArticle
rs.MoveNext
Loop
End If ' de recordcount
Set rs = Nothing
End Sub
Pour les néophytes, mais qu'est ce que tout ce charabia signifie ? En fait, le mécanisme est très simple. On se positionne sur le premier enregistrement. Et l'on commence à boucler sur les enregistrements (Do....Loop) jusqu'à la fin de la table (While rs.EOF=False). Avant le départ de la boucle, on récupère la valeur du champ ("Article") dans vArticle. A chaque enregistrement (c'est le rôle de la boucle de nous y conduire), l'on compare le champ "Article" avec la variable vArticle, si le test est positif : on poursuit le code et on incrémente les valeurs nécessaires (on réalise AncienStock + In - Out = Stock). Avant de quitter l'enregistrement que l'on a atteint, on place la valeur du champ "Stock" dans la variable AncienStock , pour pouvoir intégrer cette valeur dans le calcul suivant...et ainsi de suite ...
Et enfin, il faut aussi prévoir le déclenchement de la procédure au chargement ou l'activation du formulaire
Cliquez ici pour télécharger le fichier ReportKm