Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
All of us have come to see certain limitations in standard Fiori applications, whether we are talking about the missing Dunning Data at Company Code level in Manage Business Partner App or an upload feature in the Create Sales Order VA01 app. That is why freestyle applications can still make a difference to achieve the same level of operability in the Fiori Launchpad as the user did in the past using the SAP GUI transactions. Sometimes exposing the transaction to SAP GUI is simply not the way to go.

Introduction


The following solution is intended to demonstrate that we can build an OData service in the backend that handles the CRUD  operations ( Create, Read, Delete) for attaching a document to a particular sales order. We will connect in a later post this service to a custom UI5 app that will use an UploadSet control which will help us upload one or more files from our device ( desktop/tablet or phone) and attach them to a particular sales order.


 

 

 

 

A short  disclaimer to everybody who checks the standard apps VA01/VA02 for upload, you might be missing the GOS(Generic Object Services) toolbar, which has to be implemented. This is not mandatory needed for us though.



GOS Toolbar in VA03 - Display Sales Documents


Creating the OData Service

First thing that we have to do is create a OData Service (SEGW), that contains 3 Entity Types, where we have to select his media checkbox:




  • Create File and File entity types, can be defined with the same properties





  • GetAttach



 

For this entity, we need to create an ABAP structure, that we will use when retrieving the format:


Afterwards declare the entity sets:


With this concludes the structural building of our OData and we can proceed to generating it and moving on to our next step, redefining the Model and Data Provider Class methods that are created.

At this step you can also add the newly created OData service to the service catalog, using this reference. This is necessary in order to test the entity calls in the SAP Gateway Client.

Redefining Methods

 

In the MDC_EXT Class, we need to redefine just the Define method:



 method DEFINE.
*&---------------------------------------------------------------------*
* TITLE : DEFINE
* AUTHOR : Mihai-Alexandru Zaharescu
* DESCRIPTION : Attachments Implementation: set proprty MIME type for appropriate entities
*----------------------------------------------------------------------*

DATA: lr_entity_typ TYPE REF TO /iwbep/if_mgw_odata_entity_typ,
lr_property TYPE REF TO /iwbep/if_mgw_odata_property.

super->define( ).

* GetAttach
lr_entity_typ = model->get_entity_type( iv_entity_name = 'GetAttach' ) ##NO_TEXT.
IF lr_entity_typ IS BOUND.
lr_property = lr_entity_typ->get_property( iv_property_name = 'MIME_TYPE' ).
lr_property->set_as_content_type( ).
ENDIF.

* File
lr_entity_typ = model->get_entity_type( iv_entity_name = 'File' ) ##NO_TEXT.
IF lr_entity_typ IS BOUND.
lr_property = lr_entity_typ->get_property( iv_property_name = 'MIME_TYPE' ).
lr_property->set_as_content_type( ).
ENDIF.

* CreateFile
lr_entity_typ = model->get_entity_type( iv_entity_name = 'CreateFile' ) ##NO_TEXT.
IF lr_entity_typ IS BOUND.
lr_property = lr_entity_typ->get_property( iv_property_name = 'MIME_TYPE' ).
lr_property->set_as_content_type( ).
ENDIF.
endmethod.

Next we proceed to the DPC methods that need to be tackled:

  • /IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_STREAM

  • /IWBEP/IF_MGW_APPL_SRV_RUNTIME~DELETE_STREAM

  • /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM

  • GETATTACHSET_GET_ENTITYSET


The first redefine method is the CREATE_STREAM which will handle the creation of the Sales Order Attachment. This is achieved by getting the sales order number and file name from the import variable iv_slug, in order to provide the adequate extension, retrieving the attachment data and converting it from xstring to xtab. With the file ready, we use the FM SO_DOCUMENT_INSERT_API1 to create the attachment file in the system and later link the sales order(s) with the created attachment file.
 METHOD /iwbep/if_mgw_appl_srv_runtime~create_stream.

* local declaration
TYPES: BEGIN OF ts_data,
file_name TYPE string,
mime_type TYPE string,
END OF ts_data.

DATA: ls_folder_id TYPE soodk,
lv_folder_id TYPE so_obj_id,
lv_slug TYPE string,
lt_vbeln TYPE TABLE OF vbeln,
lv_file_name TYPE char255,
lv_extension TYPE char3,
lt_objhead TYPE soli_tab,
lt_cont_hex TYPE solix_tab,
ls_doc_data TYPE sodocchgi1,
ls_doc_info TYPE sofolenti1,
ls_rolea TYPE borident,
ls_roleb TYPE borident,
ls_data TYPE ts_data.

