Thursday, 13 September 2018

Pitfall of the CORRESPONDING operator during IDoc processing

One of my recent tasks was to develop a couple of inbound IDoc interfaces in MM. If briefly, when an IDoc from the external SAP system comes to my client’s SAP, the appropriate interface should handle it, resulting in creation or change of a Purchase Requisition. And as in almost every development, a couple of interesting problems/challenges have been detected and obviously fixed. But one sneaky bug took a few working hours of my time, so I decided to share my experience with you, dear readers, in this very first blog post of mine.

In my development, IDocs arrive in formats of basic types PREQCR102 (for PR creation) or PREQCHANGE02 (for changes on existing PRs and POs). These standard SAP IDoc basic types characterize themselves as very convenient for manipulations with Purchase Requisition documents because their segment structures correspond with BAPIs ‘BAPI_PR_CREATE’ and ‘BAPI_PR_CHANGE’. This advantage allows such IDocs to be easily (at least in theory) processed. In ideal world, all you need is to loop through IDoc non-empty segments, append their content into appropriate internal tables and send them to BAPI. In reality, however, you’ll most likely want to implement some validations of input data, augment it with some constant values etc. But still, in terms of input data format, these IDoc types are pretty convenient to work with.

A few words about BAPIs: the two BAPIs mentioned above – ‘BAPI_PR_CREATE’ and ‘BAPI_PR_CHANGE’ – as well as many other BAPIs, contain so called “X” structures/tables. For example, PR header data structure PRHEADER has its “X” structure called PRHEADERX. Table with PR item data PRITEM goes together with PRITEMX; account’s table PRACCOUNT – with PRACCOUNTX, and so on. The purpose of these “X” structures/tables is to explicitly define which parameters from “normal” tables you want a BAPI to take into consideration. So, it you populate “normal” tables with some set of values, you must assign value ‘X’ (abap_true) to all the fields with the same name in the appropriate “X” table. If you fail to do that, a BAPI will just disregard “normal” values that do not have their “counterparts” marked as ‘X’ in the “X” structure/table.

Okay, that was the theory, now it’s time for the bug I found in my code. In this post I will use a dummy IDoc with basic type PREQCR102 (or PREQCHANGE02, they have the same segment structure), focusing on the Item segment just because data from this segment is mandatory in PR BAPIs.

If you are at least somehow familiar with development of inbound IDoc interfaces, you should know that the data in every segment is stored in field ‘sdata’ of one of IDoc’s input tables idoc_data. This long character field is basically a container that we need to split and assign its parts to appropriate fields of a segment’s structure. For the sake of simplicity, let’s work with a single Item segment:

  DATA: ls_idoc_data TYPE edidd. "defining a work area of the idoc_data input table
  ls_idoc_data = idoc_data[ segnam = 'E1BPMEREQITEMIMP' ]. "filling the work area with data from the Item segment

Now it’s time to „unwrap” the data itself and populate the appropriate structure. This can be achieved in at least two ways:

◈ You can just copy the content of the ‘sdata’ field to the appropriate structure. Since in these particular IDoc basic types all segment structures have only character fields, this approach will do its job and won’t cause any Unicode compiler errors;

◈ Or you can choose a slightly more correct approach by using the read_container_c static method of SAP standard cl_abap_container_utilities

In this demo either way works, so here are both:

DATA: ls_itemimp_idoc TYPE e1bpmereqitemimp.

  "option 1:
  ls_itemimp_idoc = ls_idoc_data-sdata.

  "option 2:
  cl_abap_container_utilities=>read_container_c(
      EXPORTING
        im_container           = ls_idoc_data-sdata
      IMPORTING
        ex_value               = ls_itemimp_idoc
      EXCEPTIONS
        illegal_parameter_type = 1
        OTHERS                 = 2 ).
  IF sy-subrc <> 0.
    "throw exception maybe?
  ENDIF.

Regardless of the approach, we now should have our Item data in the ls_itemimp_idoc structure. Cool! Now it’s time to populate the BAPI-ready structure with this data. As I mentioned earlier, for the IDoc basic types mentioned above, an IDoc’s segment data has the same set of fields as the BAPI’s data, but with one big difference. IDoc’s segment structures consist of only character fields, whereas components of BAPI’s structures and tables have their dedicated dictionary types:

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certification

This means that if you try to mindlessly push IDoc values to the BAPI structure, you will most probably face the following compiler error:

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certification

To overcome this restriction, you may want to use the CORRESPONDING (or old-fashioned MOVE-CORRESPONDING) operator:

ls_itemimp_bapi = CORRESPONDING #( ls_itemimp_idoc ).

