CRM and CX Blogs by SAP
Stay up-to-date on the latest developments and product news about intelligent customer experience and CRM technologies through blog posts from SAP experts.
cancel
Showing results for 
Search instead for 
Did you mean: 
luis_riveragallego
Product and Topic Expert
Product and Topic Expert
3,823

The main idea behind this blog is to add detailed documentation about the steps in KBA 2329362 - Pricing not redetermined after change in custom header field , including all the steps that should be performed, that is, the customizing steps that would lead to the issue as well as the steps for the solution.
Nevertheless, the blog could also be useful to have a reference about the steps to create a field with the Application Enhacement Tool (AET) or the steps to create an event callback, as these are also explained with some detail.

Pricing in CRM can depend on customer fields, which could be used in pricing formulas or be fields of condition tables. The value of the customer fields that will be sent to pricing is usually determined using BADI CRM_COND_COM_BADI. In case of fields that have a value in the business transaction, either at header or item level, the implementation of BADI CRM_COND_COM_BADI might not be necessary. This is explained in the BADI documentation, in paths at header and item level:

Path with information for fields at header level:

SE18 - CRM_COND_COM_BADI - Display- Tab interface - double click interface (IF_EX_CRM_COND_COM_BADI  )  --
put cursor in method HEADER_COMMUNICATION_STRUCTURE - button documentation (little button)

Path with information for fields at item level:

SE18 - CRM_COND_COM_BADI - Display-Tab interface - double click interface (IF_EX_CRM_COND_COM_BADI  )  --

put cursor in  method ITEM_COMMUNICATION_STRUCTURE -  click button documentation (little button)

As indicated in the BADI documentation, if a field is part of the header extension for customer-specific fields, CUSTOMER_H, then you do not need to implement the BADI CRM_COND_COM_BADI to use that field in pricing. In the same way, if a field is part of the item extension for customer-specific fields, CUSTOMER_I, then you do not need to implement the BADI CRM_COND_COM_BADI to use that field in pricing. As technical information, it is important to consider that the fields in CUSTOMER_H should be displayed in data dictionary structure CRMT_CUSTOMER_H_EXT and the fields in CUSTOMER_I should be displayed in data dictionary structure CRMT_CUSTOMER_I_EXT.


The solution described above works smoothly in case of item fields, so the fields in CUSTOMER_I will be always sent to pricing without any need of the BADI implementation. However, in case of the fields in CUSTOMER_H, there is a limitation, that when the field value is changed, the change is not sent to pricing and therefore there is no pricing redetermination (no repricing). To overcome this problem, we must use a workaround, in which we will have the same header field in both structures CUSTOMER_H and CUSTOMER_I. As the changes in CUSTOMER_I do trigger a pricing redetermination, transferring the changes in CUSTOMER_H to CUSTOMER_I should also trigger a pricing redetermination and pricing will be processed using the changed field value.



In this blog, I have added detailed explanation of the complete set up. The steps are:

1) Create field with AET at header level in Sales Order

In this point, we can see the steps to create the field with the Application Enhancement Tool (AET) , so that the field is added to CUSTOMER_H.
We can also see the steps related to view configuration to display our field at header level in the sales order in sections:
- Configure the view BTCUSTOMER_H/CustomerH so that the our field is displayed in it.

- Add the Assignment block that contains the view BTCUSTOMER_H/CustomerH to the overview view of sales order.

2)  Settings to use our field in pricing in our sales order
In this point we see - although not so much detailed- the settings that allow our field to be used in pricing in our sales order, that is, adding the field to the pricing field catalog and to a condition table.

3 ) Add the same field to CUSTOMER_I object
The same field that has been added to CUSTOMER_H is added to CUSTOMER_I. As the field must not appear in the user interface at item level, we do not

use the AET to add the field. Instead,our field is added directly in the relevant structure of the data dictionary,CRMT_CUSTOMER_I_EXT.

4) Define an event callback to transfer field value from CUSTOMER_H to CUSTOMER_I after field value changes

In this point, we define a custom event callback to transfer the field value from header level (CUSTOMER_H) to item level (CUSTOMER_I) when changes occur on the field value on header. The custom event callback, with a Z name (e.g. ZCRM_CUSTOMER_I_MAINTAIN_EC) will be registered to the event AFTER_CHANGE for object CUSTOMER_H.

5)  Define an event callback to transfer the field value from CUSTOMER_H to CUSTOMER_I when item is created

We also need to cover the case that the field value on header is changed even before the item has been created. As the item does not exist yet, we cannot transfer the value from CUSTOMER_H to CUSTOMER_I when our field value has changed.
For this purpose we have to create a custom event callback for object ORDERADM_I and event AFTER_CREATE and add the coding in the calback to transfer the field value from header level (CUSTOMER_H) to item level (CUSTOMER_I) when the item is created

6) Testing in the sales order
In this step we can see how the field would be displayed in the sales order, and we see that after the field value is changed, the new value will be displayed in the pricing trace.

1) Create field with AET at header level in Sales Order

Create field with AET in business object SALES_ORDER subobject CUSTOMER_H


Launch transaction BSP_WD_CMPWB and enter component CRM_AXT_UI_COMP

Click on "New"


As we want to use our field in a Sales Order, select object SALES_ORDER in the pop-up


We see the list of enhancements for object SALES_ORDER
In "Fields" assignment block click on "New" to add a new field.


Select "CUSTOMER_H"  ( customer header data) and click OK

The field name is given automatically by the AET, in our case the field name is ZZAFLD00009I.

We will have neither search help nor check table in our field, to keep our example simple.
So we just need to fill the field label ("Luis Field") , field type(Text) and length.
Click on "Back"

Save and Generate

We select the workbench request request (not displayed here) and the generation starts.

After a while ( depends on the performance of your system) the field is generated.

As we have selected "CUSTOMER_H", our field can be seen in data dictionary structure CRMT_CUSTOMER_H_EXT

Configure the view BTCUSTOMER_H/CustomerH  so that the new field is displayed



We must configure view Customer_H of component BTCUSTOMER_H so the our new field is displayed in it.

For this purpose we launch the component workbench in transaction BSP_WD_CMPWB and enter component BTCUSTOMER_H. Then we select the only view  in it, Customer_H and go to tab configuration.

If you had not created your customer configuration before, you may need to create your own customer configuration , for example as copy of the standard one ( not displayed here). Then we check that our field ZZAFLD00009I ( with label "Luis Field" in this case" ) is in the list of available fields. We select the field and add it to the list of displayed fields ("+" button)


Click on "Save"

Add the Assignment block that contains the view BTCUSTOMER_H/CustomerH   to the overview view of sales order

The first step is getting the name of the overview view in the sales order.
In most views, if you set the cursor on a text field and afterwords you click on F2, a pop-up with technical information about the view will be shown. However for overview views, as there is no text field, the technical information about the view might be tricky to get. A possible workaround to get the correct information from F2 is to click on a button of the overview view, e.g. "Cancel" ,and click on F2 before releasing the mouse

The popup shows that the relevant view is BT115H_SLSO/SOHOverView.

We must add the view that contains our field to the view BT115H_SLSO/SOHOverView in our customer configuration.
For this purpose
- we open transaction BSP_WD_CMPWB, we select component BT115H_SLSO,
- In the next screen we select view BT115H_SLSO/SOHOverView and go to tab configuration
- Then we select the view configuration that we use in our sales order, and for this configuration we move the assignment block with view BTCUSTOMER_H/MainWindow ( assignment block that contains our custom field) from the list of available assignment blocks to the list of displayed assignment blocks (right)

Test that our field is visible in the sales order


We start the CRM Web UI and we check that the relevant assignment block with Custom-Defined Fields is displayed on header and our field is displayed in this assignment block ( in view BTCUSTOMER_H/CustomerH)

2)  Settings to use our field in pricing in our sales order


We first create the field in the pricing field catalog and afterwards we will adjust the settings that are related to the pricing procedure in our sales order.



Create field in the pricing field catalogue


