La formulation nécessite fréquemment l'usage de formules SPEL pour calculer des résultats, par exemple obtenir le coût de production d'un produit, ou bien calculer sa valeur énergétique en kJ.

Cette page est dédiée à la fabrication de telles formules dans le système, et donnera des exemples de formules simples et complexes pour calculer un résultat automatiquement.

Un minimum de connaissances en programmation en Java ou Javascript est attendu.

Localisation des formules


Les formules vont se trouver principalement à deux endroits différents :

  • Dans la liste caractéristiques de l'administration, par exemple pour les nutriments, caractéristiques physico-chimiques...
  • Dans la liste des caractéristiques dynamiques d'un produit / d'un modèle d'entité

Les caractéristiques dynamiques peuvent être effectuées sur le produit formulé, ou bien sur chaque ligne de la liste liée. Chaque liste ayant des composants (Composition, Emballages, Process) dispose de caractéristiques dynamiques distinctes.

Pour qu'une formule soit executée sur chacun des composants, il suffit de choisir une colonne. Ensuite, le résultat sera affiché dans la nouvelle colonne ajoutée à la liste.

Sur cette image, la caractéristique dynamique "Coût (€)" est calculée sur chaque ligne de la composition et s'affiche directement dessus.

Généralités sur la structure d'une formule


De manière générale, une formule SPEL va renvoyer un seul résultat, qui sera la dernière expression évaluée. Cela signifie qu'il est possible d'utiliser des variables intermédiaires au sein des calculs, à condition de simplement appeler la variable contenant le résultat final à la fin de la formule.

Par exemple la formule suivante renverra le résultat stocké dans la variable result

    var result = qty;
    var yieldCoeff = (yield/100);
    #result = #result / #yieldCoeff;
    #result;

Cependant, la plupart des formules n'auront qu'une seule expression, et l'utilisation de variables intermédiaires ne se justifiera pas, ainsi la formule précédente peut être raccourcie en :

    qty / (yield/100)

L'une des limitations principales du langage SPEL réside dans l'absence de blocs pour créer des conditions if ... else, ce qui forced l'utilisateur à utiliser des structures ternaires qui vont rapidement s'accumuler et rendre la relecture de la formule fastidieuse.

Exemple avec la formule de calcul de la quantité de sel en fonction de la quantité de sodium :

    nut['Sodium'].value == null ? 0d : nut['Sodium'].value * 2.5 / 1000

