Formules

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 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 exécuté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 force 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 chaîne 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 renvoie 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 satisfont d'avoir 0d comme valeur par défaut, alors que les divisions nécessitent 1d, et des formules renvoyant du texte (par exemple pour afficher si un produit rentre dans une catégorie) prendrons "" 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.

Caractéristiques

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

Helpers SPEL

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.

Contexte

Le contexte de la formule est le point d'entrée demandé dans la plupart des fonctions SPEL. C'est l'élément sur lequel va se passer l'application 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.

  • #this retourne l'entité courante.
  • entity retourne le type ProductData associé au context de la formule.
  • 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.
  • compoList[0].product renvoie le nodeRef de l'entité de la première ligne de la liste composition

Fonctions disponibles

  • propValue

Permet d'accéder à une propriété.

Exemple : on veut extraire le cm:name de l'entité avec une caractéristique dynamique fixe:

@beCPG.propValue(#this,"cm:name")

Exemple 2 : on veut extraire le bcpg:erpCode des entités d'une liste avec une caractéristique en colonne:

@beCPG.propValue(dataListItemEntity,"bcpg:erpCode")

Exemple 3 : on veut extraire la valeur d'une famille hiérarchique (d:noderef). Il faut, dans ce cas, faire deux fois propValue :

@beCPG.propValue(@beCPG.propValue(#this,"bcpg:productHierarchy1"),"bcpg:lkvValue")
  • setValue

Permet d'attribuer une valeur à un champ. Exemple : on veut attribuer la valeur "kg" à bcpg:productUnit:

@beCPG.setValue(#this,"bcpg:productUnit","kg")

Exemple 2 : on veut attribuer la valeur de cm:description au champ bcpg:legalName:

@beCPG.setValue(#this,"bcpg:legalName",@beCPG.propValue(#this,"cm:description"))
  • assocValue

Renvoie la valeur (nodeRef) d'une association.

Exemple : on veut extraire l'association "bcpg:trademarkRef" du produit:

@beCPG.assocValue(#this,"bcpg:trademarkRef")
  • assocValues

Renvoie les valeurs (tableau contenant des nodeRef) d'une association multiple.

Exemple : on veut extraire l'assocation "bcpg:productGeoOrigin" du produit:

@beCPG.assocValues(#this,"bcpg:productGeoOrigin")

A noter que extraire le premier élément du tableau avec:

@beCPG.assocValues(#this,"bcpg:productGeoOrigin")[0]

Est similaire à

@beCPG.assocValue(#this,"bcpg:productGeoOrigin")
  • setAssocs

Permet d'attribuer une valeur à une assoc ou plusieurs.

Exemple : on veut attribuer une usine de nodeRef "workspace://SpacesStore/169a0cf8-2f0a-4011-9b19-dea14676e136" à l'association "bcpg:plant" :

@beCPG.setAssocs(#this,"bcpg:plants", "workspace://SpacesStore/169a0cf8-2f0a-4011-9b19-dea14676e136")

Exemple 2 : on veut ajouter le client de nodeRef "workspace://SpacesStore/844aa160-f344-43be-a659-434d319e584f" à l'association "bcpg:clients" :

var clients = @beCPG.assocValues(#this,"bcpg:clients");
#clients.add("workspace://SpacesStore/844aa160-f344-43be-a659-434d319e584f");
@beCPG.setAssocs(#this,"bcpg:clients", #clients)
  • assocPropValues

Extrait les propriétés d'une association.

Exemple : on veut extraire les cm:title des clients :

@beCPG.assocPropValues(#this,"bcpg:client","cm:title")
  • assocAssocValues

Extrait les associations d'une association.

Exemple : on veut extraire les bcpg:plantCertifications des bcpg:plant du produit :

@beCPG.assocAssocValues(#this,"bcpg:plant","bcpg:plantCertifications")
  • propMLValue

Extrait les propriétés multi langue dans une langue spécifiée.

Exemple : on veut extraire le bcpg:legalName en anglais US:

@beCPG.propMLValue(#this,"bcpg:legalName","en_US")
  • updateMLText

Permet d'attribuer une valeur à un champ multilingue dans une certaine langue.

Exemple : on veut extraire attribuer la valeur "Finish Legal Name" au legalName en finois:

@beCPG.updateMLText(legalName,"fi","Finish Legal Name")

Attention : cela marche seulement pour les champs multilangues présents dans les helpers.

  • copy

Permet de copier des associations et/ou des listes d'une source vers le produit courant.

La fonction se présente sous la forme:

@beCPG.copy(sourceNodeRef, assocToCopyList, listToCopyList)

Exemple : on veut faire remonter les associtations "bcpg:trademarkRef","bcpg:subsidiaryRef","bcpg:clients", et "bcpg:suppliers" du premier élément de la composition vers notre produit courant:

@beCPG.copy(compoList[0].product,{"bcpg:trademarkRef","bcpg:subsidiaryRef","bcpg:clients","bcpg:suppliers"},"")

Exemple 2 : on veut copier la liste microbiologique du critère microbiologique vers le produit:

var productMicrobioCriteriaRef = @beCPG.assocValue(#this,"bcpg:productMicrobioCriteriaRef");
@beCPG.copy(#productMicrobioCriteriaRef,"",{"bcpg:microbioList"})

Exemple 3 : on veut copier des listes et propriétés depuis l'entité parente:

@beCPG.copy(parentEntity,{"bcpg:legalName", "bcpg:productHierarchy1", "bcpg:erpCode"},{"bcpg:compoList","bcpg:packagingList","mpm:processList"})
  • applyFormulaToList

Permet d'appliquer une formule à une liste comme une colonne dynamique.

Exemple : on veut mettre les valeurs des lignes de la composition à 0 :

@beCPG.applyFormulaToList(getCompoList(),"@beCPG.setValue(dataListItem,'bcpg:compoListQtySubFormula', 0d)")

Exemple 2 : on veut forcer la méthode de la nutList à 'Formulation' :

@beCPG.applyFormulaToList(nutList,"@beCPG.setValue(dataListItem,'bcpg:nutListMethod', 'Formulation')")

Cela fonctionne pour la plupart des listes standards, comme:

  • getCompoList() ( ou compoList )
  • getPackagingList() ( ou packagingList )
  • getProcessList() ( ou processList )
  • getControlDefList() ( ou controlDefList)
  • priceList
  • costList
  • reqCtrlList
  • ...

  • filter

Permet de filtrer. Il est généralement combiné à d'autres formules, comme le min, max ou sum.

Exemple : on veut les éléments de la reqCtrlList ayant une valeur reqMaxQty et n'étant pas du type info.

@beCPG.filter(reqCtrlList, "reqMaxQty!=null && reqType.toString() !='Info'")

Exemple 2 : on veut extraire les éléments de la liste composition de la variante principale ayant une qty supérieure à 0,5kg:

@beCPG.filter(getCompoList(new fr.becpg.repo.variant.filters.VariantFilters(true)), "dataListItem.qty > 0.5")
  • max

Permet de trouver le max d'un tableau de nombre :

@beCPG.max($arrayOfDouble)

Exemple : on veut trouver le reqMaxQty maximum de la liste reqCtrlList.

@beCPG.max(reqCtrlList.![reqMaxQty])

Exemple 2 : on veut trouver le qty maximum de la liste compoList:

@beCPG.max(compoList.![qty])
  • min

Permet de trouver le min d'un tableau de nombre :

@beCPG.min($arrayOfDouble)

Exemple : on veut le minimum parmi les éléments de la reqCtrlList ayant une valeur reqMaxQty et n'étant pas du type info.

@beCPG.min(@beCPG.filter(reqCtrlList, "reqMaxQty!=null && reqType.toString() !='Info'").![reqMaxQty])
  • avg

Permet de trouver la moyenne d'un tableau de nombre :

@beCPG.avg($arrayOfDouble)
  • sum

Permet de sommer des éléments.

@beCPG.sum(range, formula)

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
)

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

  • children

Permet d'obtenir les composants fils de l'entité entity, utilisés pour descendre dans les différents niveaux de composition.

@beCPG.children(entity)
  • findOne

Permet d'obtenir l'entité désignée par la chaîne de caractère nodeRef

@beCPG.findOne(nodeRef)
  • sourcesAssocValues

Permet d'obtenir le tableau des nodeRef des cas d'emplois en se basant sur une association.

Exemple : on veut la liste des nodeRef des projets utilisant mon produit actuel:

@beCPG.sourcesAssocValues(#this,"pjt:projectEntity");

Exemple 2 : on veut le nombre de produit utilisant dans leur composition mon produit actuel

var sources = @beCPG.sourcesAssocValues(#this,"bcpg:compoListProduct");
var size = #sources.size();
#size;
  • updateDate

Permet de modifier un champ date en ajouter un nombre de jour / semaine / mois / année :

@beCPG.updateDate($date, $field, $amount )

Exemple : on enlève 5 jours à la date du jour :

@beCPG.updateDate(new java.util.Date(), T(java.util.Calendar).DAY_OF_MONTH, -5 )

Exemple 2 : on ajoute 1 an à la date de création :

@beCPG.updateDate(@beCPG.propValue(#this, "cm:created"), T(java.util.Calendar).YEAR, 1 )

Exemple 3 : on ajoute 3 mois à la date de modification :

@beCPG.updateDate(@beCPG.propValue(#this, "cm:modified"), T(java.util.Calendar).MONTH, 3 )

Exemples de formules


@beCPG.propValue(@beCPG.assocValue(#this, "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.

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

var productName = @beCPG.propValue(#this,"cm:name");
#productName.substring(#productName.indexOf("test"));

Extrait le texte du champ cm:name après la première occurence de la chaîne de caractère "test".

@beCPG.propValue(#this,"cm:name").startsWith("X");

Test la chaîne de caractère initiale et renvoie true / false.

Nutriscore formula (beCPG 4.0)

Pour extraire les détails du nutriscore, il faut intiailiser le contexte comme variante :

var context = T(fr.becpg.repo.product.formulation.score.NutriScoreContext).parse(nutrientDetails);

Puis, nous pouvons extraire le résumé :

#context.toDisplayValue();

Nous pouvons aussi extraire les détails de chaque nutriment:

  • getNutriScore()
  • getEnergy()
  • getSatFat()
  • getTotalFat()
  • getTotalSugar()
  • getSodium()
  • getPercFruitsAndVetgs()
  • getNspFibre()
  • getAoacFibre()
  • getProtein()

Nous pouvons extraire les détails du nutriscore:

  • getCategory()
  • getNutrientClass()
  • getClassLowerValue()
  • getClassUpperValue()
  • getAScore()
  • getCScore()

Nous pouvons choisir les valeurs à extraire:

  • getLowerValue()
  • getUpperValue()
  • getScore()
  • getValue()

Par example:

#context.getProtein().getValue();
#context.getTotalSugar().getLowerValue();
#context.getAScore();
#context.getClassUpperValue();

results matching ""

    No results matching ""