Formulation often requires the use of SpEL Formulas to calculate results (for example to get the production cost of a product, or to calculate its energetic value).

This page is dedicated to the creation of these formulas in the system, and gives examples of simple and complex formulas to calculate automatically a result.

A minimum of knowledge in Java / Javascript programming is required.

Simple formulas localization


Formulas will be found mainly in two differents locations:

  • In the administration, for example for nutrients, physico-chemical characteristics,...
  • On Products and product templates, in dynamic characteristics

Dynamic characteristics can be done in a formulated product, or in every lines of the linked list. Every list with components (packaging, composition,process) owns its own dynamic characteristic list.

It's possible to apply a formula to all elements of a datalist by choosing a column. The results will then be displayed in a column.

In this screenshot, "Costs (€)" is calculated for every components of the list.

Generalities on a simple formula


Most of the time, SpEL formulas will display a single result; which is the last calculated expression. That means that it's possible to use intermediate variables in calculation, if the variable containing the final result is called at the end of the formula.

For example, the following formula will give the result, stocked in the result result

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

However, most of the formulas will have only a single expression, and the use of intermediate variables won't be necessary. Then, the previous expression could be shortened:

    qty / (yield/100)

One of the main limitation of SpEL language is the absence of conditions with if ... else. You need to use ternary structure which can accumulate and make the reading complex.

e.g. Salt quantity calculation (based on sodium quantity):

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

(Sodium is here the nodeRef of the nutrient Sodium)

The formula first checks that the Sodium value is not null. If null, the formula displays 0. Most of the reading access to the propertu or calculation values have to include a default value, in case the value is not present. This value must have the suffix d (or D) to indicate to the sustem that this number contains comma, and not a character (example: 1d is a number with comma, while 1 can represent the entire number 1, or the text character "1").

Multiplications work with 0d, but divisions must have 1d, and formulas which display text must have "" as default value. These values must be used in condition formulas.

Formula editor


Formula editor is presented as a text area, with numeroted lines, and two tables below.

Characteristics

Tables under the text area enable users to quickly select fragments of formulas to insert in the text zone where the cursor is.

You can choose elements on the left, and choose the information to display from this elements at the right (value, minimumValue,...)

Those variables come from ProductData class, which is used in every products, or from a list data (NutListDataItem, CostListdataItem...).

Helpers SPEL

CTRL + SPACE shortcut displays a help menu to have a quick access to the advanced fonctions.

Those functions come from the FormulationHelper facilitate the use of sum, multiplication, or fields value extraction.

Context

The context of the formula is usually asked by most of SPEL functions. It's the element on which the formula is working.

  • #this displays current entity
  • entity displays the productData type context of the formula.
  • dataListItem displays the line of a datalist, for example a line of the composition.
  • dataListItemEntity displays the entity (type ProductData) associated to a line of the datalist, for example the Raw Material in a composition list.
  • variantData displays the variant linked to a line of the composition list, packaging list,...
  • compoList[0].product displays the first composition lin's entity

Functions

  • propValue

Displays a property.

Example : we want to extract cm:name:

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

Example 2 : we want to extract the bcpg:erpCode of compoList's entities:

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

Example 3 : we want to extract hierarchy name (d:noderef):

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

Set a value to a property.

Example: set "kg" as productUnit:

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

Example 2 : set description value as legalName

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

Displays the nodeRef value of an association

Example : displays association trademarkRef :

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

Displays the values of a multiple associations in table.

Example : we want to extract the multiple association "bcpg:productGeoOrigin":

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

Please note that you can extract the first element with [0]

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

Which is then similar to:

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

Set one or several assoc values.

Example: we want to set the plant with nodeRef "workspace://SpacesStore/169a0cf8-2f0a-4011-9b19-dea14676e136" to the "bcpg:plant" association:

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

Example 2: we want to add the client with nodeRef "workspace://SpacesStore/844aa160-f344-43be-a659-434d319e584f" to the "bcpg:clients" association:

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

Displays properties from the associations extracted.

Example : we want to extract title for client :

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

Displays associations from the associations extracted.

Example : we want to extract bcpg:plantCertifications from bcpg:plant :

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

Extract a multilingual property in the specified locale.

Example : we wan to extract the bcpg:legalName in english US:

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

Set a locale value to a multilingual field.

Example : we want to set tthe value "Finish Legal Name" to the legalName in finish:

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

Beware: this works for multilingual fields as helpers only.

  • copy

Copy associations and/or lists from a source to the current product.

The function is working like this:

@beCPG.copy(sourceNodeRef, assocToCopyList, listToCopyList)

Example: we want to copy the associations "bcpg:trademarkRef","bcpg:subsidiaryRef","bcpg:clients", and "bcpg:suppliers" from the first entity of the compoList:

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

Example 2 : we to copy the microbioList from the association productMicrobioCriteriaRef:

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

Exemple 3 : we want to copy the list and properties from the parent entity:

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

