IDoc modification made easy with ABAP Object Oriented Programming
Hi! Welcome to my first SAP Community blog post.
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.
This blog post first appeared on the Int4 blog.
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.
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:
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( ).
With the use of just three simple ABAP classes, you are able to simplify the implementation of IDoc CRUD operations, leaving handling of the correctness of the segments’ sequence to the system.
You can download the solution from GitHub. Please, let me know your thoughts in the comments.
Very nice. Like the iterator approach, had something similar but liked your best. Will refactor my "goods movement idoc" "commercial invoice idoc" classes to use it!
That's great to hear, Felipe.
Really nice and a common task we all have on the list. So I like the get methods and have to say, my personal solution is a bit weaker.. so I think I will adopt this here.
Thanks for sharing.
Joachim Rees have a look here, think you also know people to share it 🙂
Nice to hear.
I imagine this could be a base for more concrete, segment related classes. Still, it helped me a lot even being so generic.
Thank you and Very nice. Inspired with this, was thinking if the object oriented principles can be used here. May be that will the justify that the code "is Open for extension & closed for modification". It's just a food for thought. Something like this: pls ignore the micro-details of the diagram
The example code I presented is indeed very generic. A more concrete solution could be created, for sure. Depends on the situation and needs.
Thank you so much! Perfect code ?
I will refactor my code...
Thanks a lot Tommy. I'm glad it's helpful.