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 110 Next »

Lab Info

Lesson: Quote’s Product Configurator (Custom Form Embedded into a Quote)

Target Audience: Certified Configuration Engineer

Estimated Time to complete: TBD

Requirements & Solution Author: Petr Rys

Solution Developer & Lab Author: Marcin Łuczakuczak

User Story / Requirements

Our Customer (Interior design studio) is selling doors to their end-customers. (B2B or B2C)

Some Products are defined as standard skus, but our customer can also manufacture non-standard Products via specification.

Create an embedded Custom Form on Quote to serve as "Custom Product Configurator" for the sales person for non-standard doors.

The sales agent has a Quote, which allows to add Products (skus) into the Product Master a use these Products right away as line items of this Quote.

Product Master contains standard doors as well as accessories and, doors already manufactured before.

Learning Outcomes

At the end of the Lab, you should be able to:

  • Create Embedded Custom Form, Custom Form Type

  • Utilize Input Matrix as Custom Form input

  • Understand Custom Forms Workflow

Provided resources

Logics:

  • QuoteProductConfigurator,

  • QuoteProductConfiguratorHeaderLogic

Company Parameters:

  • Material Adjustment

image-20240321-090530.png

  • Color Adjustment

image-20240321-090615.png

  • Door Size Adjustment

image-20240321-090748.png

Product Master Table that categorizes basic products into specific Product Groups. Each product has a unique identifier starting with the ‘DR’ prefix, and a label starting with the ‘Basic’ prefix.

image-20240305-095408.png

'Product Base Values' Product Extension Table contains each product's base values.

image-20240305-095949.pngECF - Tables.mp4

Acceptance Criteria

  • The user can create a quote of the appropriate type.

  • The new quote has an extra tab with a form containing all the necessary fields.

  • The form enables setting up the basic product downloaded with the Product Master Table.

  • Once the recalculation is complete, the form will show the newly calculated Base Value for the selected products. The result of the calculation should be a new product that includes Doorframes and Locks&handles. Please note that the desired outcome should include all possible combinations of the two pairs of products.

  • Users must select 'Create new Products' to make table changes permanent.

  • If a product with the same configuration does not exist, it is added to the Product Master Table. The calculated Base Value is also added to the Product Extensions Table.

  • The user should be informed what products have been added to the tables.

 Sample solution

ECF-SAmple Solution.mp4

Step-by-step solution

Develop the logic and test in Studio

  1. Go to the Pricefx Studio and create the following logics.

a)  eCFO_ProductConfigurator

Pricefx -> Create Calculation Logic

Nature: Custom form header (customFormHeader)

Name: eCFO_ProductConfigurator

image-20240304-064646.png

Open the logic.json file in the newly created logic and add a label property to the configuration.

{
  "elements" : [ ],
  "formulaNature" : "customFormHeader",
  "status" : "ACTIVE",
  "label" : "Product Configurator",
  "uniqueName" : "eCFO_ProductConfigurator",
  "validAfter" : "2020-01-01"
}

Now, let's deploy the logic onto the partition

  1. Go to Administration → Logics → Header Logics and select the logic type 'Custom Form' to check if the newly created logic is visible on the list.

  2. Access Administration → Custom Form Types from your partition. Create a new form type by clicking on the [+ New Form Type] button located at the top-right corner of the screen.​ Uncheck Embedded option and fill in the form:

image-20240304-071135.png

Specify the values for the parameters listed below.

Name

eCFO_ProductConfigurator

Label

Product Configurator

Header Logic

Product Configurator

Embedded

true

Form type

Custom Form, Quote

  1. Access Quoting → Quote Types from your partition. Create a new quote type by clicking on the [+ Add Quote Type] button located at the top-right corner of the screen.​ Specify the values for the parameters listed below.

Name

ProductConfigurator

Label

Product Configurator

Pricing Logic

QuoteProductConfigurator

Header Logic

QuoteProductConfiguratorHeaderLogic

Configuration

