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

In this section you will learn more about Customs Forms.

Lab Info

Lesson: Product Discount Data Management (Standalone Custom Form)

Target Audience: Certified Configuration Engineer

Estimated Time to complete: TBD

Requirements & Solution Author: Petr Rys

Solution Developer & Lab Author: Marcin Ɓuczakuczak

User Story / Requirements

Create a standalone Custom Form to help pricing managers manage discounts per Product Groups and Families.
Product Group is part of Product Master, Product Family is assigned per Product Group.

Each Product group belongs to 1 Product Family as per table Product Family Mapping.
Based on cumulative yearly revenue on the whole Product Family the customer has assigned Discount Level in Discount Level Definition Table.
Based on Product Group and Discount Level achieved by the Customer the sales agent has a "recommended discount" and a "maximum allowed discount" (Target Discount % and Max Discount %)

Details

Product Discount Data Management Custom Form allows user to perform these tasks:

Change Data - Pick a table, Change values in the table, without adding any rows

Add new Product Group - Add a group, Assign it to the product family, Assign to it all the required discounts for all the required levels

Discount for each additional level has to be >= to the previous level, otherwise validation fails

Add a product family - Re assign product groups to reflect new Product Family,

Add new discount level to existing product family - Create new discount levels. Levels must be continuous.

Each level 1 has to start on 0 Min Revenue. Each additional level has to have Min Revenue >= to the previous one

Assign new discounts for new levels. Discount for each additional level has to be >= to the previous level, otherwise validation fails

Learning Outcomes

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

  • Create Standalone Custom Form, Custom Form Type

  • Utilize Input Matrix as Custom Form input

  • Understand Custom Forms Workflow

Provided resources

Logics:

sCFO_ProductDiscountLib

Company Parameters:

ProductFamilyMapping, DiscountLevelDefinition, Discount

Acceptance Criteria

  • User can access a standalone Custom Form within the Analytics section to complete tasks listed in the Details section.

image-20240306-100848.png
  • User can select one of three options: Modify data, Create new Product Group, or Create new Product Family.

image-20240306-101237.png
  • A separate form will be displayed for each option

  • Tables with calculation results are available in the Calculations section, located on the right panel.

image-20240306-101933.png
  • It is recommended to highlight any changes made to tables for better visibility and clarity.

image-20240306-102509.png
  • After the user submits the form, changes should be saved in the corresponding company parameter tables. If a workflow has been added to the form, the changes should be saved after approval.

  • Sample solution

scenario 1 - smaple solution.webm

Step-by-step solution

Develop the logic and test in Studio

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

a)  sCFO_ProductDiscountDataMgmt

Pricefx -> Create Calculation Logic

Nature: Custom form header (customFormHeader)

Name: sCFO_ProductDiscountDataMgmt

image-20240223-124708.png

Add two new elements to your logic:

  • Configurator

  • Calculation

b) sCFO_Workflow

Pricefx -> Create Workflow Logic

Nature: DEFAULT

Workflow Type: Custom Form (customform)

Name: customform

image-20240223-125404.png

Add two new elements to your logic with the suggested code:

  • AbortOnInputGenerationExecution

if (api.isInputGenerationExecution()) {
    api.abortCalculation()
}
  • BuildStep

workflow.addWatcherStep("Sales Manager")
        .withUserWatchers("admin")
        .withReasons("Sales manager needs to approve this form")

workflow.addApprovalStep("Sales Manager")
        .withPostStepLogic("customform_WorkflowPostStep")
        .withApprovers("admin")
        .withReasons("Sales manager needs to approve this form")

This is a sample solution that you can customize according to your requirements. This logic contain two steps. Pay attention to line number 6, which is the name of the Post Step logic that you will create in the next step.

c) customform_WorkflowPostStep (This logic is executed after all steps in the sCFO_workflow logic have been approved.)

Pricefx -> Create Workflow Logic

Nature: Post step (wfStepLogic)

Name: customform_WorkflowPostStep

image-20240223-125200.png

Add a new element to your logic with the suggested code:

  • UpdateData

if (workflowHistory.steps.findAll { it.approvalStep }.every { it.approved } || 
workflowHistory.activeStep?.isEmpty()) {
        //TODO: The logic that will be performed after the user approved all the steps.                                    
}

Pay attention to the condition. The first part checks if all steps have been approved. The second part checks if we have added any steps to our Workflow logic.

  1. Proceed with deploying all logic to your partition.

  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-20240223-135828.png

Add the following code in the Configuration section.

{
  "name": "Default CFO config",
  "steps": [
    {
      "icon": "bars",
      "name": "step1",
      "tabs": [
        {
          "icon": "dashboard",
          "name": "details",
          "type": "details",
          "translationKey": "Management options"
        },
        {
          "icon": "process",
          "name": "workflow",
          "type": "workflow",
          "translationKey": "Workflow"
        },
        {
          "icon": "file-check-alt",
          "name": "actions",
          "type": "actions",
          "translationKey": "Todo list"
        }
      ],
      "translationKey": "Product Discount Data Management"
    }
  ]
}

