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:
- SAP Odata Extensions
- Extension of the Workflow container to fill in the extended fields using the BADI /IWWRK/ES_WF_WF_WI_BEFORE_UPD_IB
- SAP UI5 Extenstions using adaption of Views and Controllers
- 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.
- Create new entity to fetch the list of Users and their names to be displayed for the first level of approval
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
- CustomerExtensionForObjectListItem
S3.view.xml
- CustomerExtensionForObjectHeader
- CustomerExtensionForInfoTabContent
- CustomerExtensionForNoteTabContent
- CustomerExtensionForAttachmentTabContent
- CustomerExtensionForAdditionalTabs
- 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