Friday, December 13, 2013

Creating Backup Projects for Change Management

First of all, we don't utilize a change management tool like Stat or Phire.  But that does not mean we don't create backups projects for our changes.  Typically I log into Production and manually create a project and export it to file, but recently I had a project that was going to change 50+ objects.  So I asked my Admin if he had any ideas on moving just the project definition, without the objects, so I could create a backup project from Production.   He told me that if I export the project to a file in Development and then used the "compare and report from file" in Production it would copy my project definition.

This worked and I was able to quickly export my backup file from Production!  


Thursday, December 5, 2013

Dynamic CTE Prompt table

I was asked to create a prompt record for the Recruiter prompt on our Job Requisitions to make it easier for our Talent Acquisition team. I analyzed the data and could not filter it down to a couple of Jobcodes, then I realized 90% of the associates that reported to the head of Talent Acquisition were recruiters. So I got to thinking, that a CTE (Common Table Expresion) would work really well in this scenario.  So I created a field on our custom installation record that identified the head of Talent Acquisition.  Then I wrote this SQL.

WITH REPORTS ( OPRID, NAME, ROLEUSER_SUPR) AS (
 SELECT A.roleuser
 , A.descr
 , A.ROLEUSER_SUPR
  FROM PS_ROLEXLATOPR A
  , PS_ZZ_INSTALLATION I
 WHERE A.OPRID = I.ROLEUSER_SUPR
  UNION ALL
 SELECT A.OPRID
 , A.DESCR
 , A.ROLEUSER_SUPR
  FROM PS_ROLEXLATOPR A
  , REPORTS X
 WHERE  A.ROLEUSER_SUPR = X.OPRID
   AND A.OPRID > ' ')
 SELECT *
  FROM REPORTS

Here is my record Definition:


Now this assumes that you are keeping the roleuser_supr updated.  But if you are keeping it updated then you can write a recursive CTE to return all direct and indirect reports to a position.




Formatting Currency

We built a custom Job Opening Workcenter in PeopleSoft to integrate job requisitions to BrassRing and one of the challenges was formatting the Mid, Min and Max for the Salary Ranges.  We sent over the numeric value, but BrassRing said that if we wanted it formatted, ie. $999,999.99, that we need to send it formatted.  At first I tried using the Field's "formattedValue" property , but this only populated when called in the component buffer.  So when I sent future effective dated changes in Batch, I would need to format these values without the help of the formattedValue.  I searched in PeopleCode and could not find a delivered function to handle my formatting.  So I wrote my own method that I call when publishing updates. 


method format_currency
   /+ &value as String +/
   /+ Returns String +/
   Local string &formattedValue, &partValue;
   Local array of string &inputValue;
   Local number &x, &inputLength, &start, &end;
   &inputValue = Split(&value, ".");
   &inputLength = Len(&inputValue [1]);
  
   rem place the comma;
  
   For &x = &inputLength To 1 Step - 3
      &start = &x - 2;
      If &start > 0 Then
         &partValue = "," | Substring(&inputValue [1], &start, 3);
      Else
         &end = Mod(&inputLength, 3);
         &partValue = Substring(&inputValue [1], 1, &end);
      End-If;
      &formattedValue = &partValue | &formattedValue;
   End-For;
   rem remove any extra commas;
   If Substring(&formattedValue, 1, 1) = "," Then
      &formattedValue = Substring(&formattedValue, 2, (Len(&formattedValue) - 1));
   End-If;
  
   rem add dollar sign and cents;
   If &inputValue.Len = 1 Then
      &formattedValue = "$" | &formattedValue | ".00";
   Else
      &inputValue [2] = &inputValue [2] | "00";
      &formattedValue = "$" | &formattedValue | "." | Substring(&inputValue [2], 1, 2);
   End-If;
  
  
   Return &formattedValue;
end-method











If someone else has had this challenge and found an easier way to accomplish this, please let me know.  Because, I spent way too much time coming up with this solution.


Wednesday, December 4, 2013

Custom Component Interface Methods

Component Interfaces have four standard methods, Cancel, Find, Get and Save.  But PeopleSoft also gives you the ability to create custom methods to handle a scenarios where you might want to do more than just Save the component.  For Instance, we have a Custom Component Interface that we use to process inbound job transfers and rehires from our third party recruiting system BrassRing.  In the case of rehires we need to purge our bolt-on UMB Data that we store on a custom page that we added to the Job Data Component.  This is a perfect use for a Component Interface Method.


First Open up your Component Interface in PeopleTools and double click on the METHODS in the Component Interface Properties pane.


This will open a window where you can define your Custom Method.


Now you will need to add Security to you Custom Method.






Now you can access your Method in your CI Code

&JobDataCI.RESET_UMB_DATA();

Or if you are using my Excel to CI, you can use the Method Option.




Other Uses.

  1. Updating Sequence number like the Effective Date Sequence on Job or the Sequence number for the Paysheet upload.
  2. Creating a Custom Get Method for Components that like Job Code Table or Department Table that use description as a list item, to select one item from the collection.   (Since they all take you to the same place.)
  3.  Conditionally updating data based in the component buffer based.  This last one might seem curious, but if you utilize a utility like my Excel to CI tool, then it makes perfect sense.


Thursday, November 21, 2013

Purchase Requisition Approvals in Peoplesoft

I have been getting asked to show how we have designed our Approval process for Requisitions in PeopleSoft Finance.  First of all, I did not design this process.  I was just asked to come in and make it work. 

How we associate the signing authority to the user.  Oddly enough this is done in HR, we have a bolt on page within the JOB_DATA Component where we store Corporate Functions, HR is responsible for maintaining this data, so it makes sense within our organization.

This is our Corporate Functions Grid:

The Level 3-Up to $100,000 dollars is the Signing Authority for this User.  The initial work the consulting company did, failed to get this information into FIN, when we pressed them about it.  The told us that we needed to update in both FIN and HR.  So I used Integration Broker to send this information to FIN.