{
  "name": "default",
  "tabs": [
    {
      "name": "header",
      "translationKey": "sfdc_quotes_tabs_header",
      "type": "header"
    },
    {
      "name": "Product Configurator",
      "translationKey": "Product Configurator",
      "typeReference": "eCFO_ProductConfigurator",
      "type": "customForms"
    },
    {
      "name": "items",
      "parameters": {
        "defaultPlacement": "right",
        "flexibleLayout": true
      },
      "translationKey": "sfdc_quotes_tabs_items",
      "type": "items"
    },
    {
      "name": "attachments",
      "translationKey": "sfdc_quotes_tabs_attachments",
      "type": "attachments"
    },
    {
      "name": "workflow",
      "translationKey": "sfdc_quotes_tabs_workflow",
      "type": "workflow"
    },
    {
      "name": "workflow-history",
      "translationKey": "sfdc_quotes_tabs_workflowHistory",
      "type": "workflowHistory"
    },
    {
      "name": "messages",
      "translationKey": "sfdc_entity_tabs_messages",
      "type": "header"
    }
  ]
}

image-20240304-073622.png

QuoteProductConfigurator and QuoteProductConfiguratorHeaderLogic are in your partition. QuoteProductConfigurator calculates the total Invoice price based on the base price and quantity, while QuoteProductConfiguratorHeaderLogic displays the calculation results in a Quote Header.

  1. Access Quoting → Quotes from your partition. Create a new quote by clicking on the [+ NewQuote ] button located at the top-right corner of the screen.​ Choose the type that was created in the previous step. The new form should contain and additional tab called Product Configurator .

image-20240304-075501.pngECF-Quote Type - Product Configurator.mp4

Form creation

Our objective is to design a form allowing users to configure the product according to their preferences. The form will consist of two multiple-selection lists and two tables, where users can input new parameters to customize the basic version of the product.

image-20240308-063120.pngimage-20240308-063333.pngECF-Example Form.mp4

Requirements for Calculation result table:

Columns

Table consists of three columns, each with the appropriate data type.

  • 'Product Id', FieldFormatType.TEXT

  • 'Label', FieldFormatType.TEXT

  • 'Base Price', FieldFormatType.NUMERIC

Generated 'Product Id' value

The new product id should consist of two parts, separated by a dash. The first part is prefix DOORSET, and the second part is a randomly generated string of 10 characters. To meet this condition, you can utilize the api.uuid(10) method.

example: DOORSET-y0QNSxCuYj

Generated 'Label' value

The new label should be created by removing the "Basic" prefix from the product label and separating parameter names with a dash character for each product. Each pair of these two values should be connected using the "/" sign.

examples:

  • Doorframes-DR-0013-1981 x 686-Wood-yellow/Locks&handles-DR-0029-Wood-gray

  • Doorframes-DR-0022-1981 x 838-Steel-white/Locks&handles-DR-0029-Wood-gray

  • 'Base Price' calculation

The calculation of the base price involves adding the parameter coefficients multiplied by the base price to the base price of the basic product. The total base price should be calculated by adding the base price of both products together.

Please find a list of form fields along with their respective requirements below.

Field

Requirments

Doorframes

Product group entry with the following parameters. Ensure that only products with the prefix 'Basic' are included in the selection list. To meet this condition, you can utilize the "Filter.custom" method.

  • paramName: productDoorframes

  • label: Doorframes

  • filterFormulaName: eCFO_ProductFilter

  • filterFormulaParam: Doorframes

Locks&handles

Product group entry with the following parameters. Ensure that only products with the prefix 'Basic' are included in the selection list. To meet this condition, you can utilize the "Filter.custom" method.

  • paramName: productLocksAndHandles

  • label: Locks&handles

  • filterFormulaName: eCFO_ProductFilter

  • filterFormulaParam: Locks&handles

Doorframes Configurator Table

Input matrix with the the following parameters:

  • paramName: DoorframesMatrix

  • label: Doorframes Configurator Table

  • hideAddButton: true

  • hideRemove button: false

  • columns: ['Product Id', 'Label', 'Base Price', 'Door size', 'Material', 'Colour']

  • readOnlyColumns: ['Product Id', 'Label', 'Base Price']

  • 'Door size' column: names from 'DoorSizeAdjustment' lookup table.

  • 'Material' column: names from 'MaterialAdjustment' lookup table.

  • 'Colour' column: names from 'ColorAdjustment' lookup table.