We create the field in the pricing field catalog in customizing path:
SPRO - Customer Relationship Management - Basic Functions - Define settings for pricing - Maintain Field Catalog

We maintain the field in section "Header and item fields", with the same name and domain as the field created with the AET.

Once the field catalog is regenerated , the field should be displayed in structure CRMT_ACS_I_COM, which is the structure for fields at item level that can be sent to pricing. This structure also contains the fields that are relevant at both header and item levels.

Settings to have our field as relevant for pricing in our Sales order

A customer field can be made relevant for pricing in two ways:

  • The field is used in a customer user exit that is used in the pricing procedure of the business transaction. In this case we would have to add the field to the list of relevant attributes of the user exit in transaction /n/SAPCND/UEASS
  • There is a condition table containing the field, and the condition table is used in the pricing procedure of the business transaction


In our case, the second approach is used. We add our field to condition table CUS00514 ( figure below), and then add our condition table as an access of an access sequence that is linked to a condition type of our pricing procedure ( details not displayed).

3 ) Add the same field to CUSTOMER_I object

The same field that has been added to CUSTOMER_H is added to CUSTOMER_I. As the field must not appear in the user interface at item level, we do not

use the AET to add the field. Instead, we go to transaction SE11 and add the field as an APPEND in the relevant structure of the data dictionary, CRMT_CUSTOMER_I_EXT

4) Define an event callback to transfer field value from CUSTOMER_H to CUSTOMER_I after field value changes


In this point, we define a custom event callback to transfer the field value from header level (CUSTOMER_H) to item level (CUSTOMER_I) when changes occur on the field value on header. The custom event callback, with a Z name (e.g. ZCRM_CUSTOMER_I_MAINTAIN_EC) will be registered to the event AFTER_CHANGE for object CUSTOMER_H. Several steps are needed for this.



Add object function  in view CRMV_OBJ_FUNC



Transacion SM30 - Select view CRMV_OBJ_FUNC
-- Add an object function ( any Z name)

Create  function module ( event handler)



We create the function module that will be used as call-back.
When I created the import and export data, I used I  other callbacks as reference, e.g. standard callback CRM_DATES_ORDERADM_H_EC.

For the moment the coding is empty ( coding added later)

Add my function module as event handler for my obj function in view  CRMV_FUNC_ASSIGN

Transaction SM30 -- View CRMV_FUNC_ASSIGN


I assign the callback function module to the object function created in previous steps

Add object function to object CUSTOMER_H in view CRMV_OBJECT_FUNC

Transaction SM30 -- CRMV_OBJECT_FUNC
I assign the Z object function created in previous step to object CUSTOMER_H

Add event handler for event AFTER_CHANGE pointing to my function module

I maintain the event handler table in customizing path:
SPRO - Customer Relationship Management - Basic Functions - Transactions - Basic settings - Edit event handler table

In this case we want to use it in a sales order, so transaction category is BUS2000115. The event should be triggered after a change occurs in object CUSTOMER_H, hence, it will be event AFTER_CHANGE.
The function that will be triggered is the function module already defined before.

Check callbacks executed for my trans. type in transaction CRMD_EVENT_CHECK (optional)


Even though the coding has not been added to our callback ZLR_CRM_CUSTOMER_I_MAINTAIN_OW , at this point we can check whether the change in our field triggers the execution of our callback by setting a breakpoint in it. In case the breakpoint is not hit , we can use transaction CRMD_EVENT_CHECK to check the list of callbacks for our transaction type. 


Transaction CRMD_EVENT_CHECK

Enter transaction type of our sales order in the first screen

We see that the entry with our callback function ZLR_CRM_CUSTOMER_I_MAINTAIN_OW is displayed ( everything fine so far).

Add ABAP coding for my event handler ( Event AFTER_CHANGE in CUSTOMER_H)

The coding in the  custom event callback, ZLR_CRM_CUSTOMER_I_MAINTAIN_OW, must transfer the field value from header level (CUSTOMER_H) to item level (CUSTOMER_I) . The coding below will perform this task. It has been tested and it works fine  ( even though there could be better options from performance perspective) .




