Additional Blogs by SAP
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

Entering Java Userexits

This is the first installment of a number of blog posts I am planning to write about programming Java Userexits for SAP CRM pricing. This series will require you to understand the concept of pricing and of developing Java userexits in CRM. Please refer to this excellent list of introductory resources:

Learning map for SAP CRM Pricing and IPC

My series will take it from there and explore the Java Userexit API.

Generic Requirements

The first thing I always take with me on a customer project where Userexits in CRM pricing are necessary is a set of "generic" requirements. I use those in nearly every project and find them to be a great help every time. The requirements are:

  1. Always True
  2. Always False
  3. Generic Decision
  4. (Pricing Relevant and Generic Decision)

Let's enter all of them:

Always True

This an extremely simple requirement, that simply return true every time it is executed.

Note: There is already a generic Userexit that is always true in CRM, called STEPSUCCESS. I like to create my own implementation for consistency, and to enable logging.

Standard generic requirement STEPSUCCESS.

package com.customer.pricing.generic;

import com.sap.spe.base.logging.UserexitLogger;
import com.sap.spe.condmgnt.customizing.IAccess;
import com.sap.spe.condmgnt.customizing.IStep;
import com.sap.spe.condmgnt.finding.userexit.IConditionFindingManagerUserExit;
import com.sap.spe.condmgnt.finding.userexit.RequirementAdapter;

/**
* @author Manuel Seeger, SAP
*
* Generic requirement that returns always TRUE.
* Will write context it is called from to the VMC log.
*
*/
public class ZCRM_AlwaysTrue extends RequirementAdapter {

     UserexitLogger log = new UserexitLogger(ZCRM_AlwaysTrue.class);

     public boolean checkRequirement(IConditionFindingManagerUserExit item,
               IStep step, IAccess access) {

          if (access != null ) {

               log.writeLogDebug(
                         "Access " + access.getAccessSequence().getName() + " " + access.getCounter() +
                         ", Req " + access.getRequirementNumber() +
                         " : always true");
          } else {
               log.writeLogDebug(
                         "Step "+ step.getStepNumber() +
                         ", Req " + step.getRequirementNumber() +
                         " : always true");
          }
          return true;
     }
}

Always False

Just as simple, this userexit returns always false. This is mostly used on requirements that have relevance only in ERP (for example in billing) and don't need to be active in CRM.

package com.customer.pricing.generic;

import com.sap.spe.base.logging.UserexitLogger;
import com.sap.spe.condmgnt.customizing.IAccess;
import com.sap.spe.condmgnt.customizing.IStep;
import com.sap.spe.condmgnt.finding.userexit.IConditionFindingManagerUserExit;
import com.sap.spe.condmgnt.finding.userexit.RequirementAdapter;

/**
* @author Manuel Seeger, SAP
*
* Generic requirement that returns always false.
* Will write context it is called from to the VMC log.
*
*/
public class ZCRM_AlwaysFalse extends RequirementAdapter {

     UserexitLogger log = new UserexitLogger(ZCRM_AlwaysFalse.class);

     public boolean checkRequirement(IConditionFindingManagerUserExit item,
               IStep step, IAccess access) {

          if (access != null ) {

               log.writeLogDebug(
                         "Access " + access.getAccessSequence().getName() + " " + access.getCounter() +
                         ", Req " + access.getRequirementNumber() +
                         " : always false");
          } else {
               log.writeLogDebug(
                         "Step "+ step.getStepNumber() +
                         ", Req " + step.getRequirementNumber() +
                         " : always false");
          }
          return false;
     }
}

Again, the main reason for using custom requirements here is that both Always True and Always False enable logging. They will log the context they are called from (pricing procedure or access sequence) and the specific location within the context (Acces or Step).

Generic Decision

This is the more interesting part of the generic userexits. A little background:

In general the vast majority of requirements come down to simply comparing two or more values from the communication structure against other values from that structure, or against hard coded values. Most of the time it is overkill to implemented every requirement in Java, maintain all the attributes, package the archive, upload, etc.

In addition, owed to the different technology, most of the time the Java part is handled by a specialist who has to be called in for every single simple requirement that might not be more than a one-liner in ERP.