Here's a breakdown of what each component represents:

  • "name": "Default CFO config" - this is the name of the configuration, which can be used for identification and reference purposes.

  • "steps": [ ... ] - this is an array of steps, where each step represents a tab in the CFO feature. In this example, there is only one step (with the name "step1").

    • { ... } - this is the object representing the current step. In this example, it has three tabs (with the names "details", "workflow", and "actions").

      • "name": "details" - this is the name of the tab, which will be displayed in the user interface.

      • "icon": "dashboard" - this is the icon for the tab. In this example, the icon for the "details" tab is represented by the "dashboard" font awesome icon.

      • "name": "workflow" - this is the name of the second tab.

      • "name": "actions" - this is the name of the third tab.

      • "type": "details"/"workflow"/"actions" - this is the type of the tab. In this example, there are three different types of tabs ("details", "workflow", and "actions").

      • "translationKey": "Management options"/"Workflow"/"Todo list" - this is the key used for translation of the tab labels. These keys should be defined in a property file.

      • "translationKey": "Product Discount Data Management" - this is the key used for translation of the step label.

In this configuration, the first tab is named "details" and uses the "dashboard" icon. It has a name that can be translated using the "Management options" key. The second tab is named "workflow" and uses the "process" icon. It has a name that can be translated using the "Workflow" key. The third tab is named "actions" and uses the "file-check-alt" icon. It has a name that can be translated using the "Todo list" key. The step label itself is also translatable using the "Product Discount Data Management" key.

Pricefx Studio - Create Custom Form Type

Using the Create Custom Form Type feature in Pricefx Studio simplifies the process of creating a Custom Form Type as compared to manual creation through the user interface. To create a custom form type in Pricefx, please follow these steps:

  • Right-click on the project name and select "Pricefx", then choose "Create Custom Form Type".

image-20240322-122515.png
  • Provide a name for the new custom form type.

image-20240322-123006.png
  • Fill in the fields provided below in the General tab.

Property

Value

Name

ProductDiscountManager

Label

Product Discount Manager

Header Logic Name

sCFO_ProductDiscountDataMgmt

Workflow Logic Name

customform

Mode

Module

Module

ANALYTICS

image-20240322-123326.png
  • Navigate to the Steps tab and fill out all the necessary information. Add three Step Tabs, and set Name, Type, Icon, and Translation Key for each.

Name

Type

Icon

Translation Key

dashboard

details

dashboard

Management otions

workflow

workflow

process

Workflow

actions

actions

file-check-alt

Todo list

image-20240322-124214.png
  • Replicate the process for the "Tabs" tab.

image-20240322-124547.png
  • After you have provided all the necessary information, you can deploy the changes to your partition.

  • Access Administration → Custom Form Types from your partition. A new Custom Form Type should be visible on the list and in the Analytics menu.

image-20240322-125740.png

In the image below, you will see two options for Product Discount Manager. The first one was manually created, and the second one was created in Pricefx Studio.

image-20240322-125908.png
  1. Verify if you have completed all the tasks correctly.

  • Go to Administration → Logics. Navigate to the left-hand panel and select "General Workflow Logic". You should see your customform logic on the list.

  • Go to Administration → Logics. Navigate to the left-hand panel and select "Workflow Post Step Logics". You should see your customform_WorkflowPostStep logic on the list.

  • Go to Administration → Logics. Navigate to the left-hand panel and select "Custom Forms". You should see your sCFO_ProductDiscountDataMgmt logic on the list.

  • Our newly created option should now appear on the left-hand side menu.

image-20240215-114513.png

Go to the specified location and create a new form. Customize the label as per your choice, then click on the [Add] button to confirm your selection. If you don't assign a label to your form, the system will automatically set a default label.

image-20240226-070637.png

You should see a view similar to this:

image-20240226-071447.png

Compare the form with the code in Configuration section.

  1. Fetch sCFO_ProductDiscountLib to your partition.

This logic contains two elements:

  • ConstConfig

Includes permanent parameters such as formatting elements, options description, table names, and other configuration elements.

  • CfoProductConfiguratorUtils

Includes some methods that may be helpful when creating a form.

  1. Navigate to the sCFO_ProductDiscountDataMgmn logic and open the Configurator.groovy file. Once you have accessed the file, add the following code snippet to it.

if (customFormProcessor.isPrePhase()) {
    
    //1. Create two variables named 'configuratorName' and 'cfoFormIdKey'. Assign values to them from the Constconfig file.
    //String configuratorName = 
    //String cfoFormIdKey = 

    //2. Assign value to currentCfoForm. Use api.currentItem()
    //def currentCfoForm = 
    
    //3. Get id from current Item. Remember that current item can be null.
    //Long cfoFormId = 

    //4. Get inputs from currentCfoFrom.
    //5. Find input with a name that matches configuratorName
    //6. Take value  
    //7. If the above operation returns null, return an empty map [:]. You can use elvis operator '?:'
    //If you want to prevent NullPointerException, it is recommended to use the safe navigation operator '?'. This will ensure that your code doesn't throw an exception if a null value is encountered.
    //Map configuratorValueBackup = 
    
    //8. Add current form id to the configuratorValueBackup. Use cfoFormIdKey as a key and cfoFormId as value.
    //configuratorValueBackup.put()

    //9. update the input "ROOT" with a map that provides the name, label, url, type and value.
    customFormProcessor.addOrUpdateInput("ROOT",
            [name : configuratorName,
             label: configuratorName,
             url  : configuratorName,
             type : InputType.INLINECONFIGURATOR,
             value: configuratorValueBackup]
    )

}