FUNCTION zlr_crm_customer_i_maintain_ow.

*"----------------------------------------------------------------------

*"*"Local Interface:

*"  IMPORTING

*"     REFERENCE(IV_OBJECT_NAME) TYPE  CRMT_OBJECT_NAME

*"     REFERENCE(IV_EVENT_EXETIME) TYPE  CRMT_EVENT_EXETIME

*"     REFERENCE(IV_EVENT) TYPE  CRMT_EVENT

*"     REFERENCE(IV_ATTRIBUT) TYPE  CRMT_EVENT OPTIONAL

*"     REFERENCE(IV_HEADER_GUID) TYPE  CRMT_OBJECT_GUID OPTIONAL

*"     REFERENCE(IV_OBJECT_GUID) TYPE  CRMT_OBJECT_GUID OPTIONAL

*"     REFERENCE(IV_STRVAL_OLD) TYPE  ANY OPTIONAL

*"     REFERENCE(IV_STRVAL_NEW) TYPE  ANY OPTIONAL

*"     REFERENCE(IV_STRUC_NAME) TYPE  TABNAME OPTIONAL

*"----------------------------------------------------------------------

   DATA:

     lt_header_guids   TYPE crmt_object_guid_tab,

     lt_item_guids     TYPE crmt_object_guid_tab,

     lv_item_guid      TYPE crmt_object_guid,

     objname           TYPE crmt_object_name,

     lt_request_objs   TYPE crmt_object_name_tab,

     et_customer_h     TYPE  crmt_customer_h_wrkt,

     ls_customer_h     TYPE crmt_customer_h_wrk,

     ls_customer_i     TYPE crmt_customer_i_com,

     ls_customer_i_com TYPE crmt_customer_i_com,

     lt_field_names    TYPE crmt_input_field_names_tab,

     ls_field_names    TYPE crmt_input_field_names.

   INSERT iv_header_guid INTO TABLE lt_header_guids.

*Get the item guids for this order

   CALL FUNCTION 'CRM_ORDERADM_I_READ_MULTI_OB'

     EXPORTING

       it_header_guids = lt_header_guids

     IMPORTING

       et_item_guids   = lt_item_guids.

** We will read the value for CUSTOMER_H

* insert object names ( names from table CRMC_OBJECTS) to requested objects

   objname = 'CUSTOMER_H'.

   INSERT objname INTO TABLE lt_request_objs.

   CALL FUNCTION 'CRM_ORDER_READ'

     EXPORTING

       it_header_guid       = lt_header_guids

       it_requested_objects = lt_request_objs

       iv_no_auth_check     = 'X'

     IMPORTING

       et_customer_h        = et_customer_h

     EXCEPTIONS

       document_not_found   = 1

       error_occurred       = 2

       document_locked      = 3

       no_change_authority  = 4

       no_display_authority = 5

       no_change_allowed    = 6.

   IF et_customer_h IS INITIAL.

     EXIT.

   ELSE.

* Get the only existing CUSTOMER_H from the internal table

     LOOP AT et_customer_h INTO ls_customer_h.

     ENDLOOP.

   ENDIF.

* For each of the item GUIDs we check if there is a CUSTOMER_I

* If there is no CUSTOMER_I we will create it. Otherwise we will change it

   LOOP AT lt_item_guids INTO lv_item_guid.

     CALL FUNCTION 'CRM_CUSTOMER_I_READ_OB'

       EXPORTING

         iv_guid                       = lv_item_guid

       IMPORTING

         es_customer_i_com             = ls_customer_i_com

       EXCEPTIONS

         item_does_not_exist           = 1

         at_least_one_record_not_found = 2.

     IF ls_customer_i_com IS INITIAL.

* We create the structure . We call CRM_CUSTOMER_I_MAINTAIN_OW with mode A in the structure ( create mode)

* We fill ls_customer_i with value from customer_h

       ls_customer_i-ref_guid = lv_item_guid.

       MOVE ls_customer_h-zzafld00009i TO ls_customer_i-zzafld00009i.

       ls_customer_i-mode = 'A'.

