SAP S/4HANA, SAP Fiori

Fiori Workflow Inbox Extensions using UI5 and Odata Extensions – Actions During Approval

SAP Fiori Inbox extension is a common extension scenario in most of the S/4 HANA Projects .This blog focusses on selection of data during the first level of approval in workflow and passing it over to the other level of workflows.

These are below steps to be to achieve this:

  1. SAP Odata Extensions
  2. Extension of the Workflow container to fill in the extended fields using the BADI /IWWRK/ES_WF_WF_WI_BEFORE_UPD_IB
  3. SAP UI5 Extenstions using adaption of Views and Controllers
  4. Use the Extended Ui5 Application and configure the Fiori Launch Pad Designer with the new application ID and remove the original UI5 application from the Role

The below diagram refers to the possible places where the UI extensions are possible in Fiori INBOX

We will refer a simple scenario where in first level approver will be able to select the second level approver from a list of users and provide some comments for the second level approver.

Step 1 ODATA Extension:

  • Extend the standard Odata Service /IWPGW/TASKPROCESSING with Z_TASKPROCESSINGDEMO
  • Now navigate to the entity Task and extend with the additional field User ID, User Name, Comment and Visibility fields for User ID and Comment and other Approval Data if required.
Structure for Task Extension
  • Create new entity to fetch the list of Users and their names to be displayed for the first level of approval
Approvers Structure
Approvers Entity

After the service generation implement the code in the DPC_EXT class as shown below

Redefine the method ENTITYSET_TASK and write the below code .

The below code will read the workflow container and display the required values and also sets the visibility of the Approver selection and comment field selection it appears only for the first level approver for selection

method entityset_task.
    data : lt_tasks_ext type zcl_z_taskprocessingde_Mpc_EXT=>tt_task,
           lt_tasks     type /iwpgw/if_tgw_types=>tt_tasks,
           ls_tasks_ext type zcl_z_taskprocessingde_Mpc_EXT=>ts_task,
           lo_tasks     type ref to /iwpgw/if_tgw_types=>tt_tasks,
           ls_task      type /iwpgw/if_tgw_types=>ty_task.

    field-symbols: <ls_simple_container> type swr_cont.

    data: lv_subrc      like sy-subrc,
          lv_wf_id      type   swr_struct-workitemid,
          ls_new_status type swr_wistat.
    data : lx_valid type xfeld.
    data: lt_simple_container         type table of swr_cont,
          ls_simple_container         type swr_cont,
          lt_message_lines            type table of swr_messag,
          lt_message_struct           type table of swr_mstruc,
          ls_message_struct           type swr_mstruc,
          lt_subcontainer_bor_objects type table of swr_cont,
          lt_subcontainer_all_objects type table of swr_cont.

    try.
        super->entityset_task(
          exporting
            io_tech_request_context = io_tech_request_context
          importing
            er_entityset            = er_entityset
            es_response_context     = es_response_context ).
      catch /iwbep/cx_mgw_busi_exception.
        message id sy-msgid type 'E' number sy-msgno with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    endtry.
    if er_entityset is  bound.
*-- map results
      create data lo_tasks.
      lo_tasks ?= er_entityset.
*--get the container values from task
      loop at lo_tasks->* into ls_task where task_def_id = 'TS90000001'.
        clear ls_tasks_ext.
        move-corresponding ls_task to ls_tasks_ext.
        refresh lt_simple_container.
*--fill workitem container
        lv_wf_id = ls_tasks_ext-inst_id .
*-- GET workitem container

        call function 'SAP_WAPI_READ_CONTAINER'
          exporting
            workitem_id      = lv_wf_id
            user             = sy-uname
          importing
            return_code      = lv_subrc
          tables
            simple_container = lt_simple_container
            message_lines    = lt_message_lines
            message_struct   = lt_message_struct.
*      EXCEPTIONS
*        error_message    = 1
*        OTHERS           = 2.