Take a look at line 4 and the value of configuratorName, which will be created in the next step.

DEBUGING

Make sure to set the proper CustomForm for your logic in the Inputs tab. Run and test the logic afterwards.

image-20240226-121741.png

If you want to verify the results at each step, you can use the 'api.trace()' function.

Make sure that you receive values that are similar to these.

image-20240226-122349.png

If everything appears to be in order, you may proceed with the next step.

  1. Create sCFO_ProductDiscountHeader_Configurator logic.

Pricefx -> Create Calculation Logic

Nature: DEFAULT

Name: sCFO_ProductDiscountHeader_Configurator

image-20240226-124734.png

Add new elements to your logic:

  • ConfiguratorEntry

  • OptionInput

  • ChangeDataInputs

a) add the following code snippet to ConfiguratorEntry.groovy.

In this step, we will create the configuratorEntry object and add a new hidden input with our custom form id to the configuratorEntry before returning it.

import net.pricefx.server.dto.calculation.ConfiguratorEntry

// Create a key for the CFO form ID from specified library
//String cfoFormIdKey = 

// Create a new ConfiguratorEntry using the API
//ConfiguratorEntry configuratorEntry = 

// Create a HiddenEntry InputBuilder with the CFO form ID key and build a context parameter for it
//def param = 

// Add the newly created parameter to the ConfiguratorEntry. Use createParameter method.

// Return the modified ConfiguratorEntry

a) add the following code snippet to OptionInput.groovy.

In this step, we will add a header and a drop-down list with options for selecting individual forms to the configurator.

// Initialize a constant for main paragraph message
//String PARAGRAPH_MESSAGE_MAIN = 

// Get value of the first input from the ConfiguratorEntry, only if it exists
Long formId = 

// Get the product discount configurator utility script from the product discount library
Script productDiscountConfigurator = 

// Instantiate the main entry of the product discount configurator. 
// Use getMainConfiguratorEntry method with formId as a parameter
ConfiguratorEntry mainConfiguratorEntry = 

// Set the paragraph header for the main configurator entry. Use the constant initialized earlier

// Return the main configurator entry

a) add the following code snippet to ChangeDataInputs.groovy.

In this step, we retrieve the option selected by the user and pass it as a parameter to the method that changes the form depending on the selected option.

import net.pricefx.server.dto.calculation.ConfiguratorEntry

// Get value of the first input from the OtionInput.groovy, only if it exists
String selectedType = 
// Use configuratorSwitch method. Pass selectedType as a parameter.

/**
 * This method is used to select and configure different types of ConfiguratorEntries.
 * It also loads the correct messages for each type.
 *
 * @param selectedType The type of ConfiguratorEntry to be used. This could be 'CHANGE_DATA', 'NEW_FAMILY', or 'NEW_GROUP'.
 * @return A ConfiguratorEntry instance configured based on the selectedType.
 * If the getterMethod or message for the selectedType do not exist, the method will return null.
 */
ConfiguratorEntry configuratorSwitch(String selectedType) {
    Script CONFIG_UTILS = libs.sCFO_ProductDiscountLib.CfoConfiguratorUtils
    Script CONST_CONFIG = libs.sCFO_ProductDiscountLib.ConstConfig

    def getterMap = [
            (CONST_CONFIG.CHANGE_DATA) : CONFIG_UTILS.&getChangeDataConfiguratorEntry,
            (CONST_CONFIG.NEW_FAMILY)  : CONFIG_UTILS.&getAddFamilyConfiguratorEntry,
            (CONST_CONFIG.NEW_GROUP)   : CONFIG_UTILS.&getAddGroupConfiguratorEntry,
    ]

    def messageMap = [
            (CONST_CONFIG.CHANGE_DATA) : CONST_CONFIG.CFO_CONFIGURATOR_CONFIG.PARAGRAPH_MESSAGE_CHANGE,
            (CONST_CONFIG.NEW_FAMILY)  : CONST_CONFIG.CFO_CONFIGURATOR_CONFIG.PARAGRAPH_MESSAGE_ADD_FAMILY,
            (CONST_CONFIG.NEW_GROUP)   : CONST_CONFIG.CFO_CONFIGURATOR_CONFIG.PARAGRAPH_MESSAGE_ADD_GROUP,
    ]

    def getterMethod = getterMap[selectedType]
    def message = messageMap[selectedType]

    if (getterMethod && message) {
        return configureEntries(getterMethod, message)
    }
}

/**
 * This method applies configuration to entries.
 *
 * @param getterMethod Method reference for getting configurator entry.
 * @param message The message to be set as paragraph header in configurator entry.
 * @return ConfiguratorEntry The modified configurator entry with new paragraph header.
 */