At first sight, this can solve all of our problems: since both structures have identical field names, CORRESPONDING will take care of mapping. And at this very moment a little sneaky bug appears. But prior to showing it, let’s see what happens if you decide that CORRESPONDING is sufficient. Once we’ve got our BAPI-ready structure filled with data, we need to populate the “X” structure. In this case – PRITEMX. Of course you don’t wanna do this by hardcoded loop of every component of the “normal” structure, checking whether it contains any value, and finally populating the appropriate field with ‘X’ in the “X” structure. The most obvious and correct way is to get the list of the “normal” structure fields, loop through them by dynamically assigning their values to a field symbol, do the same with the “X” structure, and finally populate the “X” structure. Let’s do that:

 DATA: ls_itemx TYPE bapimereqitemx.
  DATA: lo_itemimp_bapi_struct_def TYPE REF TO cl_abap_structdescr.

  "getting the list of fields of the structure ls_itemimp_bapi to the the instance attribute of object the structure description class.
  lo_itemimp_bapi_struct_def ?= cl_abap_tabledescr=>describe_by_data( ls_itemimp_bapi ).

  LOOP AT lo_itemimp_bapi_struct_def->components ASSIGNING FIELD-SYMBOL(<ls_itemimp_bapi_component>).
    ASSIGN COMPONENT <ls_itemimp_bapi_component>-name OF STRUCTURE ls_itemimp_bapi TO FIELD-SYMBOL(<value>).
    ASSIGN COMPONENT <ls_itemimp_bapi_component>-name OF STRUCTURE ls_itemx TO FIELD-SYMBOL(<value_x>).
    IF <value> IS NOT INITIAL.
      CASE <ls_itemimp_bapi_component>-name.
        WHEN 'PREQ_ITEM'. "exception for "PREQ_ITEM field, as in "X" structure it should have the same value as in normal one.
          <value_x> = <value>.
        WHEN OTHERS.
          <value_x> = 'X'.
      ENDCASE.
    ENDIF.
  ENDLOOP.

Okay, now we should have ‘X’ values in ls_itemx for all the components that have any values in ls_itemimp_bapi. Pretty obvious, huh? But wait, what’s that? Do you also see ‘X’ alongside the fields that are supposed to be empty in ls_itemimp_bapi?

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certification

Some of you have already spotted the root cause. But for those who haven’t – elaborating. In ABAP, predefined elementary types D and T (for date and time, respectively), have initial values, respectively, ‘00000000’ and ‘000000’, but not the empty value like in case of type C. In fact, ABAP runtime environment doesn’t treat an empty date of time field as the field with initial value, thus, the “X” structure gets populated accordingly. But how comes that these fields weren’t defaulted in structure ls_itemimp_bapi? Well, they were defaulted at the moment of structure declaration, but later they were filled with empty (but non-default) values during CORRESPONDING operation!

How to avoid these kinds of situations? I have applied the same solution as in case of normal-to-X structures mapping. I replaced the CORRESPONDING operator with the following code:

DATA: lo_itemimp_idoc_struct_def TYPE REF TO cl_abap_structdescr.

  lo_itemimp_idoc_struct_def ?= cl_abap_tabledescr=>describe_by_data( ls_itemimp_idoc ).

  LOOP AT lo_itemimp_idoc_struct_def->components ASSIGNING FIELD-SYMBOL(<ls_itemimp_idoc_component>).
    ASSIGN COMPONENT <ls_itemimp_idoc_component>-name OF STRUCTURE ls_itemimp_idoc TO FIELD-SYMBOL(<value_idoc>).
    ASSIGN COMPONENT <ls_itemimp_idoc_component>-name OF STRUCTURE ls_itemimp_bapi TO FIELD-SYMBOL(<value_bapi>).
    IF <value_idoc> IS NOT INITIAL.
      <value_bapi> = <value_idoc>.
    ENDIF.
  ENDLOOP.

The difference between this approach and the CORRESPONDING operator is that the former pre-validates whether character fields are empty (and you remember that in IDoc structure all fields are character-only, don’t you?), and only if a field in the IDoc structure is not initial, only then it is assigned to the corresponding field of the BAPI structure. Thanks to that, we do not force assignment of all empty date, time and other non-character fields from the IDoc structure to the BAPI structure. And due to that, they remain with their initial values, thus, are not reflected in the “X” structure:

SAP ABAP Study Materials, SAP ABAP Guides, SAP ABAP Certification

As you have just seen, this simple improvement can help you to achieve correct results in IDoc interfaces processing.

No comments:

Post a Comment