(A noter qu'ici Sodium est en fait le nodeRef de la caractéristique Sodium, et pas juste la chaine de caractères "Sodium")

Pour ne pas que notre formule ne donne d'erreur si la caractéristique Sodium n'est pas renseignée, il faut auparavant vérifier qu'elle est bien saisie, auquel cas on effectue le calcul, et sinon on renvoit une valeur par défaut, 0.

La plupart des accès en lecture à des propriétés ou des valeurs de calculs devraient comporter une valeur par défaut, au cas où la valeur n'est pas présente. Cette valeur doit être suffixée par un d (ou D) pour indiquer au système qu'il s'agit d'un nombre à virgule, et pas un caractère (exemple : 1d va représenter le chiffre 1 à virgule, alors que 1 peut représenter le chiffre 1 en entier, ou bien la chaîne de caractères "1").

Les formules faisant des sommes ou multiplications se satisfairont d'avoir 0d comme valeur par défaut, alors que les divisions nécessiteront 1d, et des formules renvoyant du texte (par exemple pour afficher si un produit rentre dans une catégorie) prendront "" comme valeur par défaut.

Ces valeurs par défaut seront donc les dernières valeurs lors de longues expressions avec de multiples conditions (si ce n'est pas 1 alors si ce n'est pas 2 alors ... alors 0d)

L'éditeur de formules


L'éditeur de formules (voir figure suivante) se présente sous la forme d'une grande zone de texte, dont les lignes sont numérotées, ainsi que de deux tableaux en dessous.

Les tableaux sous la zone de texte vont permettre à l'utilisateur de sélectionner rapidement des portions de formules à insérer dans la zone de texte, à l'endroit où le curseur se trouve.

Le tableau de gauche affiche les propriétés relatives au type d'entité sélectionné au dessus (sur la capture, le type est Produit Fini, mais il peut s'agir de semi finis, de nutriments, d'ingrédients, coûts..), ou bien des caractéristiques si le type choisi est une liste de caractéristiques (comme les nutriments).

Le tableau de droite va, si l'utilisateur a sélectionné une caractéristique dans le tableau de gauche (voir figure suivante), afficher les propriétés relatives à cette caractéristique.

Ces variables proviennent soit depuis la classe ProductData, qui est utilisée par tous les types de produits, soit depuis la classe correspondant au type (NutListDataItem, CostListdataItem...)

Le raccourci CTRL + Espace ouvre un menu contextuel d'aide, permettant d'accéder rapidement à des fonctions avancées des formules.

Ces fonctions proviennent de la classe d'aide FormulationHelper et permettent de gérer facilement des sommes, moyennes, accès aux propriétés d'une entité, à la recherche d'un produit, etc.

  • entity retourne le type ProductData associé au contexte de la formule. Ce point d'entrée est implicite lors d'une formule effectuée sur un produit (sur une caractéristique dynamique ou une caractéristique d'une data list comme les nutriments), mais est l'élément fourni dans la formule lors de l'usage des fonctions types somme, avg, etc. décrites plus loin.
  • dataListItem renvoie la ligne d'une dataList, par exemple une ligne de la composition avec ses propriétés de quantité MeO, de freinte, de variante..
  • dataListItemEntity renvoie l'entité (type ProductData) associé à une ligne d'une dataList, par exemple la Matière Première correspondant à la ligne dans une composition
  • variantData renvoie la variante liée à une ligne de la liste de composition, emballages etc.
  • @beCPG.propValue(entity, "bcpg:legalName") permet d'accéder à la propriété Libellé légal de l'entité entity fournie en paramètre. Cela permet donc d'obtenir toutes les propriétés visibles dans le navigateur de noeuds, même celles qui n'ont pas de raccourcis dans les tableaux se situant sous le champ de saisie de la formule.
  • @beCPG.setValue(#this, "bcpg:legalName","cm:description) permet de forcer la valeur bcpg:legalName avec la valeur de cm:description dans l'entité où se toruve la formule, dans cet exemple.

Les formules suivantes ont le même principe et peuvent se combiner :

  • @beCPG.sum(range, formula)
  • @beCPG.avg(range, formula)
  • @beCPG.filter(range, formula)

L'idée est de fournir deux arguments pour obtenir respectivement une somme, une moyenne, et un filtre. Le premier argument, range, sera une liste (un tableau) d'entités, auquel sur chacune d'elle la formule formula du second argument sera appliquée.

Il peut être par exemple utile d'avoir la somme des quantités dans la composition du produit.

@beCPG.sum(getCompoList(new fr.becpg.repo.variant.filters.VariantFilters(true)),dataListItem.qty)

On ajoute en plus un filtre à la composition, qui permet de n'obtenir que les éléments qui ont la variante par défaut. Cependant, on pourrait aller plus loin, et ne prendre les quantités que des composants qui ne sont pas des semi-finis locaux.

@beCPG.sum(
    @beCPG.filter(
        getCompoList(new fr.becpg.repo.variant.filters.VariantFilters(true)), 
        !(dataListItemEntity instanceof T(fr.becpg.repo.product.data.LocalSemiFinishedProductData))
    ),
    dataListItem.qty
)
  • @beCPG.children(entity) permet d'obtenir les composants fils de l'entité entity, utilisé pour descendre dans les différents niveaux de composition.
  • @beCPG.findOne(nodeRef) permet d'obtenir l'entité désignée par la chaine de caractère nodeRef

Les fonctions de idLiquid() à isLocalSemiFinished() sont des raccourcis pour ne pas utiliser le instanceof vu dans la dernière formule.

Exemples de formules


@beCPG.propValue(@beCPG.assocValue(entity, "bcpg:clients"), "cm:name")

Cette fonction permet d'extraire le champ cm:name de l'association bcpg:clients, @beCPG.assocValue(entity, "bcpg:clients") donnant son nodeRef. Utile lorsque l'assoc est une valeur unique.

@beCPG.assocPropValues(nodeRef,"bcpg:clients","cm:name")

Cette fonction permet d'extraire le champ cm:name de l'association bcpg:clients sous forme de tableau pour traiter le cas où plusieurs valeurs sont associées à bcpg:clients.

    (ingList.^[ing.toString() == 'Blé']?.qtyPerc != null ? ingList.^[ing.toString() == 'Blé']?.qtyPerc : 0d) +
    (ingList.^[ing.toString() == 'Farine de blé']?.qtyPerc != null ? ingList.^[ing.toString() == 'Blé']?.qtyPerc : 0d)

Cette formule calcule la somme des pourcentages de blé et de farine de blé (visibles dans la liste des ingrédients) dans le produit. A noter que 'Blé' correspond en fait au nodeRef de l'ingrédient blé, qui est remplacé à l'affichage par son nom.

@beCPG.sum(getCompoList(new fr.becpg.repo.variant.filters.VariantFilters()), 
"dataListItemEntity instanceof T(fr.becpg.repo.product.data.LocalSemiFinishedProductData) ? 0d : @beCPG.propValue(dataListItem.nodeRef, 'bcpg:dynamicCharactColumn2')")

Cette formule calcule la somme de la colonne dynamique 2, pour les composants n'étant pas des produits semi-finis locaux

@beCPG.sum(getCompoList(), "@beCPG.findOne(dataListItemEntity).getEntityTpl() != 'PF reconstitué' ? @beCPG.propValue(dataListItemEntity, "bcpg:reconstitutionRate") * dataListItem.qty : dataListItemEntity.physico['Matière sèche']?.value")

Dans cette formule, suivant le modèle de l'entité du composant parcouru, seront sommés :

  • La propriété "Taux de reconstitution" multipliée par la quantité de la ligne de composition, si le modèle du composant est "PF reconstitué"
  • La physico-chimique "Matière sèche" de ce composant le cas échéant
new java.text.SimpleDateFormat("dd/MM/yyyy").format(new java.util.Date())

Permet d'avoir la date du jour au format dd/MM/yyyy.

new java.text.DecimalFormat("0.#").format(nut['Protéine']?.value)

Permet d'avoir un format personnalisé sur des nombres.

@beCPG.propValue($nodeRef,"cm:description").replaceAll("toreplace","new text")

Permet de changer le texte "to replace" en "new text" sur le champ cm:description.

entity.labelClaim['Kasher']?.isClaimed

Renvoie true si la claim est revendiquée.

results matching ""

    No results matching ""