ConfiguratorEntry configureEntries(def getterMethod, String message) {
    ConfiguratorEntry configuratorEntry = getterMethod.call()
    configuratorEntry.setMessage(message)
    return configuratorEntry
}

EXPECTED RESULT

image-20240227-104923.png

DEBUGGING

  • Open logic.json in sCFO_ProductDiscountHeader_Configurator.

  • Open Studio Editor.

  • Click on the Inputs tab.

  • Set the Context to CONFIGURATOR.

  • Test Logic

  • Click on the Results tab and check output

Your output should resemble the example given below.

image-20240227-105848.pngimage-20240227-111548.png
  1. The next step is to design forms for each of the three available options. Here are the expected results presented below.

  • Change data option. Three separate forms will be created based on the selected choice in the "Select table" dropdown.

a) Product Family Mapping

image-20240227-114302.png

Requirements

  1. "Select Product Group or Product Family" is a drop-down list with two options: Product Group, Product Family

  2. “Select the value you want to change” is drop-down list with unique values from ProductGroup/Product Family colums

  3. “New name” is input type of String

  4. All fields are required.


b) Discount Level Definition

image-20240227-114407.png

Requirements

  1. "Select table" is a drop-down list with three options: Product Family Mapping, Discount Level Definition, and Discount.

  2. After selecting the table we want to modify, a table should display the current data from the corresponding Company Parameters table. The table is not editable.

  3. The user is permitted to modify only a single row of the table.

  4. Use the drop-down list to set keys for each table.

  5. All non-key fields must be updated with edited fields that correspond to their respective values.

For Discount Level Definition option:

image-20240227-121903.pngscenario1 - change data - Discount Level Definition.webm

For Product Family Mapping option:

image-20240227-122016.pngscenario1 - change data - Product Family Mapping.webm

For Discount option:

image-20240227-122246.pngscenario1 - change data - Discount.webm

  1. All fields are required.


  • Add new Product Group option

image-20240227-113353.png

Requirements

  1. “New Product Goup“ is an input type of String

  2. “Assign Product Family” is a drop-down list with unique values of Product Familty colum in Discount Level Definition company parameter table.

  3. The table for adding and deleting discount records should have identical columns as the Discount company parameters table, and provide a user-friendly way to add records.

  4. “Product Group” column shoud have a drop-down list with newly added “New Product Group”. ColumnType: Option

  5. “Discount Level” coulumn should have a drop-down list with list of numbers from 1 to 6. columnType: Option

  6. “Traget Discount” and “Max Discount” are coulmns with columnType: Numeric

  7. All fields are required.

sceanrio1 - Add new Product Group.webm
  • Add new Product Family option

image-20240227-113603.png

Requirements

  1. “New Product Family“ is an input type of String

  2. “Assign new Product Family to an already existing Product Group.” is a checkbox. Default: unchecked.

  3. “Assign to:” is a drop-down list. This is a list of unique values from “Product Family” column in Discount Level Definition company parameters table. This filed will be visible if the “Assign new Product Family to an already existing Product Group” value is set to true (with the checkbox checked).

  4. The table for adding and deleting new discount levels records should have identical columns as the Discount Level Definition company parameters table, and provide a user-friendly way to add records.

  5. “Product Family” column shoud have a drop-down list with newly added “New Product Family”. ColumnType: Option

  6. “Discount Level” and “Min Revenue” are coulmns with columnType: Numeric

  7. All fields are required.

scenario1 - Add new Product Family.webm


SOLUTION

Go to ChangeDataInputs.groovy and look at the configuratorSwitch method. Implement methods for each option chosen by the user to create one of three forms. Each method returns the ConfiguratorEntry object.

/* Fragment of configuratorSwitch method */
def getterMap = [
            (CONST_CONFIG.CHANGE_DATA) : CONFIG_UTILS.&getChangeDataConfiguratorEntry,
            (CONST_CONFIG.NEW_FAMILY)  : CONFIG_UTILS.&getAddFamilyConfiguratorEntry,
            (CONST_CONFIG.NEW_GROUP)   : CONFIG_UTILS.&getAddGroupConfiguratorEntry,
    ]
  • getChangeDataConfiguratorEntry()

ConfiguratorEntry getChangeDataConfiguratorEntry() {
    ConfiguratorEntry changeDataSection = api.createConfiguratorEntry()
    //TODO: create changeDataSection
    //1. Get config map from libraray (CFO_CONFIGURATOR_CONFIG)
    //2. Create option input - name: "ppTableInput", label: "Select table", required: true. 
    //   Get the value from CFO_CONFIGURATOR_CONFIG by OPTIONS.TABLE_OPTIONS key and
    //   pass it as options parameter.
    //3. Get value from ppTableInput 
    //4. Display a specific group of fields based on the option that is chosen.
    //   Check required inputs in follow sections:
    //   - For Discount Level Definition option
    //   - For Product Family Mapping option
    //   - For Discount option
    return changeDataSection
}

For Discount Level Definition option:

label

name

type

1

Discount Level Definition

Discount Level Definition

InputType.INPUTMATRIX

2

Select Product Family you want to change.

productFamilyOption

InputType.OPTION

3

Select Discount Level

discountLevelsOption

InputType.OPTION

4

New Min Revenue

