Additional Blogs by Members
Showing results for 
Search instead for 
Did you mean: 
0 Kudos

IPC Customization Documentation

Custom Development

Custom developments

1 Overview
The IPC runs on a J2EE server and utilizes configuration/conditions from CRM to simulate R/3 pricing. Within CRM, Net Price List(Tcode "/BEA/CNPL01") makes RFC calls to the IPC server to retrieve this pricing information. This pricing information is then saved within the CRM system to be used for Trade Promotion Management or other modules within CRM.
We needed to implement customizations to the IPC to allow for additional subtotals to be included past the SAP hard-coded limitation of 6 subtotals and furthermore to allow IPC to include Rebate conditions in it's Pricing simulation.
The landscape this solution was developed in is CRM 4.0 with Support Pack 8 and IPC 4.0 with Support Pack 8. The IPC is using CRM as the database, there is no locally configured IPC database.
2 Prerequisites
Setup the following on your local machine for development purposes:
1) Local IPC Server (both Dispatcher and Server)
a. You can use the CRM Installer DVD or download the IPC software from This Complete IPC Installer (all Operating Systems) is 458 MB; therefore I would recommend simply installing the OS that you will be running the IPC on, i.e. your local machine's OS.
b. In your SU3 record, you can specify "IPC_RFC_DESTINATION" with the name of a specific RFC Destination for your local IPC server. This allows you to have both a local IPC and a server based IPC running for the same CRM system.
2) Local Java IDE - Eclipse is the preferred IDE, however any IDE could be used
a. This is an open source IDE which allows for compilation and debugging.
3) Connection to shared repository - we are using CVS for our Java Code repository
a. This is necessary since you should maintain a backup of the original SAP source code before making your changes.
b. This will also help with versioning and change management.
4) Create and maintain new Ztable "ZTPM_IPC_REBATE".
(Documented in NPL documentation and explained further below)
Note: These prerequisites are only required for further development/customization of the Java code, they are not required for implementing the IPC customizations. If you want to implement this code on your IPC server, simply overwrite the existing files with these customized files and restart your IPC server. The only other step necessary is to configure the Ztable mentioned above.

2.1 Configuration
Obtain change access to the CRM IMG and the Net Price List Transactions within CRM.
Obtain change access to the R/3 IMG and specifically the Pricing Procedures/Access Sequences within R/3.

3 Technical Flow Diagram
These are the diagrams from the SAP documentation delivered with the IPC Server.
If you need further information about the IPC, please refer to the SAP documentation.

4 Development
The development is primarily Java based, however there is also a minor ABAP change in CRM standard code for the Net Price List generation program to allow it to process the additional subtotals.

The following methods were implemented within the
1) checkRequirement - Included requirement "24" as a supported requirement for IPC to check. Rebates are part of the Standard Exit and therefore need to be changed here as well. This class file then needs to be imported into the SAP_IPC.jar and sent to the server. This will fulfill the necessary requirement and allow for the condition record processing to take place. The following code in the will then do the custom manipulation of these Rebate condition records.
Note: Use JAR to expand the SAP standard "SAP_IPC.jar" and then simply replace the existing class file and re-create the "SAP_IPC.jar" file on your local machine and finally overwrite the IPC Server version with this newly created file.

The following methods were implemented within the
2) checkRequirement - Included requirement "24" as a supported requirement for IPC to check.
3) overwriteConditionValue - This is where all of the additional logic has been implemented.
a. Set the Subtotal based on the Counter configured within the Pricing Procedure using the "prItem.setDynamicReturnValue" method. The counter will indicate which Subtotal field the condition value should be stored. We are not using the "counter" within R/3 or CRM pricing procedures, therefore it is an excellent place to store this configuration. Furthermore it is a standard SAP field which is easily accessible within IPC, therefore we did not have to extend IPC to get this information.
b. Use the "database.db_read_table" method to read configuration and condition tables within CRM. This is a standard supported IPC method and therefore we did not need to implement and direct JCO/JCA calls to CRM. Using this technique should make the solution supported in future releases of CRM.
c. Use the CRM Table "CRMC_MKTPL_COTAB" to lookup non-rebate conditions to determine which CRM condition table the entries are located in for a given condition record. The key for this table is ( Application = MKT / KAPPL = CRM / CUSTOMER_TYPE = 02 / KSCHL = Condition). This will return one entry for the given condition record, indicating which table CRM uses to store condition records for this condition type.
d. Use the CRM Table from the above step to find the condition records, the key for this table is (HIER_NO_GUID = Hierarchy Node GUID / Product = Product GUID / KSCHL = Condition / Timestamp_from = NPL Date entered / Timestamp_to = NPL Date entered / Release_status = blank). The field KEBTR will contain the condition value.
e. If no record is found in the above step, this indicates that the condition is of type "BO" or a Rebate condition. Therefore we need to lookup the CRM condition record table in a newly created Ztable "ZTPM_IPC_REBATE". There is further documentation on maintenance of this table in the NPL setup documentation.
f. The condition record rate is then converted based on the unit, this is from the "prCondition.getConditonRate().getUnitName()" method. This will indicate a percentage or a lump sum condition type.
g. The "prCondition.setConditionRateValue()" method is used to fill in the conditon rate. This will switch the condition record from "RED" to "GREEN" on the IPC detail information screen which is more user-friendly since it truly is not a required step.
h. Use the exisiting Base "prCondition.getConditionBase.getValue()" method to have a base price to create the discounted price. Simple java.math methods used here for the calculation.
i. Use the "prItem.setDynamicReturnValue()" method to store both the subtotal name and it's associated value. This will be passed to the CRM system when the NPL program is generating a Price List. (See NPL program modification below to determine how this information is passed to the CRM subtotal buckets)
4) getRequirementNumbers - Included requirement "24" as a supported requirement number.
5) getConditionValueFormulaNumbers - Included "988" and "987" as custom Value Formula number which were created in SAP R/3. This allows us to differentiate Rebates from non-rebate conditions.