Locks and Handles Configurator Table

Input matrix with the the following parameters:

  • paramName: LocksAndHandlesMatrix

  • label: Locks and Handles Configurator Table

  • hideAddButton: true

  • hideRemove button: false

  • columns: ['Product Id', 'Label', 'Base Price', 'Material', 'Colour']

  • readOnlyColumns: ['Product Id', 'Label', 'Base Price']

  • 'Material' column: names from 'MaterialAdjustment' lookup table.

  • 'Colour' column: names from 'ColorAdjustment' lookup table.

Create new Products

Boolean user entry with the following parameters.

  • paramName: saveChangesBoolean

  • label: Create new Products

Open eCFO_ProductConfigurator logic.json file.

Add new elements to your logic:

Logic element

  • Utils.groovy

In this element, you can add methods and fields to create the logic of the form.

  • Inputs.groovy

This component will be in charge of generating a view for a form.

  • Outputs.groovy

This section will display the result of the calculations in a table format. The table will have columns such as ‘Product Id’, ‘Label’, and ‘Base Price’.

  • SaveData.groovy

This code is responsible for saving the configured product to two tables: Product Master and Product Extensions.

Task

Code example

Create inputMatrix

api.inputBuilderFactory()
            .createInputMatrix("LocksAndHandlesMatrix")
            .setLabel("Locks and Handles Configurator Table")
            .setHideAddButton(true)
            .setHideRemoveButton(false)
            .setColumnValueOptions(['Colour': colors, 'Material': materials])
            .setColumns(['Product Id', 'Label', 'Base Price', 'Material', 'Colour'])
            .setReadOnlyColumns(['Product Id', 'Label', 'Base Price'])
            .addOrUpdateInput(customFormProcessor, 'ROOT')

Take names from company parameters teble.

api.findLookupTableValues('ColorAdjustment')['name']

Add group input entry

api.inputBuilderFactory()
        .createProductGroupEntry('productDoorframes')
        .setLabel("Doorframes")
        .setFilterFormulaName("eCFO_ProductFilter")
        .setFilterFormulaParam("Doorframes")
        .addOrUpdateInput(customFormProcessor, 'ROOT')

Please note the following instructions: You should create the logic "eCFO_ProductFilter" (line 4) responsible for filtering products based on their product group. This filter logic should return a filter object that meets two specific conditions. Firstly, it should only include products from filterFormulaParam product groups. Secondly, it should only download products that contain the word 'Basic' in the Label column.

Nature: Product input filter (productInputfilter)

Name: eCFO_ProductFilter

image-20240304-124047.png

Updating Products table

def addProduct(def product) {
    def label = product["Label"]
    def sku = product["Product Id"]

    def updateProductMap = [
            "sku"       : sku,
            "label"     : label,
            "attribute1": "DoorSet",
            "unitOfMeasure": "EA",
            "currency": "EUR",
            "attribute2": "BU-DoorSet",
            "attribute3": "UNDEFINED",
            "attribute4": "default",
            "attribute5": "default",
            "attribute6": "Introduction",
    ]

    api.add("P", updateProductMap)
}

Updating Products Base Values table

/**
 * Adds a new product's base value via an API call.
 *
 * @param product The product whose base value is to be added. This should
 *                be a map containing at least a "Label", a "Product Id"
 *                and a "Base Price".
 *                Example: 
 *                ['Label': 'Doorframes-DR-0013-1981 x 686-Wood-yellow/Locks&handles-DR-0029-Wood-gray', 
 *                 'Product Id': 'DOORSET-y0QNSxCuYj', 
 *                 'Base Price': '99.99']
 */
def addProductBaseValue(def product) {
    def label = product["Label"]
    def sku = product["Product Id"]
    def basePrice = product["Base Price"]

    def updateValueMap = [
            "name"      : "ProductBaseValues",
            "sku"       : sku,
            "attribute1": label,
            "attribute2": basePrice
    ]

    api.add("PX3", updateValueMap)
}

This task can be accomplished in numerous ways. See below for an example of how to do it, or try to solve the task independently without the use of suggestions.

