Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 11 Current »

This article summarizes possible solutions when you need to pass values between Quote’s different calculation logics. It describes various use cases, their advantages and disadvantages.

Please feel free to comment on the solutions, add explanations, or suggest a better way to implement the use cases.

In this section:

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 calls are mocked. Be aware that the syntax check dry run is executed only once 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 the Open button of Configurator.

      After clicking the Save button of a popup Configurator, a new configurator value is saved to the Quote object. (Quote object is what you get by calling quoteProcessor.getQuoteView().) But you cannot work with the values until the quote is recalculated. Why? All the logic knows is the old quote object since all other calculation logics finished before the configurator logic was executed. You must click the Recalculate button or set automatic recalculation; to learn more see how to recalculate a quote automatically.

  • Template Logic

    • The logic is executed after clicking the download PDF button.

    • 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 getQuoteView()
(Post-Phase)

5

Quote Header Logic

Publishing Template Logic

Hidden input or api.currentItem (provides quoteView)

6

Quote Header Configurator

Line Item Logic

Hidden Input

7

Line Item Logic

Quote Header Logic Configurator

Read from getQuoteView()
(Post-Phase), pass it to 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 getQuoteView()
(Post-Phase), pass it to 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.

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”). It contains input matrix in this example.

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:

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

What you will get is 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.

Let’s suppose you have the following header logic stringUserEntry field which you want to pass to the configurator:

if (quoteProcessor.isPrePhase()) {
    quoteProcessor.addOrUpdateInput("ROOT",
            [
                    "name"    : "ValueToBePassed",
                    "label"   : "ValueToBePassed",
                    "type"    : "STRINGUSERENTRY",
                    "required": false
            ]
    )
}

Step 2: Pass the header value to the configurator.

The issue here is that the configurator and header logic do not share a global space. So you cannot simply use api.global. Instead, you need to pass this value to the configurator value property.

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: Merge values.

The problem with the solution from Step 2 is that the logic always overrides the configurator value after recalculation. 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 you need to read configurator values first and then create a value map where you put backed up configurator’s values together with values you need to pass to the configurator. The goal here is this:

{QuoteFeatures=[{Name=firstLineName, firstLineDescr, selected=false}], PassedValue=asdfasfd}

Code snippet:

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: Read the passed value in the configurator logic.

To be able to read a value inside the configuration logic which was passed from the header logic, you need to use a HIDDEN input. The code illustrates this better:

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 you can use PassedValue for whatever purpose you need (e.g. conditional field showing).

Use Case 3: Quote Header Logic → Line Item logic

The solution for this use case is easy: api.global.

Header logic:

api.retainGlobal = true  

api.global.variableTest = "value from header2"

Quote line logic:

def value = api.global.variableTest

api.logInfo("LineItemLogic value:" + value);

Instead of using api.retainGlobal = true, you can enable the option “api.retainGlobal defaults to TRUE” in Configuration.

Use Case 4: Quote Line Item Logic → Quote Header Logic

This can be useful when e.g. you want to calculate a summary on the header level or display a summary chart in the Custom Quote Header.

This use case is easy, as it just involves reading the quote structure. It needs to be done in the quote post-phase (= after line item calculation).

In the header logic, you can read the values from line items this way:

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 use publishing templates for quotes, you have to pass data to the preprocessing (publishing) logic. To do that, simply call api.currentItem from the preprocessing logic to get a full quote view.

def quote = api.currentItem().

Note: Data will be available in the preprocessing logic only after the quote was saved.

Use Case 6: Quote Header Configurator → Line Item Logic

From the configurator logic you 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 do this, you need to combine the following use cases:

Use Case 1: Quote Header Logic Configurator → Quote Header Logic.

and then

Use Case 3: Quote Header Logic → Line Item logic

If priceEntityAfterSave is not set to true, the Recalculate button has to be used after saving values in the configurator.

Use Case 7: Line Item Logic → Quote Header Configurator Logic

To read values from a line item logic, use the steps from Use Case 4: Quote Line Item Logic → Quote Header Logic.

Then in the post-phase in the header logic, pass those values through the value property in the configurator. For details see Use Case 2: Quote Header Logic → Quote Header Logic Configurator.

Thanks to the post-phase you can pass these values within one recalculation transaction.

Final code:

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 the Configurator button, you will 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 to e.g. set generic parameters in the quote header configurator and then have the configurator pre-filled for each line item and then edit the values for each line item separately.

It is possible to pass data from the quote header logic configurator to the line item logic, but it is not possible to pass data from Quote header logic configurator to a configurator defined in the line item logic.

Why? Let’s see the example of passing values from a line item logic to a line item configurator.

api.configurator("Configurator A","ConfiguratorFormula")
def confA = api.getParameter("Configurator A")
if(confA != null && confA.getValue() == null) {
     confA.setValue(["ConfiguratorType":"A"])
}

This works fine 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 looks like this in the line item logic (nonworking demo!).

//NOTE: this is NONWORKING code - it is here just to illustrate the 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 work because:

  1. Line item configurator “test” is created during the syntax check.

  2. During the syntax check, api.input returns only mock data, not real data.

  3. During the syntax check (and only during the syntax check), api.getParameter("test") returns a context parameter of the "test" configurator. During recalculation (when the syntax check is not running), api.getParameter("test") returns null.

  4. ConfA?.setValue in the syntax check:

    1. Has a context parameter of the configurator.

    2. Does not have a value loaded from api.input.

  5. ConfA?.setValue when the quote is recalculated:

    1. Does not have a context parameter of the configurator.

    2. Has a value loaded from api.input but it cannot be set, as confA is null.

As a result:

  • You can set a static value via confA.setValue() as the value is known during the syntax check.

  • 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") 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 at the header level, defined in the header logic. From this configurator, you load data to the line item configurator.

if (quoteProcessor.isPrePhase()) {
    quoteProcessor.addOrUpdateInput(
            "ROOT",
            [
                    "name" : "Configurator",
                    "label": "Configurator",
                    "url"  : "ConfiguratorLogic",
                    "type" : "CONFIGURATOR"
            ]
    )
}

Line item configurator is defined in the same header logic:

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

The only change is when you 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.

    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:

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

The problem here is the function api.getParameter("Configurator A"). It returns the configurator object only if this configurator was not opened before and has no value in the value property. The 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.

Workaround/Solution

To dynamically fill in configurators in a line item logic, you have to update it using the header logic. Follow the instructions in Use case 4: Quote Line Item Logic → Quote Header Logic and 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. You need to see those input values at a line item level to work with them. You can do this in the quote header pre-phase logic. You will iterate over lineItems and pass them some HIDDEN fields. So far so good.

What happens when you add a new item? Only the item logic syntax check and item logic are executed. But when the quote header pre0phase did the iteration over all items, this new line was not there yet.

Conclusion:

You will get these passed values on a new line item only after clicking the Recalculate button.

  • No labels