newMinRevenue

InputType.INTEGERUSERENTRY

  • Discount Level Definition: data from DiscountLevelDefinition lookup table. Read-only table.

  • Select Product Family you want to change.: list distinct values found in the "Product Family" column from the DiscountLevelDefinition table.

  • Select Discount Level: Discount levels for selected Product Family.

For Product Family Mapping option:

label

name

type

1

Product Family Mapping

Product Family Mapping

InputType.INPUTMATRIX

2

Select Product Group or Product Family

changeGroupOrFamily

InputType.OPTION

3

Select the value you want to change.

productGroupOption

productFamilyOption

InputType.OPTION

InputType.OPTION

4

New name

newName

InputType.STRINGUSERENTRY

  • Product Family Mapping: data from ProductFamilyMapping lookup table. Read-only table.

  • Select Product Group or Product Family: two options [“Product Group“, “Product Family”]

  • productGroupOption or productFamilyOption : The items are shown according to the selection made in the changeGroupOrFamily option

  • productGroupOption : list of distinct values found in the "Product Group" column from the ProductFamilyMapping table.

  • productFamilyOption : list of distinct values found in the "Product Family" column from the ProductFamilyMapping table.

For Discount option:

label

name

type

1

Discount

Discount

InputType.INPUTMATRIX

2

Select the value you want to change.

productGroupOption

InputType.OPTION

3

Select Discount Level

discountLevelsOption

InputType.OPTION

4

New Target Discount %

newTargetDiscountPct

InputType.INTEGERUSERENTRY

5

New Max Discount %

newMaxDiscountPct

InputType.INTEGERUSERENTRY

  • Discount: data from Discount lookup table. Read-only table.

  • Select the value you want to change.: list distinct values found in the "Product Group" column from the Discount table.

  • Select Discount Level: Discount levels for selected Product Group.


  • getAddFamilyConfiguratorEntry()

ConfiguratorEntry getAddFamilyConfiguratorEntry() {
    ConfiguratorEntry productFamilySection = api.createConfiguratorEntry()
    //TODO: create productFamilySection
    //1. Create two inputs:
    //   a) InputType.STRINGUSERENTRY, name: "productFamilyInput", label: "New Product Family", required: true
    //   b) InputType.BOOLEAN, name: "assignToGroup", label: "Assign new Product Family to an already existing Product Group."
    //2. Get input value from "assignToGroup". If value is true:
    //   a) Find unique family names from the ProductFamilyMapping company parameters table.
    //   b) Create option input - name: "assignProductGroup", label: "Assign to", pass values from step a)
    //3. Create a table. Use result from step 1a as a option for product family option.
    return productFamilySection
}

label

name

type

1

New Product Family

productFamilyInput

InputType.STRINGUSERENTRY

2

Assign new Product Family to an already existing Product Group.

assignToGroup

InputType.BOOLEAN

3

Assign to

assignProductGroup

InputType.OPTION

4

New Discount Levels

New Discount Levels

InputType.INPUTMATRIX

  • Assign to: list of unique names from ProductFamilyMapping lookup table

  • New Discount Levels: user can add and delete rows.

newProductFamily.webm


  • getAddGroupConfiguratorEntry()

ConfiguratorEntry getAddGroupConfiguratorEntry() {
    ConfiguratorEntry productGroupSection = api.createConfiguratorEntry()
    //TODO: create productGroupSection
    //1. Find unique family names from the ProductFamilyMapping company parameters table. 
    //   You can use the API function "findLookupTableValues".
    //2. Create two inputs:
    //   a) InputType.STRINGUSERENTRY, name: "productGroupInput", label: "New Product Group", required: true 
    //   b) InputType.Option, name: "Assign Product Family", label: "assignProductFamily", required: true
    //      Pass the values from first step as a setValuesOptions() parameter
    //3. Get input value from productGroupInput
    //4. Create a table. Use result from step 3 as a option for product group option.      
    return productGroupSection
}

label

name

type

1

New Product Group

productGroupInput

InputType.STRINGUSERENTRY

2

Assign Product Family

assignProductFamily

InputType.OPTION

3

New Discount

New Discount

InputType.INPUTMATRIX

  • Assign Product Family: list of unique values from ProductFamilyMapping lookup table

  • New Discount: user can add and delete rows

addNewProductGroup.webm

Calculations

The results will be displayed as tables in the right panel. Only tables with changes will be shown as copies of parameter tables with updated values.

calc-result.png

Option

Tables that should be displayed

1

Change data → Product Family Mapping → Product Group

Discount Table

Product Family Mapping

2

Change data → Product Family Mapping → Product Family

Product Family Mapping

Discount Level Definition

3

Change data → Discount Level Definition

Discount Level Definition

4

Change data → Discount

Discount Table

5

Add new Product Group

Product Family Mapping

Discount Level Definition

6

Add new Product Family

Product Family Mapping

Discount Level Definition

  1. Open the Calculation.groovy file, which is located in the sCFO_ProductDiscountDataMgmt logic. Once it's open, please add the following code to the file.

