Comme bien d’autres langages, le RPG IV permet d’affecter les indicateurs directement à partir d’expressions logiques :
/free // Test puis affectation. if i > 30; *IN53 = *ON; endif; // Affectation directe, pour le même résultat *IN53 = (i > 30); // Les parenthèses sont optionnelles. *IN53 = i > 30; /end-free
Ce type d’affectation permet de produire un code plus lisible : simple à relire donc plus simple à maintenir ! C’est particulièrement vrai lorsque des traitements doivent réaliser une batterie de tests qui conditionnent eux-même une autre batterie d’autres tests et ainsi de suite.
Nous allons voir comment séparer une collecte d’indicateurs d’un traitement d’interprétation. Notez que l’objectif n’est pas de créer un programme test pleinement fonctionnel mais de considérer l’approche globale.
Exemple d’application
Dans le contexte d’une gestion de stock, nous devons signaler aux utilisateurs certains événements relatifs aux changements de quantités sur les articles. Ces données sont récupérées depuis une DS contenant les éléments suivants.
- Référence de l’article
- Libellé de l’article
- Ancienne quantité
- Nouvelle quantité
- Ancienne valeur de quantité minimum
- Nouvelle valeur de quantité minimum
- Ancienne valeur de quantité maximum
- Nouvelle valeur de quantité maximum
- Timestamp
Une alerte est à générer dans les cas suivant :
- La quantité passe à zéro
- La quantité devient négative
- La quantité revient à zéro
- La quantité redevient positive
- La quantité passe en dessous du stock minimum
- La quantité repasse au dessus du stock minimum
Nous devons aussi préciser la cause de l’alerte car, par exemple, si la quantité passe en dessous du stock minimum cela peut être aussi dû à une diminution du stock mais aussi à une augmentation de la valeur minimum (ou les deux).
A partir de ces éléments, nous pouvons établir un tableau des événements à signaler et des causes possibles .
Quantité… | Cause |
---|---|
Passe à zéro | La quantité a augmenté ou diminué |
Devient négative | La quantité a diminué |
Revient à zéro | La quantité a augmenté |
Redevient positive | La quantité a augmenté |
Passe en dessous du minimum | La quantité a changé et/ou la quantité minimum a changé |
Repasse au dessus du minimum | La quantité a changé et/ou la quantité minimum a changé |
Mise en oeuvre
Le traitement va utiliser deux DS :
- La DS contenant les informations de quantité : artChgQte
- Une DS contenant les indicateurs et les « textes clairs » : artChgQteTxt.
Ces deux DS seront manipulées par deux procédures :
- Une première réalisant des évaluations de artChgQte pour alimenter les indicateurs de artChgQteTxt.
- Une seconde utilisant les indicateurs de artChgQteTxt pour en alimenter « les textes clairs » en s’appuyant aussi sur artChgQte. (notamment pour récupérer les valeurs de quantités)
Définitions des DS : artChgQte
Nous commençons par artChgQte qui contient les informations de quantité. Dans le projet original c’est une DS produite depuis un trigger, ce qui explique l’utilisation des termes « avant / après ».
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++++++Comments++++++++++++ // Modèle pour les quantités d m_qte... d s 10i 0 based(null) // artChgQte d m_artChgQte... d ds qualified based(null) d ref... Ref article d 64a d lib... Libellé article d 128a d qteAvt... Quantité avant d like(m_qte) d qteApr... Quantité après d like(m_qte) d minAvt... Minimum avant d like(m_qte) d minApr... Minimum après d like(m_qte) d ts... Timestamp d z
Définition des DS : artChgQteTxt
Nous définissons maintenant artChgQteTxt qui regroupe les indicateurs permettant d’enregistrer toutes les évaluations dont voici la liste :
- La quantité minimum a changé
- La quantité minimum a augmenté
- La quantité minimum a diminué
- La quantité a changé
- La quantité a augmenté
- La quantité a diminué
- La quantité passe à zéro
- La quantité est négative
- La quantité reste négative
- La quantité devient négative
- La quantité n’est plus négative
- La quantité est devenu inférieur au minimum
- La quantité est devenu supérieur au minimum
- La quantité reste inférieur au minimum
- La quantité est repassé au-dessus du minimum
Et la DS modèle :
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++++++Comments++++++++++++ d m_artChgQteTxt... d ds qualified based(null) d* Texte clair d texte... d 128a d* Cause d cause... d 128a d* Informations complémentaires d infoc... d 128a d* Quantité à changé d T_QTE... d n d* Quantité minimum à changé d T_MIN... d n d* Quantité minimum a augmenté d T_MINAUG... d n d* Quantité minimum a diminué d T_MINDIM... d n d* Quantité a augmenté d T_QTEAUG... d n d* Quantité a diminué d T_QTEDIM... d n d* Quantité est négative d T_QTENEG... d n d* Quantité reste négative d T_QTERSTNEG... d n d* Quantité devient négative d T_QTEDVNNEG... d n d* Quantité passe à zéro d T_QTEDVNZRO... d n d* Quantité n'est plus négative d T_QTEPLSNEG... d n d* Quantité est devenue inférieure au minimum d T_QTEINFMIN... d n d* Quantité est devenue supérieure au minimum d T_QTESUPMIN... d n d* Quantité est restée inférieure au minimum d T_QTEDJAMIN... d n d* Quantité est repassée au-dessus du minimum d T_QTEAVTINFMIN... d n
L’indicateur T_QTEPLSNEG
, s’il est *on, permet de savoir que la quantité n’est plus négative. Autrement dit, la quantité n’est pas négative MAIS elle l’était avant. Il contient donc deux informations.
Les trois zones texte pourraient très bien être déclarées varying.
Définition des procédures
Nous nous intéressons maintenant aux procédures qui vont manipuler ces DS. Le traitement va s’opérer en deux temps, dans deux procédures distinctes :
- Une évaluation de la artChgQte pour alimenter les indicateurs de artChgQteTxt.
- Une lecture des indicateurs de artChgQteTxt pour en alimenter les « textes clairs » en s’appuyant en complément sur artChgQte.
Ces deux procédures renverront *ON si quelque chose doit être signalé, *OFF dans le cas contraire. Elles recevront les mêmes paramètres :
- Un pointeur sur artChgQte
- Un pointeur sur artChgQteTxt
Voici les prototypes de nos procédures, respectivement : observerQuantites() et redigerObservations() :
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++++++Comments++++++++++++
d observerQuantites...
d pr n
d artQte...
d * const
d artQteTxt...
d * const
d redigerObervations...
d pr n
d artQte...
d * const
d artQteTxt...
d * const
Implémentation de la fonction de collecte
Nous commençons par la première étape : la collecte de données.
Nous évaluons les expression directement vers les indicateurs. L’instruction if n’est utilisée que pour des branchements conditionnés. Il en résulte un code plus concis et plus clair (au passage, code concis n’est pas forcément code plus clair…)
Voici la fonction complète, nous détaillerons après :
*--------------------------------------------------------------------------------------------- * * observerQuantites() * * Alimente * * Paramètres : * * - artChgQte_ * Pointeur vers un buffer contenant une DS basée sur m_artChgQte * * - artChgQteTxt_ * Pointeur vers un buffer contenant une DS basée sur m_artChgQteTxt * * Retour : * *on * *--------------------------------------------------------------------------------------------- p observerQuantites... p b d pi n d artChgQte_... d * const d artChgQteTxt_... d * const d artQte... d ds likeds(m_artChgQte) based(artChgQte_) d indTxt... d ds likeds(m_artChgQteTxt) d based(artChgQteTxt_) inz /free // Variation de quantité ? indTxt.T_QTE = (artQte.qteAvt <> artQte.qteApr); // Variation de la quantité mini ? indTxt.T_MIN = (artQte.minAvt <> artQte.minApr); // Pas de changement if indTxt.T_QTE = *off and indTxt.T_MIN = *off; return *off; endif; // Changement de quantité if indTxt.T_QTE = *on; // A augmenté indTxt.T_QTEAUG = (artQte.qteAvt < artQte.qteApr); // A diminué indTxt.T_QTEDIM = not indTxt.T_QTEAUG; // Est négative indTxt.T_QTENEG = (artQte.qteApr < 0); // Est passée à zéro indTxt.T_QTEDVNZRO = (artQte.qteApr = 0); // Devient négative indTxt.T_QTEDVNNEG = indTxt.T_QTENEG and not(artQte.qteAvt < 0); // Reste négative indTxt.T_QTERSTNEG = indTxt.T_QTENEG and (artQte.qteAvt < 0); // N'est plus négative indTxt.T_QTEPLSNEG = (artQte.qteApr >= 0 and artQte.qteAvt < 0); // Est inférieure au stock mini. indTxt.T_QTEINFMIN = (artQte.qteApr < artQte.minApr); // Devient inférieure au stock mini. indTxt.T_QTEINFMIN = indTxt.T_QTEINFMIN and (artQte.qteAvt >= artQte.minAvt); // Reste inférieure au stock mini. indTxt.T_QTEDJAMIN = indTxt.T_QTEINFMIN and (artQte.qteAvt < artQte.minAvt); // Repasse au dessus ou au niveau du stock mini. indTxt.T_QTESUPMIN = (artQte.qteAvt < artQte.minAvt) and (artQte.qteApr >= artQte.minApr); // Etait avant inférieure à la quantité minimum indTxt.T_QTEAVTINFMIN = (artQte.qteAvt < artQte.minAvt); endif; // Appel de la fonction de rédaction. redigerObservations(artChgQte_: artChgQteTxt_); return *on; /end-free p e
La procédure observerQuantites() reçoit un premier pointeur sur une DS de type artChgQte et un second sur une DS de type artChgQteTxt, respectivement mappés sur artQte et indTxt. Remarquez que la procédure ne déclare aucune variable : artQte et indTxt ne sont que des chemins d’accès vers des buffers déjà existants.
Nous évaluons donc les informations de quantités proposées par artChgQte et nous consignons ces tests dans artChgQteTxt. L’affection directe prend ici tout son sens : le même source utilisant des conditionnements if/else imbriqués pour mettre à jours ces indicateurs serait bien plus difficile à lire.
A l’issue de cette collecte, nous appelons la fonction d’interprétation redigerObservations() à qui nous faisons suivre les deux DS.
Implémentation de la fonction d’interprétation
Le principe de traitement des paramètres et de mappages des structures sur les pointeurs est rigoureusement identique à la procédure précédente.
Le rôle de cette procédure est de produire un texte clair à partir des indicateurs de la DS artChgQteTxt. Il convient de faire attention à l’ordre des tests.
Vous constaterez que la procédure commence par tester les deux indicateurs T_QTE et T_MIN, or, dans notre exemple, cette procédure n’est invoquée que si l’un des ces deux indicateurs est *on. En ajoutant ce test, nous permettons à la procédure de ne pas dépendre de l’appelant.
La fonction complète :
*--------------------------------------------------------------------------------------------- * * redigerObservations() * * Complète les variables "texte clair" de la DS artChgQteTxt. * * - artChgQte_ * Pointeur vers un buffer contenant une DS basée sur m_artChgQte * * - artChgQteTxt_ * Pointeur vers un buffer contenant une DS basée sur m_artChgQteTxt * * Retour : * *on * *--------------------------------------------------------------------------------------------- p redigerObservations... p b d pi n d artChgQte_... d * const d artChgQteTxt_... d * const d artQte... d ds likeds(m_artChgQte) based(artChgQte_) d indTxt... d ds likeds(m_artChgQteTxt) d based(artChgQteTxt_) d qteMvt... d s like(m_qte) /free clear indTxt.texte; clear indTxt.cause; clear indTxt.infoc; // Pas de changement... if indTxt.T_QTE = *off and indTxt.T_MIN = *off; return *off; endif; qteMvt = artQte.qteAvt - artQte.qteApr; if qteMvt < 0; qteMvt = qteMvt * (-1); endif; // Evènement : La quantité passe à zéro if indTxt.T_QTEDVNZRO = *on; indTxt.texte = 'La quantité est passée à zéro (' + %char(artQte.qteAvt) + ' --> ' + %char(artQte.qteApr) + ')'; return *on; endif; // Evènement : La quantité devient négative if indTxt.T_QTEDVNNEG = *on; indTxt.texte = 'La quantité est devenue négative (' + %char(artQte.qteAvt) + ' --> ' + %char(artQte.qteApr) + ')'; indTxt.cause = 'Sortie ou régularisation de ' + %char(qteMvt) + ' pièce(s).'; return *on; endif; // Evènement : La quantité passe sous le seuil minimum if indTxt.T_QTEINFMIN = *on; infTxt.texte = 'La quantité (' + %char(artQte.qteApr) + ') ' + 'est passée sous le seuil minimum (' + %char(artQte.minApr) + ')'; // Cause : diminution du mini if indTxt.T_MINDIM = *on and indTxt.T_QTE = *off; indTxt.cause = 'Diminution de la quantité minimum ( ' + %char(artQte.minAvt) + ' --> ' + %char(artQte.minApr) + ')'; return *on; endif; infTxt.cause= 'Mouvement ou régularisation de ' + %char(qteMvt) + ' pièce(s) : ' + %char(artQte.qteAvt) + ' --> ' + %char(artQte.qteApr); return *on; endif; // Evènement : La quantité n'est plus négative if indTxt.T_QTEPLSNEG = *on; infTxt.texte = 'La quantité n''est plus négative (' + %char(artQte.qteAvt) + ' --> ' + %char(artQte.qteApr) + ')'; infTxt.cause = 'Entrée ou régularisation de ' + %char(qteMvt) + ' pièce(s).'; if indTxt.T_QTEINFMIN = *on; infTxt.infoc = 'La quantité diponible reste cependant en dessous du stock ' + 'minimum (' + %char(artQte.minApr) + ').'; endif; return *on; endif; // Evènement : La quantité repasse au dessus du stock minimum if T_QTESUPMIN = *on; indTxt.texte = 'La quantité (' + %char(artQte.qteApr) + ') ' + 'est repassée au dessus du seuil minimum (' + %char(artQte.minApr) + ')'; // Cause : Augmentation de la quantité if T_QTEAUG = *on and T_MINDIM = *off; indTxt.cause = 'Entrée ou régularisation de ' + %char(qteMvt) + ' pièce(s) : ' + %char(artQte.qteAvt) + ' --> ' + %char(artQte.qteApr); indTxt.infoc = 'Le stock mini est de ' + %char(artQte.minApr) + ' pièce(s).'; return *on; endif; // Cause : diminution du mini if T_QTEAUG = *off and T_MINDIM = *on; indTxt.cause = 'La quantité minimum à été diminué de ' + %char(artQte.minAvt - artQte.minApr) + ' pièce(s) : ' + %char(artQte.minAvt) + ' --> ' + %char(artQte.minApr); indTxt.infoc = 'La quantité en stock reste à ' + %char(artQte.qteApr) + ' pièce(s).'; return *on; endif; // autre cause indTxt.cause = 'Entrée ou régularisation de ' + %char(qteMvt) + ' pièce(s) : ' + %char(indTxt.qteAvt) + ' --> ' + %char(indTxt.qteApr); return *on; endif; return *off; /end-free
Utilisation de la procédure
Voici à quoi ressemblerait une procédure utilisant ce traitement.
Notez que c’est ici que sont créés les buffers correspondants aux DS artChgQte et artChgQteTxt, les procédures ne reçoivent que des pointeurs qui leur permettent ensuite de les modifier.
h dftactgrp(*no) d artChgQte... d ds likeds(m_artChgQte) d artChgQteTxt... d ds likeds(m_artChgQteTxt) inz /free // Alimentation de la ds pour test artChgQte.ref = 'ref.article'; artChgQte.lib = 'Libellé article'; artChgQte.ts = %timestamp(); artChgQte.qteAvt = 100; artChgQte.qteApr = 50; artChgQte.minAvt = 80; artChgQte.minApr = 80; // Traitement if observerQuantites(%addr(artChgQte): %addr(artChgQteTxt)) = *on; consignerAlerte(%addr(artChgQteTxt)); endif; // artChgQteTxt.texte contient 'La quantité (50) est passée sous le seuil minimum (80)' // artChgQteTxt.cause contient 'Mouvement ou régularisation de 30 pièces(s) : 80 --> 50' // artChgQteTxt.info contient '' artChgQte.minApr = 30; // Traitement if observerQuantites(%addr(artChgQte): %addr(artChgQteTxt)) = *on; consignerAlerte(%addr(artChgQteTxt)); endif; // artChgQteTxt.texte contient 'La quantité (50) est repassée au dessus du seuil minimum (30)' // artChgQteTxt.cause contient 'La quantité minimum à été diminuée de 50 pièce(s) : 80 --> 30' // artChgQteTxt.info contient 'La quantité en stock reste de 30 pièce(s).' *inlr = *on; /end-free
Pour finir
J’espère que ce petit billet trouvera lecteur (et qu’il le comblera, bien entendu !). Si jamais vous avez besoin de précisions ou si vous souhaitez en apporter je suis à votre écoute.