*-- Get the Workflow Container Data
        data(ls_wfcont) = lt_simple_container[ element = 'cont_wf' ]-value.

        ycl_wf=>validate_pdf_data(
                                exporting
                                is_wfcont =   ls_wfcont
                               importing
                                valid = lx_valid   ).

        if lx_valid is not initial.
          if lt_simple_container[ element = 'APPROVER_LEVEL' ]-value ne 2.
            ls_tasks_ext-uservisibility =  abap_true.
            ls_tasks_ext-commentvisibility     = abap_true.
             ls_tasks_ext-comment    = ls_wfcont-comment.
          endif.
        endif.
        if   lx_valid is not initial.
          ls_tasks_ext-commentvisibility = abap_true.
          ls_tasks_ext-uservisibility =  abap_false.
        endif.
        append ls_tasks_ext to lt_tasks_ext.
        clear ls_tasks_ext.
      endloop.
      call method copy_data_to_ref
        exporting
          is_data = lt_tasks_ext
        changing
          cr_data = er_entityset.
      refresh : lt_tasks_ext.
    endif.
  endmethod.

Now Redefine the method get_entityset_extend and fill the entity with the user ID and User names information as shown below:

method get_entityset_extend.
    data : it_wf_users type standard table of zinboxapprovals.
*try.
    call method super->get_entityset_extend
      exporting
        iv_entity_name           = iv_entity_name
        iv_entity_set_name       = iv_entity_set_name
        iv_source_name           = iv_source_name
        it_filter_select_options = it_filter_select_options
        it_order                 = it_order
        is_paging                = is_paging
        it_navigation_path       = it_navigation_path
        it_key_tab               = it_key_tab
        iv_filter_string         = iv_filter_string
        iv_search_string         = iv_search_string
*       io_tech_request_context  =
*  importing
*       er_entityset             =
*       es_response_context      =
      .
*  catch /iwbep/cx_mgw_busi_exception.
*  catch /iwbep/cx_mgw_tech_exception.
*endtry.
    if iv_entity_name = 'WFAPPROVERS'.
      append value #( userid = 'Test1'
                      name_first = 'TESTNAME'
                      name_last = 'TESTLAST'  ) to it_wf_users.
      append value #( userid = 'Test2'
                name_first = 'TESTNAME'
                name_last = 'TESTLAST'  ) to it_wf_users.
      copy_data_to_ref(
     exporting
       is_data = it_wf_users
     changing
       cr_data = er_entityset ).
    endif.
  endmethod.

In order to move the selected the user to the workflow container after approving the task from Inbox we have to redefine the method action_decision:

METHOD action_decision.
    DATA: lv_subrc      LIKE sy-subrc,
          ls_new_status TYPE swr_wistat.

    DATA: lt_simple_container         TYPE TABLE OF swr_cont,
          ls_simple_container         TYPE swr_cont,
          lt_message_lines            TYPE TABLE OF swr_messag,
          lt_message_struct           TYPE TABLE OF swr_mstruc,
          ls_message_struct           TYPE swr_mstruc,
          lt_subcontainer_bor_objects TYPE TABLE OF swr_cont,
          lt_subcontainer_all_objects TYPE TABLE OF swr_cont.
    DATA : lo_message_container TYPE REF TO /iwbep/if_message_container.


    TRY.
        DATA(lv_instance_id)  = extract_instance_id( it_parameter ).
        DATA(lv_decision_key) = extract_decision_key( it_parameter ).
*-- GET workitem container

        CALL FUNCTION 'SAP_WAPI_READ_CONTAINER'
          EXPORTING
            workitem_id      =  lv_instance_id
            user             = sy-uname
          IMPORTING
            return_code      = lv_subrc
          TABLES
            simple_container = lt_simple_container
            message_lines    = lt_message_lines
            message_struct   = lt_message_struct.
*      EXCEPTIONS
*        error_message    = 1
*        OTHERS           = 2.

*          DATA(ls_wfcont) = lt_simple_container[ element = 'cont_wf' ]-value.
        DATA : lx_valid TYPE xfeld.
        ycl_wf=>validate_pdf_data(
                                EXPORTING
                                is_wfcont =   ls_wfcont
                                IMPORTING
                                  valid = lx_valid   ).

        IF lv_decision_key = 2  .
          IF   lx_valid  IS NOT INITIAL .
            lo_message_container = mo_context->get_message_container( ).
            CALL METHOD lo_message_container->add_message_text_only
              EXPORTING
                iv_msg_type = 'E'
                iv_msg_text = CONV #( TEXT-004 ).
            RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
              EXPORTING
                message_container = lo_message_container.
            EXIT.
          ENDIF.
        ENDIF.

        IF lv_decision_key = 1.
          DATA(lv_approver2) = it_parameter[ name = 'APPROVER_LEVEL' ]-value.
          IF lv_approver2 IS NOT INITIAL.

            SELECT SINGLE * FROM usr21
              INTO @DATA(ls_usr21)
              WHERE bname = @lv_approver2.
            IF sy-subrc IS INITIAL.
              SELECT SINGLE * FROM adr6
                INTO @DATA(ls_adr6)
                WHERE addrnumber = @ls_usr21-addrnumber
                  AND persnumber = @ls_usr21-persnumber
                  AND flgdefault = @abap_true.
              IF sy-subrc IS INITIAL.
                DATA(lv_approver_email) = ls_adr6-smtp_addr.
*                DATA: ls_simple_container TYPE swr_cont.

                FIELD-SYMBOLS: <ls_simple_container> TYPE swr_cont.

                READ TABLE lt_simple_container WITH KEY element = 'CV_APPROVER_EMAIL' ASSIGNING <ls_simple_container>.
                IF sy-subrc = 0.
                  <ls_simple_container>-value = lv_approver_email.
                ELSE.
                  ls_simple_container-element = 'CV_APPROVER_EMAIL'.
                  ls_simple_container-value   = lv_approver_email.
                  APPEND ls_simple_container TO lt_simple_container.
                ENDIF.

              ENDIF.
            ENDIF.
            lv_approver2 = |US{ lv_approver2 }|.

            READ TABLE lt_simple_container WITH KEY element = 'CV_APPROVER' ASSIGNING <ls_simple_container>.
            IF sy-subrc = 0.
              <ls_simple_container>-value = lv_approver2.
            ELSE.
              ls_simple_container-element = 'CV_APPROVER'.
              ls_simple_container-value   = lv_approver2.
              APPEND ls_simple_container TO lt_simple_container.
            ENDIF.
            CALL FUNCTION 'SAP_WAPI_WRITE_CONTAINER'
              EXPORTING
                workitem_id      =  lv_instance_id
                actual_agent     = sy-uname
              IMPORTING
                return_code      = lv_subrc
              TABLES
                simple_container = lt_simple_container
                message_lines    = lt_message_lines
                message_struct   = lt_message_struct.

          ELSEIF lt_simple_container[ element = 'CV_APPROVER_LEVEL' ]-value NE 2
                  AND lx_valid IS  INITIAL .

            lo_message_container = mo_context->get_message_container( ).
            CALL METHOD lo_message_container->add_message_text_only
              EXPORTING
                iv_msg_type = 'E'
                iv_msg_text = CONV #( TEXT-003 ).
            RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
              EXPORTING
                message_container = lo_message_container.
            EXIT.
          ELSEIF lt_simple_container[ element = 'CV_APPROVER_LEVEL' ]-value EQ 1
                AND ( lx_valid IS NOT  INITIAL  ).


            READ TABLE lt_simple_container WITH KEY element = 'CV_APPROVER_EMAIL' ASSIGNING <ls_simple_container>.
            IF sy-subrc = 0.
              <ls_simple_container>-value =  ' '.
            ELSE.
              ls_simple_container-element = 'CV_APPROVER_LEVEL'.
              ls_simple_container-value   = ' '.
              APPEND ls_simple_container TO lt_simple_container.
            ENDIF.

            READ TABLE lt_simple_container WITH KEY element = 'CV_APPROVER' ASSIGNING <ls_simple_container>.
            IF sy-subrc = 0.
              <ls_simple_container>-value =  ' '.
            ELSE.
              ls_simple_container-element = 'CV_APPROVER'.
              ls_simple_container-value   = ' '.
              APPEND ls_simple_container TO lt_simple_container.
            ENDIF.
            CALL FUNCTION 'SAP_WAPI_WRITE_CONTAINER'
              EXPORTING
                workitem_id      =  lv_instance_id
                actual_agent     = sy-uname
              IMPORTING
                return_code      = lv_subrc
              TABLES
                simple_container = lt_simple_container
                message_lines    = lt_message_lines
                message_struct   = lt_message_struct.
          ENDIF.
        ENDIF.
        CALL METHOD super->action_decision
          EXPORTING
            it_parameter = it_parameter
          RECEIVING
            rr_data      = rr_data.
      CATCH /iwbep/cx_mgw_busi_exception.
        MESSAGE ID sy-msgid TYPE 'E' NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
      CATCH /iwbep/cx_mgw_tech_exception.
        MESSAGE ID sy-msgid TYPE 'E' NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDTRY.
  ENDMETHOD.

Once the Odata is implemented register the Odata Service

Step 2 BADI Implementation:

