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 2 Current »

Lab Info

Lesson

Agreements & Promotions

Category / Topic / Section

Pricefx Core / Agreements & Promotions / CFG 2

Target Audience

Certified Configuration Engineer

Estimated Time to complete

1:00

Lab Disclaimer: Although this lab offers comprehensive information on the mentioned capability, it lacks a Pricefx environment, that is only available in our instructor-led training, where you can interact with these features and practice using them.

For further insights into the distinctions between our on-demand learning paths and instructor-led training, please click here.

Labs in this section require a specific training partition to complete. To request your training partition please write a request on this email: maros.grman@pricefx.com.
The partition request can take a few days to be processed, once the verification is finished you will receive the credentials by email.

Learning Outcomes

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

  • implement 2nd Condition Type with different conditions than in Lab 1a

  • implement simple impact analysis, to review impact of the negotiated conditions on the business

1SneekPeek

Figure 1. Sneakpeek of the implemented Promotion Condition Type

Pre-requisites

In order to be able to complete this laboratory exercise, please complete the prerequisites before you proceed.

Provided resources

Review the provided resources, to get familiar with their content

  • Datamart "Transaction"

Labs

This lab expects, that you already have in your partition a solution of the preceding labs:

  • Agreements & Promotions Engineering - Lab 1a

If you need to work on this lab without finishing the preceding ones first, you can deploy the solutions of the preceding labs provided in a ZIP file attached to the course.

User Story / Requirements (for Lab 1a & 1b)

As a Sales Manager, I want to negotiate 2 types of promotion conditions with the customer, so that I can give incentive to the customer and increase sales.

As a Sales Manager, I want to see an impact analysis on each line of contract, so that I can easily compare the benefits of the contract with losses.

Details

The 2 types of promotion conditions are:

  • Promotion Discount

    • the size of the on-invoice discount is a fixed percentage value

    • the discount is negotiated for set of products, selected by the manager

  • Volume Discount

    • the size of the on-invoice discount depends on the quantity/volume negotiated. So the user need to enter a several pairs of values, for various tiers/levels - e.g.:

      • when more than 5 pieces are ordered, the discount should be 1%

      • when more than 10 pieces are ordered, the discount should be 2%

    • the discount is negotiated for:

      • set of customers

      • set of products

The 2 types of on-invoice discounts have a separate adjustments in a waterfall calculation, so they can be later (e.g. on Quote) both used at the same time.

Acceptance Criteria (for Lab 1b)

Main:

  • A Condition Types exist for "Volume Discount"

  • for "Volume Discount"

    • User can enter:

      • Customer Group

      • Product Group

      • Promotion Discount size in %

    • Upon Recalculation, the system provides:

      • Revenue Last Year - how much money the selected group of customers paid us on invoice price last year. Last year means, that you take the period specified by the user, and shift it back by 1 year. Invoice price data are available in Datamart "Transaction".

      • Impact on Revenue - how much money would we pay to the group of customers last year (if this discount would be active already last year)

Other:

  • the product and customer groups are negotiated separately on each promotion line type, i.e. no need for the product and customer group selector on the contract header.

Implementation Steps

If you already have some previous knowledge about implementation/configuration of the PromotionManager module, we encourage you to try to implement it first on your own based on the information provided in the User Story and Acceptance Criteria.