* Fill the list of field names to be changed (only one)

       ls_field_names-fieldname = 'ZZAFLD00009I'.

       INSERT ls_field_names INTO TABLE lt_field_names.

       CALL FUNCTION 'CRM_CUSTOMER_I_MAINTAIN_OW'

         EXPORTING

           is_customer_i_com    = ls_customer_i

         CHANGING

           ct_input_field_names = lt_field_names.

     ELSE.

* We must update existing structure ls_customer_i_com.

* Call CRM_CUSTOMER_I_MAINTAIN_OW' with mode B in the structure (change mode)

       MOVE ls_customer_h-zzafld00009i TO ls_customer_i_com-zzafld00009i.

       ls_customer_i_com-mode = 'B'.

* Fill the list of field names to be changed (only one)

       ls_field_names-fieldname = 'ZZAFLD00009I'.

       INSERT ls_field_names INTO TABLE lt_field_names.

       CALL FUNCTION 'CRM_CUSTOMER_I_MAINTAIN_OW'

         EXPORTING

           is_customer_i_com    = ls_customer_i_com

         CHANGING

           ct_input_field_names = lt_field_names.

     ENDIF.

   ENDLOOP.

ENDFUNCTION.



5)  Define an event callback to transfer the field value from CUSTOMER_H to CUSTOMER_I when item is created

We also need to cover the case that the field value on header is changed even before the item has been created. As the item does not exist yet, we cannot transfer the value from CUSTOMER_H to CUSTOMER_I when our field value has changed.

For this purpose we have to create a custom event callback for object ORDERADM_I and event AFTER_CREATE, which will be executed after the item has been created, and add the coding in the calback to transfer the field value from header level (CUSTOMER_H) to item level (CUSTOMER_I) .

The steps that we must perform to create the callback are similar to those in previous point 4, so I do not show them in detail.
In the last step we add the following entry to the event handler table, where function module ZLR_GET_DATA_FROM_CUSTOMER_H is the callback

ABAP Coding for event handler ( event AFTER_CREATE in ORDERADM_I )

The ABAP code in our callback ZLR_GET_DATA_FROM_CUSTOMER_H, will transfer the field value from header level (CUSTOMER_H) to item level (CUSTOMER_I) when the item is created

FUNCTION zlr_get_data_from_customer_h.

*"----------------------------------------------------------------------

*"*"Local Interface:

*"  IMPORTING

*"     REFERENCE(IV_OBJECT_NAME) TYPE  CRMT_OBJECT_NAME

*"     REFERENCE(IV_HEADER_GUID) TYPE  CRMT_OBJECT_GUID

*"     REFERENCE(IV_OBJECT_GUID) TYPE  CRMT_OBJECT_GUID

*"     REFERENCE(IV_STRVAL_OLD) TYPE  ANY

*"     REFERENCE(IV_STRVAL_NEW) TYPE  ANY

*"     REFERENCE(IV_EVENT_EXETIME) TYPE  CRMT_EVENT_EXETIME

*"     REFERENCE(IV_ATTRIBUT) TYPE  CRMT_EVENT_ATTRIBUT

*"     REFERENCE(IV_EVENT) TYPE  CRMT_EVENT

*"  EXCEPTIONS

*"      ERROR_OCCURED

*"----------------------------------------------------------------------

   DATA:

     lv_item_guid      TYPE crmt_object_guid,

     ls_customer_h_com TYPE crmt_customer_h_com,

     ls_customer_i_com TYPE crmt_customer_i_com,

     ls_customer_i     TYPE crmt_customer_i_com,

     lt_field_names    TYPE crmt_input_field_names_tab,

     ls_field_names    TYPE crmt_input_field_names.

* GUID of this item

   lv_item_guid = iv_object_guid.

* Get the CUSTOMER_H

   CALL FUNCTION 'CRM_CUSTOMER_H_READ_OB'

     EXPORTING

       iv_guid               = iv_header_guid

     IMPORTING

       es_customer_h_com     = ls_customer_h_com

     EXCEPTIONS

       header_does_not_exist = 1.