Logic element

  • Utils.groovy

Add the following code:

//Output.groovy methods
/**
 * This method is used to create a label configuration based on the provided parameters.
 *
 * @param label     The initial label.
 * @param doorSize  The size of the door. If it's available, it will be appended to the label after a dash.
 * @param material  The material of the door. If it's not empty/null, it will be appended to the label after a dash.
 * @param colour    The colour of the door. If defined, it will be appended to the label after a dash.
 * @return          A String that represents the new label. If the original label starts with 'Basic-',
 *                  it is replaced. Dash-separated size, material, and colour are appended if available.
 */
def labelConfigurator(def label, def doorSize, def material, def colour) {
    //Your code: check the requirements for calculation result table
}

/**
 * Calculates the price of a door based on various parameters.
 *
 * @param basePrice  The base price of the door. It must be a valid number.
 * @param doorSize   The size of the door. If not provided, it defaults to "default".
 * @param material   The material of the door. If not provided, it defaults to "default".
 * @param colour     The colour of the door. If not provided, it defaults to "default".
 *
 * @return Returns the total price as a BigDecimal.
 *         The price is calculated as the base price multiplied by the total increase percent which includes the material,
 *         color and door size price increments.
 */
def calculateDoorPrice(basePrice, doorSize, material, colour) {
    //Your code: check the requirements for calculation result table
}

/**
 * This function generates a new product Id in a specific format.
 * The format is "DOORSET-" followed by a UUID of length 10 characters.
 * The UUID is generated using an external API.
 *
 * @return A New formatted Product ID string
 */
def generateNewProductId(def productId) {
      //Your code: check the requirements for calculation result table
}

// SaveData.groovy methods
/**
 * This method creates information messages about products and their base values.
 * Then, it uses this data to set an alert message through the `api`.
 *
 * @param addedProducts  - A list of products represented as Maps where each Map should contain a "LABEL" key. The values of "LABEL" are used to construct a part of the information message.
 * @param addedBaseValue - A list of product base values represented as Maps where each Map should contain "LABEL" and "BASE_PRICE" keys. The values of these keys are used to construct a part of the information message.
 *
 * Note: Any changes or operations executed inside this method does not affect original list of maps passed as `addedProducts` and `addedBaseValue`.
 */
def addInfoMessage(addedProducts, addedBaseValue) {
    addedProducts  = addedProducts.collect({
        it[LABEL]
    }).join("<br>")

    addedBaseValue  = addedBaseValue.collect({
        it[LABEL]+", Base Price: "+it[BASE_PRICE]
    }).join("<br>")

    String productInfo = "<br>Following products has been added to Product Table: <br>$addedProducts <br>"
    String valuesInfo = "<br>Following products has been added to Product Base Values Table: <br>$addedBaseValue<br>"
    String info = ""

    if (!addedBaseValue.isEmpty()) {
        info += valuesInfo
    }

    if (!addedProducts.isEmpty()) {
        info += productInfo
    }

    api.setAlertMessage(info)
}

It's important to note that the methods calculateDoorPrice and labelConfigurator are designed to calculate values for individual products, such as Doorframe or Lock&Handle.

  • Inputs.groovy

// 1. If the form being processed is in the post phase, 
//    immediately return and skip the rest of the code.

// 2. Add a product group entry for doorframes, 
//    filtered by 'Doorframes' to the form.

// 3. Add a product group entry for locks and handles, 
//    filtered by 'Locks&handles' to the form.

// 4. Get the names from the 'Material', 
//    'Color' and 'DoorSizes' look-up tables.

// 5. Create configurator tables for doorframes and locks/handles.

// 6. Get Products data from "productDoorframes" and "productLocksAndHandles"

// 7. If there are any Doorframes products, populate 
//    the DoorframesMatrix of the form with relevant data. 
//    Otherwise, set the matrix to empty.

// 8. If there are any LocksAndHandles products, populate 
///   the LocksAndHandlesMatrix of the form with relevant data. 
//    Otherwise, set the matrix to empty.

// 9. Create a boolean entry in the form processor to hold 
//    the state of whether changes should be saved.
  • Outputs.groovy