Step: Implement Condition Type for "Volume Discount"

  1. In Studio, create a new Calculation Logic of Nature Contract.

    1. Name it "ContractVolumeDiscount"

    2. for label use "Volume Discount".

    3. The Validity will be moved backwards, in case you would like to experiment with data from the previous year.

  2. The user wants to select a set of customers, add Element "CustomerGroup" with code:

    final String INPUT_NAME = "CustomerGroup"
    
    if (api.isInputGenerationExecution()) {
        api.inputBuilderFactory().createCustomerGroupEntry().getInput()
    } else {
        return CustomerGroup.fromMap(input[INPUT_NAME])
    }
    1. this Element should not display its value in results, unless you want to use that for debugging

  3. As the user wants to select a set of products, add Element "ProductGroup" with code:

    final String INPUT_NAME = "ProductGroup"
    
    if (api.isSyntaxCheck()) {
        api.inputBuilderFactory().createProductGroupEntry().getInput()
    } else {
        return ProductGroup.fromMap(input[INPUT_NAME])
    }
    1. this Element should not display its value in results, unless you want to use that for debugging

  4. the user need to enter a several pairs of values, for various tiers/levels of the Promotion Discount thresholds. These values also need to be on the Price Record later. Add a new Element "VolumeDiscountMap":

    final String INPUT_NAME = "VolumeDiscount"
    final String INPUT_LABEL = "Volume Discount"
    final String COLUMN_QUANTITY = "Quantity"
    final String COLUMN_DISCOUNT = "Discount %"
    
    if (api.isInputGenerationExecution()) {
        api.inputBuilderFactory().createInputMatrix(INPUT_NAME)
                .setLabel(INPUT_LABEL)
                .setColumns([COLUMN_QUANTITY, COLUMN_DISCOUNT])
                .setColumnValueOptions()
                .getInput()
    
    } else {
        /* [ {Quantity=5, selected=false, Discount %=1},
             {Quantity=10, selected=false, Discount %=2},
             {Quantity=50, selected=false, Discount %=3} ]  */
    
        return input[INPUT_NAME]
                ?.findAll { (it[COLUMN_QUANTITY]) && (it[COLUMN_DISCOUNT]) }
                ?.collectEntries {
                    [
                        (it[COLUMN_QUANTITY] as BigDecimal)
                        :
                        ((it[COLUMN_DISCOUNT]) ? ((it[COLUMN_DISCOUNT] as BigDecimal) * 0.01) : null)
                    ]
                }
    
    }
    1. the element should not be displayed in the results, unless you want to see the value for debugging purposes. In that case, do not set any formatting.

  5. For calculations, we need to retrieve the information about Start Date, which the user is entering on the contract header. In the Contract Header logic, you’re passing this value to the binding variable api.global.startDate. The global variable keeps the values across the whole re-calculation of the contract, so we can read it on the line item. Add a new Element StartDate:

    if (api.isDebugMode()) {
        api.global.startDate = api.inputBuilderFactory()
                                  .createDateUserEntry("startDate")
                                  .getInput()  
    }
    
    return api.global.startDate

    the input will be used only, when you Test Logic in Studio, becasue there you will not have the information from the Header available.

  6. In the same way, we need also the endDate. Add new Element EndDate:

    if (api.isDebugMode()) {
        api.global.endDate = api.inputBuilderFactory()
                                .createDateUserEntry("endDate")
                                .getInput()
    }
    
    return api.global.endDate
  7. As you do not need any more input fields, let’s abort the logic, if it is executed in Syntax Check mode. Add a new Element AbortOnInputGeneration with code:

    if (api.isInputGenerationExecution()) {
        api.abortCalculation()
    }
  8. after the contract is approved, you would like to have in the Price Record also the name of the Condition Type. So you will find out the Condition Type of the current line and store it on the line item, so that it is later copied to the Price Record. Make new Element "ContractTermType" with code:

    return api.currentItem()?.contractTermType
    1. ensure this value is available in results

  9. To be able to pass the Volume Discount tiers entered by the user to the PriceRecord you will need to change the shape of the result little bit, so that it maps to Price Record well and we are able later to read and use it from there. Add new Element "VolumeDiscount" with code:

    return api.jsonEncode(out.VolumeDiscountMap)
  10. The user would like to see the impact of their decisions on the business. Since we cannot see the future, the customer asked to use data from last year as an estimation for the following months. Add a new Element "RevenueLastYear"

    1. the result of this element should be visible in the results

    2. set the Format Type to Money (EUR)

    3. with code:

      final String COLUMN_REVENUE = "InvoicePrice"
      final String COLUMN_DATE = "InvoiceDate"
      
      // Find the time period in previous year for the analysis
      def startDate = api.parseDate("yyyy-MM-dd", out.StartDate)
      startDate?.set(year: (startDate ? (startDate[Calendar.YEAR] - 1) : null))
      
      def endDate = api.parseDate("yyyy-MM-dd", out.EndDate)
      endDate?.set(year: (startDate ? (endDate[Calendar.YEAR] - 1) : null))
      
      
      def ctx = api.getDatamartContext()
      def q = ctx.newQuery(ctx.getDatamart("Transaction"))
                  .select("SUM(${COLUMN_REVENUE})", COLUMN_REVENUE)
                  .where(
                      Filter.greaterOrEqual(COLUMN_DATE, startDate),
                      Filter.lessOrEqual(COLUMN_DATE, endDate)
                  )
      
      if (out.ProductGroup) {
          q.where(out.ProductGroup)                                   
      }
      
      if (out.CustomerGroup) {
          q.where(out.CustomerGroup)                                  
      }
      
      return ctx.executeQuery(q)?.data?.find()?.getAt(COLUMN_REVENUE)

      CAUTION: the value of out.ProductGroup was selected on top of Product Master table, but the filter is used for the Datamart fields! The mapping is going from ProductMaster to DS Products and it’s fields are brought to the Datamart. You MUST ensure, that all the columns the user can select in the filter, will also be available in the Datamart, otherwise the Query will fail.the same rule as in #1

      Datamart queries at the line item level are potential performance risk, especially, when there are a lot of line items in the Contract. In such case it would be good to consider pre-caching already on the header level, if possible.

  11. And finally, the user would like to see the impact of the negotiated Volume Discount on the estimated Revenue (found in the previous element). In this case, it’s quite simplified for purpose of training. Add a new element "ImpactOnRevenue" with code:

    if (out.RevenueLastYear != null && out.VolumeDiscountMap) {
        def worstPromotionDiscount = out.VolumeDiscountMap
                                        ?.sort { -it.key }
                                        ?.find()?.value
        return out.RevenueLastYear * worstPromotionDiscount * -1.0
    } else {
        api.addWarning("Cannot calculate the estimated impact on Revenue, " +
                "because either Last Year Revenue or Promotion Discount" +
                "is unavailable.")
    }
    1. this value must be in the results

    2. set the Format Type to Money (EUR)

  12. Deploy the Logic ContractVolumeDiscount to the partition

    It would be the best to be able to test the logic right now, but before we can test the Logic, we need to have the Condition Type prepared, otherwise you cannot test the Contract Logic in Studio.

  13. Create new Condition Type

    1. in Pricefx Unity, navigate to Agreements & Promotions  Condition Types

    2. use Add Condition Type and set up the new type

      ContractTermType Volume New
    3. set the Name to "VolumeDiscount"

    4. set the Label to "Volume Discount"

    5. set the Pricing Logic to your newly deployed ContractVolumeDiscount logic

    6. the Waterfall Element is optional, and is used to indicate, which adjustment element of the waterfall is influenced by this Condition Type. This value is usually not used in the Contract Logic, but later (e.g. on a Quote), when the Contract conditions are applied.

    7. click on Add to add the new type

  14. Test the logic

    1. On logic’s Parameters tab, remember to click Generate Parameters to see all inputs

    2. Setup all the inputs and Test Logic

      VolumeDiscountLogic Test
      1. remember to select the ContractTermTypeName, otherwise you will get unexpected error while trying to Test the Logic.

      2. TargetDate would have effect only if you’re reading data from PriceParameters, or if you would explicitely use the TargetDate in some calculation. In this case you can simply keep today date.

      3. for the Volume Discount tiers/levels, ensure to add at least 2-3 tiers, so that you can see how the map looks like

    3. Test the logic and verify the results. Ensure it works all ok before moving to next steps.

  15. Test the Condition Type in the Unity interface

    1. in Pricefx Unity, navigate to Agreements & Promotions  Agreements & Promotions

      2NewContract
    2. Click on + New Agreement & Promotion

      VolumeContract New
      1. set the Start Date and End Date to be a range of the whole year (regarding your today)

      2. the Calculation Date will influence, which Price Parameters versions are used data are used for calculations, so it would likely be either day of calculation (usually "today"), or the first day of validity of the Contract.

    3. move to Items tab

    4. add the line with VolumeDiscount Condition Type

      ContractTermType Search
      1. Select your new Condition Type VolumeDiscount as new line

      2. Select the Customer(s) filter

      3. Select the Product(s) filter

      4. enter the Volume Discount tiers

        VolumeDiscount Inputs
      5. Click on Recalculate to recalculate the contract and all of its line items

      6. Review the Calculation Results

      7. Save your contract, you will need it later