if (customFormProcessor.isPostPhase()) {
    // choosenOption is a value from managementOptionsInput
    if (chosenOption == "Add new Product Group") {
        //TODO: Part-1
    } else if (chosenOption == "Add new Product Family") {
        //TODO: Part-2
    } else if (chosenOption == "Change data") {
        //TODO: Part-3
        if (ppTableInput == "Product Family Mapping") {
            //TODO: Part-4
            if (chosenTable == "Product Family") {
            //TODO: Part-5    
            } else if (chosenTable == "Product Group") {
            //TODO: Part-6  
            } 
            //TODO: Part-7
        } else if (ppTableInput == "Discount Level Definition") {
            //TODO: Part-8  
        } else if (ppTableInput == "Discount") {
            //TODO: Part-9
        }
    }
    
}

Steps to follow

1

Part-1

  1. Create a table with the following columns:

  • 'Product Group', FieldFormatType.TEXT

  • 'Discount Level', FieldFormatType.NUMERIC

  • 'Target Discount %', FieldFormatType.PERCENT

  • 'Max Discount %', FieldFormatType.PERCENT

  1. Get data from 'Discount' company parameter table.

  2. Add data from 'Discount' to the newly created.

  3. Get data from 'New Discount' table. This table has been created in getAddGroupConfiguratorEntry method.

  4. Add data from 'New Discount' to the newly created with proper style

  • Tables should use a distinct color for new values.

  • Remember that you need to divide the percentages by 100.

  1. Add new table from point 1 to the customFormProcessor.

  2. Create a second table with the following columns:

  • "Product Group", FieldFormatType.TEXT

  • "Product Family", FieldFormatType.TEXT

  1. Get data from 'ProductFamilyMapping' company parameter table.

  2. Add data from 'ProductFamilyMapping' to the newly created.

  3. Get data from productGroupInput and assignProductFamily and add them to the newly created table. Remember to apply appropriate formatting.

  4. Add new table from point 7 to the customFormProcessor.

2

Part-2

  1. Create a table with the following columns:

  • "Product Family", FieldFormatType.TEXT

  • "Discount Level", FieldFormatType.INTEGER

  • "Min Revenue", FieldFormatType.NUMERIC

  1. Get data from 'DiscountLevelDefinition' company parameter table.

  2. Add data from 'DiscountLevelDefinition' to the newly created.

  3. Get data from 'New Discount Levels' table. This table has been created in getAddFamilyConfiguratorEntry method.

  4. Add data from 'New Discount Levels' to the newly created. Remember to apply appropriate formatting.

  5. Add new Table from point 1 to the customFormProcessor.

  6. Create a second table with the following columns:

  • "Product Group", FieldFormatType.TEXT

  • "Product Family", FieldFormatType.TEXT

  1. Get values from assignProductGroup and productFamilyInput

  2. Get data from 'ProductFamilyMapping' company parameter table

  3. Iterate through 'ProductFamilyMapping' data and change product family for product group. Ensure that only a single value is modified. Remember to apply appropriate formatting.

  4. Add new Table from point 7 to the customFormProcessor.

3

Part-3

  1. Get the changeGroupOrFamily value. This is an input option from changeDataSection.

  2. Get the newName vaue. This is an input string entry from changeDataSection

  3. Get the ppTableInput value. This is an input option from changeDataSection

4

Part-4

  1. Create a table with the following columns:

  • "Product Group", FieldFormatType.TEXT

  • "Product Family", FieldFormatType.TEXT

  1. Get data from 'ProductFamilyMapping' company parameter table.

5

Part-5

  1. Get the productFamilyOption value.

  2. Iterate through 'ProductFamilyMapping'. If productFamilyOption equals product family add newName value to the row. Remember to apply appropriate formatting.

  3. Create a table with the following columns:

"Product Family", FieldFormatType.TEXT

"Discount Level", FieldFormatType.INTEGER

"Min Revenue", FieldFormatType.NUMERIC

  1. Get data from 'DiscountLevelDefinition' company parameter table.

  2. Add data from 'DiscountLevelDefinition' to the newly created from point 3. Iterate through data 'DiscountLevelDefinition' add and new product family. Remember to apply appropriate formatting.

  3. Add new table from point 3 to the customFormProcessor.

6

Part-6

  1. Get the productGroupOption value.

  2. Iterate through 'ProductFamilyMapping'. If productFamilyOption equals product group add newName value to the row. Remember to apply appropriate formatting.

  3. Create a table with the following columns:

  • 'Product Group', FieldFormatType.TEXT

  • 'Discount Level', FieldFormatType.NUMERIC

  • 'Target Discount %', FieldFormatType.PERCENT

  • 'Max Discount %', FieldFormatType.PERCENT

  1. Get data from 'Discount' company parameter table.

  2. Add data from 'Discount' to the newly created from point 3. Iterate through data 'Discount' add and new product group. Remember to apply appropriate formatting.

  3. Add new table from point 3 to the customFormProcessor.

7

Part-7

  1. Add new table from Part-4 to the customFormProcessor.

8

