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 3 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:

  • lookup contract’s Price Records from within another module

  • calculate the Promotion Contract impact on Quote calculation

Pre-requisites

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

Labs

In order to implement the Quote which reads the Price Records, we expect you to already have some Price Records created from the previous 2 Labs.

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

  • Agreements & Promotions Engineering - Lab 1a

  • Agreements & Promotions Engineering - Lab 1b

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

As a Sales Manager, I want the Invoice Price (on the Quote Line Item) to be influenced by the Promotion Contracts, so that I can negotiate correct Invoice Price with the customer and correctly apply the corresponding promotion contracts.

Details

  • The Quote line item’s Invoice Price can be impacted by several different types of the Promotion Contracts.

    • In our example,they are Promotion Discount and Volume Discount.

      • the discounts in this lab are additive - i.e. on the Quote the discount percents will be totaled before they are applied.

    • If more Condition Types apply to the Quote line item, all of them should be used.

  • There’s a chance, that Sales/Marketing managers negotiated overlapping contracts of the same type.

    • In such case, only the contract with highest discount will be applied.

  • Besides the actual impact on the Invoice Price, the user would also like to see the ID of the contract which is used for the promotion discount.

    • if possible, the ID should be displayed as a link, so that the user can directly click to see the contract detail.

Acceptance Criteria

On the Quote Line Item, the user can see (additionally to the provided implementation):

  • for Promotion Discount

    • Promotion Discount percentage - value found on the Promotion Contract (stored in Price Records) matching to the quote line item’s Product and Customer selection.

      • if more contracts of the Promotion Discount type are found for the line item, use the one with highest percentage value.

    • Promotion Discount contract ID - which matching contract is used. This number should be displayed as a link leading the user to the detail page of the particular contract.

  • for Volume Discount

    • Volume Discount percentage - value found on the Promotion Contract (stored in Price Records) matching to the quote line item’s Product and Customer selection and also matching to the Quantity entered by the used on the line item.

      • if more contracts of the Volume Discount type are found for the line item, use the one with highest percentage value.

    • Volume Discount contract ID - which matching contract is used. This number should be displayed as a link leading the user to the detail page of the particular contract.

  • Invoice Price

    • the Invoice Price is calculated from the List price in the following way: ListPrice * (1.0 - VolumeDiscount% - PromotionDiscount% )

      the discounts in this lab are additive - i.e. on the Quote the discount percents will be totaled before they are applied.

      In the consumer space though, many times the discounts are multiplicative (i.e. get an additional discount), in such case the Invoice Price would be ListPrice * (1.0 - VolumeDiscount%) * (1.0 - PromotionDiscount%)

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: Create basic Quote Line Item Logic, the starting point

  1. create a logic

    1. in Studio, create new Calculation Logic, named "QuotePromotion"

      1. the logic has Default Nature

    2. add Element "CustomerId", with Display mode Never and with code:

      return api.customer("customerId")
    3. add Element "ProductId",with Display mode Never and with code:

      return api.product("sku")
    4. add Element "Quantity",with Display mode Never and with code:

      //api.userEntry("Quantity")
      
      final String INPUT_NAME = "Quantity"
      if (api.isInputGenerationExecution()) {
      
          api.inputBuilderFactory().createUserEntry(INPUT_NAME).getInput()
      } else {
          return input[INPUT_NAME]
      }
    5. add Element "AbortOnInputGeneration", with Display mode Never and with code:

      if (api.isInputGenerationExecution()) {
          api.abortCalculation()
      }
    6. add Element "ListPrice", with code:

      // return fictious number for the sake of the training exercise
      
      return 21.3
      1. this value should be displayed

      2. this value is formatted as Money (EUR)

    7. Test the Logic

      1. test with empty parameters, if all ok

      2. test with all parameters entered, if all ok

    8. Deploy the logic to the partition

  2. create a Quote Type

    1. in Pricefx, navigate to Quoting  Quote Types

    2. click on Add Quote Type and add new Quote Type

      1. Name: "QuotePromotion"

      2. Pricing Logic: "QuotePromotion"

    3. in Studio, fetch the new Quote Type QuotePromotion to your project, so you can later put it to Git