Here is the information you need to deploy your IPC Custom Code:
Contact the IPC Administrator and have them overwrite the PricingUserExits.class on the IPC Server with the version you have on your local machine.

They will replace the existing file in the path: "ipc/ipc/lib/userexists"
Then have the IPC Administrator restart the IPC Server.

Custom tables
4.1.1 ZTPM_IPC_REBATE Table - Configuration Table for Rebate Conditions Use
This table contains the mapping between an R/3 generated Rebate condition and the associated Table/Table keys used to lookup condition record values. The SAP standard view "/sapcnd/acc_type" has similar information, however this view contains all of the accesses within the Access Sequence, therefore we could not utilize this table. The primary reason for this is that our R/3 Access Sequences have too many tables currently configured and this would result in a dramatic performance issue while generating Net Price List. We decided to use a Ztable, even though this is not a preferred option typically, to simply the solution and improve performance of these lookups. The alternate could be to simply map these within the Java code; however that is not a flexible solution in our ever changing landscape. Prerequisites

   Configure the table with values per the documented Net Price List instructions.

   Basically there needs to be an entry in the table for each of the R/3 generated    Rebate condition types you are interested in adding to your pricing procedure    within CRM.

   Example Entries for the Condition "ZM05" which has three keys for    selection records from Rebate condition record table CUS960 (full table name    is "/1CN/CCBCUS960" based on SAP naming convention when the BO usage    is used to generate the table via CRM Middleware Condition Download ).



   ZM05 CUS960 2 PRODUCT

   ZM05 CUS960 2 SALES_ORG Table attributes

   See above Table structure

   KSCHL KSCHL CHAR 4 0 Condition type

   KOTABNR CHAR 8 0 Table Name

   KOLNR CHAR 3 0 ID for Access Within an Access Sequence

   ZIFNA CHAR30 30 0 30 Characters Primary key and Indexes
KSCHL KSCHL CHAR 4 0 Condition type
KOTABNR CHAR 8 0 Table Name
KOLNR CHAR 3 0 ID for Access Within an Access Sequence
ZIFNA CHAR30 30 0 30 Characters

4.2 Custom programs

