Introduction
This article summarizes possible solutions for different use cases of passing on values within Quote’s different calculation logics. Some of the use cases can be found here on confluence in articles, comments or in teams chat. We tried to implement all the use cases for our internal PoC’s and we encountered several obstacles while doing so.
We tried to describe possible solutions, some obstacles or wrong ways how to do it. Please feel free to comment the solutions, add wider explanations, or if you know better way how to do some of the use cases or feel strong about some use case that we should not try to implement it at all, please share the knowledge in comments.
...
.
Calculation Logic Architecture Prerequisities
How different logics are executed from the Quote perspective:
...
Syntax Check
This dry run is used to discover inputs in the line itemitems. Most of other api API calls are mocked. Be aware that syntax check dry run is executed only once in case of adding a new line item.
Other
...
Logics Executable from
...
Quote
There are two other logics , which can be executed from header logics:
Configurator Logic
This logic is executed after clicking on the Open Button of Configuratora configurator:
After clicking on save button of Pop up Configurator, new Configurator the Save button in the configurator pop-up, a new configurator value is saved to the Quote object. (The quote object is what you get by calling
quoteProcessor.getQuoteView()
). But you cannot work with the values until the Quote is recalculated. Why? All This is because the logic knows only the old quote 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 (for details see how to recalculate a quote automatically).
Template Logic
This logic is executed after clicking to download the Download PDF button.
from From the template logic the whole quote object is accessible through
api.getCurrentItem()
.
Summary of
...
Passing Values between Different Types of Logics
Use case | 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 through |
the configurator value field, read 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 |
( |
post- |
phase). | |||
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 |
( |
post- |
phase), pass to |
the configurator trough a value |
field. | |||
8 | Quote Header Logic Configurator | Line Item Logic Configurator | Not possible, please see details below, including workaround. |
9 | Line Item Logic Configurator | Quote Header Configurator | Read from |
( |
post- |
phase), pass to |
the configurator trough a value |
field. | |||
10 | Line Item Logic | Line Item Logic Configurator | Not possible, please see details below, including 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 an 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 we have the following header logic stringUserEntry field which I we 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 is that Configurator and Header Logic the configurator and header logic do not share a global space. So I we cannot simply use api.global
. Instead of using global I that we need to pass this value to configurator “value” the configurator "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 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 we need to read configurator values first and then create a value map where I we put backed up configurator’s values together with values I we need to pass to the configurator. My The goal 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 This is the final step: . To be able to read a value inside the configuration logic which has been passed from the header logic I we need to use HIDDEN input. The code bellow has better description capabilitiesillustrates this:
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 the input matrix and I we can use PassedValue for whenever any purpose I need (fe.eg. conditional field showingdisplay).
Use
...
Case 3: Quote Header Logic
...
→ Line Item
...
Logic
Solution for this use case is easy: api.global
...
Instead of api.retainGlobal = true
you can set set flag “api.retainGlobal defaults to TRUE” in configuration of partition (Admin->Configuration->General Settings) to true.
...
Use case 4: Quote Line Item Logic → Quote Header Logic
This can be useful e.g. if you want to calculate summary on header level or display summary chart on Custom Quote Header.
...
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 publishing templates for quotes, you have to pass data to preprocessing (publishing) logic. Simply call api.currentItem from preprocessing logic to get full quote view.
...
Note: data will be available in preprocessing logic after the quote was saved, not before.
Use case 6: Quote Header Configurator
...
→ Line Item Logic
From configurator logic you don’t have direct access to quote object. This means you have to pass values from header logic to line item logic.
...
Without priceEntityAfterSave set up to true the recalculate button has to be pressed after saving values in Configurator.
Use case 7: Line Item Logic
...
→ Quote Header Configurator Logic
To read values from line item logic we can use logic from Use case 4: Quote Line Item Logic → Quote Header Logic
...
Now when you click on configurator button, you will have 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 set generic parameters in quote header configurator and then have pre-filled configurator on each line item, to edit the values for each line item separately.
...
In this case quote recalculation will not help, as during recalculation, there is no syntax check. So let’s talk about workaround.
Workaround - Create Line Item Configurator on Quote Header
Create a configurator for line items from header logic and pass it to line items via addOrUpdate function.
...
In this case, line item configurator will always have the same values as quote header configurator. Any change in line item configurator will be overwritten on quote recalculation. If you want to keep changes done on item level, you have to merge it with header value, before you set them via addOrUpdateInput.
Use case 9: Quote Line Item Configurator → Quote Header Configurator
The principles are same as in Use case 7: Line Item Logic -> Quote Header Configurator 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 line item configurator from line item logic using following code:
...
Problem here is function api.getParameter("Configurator A")
. It returns Configurator object only in case that configurator was not open before and has no value in value property. Same is with f.e. stringuserentry, while is empty you can get 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 line calculation logic, you have to update it using header logic. Basically follow the instructions in Use case 4: Quote Line Item Logic → Quote Header Logic and update configurator values while iterating.
Possible design troubles
“Add new line item” architecture awareness
Some of the use cases above are implemented with header logic updating line items.
...