Step: Implement reading Price Records with promotion conditions

Reading of the Price Record is done using either api.find() or api.stream(), but the key thing is the filtering, because on the Quote line you have a single SKU and single CustomerId, where as on the the contract conditions are defined mostly on a set of products and set of customers.

So you must use the special Filter functions to implement the search.

As you will need to build the filters several times for different types of contracts, you will implement the filters as a function.

  1. add new Element "FilterLib", with Display mode Never, with code:

    List<Filter> buildFilters(String conditionType, String productId, String customerId) {
    
        def filters = [
                Filter.equal("ContractTermType", conditionType),          
    
                Filter.equal("status", "ACTIVE"),
    
                /* select only Price Records valid at the Effective Date of the Quote */
                Filter.lessOrEqual("validAfter", api.targetDate()),
                Filter.greaterOrEqual("expiryDate", api.targetDate()),
        ]
        if (productId) {
            filters << Filter.or(                                            
                    api.productToRelatedObjectsFilter("PR", productId),      
                    Filter.isNull("productGroup")                            
            )
        }
        if (customerId) {
            filters << Filter.or(                                            
                    api.customerToRelatedObjectsFilter("PR", customerId),    
                    Filter.isNull("customerGroup")                           
            )
        }
    
        return filters
    }

    search only for the specific Condition Type. Remember, this is your customized column of Price Record, and the value gets there from each Contract Line.this is the special function, which builds a filter unique for search of Price Records table by specific SKU, even though the Price Record is defined for a set of products.this is additional check (must be there), because the productGroup can be null (some contracts allows it), and api.productToRelatedObjectsFilter() does not cover that situationthis is the special function, which builds a filter unique for search of Price Records table by specific CustomerId, even though the Price Record is defined for a set of customers.this is additional check (must be there), because customerGroup can be null (some contracts allows it), and api.customerToRelatedObjectsFilter() does not cover that situationnotice, that there’s an OR between the next 2 filters, it important for correct working

Step: Implement finding the right contract