Part-8

  1. Create a table with the following columns:

  • "Product Family", FieldFormatType.TEXT

  • "Discount Level", FieldFormatType.INTEGER

  • "Min Revenue", FieldFormatType.NUMERIC

  1. Get data from 'DiscountLevelDefinition' company parameter table.

  2. Get the discountLevelsOption value.

  3. Get the productFamilyOption value.

  4. Add data from 'DiscountLevelDefinition' to the newly created table. Iterate through data from 'DiscountLevelDefinition'. If product family equals productFamilyOption and discount level equals discountLevelsOption change newMinRevenue for the row. Remember to apply appropriate formatting.

  5. Add new table from point 1 to the customFormProcessor.

9

Part-9

  1. Create a table with the following columns:

  • 'Product Group', FieldFormatType.TEXT

  • 'Discount Level', FieldFormatType.NUMERIC

  • 'Target Discount %', FieldFormatType.PERCENT

  • 'Max Discount %', FieldFormatType.PERCENT

  1. Get data from 'Discount' company parameter table.

  2. Get the productGroupOption value.

  3. Get the discountLevelsOption value.

  4. Add data from 'Discount' to the newly created table. Iterate through data from 'Discount'. If product group equals productGroupOption and discount level equals discountLevelsOption change max discount to (newMaxDiscountPct/100) and target discount to (newTargetDiscountPct/100). Remember to apply appropriate formatting.

  5. Add new table from point 1 to the customFormProcessor.

Code samples

Task

Sample Code

Create a table

ResultMatrix newTable = api.newMatrix('Column_1', 'Column_2',)
newTable.setColumnFormat('Column_1', FieldFormatType.TEXT)
newTable.setColumnFormat('Column_2', FieldFormatType.NUMERIC)

Get data from company parameter table

def getDiscount() {
    def id = api.findLookupTable("Discount").id
    def filters = [ Filter.equal('lookupTable.id', id) ]
    def discountTable = api.find('MLTV2', 0, 0, null, *filters)

    def rows = discountTable.collect {
        [
                productGroup  : it.key1,
                discountLevel : it.key2,
                targetDiscount: it.attribute1,
                maxDiscount   : it.attribute2,
                typedId       : it.typedId]
    }
    return rows
}

How to edit cell style in table

def cell_1 = newTable.styledCell(example_value, "black", "yellow", "bold")
def cell_2 = newTable.styledCell(example_value, "black", "yellow", "bold")
newTable.addRow(cell_1, cell_2)

How to add new table to the customFormProcessor.

customFormProcessor.addOrUpdateOutput(
        [
                "resultName" : "new_name",
                "resultLabel": "new_label",
                "resultType" : "MATRIX",
                "result"     : newTable,
        ]
)


Saving changes to the tables.

We need to ensure that changes in the target tables are displayed accurately. Once the user approves the changes, they should be saved. The customform_WorkflowPosStep logic is responsible for this process.

We need to revisit the logic that controls our Workflow. Our logic consists of two steps. Take a look at BuildStep.groovy

workflow.addWatcherStep("Sales Manager")
        .withUserWatchers("admin")
        .withReasons("Sales manager needs to approve this form")

workflow.addApprovalStep("Sales Manager")
        .withPostStepLogic("customform_WorkflowPostStep")
        .withApprovers("admin")
        .withReasons("Sales manager needs to approve this form")

In line number 6, we see a reference to logic, which will be performed after the user approval. Open the UpdateData.groovy file in customform_WorkflowPosStep logic and add the code to consolidate data in the Company Parameters tables.

This task can be approached in various ways. You can either attempt to preserve the data or use the provided solution below.

if (workflowHistory.steps.findAll { it.approvalStep }.every { it.approved } || workflowHistory.activeStep?.isEmpty()) {
    //part-1
    if (optionInput == "Change data") {
        if (changeGroupOrFamily == "Product Group") {
        //part-2
        } else if (changeGroupOrFamily == "Product Family") {
        //part-3
        } else if (ppTableInput == "Discount") {
        //part-4
        } else if (ppTableInput == "Discount Level Definition") {
        //part-5
        }
    } else {
        //part-6
        if (optionInput == "Add new Product Family") {
        //part-7
        } else if (optionInput == "Add new Product Group") {
        //part-8
        }
    }
}

Steps to follow

part-1

  1. Get user inputs from current item assign it to inputs variable

  2. Get individual field values from user inputs:

  • "managementOptionsInput" - assign it to the optionInput variable

  • "changeGroupOrFamily" - assign it to the changeGroupOrFamily variable

  • "ppTableInput" - assign it to the ppTableInput variable

  • "newName" - assign it to the newName variable

part-2

  1. Get value of "productGroupOption" from user inputs and assign it to the oldName variable

  2. Get "Discount" lookup table Id assign it to the discountID variable.

  3. Create filters:

  • 'ProductGroup' equal oldName

  • 'lookupTable.id' equal discountID

  1. Update "Discount" lookup table. To obtain accurate data, make use of filters.

  2. Get "ProductFamilyMapping" lookup table Id assign it to the productFamilyMappingID variable.

  3. Create filters:

  • 'name' equal oldName

  • 'lookupTable.id' equal productFamilyMappingID

  1. Update "ProductFamilyMapping" lookup table. To obtain accurate data, make use of filters.