Applicate a formula to a list, like a dynamic column.

: we want to set compoListQtySubFormula of compoList as 0 :

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

This works on most of standards listes, like:

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

  • filter

Use to filter. Is generally combined with other formulas, like min, max, avg or sum.

Example : we want to find the reqCtrlList elements, which have a reqMaxQty value and are not associated to "Info" type:

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

Example 2 : we wan to extract the compoList elements of the main variant which have a qty > 0.5 kg:

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

Use to find the maximum value of an array:

@beCPG.max($arrayOfDouble)

Example : we want to find the maxmim reqMaxQty of the reqCtrlList:

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

Example 2 : we want to find the maximum qty of the compoList:

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

Use to find the minimim value of an array:

@beCPG.min($arrayOfDouble)

Example: we want to find the minimum among the reqCtrlList elements which have a reqMaxQty and are not associated to "Info" type:

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

Use to find the average value of an array:

@beCPG.avg($arrayOfDouble)
  • sum

Use to sum the values of an array.

@beCPG.sum(range, formula)

Example: You can have the sum of the elements in a datalist.

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

We add the filter to only consider the quantity of the composition elements with the default variant. However, we can go further and only takes in account the quantity of the componenents which are not local semi finished products.

@beCPG.sum(
    @beCPG.filter(
        getCompoList(new fr.becpg.repo.variant.filters.VariantFilters(true)), 
        !(dataListItemEntity instanceof T(fr.becpg.repo.product.data.LocalSemiFinishedProductData))
    ),
    dataListItem.qty
)

Functions isLiquid() to isLocalSemiFinished() are shotcut to not use instanceof seen in the last formula.

Second example, you want to do a sum of the elements in a dynamic column. The fomula is the following using column 7 in the composition as an example:

@beCPG.sum(compoList, "@beCPG.propValue(dataListItem,'bcpg:dynamicCharactColumn7')" )
  • children

Used to get the children of entity, used to go down in the composition levels.

@beCPG.children(entity)
  • findOne

Used to get the entity designed with the nodeRef

@beCPG.findOne(nodeRef)
  • sourcesAssocValues

Displays the where-used nodeRef array, based on a target association.

Exqmple : we want to have the project list using my current product:

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

Exqmple 2 : we want to have the number of product using my current product in their composition:

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

Modify a date value by adding day/ week / month / year:

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

Example: we substract 5 days to the today's date:

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

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

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

Example 3: We add 3 months to the modified date:

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


@beCPG

# Examples of formulas
-----------------------------

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

This fonction extracts the field Name "cm:name" of the association bcpg:clients, @beCPG.assocValue(entity, "bcpg:clients") gives its nodeRef.
Useful in the case the association has a single value.
(ingList.^[ing.toString() == 'Wheat']?.qtyPerc != null ? ingList.^[ing.toString() == 'Wheat']?.qtyPerc : 0d) +
(ingList.^[ing.toString() == 'Wheat flour']?.qtyPerc != null ? ingList.^[ing.toString() == 'Blé']?.qtyPerc : 0d)

This formula calculates the sum of wheat and wheat flour % (in ingredient slist) in the product.
('Wheat' is the nodeRef, not text).

@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')")

This formula calculates the sum of the dynamic column 2, for the components which are not local semi finished products.

@beCPG.sum(getCompoList(), "@beCPG.findOne(dataListItemEntity).getEntityTpl() != 'Reconstituted FP' ? @beCPG.propValue(dataListItemEntity, "bcpg:reconstitutionRate") * dataListItem.qty : dataListItemEntity.physico['Dry matter']?.value")


In this formula, in following the entity model of the component, will be sum:

* The property "Reconstitution Rate", multiplied by the quantity of the composition line, if the template of the component is "Reconstituted FP".
* The physicy-chemical characteristic "Dry matter" of this component if the previous is not applicable

new java.text.SimpleDateFormat("dd/MM/yyyy").format(new java.util.Date())


Used to get the today's date in the dd/MM/yyyy format.

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

Used to get personalized format on number.

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


Used to replace the text "to replace" to "new text" in the field cm:description.

entity.labelClaim['Kasher']?.isClaimed


Displays *true* if Kosher is claimed.

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

productName.substring(#productName.indexOf("test"));


Extract the text of the field cm:name after the first occurance of a text string.

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


Test the presence of a text string at the beggining of a text and display true / false.

# Nutriscore formula

To extract the nutriscore details, we need to initialise the context as variante

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


Then,  we can extract a summarize:

context.toDisplayValue();


We can also extract details on each nutrient:

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

We can also extract details on nutriscore:

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

We can use the value to extract from them:

* getLowerValue()
* getUpperValue()
* getScore()
* getValue()

For example:

context.getProtein().getValue();

context.getTotalSugar().getLowerValue();

context.getAScore();

context.getClassUpperValue();

```

results matching ""

    No results matching ""