12

Upload and Modify Documents in VA01/VA02 using UI5 freestyle application – part...

 2 years ago
source link: https://blogs.sap.com/2022/02/18/upload-and-modify-documents-in-va01-va02-using-ui5-freestyle-application-part-1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
February 18, 2022 12 minute read

Upload and Modify Documents in VA01/VA02 using UI5 freestyle application – part 1

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

Upload-Attachment.jpg

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%20Toolbar%20in%20VA01

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:

ODATA7.jpg

 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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK