Skip to end of metadata
Go to start of metadata

Symptom

This Wiki Page will show you how to Customize the ESS Leave Approval process using SAP Business Workflow.

Solution

We will configure the Leave Approval process with Standard Workflow Template  WS12300111, which provides a single level of approval.
Additionally, we will also discuss the possibility of scaling this to a Workflow with multiple levels of approval.

Activating the Leave Workflow

As you would have already read on page Transactions and Reports in PT on ESS, the entire Leave Framework can be simulated via Transaction code PTARQ.
These steps should be followed in order to activate the Approval Workflow for Leaves.

  1. First, maintain Visualization Parameters for tasks of  WS12300111 in SWFVISU .
    The two Tasks of the Workflow, TS12300097 and TS12300116 are to be visualized to Java Webdynpro Applications.
    If there is a Task TS12300104, this Task should be deleted according to SAP note 779075



 

I found this Wiki page on SWFVISU , which might be helpful if you require to customize and visualize your own tasks. 

  1. Next, Activate Approval via Workflow for the leave types.
    This can be done via PTARQ > Customizing > Employee Self-Service > Service-Specific Settings > Working Time > Leave Request > Processing Processes > Specify Processing Processes for Types of Leave > Define Absences/Processing Processes
    ( You can also reach the customizing setting from SPRO via SAP Customizing Implementation Guide > Personnel Management > Employee Self-Service > Service-Specific Settings > Working Time > Leave Request > Processing Processes > Specify Processing Processes for Types of Leave > Define Absences/Processing Processes )
    These are the same tables like in the old ITS based version - and can also be reached over the customizing for the old ITS based version. However, the above path also provides the correct documentation.

Select or Add a Leave Type, then click then edit button/or double click it to go to the next screen which displays more parameters. 

Check the box that says " Process Request using Workflow".
Once you check this option, 3 new parameters appear :
                  a. WF ID of New Request
                  b. WF ID of Cancellation Request
                  c. WF ID of Change Request
