Wednesday, 23 September 2020

IDoc modification made easy with ABAP Object Oriented Programming

Introduction

Throughout all my years with ABAP development, modifying IDocs in an easy to understand, elegant way has always been a struggle. First, the issue of making changes to the segments, second, the issue of correct sequence of the IDoc’s segments.

In this blog post, I will show how to easily perform the create, read, update and delete (CRUD) operations on IDoc segments, thanks to the use of ABAP Object Oriented Programming.

The standard way

The Intermediate Document (IDoc) format is widely used throughout the SAP system, with thousands of message types and countless segment types. Still, the standard way of handling the CRUD operations relies on performing modifications directly on the internal table that contains all of the message’s segments. This means that you, the developer, are responsible for not only modifying the values in the segments, but also for maintaining the correct structure of the IDoc (segments sequence). This can be cumbersome when dealing with different IDoc types/extensions and adds unnecessary work.

The example below shows the standard logic responsible for creating an invoice IDoc, as seen in the function ISU_IDOC_INVOICE_CREATE:

*$*$ macro to append a segment to an IDOC

*   &1  segment name

*   &2  segment data

  define append_segment.

    clear t_idoc_data.

    t_idoc_data-segnam = &1.

    t_idoc_data-sdata  = &2.

    append t_idoc_data.

    clear &2.

  end-of-definition.

  case e1isu_begin_inv_big4010-big08.

    when co_cancel.

*   Invoice cancelled

      ref-ref01 = co_original_doc_no.

*     BUILD UNIQUE TRANSACTION NUMBER

      concatenate x_invoice_data-header-intopbel

      x_invoice_data-erch-belnr into ref-ref02 .

      append_segment co_seg_ref ref .

    when others.

*     Do nothing

  endcase.

ABAP objects

To simplify the code responsible for IDoc CRUD operations, you can implement a Facade pattern. The proposed facade consists of a set of three custom classes, and the relationship between them is shown on the UML diagram below:

SAP ABAP Tutorial and Materials, SAP ABAP Exam Prep, SAP ABAP Certification, SAP ABAP Learning, SAP ABAP Prep

UML diagram of Facade objects’ relationships

In short, each of the classes is responsible for:

◉ ZCL_IDOC_EDIDD – the root segment of the IDoc and generic IDoc operations,
◉ ZCL_IDOC_EDIDD_SEGMENT – any other segment of the IDoc and generic segment operations,
◉ ZCX_IDOC_EXCEPTIONS – exceptions raised in case of an incorrect operation, e.g. adding a segment of an incorrect type.

The ABAP OOP way


Before any modification is done to the IDoc segments, you need to create an object, which will represent the message. You can do this using the CREATE_WITH_DATA method of the ZCL_IDOC_EDIDD class.

CONSTANTS:
  lc_type_orders05 TYPE edi_idoctp VALUE 'ORDERS05',
  lc_extension_none TYPE edi_cimtyp VALUE ''.

DATA:
  lt_edidd TYPE edidd_tt.

TRY.
    DATA(lo_idoc) = zcl_idoc_edidd=>create_with_data(
        iv_idoc_type = lc_type_orders05
        iv_idoc_extension = lc_extension_none
        it_edidd = lt_edidd ).
  CATCH zcx_idoc_exceptions INTO DATA(lo_exception).
    MESSAGE lo_exception TYPE 'I'.
    RETURN.
ENDTRY.

As you can see in the example above, the first two parameters define the message type and extension. This way, the LO_IDOC object is able to retrieve all information about the structure of the message that is required for maintaining the correct sequence of the segments. 

The IT_EDIDD parameter is optional. You should populate it with the EDIDD internal table of the IDoc you wish to modify.

When the IDoc object is created, you are ready to modify its contents. You can use the ADD_SEGMENT method to add a new segment to the IDoc. Remember about catching exceptions that may be raised during the operation, for example if the segment is not part of the message type.

DATA:
  ls_edidd TYPE edidd,
  ls_e1edka1 TYPE e1edka1.

ls_e1edka1-parvw = 'WE'.
ls_e1edka1-partn = '1234567890'.

ls_edidd-segnam = 'E1EDKA1'.
ls_edidd-sdata = ls_e1edka1.

TRY.
    lo_idoc->add_segment( ls_edidd ).
  CATCH zcx_idoc_exceptions INTO DATA(lo_exception).
    MESSAGE lo_exception TYPE 'I'.
    RETURN.
ENDTRY.

As you can see, there is no need to worry about the position of the added segment. The LO_IDOC object will handle it on its own, adding the segment in the correct position under the root segment of the IDoc or under the last parent segment (if a non-root parent is required).

If the parent segment is not found, an exception will be raised.

You can also add a child segment directly under a specific parent segment.

DATA:
  ls_edidd TYPE edidd,
  ls_e1edp01 TYPE e1edp01,
  ls_e1edp19 TYPE e1edp19.

ls_e1edp01-posex = '10'.
ls_e1edp19-qualf = '003'.

TRY.
    ls_edidd-segnam = 'E1EDP01'.
    ls_edidd-sdata = ls_e1edp01
    DATA(lo_item) = lo_idoc->add_segment( ls_edidd ).

    CLEAR ls_edidd.
    ls_edidd-segnam = 'E1EDP19'.
    ls_edidd-sdata = ls_e1edp19.
    lo_item->add_segment( ls_edidd ).
  CATCH zcx_idoc_exceptions INTO DATA(lo_exception).
    MESSAGE lo_exception TYPE 'I'.
    RETURN.
ENDTRY.

This way, you are in total control under which parent the new segment will appear in the IDoc. But how to find the segment you are interested in? For this, you need to use the GET_SEGMENTS method, which returns a collection of segments. Have a look at the example below:

DATA:
  ls_edidd TYPE edidd,
  ls_e1edp01 TYPE e1edp01,
  ls_e1edp19 TYPE e1edp19,
  lo_segment TYPE REF TO zcl_idoc_edidd_segment.

ls_e1edp19-qualf = '003'.
ls_edidd-segnam = 'E1EDP19'.
ls_edidd-sdata = ls_e1edp19.

TRY.
    DATA(lo_collection) = lo_idoc->get_segments( 'E1EDP01' ).
    DATA(lo_iterator) = lo_collection->get_iterator( ).

    WHILE lo_iterator->has_next( ).
      lo_segment ?= lo_iterator->get_next( ).
      ls_e1edp01 = lo_segment->get_sdata( ).

      " add child under position 50
      CHECK ls_e1edp01-posex = '50'.
      lo_segment->add_segment( ls_edidd ).
      EXIT.
    ENDWHILE.
  CATCH zcx_idoc_exceptions INTO DATA(lo_exception).
    MESSAGE lo_exception TYPE 'I'.
    RETURN.
ENDTRY.

Modification of any of the segments is as simple as finding it, retrieving its SDATA structure, modifying it and putting it back into the segment.

DATA:
  ls_e1edka1 TYPE e1edka1,
  lo_segment TYPE REF TO zcl_idoc_edidd_segment.

" get the first E1EDKA1 segment
lo_segment ?= lo_idoc->get_segments( 'E1EDKA1' )->get( 1 ).

IF lo_segment IS BOUND.
  ls_e1edka1 = lo_segment->get_sdata( ).
  ls_e1edka1-partn = '0987654321'.
  lo_segment->set_sdata( ls_e1edka1 ).
ENDIF.

Last but not least, the delete operation.

If you want to remove any of the segments from the IDoc, simply use the REMOVE_SEGMENT method. The segment will be removed even if it is not placed directly under the specified parent. This means that if you use the LO_IDOC object for the method call, the segment will be removed regardless of its position in the IDoc.

DATA:
  lo_segment TYPE REF TO zcl_idoc_edidd_segment.

" get the first E1EDKA1 segment
lo_segment ?= lo_idoc->get_segments( 'E1EDKA1' )->get( 1 ).
IF lo_segment IS BOUND.
  lo_idoc->remove_segment( lo_segment ).
ENDIF.

It is worth mentioning that when you remove a segment from the IDoc, all of its children are removed as well. So, again, there is no need to bother with maintaining the correct structure of the message.

After you are finished with the modifications, all is left to do is to retrieve the resulting internal table containing the IDoc segments. You can do this by calling the GET_EDIDD method.

DATA(lt_edidd) = lo_idoc->get_edidd( ).

No comments:

Post a Comment