If you have connected any workflows to a pernr via BUS1065 (like it is described here), you can receive all related workflows/workitems related to this pernr via the following code:
NEW cl_def_im_com_bsp_workflow( )->if_ex_com_bsp_workflow~read_workitems_for_object( EXPORTING iv_swo_objtype = 'BUS1065'
iv_swo_objkey = CONV #( lv_pernr )
IMPORTING et_workitems = DATA(lt_workitems) ).
LOOP AT lt_workitems ASSIGNING FIELD-SYMBOL(<workitems>).
"If you are only interested in specific workflows, you could filter here
"WHERE wi_rh_task = 'WSxxxxxxxx'
"AND wi_stat = 'STARTED'.
NEW /iwwrk/cl_wf_read_workitem( <workitems>-wi_id )->get_wi_container( IMPORTING et_wi_container = DATA(lt_wi_container) ).
" access container items via lt_wi_container[ element = 'IV_PERNR' ]-value
ENDLOOP.
To attach your own workflow in this overview, you have to add the Task TS51900010 to your workflow and pass over the employee number to the Business Object “Employee” (BUS1065)
The Function Module SAP_WAPI_START_WORKFLOW uses RH_TASK_START_CHECK to check, if the calling user is allowed to start the Workflow. In it RH_TASKS_TO_START is used to read the WF and Task IDs which the user is allowed to call. But it uses a buffer and if you just did some changes to the Workflow Classification, i.e. setting it to General Task,
it can be that this check will continue to fail as it is reading old data from the buffer.
You will receive an error message from Message Class WZ: You are not one of the possible agents of task ‘&1’
I had this problem sometimes when transporting objects to the next system, but until now I could not figure out when it happens and when not.
Luckily the solution is pretty simple, just call T-Code SWU_OBUF and do a buffer refresh/synchronization. Rob Dielemans has explained the cause very well here.
The Attribute ACTION_COMMENTS is of type SWC_VALUE, which is a char with length 255.
If the entered text has less than < 255 characters, there is just one element named ‘ACTION_COMMENTS’ in the workitem container.
If the user entered a text with more than 255 characters, the text is split and there are more elements named with ‘ACTION_COMMENTS_1’, ‘ACTION_COMMENTS_2’ etc. (Note 3017539)
There is the function module SAP_WAPI_READ_CONTAINER to read Workitems. As input for the function module, you need the workitemId from the decision step. You will then find the ACTION_COMMENTS in the simple_container table.
DATA: return_code TYPE sy-subrc,
simple_container TYPE TABLE OF swr_cont,
message_lines TYPE TABLE OF swr_messag,
message_struct TYPE TABLE OF swr_mstruc,
subcontainer_bor_objects TYPE TABLE OF swr_cont,
subcontainer_all_objects TYPE TABLE OF swr_cont,
object_content TYPE TABLE OF solisti1.
" Read the work item container from the work item ID
CALL FUNCTION 'SAP_WAPI_READ_CONTAINER'
EXPORTING
workitem_id = wiid
IMPORTING
return_code = return_code
TABLES
simple_container = simple_container
message_lines = message_lines
message_struct = message_struct
subcontainer_bor_objects = subcontainer_bor_objects
subcontainer_all_objects = subcontainer_all_objects.
TRY.
DATA(text) = simple_container[ element = 'ACTION_COMMENTS' ]-value.
CATCH cx_sy_itab_line_not_found.
" Check for ACTION_COMMENTS_1 etc.
" or follow the approach below
ENDTRY.
The comment is also added as attachment to the workitem. Just check the table subcontainer_all_objects, which is also returned by the previous function module, for attribute _ATTACH_COMMENT_OBJECTS (or _ATTACH_OBJECT or DECISION_NOTE). With function module SO_DOCUMENT_READ_API1 you can then get the actual comment.
" Read the _ATTACH_COMMENT_OBJECTS element
" There can be more than one comment, just take the last one
LOOP AT subcontainer_all_objects INTO DATA(comment_object) WHERE element = '_ATTACH_COMMENT_OBJECTS'.
ENDLOOP.
CHECK comment_object-value IS NOT INITIAL.
" Read the SOFM Document
CALL FUNCTION 'SO_DOCUMENT_READ_API1'
EXPORTING
document_id = CONV so_entryid( comment_object-value )
TABLES
object_content = object_content
EXCEPTIONS
OTHERS = 1.
LOOP AT object_content INTO DATA(lv_soli).
CONCATENATE text lv_soli-line INTO text.
ENDLOOP.
As you get a table as a result, it’s properly easier to read long comments this way.
If you forward a workitem manually from the MyInbox or via function module SAP_WAPI_FORWARD_WORKITEM, the function module SWW_WI_FORWARD will be called. There the SAP implemented a BAdI call of the BAdI WF_WI_FORWARD.
The BAdI provides a method to add additional checks, when forwarding a workitem. I will use it, to send notification mails.
Create your own BAdI implementation and add a task id as filter. In my case it’s the task TS21500003 of the leave request approval workflow. Now your implementation will only be called, if this specific workitem is forwarded. Now we implement the method CHECK_BEFORE_FORWARD. The method has enough parameters to get all necessary information to enrich the mail text.
First the workitem container is read out of the context. There we get the _WI_OBJECT_ID element, which contains the request object reference. With this information we are able to get the current request object out of the workflow. I pass this into antother class where I already have a mail sending implementation.
METHOD if_ex_wf_wi_workitem~check_before_forward.
*---------------------------------------------------------------------------------*
* This BAdI implementation is used to send an info mail when a workitem is forwarded.
*---------------------------------------------------------------------------------*
TRY.
"Get workitem container and requestId
DATA(container) = im_workitem_context->get_wi_container( ).
container->get_value_ref( EXPORTING name = |_WI_OBJECT_ID|
IMPORTING value_ref = DATA(lr_req_id) ).
ASSIGN lr_req_id->* TO FIELD-SYMBOL(<lpor>).
"Get current request object
DATA(lo_req) = NEW cl_pt_req_wf_attribs( )->bi_persistent~find_by_lpor( lpor = <lpor> ).
"Send an info mail to each new agent (should be only one)
LOOP AT im_table_new_agents INTO DATA(new_agent).
zcl_hcm_leave_request_assist=>send_mail( io_req = CAST cl_pt_req_wf_attribs( lo_req )
iv_tdname = mc_mailtext
iv_pernr = cl_hcmfab_employee_api=>get_instance( )->get_employeenumber_from_user( iv_user = new_agent-objid ) ).
ENDLOOP.
CATCH cx_hcmfab_common.
CATCH cx_swf_cnt_elem_not_found.
CATCH cx_swf_cnt_container.
"In error case, do nothing. The workitem should still be forwarded.
RETURN.
ENDTRY.
"Write Info to WF Log
MESSAGE s001(00) WITH |Forward: Mail { mc_mailtext }| INTO DATA(lv_message).
im_workitem_context->set_message_to_log( im_function = CONV #( lv_message ) "max char30
im_message = VALUE #( msgid = sy-msgid
msgty = sy-msgty
msgno = sy-msgno
msgv1 = sy-msgv1 ) ).
COMMIT WORK.
ENDMETHOD.
At the end I’m writing a little notification in the workflow log. The workitem context provides the method set_message_to_log for this. The log will look like this.
But to add a message in general, only the workitem context is needed. If you got the context, just use method set_message_to_log.
* context type: im_workitem_context TYPE REF TO if_wapi_workitem_context
" text in im_function will be displayed in workitem log (max char30),
" message in im_message will give further information when clicking on the traffic light (max char 70).
im_workitem_context->set_message_to_log( im_function = |Forward: Mail { var }|
im_message = VALUE #( msgid = '00'
msgty = 'S'
msgno = '000'
msgv1 = |var 1|
msgv2 = |var 2| ) ).
* In some cases "COMMIT WORK" is needed.
If you want to copy the standard workflow of the leave request approval process (WS12300111) and are adding another approval step (or you just want to add an escalation where you set the approve workitem to obsolete and create a new approval step for the next approver) you have to implement the following BAdI. In detail you only have to add the new step ID in the filter, else the approver will not see any approval or reject buttons in his inbox. See details here.
BAdI: /IWWRK/BADI_WF_BEFORE_UPD_IB
As second step you have to add the Workflow in the customizing. You’ll find further information here.
During the leave request approval process the standard workflow WS12300111 does not include any deadlines. To archieve this you can simply copy the workflow and add a deadline at the approval task (Step 38). There are two possible ways to forward the approval step to the next processor: 1. When deadline is reached, forward the current workitem to the next approver. 2. When deadlines is reached, set current workitem to obsolete and create a new workitem and assign it to the next approver. Then you also have to add the new step ID to this BAdI Filter.
I selected the first option. In my demo the escalation is triggered after just three minutes.
The deadline generates a new exit on the approval step.
In this new exit you are able to call your own logic on finding the new approver, forward the workitem and inform him via mail if necessary.
To forward the request to the next approver, you have to forward the workitem and also set this new approver as next processor in the current leave request. So first identify the next approver. I used RH_GET_LEADER for this.
*--------------------------------------------------*
* Get next approver
*--------------------------------------------------*
CALL FUNCTION 'RH_GET_LEADER'
EXPORTING
plvar = '01'
keydate = sy-datum
otype = is_approver-otype
objid = CONV realo( is_approver-objid )
IMPORTING
leader_type = lv_leader_type
leader_id = lv_leader_id
EXCEPTIONS
no_leader_found = 1
no_leading_position_found = 2
OTHERS = 3.
CHECK lv_leader_type EQ 'P' AND lv_leader_id IS NOT INITIAL.
CALL FUNCTION 'HR_GET_USER_FROM_EMPLOYEE'
EXPORTING
pernr = CONV pernr_d( lv_leader_id )
iv_with_authority = abap_false
IMPORTING
user = lv_userid_approver.
ev_approver-otype = |US|.
ev_approver-objid = lv_userid_approver.
Then set this new approver as next processor in your request. While doing this, it’s recommended to enqueue and dequeue the request. To get the current request object use class cl_pt_req_badi.
*--------------------------------------------------*
* Set new approver in request
*--------------------------------------------------*
" Enqueue the request
CALL FUNCTION 'ENQUEUE_EPTREQ'
EXPORTING
mode_ptreq_header = 'S'
mandt = sy-mandt
request_id = io_req->req_id
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
" Get the request object instance
CALL METHOD cl_pt_req_badi=>get_request
EXPORTING
im_req_id = io_req->req_id
IMPORTING
ex_request = DATA(lcl_request).
CALL METHOD lcl_request->set_next_processor
EXPORTING
im_actor_type = 'P'
im_plvar = '01'
im_otype = 'P'
im_objid = CONV #( lv_leader_id ). " PERNR of Next Approver
IF sy-subrc = 0.
COMMIT WORK AND WAIT.
ENDIF.
" Dequeue the request
CALL FUNCTION 'DEQUEUE_EPTREQ'
EXPORTING
mode_ptreq_header = 'S'
request_id = io_req->req_id.
Finally forward the workitem to the new approver. There of course you need the right workitem ID of the approving step.
*--------------------------------------------------*
* Forward workitem to next approver
*--------------------------------------------------*
CALL FUNCTION 'SAP_WAPI_FORWARD_WORKITEM'
EXPORTING
workitem_id = iv_wi_id "woritem id of approving step
user_id = lv_userid_approver
language = sy-langu
do_commit = 'X'
IMPORTING
return_code = lv_subrc.
IF lv_subrc <> 0.
ENDIF.
Recently I got the demand to enhance the leave request approvel workflow with some mail steps. These mails should also include the attachments, which can be uploaded in the “Leave Request” Fiori App.
To achive this, I used the exit functionality in the mail step.
There you have to provide a class name. The class has to implement the following interface:
IF_SWF_IFS_WORKITEM_EXIT
You will get a method you can implement.
This Method has two parameters, the “Event Name” and the “Workitem Context”.
With the event, you are able to control, in which situations your exit should be fired. In my case, I just need my code run when the workitem is created (event “CREATED”). You’ll find all usable events in domain SWW_EVTTYP.
To get and pass back the attachment to the mail step, a few things need to be done. First, get the curent workitem container and check, if there is already any attachment. If not, read the request id from the leave request. With this request id, you are able to find the attachment you need. I’ve already written a method for that, you’ll find below. Then convert the xstring to a solix table, create the attachment document with function module “SO_DOCUMENT_INSERT_API1” and “SWO_CREATE” and finally pass it to the container.
METHOD if_swf_ifs_workitem_exit~event_raised.
DATA: lv_container TYPE REF TO if_swf_ifs_parameter_container,
lv_attach TYPE TABLE OF obj_record,
lv_folder_id TYPE soodk,
wa_document_info TYPE sofolenti1,
lv_data TYPE sodocchgi1,
lv_objtype TYPE swo_objtyp,
lv_objkey TYPE swo_typeid,
lv_return TYPE swotreturn,
lv_sofm TYPE swo_objhnd,
lv_objject TYPE obj_record,
tb_obj TYPE TABLE OF obj_record,
it_solix_tab1 TYPE solix_tab,
req_id TYPE tim_req_id,
lv_doc_type TYPE so_obj_tp.
CHECK im_event_name = 'CREATED'.
* Fetch Container
lv_container = im_workitem_context->get_wi_container( ).
* Read attachment to confirm that there is no duplication
TRY.
lv_container->get(
EXPORTING
name = '_ATTACH_OBJECTS'
IMPORTING
value = lv_attach ).
CATCH: cx_swf_cnt_elem_not_found,
cx_swf_cnt_elem_type_conflict,
cx_swf_cnt_unit_type_conflict,
cx_swf_cnt_container.
ENDTRY.
CHECK lv_attach IS INITIAL.
CALL FUNCTION 'SO_FOLDER_ROOT_ID_GET'
EXPORTING
owner = sy-uname
region = 'B'
IMPORTING
folder_id = lv_folder_id.
* Get Request ID
lv_container->get(
EXPORTING
name = 'RequestId'
IMPORTING
value = req_id ).
* Get XSTRING of attachment
zcl_xxx=>get_req_attachment(
EXPORTING
iv_req_id = req_id
IMPORTING
ev_xstring = DATA(xstring)
es_attachment = DATA(ls_attachment) ).
CHECK xstring IS NOT INITIAL.
* Get document type
SELECT SINGLE doc_type FROM toadd INTO @lv_doc_type
WHERE mimetype EQ @ls_attachment-file_type.
IF sy-subrc NE 0.
lv_doc_type = 'PDF'. "If doc_type not found, at least try with pdf.
ENDIF.
* Create and set document
it_solix_tab1 = cl_document_bcs=>xstring_to_solix( xstring ).
* Creating First attachment
lv_data-obj_name = ls_attachment-file_name.
lv_data-obj_descr = ls_attachment-file_name.
lv_data-obj_langu = sy-langu.
lv_data-sensitivty = 'P'.
* lv_data-doc_size = ls_attachment-file_size.
CALL FUNCTION 'SO_DOCUMENT_INSERT_API1'
EXPORTING
folder_id = lv_folder_id
document_data = lv_data
document_type = lv_doc_type
IMPORTING
document_info = wa_document_info
TABLES
contents_hex = it_solix_tab1
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.
* Populate object type and object key for create an instance
lv_objtype = 'SOFM'.
lv_objkey = wa_document_info-doc_id.
CALL FUNCTION 'SWO_CREATE'
EXPORTING
objtype = lv_objtype
objkey = lv_objkey
IMPORTING
object = lv_sofm
return = lv_return
EXCEPTIONS
no_remote_objects = 1
OTHERS = 2.
* Prepare for attaching the object to container
lv_objject-header = 'OBJH'.
lv_objject-type = 'SWO'.
lv_objject-handle = lv_sofm.
APPEND lv_objject TO tb_obj.
*—can be used for other workitems
lv_container->set(
EXPORTING
name = '_ATTACH_OBJECTS'
value = tb_obj[] ).
*–this will add the attachment to email
lv_container->set(
EXPORTING
name = 'ATTACHMENTS'
value = tb_obj[] ).
* Commit the changes
im_workitem_context->do_commit_work( ).
ENDMETHOD.
The following function modules are needed to read the leave request attachment: