Recipe: Complete Case Study for Price Setting Package (WIP)

This recipe is outdated

Business Scenario

This section presents a detailed case study for Price Setting Package. With the provided sample data you will be able to set up a complex business scenario and you will learn how to deploy, configure and tweak Price Setting Package from scratch.

In this business use case we want to generate price grids and simulate impact of an additional discount given on the level of dedicated customer and product segments.

The general Lookup Key in this scenario is:

  • Product hierarchy lvl 1

  • Product hierarchy lvl 2

  • Product hierarchy lvl 3

  • Product hierarchy lvl 4

We want to create the following pricelists:

Price List Level

Calculation

Business Use Case

Tarif Global

Calculation of a Global List Price. Calculation of 4 different Price Strategies and additional manual prices on the SKU level.

Generate a Global List Price.

Local Tarif (Tarif A, Tarif B, …)

We want to calculate a Local List Price. For calculation we want to take Tarif Global and add region specific markup based on the granularity of the Lookup Key.

Create a Local Price List. This price list is later distributed to the customers.

Simulation on customer level

We want to create a price list on the level of a given customer. We take Tarif Group where the customer belongs and apply an additional customer discount based on granularity of the customer and the first two levels of the product hierarchy.

Target of this step is a “simulation” how a customer discount would affect the business. With a given customer discount we want to compare some KPIs considering the customer will buy the same quantities as last year.

The customer price list should apply an additional customer discount. The customer discount in maintained on two levels of the generic lookup key plus the Customer ID.

Lookup Key for the customer specific discount is:

  • Customer ID

  • Product hierarchy Lvl 1

  • Product hierarchy Lvl 2

We want to have a physical price list for the Global level and for every level of the regions. We want to create (as needed) an LPG on the Customer ID level to simulate the impact of the customer specific discount. So we create an LPG with a given customer and put all products in it. After maintenance of the customer specific discount we calculate the LPG. We filter for all SKUs where the customer had sales in the last year. We assume that sales volume will be the same in the next year, and so we can compare the different KPI (Turnover, Margin) with the newly created customer discount.

We want to use the following Pricing Strategies to calculate prices on the Global level:

Pricing Strategy

How to Calculate

Manual Price

Manual price, stored in a separate table.

Price List FR

Simple lookup in historical price list for FR.

Price List DE

Simple lookup in historical price list for DE.

Max Transacted Price

Maximum transacted price for the given SKU.

Cost+

Take the cost and add a relative “plus” to it. The markup is maintained on the level of the Lookup Key.


Accelerator Package as Solution

To implement this scenario, we use the Accelerate Price Setting Package. First we do a gap analysis and then we see how we can enhance the solution to cover the business scenario.

Gap Analysis “Requirements vs. Accelerator Package”

Requirement

Price Setting Package

Gap

How to Achieve It

Create Global Price List

check mark


Independent Level Price List in Price Setting Package

Create Local Price Lists based on a region and apply a markup on the global price

check mark


Dependent Level Price List in Price Setting Package

Price Strategies

Lookup for DE, FR prices check mark


Lookup Engine

Exception Prices on SKU level check mark


Price Exception Module

Highest transacted price cross mark

We need a custom engine for price lookup in transaction data.

Create a custom engine, using the library functionality of Price Setting Package Library

Cost+ check mark


Adjustment Engine

Manual Price

Exception Table check mark


Manual Override Module with Price Exception stored in a PP

Create customer level price

cross mark

Customer level pricing has to be configured completely as it is not OOTB functionality of Price Setting Package

Fork of DependentPriceListLogic, with tweak to support pricing on the customer level

Setup of Price Setting Package OOTB functionality

Based on this business use case we will show you the complete deployment, configuration and customization of Accelerate Price Setting Package. This can be used as a reference guide for the Accelerator.

Deployment in Platform

Pricing Level

We want to have a simple 2-level pricing hierarchy. We will have one Independent Level “Tarif Export” and 6 Tarif Zones. After upload of Pricing Levels we will have the following structure in Pricefx:

image-20210301-085841.png
Lookup Keys

Configuration of the Package

Set Up Cost Data

The Cost data is located in a Product Extension.