In Order to maintain the Approval Chain, we populate the PSOPERDEFN's ROLEUSER_SUPR in HR and use Integration Broker to send this to FIN.  We have a process that runs every fifteen minutes looking for changes and it maintains our PeopleSoft User Profiles and updates our Identity Management System, Novel Access Manager.

The on the FIN side, the consulting company built a configuration page to track the signing authority levels:


 They used this information from HR Corporate functions, the User Profile roleuser supervisor to populate a table called ZZ_WF_AUTHORITY.






The View within my Dynamic User List joins the signing Authority Configuration and the table above.:


SELECT A.OPRID
 , A.EMPLID
 , A.DESCR
 , A.EMAILID
 , A.ZZ_SIGN_AUTHORITY
 , A.SUPERVISOR_ID
 , B.MERCHANDISE_AMT
  FROM PS_ZZ_WF_AUTHORITY A
  , PS_ZZ_SIGN_AUTH B
 WHERE A.ZZ_SIGN_AUTHORITY = B.ZZ_SIGN_AUTHORITY



Thursday, November 14, 2013

Mobile Approvals Using Integration Broker and DataPower ESB Part II

Part two of my blog entry on using integration broker(tools 8.51) covers the request handler and the HTML objects used to generate the pages.  If you have not read the first entry I am attempting to build a simple mobile approval application that uses Integration Broker to parse a form submit and then return either a worklist page, transaction page or confirmation page.  Within each of these pages there are hidden inputs that get populated by the handler to control what HTML page the handler will generate.  The hidden inputs are the PSTOKEN, User Action (Approve or Deny), AWE Process ID and all the keys needed to identify the transaction the user is trying to approve.  These are stored in the HTML.ZZ_MYAPPROVAL_WRAPPER:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="application/x-www-form-urlencoded;charset=UTF-8"/>
</head>
<script>
function submitAction(form,  processid, bgn_dt, emplid, empl_rcd, end_dt, pin_take_num, transactionid, transactionnbr, eoawdefn_id, punch_type, zz_job_opening_id, userValue)
{
//alert('starting process');
// set form values


form.processid_e.value = processid;
form.bgn_dt_e.value = bgn_dt;
form.emplid_e.value = emplid;
form.empl_rcd_e.value = empl_rcd;
form.end_dt_e.value = end_dt;
form.pin_take_num_e.value = pin_take_num;
form.transactionid_e.value = transactionid;
form.transaction_nbr_e.value = transactionnbr;
form.eoawdefn_id_e.value = eoawdefn_id;
form.punch_type_e.value = punch_type;
form.zz_job_opening_id_e.value = zz_job_opening_id;
form.useraction_e.value = userValue;
// form.Submit.disabled=true;
form.submit();
}

</script>

<style>  add some css</style>

<body>

<form   action="%BIND(:1)" id="approval" name="approval" method="Post" >
<input type="hidden" name="pstoken_e" value="%BIND(:2)">

<input type="hidden" name="processid_e" value=" ">
<input type="hidden" name="bgn_dt_e" value=" ">
<input type="hidden" name="emplid_e" value=" ">
<input type="hidden" name="empl_rcd_e" value=" ">
<input type="hidden" name="end_dt_e" value=" ">
<input type="hidden" name="pin_take_num_e" value=" ">
<input type="hidden" name="transactionid_e" value=" ">
<input type="hidden" name="transaction_nbr_e" value=" ">
<input type="hidden" name="eoawdefn_id_e" value=" ">
<input type="hidden" name="punch_type_e" value=" ">
<input type="hidden" name="zz_job_opening_id_e" value=" ">
<input type="hidden" name="useraction_e" value="">

%bind(:3)

</form> 

</body>
</html>

This wrapper is used every time the service generates HTML.  And the Bind(:3) is replaced by either the worklist, transaction or confirmation pages. 

The final HTML object is the HTML.ZZ_MYAPPROVAL_XML, that wraps the HTML.ZZ_MYAPPROVALS_WRAPPER and transaction data into a CDATA of an XML document so Integration broker can respond with an XML.  The XSLT to remove this XML wrapper can be found on the part I of this blog.

<?xml version="1.0"?>
<data psnonxml="Yes"><![CDATA[
%BIND(:1)
]]></data>

And finally the entire Handler:

import PS_PT:Integration:IRequestHandler;
import EOAW_CORE:ApprovalManager;
import ZZ_UTILITIES:getUserName;

class RequestHandler implements PS_PT:Integration:IRequestHandler
   method RequestHandler();
   method OnRequest(&_MSG As Message) Returns Message;
   method BuildWorkList() Returns string;
  
   property Rowset rs_TL_APP_RPTD_XRF;
   property Rowset rs_GP_ABSSS_V_XREF_RS;
   property Rowset rs_ZZ_REQ_AWE_XREF;
   property ZZ_UTILITIES:getUserName Util;
   property number TimeSheet_Count;
   property number Absence_Count;
   property number JobReqs_Count;
   property string postURL;
end-class;

/* constructor */
method RequestHandler
   %This.postURL = GetURL(URL.ZZ_MYAPPROVALS);
end-method;