* Get folder id
CALL FUNCTION 'SO_FOLDER_ROOT_ID_GET'
EXPORTING
region = 'B'
IMPORTING
folder_id = ls_folder_id
EXCEPTIONS
communication_failure = 1
owner_not_exist = 2
system_failure = 3
x_error = 4
OTHERS = 5.
IF sy-subrc IS INITIAL.
lv_folder_id = ls_folder_id.
ENDIF.

*Get sales order number(s) and file name from import variable IV_SLUG
SPLIT iv_slug AT '/' INTO lv_slug lv_file_name.
SPLIT lv_slug AT '|' INTO TABLE lt_vbeln.

*set up document data
ls_doc_data = VALUE #( obj_name = 'MESSAGE'
obj_descr = lv_file_name
obj_langu = sy-langu ).

*Get extension from file name
CALL FUNCTION 'TRINT_FILE_GET_EXTENSION'
EXPORTING
filename = lv_file_name
uppercase = abap_true
IMPORTING
extension = lv_extension.

* set up object head
lt_objhead = VALUE #( ( line = '&SO_FILENAME=' && lv_file_name )
( line = '&SO_FORMAT=BIN' )
( line = '&SO_CONTTYPE=' && is_media_resource-mime_type ) ).

* convert attachment data from xstring to xtab
TRY.
cl_bcs_convert=>xstring_to_xtab(
EXPORTING
iv_xstring = is_media_resource-value
IMPORTING
et_xtab = lt_cont_hex ).

CATCH cx_bcs INTO DATA(lr_cx_bcs).
DATA(lv_csmsg) = lr_cx_bcs->if_message~get_text( ) ##NEEDED.
RETURN.

ENDTRY.

* Create attachment file in system
CALL FUNCTION 'SO_DOCUMENT_INSERT_API1'
EXPORTING
folder_id = lv_folder_id
document_data = ls_doc_data
document_type = lv_extension
IMPORTING
document_info = ls_doc_info
TABLES
object_header = lt_objhead
contents_hex = lt_cont_hex
EXCEPTIONS
folder_not_exist = 1
document_type_not_exist = 2
operation_no_authorization = 3
parameter_error = 4
x_error = 5
enqueue_error = 6
OTHERS = 7.
IF sy-subrc IS NOT INITIAL.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO lv_csmsg.
RETURN.
ENDIF.

* Link sales order(s) with created attachment file
LOOP AT lt_vbeln ASSIGNING FIELD-SYMBOL(<ls_vbeln>).
ls_rolea = VALUE #( objkey = <ls_vbeln>
objtype = 'BUS2032' ).

ls_roleb = VALUE #( objkey = ls_doc_info-doc_id
objtype = ls_doc_info-obj_name ).

CALL FUNCTION 'BINARY_RELATION_CREATE_COMMIT'
EXPORTING
obj_rolea = ls_rolea
obj_roleb = ls_roleb
relationtype = 'ATTA'
EXCEPTIONS
no_model = 1
internal_error = 2
unknown = 3
OTHERS = 4.
IF sy-subrc IS NOT INITIAL.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO lv_csmsg.
RETURN.
ENDIF.

AT LAST.
COMMIT WORK.
ENDAT.

ENDLOOP.

* Fill the export parameter er_entity accordingly
ls_data = VALUE #( file_name = lv_file_name
mime_type = is_media_resource-mime_type ).

copy_data_to_ref(
EXPORTING
is_data = ls_data
CHANGING
cr_data = er_entity ).
ENDMETHOD.

Testing the outcome is always vital, so after each method redefinition I will provide the test data accordingly that was executed in the SAP Gateway Client. (/iwfnd/maint_service). As a prerequisite for this we need to do a operation like GET or HEAD beforehand to obtain the CSRF token, as well as adding the slug header alongside the "sales_order_number/filename_with_extension".

Enter the following URI:
/sap/opu/odata/sap/ZSRV_UPLOAD_SRV/CreateFileSet


Handling the Delete is probably the shortest method since we just only need to fetch the document ID and details and just loop the retrieved table in order to delete the attachment.
method /IWBEP/IF_MGW_APPL_SRV_RUNTIME~DELETE_STREAM.

* local declaration
DATA: lv_doc_id TYPE swo_typeid,
ls_objectd TYPE borident,
lv_csmsg TYPE string ##NEEDED,
lr_attsrv TYPE REF TO cl_gos_document_service.

* retrieve document id
READ TABLE it_key_tab INTO DATA(ls_key) WITH KEY name = 'DOCUMENT_ID'.
IF sy-subrc IS INITIAL.
lv_doc_id = ls_key-value.
ENDIF.

* fetch complete details
SELECT brelguid,instid_a,typeid_a,logsys_a FROM srgbtbrel
INTO TABLE @DATA(lt_srgbtbrel)
WHERE instid_b = @lv_doc_id
AND typeid_b = 'MESSAGE'
AND catid_b = 'BO'.
IF sy-subrc IS NOT INITIAL.
MESSAGE e002(msg002) WITH lv_doc_id INTO lv_csmsg.
RETURN.
ENDIF.

LOOP AT lt_srgbtbrel ASSIGNING FIELD-SYMBOL(<ls_srgbtbrel>).
* formulate object details
ls_objectd = VALUE #( objkey = <ls_srgbtbrel>-instid_a
objtype = <ls_srgbtbrel>-typeid_a
logsys = <ls_srgbtbrel>-logsys_a ).

* delete the attachment
CREATE OBJECT lr_attsrv.
lr_attsrv->delete_attachment(
EXPORTING
is_object = ls_objectd
ip_attachment = lv_doc_id ).

COMMIT WORK.
ENDLOOP.
endmethod.

Entering the following URI ( note that you will have to substitute the document ID with the one that was created in your system)
/sap/opu/odata/sap/ZSRV_UPLOAD_SRV/FileSet(DOCUMENT_ID='FOL42000000000004EXT46000000000112')/$value


For the GET call , that will be used to populate the table in our UI5 application, we will use the GET_ENTITYSET in order to return the Attachment data of a particular Sales Order. The reason for using GET_ENTITYSET and not GET_STREAM method for this, is because we want to give the user the possibility to fetch multiple entries at a time. We will be redefining the GET_STREAM method as well, however, we will be using it just for downloading individual specific documents.

The following method is retrieving the input information from the request, since the DPC works with internal property names, whilst the runtime API interface holds a external property name. The process will need then to get all of the information from the technical request context object along with the filter/select option information. With all of this obtained we proceed by mapping the filter table lines with the function module parameter. Later we call a custom FM that reads all of the links of the attachments and retrieves our data with the help of another FM SO_DOCUMENT_READ_API1. The result should return one or several records of documents that were stored in that specific Sales Order Number.
  method GETATTACHSET_GET_ENTITYSET.

DATA: lt_object_id TYPE RANGE OF ZGETATTACHMENT-object_id,
lt_object_type TYPE RANGE OF ZGETATTACHMENT-object_type,
lt_object_cat TYPE RANGE OF ZGETATTACHMENT-object_cat,
lt_document_id TYPE RANGE OF ZGETATTACHMENT-document_id,
lv_object_id TYPE sibfboriid,
lv_object_type TYPE sibftypeid,
lv_object_cat TYPE sibfcatid,
lv_document_id TYPE documentid.

* Get filter or select option information
DATA(lr_filter) = io_tech_request_context->get_filter( ).
DATA(lt_filter_so) = lr_filter->get_filter_select_options( ).
DATA(lv_filter_st) = lr_filter->get_filter_string( ).

** Check if the supplied filter is supported by standard gateway runtime process
IF lv_filter_st IS NOT INITIAL AND lt_filter_so IS INITIAL.
* if the string of the filter system query option is not automatically converted into
* filter option table (lt_filter_so), then the filtering combination is not supported
* LOG MESSAGE in the application LOG
me->/iwbep/if_sb_dpc_comm_services~log_message(
EXPORTING
iv_msg_type = 'E'
iv_msg_id = '/IWBEP/MC_SB_DPC_ADM'
iv_msg_number = '025' ).

* raise exception
RAISE EXCEPTION TYPE /iwbep/cx_mgw_tech_exception
EXPORTING
textid = /iwbep/cx_mgw_tech_exception=>internal_error.
ENDIF.

* Maps filter table lines to function module parameters
LOOP AT lt_filter_so INTO DATA(ls_filter_so).

CASE ls_filter_so-property.
WHEN 'OBJECT_ID'.
lr_filter->convert_select_option(
EXPORTING
is_select_option = ls_filter_so
IMPORTING
et_select_option = lt_object_id ).

READ TABLE lt_object_id INTO DATA(ls_object_id) INDEX 1.
IF sy-subrc IS INITIAL.
lv_object_id = ls_object_id-low.
ENDIF.

WHEN 'OBJECT_TYPE'.
lr_filter->convert_select_option(
EXPORTING
is_select_option = ls_filter_so
IMPORTING
et_select_option = lt_object_type ).

READ TABLE lt_object_type INTO DATA(ls_object_type) INDEX 1.
IF sy-subrc IS INITIAL.
lv_object_type = ls_object_type-low.
ENDIF.

WHEN 'OBJECT_CAT'.
lr_filter->convert_select_option(
EXPORTING
is_select_option = ls_filter_so
IMPORTING
et_select_option = lt_object_cat ).

READ TABLE lt_object_cat INTO DATA(ls_object_cat) INDEX 1.
IF sy-subrc IS INITIAL.
lv_object_cat = ls_object_cat-low.
ENDIF.

WHEN 'DOCUMENT_ID'.
lr_filter->convert_select_option(
EXPORTING
is_select_option = ls_filter_so
IMPORTING
et_select_option = lt_document_id ).

