SAP Fiori Notifications – Part 2
In this blog we will explain how to connect SAP Fiori Notifications with the classic SAP Workflow. We will explain it along with a Real Business Scenario Case in a step-by-step example with the entire code snippets.
Prerequisites
To such functionality work, you need before do the customizing of the Fiori Notification. To do so, follow the steps discussed in the Part 1 of this blog: https://www.exxcellent.de/tech-blog/2021-01-25-sapfiori_notifications1/SAP-Fiori-Notifications-Part-1/
Real Business Case Scenario 3
In this Scenario we are going to send the Fiori Notification based on the SAP Workflow result that we are going to create from scratch. The scope: everytime a Purchase Order is created or changed in transaction ME21N/ME22N with price bigger or equal to 10.000 EUR a Fiori Notification will be sent to the Head of Department. Then, the Head of Department is going to approve or reject the Purchase Order. When it is rejected, the Purchase Order items will be blocked. Below we have a big picture of the flow of the process, which is going to start once we create or change a Purchase Order with price over 10.000 EUR:
To do this, first we need to create a custom workflow for this criteria. First, lets create a Purchase Order via transaction ME21N and trace the workflow Object Types in background. Before saving the Purchase Order lets activate the workflow trace trough SWELS transaction and check the results:
Checking the trace in transaction SWEL, we can see that the following Object Types were called:
Now let’s change the Purchase Order to 10.000 EUR via ME22n and check the trace again:
Trace:
We have then the Business Object BUS2012 as the main Object Type for the Purchase Orders. Another way to find the correct Object Type, would be by the transaction itself:
Now, copy this Object Type and create a custom Object Type via transaction SWO1. Use the BUS2012 as the super type: SWO1:
In the next screen make sure to always change the release status of the Object Type and Object Type Component to „implemented“ after you change or create something. Set the release status to „released“ when you want to transport it to the test system or production system.
Create a new event in the object type Z_PO_FIORI:
Then update the status to implemented:
Now lets create 3 new attributes like below:
Now lets code the „GET“ of these attributes, by clicking in „Program“:
CreatedBy:
get_property createdby changing container. SELECT SINGLE ernam FROM ekko INTO @DATA(lv_ernam) WHERE ebeln = @object-key-purchaseorder. IF sy-subrc = 0. SELECT SINGLE adrp~name_text INTO @DATA(lv_name_text) FROM usr21 JOIN adrp ON usr21~persnumber = adrp~persnumber WHERE usr21~bname = @lv_ernam. ENDIF. object-createdby = lv_name_text. swc_set_element container 'CreatedBy' object-createdby. end_property.
FullPrice:
get_property fullprice changing container. DATA: lv_fullprice TYPE ekpo-netwr. SELECT a~ebeln, a~ebelp, a~netwr, a~menge, b~waers, b~ernam FROM ekpo AS a INNER JOIN ekko AS b ON ( a~ebeln = b~ebeln ) INTO TABLE @DATA(lt_ekpo) WHERE a~ebeln = @object-key-purchaseorder. IF sy-subrc = 0. LOOP AT lt_ekpo ASSIGNING FIELD-SYMBOL(<fs_ekpo>). lv_fullprice = <fs_ekpo>-netwr + lv_fullprice. ENDLOOP. swc_set_element container 'FullPrice' lv_fullprice. ENDIF. end_property.
The Currency is automatically inherited from object type BUS2012, so it is not necessry to do a customizing code for it.
Now, delegate the BUS2012 to our Object Type Z_PO_FIORI trough transaction SWO1:
To send the notification to the Head of Department, we need to create a Rule. This rule will get the workflow initiator, in other words, the user who created or changed the Purchase Order and then get the Head of Department, who is responsible for this user. For example:
If the user Anja or Derick, create or change a Purchase Order over 10.000, then the Head of Department Igor will receive a notification to approve or reject the Purchase Order. We create the rule through transaction PFAC:
Create a new import element called "Initiator" in the Container tab:
Now lets code the Function Module "Z_RULE_PO_PRICE":
FUNCTION z_rule_po_price. *"---------------------------------------------------------------------- *"*"Local Interface: *" TABLES *" ACTOR_TAB STRUCTURE SWHACTOR *" AC_CONTAINER STRUCTURE SWCONT *" EXCEPTIONS *" NOBODY_FOUND *"---------------------------------------------------------------------- INCLUDE <cntn01>. DATA: lt_objec TYPE STANDARD TABLE OF objec, lv_initiator TYPE wfsyst-initiator, lv_user TYPE sy-uname. swc_get_element ac_container 'Initiator' lv_initiator. lv_user = lv_initiator+2. "The Notification will be sent only to the Head of the Department "Get the Organization Unit CALL FUNCTION 'RH_STRUC_GET' EXPORTING act_otype = 'O' act_objid = '50000077' " eXXcellent Solutions Organization Unit act_wegid = 'AI_ORGUS' " All users of the Organization Unit TABLES result_objec = lt_objec EXCEPTIONS no_plvar_found = 1 no_entry_found = 2 OTHERS = 3. IF sy-subrc = 0. "Send notification only to the Head of Department READ TABLE lt_objec TRANSPORTING NO FIELDS WITH KEY objid = '50000079'. IF sy-subrc = 0. DATA(lv_tabix) = sy-tabix + 1. TRY. DATA(ls_object) = lt_objec[ lv_tabix ]. CHECK lv_user <> ls_object-realo. "Avoid the Head of sending the notification to himself READ TABLE lt_objec TRANSPORTING NO FIELDS WITH KEY realo = lv_user. "Make sure that the user is in the Organization Unit IF sy-subrc = 0. actor_tab-otype = 'US'. actor_tab-objid = ls_object-realo. APPEND actor_tab. ENDIF. CATCH cx_sy_itab_line_not_found. ENDTRY. ENDIF. ENDIF. IF actor_tab IS INITIAL. RAISE nobody_found. ENDIF. ENDFUNCTION.
Now lets simulate the rule:
As we can see, it is working.
Now enter in the Workflow Builder through PFTC or SWDD and lets create the flow. The flow is going to have a task where the user will decide to approve or not the Purchase Order:
Create the element "PurchaseOrder" in the container and link it to the Object Type "Z_PO_FIORI":
Do the connection between the Task and the Object type at the "Triggering Events" Tab:
Navigate to the workflow builder through the button at the „Basic data“ tab or through the transaction SWDD:
Click on the Header, through the Hat button and configure the Start Events like below:
Do the Binding:
Now lets create a User Decision task:
Give it a Title, set the parameters, give the rule created and write the decision options:
Now do the Binding between the Workflow and the Rule:
Go to the Control tab and do the automatic Binding between the Workflow and the Task:
Now, go to the Notification tab, give the Workflow initiator as the Recipient of Completion Message and click on the Push Notification:
Now, fill the Actions and Texts. Save it:
Now we need to create 2 Tasks, one to approve and another one to reject the Purchase Order:
Reject Task:
Enter in the task and fill the Basic data:
The method "BLOCKPO" should be created in the Object Type Z_PO_FIORI in transaction SWO1:
In this Method we will block the Purchase Order:
begin_method blockpo changing container. DATA: lt_poitem TYPE STANDARD TABLE OF bapimepoitem, lt_poitemx TYPE STANDARD TABLE OF bapimepoitemx, ls_poitemx TYPE bapimepoitemx, lt_return TYPE STANDARD TABLE OF bapiret2. CALL FUNCTION 'BAPI_PO_GETDETAIL1' EXPORTING purchaseorder = object-key-purchaseorder TABLES return = lt_return poitem = lt_poitem. LOOP AT lt_return TRANSPORTING NO FIELDS WHERE type CA 'EAX'. DATA(lv_error) = abap_true. ENDLOOP. IF lv_error IS INITIAL. LOOP AT lt_poitem ASSIGNING FIELD-SYMBOL(<fs_poitem>). <fs_poitem>-delete_ind = 'S'. "Block ls_poitemx-po_item = <fs_poitem>-po_item. ls_poitemx-po_itemx = abap_true. ls_poitemx-delete_ind = abap_true. APPEND ls_poitemx TO lt_poitemx. CLEAR ls_poitemx. ENDLOOP. REFRESH lt_return. CALL FUNCTION 'BAPI_PO_CHANGE' EXPORTING purchaseorder = object-key-purchaseorder TABLES return = lt_return poitem = lt_poitem poitemx = lt_poitemx. LOOP AT lt_return TRANSPORTING NO FIELDS WHERE type CA 'EAX'. lv_error = abap_true. ENDLOOP. IF lv_error IS INITIAL. CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'. ENDIF. ENDIF. end_method.
Fill the Completion Text as below:
Create an import element in the Container:
Save it, return to the Task in SWDD and do the binding as below:
Fill the Recipient of Completion Message:
Now, we must do the same steps as above for the Approve task:
Code for the "APPROVEPO" Method:
begin_method approvepo changing container. DATA: lt_poitem TYPE STANDARD TABLE OF bapimepoitem, lt_poitemx TYPE STANDARD TABLE OF bapimepoitemx, ls_poitemx TYPE bapimepoitemx, lt_return TYPE STANDARD TABLE OF bapiret2. CALL FUNCTION 'BAPI_PO_GETDETAIL1' EXPORTING purchaseorder = object-key-purchaseorder TABLES return = lt_return poitem = lt_poitem. LOOP AT lt_return TRANSPORTING NO FIELDS WHERE type CA 'EAX'. DATA(lv_error) = abap_true. ENDLOOP. IF lv_error IS INITIAL. LOOP AT lt_poitem ASSIGNING FIELD-SYMBOL(<fs_poitem>). <fs_poitem>-delete_ind = space. " Free ls_poitemx-po_item = <fs_poitem>-po_item. ls_poitemx-po_itemx = abap_true. ls_poitemx-delete_ind = abap_true. APPEND ls_poitemx TO lt_poitemx. CLEAR ls_poitemx. ENDLOOP. REFRESH lt_return. CALL FUNCTION 'BAPI_PO_CHANGE' EXPORTING purchaseorder = object-key-purchaseorder TABLES return = lt_return poitem = lt_poitem poitemx = lt_poitemx. LOOP AT lt_return TRANSPORTING NO FIELDS WHERE type CA 'EAX'. lv_error = abap_true. ENDLOOP. IF lv_error IS INITIAL. CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'. ENDIF. ENDIF. end_method.
Fill the Completion Text as below:
Create the import element in the task Container:
Do the Binding:
Fill the Recipient of Completion Message:
Finally as a final step, we need to call the workflow event through abap code. But where to do it exactly? Well, when we save the Purchase Order in the transaction ME21N or ME22N. That’s why we have chosen the BADI "ME_PURCHDOC_POSTED" , if you want to learn more about this step and how to find the correct BADI for your process, please follow the link: https://wiki.scn.sap.com/wiki/pages/viewpage.action?pageId=133758980
Implement the BADI "ME_PURCHDOC_POSTED" through se18 transaction:
Code the method like below and activate the code. Note that here we will call our workflow event only when the total price of the Purchase Order is equal or more expensive than 10.000.
METHOD if_ex_me_purchdoc_posted~posted. DATA: lv_ebeln TYPE swr_struct-object_key, lv_total_price TYPE ekpo-netwr, lt_msg_lines TYPE STANDARD TABLE OF swr_messag. LOOP AT im_ekpo ASSIGNING FIELD-SYMBOL(<fs_ekpo>). lv_total_price = lv_total_price + <fs_ekpo>-netwr. ENDLOOP. CHECK lv_total_price >= 10000. lv_ebeln = im_ekko-ebeln. CALL FUNCTION 'SAP_WAPI_CREATE_EVENT' EXPORTING object_type = 'Z_PO_FIORI' object_key = lv_ebeln event = 'SENDNOTIFICATION' TABLES message_lines = lt_msg_lines. ENDMETHOD.
Testing
With the user ABC, we are going to create a Purchase Order through ME21N transaction with a price over 10.000 EUR:
Notification in transaction SBWP from the Head of Department:
Notification in Fiori Launchpad from the Head of Department:
Now lets press Reject and check what happens with the Purchase Order:
As we can see, the Purchase Order was blocked:
Then the user who created or changed the Purchase Order, receive also a notification of Completion:
We could also send a Fiori Notification as feedback instead of only an entry in SBWP. To do it, you can use what you learn in the Part 1 of this Blog Post and implement it inside the Task „Approve“ and „Reject“. Since this 2 methods are executed in background, then you need to send the notification manually by calling the method: /iwngw/cl_notification_api=>create_notifications And by configuring a new Notification Channel Provider in the SPRO transaction.
Conclusion
As we can see the SAP Fiori Notification is very well integrated with the classic SAP Workflow and both technologies complement each other.
Image sources
The cover image used in this post was created by Unsplash under the following license. All other images on this page were created by eXXcellent solutions under the terms of the Creative Commons Attribution 4.0 International License