RPG IV : Simplifier le traitement des expressions logiques

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 :

  1. Une première réalisant des évaluations de artChgQte pour alimenter les indicateurs de artChgQteTxt.
  2. 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 :

  1. Une évaluation de la artChgQte pour alimenter les indicateurs de artChgQteTxt.
  2. 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 *onEn 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.