Modification to standard SAP Program "LPRC_INTU24"

   This is Function Module "PRC_INT_ITEM_CREATE_MULTI"



  • fill values for condition function (purposes)

   loop at lt_purposes_ref into ls_purposes_ref

   where ref_id = lv_ref_id.

   ls_cond_function-destination = ls_purposes_ref-field_name.

   perform convert_ipc2prc_value using ls_purposes_ref-value


   changing ls_cond_function-value.

   append ls_cond_function to ls_prc_item_ret-cond_function.

   endloop.public BigDecimal overwriteConditionValue(IPricingItemUserExit prItem,

ILastPrice lastPrice,

IPricingConditionUserExit prCondition,

int valueFormNo) {

//Added by Colgate

db database = pricingEngine.getDatabase();

int conditioncounter = prCondition.getStep().getCounter();

short Cconditioncounter = (short)conditioncounter;

String s = ""+Cconditioncounter;

String mysubtotal = new String("ZKZWI").concat(s);

String blank = new String("ZKZWI0");

switch (valueFormNo) {

case 988:

BigDecimal MyBase = prCondition.getConditionBase().getValue();

String MyProductDesc = prItem.getProduct().getExternalId(); //Returns FMX00012

String MyProductGUID = prItem.getProduct().getId(); //Returns GUID

String MyCondition = prCondition.getConditionTypeName(); //Returns YM70

String MyUseage = prItem.getUsage(); //Returns PR

String MyHierNode = "default";

try {

MyHierNode = prItem.getHeaderAttributeValue("SOLD_TO_PARTY").getValue();

} catch (UnsuppliedAttributeException e) {



String MySalesOrg = "default";

try {

MySalesOrg = prItem.getHeaderAttributeValue("SALES_ORG").getValue();

} catch (UnsuppliedAttributeException e) {



String MyZZProdHier = "default";

try {

MyZZProdHier = prItem.getHeaderAttributeValue("ZZPROD_HIERARCHY").getValue();

} catch (UnsuppliedAttributeException e) {



//Determine the database table name to read the conditions from

//Only TPM Conditions are stored in this lookup table, not R/3 conditions!

Hashtable zzTableContent = new Hashtable();

//db database = pricingEngine.getDatabase();

String tableStart = "CRMC_MKTPL_COTAB";

String ZZFIELDStart = "KOTABNR";

String ZZFIELD_VALUEStart = "";

String[] projectionStart;

String MyApplication = new String("MKT");

String MyKAPPL = new String("CRM");

String MyCUSTOMER_TYPE = new String("02");

sys_query_pair[] queryPairStart = new sys_query_pair[5];

queryPairStart[0] = new sys_query_pair("APPLICATION", MyApplication);

queryPairStart[1] = new sys_query_pair("KAPPL", MyKAPPL);

queryPairStart[2] = new sys_query_pair("CUSTOMER_TYPE", MyCUSTOMER_TYPE);

queryPairStart[3] = new sys_query_pair("PRODUCT_TYPE", MyUseage);

queryPairStart[4] = new sys_query_pair("KSCHL", MyCondition);

projectionStart = new String[];


res resultSetStart = database.db_read_table(tableStart,



if (!res.db_empty_results_p(resultSetStart)) {

// Should only have one matching record ever in CRM 4.0 without overlapping!

ZZFIELD_VALUEStart = resultSetStart.db_get_row_element_string(0);



else {

log_api.log_write_msg("SPE", "PricingUserExits", 1,

"Coudn't find a value of field KOTABNR in table TABLE!");



catch (exc_database_error e){

log_api.log_write_msg("SPE", "PricingUserExits", 1,

"ERROR reading TABLE!!!");




//Select from database to get the actual condition value

String table = "/1CN/CCB" + ZZFIELD_VALUEStart;


String ZZFIELD_VALUE = "";

BigDecimal MyResult = null;

String[] projection;

//Get correct date

String inputteddate = prItem.getPricingTimestamp().toString();

//Compare Operators

String EQUAL = "=";

String NOT_EQUAL = "<>";

String LESS = "<";

String GREATER = ">";

String LESS_OR_EQUAL = "<=";

String GREATER_OR_EQUAL = ">=";

String LIKE = "LIKE";

String MyFrom = null;

String MyTo = null;

MyFrom =;

MyTo =;

//Remember this Array must be the size of the filled data!!!

//Otherwise the database call fails..tough for debugging

sys_query_pair[] queryPair = new sys_query_pair[6];

queryPair[0] = new sys_query_pair("HIER_NO_GUID", MyHierNode);

queryPair[1] = new sys_query_pair("PRODUCT", MyProductGUID);

queryPair[2] = new sys_query_pair("KSCHL", MyCondition);

queryPair[3] = new sys_query_pair("TIMESTAMP_FROM", inputteddate , LESS_OR_EQUAL);

queryPair[4] = new sys_query_pair("TIMESTAMP_TO", inputteddate , GREATER_OR_EQUAL);

queryPair[5] = new sys_query_pair("RELEASE_STATUS", new String("  "));

projection = new String[];


res resultSet = database.db_read_table(table,



if (!res.db_empty_results_p(resultSet)) {

// Simply take the first record that is returned!

ZZFIELD_VALUE = resultSet.db_get_row_element_string(0);



else {


} //end else


//Must be an Rebate Condition from R/3, so must look elsewhere for it

catch (exc_database_error e){

//log_api.log_write_msg("SPE", "PricingUserExits", 1,

//"ERROR reading TABLE or no entries found!!!");


Hashtable ConfigTable = new Hashtable();

String Configtable = "ZTPM_IPC_REBATE";

String ConfigZField1 = "KOTABNR";

String ConfigZField2 = "KOLNR";

String ConfigZField3 = "ZIFNA";

String[] Configprojection;

sys_query_pair[] ConfigqueryPair = new sys_query_pair[1];

ConfigqueryPair[0] = new sys_query_pair("KSCHL", prCondition.getConditionTypeName());

Configprojection = new String[]{ConfigZField1, ConfigZField2, ConfigZField3};


res ConfigresultSet = database.db_read_table(Configtable,



if (!res.db_empty_results_p(ConfigresultSet)) {

//Table name will always be the same

table = "/1CN/CCB" + ConfigresultSet.db_get_row_element_string(ConfigZField1);

int i = 0;

int j = 0;

sys_query_pair[] queryPair2 = new sys_query_pair[10]; //31 potential total

//loop through the ConfigresultSet and use if statement to set query_pair

while (!ConfigresultSet.db_eof_p())


String keyname = ConfigresultSet.db_get_row_element_string(ConfigZField3);

if ( keyname.equalsIgnoreCase(new String("HIER_NO_GUID"))){

queryPair2[i] = new sys_query_pair("HIER_NO_GUID", MyHierNode);


if ( keyname.equalsIgnoreCase(new String("PRODUCT"))){

queryPair2[i] = new sys_query_pair("PRODUCT", MyProductGUID);


if ( keyname.equalsIgnoreCase(new String("SALES_ORG"))){

queryPair2[i] = new sys_query_pair("SALES_ORG", MySalesOrg);


if ( keyname.equalsIgnoreCase(new String("SOLD_TO_PARTY"))){

queryPair2[i] = new sys_query_pair("SOLD_TO_PARTY", MyHierNode);


if ( keyname.equalsIgnoreCase(new String("ZZPROD_HIERARCHY"))){

queryPair2[i] = new sys_query_pair("ZZPROD_HIERARCHY", MyZZProdHier);


ConfigresultSet.db_next_row_p(); //goto next row, obviously

i = i + 1; //increment counter


queryPair2[i] = new sys_query_pair("KSCHL", MyCondition);

queryPair2[i+1] = new sys_query_pair("TIMESTAMP_FROM", inputteddate  , LESS_OR_EQUAL);

queryPair2[i+2] = new sys_query_pair("TIMESTAMP_TO", inputteddate ,  GREATER_OR_EQUAL);

queryPair2[i+3] = new sys_query_pair("RELEASE_STATUS", new String("  "));

ConfigresultSet.db_finish(); //close the db connection

j = i + 3; //full size of the array

sys_query_pair[] queryPair3 = new sys_query_pair[j+1];

while (j >= 0){

queryPair3[j] = new sys_query_pair(queryPair2[j].sys_getName(), queryPair2[j].sys_getValue(),  queryPair2[j].sys_getOperator());




res RebateresultSet = database.db_read_table(table,



if (!res.db_empty_results_p(RebateresultSet)) {

// Simply take the first record that is returned!

ZZFIELD_VALUE = RebateresultSet.db_get_row_element_string(0);




//ZZFIELD_VALUE = new String("0");




catch (exc_database_error f){

log_api.log_write_msg("SPE", "PricingUserExits", 1,

"No matching values from R/3!");




catch (exc_database_error g){

log_api.log_write_msg("SPE", "PricingUserExits", 1,

"No entries in ZTPM!");





//Determine if the value is a percentage or Lump Sum

String MySymbol = prCondition.getConditionRate().getUnitName();

BigDecimal myzfield;

if ( MySymbol.equalsIgnoreCase("%") ){

myzfield = new BigDecimal(ZZFIELD_VALUE).divide(new BigDecimal(1000), 2);

prCondition.setConditionRateValue(new BigDecimal(ZZFIELD_VALUE).divide(new BigDecimal(10),2));

MyResult = myzfield.multiply(MyBase);


else { //Dollar Value

myzfield = new BigDecimal(ZZFIELD_VALUE);


MyResult = myzfield.add(MyBase);


//--Set the Subtotal Dynamic Return Value(for Rebates)--//

if ( !mysubtotal.equalsIgnoreCase(blank) ){

//prItem.setSubtotal(Cconditioncounter, MyResult); //doesn't get passed to NPL

prItem.setDynamicReturnValue(mysubtotal, MyResult.toString());


return MyResult;

case 987:

if (!mysubtotal.equalsIgnoreCase(blank) ){

prItem.setDynamicReturnValue(mysubtotal, prCondition.getConditionValue().toString());



//--Set the Subtotal Dynamic Return Value--//

if (!mysubtotal.equalsIgnoreCase(blank) ){

prItem.setDynamicReturnValue(mysubtotal, prCondition.getConditionValue().toString());


throw new FormulaNotImplementedException(prItem, "value formula", valueFormNo); 


// End Added by Colgate

}  </pre>