To eliminate the Java part from implementing requirements, I create one generic requirement which is reused by all requirement formulas. The generic requirement is controlled using formula-specific flags which are set by the communication structure BAdI. Here is how this looks like:

package com.customer.pricing.generic;

import com.sap.spe.base.logging.UserexitLogger;
import com.sap.spe.condmgnt.customizing.IAccess;
import com.sap.spe.condmgnt.customizing.IStep;
import com.sap.spe.condmgnt.finding.userexit.IConditionFindingManagerUserExit;
import com.sap.spe.pricing.transactiondata.userexit.requirement.PricingRelevant;

/**
* @author Manuel Seeger, SAP
*
* Generic decision based on 1 char attribute
* 0 = TRUE
* everything else = FALSE
*
* Also checks standard requirement PricingRelevant (formula 002)
*
*/
public class ZCRM_PRAndGenericDecision extends PricingRelevant {
    
     private static UserexitLogger log = new UserexitLogger(ZCRM_PRAndGenericDecision.class);

     public boolean checkRequirement(IConditionFindingManagerUserExit item,
               IStep step, IAccess access) {
         
          // Check standard PricingRelevant (formula 002)
          boolean isPricingRelevant = super.checkRequirement(item, step, access);
         
          String subrc = item.getAttributeValue("ZZSUBRC").trim();

          if (step != null) {
              
               String condTypeName;
               if (step.getConditionType() != null) {
                    condTypeName = step.getConditionType().getName();
               } else {
                    condTypeName = "null";
               }
              
               log.writeLogDebug(
                         "Step "               + step.getStepNumber() +
                         ", Counter "     + step.getCounter() +
                         ", CondType "      + condTypeName +
                         ", Req "           + step.getRequirementNumber() +
                         ", PricingRelevant " + isPricingRelevant + 
                         " ZZSUBRC : "     + subrc);
          }

          if (isPricingRelevant && subrc.equals("0")) {
               return true;
          } else {    
               return false;
          }
     }
}

You will notice this requirement class inherits from the standard requirement PricingRelevant. I try to make it a habit to only execute on items that are actually relevant for pricing - inheriting from the standard requirement instead of from com.sap.spe.condmgnt.finding.userexit.RequirementAdapter is an easy way to do this.

Side note: I could not confirm what the reason was, but when I tried to inherit through multiple levels the system became incredibly slow. This might have been an issue with that specific installation though.

This generic requirement takes ones attribute called ZZSUBRC. The requirement will only return TRUE if ZZSUBRC equals '0', similar to what is convention in ABAP. Here is how the implementation looks like in /SAPCND/UEASS after it has been uploaded into CRM:

Note: Attribute PRC_INDICATOR needs to be assigned to enable PricingRelevant (002) to work.

Now, for every requirement formula I need to implement, I will add a new flag (CHAR1) to the communication structure, which will control that specific requirement.

And using those flags, I assign the requirements to formula numbers in /SAPCND/UEASS.

Now that this is done requirement number 732 can switched on and off by simply setting the communication structure field ZZ_SUBRC_REQ732 to '0' for TRUE or to anything else for FALSE.

This might look like lots of customizing for every requirement, but the idea is that all this can be done by someone with basic ABAP skills - instead of a CRM pricing specialist. Switching requirements on and off now is simple enough: In an implementation of BAdI CRM_COND_COM_BADI, set the relevant flags to '0' to enable a requirement.

Should at some point in the future the functional requirements for a pricing procedure change, anyone with basic ABAP knowledge will be able to change the behavior of the pricing requirement userexits from BAdI CRM_COND_COM_BADI.

Similarly, should a new requirement userexit be needed, an ABAP developer can go ahead and extend the field catalogue, make a new entry in /SAPCND/UEASS, and implement the requirement in the BAdI. No additional Java-coding necessary.

Once the generic userexits are in place, I simply ask myself the same question when a new functional requirement comes in: Can the requirement userexit decision be made based on information available before pricing is initialized? If that is a yes (99% of the time), then I create a new formula for my generic decision and program the userexit logic in ABAP. No additional Java code, everything in one place, easier to maintain, and accessible to ABAP programmers.

This was a rather simple introduction - I hope I can continue this series soon with some more sophisticated concepts.