// 1. If the form processor is in pre-phase, 
//    exit from the function

// 2. Get the current item from the API

// 3.Find the 'LocksAndHandlesMatrix' values 
//   from the currentItem's inputs. 
//   Assign these values to the LocksAndHandles collection.

// 4.Find the 'DoorframesMatrix' values from the currentItem's inputs.
//   Assign these values to the doorframes collection.

// 5. Use nested loops to add new door product to the newDoors collection    
def newDoors = []
// Iterate over the doorframes collection.
doorframes.each { doorframe ->
    // Configure and assign a label to the doorframe item by using its properties.
    // Calculate price for the doorframe item by using its properties.
    // Use calculateDoorPrice and labelConfigurator methods

    // Iterate over the lockAndHandles collection.
    lockAndHandles.each {
        // Configure and assign a label to the lockAndHandle item by using its properties.
        // Calculate price for the lockAndHandle item by using its properties.      
        // Use calculateDoorPrice and labelConfigurator methods
        
        // Concatenate doorframeLabel and lockHandleLabel separated by a slash.(newLabel)
        // Calculate the total price by adding doorframePrice and lockHandlePrice.(newPrice)
    
        // Create a row for the new door product with its ID, label, and base price.
        def singleRow = [
                'Product Id': Utils.generateNewProductId(),
                'Label'     : newLabel,
                'Base Price': newPrice
        ]

        // Add the new door product to the newDoors collection.
        newDoors.add(singleRow)
    }
}

// 6. Generate a new matrix for the new products

// 7. Add the generated newDoors to de newProducts matrix

// 8. Add or update the output of the processed form by 
//    establishing a new "discountMatrix" result

// 9. return the generated rows
  • SaveData.groovy

// 1. If the customFormProcessor is in the pre-phase, 
//    end the function immediately

// 2. get the current item from the api

// 3. find 'saveChangesBoolean' from currentItem's inputs, 
//    which determines whether changes should be saved
      
// 4. If changes need to be saved, proceed
if (save) {
    // 5. get the output from the out.Output
  
    // 7. create empty lists for addedBaseValue 
    //    and addedProducts
    
    // 8. for each product in output
    output.each { product ->
        // check if the product exists in Product Master table
        // check if product base value exists in Product Extensions table
          
        // if the product does not exist in Product Master table
        // a) add the product
        // b) add the product to the list of added products
          
        // if product base value does not exist in Product Extensions table
        // a) add product base value
        // b) add the product to the list of added base value
    }
    
    // add information message about the added products and base values
    Utils.addInfoMessage(addedProducts, addedBaseValue)
}

DEBUGGING with Studio Editor

  • Create a New Product Discount Manager

image-20240306-132144.pngimage-20240306-132451.png
  • Open logic.json in eCFO_ProductConfigurator.

  • Open Studio Editor.

  • Click on the Inputs tab.

  • Set Context to CUSTOMFORM if needed

  • Set the CustomForm to the previously created form. If you can't find it, use the Reload option at the end of the dropdown list.

image-20240306-132802.png
  • To check the output, please click on the image-20240306-133449.png button and go to the Result tab. Please refer to the following output as an example of what you should expect to see.

image-20240306-133648.png
  • To track the variables that you need, you can use the api.trace method.

DEBUGGING with LOGS

  • To debug your application, you can read the logs of your partition. This will help you identify and fix any issues in your application.

  • Go to Administration → Logs → Logs. A new tab containing partition logs will open in your browser. The display may vary based on your patition preferences.

image-20240306-135036.png
  • To add logging, you should open the logic and place api.logInfo or api.logWarn methods in the appropriate location.

def currentItem = api.currentItem()
api.logInfo("##TEST##", currentItem)
  • Deploy your logic to your designated partition.

  • Open the previously created form and recalculate values. Check the log for expected output.

image-20240306-141506.png

Resources for Further Learning

References

Custom Forms (Reference)

Custom Forms Configuration How-To(s)

Documentation

Custom Forms

Custom Forms General Settings

Custom Forms Header Logics

Company Parameters

Groovy API

setConfigParameter

class Filter

Other

Pricefx chatbot AI

  • No labels