method OnRequest
   /+ &_MSG as Message +/
   /+ Returns Message +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
   /* Variable Declaration */
   Local Message &msgData, &msgReply;
   Local Record &recReply, &headerRec;
   Local JavaObject &urldecoder;
   Local XmlDoc &ResponseXML, &XML_MESSAGE;
   Local array of string &formArray, &valArray;
   Local number &X, &c;
   Local Rowset &rs_TL_APP_RPT_LINE;
   Local boolean &lineApproval;
   Local string &Worklist_html, &Worklist_html2, &Worklist_html3;
   Local string &CreateRecName, &RecName, &FormPost, &userId, &userPswd, &pstoken, &processId, &userAction, &bgn_dt, &emplid, &empl_rcd, &end_dt, &pin_take_num, &transaction_id, &transaction_nbr, &eoawdefn_id, &punch_type, &zz_job_opening_id, &reason;
   Local EOAW_CORE:ApprovalManager &apprManager;
  
   %This.Util = create ZZ_UTILITIES:getUserName();
   REM &msgReply = CreateMessage(Operation.ZZ_TEST_SERVICE, %IntBroker_Response);
   &msgReply = CreateMessage(Operation.ZZ_MYAPPROVALS, %IntBroker_Response);
   &lineApproval = False;
   &XML_MESSAGE = &_MSG.GetXmlDoc();
  
   &urldecoder = CreateJavaObject("java.net.URLDecoder");
   &FormPost = &urldecoder.decode(&XML_MESSAGE.DocumentElement.GetCDataValue(), "UTF-8");
   &formArray = Split(&FormPost, "&");
   For &X = 1 To &formArray.Len
      &valArray = Split(&formArray [&X], "_e=");
      If &valArray.Len > 1 Then
         Evaluate &valArray [1]
         When = "username"
            &userId = &valArray [2];
            Break;
         When = "password"
            &userPswd = &valArray [2];
            Break;
         When = "pstoken"
            &pstoken = &valArray [2];
            Break;
         When = "processid"
            &processId = &valArray [2];
            Break;
         When = "useraction"
            &userAction = &valArray [2];
            Break;
         When = "bgn_dt"
            &bgn_dt = &valArray [2];
            Break;
         When = "emplid"
            &emplid = &valArray [2];
            Break;
         When = "empl_rcd"
            &empl_rcd = &valArray [2];
            Break;
         When = "end_dt"
            &end_dt = &valArray [2];
            Break;
         When = "pin_take_num"
            &pin_take_num = &valArray [2];
            Break;
         When = "transactionid"
            &transaction_id = &valArray [2];
            Break;
         When = "transaction_nbr"
            &transaction_nbr = &valArray [2];
            Break;
         When = "eoawdefn_id"
            &eoawdefn_id = &valArray [2];
            Break;
         When = "punch_type"
            &punch_type = &valArray [2];
            Break;
         When = "zz_job_opening_id"
            &zz_job_opening_id = &valArray [2];
            Break;
         When-Other
            Break;
         End-Evaluate;
      End-If;
   End-For;
  
  
   If Not SwitchUser(&userId, &userPswd, &pstoken, "") Then
      /* switch failed, do error processing */
      &ResponseXML = CreateXmlDoc(GetHTMLText(HTML.ZZ_MYAPPROVAL_TIMESHEET));
   Else
      REM  switch user success;
      If %PasswordExpired Then
         /* application specific processing for expired passwords */
         &ResponseXML = CreateXmlDoc(GetHTMLText(HTML.ZZ_MYAPPROVAL_TIMESHEET));
      Else
         /*Switch user Succeded, now do processing*/
         If None(&processId) Then
            rem build worklist;
           
            %This.rs_TL_APP_RPTD_XRF = CreateRowset(Record.TL_APP_RPTD_XRF);
            %This.TimeSheet_Count = %This.rs_TL_APP_RPTD_XRF.Fill("Where FILL.Recname = 'TL_APP_HDR' And FILL.Eoawthread_Status = 'S' AND FILL.EOAWPARENT_THREAD IN (Select L.Eoawparent_Thread From Ps_Hcm_Appr_Stat_V X,Ps_Tl_App_Rptd_Xrf L Where X.Eoawthread_Id = L.Eoawthread_Id  AND X.EOAWPRCS_ID = L.EOAWPRCS_ID AND X.oprid = :1 AND X.EOAWSTEP_STATUS = 'P'  AND X.EOAWPRCS_ID = 'TLReportedTime')", %OperatorId);
            %This.rs_GP_ABSSS_V_XREF_RS = CreateRowset(Record.GP_ABSSS_V_XREF);
            %This.Absence_Count = %This.rs_GP_ABSSS_V_XREF_RS.Fill("WHERE FILL.EOAWTHREAD_ID IN (select EOAWTHREAD_ID from ps_hcm_appr_stat_v X where X.oprid  = :1 And X.EoawSTEP_Status = 'P' AND X.EOAWPRCS_ID = 'Absence_Mgmt_ByPosnSupervisor')", %OperatorId);
            %This.rs_ZZ_REQ_AWE_XREF = CreateRowset(Record.ZZ_REQ_AWE_XREF);
            %This.JobReqs_Count = %This.rs_ZZ_REQ_AWE_XREF.Fill("WHERE FILL.EOAWTHREAD_ID IN (select EOAWTHREAD_ID from ps_hcm_appr_stat_v X where X.oprid  = :1 And X.EoawSTEP_Status = 'P' AND X.EOAWPRCS_ID = 'ZZ_BR_APPROVE_REQ')", %OperatorId);
            &Worklist_html = %This.BuildWorkList();
            &ResponseXML = CreateXmlDoc(GetHTMLText(HTML.ZZ_MYAPPROVAL_XML, GetHTMLText(HTML.ZZ_MYAPPROVAL_WRAPPER, %This.postURL, GenToken(), GetHTMLText(HTML.ZZ_MYAPPROVAL_WL, &Worklist_html))));
           
         Else
            rem we have a process id do we have an action to approve or deny?;
            If Substring(&userAction, 1, 1) = "A" Or
                  Substring(&userAction, 1, 1) = "D" Then
               rem we have an action and a processid we have an approval;
               SQLExec("SELECT RECNAME FROM PS_EOAW_TXN_LVL WHERE EOAWPRCS_ID = :1 and EOAWLEVEL = 0", &processId, &RecName);
               &CreateRecName = "Record." | &RecName;
               &headerRec = CreateRecord(@&CreateRecName);
               Evaluate &RecName
               When = "TL_APP_HDR"
                  &headerRec.EMPLID.Value = &emplid;
                  &headerRec.EMPL_RCD.Value = &empl_rcd;
                  &headerRec.EOAWDEFN_ID.Value = &eoawdefn_id;
                  &headerRec.PUNCH_TYPE.Value = &punch_type;
                  &headerRec.TRANSACTIONID.Value = &transaction_id;
                  &lineApproval = True;
                  Break;
               When = "GP_ABS_SS_DAT"
                  &headerRec.EMPLID.Value = &emplid;
                  &headerRec.EMPL_RCD.Value = &empl_rcd;
                  &headerRec.BGN_DT.Value = &bgn_dt;
                  &headerRec.END_DT.Value = &end_dt;
                  &headerRec.PIN_TAKE_NUM.Value = &pin_take_num;
                  &headerRec.TRANSACTION_NBR.Value = &transaction_nbr;
                  Break;
               When = "ZZ_REQUISITIONS"
                  &headerRec.ZZ_JOB_OPENING_ID.Value = &zz_job_opening_id;
                  Break;
               When-Other;
                  Break;
               End-Evaluate;
               &headerRec.SelectByKey();
               &apprManager = create EOAW_CORE:ApprovalManager(&processId, &headerRec, %OperatorId);
               If (&apprManager.hasAppInst) Then
                  Evaluate Substring(&userAction, 1, 1)
                  When = "A"
                     If &lineApproval Then
                        &rs_TL_APP_RPT_LINE = CreateRowset(Record.TL_APP_RPT_LINE);
                        &rs_TL_APP_RPT_LINE.Fill("Where fill.TRANSACTIONID = :1 AND FILL.EMPLID = :2 AND FILL.EMPL_RCD = :3 AND FILL.EOAWDEFN_ID = :4 AND FILL.PUNCH_TYPE = :5", &headerRec.TRANSACTIONID.Value, &headerRec.EMPLID.Value, &headerRec.EMPL_RCD.Value, &headerRec.EOAWDEFN_ID.Value, &headerRec.PUNCH_TYPE.Value);
                        &apprManager.DoApproveRowSet(&rs_TL_APP_RPT_LINE);
                     Else
                        &apprManager.DoApprove(&headerRec);
                     End-If;
                     Break;
                  When = "D"
                     &apprManager.DoDeny(&headerRec);
                     Break;
                  End-Evaluate;
               Else
                  REM NOTHING TO APPROVE;
                 
                 
               End-If;
               &ResponseXML = CreateXmlDoc(GetHTMLText(HTML.ZZ_MYAPPROVAL_XML, GetHTMLText(HTML.ZZ_MYAPPROVAL_WRAPPER, %This.postURL, GenToken(), GetHTMLText(HTML.ZZ_MYAPPROVAL_CONFIRM, "", "", "", "", "", "", "", "", "", "", ""))));
              
            Else
               rem build transaction html;
               Evaluate &processId
               When = "Absence_Mgmt_ByPosnSupervisor"
                  Local Record &gp_pin = CreateRecord(Record.GP_PIN);
                  &gp_pin.PIN_NUM.Value = &pin_take_num;
                  &gp_pin.SelectByKey();
                  Local Record &GP_ABS_EVENT = CreateRecord(Record.GP_ABS_EVENT);
                  &GP_ABS_EVENT.EMPLID.Value = &emplid;
                  &GP_ABS_EVENT.EMPL_RCD.Value = &empl_rcd;
                  &GP_ABS_EVENT.BGN_DT.Value = &bgn_dt;
                  &GP_ABS_EVENT.PIN_TAKE_NUM.Value = &pin_take_num;
                  &GP_ABS_EVENT.END_DT.Value = &end_dt;
                  &GP_ABS_EVENT.SelectByKey();
                  If All(&GP_ABS_EVENT.ABSENCE_REASON.Value) Then
                     Local Record &GP_ABS_TAKE = CreateRecord(Record.GP_ABS_TAKE);
                     &GP_ABS_TAKE.PIN_NUM.Value = &pin_take_num;
                     &GP_ABS_TAKE.SelectByKeyEffDt(%Date);
                     Local Record &GP_ABS_REASON = CreateRecord(Record.GP_ABS_REASON);
                     &GP_ABS_REASON.USED_BY.Value = "C";
                     &GP_ABS_REASON.COUNTRY.Value = "USA";
                     &GP_ABS_REASON.ABS_TYPE_OPTN.Value = &GP_ABS_TAKE.ABS_TYPE_OPTN.Value;
                     &GP_ABS_REASON.ABSENCE_REASON.Value = &GP_ABS_EVENT.ABSENCE_REASON.Value;
                     &GP_ABS_REASON.SelectByKeyEffDt(%Date);
                     &reason = &GP_ABS_REASON.DESCR.Value
                  Else
                     &reason = "None";
                  End-If;
                 
                  &ResponseXML = CreateXmlDoc(GetHTMLText(HTML.ZZ_MYAPPROVAL_XML, GetHTMLText(HTML.ZZ_MYAPPROVAL_WRAPPER, %This.postURL, GenToken(), GetHTMLText(HTML.ZZ_MYAPPROVAL_ABS, &processId, &bgn_dt, &emplid, &empl_rcd, &end_dt, &pin_take_num, &transaction_id, &transaction_nbr, &eoawdefn_id, &punch_type, &zz_job_opening_id, &bgn_dt, &end_dt, &gp_pin.DESCR.Value, &reason, &GP_ABS_EVENT.DURATION_ABS.Value, "8", &Util.getUserNamebyEmplid(&emplid)))));
                  Break;
               When = "TLReportedTime"
                  rem build the time and labor html;
                  Break;
               When = "ZZ_BR_APPROVE_REQ"

                  rem build the absence request;
                  Local Record &Job_Opening = CreateRecord(Record.ZZ_REQUISITIONS);
                  &Job_Opening.ZZ_JOB_OPENING_ID.Value = &zz_job_opening_id;
                  &Job_Opening.SelectByKey();
                  Local Record &DEPT_TBL = CreateRecord(Record.DEPT_TBL);
                  &DEPT_TBL.SETID.Value = "SHARE";
                  &DEPT_TBL.DEPTID.Value = &Job_Opening.DEPTID.Value;
                  &DEPT_TBL.SelectByKeyEffDt(%Date);
                  Local Record &COMPANY_TBL = CreateRecord(Record.COMPANY_TBL);
                  &COMPANY_TBL.COMPANY.Value = &DEPT_TBL.COMPANY.Value;
                  &COMPANY_TBL.SelectByKeyEffDt(%Date);
                  Local Record &ZZ_FLATTREE_ORG = CreateRecord(Record.ZZ_FLATTREE_ORG);
                  SQLExec("select * from %TABLE(:1) WHERE DEPTID = :2", &ZZ_FLATTREE_ORG, &Job_Opening.DEPTID.Value, &ZZ_FLATTREE_ORG);
                  Local Record &JOBCODE_TBL = CreateRecord(Record.JOBCODE_TBL);
                  &JOBCODE_TBL.SETID.Value = "SHARE";
                  &JOBCODE_TBL.JOBCODE.Value = &Job_Opening.JOBCODE.Value;
                  &JOBCODE_TBL.SelectByKeyEffDt(%Date);
                  Local Record &LOCATION_TBL = CreateRecord(Record.LOCATION_TBL);
                  &LOCATION_TBL.SETID.Value = "SHARE";
                  &LOCATION_TBL.LOCATION.Value = &Job_Opening.LOCATION.Value;
                  &LOCATION_TBL.SelectByKeyEffDt(%Date);
                  &ResponseXML = CreateXmlDoc(GetHTMLText(HTML.ZZ_MYAPPROVAL_XML, GetHTMLText(HTML.ZZ_MYAPPROVAL_WRAPPER, %This.postURL, GenToken(), GetHTMLText(HTML.ZZ_MYAPPROVAL_REQ, &processId, &bgn_dt, &emplid, &empl_rcd, &end_dt, &pin_take_num, &transaction_id, &transaction_nbr, &eoawdefn_id, &punch_type, &zz_job_opening_id, &Job_Opening.ZZ_JOB_OPENING_ID.Value, &Job_Opening.ZZ_STATUS.LongTranslateValue, &Job_Opening.POSN_DESCR.Value, &Job_Opening.DESCR.Value, &Job_Opening.POSITION_NBR.Value, &DEPT_TBL.COMPANY.Value, &COMPANY_TBL.DESCR.Value, &ZZ_FLATTREE_ORG.ZZ_DIVISION.Value, &ZZ_FLATTREE_ORG.ZZ_DIVISION_DESCR.Value, &Job_Opening.REPORTS_TO.Value, "LOOKUP MANAGER", &Job_Opening.MANAGER_ID.Value, "LOOK UP MANAGER", &Job_Opening.JOBCODE.Value, &JOBCODE_TBL.DESCR.Value, &Job_Opening.DEPTID.Value, &DEPT_TBL.DESCR.Value, &Job_Opening.LOCATION.Value, &LOCATION_TBL.DESCR.Value, &Job_Opening.ZZ_WORK_SCHED_TEXT.Value, &Job_Opening.ZZ_FULL_PART_TIME.LongTranslateValue, &Job_Opening.SHIFT.LongTranslateValue, &Job_Opening.ZZ_JOB_OPN_REASON.LongTranslateValue))));
                  Break;
               End-Evaluate;
             
            End-If;
         End-If;
      End-If;
   End-If;
  
  
  
  
   &msgReply.SetXmlDoc(&ResponseXML);
  
  
   Return &msgReply;
  