We have to configure the following behavior of the Cost data lookups:

Source Type

Product Extension

Name of Product Extension

ProductCosts

Source Field

Cost

  • Dependency Mapping





The following configuration lines will be created by the configuration wizard:

image-20210301-084737.png
image-20210301-085234.png


As we have not configured validity periods and currency, the system expects one record per SKU (and pricing level) and currency of the cost data will be the base currency of the depending pricing level.


Set Up Cost+ Pricing Strategy

Setup of the Cost+ Pricing Strategy is done in the “Strategy Definition” PP. It should be possible to use the default entry there. It has to look like this:

Option

Value

Comment

Strategy Name

Cost+


Level

Independent

We only have the row with “Independent” Level.

Cost+ Strategy is calculated on Global Level and we do not want to re-calculate Cost+ with local cost (or local plus).

Calculation Engine

Adjustment Engine


Additional Engine Configuration

CostPlusAdditionalConfig

Simple additional config, selecting the Calculation Mode of Adjustment Engine:

image-20210301-100515.png

Strategy Calculation Parameters

PRODUCT_COST, PLUS_FOR_PRODUCT_PERCENTAGE, PLUS_FOR_PRODUCT_ABSOLUTE

The default set of parameters passed to Engine in OOTB Cost+ implementation.

image-20210301-100018.png

With this we can calculate Cost+ Prices. The “Plus” is taken from the “CostPlus” PP. This is the OOTB built-in table for this. You can enter data like this:

image-20210301-100738.png


Set Up Lookup Pricing Strategy for Price List DE/FR Lookups

We want to have a simple lookup in some defined tables as a pricing rule. In this case we want to look up defined fixed price lists.

We can use Lookup Engine for this. First we need the configuration rows in “Strategy Definition” PP. In this chapter you will see the definition for “Price List DE”; the other price list is similar.

Option

Value

Comment

Strategy Name

Pricelist DE


Level

Independent

We only have the row with “Independent” Level.

Calculation Engine

LookupEngine


Additional Engine Configuration

PL_DE_Additional

Parameter that contains the additional configuration for Lookup Engine.

Strategy Calculation Parameters

SKU; TARGET_DATE


image-20210301-101342.png

To make Lookup Engine work, we need to have proper additional engine parameters. The PP has to look like this:

image-20210301-101723.png


Set Up Strategy Selection

In this case we want to use the strategies in the following order:

  1. Pricelist DE

  2. Pricelist FR

  3. Max Transaction Price (will be added later as Custom Strategy)

  4. Cost+

These prices will be calculated on the Independent Level (Global Price List). The Local Price List will use the global price and add a local markup factor.

We add these strategies to Base Strategy Selection PP. We use Base Strategy Selection (and not Strategy Selection) because we only want to show the result when a proper price is found. We are not interested in lines with empty prices.

image-20210301-102338.png


Set Up Manual Override Module

For the Manual Override Module we want to have Price Exception Lookup in Independent Price List. No manual overrides in Dependent Price List because it should take the manual price from the Global Price List and apply a markup like in every other price strategy.

We want to store the Price Exceptions in the PricingExceptions PP. There we have the “PriceException” field with the manual price. We use another field “Dependency Level Name” to link the price exception to the Global Price List.

After execution of the wizard, we should have the following configuration in the PriceSettingConfig PP:

image-20210301-144354.png

To map the value to the Pricing Level (Global Price List), we use the Dependency Level Name field from the Pricing Exceptions PP and map to the field ISO Code (Preference1) in the Dependency Configuration PP.

image-20210301-144528.png


Set Up Transaction & Forecast Configuration

We want to take transaction data from Datamart.

Create Independent Price List (Tarif Global)

The basic setup is done now and so we can create the price grids. First we have to create the Independent Price Grid (Tarif Global). You can add all SKUs to it and calculate.

Option

Value

Default Pricing Logic

IndependentPriceListLogic

ResultPrice

Final Price

Independent Level Name

Tarif Global

Output Elements

Select all

Default Fields

Enable the “Hide in item list” option for all elements.

After the price grid is created, you can review it.

Create Independent Price Lists (Tarif Local)