Step: Setup Mapping of the Contract to Price Record

For either export of contract conditions to external systems, or for easier retrieval of negotiated promotion conditions from another modules, it is useful to export the Condition Type lines into Price Records.

The out-of-the-box fields are mapped automatically, but for your new fields, you have to setup the mapping. The mapping is done only by matching of names - the name of the result(or input) on the line item must match the name of the column on Price Record.

Both input fields and output fields can be mapped to the Price Record.

  1. In Unity, navigate to Promotion Manager  Price Records

  2. Review the column ConditionType, which keeps the name of the Condition Type, so that we know, what type of conditions are stored on that line. This column was created in Lab 1a. The value of the column will come from the result ConditionType of the line item.

  3. Setup a column to store the Volume Discount percentage value. The value of the column will come from the input field VolumeDiscount of the line item.

    1. Rename and Customize the column attribute3

    2. set Name to "VolumeDiscount"

    3. set Label to "Volume Discount"

    4. set Type to String

  4. Verify the mapping of the Contract line to the Price Record

    1. in Unity, navigate to Agreements & Promotions  Agreements & Promotions

    2. open the previously saved Volume Contract (click on its ID)

    3. Submit the contract for approval

    4. since there’s no approval workflow now, it does not require any approval, and so gets through immediately, and the mapping of the contract line items to the Price Records happens also immediately.

    5. in Unity, navigate to PromotionManager  Price Records

    6. Verify the new record in this list

      Volume PriceRecord
      1. you should find there a new record, with SourceID the same, as your Contract ID.

      2. Customer Group should match your selection on the contract line item

      3. Product Group should match your selection on the contract line item

      4. Valid After and Expiry Date must match to your selection of Start Date and End Date on the Contract Header

      5. Condition Type is mapped from the results of your contract line item

      6. Volume Discount is mapped from the input of your contract line item

        Complex input / output types like this are represented as JSON in the Price Records and so you need to accommodated it in the output if this is sent to external systems.

        As an example, some external systems will need a row per volume break - meaning Platform or some other method (perhaps moving to a DS) will need to "explode" the one price record in to many rows.

Example of a Solution

volume-discount.zip

References

Developer Documentation

Documentation (Classic)

Older Training Materials

Accelerators (non-public)

  • No labels