end-method;

method BuildWorkList
   /+ Returns String +/
   Local string &html;
   Local string &empl_name;
   Local Record &Requistion;
   Local number &z;
  
   Local string &dur;
   &Requistion = CreateRecord(Record.ZZ_REQUISITIONS);
  
  
   If %This.TimeSheet_Count + %This.Absence_Count + %This.JobReqs_Count = 0 Then
      &html = "<tr><td>No Pending Transactions</td></tr>"
   Else
      For &z = 1 To %This.TimeSheet_Count
         &empl_name = %This.Util.getUserNamebyEmplid(%This.rs_TL_APP_RPTD_XRF.GetRow(&z).GetRecord(1).EMPLID.Value);
         SQLExec("select Min(dur) from ps_tl_app_rptd_xrf where eoawparent_thread = :1", %This.rs_TL_APP_RPTD_XRF.GetRow(&z).GetRecord(1).EOAWTHREAD_ID.Value, &dur);
         &html = &html | "<tr><td>Timesheet</td>" | "<td>" | &empl_name | " Period starting " | &dur | "</td><td>" | GetHTMLText(HTML.ZZ_MYAPPROVAL_WL_SUBMIT, %This.rs_TL_APP_RPTD_XRF.GetRow(&z).GetRecord(1).EOAWPRCS_ID.Value, "", %This.rs_TL_APP_RPTD_XRF.GetRow(&z).GetRecord(1).EMPLID.Value, %This.rs_TL_APP_RPTD_XRF.GetRow(&z).GetRecord(1).EMPL_RCD.Value, "", "", %This.rs_TL_APP_RPTD_XRF.GetRow(&z).GetRecord(1).TRANSACTIONID.Value, "", %This.rs_TL_APP_RPTD_XRF.GetRow(&z).GetRecord(1).EOAWDEFN_ID.Value, %This.rs_TL_APP_RPTD_XRF.GetRow(&z).GetRecord(1).PUNCH_TYPE.Value, "", "TimeSheet") | "</td></tr>";
      End-For;
      For &z = 1 To %This.Absence_Count
         &empl_name = %This.Util.getUserNamebyEmplid(%This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).EMPLID.Value);
         &html = &html | "<tr><td>Absence Request</td>" | "<td>" | &empl_name | " from " | %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).BGN_DT.Value | " Through " | %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).BGN_DT.Value | "</td><td>" | GetHTMLText(HTML.ZZ_MYAPPROVAL_WL_SUBMIT, %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).EOAWPRCS_ID.Value, %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).BGN_DT.Value, %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).EMPLID.Value, %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).EMPL_RCD.Value, %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).END_DT.Value, %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).PIN_TAKE_NUM.Value, "", %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).TRANSACTION_NBR.Value, %This.rs_GP_ABSSS_V_XREF_RS.GetRow(&z).GetRecord(1).EOAWDEFN_ID.Value, "", "", "Absence Request") | "</td></tr>";
      End-For;
      For &z = 1 To %This.JobReqs_Count
         &Requistion.ZZ_JOB_OPENING_ID.Value = %This.rs_ZZ_REQ_AWE_XREF.GetRow(&z).GetRecord(1).ZZ_JOB_OPENING_ID.Value;
         &Requistion.SelectByKey();
        
         &html = &html | "<tr><td>Job Opening</td>" | "<td>" | %This.rs_ZZ_REQ_AWE_XREF.GetRow(&z).GetRecord(1).ZZ_JOB_OPENING_ID.Value | "-" | &Requistion.DESCR.Value | "</td><td>" | GetHTMLText(HTML.ZZ_MYAPPROVAL_WL_SUBMIT, %This.rs_ZZ_REQ_AWE_XREF.GetRow(&z).GetRecord(1).EOAWPRCS_ID.Value, "", "", "", "", "", "", "", %This.rs_ZZ_REQ_AWE_XREF.GetRow(&z).GetRecord(1).EOAWDEFN_ID.Value, "", %This.rs_ZZ_REQ_AWE_XREF.GetRow(&z).GetRecord(1).ZZ_JOB_OPENING_ID.Value, "Job Opening") | "</td></tr>";
      End-For;
   End-If;
  
  
  
   Return &html
end-method;

The Handler uses either the PSTOKEN or the USERID/PASSWORD to authenticate the request. Then it evaluates if there is a PROCESS_ID, if there is a process id and a user Action then it attempts to Approve or Deny the transaction and then present the confirmation page.  If there is a Process ID and no Action, then it creates the Transaction page for the user to Approve or Deny.  If there is no Process Id, then the Handler returns the Worklist page of pending transactions.  In my example I have only accounted for Three HCM transactions.  One being a custom bolt-on that we built to approve Job Requisitions and two delivered, Time and Labor and Absence Requests.


Saturday, November 2, 2013

AutoSave ePerformance Part II

In September I posted a link to some auto Save JavaScript for ePerformance.  This worked, but it prevented the page from timing out and it would also save while I would be typing on the document and then all of a sudden I would be at the top of the document.  This was quite annoying.  At the same time I was working on warning the user about leaving the page via the back button, I had asked Jim Marion a couple questions and he recommended I monkey patch the timeout function to auto save the document. So below is my JavaScript that monkey patches the displayTimeoutMsg function and warns the user if they navigate away from the page with the back button.


<input type="hidden" name="AUTOSAVE" value=""/>
<script type="text/javascript">

threadLock = false;

function user_function()
{
//alert('starting process');

var changes = checkFormChanged(document.%formname);
if (changes && !threadLock)
{
threadLock = true;
if ("%page" == "EP_APPR_MAIN1" || "%page" == "EP_APPR_BASE1")
{
//submitAction_%Formname(document.%Formname,"EP_BTN_LINK_WRK_EP_STORE_PB");
hAction_%Formname(document.%Formname,'EP_BTN_LINK_WRK_EP_STORE_PB', 0, 0, 'Save', false, true);
}
}
}

</script>
<script type="text/javascript" language="javascript">
(function() {
  var originalendModalCntrl = ptCommonObj.endModalCntrl;
  ptCommonObj.endModalCntrl = function() {
    window.onbeforeunload = function(){};
    return new originalendModalCntrl();
  }
})();

window.onbeforeunload = function(e) {
           var changes = checkFormChanged(document.%formname);
if (changes && !threadLock)
{
threadLock = true;
if ("%page" == "EP_APPR_MAIN1" || "%page" == "EP_APPR_BASE1")
{
            return "You have unsaved changes, please save them."
        };
}
}
</script>

<script type="text/javascript">
function adaptLinks() {
    var links = document.getElementsByTagName('a');
    for (i = 0; i != links.length; i++) {
        links[i].onclick = (function () {
            var origOnClick = links[i].onclick;
            return function (e) {
                 window.onbeforeunload = function(){};
                if (origOnClick != null && !origOnClick()) {
                    return false;
                }               
            }
        })();
    }
}
(function() {
  var originaldisplayTimeoutMsg = displayTimeoutMsg;
  displayTimeoutMsg = function() {
    window.onbeforeunload = function(){};
   
    user_function();

    return new  originaldisplayTimeoutMsg();
  }
})();

adaptLinks();
</script>

Tuesday, October 29, 2013

Mobile Approvals using Integration Broker and DataPower ESB

Recently I was playing around with JDeveloper trying to build an mobile approval application to allow managers to approve AWE transactions via their mobile device.  That is when I had the idea to use Integration Broker to generate an XML document that would contain an HTML Document wrapped in a CDATA, then I could use a simple XLST on the response to remove the HTML document from the XML and send it to the manager's mobile device via DataPower.   DataPower would be utilized to get through our firewall and to change the content-type from xml/text to html/text.  Basically, I am using DataPower as a web server.  So you could also accomplish this same thing without DataPower, you could use a Apache web server with mods headers or even PHP could be used to change the headers.


       1.  Create simple Login Screen and place on external web server
 
     2.  Build one Synchronous Integration Broker Message with handler


     3. Create an “Any to Local” Routing and configure it to utilize XLST to transform the response from XML to HTML


        <?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="html" omit-xml-declaration="yes"/>

<xsl:template match="/">
<xsl:value-of select="/" disable-output-escaping="yes"/>
</xsl:template>

</xsl:stylesheet>
  4.  Use Enterprise Service Bus or external Web server to handle change HTTP header content from XML to HTML
  5.  Build WorkList table with transaction details and URL or button to Approval Pages.
 
 
  6. Build Transaction Specific Approval pages using HTML5 and CSS

This was the presentation I gave a couple of weeks ago at the KCRUG, in my next post I will attempt to go into more regarding the Integration Broker handler.

Monday, October 28, 2013

Disabling the back button in PeopleSoft

I was asked to disable the back button in the browser for ePerformance. However, based on my research you I concluded that the back button cannot be disabled and nor would I want to disable it if I could.  So what can you do to prevent users from losing their work, by using the back button in PeopleSoft?  The answer appears to be the onbeforeunload event in the browser.  This event is designed to prompt the user if they really want to leave the page and potentially lose their changes.  To implement this I needed to add the following JavaScript to the page I wanted to prevent the user from losing their changes.

1. Modify the onbeforeunload to warn the user if the page has changed and they are navigating away from the page.
window.onbeforeunload = function(e) {
           var changes = checkFormChanged(document.%formname);
if (changes)
{           
  return "You have unsaved changes, please save them."
}
}

2.add some JavaScript to clear out the onbeforeunload if they user selected a link/button on the page.
function adaptLinks() {
    var links = document.getElementsByTagName('a');
    for (i = 0; i != links.length; i++) {
        links[i].onclick = (function () {
            var origOnClick = links[i].onclick;
            return function (e) {
                 window.onbeforeunload = function(){};
                if (origOnClick != null && !origOnClick()) {
                    return false;
                }               
            }
        })();
    }
}
adaptLinks();


3.  Finally, I  Monkey Patched the endModalCntrl to clearout the onbeforeunload if they used the "X" to close out a modal window. 

 (function() {
  var originalendModalCntrl = ptCommonObj.endModalCntrl;
  ptCommonObj.endModalCntrl = function() {
    window.onbeforeunload = function(){};
    return new originalendModalCntrl();
  }
})();

Special thanks to Jim Marion for #3

Now when the user clicks the back button or changes the url they are prompted by the page that they have unsaved changes, like this:





Thursday, October 10, 2013

Using the Related Content Framework to avoid Customizations

We run a pretty Vanilla shop here and we frown upon unnecessary customizations to the applications, but with that said we do have a few customizations.  To be precise we have 18 total objects customized.   So when I got a request to add the ability for employees and managers to add attachments to the ePerformance Appraisal document, I searched for a way to do this without touching the delivered pages.  This got me thinking about Related Content, if I could create a simple Component that used the Appraisal Document ID as a key and I shared this Related Content with both the EP_APPR_MAIN1 and the EP_APPR_BASE1 pages, then managers and employees could attach and share documents regardless of what phase the document was currently in.


So I built a Component that was keyed by Appraisal ID and allowed for multiple Documents to be attached.
Then I created the assigned the Related Content at the page level.

Assigning Related Content to Page

 Then press the configure button to pass the Document ID to the related content component.

Make sure you click the Related Content Menu at the bottom!


  Now when I navigate to the Establish Criteria, it has the related Content Menu on the top right and the bottom o the page now has a place for employee's to add attachments.  And since the key is just he document id, all I have left to do is to configure the Manager and Employee evaluation pages to share this content.



Wednesday, October 9, 2013

App Package PeopleCode-"There is no current buffer context."

I was tasked with automating a couple of ePerformance Tasks for our end users and one of those tasks was transferring a document to the next supervisor in the chain of command when the supervisor assigned to the document vacates the position.  At first I thought I would just create a CI and replicate what the user does manually, but when I created a CI of the  EP_APPR_XFER component and tried to use it, I got a plethora of .Null errors.  So I did some research and discovered that the component was just calling the TransferDocument method in the Application Package PeopleCode in the EP_FUNCTIONS:EP_Utilities.   So I decide to just write a simple Application engine step to load a rowset of documents reporting to empty positions and call this method.  But what should have been so easy, ran to "No Success"! 

There is no current buffer context. (2,681) EP_FUNCTIONS.EP_Notification.OnExecute Name:Format_EP_E mail PCPC:24289 Statement:421 Called from:EP_FUNCTIONS.EP_Notification.OnExecute Name:EP_Notify Statement:588 Called from:EP_FUNCTIONS.EP_Utilities.OnExecute Name:TransferDocument Statement:343 Called from:ZZ_EPDOCS.XFR.GBL.default.1900-01-01.Step01.OnExecute Statement:22
I really wish Oracle would adopt a standard that Application Package PeopleCode should not be tied to the Component Buffer.  In order to get around this I cloned the entire EP_UTILITIES and removed the references to the Component Buffer.  Now I am a novice when it comes to Application Package PeopleCode.  I have written a few of my own packages, all of which are independent of the component buffer, but I wonder if I should have extended the EP_UTILITIES, of if cloning the package was the best option?

Friday, September 27, 2013

Integrating PeopleSoft HCM 9.1 with PeopleSoft AP (Non-Commit Accounting)

  1. (Optional) Specify the invoice prefix on the Installation Table (INSTALLATION_TBL) - Product Specific page.
    The character you put in the AP Inv. Prefix field is used as a prefix on all invoices coming from PeopleSoft HCM.



  2. Specify payroll tax extraction on the Company (COMPANY_TABLE) - Default Settings page.
    Select the Pay Taxes through AP check box if you plan to use the interface with Payables to extract payroll taxes.
  3. Define general and benefit deductions.
    For both general and benefit deductions, you must define when each deduction is to be paid. For example, you might want Payables to pay some deductions every time they are calculated and pay other deductions only when the goal balance has been met.


  4. Define vendors in Payables and publish them to the PeopleSoft HCM database.


    Service Operation
    Description
    VENDOR_FULLSYNC
    Payables publishes complete data from the VENDOR and related tables and Payroll for North America subscribes.
    VENDOR_SYNC
    Payables publishes incremental update data from the VENDOR and related tables and Payroll for North America subscribes.
    VOUCHER_BUILD
    Payroll for North America publishes data to the VOUCHER, VOUCHER_LINE, and DISTRIB_LINE tables and Payables subscribes.

     


  5. Link deduction definitions to vendors and define when deductions are to be paid.
    All deductions to be paid by Payables must be linked to a vendor.
    • To link nontax deductions (benefits, general deductions, garnishments) to vendors, complete the SetID and Vendor ID fields on the Benefit Plan Table page, Garnishment Spec Data 2 page, and General Deduction Table page.
    • To determine which deductions to retrieve for Payables, use the Pay Mode and AP Payment Date Type fields on the Benefit Plan Table page, Garnishment Spec Data 7 page, and General Deduction Table page.
      The Garnishment Spec Data 7 page also includes a Separate AP Payment check box that you use to control whether to generate separate checks when there are multiple garnishments for the same vendor.
    • To link U.S. tax deductions to vendors:
      Use the Tax Type Table page to define the tax types paid to a vendor
      Link each tax type to the appropriate vendor and tax classes on the AP State Tax Types/Classes Table page or the AP Local Tax Types/Classes page.
    • To link Canadian tax deductions to vendors, use the Canadian Tax Type Table page.
  6. Set up ChartFields for the GL (general ledger) interface.
    If your organization uses noncommitment accounting, set up the grouping and mapping of expense ChartField for PeopleSoft GL (General Ledger) Interface

Tuesday, September 24, 2013

Auto Save in ePerformance

I just got a request to add auto save to ePerformance and after about 30 minutes of searching a I found this thread on the OTN Community Website.

https://forums.oracle.com/thread/2223522

But the javascript was pretty simple and worked well.

<input type="hidden" name="AUTOSAVE" value=""/>
<script type="text/javascript">

threadLock = false;

function user_function()
{
//alert('starting process');

var changes = checkFormChanged(document.%formname);
if (changes && !threadLock)
{
threadLock = true;
if ("%page" == "EP_APPR_MAIN1" || "%page" == "EP_APPR_BASE1")
{
submitAction_%Formname(document.%Formname,"EP_BTN_LINK_WRK_EP_STORE_PB");
}
}
}

window.setInterval("user_function()",15000);


Thanks to whoever left this thread!

 




Monday, September 9, 2013

Integrating PeopleSoft HCM 9.1 with PeopleSoft GL (Non-Commit Accounting)

We implemented both PeopleSoft FIN and HCM back in 2011, and one of the things we wanted to utilize was the integration between PeopleSoft Payroll NA and PeopleSoft GL.  However, there was a lot of confusion on how this integration worked and what need to be done to accomplish it.  The consultant's that we hired had never setup the integration completely and recommended that we customize the PAYGL01 to populate the chartfield values on the hr accounting line based on the employee's job record.  This worked fine, but it limited our organization's ability to track chartfield values below the department level and it prevented the Payroll to AP integration from working, since the chartfield values where not getting populated on the liability tables only the accouting line.  It also created some posting issues, since we never knew if the chartfield values we populated where valid until we posted them in FIN!

In summary, we took a quick fix approach to get us live on PeopleSoft, that we knew we would have to readdress later.  When later finally came, we discovered that the process for setting up the integration was not that difficult after all!  Here are the steps to setup Payroll Integration between and GL.

 1. Configure Integration Broker and sync the data between your systems.



Service Operation
Description
RELEASE_REQUEST
HRMS requests an update of the General Ledger version from Financials.
RELEASE_RESPONSE
HRMS receives the update of the General Ledger version from Financials.
FSCM_CF_CONFIG
HRMS receives the specific ChartField configuration template from Financials.
One integration point for each GL ChartField
HRMS receives the ChartField values published from the Financials database.
See Entering and Maintaining ChartField Values.
HR_ACCT_CD_LOAD
HRMS receives the combination codes imported from the Financials database or flat file and populates the GL Account Code table (GL_ACCT_CD_TBL).
HR_CHARTFLD_COMBO_SYNC
HRMS receives the service operation published by the Financials database to keep the Speed Type table up to date.
COMBO_CF_EDIT_REQUEST
HRMS sends a request to the Financials database (8.4.01 or higher version) to validate a combination code.
COMBO_CF_EDIT_REPLY
HRMS receives and processes the response from the Financials database to a request for validation
BUS_UNIT_GL_FULLSYNC
Financials database initial full table publish of GL business unit IDs.
BUS_UNIT_GL_SYNC
Financials database ongoing incremental publish of GL business unit IDs.
BUS_UNIT_FS_FULLSYNC
Financials database initial full table publish of GL business unit descriptions
BUS_UNIT_FS_SYNC
Financials database ongoing incremental publish of GL business unit descriptions
JOURNAL_GEN_APPL_ID_FULLSYNC
Financials database initial full table publish of PeopleSoft Journal Generator templates
JOURNAL_GENERATOR_APPL_ID_SYNC
Financials database ongoing incremental publish of PeopleSoft Journal Generator templates.
PAYROLL_ACCTG_TRANSACTION
PeopleSoft HCM database batch publish of HR (payroll) accounting lines.

2. Associate a GL/AP Buiness Unit to your HR Business Units  













3. GL/HR Business Unit Mapping- Associate a Journal Template to your Business Unit.











4. Turn on the General Ledger option on the HCM Installation Record













5. On the Product Specific Tab on the HCM Intall press the Update Version button in the General Ledger Group box














6. Publish Chartfield Data from Finance to HCM



   A. When building Combination Data in FIN publish process groups to HCM 








 B. Load Combination Code Table in HCM











7. Create GL Activity Groups











 

8. Map your Expenses.













Now you are ready to send Payroll to GL.