...
This article summarizes possible solutions when you need to pass values between Quote’s different calculation logics. We implemented It describes various use cases in an internal POC and encountered several obstacles while doing so, their advantages and disadvantages.
Please feel free to comment on the solutions, add explanations, or if you know suggest a better way to implement the use cases, let us know.
In this section:
Table of Contents | ||||
---|---|---|---|---|
|
Calculation Logic Architecture Prerequisities
This flow shows how different logics are executed from the Quote perspective:
...
Syntax Check
This dry run is used to discover inputs in the line item. Most of other api API calls are mocked. Be aware that the syntax check dry run is executed only once in case of adding when a new line item is added.
Other
...
Logics Executable from
...
Quote
There are two other types of logics , which can be executed from header logics:
Configurator Logic
The logic is executed after clicking on the Open Button button of Configurator.
After clicking on save the Save button of Pop up a popup Configurator, a new Configurator configurator value is saved to the Quote object. (quote Quote object is what you get by calling
quoteProcessor.getQuoteView()
.) . But you cannot work with the values until Quote the quote is recalculated. Why? All the logic knows only is the old quote object since all other calculation logics finished before Configurator the configurator logic was executed. You must press recalculate click the Recalculate button or set automatic recalculation, please read the article ; to learn more see how to recalculate a quote automatically.
Template Logic
The logic is executed after clicking to the download PDF button.
from From the template logic the whole quote object is accessible through
api.getCurrentItem()
.
...
Use Cases Summary
Use |
---|
Case | Pass Values From | To | Solution |
---|---|---|---|
1 | Quote Header Logic Configurator | Quote Header Logic | Read |
the configurator value field. | |||
2 | Quote Header Logic | Quote Header Logic Configurator | Pass it through |
the configurator value field, read it using a HIDDEN field inside the configurator. | |||
3 | Quote Header Logic | Line Item Logic | Hidden Input (Pre-Phase) |
4 | Line Item Logic | Header Logic | Read from |
5 | Quote Header Logic | Publishing Template Logic | Hidden |
input or | |||
6 | Quote Header Configurator | Line Item Logic | Hidden Input |
7 | Line Item Logic | Quote Header Logic Configurator | Read from |
the configurator trough a value |
field. | |||
8 | Quote Header Logic Configurator | Line Item Logic Configurator | Not possible, please see the details below, including a workaround. |
9 | Line Item Logic Configurator | Quote Header Configurator | Read from |
the configurator trough a value |
field. | |||
10 | Line Item Logic | Line Item Logic Configurator | Not possible, please see the details below, including a workaround. |
Use
...
Case 1: Quote Header Logic Configurator → Quote Header Logic
Step 1: Create the Configurator button in the header logic.
Code Block |
---|
if (quoteProcessor.isPrePhase()) { quoteProcessor.addOrUpdateInput( "ROOT", [ "name" : "Configurator", "label": "Configurator", "url" : "ConfiguratorLogic", "type" : "CONFIGURATOR" ] ) } |
Step 2: Define a generic logic (name it e.g. “ConfiguratorLogic”
“ConfiguratorLogic”). It contains input matrix in our casethis example.
Code Block |
---|
def ce = api.createConfiguratorEntry() def p = ce.createParameter(InputType.INPUTMATRIX, "QuoteFeatures") def cols = ["Name", "Description"] def types = ["Text", "Text"] p.addParameterConfigEntry("columns", cols) p.addParameterConfigEntry("columnType", types) return ce |
Step 3: In the header logic, access all the values from the configurator using the following code snippet:
Code Block |
---|
if(quoteProcessor.isPrePhase()) { def quote = quoteProcessor.getQuoteView() def quoteFeatures = quote?.inputs?.find { it.name == "Configurator" }?.value } |
Where Configurator
is a name of the configurator element (created in step Step 1).
What you will get is a the following HashMap:
{QuoteFeatures=[{Name=firstLineName, firstLineDescr, selected=false}]
Use
...
Case 2: Quote Header Logic → Quote Header Logic Configurator
Step 1: Create a header input field.
Suppose I Let’s suppose you have the following header logic stringUserEntry field which I you want to pass to the configurator:
Code Block |
---|
if (quoteProcessor.isPrePhase()) { quoteProcessor.addOrUpdateInput("ROOT", [ "name" : "ValueToBePassed", "label" : "ValueToBePassed", "type" : "STRINGUSERENTRY", "required": false ] ) } |
Step 2: Passing Pass the header value to the configurator.
The problem issue here is that Configurator and Header Logic the configurator and header logic do not share a global space. So I you cannot simply use api.global
. Instead of using global I , you need to pass this value to the configurator “value” value
property.
Code Block |
---|
if (quoteProcessor.isPrePhase()) { def passedValue = quoteProcessor.getQuoteView().inputs?.find { it.name == "ValueToBePassed" }?.value quoteProcessor.addOrUpdateInput( "ROOT", [ "name" : "Configurator", "label": "Configurator", "url" : "ConfiguratorLogic", "type" : "CONFIGURATOR", "value": passedValue ] ) } |
Step 3: Merging Merge values.
Problem The problem with the solution from step Step 2 is that the logic always overrides the configurator value after recalculation. Configurator The configurator value field is used for storing the configurator object. It looks like this (QuoteFeature is inputMatrix object):
{QuoteFeatures=[{Name=firstLineName, firstLineDescr, selected=false}]
Therefore I you need to read configurator values first and then create a value map where I you put backed up configurator’s values together with values I you need to pass to the configurator. My The goal here is this:
{QuoteFeatures=[{Name=firstLineName, firstLineDescr, selected=false}], PassedValue=asdfasfd}
...
Code Block |
---|
if (quoteProcessor.isPrePhase()) { def quote = quoteProcessor.getQuoteView() def passedValue = quote.inputs?.find { it.name == "ValueToBePassed" }?.value def configuratorValueBackup = quote.inputs?.find { it.name == "Configurator" }?.value ?: [:] configuratorValueBackup.put("PassedValue", passedValue) quoteProcessor.addOrUpdateInput( "ROOT", [ "name" : "Configurator", "label": "Configurator", "url" : "ConfiguratorLogic", "type" : "CONFIGURATOR", "value": configuratorValueBackup ] ) } |
Step 4: How to read Read the passed value in the configurator logic.
Now the final step: To be able to read a value inside the configuration logic which has been was passed from the header logic I , you need to use a HIDDEN input. The code bellow has illustrates this better description capabilities:
Code Block |
---|
def confEntryPassedValue = api.createConfiguratorEntry(InputType.HIDDEN, "PassedValue") def passedValue = confEntryPassedValue.getFirstInput()?.getValue() |
The QuoteFeatures
object is automatically mapped to the input matrix, so the passed values are displayed in an input matrix and I you can use PassedValue for whenever whatever purpose I you need (fe.eg. conditional field showing).
Use
...
Case 3: Quote Header Logic
...
→ Line Item logic
Solution The solution for this use case is easy: api.global
.
In header Header logic:
Code Block | ||
---|---|---|
| ||
api.retainGlobal = true api.global.variableTest = "value from header2" |
Quote Line Logicline logic:
Code Block |
---|
def value = api.global.variableTest api.logInfo("LineItemLogic value:" + value); |
Instead of using api.retainGlobal = true
, you can set set flag enable the option “api.retainGlobal defaults to TRUE” in configuration of partition (Admin->Configuration->General Settings) to true Configuration.
...
Use
...
Case 4: Quote Line Item Logic → Quote Header Logic
This can be useful when e.g. if you want to calculate a summary on the header level or display a summary chart on in the Custom Quote Header.
This use case is very easy, as it is just involves reading the quote structure. This It needs to be done in Quote Post Phase the quote post-phase (= after line item calculation).
In the header logic, you can read the values from line items this way:
Code Block | ||
---|---|---|
| ||
if(quoteProcessor.isPostPhase()){ def lineItems = quoteProcessor.getQuoteView().lineItems lineItems.each{ lineItem -> def sku= lineItem.sku def outputItems = lineItem.outputs def value = outputItems.find{ it.resultName == "LineItemValue"}?.result api.trace("result", "sku: " +sku + " value: "+ value); } } |
Use
...
Case 5: Quote Header Logic → Publishing (Preprocessing) Logic
If you are using use publishing templates for quotes, you have to pass data to the preprocessing (publishing) logic. Simply To do that, simply call api.currentItem
from the preprocessing logic to get a full quote view.
Code Block |
---|
def quote = api.currentItem(). |
Note: data Data will be available in the preprocessing logic only after the quote was saved, not before.
Use
...
Case 6: Quote Header Configurator
...
→ Line Item Logic
From the configurator logic you don’t do not have direct access to the quote object. This means you have to pass values from the header logic to the line item logic.
To accomplish do this behavior , you need to combine the following use cases:
Use case Case 1: Quote Header Logic Configurator → Quote Header Logic.
and then
Use case Case 3: Quote Header Logic -> → Line Item logic
Without If priceEntityAfterSave is not set up to true, the recalculate Recalculate button has to be pressed used after saving values in Configuratorthe configurator.
Use
...
Case 7: Line Item Logic
...
→ Quote Header Configurator Logic
To read values from a line item logic we can , use logic the steps from Use case Case 4: Quote Line Item Logic → Quote Header Logic.
Then at PostPhase in in the post-phase in the header logic we , pass those values through “value” the value
property inside in the configurator. For details see Use case Case 2: Quote Header Logic → Quote Header Logic Configurator.
Thanks to postPhase we the post-phase you can pass those these values within one recalculation transaction.
This how would look like final Final code:
Code Block |
---|
if (quoteProcessor.isPostPhase()) { def quote = quoteProcessor.getQuoteView() def lineItems = quote?.lineItems def sum = 0 lineItems.each { lineItem -> def outputItems = lineItem.outputs def value = outputItems.find { it.resultName == "LineItemValue" }?.result sum = sum + value } def passedSumValue = sum def mergedValue = quote.inputs?.find { it.name == "Configurator" }?.value ?: [:] mergedValue.put("PassedValue", passedSumValue) quoteProcessor.addOrUpdateInput( "ROOT", [ "name" : "Configurator", "label": "Configurator", "url" : "ConfiguratorLogic", "type" : "CONFIGURATOR", "value": mergedValue ] ) } |
Now when you click on configurator the Configurator button, you will have get values from line items available in that configurator.
Use
...
Case 8: Quote Header Logic Configurator → Line Item Logic Configurator
This may be useful if you want for example to e.g. set generic parameters in the quote header configurator and then have the configurator pre-filled configurator on for each line item , to and then edit the values for each line item separately.
It is possible to pass data from Quote Header Logic Configurator to Line Logicthe quote header logic configurator to the line item logic, but it is not possible to pass data from Quote Header Logic Configurator to Configurator defined in Line Item Logicheader logic configurator to a configurator defined in the line item logic.
Why? Let’s see the example of how you pass the passing values from Line Item Logic to Line Item Configuratora line item logic to a line item configurator.
Code Block |
---|
api.configurator("Configurator A","ConfiguratorFormula") def confA = api.getParameter("Configurator A") if(confA != null && confA.getValue() == null) { confA.setValue(["ConfiguratorType":"A"]) } |
This works fine as because you use static (hard-coded) values. Data are not loaded dynamically. If you want to load data dynamically from a quote header configurator, the code is looks like this in the line item logic (non-working nonworking demo!).
Code Block | ||
---|---|---|
| ||
//NOTE: this is NONNONWORKING WORKING code - it is here just forto exlanationillustrate what this approach it not workingthe case def conf = api.configurator("test", "ConfiguratorLogic"); //line item configurator def headerValues = api.input("valueFromHeaderConfigurator") //loading values from hidden input created in header logic valueToSet = headerValues?.QuoteFeatures?.Name?.getAt(0) //get one of the values def confA = api.getParameter("test") //getting reference for line item configurator def valueToSet = ["QuoteFeatures":["Name":valueToSet, "selected":"false"]] //data structure for input matrix to set confA?.setValue(valueToSet) //setting value to configurator |
But this is does not not working, work because:
Line Item Configurator item configurator “test” is created during the syntax check.
During the syntax check,
api.input
returns only mock data, not real data.During the syntax check (and only during the syntax check),
api.getParameter(
“test”) return contex"test")
returns a context parameter of the “test” "test" configurator. During recalculation (when the syntax check is not running),api.getParameter(
“test”"test")
returns null.ConfA?.setValue
in the syntax check:has Has a context parameter of the configurator.
does Does not have a value loaded from
api.input
.
ConfA?.
setvaluesetValue
when the quote is recalculated:does Does not have a context parameter of the configurator.
has Has a value loaded from
api.input
, but it cannot be set, as confA is null.
As a result:
you are able to set You can set a static value via
confA.setValue()
as the value are is known at during the syntax check.you are not able to set dynamically You cannot dynamically set loaded data from the header configurator because in the syntax check
api.input
does not return an expected value and during recalculation,api.getParameters(
“test”"test")
does not return the context parameter of the configurator.
In this case, quote recalculation will not help, as during recalculation , there is no syntax check. So let’s talk about a workaround.
Workaround
...
: Create Line Item Configurator
...
in Quote Header
Create a configurator for line items from the header logic and pass it to the line items via the addOrUpdate
function.
Example:
Configurator on at the header level, defined in the header logic. From this configurator, we you load data to our the line item configurator.
Code Block |
---|
if (quoteProcessor.isPrePhase()) { quoteProcessor.addOrUpdateInput( "ROOT", [ "name" : "Configurator", "label": "Configurator", "url" : "ConfiguratorLogic", "type" : "CONFIGURATOR" ] ) } |
Line item configurator is defined in the same header logic:
Code Block | ||
---|---|---|
| ||
if(quoteProcessor.isPostPhase()) { for (lineItemMap in quoteProcessor.getQuoteView().lineItems) { if (lineItemMap.folder) continue //skip folders def quote = quoteProcessor.getQuoteView() def configuratorValues = quote.inputs?.find { it.name == "Configurator" }?.value ?: [:] def valuesToSet = configuratorValues; quoteProcessor.addOrUpdateInput(lineItemMap.lineId, ["name" : "LinteItemConfigurator", "label" : "LinteItemConfigurator", "type" : InputType.CONFIGURATOR, "url" : "ConfiguratorLogic", "readOnly": false, "value" : valuesToSet] ) } } |
In this case, the line item configurator will always have the same values as the quote header configurator. Any change in the line item configurator will be overwritten on quote recalculation. If you want to keep changes done on the item level, you have to merge it with the header value , before you set pass them via addOrUpdateInput
.
Use
...
Case 9: Quote Line Item Configurator → Quote Header Configurator
The principles are same as in Use case Case 7: Line Item Logic -> → Quote Header Configurator Logic.
Only The only change here is when you are iterating iterate over lineItems you need to look inside the configurator value field and collect all needed values.
This needs to be done in the quote header logic.
Code Block |
---|
def quote = quoteProcessor.getQuoteView() def lineItems = quote?.lineItems lineItems.each { lineItem -> def inputItems = lineItem.inputs def value = inputItems.find { it.name == "ConfiguratorName" }?.value sum = sum + value?.configuratorFieldValue? : 0 } |
Use
...
Case 10: Line Item Logic → Line Item Logic Configurator
It is possible to add default values to the line item configurator from the line item logic using the following code:
Code Block |
---|
api.configurator("Configurator A","ConfiguratorFormula") def confA = api.getParameter("Configurator A") if(confA != null && confA.getValue() == null) { confA.setValue(["ConfiguratorType":"A"]) } |
So when you need to pass values only once as a an initialization step, it can be done using the code above. If you would like to pass different values each time on recalculate, you must change the approach.
Problem The problem here is the function api.getParameter("Configurator A")
. It returns Configurator the configurator object only in case that if this configurator was not open opened before and has no value in the value property. Same is with f.eThe same happens with e.g. stringuserentry
, : while it is empty you can get the userEntry
object by calling api.getParameter
. After you fill it in, no object is returned to you.
Workaround/Solution
To dynamically fill in configurators in a line calculation item logic, you have to update it using the header logic. Basically follow Follow the instructions in Use case 4: Quote Line Item Logic → Quote Header Logicand update configurator values while iterating.
Possible
...
Design Issues
“Add new line item”
...
Architecture Awareness
Some of the use cases above are implemented with a header logic updating line items.
Example:
Header logic configurator has three string inputs. I You need to see those input values at a line item level to work with them. I You can do this in the quote header prephase pre-phase logic. I You will iterate over lineItems and passing pass them some HIDDEN fields. So far so good.
What happen happens when I you add a new item? Only the item logic syntax check and item logic is are executed. When But when the quote header prephase pre0phase did the iteration over all items, this new line was not there yet.
Conclusion:
It means that I You will get those these passed values on a new line item only after clicking on the Recalculate button. This could be UX issue.