Implement the BADI /IWWRK/BADI_WF_BEFORE_UPD_IB so that we can update the workflow with the second level approver details:

/IWWRK/IF_WF_WI_BEFORE_UPD_IB~BEFORE_UPDATE

method /IWWRK/IF_WF_WI_BEFORE_UPD_IB~BEFORE_UPDATE.
    DATA: lv_subrc      LIKE sy-subrc,
          ls_new_status TYPE swr_wistat.
    DATA: lt_simple_container TYPE TABLE OF swr_cont,
          ls_simple_container TYPE swr_cont,
          lt_message_lines    TYPE TABLE OF swr_messag,
          lt_message_struct   TYPE TABLE OF swr_mstruc,
          ls_message_struct   TYPE swr_mstruc.
 CALL FUNCTION 'SAP_WAPI_READ_CONTAINER'
      EXPORTING
        workitem_id      = is_wi_details-wi_id
        user             = sy-uname
      IMPORTING
        return_code      = lv_subrc
      TABLES
        simple_container = lt_simple_container
        message_lines    = lt_message_lines
        message_struct   = lt_message_struct.

    IF lv_subrc <> 0.
*      lo_excp = cx_fmef_msg=>create_from_symsg(  ).
*      RAISE EXCEPTION lo_excp.
    ENDIF.
    IF iv_decision_key EQ 1.

      set_workflow_container_element(
       EXPORTING
         if_element = 'APPROVE'
         if_value   = abap_true
       CHANGING
         ct_simple_container = lt_simple_container ).

   CALL FUNCTION 'SAP_WAPI_WORKITEM_COMPLETE'
      EXPORTING
        workitem_id      = is_wi_details-wi_id
        actual_agent     = sy-uname
        do_commit        = 'X'
      IMPORTING
        return_code      = ev_subrc
        new_status       = ls_new_status
      TABLES
        simple_container = lt_simple_container
        message_lines    = lt_message_lines.

      IF lv_subrc <> 0.
**        lo_excp = cx_fmef_msg=>create_from_symsg(  ).
**        RAISE EXCEPTION lo_excp.
      ENDIF.
      IF lv_subrc <> 0.
      ENDIF.
    ELSE.
     set_workflow_container_element(
           EXPORTING
             if_element = 'REJECT'
             if_value   = abap_true
           CHANGING
             ct_simple_container = lt_simple_container ).
    set_workflow_container_element(
           EXPORTING
             if_element = 'REJECTION_TEXT'
             if_value   = it_wf_container_tab[ element = /iwwrk/if_wf_constants_gw=>gc_action_comments ]-value
           CHANGING
             ct_simple_container = lt_simple_container ).
  CALL FUNCTION 'SAP_WAPI_WORKITEM_COMPLETE'
      EXPORTING
        workitem_id      = is_wi_details-wi_id
        actual_agent     = sy-uname
        do_commit        = 'X'
      IMPORTING
        return_code      = ev_subrc
        new_status       = ls_new_status
      TABLES
        simple_container = lt_simple_container
        message_lines    = lt_message_lines.

      IF lv_subrc <> 0.
**        lo_excp = cx_fmef_msg=>create_from_symsg(  ).
**        RAISE EXCEPTION lo_excp.
      ENDIF.
    ENDIF.
  ENDMETHOD.

Step 3 Implement the SAP UI5 Extensions:

There are multiple ways where we can extend the Fiori Inbox front end as shown in the below links:

https://help.sap.com/viewer/d2c296c4f32d4f2a9e3752f58d5ef222/2.0%202017-05/en-US/ddfc595461fce630e10000000a44538d.html

2118812 – How to Extend SAP Fiori My Inbox – SAP ONE Support Launchpad

The above SAP Note provides the cook book for the extensions .

The below section is referred from the cook box for the view and controller extensions .

The following picture gives an overview about which extension points to use when you want to extend a specific section of the application screen:

The following picture gives an overview about which extension points to use when you want to extend a specific section of the application screen:

S2.view.xml

  1. CustomerExtensionForObjectListItem

S3.view.xml

  1. CustomerExtensionForObjectHeader
  2. CustomerExtensionForInfoTabContent
  3. CustomerExtensionForNoteTabContent
  4. CustomerExtensionForAttachmentTabContent
  5. CustomerExtensionForAdditionalTabs
  6. CustomerExtensionForAdditionalDetails

Controller Hooks:

The below extensions are performed for the inbox extensions:

Create a extension project for the standard Fiori application CA_FIORI_INBOX

Add the below code in the Manifest.json

"title": "{{SHELL_TITLE}}",	
"dataSources": {
			"TASKPROCESSING": {
				"uri": "/sap/opu/odata/sap/ZTASKPROCESSINGDEMO_SRV;mo",
				"settings": {
					"localUri": "./localService/metadata.xml"
				}
			},
			"APPROVER": {
				"uri": "/sap/opu/odata/sap/ZTASKPROCESSINGDEMO_SRV;mo",
				"settings": {
					"localUri": "./localService/metadata.xml"
				}
			}
},

We will be extending S3 View and controller in order to show the selection of the users:

Implement the S3 View for information tab extension S3_CustomerExtensionForInfoTabContentCustom.fragment.xml’

<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m" xmlns:form="sap.ui.layout.form" xmlns:layout="sap.ui.layout"
	xmlns:sap.ca.ui="sap.ca.ui" xmlns:suite="sap.suite.ui.commons">
	<layout:VerticalLayout class="sapUiContentPadding" width="100%">
		<form:SimpleForm id="customAttributesContainer1" editable="false" layout="ColumnLayout" title="{i18n>Inboxext}">
			<form:content>
				<Label visible="{detail>/CommentVisibility}" text="{i18n>Comments}"/>
				<Text visible="{detail>/CommentVisibility}" text="{detail>/Comment}"/>
				<Label visible="{detail>/UserVisibility}" text="{i18n>Approver2}"/>
				<ComboBox visible="{detail>/UserVisibility}" showSecondaryValues="true" id="Approver2" filterSecondaryValues="true"
					items="{ path: 'oApprover>/WfApproversCollection', sorter: { path: 'UserID' } }">
					<core:ListItem key="{oApprover>Userid}" text="{oApprover>User_First} {oApprover>User_Last}"/>
				</ComboBox>
			</form:content>
		</form:SimpleForm>
	</layout:VerticalLayout>
</core:FragmentDefinition>

In order to data to appear on the S3 view also extend and rewrite the controller and do the below changes:

In the function onExit: function () comment the code in order to avoid any issue with cache

// this.oComponentCache.destroyCacheContent();
		// delete this.oComponentCache;
		// if (sap.ca.scfld.md.controller.BaseDetailController.prototype.onExit) {
		// 	sap.ca.scfld.md.controller.BaseDetailController.prototype.onExit.call(this);
		// }

To send the details of the approver 2 add the below function at the end of the controller

_sendActionext: function (sFIName, oDecision, sNote, fnSuccess, fnError) {

		oUrlParams.Approver2 = "'" + oDecision.Approver2 + "'";

		this.oDataManager._performPost("/" + sFIName, oDecision.SAP__Origin, oUrlParams,
			$.proxy(function (oDecision, fnSuccess) {
				this.oDataManager.fnShowReleaseLoader(false);
				// remove the processed item from the list
				this.oDataManager.processListAfterAction(oDecision.SAP__Origin, oDecision.InstanceID);
				this.oDataManager.triggerRefresh("SENDACTION", this.ACTION_SUCCESS);
				this.oDataManager.fireActionPerformed();
				// call the original success function
				if (fnSuccess) {
					fnSuccess();
				}
			}, this, oDecision, fnSuccess),
			$.proxy(function (oError) {
				this.oDataManager.fnShowReleaseLoader(false);
				// call the original error function
				if (fnError) {
					fnError(oError);
				}
				//refresh the list after error and select a task accordingly
				this.oDataManager.processListAfterAction(oDecision.SAP__Origin, oDecision.InstanceID);
				this.oDataManager.fireActionPerformed();
				this.oDataManager.triggerRefresh("SENDACTIONEXT", this.ACTION_FAILURE);
			}, this.oDataManager), sErrorMessage);
	}

Additionally pass the date of the visibility Enhance the S2 Custom Controller using the extHookGetPropertiesToSelect fragment extension:

sap.ui.controller("cross.fnd.fiori.inbox.INBOX_EXT.view.S2Custom", {

extHookGetPropertiesToSelect: function () {

var select = [ "Comment", "UserVisibility","CommentVisibility"];
return select;
}

});

Step 4

This step can be referred from the cook book

This way we can enhance the SAP Fiori inbox extensions with Odata and UI5 Extensions for performing custom actions during the approvals

Leave a Reply

Your email address will not be published. Required fields are marked *