After creation and calculation of the global level price grid we have to connect the local level price grid to the global one. Therefore we have to search the id for the global price grid and fill it into the DependencyConfiguration PP.

image-20210301-124455.png


image-20210301-124414.png

Then we have to fill in the markup factors between Global and Local prices. We can find the TarifXDependencyAdjustment PP for every pricing level. Here we have to create data rows with the markup factors.

image-20210301-124647.png

Now we can create the price grids.

Option

Value

Comment








Custom Tweaks for Price Setting Package

We need to enhance Price Setting Package in two ways. We need to add a custom strategy and we need to tweak the logic for adding prices based on customer discount.

Add Custom Engine for “Highest transacted Price”

We want to create a custom engine that will return the maximum price from transaction data. We use one of our library functions to return it.

First we create a new Groovy Library “CustomStrategyEngines”. In this new library, we create a new element called TransactionEngine.

In this engine we add the following code:

BigDecimal calculatePrice(String sku) {
    String inputDependencyLevel = "Global" // We want to map always the the same level, so we have it hardcoded here

    def configManager = libs.PriceBuilderCommonElementUtils.ConfigManager.getInstance(inputDependencyLevel)
    def transactionConfigManager = libs.PriceBuilderCommonElementUtils.TransactionConfigManager.getInstance(configManager)

    def transactionAndForecastManager = libs.PriceBuilderCommonElementUtils.TransactionAndForecastManager.getInstance(transactionConfigManager)
    Map data = transactionAndForecastManager.getTransactionDataForDashboard(sku)

	def dataMap = data.lastYear.getAt(sku) as Map
  
    def maxTransactionPrice = dataMap?.Max

    if (!maxTransactionPrice) api.throwException("No Transacions")
    return maxTransactionPrice
}

To make the custom engine work, we have to add it to the “StrategyDefinition” PP.

image-20210222-124813.png


Once this is done, we can use the new custom strategy in the same way as the OOTB built-in strategies.

Tweak Price Setting Package to Calculate Customer Level Prices

We need to add a customer dimension in the price calculation in Price Setting Package. We want to start with the “Local” Price List and apply an additional customer discount. Therefore we take the “IndependentPricelistLogic” as a starting point. As we want to keep the “Local Price List”, we create a fork of the logic. This will give you a sample idea how it can be done by tweaking the Accelerator Package. Please note that this is just PoC grade solution.


Add Customer as Input Dimenstion

Create an additional element for the customer ID input:

image-20210419-092500.png

In C_CustomerInput, you can just add a customer picker for the configuration of the price list:

Customer("customerId")

In the new element “C_customerObject” you can query the customer from the customer master data:

if (api.isSyntaxCheck()) return

def customerId = api.getElement("C_CustomerInput")

def customer = api.find("C", Filter.equal("customerId", customerId))

if (customer && customer.size() > 0)
	return customer.first()


Determine “Dependency Level” from the selected customer

In the element “DependsOn”, we input a hardcoded value of the only independent level we have.

To get the dependency level name, we take it from our attribute in the customer master, so we change the element “DependencyLevelName” to the following:

if (api.isSyntaxCheck()) return

//Map inputConfigurator = api.getElement("InputConfigurator")
//Map inputConfig = libs.PriceBuilderCommonElementUtils.ConstConfigs.getInputConstConfig()

//return inputConfigurator.getAt(inputConfig.INPUT_PARAMETERS.DEPENDENT_LEVEL_NAME)
def customer = api.getElement("C_customerObject")
return customer.attribute4 ?: "Tarif Zone A"

Now it will take the dependency level name of the customer given from the customer attribute. We add one “default” as a fallback.


Add Customer Discount to Pricing Logic

We have the customer discount stored in PP table named “CustomerDiscount”.

The key in this table is:

  • Customer ID

  • First two levels of generic lookup key

So we build the hierarchical lookup key for this lookup and use the library function for hierarchical lookup.

For this we create a new element “C_CustomerDiscount” with the following code:

String parameterName = "CustomerDiscount"

List productDimensionalLookupKeys = api.getElement("DimensionalLookupKeys").Products
def customerId = api.getElement("C_customerObject").customerId