Remember, there’s a chance, that you will have at the same date more contracts (of the same type) effective. In such case, in our training requirement, the Sales wants to use the highest discounts.

  1. Using the Promotion Discount:

    1. add Element "PromotionDiscountContract", with Display mode _Never, with code:

      final String FIELD_PROMOTION_DISCOUNT = "PromotionDiscount"
      
      def filters = FilterLib.buildFilters("PromotionDiscount", out.ProductId, out.CustomerId)
      
      def highestPromotionDiscount = 0.0
      def contract = [:]
      
      def iter = api.stream("PR", null, ["sourceId", FIELD_PROMOTION_DISCOUNT], *filters)
      iter.each { pr ->                                                   
      
          def discountPct = pr[FIELD_PROMOTION_DISCOUNT] as BigDecimal
      
          if (discountPct > highestPromotionDiscount) {                   
              highestPromotionDiscount = discountPct
      
              contract.highestPromotionDiscount = highestPromotionDiscount
              contract.selectedContractId = pr.sourceId
          }
      
      }
      iter.close()
      
      return contract

      build the Price Record filters only for the specific type of Condition types linesmore Price Records could be available - need to review all of them.In our case, based on the business decision, the higher discount will be used.

    2. add Element "PromotionDiscountPct", with code:

      return out.PromotionDiscountContract?.highestPromotionDiscount
      1. this element’s result should be visible

      2. the value should be formatted as Percentage

    3. add Element "PromotionDiscountContractId", with code:

      def contractId = out.PromotionDiscountContract?.selectedContractId
      if (contractId) {
          return api.getBaseURL() + "/app/#/contracts/detail/${contractId}/contractDetail" 
      }

      Note, that this solution will work only in Pricefx UI.

      1. this value should be visible

      2. this value must be formatted as Link

    4. Verify, that the logic works as expected

  2. Using the Volume Discount:

    1. add Element "VolumeDiscountContract", with Display mode _Never, with code:

      final String FIELD_VOLUME_DISCOUNT = "VolumeDiscount"
      
      def filters = FilterLib.buildFilters("VolumeDiscount", out.ProductId, out.CustomerId)  
      
      // more PRs could be available - need to solve conflict. In our case, the higher discount wins
      def highestVolumeDiscount = 0.0
      def contract = [:]
      
      def iter = api.stream("PR", null, ["sourceId", FIELD_VOLUME_DISCOUNT], *filters)
      iter.each { pr ->                                                              
      
          def volumeDiscountTiers =
                  api.jsonDecode(pr[FIELD_VOLUME_DISCOUNT])
                     .collect { [(it.key as BigDecimal), (it.value as BigDecimal)] } 
          volumeDiscountTiers.sort { -(it[0]) }
      
          def discountPct = volumeDiscountTiers.find { out.Quantity >= it[0] }?.getAt(1)
      
          if (discountPct > highestVolumeDiscount) {                                 
              highestVolumeDiscount = discountPct
      
              contract.highestVolumeDiscount = highestVolumeDiscount
              contract.selectedContractId = pr.sourceId
          }
      
      }
      iter.close()
      
      return contract

      build the Price Record filters only for the specific type of Condition types linesmore Price Records could be available - need to review all of them.remember, the volume tiers thresholds are stored in a map encoded in JSON format. Review your Price Records created from the Volume Discount contracts to recall the values.In our case, based on the business decision, the higher discount will be used.

    2. add Element "VolumeDiscountPct", with code:

      return out.VolumeDiscountContract?.highestVolumeDiscount
      1. this element’s result should be visible

      2. the value should be formatted as Percentage

    3. add Element "VolumeDiscountContractId", with code:

      def contractId = out.VolumeDiscountContract?.selectedContractId
      if (contractId) {
          return api.getBaseURL() + "/app/#/contracts/detail/${contractId}/contractDetail" 
      }

      Note, that this solution will work only in Pricefx UI.

      1. this value should be visible

      2. this value must be formatted as Link

    4. Verify, that the logic works as expected

  3. Invoice Price evaluation - finally, you have to evaluate the Invoice Price, because it is impacted by both types of the contracts.

    1. add Element "InvoicePrice", with code:

      if (out.ListPrice == null) {
          api.addWarning("Cannot calculate Invoice Price, "+
                          "because List Price is not available.")
          return
      }
      
      def promotionDiscount = out.PromotionDiscountPct ?: 0.0
      def volumeDiscount = out.VolumeDiscountPct ?: 0.0
      
      return out.ListPrice * (1.0 - promotionDiscount - volumeDiscount)
      1. the value must be visible

      2. it should be formatted as Money (EUR)

    2. Verify, that the logic works as expected

  4. Deploy your Quote Logic to the partition

Step: Verify the Quote Logic

We invite you now not only to verify the functionality, but mainly to experiment with your system - try different combinations of new Promotion Contracts and then experiment with different Quote settings, and review how the contract conditions are applied.

Pay attention while testing of this use case, because it is sensitive to data available in Price Records table and Effective date of your Quote

So before you start testing, review:

  • Price Records available and their

    • Condition Types in column ConditionType

    • validity range, in columns ValidAfter

    • values of the discounts in columns PromotionDiscount and VolumeDiscount

  • Quote

    • the Effective date of the Quote must fit into the range of your Price Record contract lines, otherwise the code will not find any

If you are missing PriceRecords needed for testing, remember, you can quickly go to PromotionManager, create new contracts and submit them for approval.

  1. Let’s expect to have PriceRecords like following:

    QuoteTest PriceRecords
  2. in Pricefx, navigate to Quoting  Quotes

  3. Click on New Quote and select the Quote Type QuotePromotion

    QuotePromotion Test
    1. set Effective Date to be within the range of your Price Records

    2. select a Customer, which fits into customer groups used by your Price Records

  4. move to Items tab

    PromotionQuoteItems Test
    1. add a product, which fits the sets of products in your Price Records

    2. enter Quantity so that some VolumeDiscount contract could effect the line

    3. Use Recalculate to ensure, that the Quantity si used for Contract lookup

    4. Verify, if correct values are caclulated

    5. Verify, that the links work ok

References

Developer Documentation

Documentation

Older Training Materials

Accelerators (non-public)

  • No labels