* If the structure CUSTOMER_H does not exist we exit

   IF ls_customer_h_com IS INITIAL.

     EXIT.

   ELSE.

* Get the structure CUSTOMER_I exists for this item

     CALL FUNCTION 'CRM_CUSTOMER_I_READ_OB'

       EXPORTING

         iv_guid                       = lv_item_guid

       IMPORTING

         es_customer_i_com             = ls_customer_i_com

       EXCEPTIONS

         item_does_not_exist           = 1

         at_least_one_record_not_found = 2.

* If the structure CUSTOMER_I does not exist we create it and fill the value from CUSTOMER_H

     IF ls_customer_i_com IS INITIAL.

* We create the structure . We call CRM_CUSTOMER_I_MAINTAIN_OW with mode A in the structure ( create mode)

* We fill ls_customer_i with value from customer_h

       ls_customer_i-ref_guid = lv_item_guid.

       MOVE ls_customer_h_com-zzafld00009i TO ls_customer_i-zzafld00009i.

       ls_customer_i-mode = 'A'.

* Fill the list of field names to be changed (only one)

       ls_field_names-fieldname = 'ZZAFLD00009I'.

       INSERT ls_field_names INTO TABLE lt_field_names.

       CALL FUNCTION 'CRM_CUSTOMER_I_MAINTAIN_OW'

         EXPORTING

           is_customer_i_com    = ls_customer_i

         CHANGING

           ct_input_field_names = lt_field_names.

     ELSE.

* We must update existing structure ls_customer_i_com.

* Call CRM_CUSTOMER_I_MAINTAIN_OW' with mode B in the structure (change mode)

       MOVE ls_customer_h_com-zzafld00009i TO ls_customer_i_com-zzafld00009i.

       ls_customer_i_com-mode = 'B'.

* Fill the list of field names to be changed (only one)

       ls_field_names-fieldname = 'ZZAFLD00009I'.

       INSERT ls_field_names INTO TABLE lt_field_names.

       CALL FUNCTION 'CRM_CUSTOMER_I_MAINTAIN_OW'

         EXPORTING

           is_customer_i_com    = ls_customer_i_com

         CHANGING

           ct_input_field_names = lt_field_names.

     ENDIF.

   ENDIF.

ENDFUNCTION.

6) Testing in the sales order

Create condition master data (optional)

We can create a condition record in the condition table CUS0014 that contains our field. This is just to have an even more clear evidence that the approach is working, ,but it is not strictly necessary, as the important thing to be tested is that the field is sent to pricing ( with or without condition records being found).

Test in the Sales order


I add screenshots only for the test of the callback created in point 4, that will be called when the item already exists and our field value changes on header. The test for the other callback, called when the item is created, can be done with a similar approach. 

Before starting the CRM Web UI

  • I set a breakpoint in method ITEM_COMMUNICATION_STRUCTURE of an active implementation of BADI CRM_COND_COM_BADI. This breakpoint can be used to see the field values sent to pricing ( even though the fields may not be manipulated in the BADI itself)
  • I set user parameter PRC_TRACE = X , so that the pricing trace can be displayed

I start the CRM Web UI and create a sales order and add one item.

Then I go back to header. I see our field, labeled "Luis Field" in assignment block "Customer.Defined fields"
I give the field "Luis Field" a value of 150

The breakpoint in BADI CRM_COND_COM_BADI is hit, and I see that the structure CS_ACS_I_COM contains our field ZZAFLD00009I with value 150.

So this means that the field has been properly transferred from CUSTOMER_H to CUSTOMER_I by our callback ZLR_CRM_CUSTOMER_I_MAINTAIN_OW. The standard coding just made the rest, that is transferring the field from CUSTOMER_I to the structure at item level in BADI CRM_COND_COM_BADI. Hence the field will be transferred to pricing.

Finally, we go to item level in the order to check the pricing trace
We see that the access to table CUS500014 contains our field ZZAFLD00009I  ( labeled "Luis Field") with the proper field value that we had entered in the sales order, 150.
As we had created a condition record that will be hit with that field value, our condition record is found