READ TABLE lt_document_id INTO DATA(ls_document_id) INDEX 1.
IF sy-subrc IS INITIAL.
lv_document_id = ls_document_id-low.
ENDIF.

WHEN OTHERS.
* log message in the application log
me->/iwbep/if_sb_dpc_comm_services~log_message(
EXPORTING
iv_msg_type = 'E'
iv_msg_id = '/IWBEP/MC_SB_DPC_ADM'
iv_msg_number = '020'
iv_msg_v1 = ls_filter_so-property ).

* raise exception
RAISE EXCEPTION TYPE /iwbep/cx_mgw_tech_exception
EXPORTING
textid = /iwbep/cx_mgw_tech_exception=>internal_error.

ENDCASE.

ENDLOOP.

* Get Sales order attachment list
CALL FUNCTION 'ZGET_ATTACHMENT'
EXPORTING
iv_object_id = lv_object_id
iv_object_type = lv_object_type "'BUS2032'
iv_object_cat = lv_object_cat "'BO'
iv_document_id = lv_document_id
IMPORTING
et_entityset = et_entityset
es_response_context = es_response_context.

endmethod.

Enter the following URI:
/sap/opu/odata/sap/ZSRV_UPLOAD_SRV/GetAttachSet?$filter=OBJECT_ID eq '0000000002' and OBJECT_TYPE eq 'BUS2032' and OBJECT_CAT eq 'BO'


Last Method that we will need is GET_STREAM. This will be used to download the file attachment from the sales order.
METHOD /iwbep/if_mgw_appl_srv_runtime~get_stream.

* local declaration
DATA: lv_doc_id TYPE so_entryid,
ls_doc_data TYPE sofolenti1,
lt_obj_header TYPE STANDARD TABLE OF solisti1,
lt_obj_cont TYPE STANDARD TABLE OF solisti1,
lt_cont_hex TYPE STANDARD TABLE OF solix,
lv_csmsg TYPE string ##NEEDED,
lv_mime_type TYPE w3conttype,
ls_stream TYPE /iwbep/cl_mgw_abs_data=>ty_s_media_resource.

* fetching document id
READ TABLE it_key_tab INTO DATA(ls_key) WITH KEY name = 'DOCUMENT_ID'.
IF sy-subrc IS INITIAL.
lv_doc_id = ls_key-value.
ENDIF.

* fetching attachment data
CALL FUNCTION 'SO_DOCUMENT_READ_API1'
EXPORTING
document_id = lv_doc_id
IMPORTING
document_data = ls_doc_data
TABLES
object_header = lt_obj_header
object_content = lt_obj_cont
contents_hex = lt_cont_hex
EXCEPTIONS
document_id_not_exist = 1
operation_no_authorization = 2
x_error = 3
OTHERS = 4.
IF sy-subrc IS NOT INITIAL.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO lv_csmsg.
RETURN.
ENDIF.

**.. setting header
set_header( is_header = VALUE #( name = 'Content-Disposition'
value = |attachment; filename="{ ls_doc_data-obj_descr }"| ) ) ##NO_TEXT.

* fetch the MIME tyoe
CALL FUNCTION 'SDOK_MIMETYPE_GET'
EXPORTING
extension = ls_doc_data-obj_type
IMPORTING
mimetype = lv_mime_type.
ls_stream-mime_type = lv_mime_type.

* read attachment data and pass it back to UI
TRY.
READ TABLE lt_obj_header ASSIGNING FIELD-SYMBOL(<ls_objhead>)
WITH KEY line = '&SO_FORMAT=BIN'.
IF sy-subrc IS INITIAL.
ls_stream-value = cl_bcs_convert=>xtab_to_xstring( it_xtab = lt_cont_hex ).

ELSE.
ls_stream-value = cl_bcs_convert=>txt_to_xstring( it_soli = lt_obj_cont ).

ENDIF.
CATCH cx_bcs INTO DATA(lr_cx_bcs).
lv_csmsg = lr_cx_bcs->if_message~get_text( ).
RETURN.

ENDTRY.

copy_data_to_ref(
EXPORTING
is_data = ls_stream
CHANGING
cr_data = er_stream ).

ENDMETHOD.

Enter the following URI:
/sap/opu/odata/sap/ZSRV_UPLOAD_SRV/FileSet(DOCUMENT_ID='FOL42000000000004EXT47000000000019')/$value


Conclusion

At this step, we now have a working OData service that can perform uploading, downloading, removing and displaying the attachments for any sales order that is created in VA01. With this ready we can proceed to building the UI5 app that will handle the frontend upload part.

In next blog post, we will consume this OData in a freestyle UI5 application using UploadSet.

Stay Tuned.
1 Comment
Labels in this area