part-3

  1. Get value of "productGroupOption" from user inputs and assign it to the oldValue variable

  2. Get "DiscountLevelDefinition" lookup table Id assign it to the discountLevelDefinitionID variable.

  3. Create filters:

  • 'ProductFamily' equal oldValue

  • 'lookupTable.id' equal discountLevelDefinitionID

  1. Update "DiscountLevelDefinition" lookup table. To obtain accurate data, make use of filters.

  2. Get "ProductFamilyMapping" lookup table Id assign it to the productFamilyMappingID variable.

  3. Create filters:

  • 'value' equal oldValue

  • 'lookupTable.id' equal productFamilyMappingID

  1. Update "ProductFamilyMapping" lookup table. To obtain accurate data, make use of filters.

part-4

  1. Get individual field values from user inputs:

  • "productGroupOption" - assign it to the optionInput variable

  • "discountLevelsOption" - assign it to the discountLevelsOption variable

  • "newTargetDiscountPct" - assign it to the newTargetDiscountPct variable

  • "newMaxDiscountPct" - assign it to the newMaxDiscountPct variable

  1. Get "Discount" lookup table Id assign it to the discountID variable.

  2. Create filters:

  • 'ProductGroup' equal productGroupOption

  • 'DiscountLevel' equal discountLevelsOption

  • 'lookupTable.id' equal discountID

  1. Update "Discount" lookup table. To obtain accurate data, make use of filters.

part-5

  1. Get individual field values from user inputs:

  • "newMinRevenue" - assign it to the newMinRevenue variable

  • "productFamilyOption" - assign it to the productFamilyOption variable

  • "discountLevelsOption" - assign it to the discountLevelsOption variable

  1. Get "DiscountLevelDefinition" lookup table Id assign it to the discountLevelDefinitionID variable.

  2. Create filters:

  • 'ProductFamily' equal productFamilyOption

  • 'DiscountLevel' equal discountLevelsOption

  • 'lookupTable.id' equal discountLevelDefinitionID

  1. Update "DiscountLevelDefinition" lookup table. To obtain accurate data, make use of filters.

part-6

  1. Get following tables outputs from current item:

  • "DiscountLevelDefinitionMatrix"

  • "ProductFamilyMappingMatrix"

  • "discountMatrix"

  1. If output is null get an empty list

  2. Filter the rows in their respective tables based on the values of Map.

part-7

  1. Get individual field values from user inputs:

  • "assignProductGroup" - assign it to the assignProductGroup variable

  • "productFamilyInput" - assign it to the productFamilyInput variable

  1. Get "ProductFamilyMapping" lookup table Id assign it to the productFamilyMappingID variable.

  2. Create filters:

  • 'name' equal assignProductGroup

  • 'lookupTable.id' equal productFamilyMappingID

  1. Update "ProductFamilyMapping" lookup table. To obtain accurate data, make use of filters.

  2. Use discountLevelDefinitionEntries and update "DiscountLevelDefinition" lookup table

part-8

  1. Use productFamilyMappingEntries and update "ProductFamilyMapping" lookup table

  2. Use discountEntries and update "Discount" lookup table

Code samples

Task

Code samples

Get user inputs from current item assign it to inputs variable

api.currentItem()?.inputs?.get(0)?.value

Get individual field values from user inputs:

inputs?.get("managementOptionsInput")

Get "Discount" lookup table Id assign it to the discountID variable.

def discountID = api.findLookupTable("Discount").id

Create filters:

  • 'ProductGroup' equal oldName

  • 'lookupTable.id' equal discountID

def discountFilters = [
        Filter.equal("ProductGroup", oldName),
        Filter.equal('lookupTable.id', discountID)
]

Update "ProductFamilyMapping" lookup table. To obtain accurate data, make use of filters.

api.find('LTV', 0, *discountFilters).collect {
    def entry = [
            lookupTableName: "ProductFamilyMapping",
            id             : it.id,
            valueType      : it.valueType,
            typedId        : it.typedId,
            name           : newName,
            value          : it.value
    ]
    api.addOrUpdate("LTV", entry)
}

Use discountEntries and update "Discount" lookup table

discountEntries.each {
    api.addOrUpdate("MLTV2", [
            lookupTableName: "Discount",
            key1           : it["Product Group"].value,
            key2           : it["Discount Level"].value,
            attribute1     : it["Target Discount %"].value,
            attribute2     : it["Max Discount %"].value
    ])
}

Get "DiscountLevelDefinitionMatrix" table outputs from current item

api.currentItem()?.outputs?.find { it.resultName == "DiscountLevelDefinitionMatrix" }

If output is null get an empty list

discountLevelDefinitionTable = discountLevelDefinitionTable ? discountLevelDefinitionTable : []

Filter the rows in their respective tables based on the values of Map.

def discountLevelDefinitionEntries = discountLevelDefinitionTable.result.entries.findAll { it['Product Family'] instanceof Map }

Resources for Further Learning

References

Custom Forms (Reference)

Custom Forms Configuration How-To(s)

Unity Documentation

Custom Forms

Custom Forms General Settings

Custom Forms Header Logics

Company Parameters

Groovy API

setConfigParameter

Other

Pricefx chatbot AI

  • No labels