You should fill out the Workflow Template number "12300111" (or any custom workflow that you'd developed for the purpose) against each of these, ofcourse based on scenarios you would want to handle with the Workflow.
This allows us to define which actions use which workflow for a given Leave Type. If you keep one of the fields mentioned above empty, the standard workflow is taken - please fill all the fields all the time.
In case you do not want an approval for a special leave type, you may uncheck the checkbox 'Request Have To Be Approved'.

  1. Next, You should  run/schedule the report RPTARQPOST to post the Approved Leaves (For Approved Leaves, PTREQ_HEADER-STATUS = APPROVED).
    On successful execution, RPTARQPOST updates the Leave Infotypes with the approved Leave data.

Good to know stuff:

1. BADI  PT_GEN_REQ can give you more control on the processing process like Filtering Agents, Starting the Workflow, etc.
2. When a Leave Request is created, an entry is created in the PTREQ_HEADER table. REQUEST_ID is the unique identifier for a Leave Request.
3. Actors (All agents involved with the request, like Owner, Initiaor, Next Processor, etc.) would have an entry in the Table PTREQ_ACTOR.
     If it does not exist, it will be created during the Leave Approval process (Refer Implementation of CA_PT_REQ_ACTOR->CA_PT_REQ_ACTOR) .
4. PTREQ_ITEMS and PTREQ_ATTABSDATA are other tables that contain data about the Leave Request. PTREQ_ITEMS can tell us if the Leave
    Request was New or a request for Change or Cancel. The Time data is recorded in PTREQ_ATTABSDATA

Scaling the Workflow for Multiple Approval Levels

An object 'Req' of the CL_PT_REQ_WF_ATTRIBS is passed to the Workflow container when it is being triggered.
Note that 'Req' references the Persistent Object of the Leave Request. Any change to the Persistent Instance of the Request means that the change will be visible in 'Req'.
You can always reference the Persistent Instance of the Request by calling the method CL_PT_REQ_WF_ATTRIBS=>BI_PERSISTENT~FIND_BY_LPOR  (Note that : LPOR-INSTID = REQUEST_ID).

To scale the Approval process to multiple levels, you should copy the existing template WS12300111 and modify the new template introducing steps for further levels of approval.
You would have noticed that once approved, the Request status would go from SENT to APPROVED. In the Workflow this would affect the value of REQ.STATUS.

We need to introduce a couple of steps to facilitate the next level Approval process via ESS.

Step 1. Reseting the Status to SENT
In WS12300111 this was the Status change that  we checked for at Step 000158  ('Request Approved?'). Now, for the new template, if the outcome of this step is 'Approved',
we should re-set the status to SENT. Sample code for state transition from APPROVED to SENT is given below:

DATA: lcl_request type ref to if_pt_req_request.

*-- Enqueue the request
call function 'ENQUEUE_EPTREQ'
    exporting
      mode_ptreq_header = 'S'
      mandt             = sy-mandt
      request_id        = Request_id
    exceptions
      foreign_lock      = 1
      system_failure    = 2
      others            = 3.
  if sy-subrc <> 0.
    message id sy-msgid type sy-msgty number sy-msgno
            with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  endif.
 
*-- Get the Persistent Leave Object

  call method cl_pt_req_badi=>get_request
    exporting
      im_req_id  = Request_id
    importing
      ex_request = lcl_request.

 
*-- Initiate the State Transition to SENT
  call method cl_pt_req_badi=>initiate_state_transition
    exporting
      im_request    = lcl_request
      im_event      = cl_pt_req_const=>c_reqtrans_send
    importing
      ex_new_status = new_status.


  commit work and wait.
 
*-- Dequeue

  call function 'DEQUEUE_EPTREQ'
    exporting
      mode_ptreq_header = 'S'
      request_id        = Request_id.

Step 2: Setting the Next Processor

After the Agent Determination for the Next Level of Approval, it is essential that you update the Request with this information.
The CL_PT_REQ_WF_ATTRIBS class has an Attribute called N_PROCESSOR which needs to be set so that this is visible in ESS.
The sample code to achieve this is given below :

 *--Enqueue the request
callfunction'ENQUEUE_EPTREQ'
exporting
mode_ptreq_header='S'
mandt=sy-mandt
request_id=Request_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 =Request_ID
 importing
 ex_request = lcl_request.
 call method lcl_request->set_next_processor
 exporting
 im_actor_type = 'P'
 im_plvar = '01'
 im_otype = 'P'
 im_objid = NextApprover_PERNR. " PERNR of Next Approver
 if sy-subrc = 0.
 commit work and wait.
 endif.
 
*-- Dequeue the request
callfunction'DEQUEUE_EPTREQ'
exporting
mode_ptreq_header='S'
request_id=Request_id.

 

The new Workflow Template can now have any number of Approvals with the above Step 1 and Step 2 being inserted after each approval (Task TS12300097) and only in case if there is another Level of Approval .

Do not forget to set this New Approver as the Agent for TS12300097 as well.

See Also

If you are new to PT on ESS, you should consider reading these pages on this Wiki:

Got any problems with this solution? You can read related articles in the Forums on this:

Author: Vimal Vidyadharan
Submitted: 27-10-2011

26 Comments

  1. Unknown User (wxtag87)

    I tried to adapt your solution to my current project but i found some problem when I tried to reset status in table PTREQ_HEADER using your code.

    I put your code to one background task after approval step to reset status to be "SENT" but sometime status don't change.

    If i change this task to foreground mode, this problem never occurred.

    I'm not sure, do you face this problem or not ?

    1. I believe you are facing problem not in resetting the status, but with the status not correctly reflecting in the workflow. 

      All you need to do is, if the reset is successful, update the Status variable in Workflow container with the latest status.

      Do not forge the commit work and wait when updating the DB as in case there is a DB update delay you might not get the right status in the Workflow.

  2. Unknown User (t039e9l)

    Good write up!

  3. Khongsiln Werawat says:

     I tried to adapt your solution to my current project but i found some problem wh...
    I tried to adapt your solution to my current project but i found some problem when I tried to reset status in table PTREQ_HEADER using your code.

    I put your code to one background task after approval step to reset status to be "SENT" but sometime status don't change.

    If i change this task to foreground mode, this problem never occurred.

    I'm not sure, do you face this problem or not ?



    Sorry about the late response. But did you resolve this? Just in case you did not, please place a COMMIT WORK AND WAIT before you complete the Work Item. 

  4. Hi Vimal,

    Where to add all the code that you have mentioned. Enhancing class CL_PT_REQ_WF_ATTRIBS with new method couldn't be an option as those method wouldn't show up during method assignment in Task maintenance. And if we copy this class and create a new method then I am unable to pass on the workflow container element REQ to this custom class instance (for obvious reason of compatibility).

    How we do this then?

    Best Regards,

    Bhaskar

    1. Hello Bhaskar,

      There are multiple approaches to this - including redefining the methods you would want to enhance, or adding post or pre methods .
      However, IMHO, a less disruptive one/an approach with little modification, would be to include a Business Object for the custom process tasks, such as resetting the approval, determination of approver, etc.This helps as all you need to do with standard is to configure the new WF in PTARQ and then handle all the custom scenario via your custom WF and BO.

      The points I enhanced were where ESS Leave connects to the Workflow. For everything else, i.e. Workflow steps, I had the new Business object.

      A few Scenarios that needed enhancing CL_PT_REQ* / RFCs were for instance -

      1. The first approver gets determined before the WF starts, we wanted to change this(can also be handled in WF later - but still).
      2. We wanted to pass additional data to the ESS Leave Java class on approval.
      3. We wanted instant leave posting on final approval and decided not to schedule the standard job.

      CL_PT_REQ_WF_ATTRIBS happens to be a persistent class for ESS Leave. This is also the class that is referenced in your  ESS Leave java component. If you decide to copy and customize, you might have to handle a lot of changes at integration points yourself.

      Hope this helps.

      Do feel free to discuss your issues with this solution.
      We have this ESS WF live and running fine at some customer sites already. I'll be interested to know of any scenario this solution can not handle, we could always improvise.

      Thanks,

      Vimal

      1. Former Member

        Hi Vimal ,

        how can we change approver and determine another approver based on some conditions we have ??

  5. Hi Vimal,

    Where could we add the code that you give following step 1?

    "Step 1. Reseting the Status to SENT In WS12300111 this was the Status change that  we checked for at Step 000158  ('Request Approved?'). Now, for the new template, if the outcome of this step is 'Approved', we should re-set the status to SENT. Sample code for state transition from APPROVED to SENT is given below:"

     

    Thanks is advance

    1. Hi Nurullah Rüstem - This code is in the Background workitem, which is the next step in workflow after the Approval - in case you want to handle multiple levels.

      Apologies for the late reply.

  6. hi all

    good blog.

    one question here.i have two approvers and the requirement is that initiator applies for request and first approver approves.

    here you are changing the status to sent.

    now the initiator cancells and the requirement is that as the first approver should receive the cancellation workitem. however as the status is sent the workflow doesn't ge initiated and the record is deleted without workflow.

    please advise on above.

    thank you.

    1. Hi Barin, 

      If the initiator cancels the Leave -

      1. while it is still pending approval - Workflow gets deleted by system.
      2. if already approved and posted in system, a new Cancellation Workflow is created - if you have configured one.

       

      Hope this helps.

      Apologies for the delayed response.

       

  7. Anonymous

    hi @BARIN DESAI, 

    when employee applies leave he can see the first level approver name in the Next processor .....and when first approver approves , it will go to second approver , and when employee tries to cancel teh leave he cannot delete the leave becoz he cannot see the approver name , so it will not cancel the leave if first level approves , if first level is not approved he can cancel the leave ...try once this ....if both level approves then employee can cancel the leave and it will go to first level approver the cancellation request from workflow  .....if both level approves it will get delted from 2001

  8. Nurullah Rüstem - This code is in the Background workitem, which is the next step in workflow after the Approval - in case you want to handle multiple levels.

    Apologies for the late reply.

  9. Former Member

    I encountered a problem with the setting of the next approver with the coding above.

    While in the method the request was updated correct, the commit didn't update the request in the database.

    I was able to solve the problem like following:

    Instead of 

    call method lcl_request->set_next_processor
     exporting
     im_actor_type = 'P'
     im_plvar = '01'
     im_otype = 'P'
     im_objid = NextApprover_PERNR. " PERNR of Next Approver
     if sy-subrc = 0.
     commit work and wait.
     endif.
    I used:

     CALL METHOD lcl_request->if_pt_req_request~set_next_processor
        EXPORTING
          im_actor_type 'P'
          im_plvar      '01'
          im_otype      'P'
          im_objid      iv_pernr. " PERNR of Next Approver
      IF sy-subrc 0.

    CALL METHOD lcl_request->if_pt_req_request~workarea_version->get_all_attribs
        IMPORTING
          ex_all_attribs ls_attribs.

        CALL METHOD lcl_request->clone_to_old.

         commit_work and wait.

      ENDIF.

    Maybe someone has the same trouble like me. 

    1. Thank you for pointing this out Jens, Yes I too have seen the problem in one system - could be something to do with patch level - they behave slightly differently.

       

  10. Hi Jens,

    I got the same issue that Next Processor is not getting updated in the Database.

    Can you please give me the complete code how you solved the issue.

    How to get the LCL_request.

     

    Thanks

    1. Former Member

      Hi,

      sure I can post the complete coding:

      I got REQ_ID Type TIM_REQ_ID and PERNR Type PERNR_D as Importing Parameter in my class.

      Method coding is as followed:

      DATAlr_request       TYPE REF TO cl_pt_req_request,
      lv_oid           TYPE os_guid,
      ls_attribs       type PTREQ_ATTRIBS_STRUC_FLAT.

        lv_oid req_id.

      *--Enqueue the request
        CALL FUNCTION 'ENQUEUE_EPTREQ'
          EXPORTING
            mode_ptreq_header 'S'
            mandt             sy-mandt
            request_id        req_id
          EXCEPTIONS
            foreign_lock      1
            system_failure    2
            OTHERS            3.

      *-- Get the request object instance
        lr_request ?= ca_pt_req_header=>agent->if_os_ca_persistency~get_persistent_by_oidlv_oid ).

      CALL METHOD lr_request->if_pt_req_request~set_next_processor
          EXPORTING
            im_actor_type 'P'
            im_plvar      '01'
            im_otype      'P'
            im_objid      pernr.
        IF sy-subrc 0.
          CALL METHOD lr_request->if_pt_req_request~workarea_version->get_all_attribs
          IMPORTING
            ex_all_attribs ls_attribs.

          CALL METHOD lr_request->clone_to_old.

        ENDIF.

      *-- Dequeue the request
        CALL FUNCTION 'DEQUEUE_EPTREQ'
          EXPORTING
            mode_ptreq_header 'S'
            request_id        req_id.

       

       

      1. Hi Jens,

         

        Thanks a lot . The issue got resolved.

         

  11. Former Member

    Hi , 

    Can you guide me how to change workflow to fit for multilevel Approval?

    suggest me workflow steps details

    Thanks.

     

  12. Former Member

     

    Hi Experts,

    I have implemented BADI : PT_GEN_REQ.
    I used method START_WF for Workflow part.

    I am getting below error message at CALL METHOD message_handler->add_message.

    Error Message:

    Access using a 'ZERO' object reference is not possible. (termination: RABAX_STATE)


    Can anyone please let me know if any notes need to be implemented for this?

    Code in Method: START_WF

    method IF_EX_PT_GEN_REQ~START_WF.

    DATA: im_agent_container TYPE REF TO if_swf_ifs_parameter_container,
    lc_workitem_container TYPE REF TO if_swf_cnt_container,
    agents TYPE TABLE OF swhactor INITIAL SIZE 0,
    l_creator TYPE swwwihead-wi_creator,
    workitem_id TYPE sww_wiid,
    lc_workflow TYPE REF TO cl_wfd_adhoc_workflow_start,
    lc_step TYPE REF TO if_wfd_adhoc_step,
    l_tabix TYPE sy-tabix,
    l_agent TYPE swdaagnt,
    l_n_processor TYPE ptreq_actor_struc_flat,
    lc_agent_container TYPE REF TO if_swf_cnt_container,
    lcl_attribs TYPE ptreq_request_struc_flat,
    lcl_wf_attribs TYPE REF TO cl_pt_req_wf_attribs,
    lcl_lpor TYPE sibflpor,
    lcl_bi_persistent TYPE REF TO bi_persistent,
    lcl_workarea TYPE REF TO cl_pt_req_header,
    lt_workflow_container TYPE swrtcont,
    lt_wf_container_obj TYPE swrtcont,
    lt_wf_container_por TYPE swrtcont,
    lt_errors TYPE swft100tab.
    DATA: lt_wf_error_tab TYPE TABLE OF swr_mstruc,
    lt_wf_error_line TYPE swr_mstruc.

    DATA: exit_gen TYPE REF TO pt_gen_req.
    DATA: re_absent TYPE boole_d.
    DATA: user TYPE sy-uname.
    DATA: ex_subst_str TYPE struc_t.
    DATA: subst_settings TYPE padd2.
    DATA: wa_ex_subst TYPE LINE OF struc_t.
    DATA: application TYPE REF TO cl_pt_req_application.

    DATA: ex_subst_obj TYPE objec_t.
    DATA: wa_ex_subst_obj TYPE LINE OF objec_t.
    DATA: l_index type sy-tabix.


    * RETURN_CODE logic:
    * = 1 agent missing error
    * = 2 data container error
    * = 3 WF start failed
    * = 4 some other error

    application = cl_pt_req_application=>get_instance( ).

    * call exit
    exit_gen = application->functional_exit_gen.

    *create workflow-start object
    CREATE OBJECT lc_workflow
    EXPORTING
    task = im_main_task
    EXCEPTIONS
    task_without_adhoc_capability = 1
    OTHERS = 2
    .
    IF sy-subrc <> 0.
    return_code = 4.
    EXIT.
    ENDIF.


    * get all step objects and display the assigned agents
    DO.
    ADD 1 TO l_tabix.
    * get ad-hoc-workflow-step
    CALL METHOD lc_workflow->step_by_index_get
    EXPORTING
    index = l_tabix
    RECEIVING
    step = lc_step
    EXCEPTIONS
    invalid_index = 1
    OTHERS = 2.
    IF sy-subrc <> 0.
    EXIT.
    ENDIF.

    * set user for ad-hoc-agent --> NEXT PROCESSOR
    CALL METHOD cl_pt_gen_badi=>get_next_processor
    EXPORTING
    im_request = im_request
    IMPORTING
    ex_actor_attribs = l_n_processor.

    CALL BADI exit_gen->check_if_actor_absent
    EXPORTING
    im_pernr = l_n_processor-pernr
    RECEIVING
    result = re_absent.

    * If the next processor is absent for xx days, then determine substitute for him/her.
    IF re_absent = 'X'.
    user = l_n_processor-user.
    CALL BADI exit_gen->get_actor_substitutes
    EXPORTING
    im_user = user
    IMPORTING
    ex_subst_str = ex_subst_str
    ex_subst_obj = ex_subst_obj.

    CHECK NOT ex_subst_str IS INITIAL.
    LOOP AT ex_subst_str INTO wa_ex_subst.
    subst_settings = wa_ex_subst-vadata.
    CHECK NOT subst_settings-active IS INITIAL. "Consider only active substitute

    IF wa_ex_subst-otype = 'US'.
    CLEAR l_agent-agent.
    READ TABLE ex_subst_obj INTO wa_ex_subst_obj INDEX l_index.
    CONCATENATE wa_ex_subst-otype wa_ex_subst_obj-realo INTO l_agent-agent.

    CONCATENATE wa_ex_subst-otype wa_ex_subst-objid INTO l_agent-agent.
    CALL METHOD lc_step->agent_append
    EXPORTING
    agent = l_agent
    EXCEPTIONS
    already_exists = 1
    locked = 2
    OTHERS = 3.
    IF sy-subrc <> 0.
    return_code = 1.
    EXIT.
    ENDIF.
    ENDIF.
    ENDLOOP.
    ELSE.
    CONCATENATE 'US' l_n_processor-user INTO l_agent-agent.
    * append agent to processor-list
    CALL METHOD lc_step->agent_append
    EXPORTING
    agent = l_agent
    EXCEPTIONS
    already_exists = 1
    locked = 2
    OTHERS = 3.
    IF sy-subrc <> 0.
    return_code = 1.
    EXIT.
    ENDIF.
    ENDIF.
    ENDDO.


    * get agents (container format)
    CALL METHOD lc_workflow->agents_as_container_get
    IMPORTING
    container = im_agent_container
    EXCEPTIONS
    agents_missing = 1
    OTHERS = 2.
    IF sy-subrc <> 0.
    return_code = 1.
    EXIT.
    ENDIF.

    TRY.
    CALL METHOD cl_swf_cnt_factory=>create_task_container
    EXPORTING
    im_task_id = im_main_task
    IMPORTING
    ex_task_container = lc_workitem_container.
    CATCH cx_swf_utl_obj_create_failed
    cx_swf_utl_no_plan_variant
    cx_swf_utl_task_not_found
    cx_swf_utl_obj_invalid_ref .
    return_code = 2.
    EXIT.
    ENDTRY.

    * merge agents to container
    IF im_agent_container IS BOUND.
    lc_agent_container ?= im_agent_container.
    TRY.
    CALL METHOD lc_workitem_container->merge
    EXPORTING
    other_container = lc_agent_container.
    CATCH cx_swf_cnt_container.
    return_code = 2.
    EXIT.
    ENDTRY.
    ENDIF.

    *---Set workflow attributes
    lcl_lpor-instid = im_request->if_pt_req_request~request_id.
    CALL METHOD cl_pt_req_wf_attribs=>bi_persistent~find_by_lpor
    EXPORTING
    lpor = lcl_lpor
    RECEIVING
    result = lcl_bi_persistent.
    lcl_wf_attribs ?= lcl_bi_persistent.
    TRY.
    CALL METHOD lc_workitem_container->element_set
    EXPORTING
    name = 'Req'
    value = lcl_wf_attribs.
    CATCH cx_swf_cnt_cont_access_denied
    cx_swf_cnt_elem_not_found
    cx_swf_cnt_elem_access_denied
    cx_swf_cnt_elem_type_conflict
    cx_swf_cnt_unit_type_conflict
    cx_swf_cnt_elem_def_invalid
    cx_swf_cnt_container .
    return_code = 2.
    EXIT.
    ENDTRY.

    *---Fill simple workflow container
    CALL METHOD lc_workitem_container->to_simple_container
    EXPORTING
    import_param = 'X'
    export_param = 'X'
    changing_param = 'X'
    returning_param = 'X'
    no_system_elements = space
    no_initial_elements = 'X'
    IMPORTING
    ex_container_values = lt_workflow_container
    ex_container_sibflporbs = lt_wf_container_por
    ex_t_messages = lt_errors.
    APPEND LINES OF lt_wf_container_por TO lt_workflow_container.

    *---Start workflow
    CALL FUNCTION 'SAP_WAPI_START_WORKFLOW'
    EXPORTING
    task = im_main_task
    do_commit = space
    start_asynchronous = 'X'
    IMPORTING
    return_code = return_code
    workitem_id = workitem_id
    TABLES
    input_container = lt_workflow_container
    message_struct = lt_wf_error_tab.

    IF return_code NE 0.
    LOOP AT lt_wf_error_tab INTO lt_wf_error_line.
    *---Add message to message handler
    CALL METHOD message_handler->add_message
    EXPORTING
    im_type = lt_wf_error_line-msgty
    im_cl = lt_wf_error_line-msgid
    im_number = lt_wf_error_line-msgno
    im_par1 = lt_wf_error_line-msgv1
    im_par2 = lt_wf_error_line-msgv2
    im_par3 = lt_wf_error_line-msgv3
    im_par4 = lt_wf_error_line-msgv4
    im_classname = 'CL_DEF_IM_PT_GEN_REQ'
    im_methodname = 'IF_EX_PT_GEN_REQ~START_WF'.
    ENDLOOP.
    return_code = 3.
    ENDIF.

    CALL METHOD im_request->set_workitem_id
    EXPORTING
    im_workitem_id = workitem_id.

    endmethod.


    Thanks,
    KR.

    1. Former Member

      Hi KR,

       

      Excellent work thanks bro (smile)

  13. a good work Vimal and Jagadessh

  14. Former Member

    Dear All,

    Thanks for very useful blog..

    I am facing one issue. Once i change the status to sent there is duplication of Note value. how can avoid duplication of note field.

    Atul  

  15. Hi

    I have a scenario here. As I understand you are changing the status of the request to sent after first approver approves.

    Now in a two level request when the first approver has approved what will happen if employee decides to cancel the request? Will cancellation go to the first approver who has approved the leave request?

     

    Thanks

    barin