List<String> inputKeys = [customerId, productDimensionalLookupKeys[0], productDimensionalLookupKeys[1]]
api.trace("Lookup Key", inputKeys)
def lookupResult = libs.PriceBuilderCommonElementUtils.PriceParameterUtil.vLookupHierarchic(parameterName, inputKeys, true)

api.trace(lookupResult)

if (lookupResult.size() > 0) {
  return lookupResult.first().attribute1
}


Apply Customer Discount in Price Calculation

We have to pass the new customer discount to a price calculator, so we need to modify the element “CalculatedPrices”:

return api.getElement("WarningManager").tryToExecuteElement("CalculatedPrices") {
    
    (...)

    //Calculation of prices
    priceManager.processStrategiesToCalculate(productDimensionalLookupKeys, useCache)
    Map independentItem = api.getElement("IndependentLevelCalculatedData")
    if (independentItem) {
        BigDecimal customerDiscount = api.getElement("C_CustomerDiscount") ?: 0.0
        priceManager.addIndependentLevelAdjustedPrice(productDimensionalLookupKeys, independentItem, api.getElement("DependentLevelAdjustment"), useCache, customerDiscount)
        priceManager.addIndependentLevelPrices(independentItem, api.getElement("DependentLevelAdjustment"),true ,customerDiscount)
    }

    (...)
}

Now we have to tweak the Groovy Library “PriceBuilderCommonElementUtils”.

First we take a look at the element “PriceManager”. We have to make the customer discount an optional parameter in the price calculation method of price manager:

        addIndependentLevelPrices       : { Map independentLevelItemData, BigDecimal dependencyLevelAdjustment, Boolean showCustomerLevel = false, BigDecimal customerLevelAdjustment = 0 ->
               
               (...)
               
            },
        addIndependentLevelAdjustedPrice: { List strategyLookupKeys, Map independentLevelItemData, BigDecimal dependencyLevelAdjustment, boolean useCache, BigDecimal customerLevelAdjustment = 0 ->  // We use header level information since this strategy represents independent final price
                
                (...)
                
            },


Once the two closures “addIndependentLevelPrices” and “addIndependentLevelAdjustedPrice” have the optional customer discount parameter we have to calculate the customerLevelPrice (and the final price) according to that discount:

addIndependentLevelPrices       : { Map independentLevelItemData, BigDecimal dependencyLevelAdjustment, Boolean showCustomerLevel = false, BigDecimal customerLevelAdjustment = 0 ->
               
               (...)
               
                priceManagerImpl.getStrategiesForProduct().findAll { it.independentLevelDefinition != null }
                        .each { Map dependentLevelStrategy ->
                            boolean independentLevelFirst = dependentLevelStrategy.independentLevelDefinition.independentPrioritized == strategiesDefinitionConstConfig.INDEPENDENT_PRIORITIZED_STRING
                            Map newIndependentLevelEntry = getInternalStrategyTemplate()
                            newIndependentLevelEntry.strategyName = dependentLevelStrategy.strategyName + pricingProcessConstConfig.INDEPENDENT_STRATEGY_SUFFIX
                            newIndependentLevelEntry.isPriceNullable = true
                            newIndependentLevelEntry.dependentLevelPrice = priceManagerImpl.getDependentLevelAdjustedPrice(dependentLevelStrategy.independentLevelPrice, dependencyLevelAdjustment)

                            newIndependentLevelEntry.showCustomerLevel = showCustomerLevel
                          	newIndependentLevelEntry.customerLevelPrice = priceManagerImpl.getDependentLevelAdjustedPrice(newIndependentLevelEntry.dependentLevelPrice, customerLevelAdjustment)

                            newIndependentLevelEntry.calculatedPrice = newIndependentLevelEntry.customerLevelPrice
                            
                            (...)
                        }

                return
            },
            addIndependentLevelAdjustedPrice: { List strategyLookupKeys, Map independentLevelItemData, BigDecimal dependencyLevelAdjustment, boolean useCache, BigDecimal customerLevelAdjustment = 0 ->  // We use header level information since this strategy represents independent final price
                String strategyName = pricingProcessConstConfig.INDEPENDENT_STRATEGY_NAME_GENERATOR(independentLevelItemData.decision)
                BigDecimal dependencyLevelAdjustedPrice = priceManagerImpl.getDependentLevelAdjustedPrice(independentLevelItemData.price, dependencyLevelAdjustment)
				BigDecimal customerLevelAdjustedPrice = priceManagerImpl.getDependentLevelAdjustedPrice(dependencyLevelAdjustedPrice, customerLevelAdjustment)
                
                (...)
            },


Now the Price Manager is able to calculate the customer level price when we provide the customer discount. When there is a customer level price, it is taken as the final price.


Show Customer Level Price in “Prices” Pop-Up

Now we want to add the customer level price to the “Prices” pop-up in the PL/LPG when we are in the customer level calculation.

First we have to add a new colum “Customer Level Price” to the static config of the config manager. So we add it in the element “ConstConfigs”:

DEPENDENT_PRICES_POPUP_LABELS      : [[NAME: "ORDER", LABEL: "Order"],
                                                                                       [NAME: "STRATEGY_NAME", LABEL: "Price Type"],
                                                                                       [NAME: "NO_ADJUSTED_INDEPENDENT_LEVEL_PRICE", LABEL: "Independent Level Price", TYPE: "MONEY"],
                                                                                      
																					   [NAME: "GROSS_PRICE", LABEL: "Dependent Level Price", TYPE: "MONEY"],
																					   [NAME: "CUSTOMER_LEVEL_PRICE", LABEL: "Customer Level Price", TYPE: "MONEY"],
                                                                                       [NAME: "MESSAGE", LABEL: "Reason"],
                                                                                       [NAME: "FINAL_PRICE", LABEL: "Final Price", TYPE: "MONEY"],
                                                                                       [NAME: "MARGIN", LABEL: "Margin", TYPE: "PERCENT"],
                                                                                       [NAME: "MESSAGE_TYPE", LABEL: "Message Type"]]]]


To make sure the new colum is only shown in the Customer Level Mode and filled with Customer Level Price, we have to go back to the element “PriceManager” and add it to the “getPriceOutputManager()” function:

protected Map getPriceOutputManager(Map constConfig, boolean isIndependentCalculation) {
    Map priceOutputManager
    priceOutputManager = [
           
           (...)
           
              if (!isIndependentCalculation && !showCustomerLevel) columns.remove(4) // seems hacky, but works for now
              
           (...)
                    if (isIndependentCalculation) {
                        cells = [indexCell, strategyCell, grossPriceCell, messageCell, finalPriceCell, marginCell, messageTypeCell]
                    } else {
                        def independentLevelPriceCell = resultMatrix.styledCell(strategy.independentLevelPrice)
                        def dependentLevelPriceCell = resultMatrix.styledCell(strategy.dependentLevelPrice)
						api.logInfo("Matrix: " + strategy)
                      	def customerLevelPriceCell = finalPriceCell//resultMatrix.styledCell(strategy.customerLevelPrice)
                      if (showCustomerLevel) {
                          cells = [indexCell, strategyCell, independentLevelPriceCell,dependentLevelPriceCell, customerLevelPriceCell, messageCell, finalPriceCell, marginCell, messageTypeCell]
                      	
                      } else
                          cells = [indexCell, strategyCell, independentLevelPriceCell,dependentLevelPriceCell, messageCell, finalPriceCell, marginCell, messageTypeCell]
                    }


    (...)
        
    return priceOutputManager
}


As this function is called by the price manager, we have to tweak the following line to call with the correct (optional) parameter:

def getInstance(def exceptionsManager, def warningManager, def configManager, def moduleManager, boolean isIndependentCalculation, def roundingRulesConfigManager = null) {
    
    (...)

    def manager
    manager = [
          
            (...)
          
            getStrategyMatrix               : { boolean showCustomerLevel = false ->
                priceOutputManager.getStrategyMatrix(priceManagerImpl.getStrategiesForProduct(), showCustomerLevel)
            },
            
            (...)


Restrict Transaction/Forecast Data to Customer

One last thing we have to tweak is the transaction/forecast module. We have to make sure that when we are in a customer PL/LPG, only the transaction data for the one selected customer is queried.

  • Remove dependency mapping

  • Apply customer id filter