Tuesday, July 23, 2013

Dynamic Approver User List for AWE

If you ever have a workflow requirement that may require one to many levels of approval for a step, then you should consider using Dynamic Approver User List.   Dynamic Approver User List are User List that are defined as Application Packages and the Path for the Approval Process is setup as Dynamic.  The example below walks up the supervisor using Oracle's Connect sql and finds the next Approver who has a signing authority. 

Below is the Approval Process Setup:




Application Package Code


import EOAW_CORE:DEFN:UserListBase;

class ZZ_ApprUserList extends EOAW_CORE:DEFN:UserListBase
   method ZZ_ApprUserList(&rec_ As Record);
   method GetUsers(&aryPrevOpr_ As array of string, &thread_ As Record) Returns array of string;
end-class;

method ZZ_ApprUserList
   /+ &rec_ as Record +/
   %Super = create EOAW_CORE:DEFN:UserListBase(&rec_);
end-method;

method GetUsers
   /+ &aryPrevOpr_ as Array of String, +/
   /+ &thread_ as Record +/
   /+ Returns Array of String +/
   /+ Extends/implements EOAW_CORE:DEFN:UserListBase.GetUsers +/
  
   Local array of string &oprid_arr = CreateArrayRept("", 0);
   Local string &REQUESTOR, &ORIGIN;
   Local number &req_amt, &SIGN_AUTH, &PREV_SIGN_AUTH;
   Local string &oprid;
   SQLExec("SELECT  B.REQUESTOR_ID, B.ORIGIN FROM  PS_REQ_HDR B WHERE  B.REQ_ID = :1 AND B.BUSINESS_UNIT = :2", &thread_.REQ_ID.Value, &thread_.BUSINESS_UNIT.Value, &REQUESTOR, &ORIGIN);
   If &ORIGIN = "DIR" Then
      SQLExec("SELECT sum(A.MERCHANDISE_AMT) FROM PS_REQ_LINE A  WHERE  A.REQ_ID = :1 AND A.BUSINESS_UNIT = :2 ", &thread_.REQ_ID.Value, &thread_.BUSINESS_UNIT.Value, &req_amt);
     
      Local SQL &admin_sql = CreateSQL("select OPRID ,MERCHANDISE_AMT from PS_ZZ_WF_AUTH_VW D START WITH D.OPRID = :1 CONNECT BY PRIOR SUPERVISOR_ID = D.OPRID", &aryPrevOpr_ [1]);
      Local number &counter = 0;
      While &admin_sql.Fetch(&oprid, &SIGN_AUTH)
         &counter = &counter + 1;
         If &counter > 1 Then
            If &SIGN_AUTH > 0 Then
               Break;
            End-If;
         End-If;
      End-While;
     
      SQLExec("SELECT MERCHANDISE_AMT FROM PS_ZZ_WF_AUTH_VW WHERE OPRID = :1", &aryPrevOpr_ [1], &PREV_SIGN_AUTH);
     
      If &aryPrevOpr_ [1] = &REQUESTOR Then
         rem the previous approver was the requestor, so we have to have one approver regardless of signing authority
          so add this user to the approver array;
         &oprid_arr.Push(&oprid);
      Else
         If &req_amt > &PREV_SIGN_AUTH Then
            &oprid_arr.Push(&oprid);
         End-If;
      End-If;
   End-If;
   Return &oprid_arr;
end-method;




Things to know. 
  1. All User list extend the EOAW_CORE:DEFN:UserListBase  
  2. Needs to Return an Array of User Ids
  3. AWE will call this User List until the user list returns an empty array.

35 comments:

  1. Hello Kevin,

    That Peoplecode was of great help. I was wondering if you can also share the SQL for PS_ZZ_WF_AUTH_VW view and the details on how you setup the ZZ_REQ_AWE Approver User list.

    Thank you.

    ReplyDelete
  2. Sunil,

    I will publish the complete design of the zz_wf_auth_vw table tomorrow. However, I did not come up with the overall design. I just came in and fixed it, and made it work.

    Thanks!

    ReplyDelete
    Replies
    1. Kevin,

      I am litte stuck with my dynamic workflow code. Any update on the details I requested? Are you storing the amount approval authority in EOAW tables or is it a custom setup? Please let me know.
      Thanks.

      Delete
  3. Sunil,

    We us custom tables to track the dollar amount, I published the rest of the design here, http://www.pskcw.blogspot.com/2013/11/purchase-requisition-approvals-in.html.

    Hope this helps.

    Kevin

    ReplyDelete
  4. Hi Kevin,

    I am trying to implement Dynamic AWE based on position management. I undestand that i need to use App class: EOAW_CORE:ENGINE:DynamicPathInst. Can you please guide me where should i call method of this app class and how to pass parameters.

    Best,
    Saurabh

    ReplyDelete
  5. Saurabh,

    What are you trying to accomplish? Are you attemting to identify the next approver based on position management?

    ReplyDelete
  6. Hi Kevin,

    Glad you replied to my post :)
    Yup...i need to create dynamic awe based on position management which should trigger worklist till CEO irrespective of who started it in first place. My user list is returning all approvers so the workflow is going to everyone at the same time.

    Do I need to code user list in such a way that while submitting request it only gets submitter’s reports to and later during approval it gets approver’s reports to.

    Best,
    Saurabh

    ReplyDelete
  7. I apologize about the delay in my response. Sounds like this post is for you, based on what you are saying it sounds like you are returning all users in the array upon calling the user list. This is the mistake I made when I first tried to write my dynamic user list. What I learned is that AWE will call this user list until you return an empty array. So you need to populate the array with the user id that is the manager of the &aryPrevOpr_. Once it gets to the CEO the logic should look for the supervisor of the CEO and then return an empty array. This will effectively end the workflow.

    ReplyDelete
    Replies
    1. Hi Kevin,

      I want to implement dynamic path as we have 4 level of approvals it will flow based on performance rating
      but here i m stuck with userlist part even the method returns the oprids it auto approves and nothing get flow .
      Also is it compulsory to have approve authorization checked on the dynamic path.

      Delete
    2. I am not sure where "approve authorization" is an option, the only thing close to this is "check Authorization" in my system and I don't have that checked on my dynamic path.

      You can test your approval setup by creating a simple Userlist with hard coded values. In the example below replace TEST1 with the user you are using to submit your transaction and TEST2 and TEST3 with approvers. Make sure TEST2 and TEST3 are setup as workflow users prior to test.

      import EOAW_CORE:DEFN:UserListBase;

      class ZZ_ApprUserList extends EOAW_CORE:DEFN:UserListBase
      method ZZ_ApprUserList(&rec_ As Record);
      method GetUsers(&aryPrevOpr_ As array of string, &thread_ As Record) Returns array of string;
      end-class;

      method ZZ_ApprUserList
      /+ &rec_ as Record +/
      %Super = create EOAW_CORE:DEFN:UserListBase(&rec_);
      end-method;

      method GetUsers
      /+ &aryPrevOpr_ as Array of String, +/
      /+ &thread_ as Record +/
      /+ Returns Array of String +/
      /+ Extends/implements EOAW_CORE:DEFN:UserListBase.GetUsers +/

      Local array of string &oprid_arr = CreateArrayRept("", 0);

      EVALUATE &aryPrevOpr_ [1]
      WHEN = 'TEST1'
      &oprid_arr.Push('TEST2');
      BREAK;
      WHEN = 'TEST2'
      &oprid_arr.Push('TEST3');
      BREAK;
      END-EVALUATE;



      Return &oprid_arr;
      end-method;

      Delete
    3. Hi Kevin,

      As we have 3 levels of approver
      say Reviewing officer >> employee >> Admin

      Using static we need to setup 3 steps and at the time of submit it loads all the steps as per criteria.

      Using dynamic we need to setup only 1 step and at the time of submit it loads that step. If I approve that step it will flow to another approver.
      Here do it will show 2 steps or only one step as we have setup only one.

      Can you please let me know.

      Delete
    4. If you are using a dynamic user list, it will have just one step. However, if your approval chain is a simple as you have stated, I would just create an approval with 3 steps.

      Delete
  8. Hi Kevin,

    Let me start by thanking you for this post as this is the only place till now where I see a simple to understand example for Dynamic approval!! I tried the TEST1, TEST2, TEST3 example and results are promising.

    I have a question for you. Can we generate dynamic paths. When I say path I do not mean generate the steps dynamically inside the path but can paths be generated by programming.

    In our requirement for requisition AWE, we have approval for line level. The chain of approvers are decided on chartfield at distrib level. This means that for line with N distributions, we would need to have N paths created dynamically.

    To your knowledge, can the above be done? I have spoken to few functional folks who have seen something similar being accomplished, but have no idea how it was done. Would love to hear back your thoughts on this.

    Thanks,
    Ali

    ReplyDelete
    Replies
    1. To my knowledge there is no way to create a dynamic path. However, this is how I would handle that scenario. I would create and Approval process that would be one stage, with one path and create one step with a Dynamic userlist. This User List would be passed the xref record for the line and would need to loop through every line in at the Distrib. It would populate one array with multiple approvers. Basically, loop through the distrib for each line and determine who the approver is and add them to the array if they are not already there. Then it would return that array once, then you would have to account for AWE calling the userlist again and return and empty array( that is pretty easy). You would then need to define the Step Approver Requirements and seti it to "All Approvers Required".

      Delete
  9. Hi Kevin,

    I am working on a requirement where i need to place line level approvals for a workflow. I wanted to know if the following is possible using AWE - Place Approvals at Header level and at line level and the line level approval button to be placed at every line (i.e inside the grid). The end user must be provided option to approve at header level which will indicate that all the lines are approved or he should be able to approve/reject at line level

    ReplyDelete
    Replies
    1. This is how Timesheets work in PeopleSoft HR, so yes this is possible.

      Delete
    2. Hi Kevin, thanks for your response. C an you please provide me the transaction registry details for this i.e the process id. I will have a look into this. Thanks again.

      Delete
    3. This looks like a very good example:

      http://blog.kovaion.com/2015/10/peoplesoft-approval-framework-line.html

      Delete
  10. AWE has a configuration option for number off approvers required, make sure that this is set to more than 1.

    ReplyDelete
  11. Hi All,
    I understand how to retrieve the multiple levels of approvals but have a question about how to get the list into sequential approvals within a path. The list of approvals can be any length from 1 to 6 levels deep however I cannot use 6 separate steps as the last ones would be skipped and go to Admin if no approver is returned in those steps. I want the approvals to be sequential but I cannot determine the number of steps dynamically.

    ReplyDelete
    Replies
    1. Todd,

      The purpose of this post is to illustrate how do exactly what you are trying to attempt. The only way to handle your scenario is to define one step and define it as dynamic. Then create a user list that is Application Package PeopleCode that will be called until no can be found that needs to approve the transaction.

      Delete
    2. Agree on the dynamic path/step but we want the returned list of approvers to approve sequentially and not in parallel. This seems to be the limitation of the dynamic path/step. Is there a way around this?

      Delete
    3. That is not a limitation of AWE. You can have sequential approvals using dynamic step. What you have to do is write your application package userlist in a way that it will only return one set of approvers per call. I think what you are missing is that AWE will call your userlist over and over again until you return and empty array. Each call made to the Application Package will represent a sequential approver. If you write your application package to find all your approvers at once then it will be one step with all your approvers on that step.

      Delete
  12. I developed the dynamic user list and win message shows all the approvers info. But the approval path shows only CEO information. I need the approval chain from the employee to CEO. We use positionmanagement. Please help.

    import EOAW_CORE:DEFN:*;
    import EOAW_CORE:EXCEPTIONS:*;
    import EOAW_CORE:ENGINE:*;

    class DynamicUserlist extends EOAW_CORE:DEFN:UserListBase
    /* method UserListBase(&rec_ As Record); */
    method DynamicUserlist(&rec_ As Record);
    method GetUsers(&aryPrevOprs_ As array of string, &recThread_ As Record) Returns array of string;

    private
    instance Record &recUserListDef, &recThread;
    instance array of string &aryPrevOprs;

    end-class;

    Component boolean &Z_PENDING;
    Component string &Emplid;


    method DynamicUserlist
    /+ &rec_ as Record +/
    %Super = create EOAW_CORE:DEFN:UserListBase(&rec_);
    &recUserListDef = &rec_;
    end-method;

    method GetUsers
    /+ &aryPrevOprs_ as Array of String, +/
    /+ &recThread_ as Record +/
    /+ Returns Array of String +/
    /+ Extends/implements EOAW_CORE:DEFN:UserListBase.GetUsers +/
    Local array of string &arr = CreateArrayRept("", 0);
    Local SQL &OprIDCursor;
    Local string &Emplid_local, &oprid, &Submitter_Emplid, &SubmitterId, &Reportsto, &Reports_to_position_nbr, &Position_data_reports_to, &Reports_to_emplid, &sOprID, &Reports_to_position_data;
    Local number &Count, &index;

    rem Repeat;
    SQLExec("SELECT REPORTS_TO FROM PS_KK_REPORTS_TO WHERE EMPLID =:1 ", &Emplid, &Reportsto);
    SQLExec("SELECT POSITION_NBR,EMPLID FROM PS_JOB_CURR_VW WHERE POSITION_NBR =:1 AND EMPL_STATUS ='A' ", &Reportsto, &Reports_to_position_nbr, &Reports_to_emplid);

    SQLExec("SELECT OPRID FROM PSOPRDEFN WHERE EMPLID =:1", &Reports_to_emplid, &sOprID);
    &arr.Push(&sOprID);
    WinMessage(&sOprID, 0);
    &Emplid = &Reports_to_emplid;
    rem End-If;
    rem Break;
    rem Until &Reportsto = "00000001";
    Return &arr;
    end-method;

    ReplyDelete
    Replies
    1. import EOAW_CORE:DEFN:UserListBase;
      import HR_DIRECT_REPORTS:DirectReports;


      class dynamicList extends EOAW_CORE:DEFN:UserListBase
      method dynamicList(&rec_ As Record);
      method getManager(&emplidIn As string, &emplRcdIn As number, &position As string) Returns string;
      method GetUsers(&aryPrevOpr_ As array of string, &thread_ As Record) Returns array of string;
      end-class;

      method dynamicList
      /+ &rec_ as Record +/
      %Super = create EOAW_CORE:DEFN:UserListBase(&rec_);
      end-method;

      method getManager
      /+ &emplidIn as String, +/
      /+ &emplRcdIn as Number, +/
      /+ &position as String +/
      /+ Returns String +/
      Local integer &x;
      Local Rowset &results;
      Local string &rsltPosition;
      Local string &managerID;
      Local boolean &mgrFound;
      Local HR_DIRECT_REPORTS:DirectReports &DirRpts = create HR_DIRECT_REPORTS:DirectReports();
      &DirRpts.AsOfDate = %Date;
      &rsltPosition = &position;
      If All(&rsltPosition) Then
      &DirRpts.TargetPosition = &position;
      Else
      &DirRpts.TargetEmplid = &emplidIn;
      &DirRpts.TargetERN = &emplRcdIn;
      End-If;
      &DirRpts.UseDirectReportsTables = False;
      &DirRpts.ShowName = False;
      &DirRpts.NavigationMethod = 4;
      &DirRpts.GetSupervisor();
      &results = &DirRpts.Supervisor;
      For &x = 1 To &results.RowCount
      If &results(&x).GetRecord(1).EMPL_STATUS.Value = "A" Then
      &managerID = &results(&x).GetRecord(1).EMPLID.Value;
      &mgrFound = True;
      End-If;
      End-For;

      If Not &mgrFound Then
      &rsltPosition = &results(1).GetRecord(1).POSITION_NBR.Value;
      If All(&rsltPosition) Then
      &position = &rsltPosition;
      &managerID = %This.getManager("", 0, &position);
      End-If;
      End-If;
      If &managerID = &emplidIn Then
      Return " ";
      Else
      Return &managerID;
      End-If;


      end-method;

      method GetUsers
      /+ &aryPrevOpr_ as Array of String, +/
      /+ &thread_ as Record +/
      /+ Returns Array of String +/
      /+ Extends/implements EOAW_CORE:DEFN:UserListBase.GetUsers +/
      Local array of string &oprid_arr = CreateArrayRept("", 0);
      Local Rowset &results;
      Local string &rsltPosition, &lastApproverId, &nextApproverId;
      Local string &mID;
      Local number &lastApproverRcd;
      If &aryPrevOpr_ [1] = %OperatorId Then

      rem this is the first time through;

      &mID = %This.getManager(&thread_.EMPLID.Value, &thread_.EMPL_RCD.Value, "");

      Else



      SQLExec("SELECT EMPLID FROM PSOPRDEFN WHERE OPRID =:1", &aryPrevOpr_ [1], &lastApproverId);
      SQLExec("SELECT EMPL_RCD FROM PS_EMPLOYEES WHERE EMPLID = :1", &lastApproverId, &lastApproverRcd);

      &mID = %This.getManager(&lastApproverId, &lastApproverRcd, "");

      End-If;


      SQLExec("SELECT OPRID FROM PSOPRDEFN WHERE EMPLID = :1", &mID, &nextApproverId);

      &oprid_arr.Push(&nextApproverId);

      Return &oprid_arr;
      end-method;

      Delete
  13. Is there any setup needs to be modified. Skipping the first approver.
    Ex: employee X got promotion. My expectation is that needs to move to X ( Manager) -> X(VP) - X(CEO).
    but in my code it is skipping the X's Manager and routed to X (VP) and X's CEO. Please help.

    ReplyDelete
    Replies
    1. I am writing you an example that use HR_DIRECT_REPORTS. That will be the cleanest way to handle your scenario. Give me a few.

      Delete
    2. I just posted an example userlist, I did not test this list and it does not take in account multi-jobs. You would need to add logic for multi-jobs is that is a valid scenario.

      Delete
  14. Kevin,
    Really I need help on this.
    I have a requirement that 1st approver should be HR manager > Approval chain up to CEO > Staffing.

    In this case, after reaching staffing my Approval chain starts again and it messing up the loop. Please advise.

    I wrote the following dynamic userlist code and getting some issues. Please help

    If &Z_PENDING = True Then
    &Z_PENDING = False;
    &u = &f.GetUserList("Compensation_NES");
    &u.step = %This.step;
    &u.path = %This.path;
    &staffing = False;
    &arr = &u.GetUsers(&aryPrevOprs_, &recThread_);
    Else
    &u = &f.GetUserList("HRApprovalsChain");
    &u.step = %This.step;
    &u.path = %This.path;
    &arr = &u.GetUsers(&aryPrevOprs_, &recThread_);

    SQLExec("SELECT B.OPRID FROM PS_JOB A, PSOPRDEFN B WHERE A.EFFDT = (SELECT MAX(A_ED.EFFDT) FROM PS_JOB A_ED WHERE A.EMPLID = A_ED.EMPLID AND A.EMPL_RCD = A_ED.EMPL_RCD AND A_ED.EFFDT <= SYSDATE) AND A.EFFSEQ = (SELECT MAX(A_ES.EFFSEQ) FROM PS_JOB A_ES WHERE A.EMPLID = A_ES.EMPLID AND A.EMPL_RCD = A_ES.EMPL_RCD AND A.EFFDT = A_ES.EFFDT) AND A.EMPLID = B.EMPLID AND A.HR_STATUS ='A' AND A.POSITION_NBR = '00000001'", &CEO);


    If &aryPrevOprs_ [1] <> &CEO And
    &intheloop = True Then
    &u = &f.GetUserList("HRReportstochain");
    rem &u = &f.GetUserList("HRapprovalschain2");
    &u.step = %This.step;
    &u.path = %This.path;
    WinMessage("in the chain", 0);
    WinMessage("&aryPrevOprs_" | &aryPrevOprs_ [1], 0);
    &arr = &u.GetUsers(&aryPrevOprs_, &recThread_);
    End-If;

    If &aryPrevOprs_ [1] = &CEO And
    &intheloop = True Then
    If &recThread_.ACTION.Value <> "HIR" Then
    &u = &f.GetUserList("Staffing_NES");
    &u.step = %This.step;
    &u.path = %This.path;
    &staffing = True;
    WinMessage("in the staffing", 0);
    &arr = &u.GetUsers(&aryPrevOprs_, &recThread_);
    End-If;

    If &recThread_.ACTION.Value = "HIR" Then
    &u = &f.GetUserList("Staffing_NES_HIRE");
    &u.step = %This.step;
    &u.path = %This.path;
    &arr = &u.GetUsers(&aryPrevOprs_, &recThread_);
    End-If;
    End-If;
    End-If;
    End-If;

    &Z_PENDING = False;
    Return &arr;

    ReplyDelete
    Replies
    1. Satya,

      You can perform the HR Manager approval on a separate stage. Create a Stage 10 that goes to a pooled worklist of HR approvers and then have Stage 20 be the manager approval to the CEO.

      Delete
    2. Kevin,
      Thanks for your help. Now I am able to display 3 paths. First one hr mgr
      Second one new promotion reports to chain to CEO. The problem is that the chain is starting with the submitter instead of new position reports to ...CEO.plz advise

      Delete
    3. Modify the handler that I sent you to use the reports to as the starting point.

      Delete
  15. Kevin,
    Thank you so much for your help.
    How to get the current stage number through people code?.

    ReplyDelete
    Replies
    1. Your dynamic class extends the EOAW_CORE:DEFN:UserListBase. You should be able to use the get method from the super. Something like this:

      %This.util.Stage_id...

      Anyway, take a look at this class and look at the util property.

      Delete