Friday, April 4, 2014

Autosave ePerfromance Take III

Since we are working on our tools upgrade to 8.53, I was forced to readdress the auto save functionality that I had previously blogged about.  In my initial posting I was auto saving when the user navigated to the EP_APPR_EDIT5 or EP_APPR_M_EDIT5 pages.  The auto save functionality was no matching buffer issues if the document did not already have a goal attached.  I also attempted to auto save from the two pages above, but it to generated the same error messages.  For now I am leaving them out, and commenting out the JavaScript that calls the save prior to navigating to these pages.  You are welcome to uncomment this code out and play around at your own risk as always. Back to the method I went with:

First, create a derived work record and add the fields HTMLAREA and EP_STORE_PB





Second,  add the fields from step one to the EP_APPR_MAIN1 and EP_APPR_BASE1.




 *Make sure you set the EP_STORE_PB to invisible and modifiable via JavaScript and give it a page field name of ZZ_EP_STORE.

Third, add the following PeopleCode to the work records.



Add some FieldChange PeopleCode to the EP_STORE_PB to save the Document.  This can be copied from the Delivered Save Logic.



/*******************************************
RITM0054746 8.53 TOOLS UPGRADE 
UMBMOD
KCW002

Autosave code changed to for new timout logic
in 8.53
*****************************************/

import EP_FUNCTIONS:EP_Utilities;

Declare Function TransferToDocumentSelection PeopleCode FUNCLIB_EP.EP_APPRAISAL_ID FieldFormula;

Global Record &EP_GBLKEYS_WRK;
Component string &PushButton;



Evaluate %Component
When = Component.EP_APPR_BASE
   &ReturnCode = "RETURN_EST_SAVE";
   &TransferPg = "Page.EP_APPR_BASE1";
   Break;
When = Component.EP_APPR_MAIN
   &ReturnCode = "RETURN_EVL_SAVE";
   &TransferPg = "Page.EP_APPR_MAIN1";
   Break;
When-Other
   Exit;
   Break;
End-Evaluate;

/* Clear error messages, if any, and Save */
EP_BTN_LINK_WRK.EP_HTMLAREA_ERROR1.SetDefault();
EP_BTN_LINK_WRK.EP_HTMLAREA_ERROR2.SetDefault();
EP_BTN_LINK_WRK.EP_HTMLAREA_ERROR1.Visible = False;
EP_BTN_LINK_WRK.EP_HTMLAREA_ERROR2.Visible = False;
&PushButton = "STORE";
DoSaveNow();

Rem: Set Return Status to display a confirmation message;
&EP_GBLKEYS_WRK.EP_RETURN_STATUS.Value = &ReturnCode;
Rem: Set the Return Status HTML Area;
Local EP_FUNCTIONS:EP_Utilities &EP_Utilities = create EP_FUNCTIONS:EP_Utilities();
&EP_Utilities.SetReturnStatusHtmlArea();
Rem: Set focus to the top of the page for the return status message;

If ZZ_DERIVED_EP.EP_STORE_PB.Value = "T" Then
   
   REM DO TIMEOUT;
   ZZ_DERIVED_EP.HTMLAREA.Value = GetHTMLText(HTML.ZZ_EP_TIMEOUT);
Else
   
   ZZ_DERIVED_EP.EP_STORE_PB.Value = "";
   
End-If;

Also add some PeopleCode to the RowInit of the HTMLAREA.

/*******************************************
RITM0054746 8.53 TOOLS UPGRADE 
UMBMOD
KCW002

Autosave code changed to for new timout logic
in 8.53

Setup JavaScript for UMB Autosave timeout
*****************************************/

ZZ_DERIVED_EP.HTMLAREA.Value = GetHTMLText(HTML.ZZ_EP_AUTOSAVE);
 

Fourth, create two HTML Objects.

HTML.ZZ_EP_AUTOSAVE below:
<input type="hidden" name="ZZ_EP_STORE" id="ZZ_EP_STORE" value=""/>

<script type="text/javascript">

threadLock = false;
var xStore = document.getElementById("ZZ_EP_STORE");

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

 if (!threadLock)
  {
   threadLock = true;
   if ("%page" == "EP_APPR_MAIN1" || "%page" == "EP_APPR_BASE1" )
   {


    xStore.value = 'T';
    //alert(xStore.value);
    document.%formname.submit();

   }
  }
}

</script>
<script type="text/javascript" language="javascript">

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) { 
                 //add logic to save if the user is adding and item to the document                
                 //linkId = this.id.split('$');                 
                 //if (linkId[0] == 'EP_BTN_LINK_WRK_EP_ADD_ITEM_PB')
                 // {
                 //   xStore.value = 'Z'; 
                 // }
                 //end mod to auto save if the user is adding an item to the document
                 window.onbeforeunload = function(){};
                if (origOnClick != null && !origOnClick()) {
                    return false;
                }                
            }
        })();
    }
}
(function() {
  var originaldisplayTimeoutMsg = displayTimeoutMsg2;
  displayTimeoutMsg2 = function() {
    window.onbeforeunload = function(){};
    
    user_function();
  
  }
})();

adaptLinks();

</script> 
 
 
HTML.ZZ_EP_TIMEOUT below:
<input type="hidden" name="TIMEOUT" value=""/>

<script type="text/javascript">

self.location=timeOutURL; ;

</script> 
 
 

Test it out.

 When the user timesout, the user function will be called instead of the delivered timout logic, this will set the hidden ZZ_EP_STORE field value to 'T'.  This will trigger the fieldchange Peoplecode to trigger and this will save the document and set the HTMLAREA to the ZZ_EP_TIMEOUT.  This will inject JavaScript into the page that will log the user out and present them with the timeout message.  The adaptLinks function will prevent the user from navigating away from the current page when the form is changed and will prompt them to stay and save the information, for example when the user hits the Back button.  But it will also change the value of the hidden ZZ_EP_STORE to 'Z' if the link clicked is one to navigate the user to add something to the document, this will also trigger the FieldChange PeopleCode and it will save the document, but it won't timeout the user.    

Tuesday, April 1, 2014

Mobile Approvals using Integration Broker and Node.js

If you read this blog then you know I have already blogged about using our ESB, DataPower, to process mobile approvals. The concept is to have integration broker server up really simple HTML forms and use a web server or ESB to send the request to Integration broker.  Integration broker then responds with some HTML that looks like this:


Much of the code that generates this HTML is shown with the previous post,  however I omitted the HTML objects.  So looking at how the HTML gets generated and then wrapped in an xml document, this is the code that creates the form for the mobile user to act on.

&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)))));

 Starting with the inner most HTML object we find the HTML.ZZ_MYAPPROVAL_ABS:
         

<h2>Absence Request</h2>
<h4>%Bind(:18)</h4>
<br>
     
Start Date: <input type="text" name="StDt"  value="%bind(:12)" readonly><br>
End Date: <input type="text" name="EndDt"  value="%bind(:13)" readonly><br>
Absence Name: <input type="text" name="Name"  value="%Bind(:14)" readonly><br>
Duration: <input type="text" name="Dur"  value="%bind(:17) Hours**" readonly><br>


<br><br>

<input type="submit" value="Approve"  onclick="submitAction(document.approval,'%bind(:1)','%bind(:2)','%bind(:3)','%bind(:4)','%bind(:5)','%bind(:6)','%bind(:7)','%bind(:8)','%bind(:9)','%bind(:10)','%bind(:11)', 'Approve')" >
<input type="submit" value="Deny"  onclick="submitAction(document.approval,'%bind(:1)','%bind(:2)','%bind(:3)','%bind(:4)','%bind(:5)','%bind(:6)','%bind(:7)','%bind(:8)','%bind(:9)','%bind(:10)','%bind(:11)','Deny')" >
<input type="submit" value="Worklist"  onclick="submitAction(document.approval,' ','%bind(:2)','%bind(:3)','%bind(:4)','%bind(:5)','%bind(:6)','%bind(:7)','%bind(:8)','%bind(:9)','%bind(:10)','%bind(:11)','')" >

<p>If you click the "Submit" button, the form-data will be sent to Integration Broker.</p> 


The HTML above then gets wrapped by the HTML.ZZ_MYAPPROVAL_WRAPPER.


<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="application/x-www-form-urlencoded;charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.0/jquery.mobile-1.4.0.min.css" />
//<link rel="stylesheet" href="http://shell.acmelabs.io/~kweaver/css/umb-custom-theme.css" />
//<link rel="stylesheet" href="http://www.shell.acmelabs.io/~kweaver/css/umb-custom-theme.css" />
<link rel="stylesheet" href="http://www.connectedchef.com/umb-custom-theme.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.js">

</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>

<body>
<div id="container_form" data-role="page"  data-theme="a">
<header data-role="header" data-theme="a">
<h1>myApprovals</h1>
 </header>

<form   action="%BIND(:1)" data-ajax="false"  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>
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.0/jquery.mobile-1.4.0.min.js"></script>
</html>
 
And finally these to HTML objects get wrapped in then HTML.ZZ_MYAPPROVAL_XML

<?xml version="1.0"?>
<data psnonxml="Yes"><![CDATA[
%BIND(:1)
]]></data>
 
 
This last XML wrapper gets taken off via a transformation, but is essential in preventing tools pre 8.52 from creating an Integration Broker Error.


My final entry for this is my node.js web server code to facilitate the serving of the HTML content from Integration Broker. This code below will take any get and server up a simple login screen all posts that have the path /myApprovals then get posted to Integration Broker and the HTML response from above is then served to the device for the user to Approve or Deny.


var http = require('http');
var fs = require('fs');
var querystring = require('querystring');
var utils = require('util');
var request = require("request");

var options = {
  uri: "http://lsys113c.umb.com:13100/PSIGW//HttpListeningConnector?Operation=ZZ_MYAPPROVALS.v1&OperationType=Sync",
  method: "POST",
  form: {
  }
};


var templateEngine = function (template, data) {
  var vars = template.match(/\{\w+\}/g);

  if (!vars) {
    return template;
  }

  var nonVars = template.split(/\{\w+\}/g);
  var output = '';

  for (var i = 0; i < nonVars.length; i++) {
    output += nonVars[i];

    if (i < vars.length) {
      var key = vars[i].replace(/[\{\}]/g, '');
      output += data[key]
    }
  }

  return output;
};

http.createServer(function (req, res) {
  // set up some routes
  switch(req.url) {
    case '/':
  //  console.log("[200] " + req.method + " to " + req.url);
   res.statusCode = 200;
   res.setHeader("Content-Type", "text/html");

  fs.readFile('login.html', function (err, data) {
    if (!err) {
      res.write(templateEngine(data.toString(), {})); // use our template engine here
      res.end();
    }
  });
  break;


   case '/myApprovals':

  if (req.method == 'POST') {
//    console.log("[200] " + req.method + " to " + req.url);
    var fullBody = '';

    function PostData(toIB) {

     options.form = toIB;

     request(options, function(error, response, body) {
     res.writeHead(200, "OK", {'Content-Type': 'text/html'});
     res.write(body);
     res.end();
     });

    };
    
    req.on('data', function(chunk) {
      // append the current chunk of data to the fullBody variable
      fullBody += chunk.toString();
    });
    
    req.on('end', function() {
       // parse the received body data
      var decodedBody = querystring.parse(fullBody);
  //    console.log(decodedBody);
      var psoftRes = PostData(decodedBody);

    });
    
  } else {
    console.log("[405] " + req.method + " to " + req.url);
    res.writeHead(405, "Method not supported", {'Content-Type': 'text/html'});
    res.end('<html><head><title>405 - Method not supported</title></head><body><h1>Method not supported.</h1></body></html>');
  }
  
  break;

    default:
      res.writeHead(404, "Not found", {'Content-Type': 'text/html'});
      res.end('<html><head><title>404 - Not found</title></head><body><h1>Not found.</h1></body></html>');
      console.log("[404] " + req.method + " to " + req.url);
  };
}).listen(8080); 


Monday, March 10, 2014

Bookmarklets for Integration Broker msgLog and errorLog

If you have a copy of Jim Marion's book, they you are probably familiar with the concept of bookmarklets.  In chapter 5 he provides an example of how to turn trace on and off from a bookmarklet calling an iScript.  If you are not familiar with the concept of bookmarklets, then you can read about them on wikipedia.  However, they are just JavaScript programs used to extend the user's experience.

I use them in DataPower, IBM's SOA device to navigate to commonly used tools, such as the xpath finder and I use them in PeopleSoft to turn trace on and off and to navigate to the msgLog.html and errorLog.html for integration broker.

To create a new bookmarklet in Chrome, right click on the bookmark and select "New Page"




Then add a descriptions like msgLog and past this JavaScript funtion in the url:

javascript:void(window.open(window.location.href.replace(/(^[^:]*:\/\/[^/]*).*$/g,'$1') +'/PSIGW/msgLog.html').focus())



Repeat this for the errorLog.html and add the following JavaScript to the url:

javascript:void(window.open(window.location.href.replace(/(^[^:]*:\/\/[^/]*).*$/g,'$1') +'/PSIGW/errorLog.html').focus())

Tuesday, February 25, 2014

Identifying Asynchronous Integration Points

Every time we do any sort of upgrade on Tools or Application I find myself writing this sql to identify what Async Transactions we are using so I can add them to my test plan.  So here is how I identify my Integration Broker messages that I need to test.


SELECT A.ROUTINGDEFNNAME 
 , A.ib_operationname
 , A.VERSIONNAME
 , A.SENDERNODENAME
 , A.RECEIVERNODENAME 
  FROM PSIBRTNGDEFN A 
 WHERE EFF_STATUS = 'A' 
   AND EXISTS (
 SELECT 'X' 
  FROM PSOPRVERDFN_VW X 
 WHERE a.ib_operationname = X.ib_operationname 
   AND a.versionname = X.versionname 
   AND X.ACTIVE_FLAG = 'A') 
   AND EXISTS (
 SELECT 'X' 
  FROM PSAPMSGPUBHDR X 
 WHERE a.ib_operationname = X.ib_operationname);



I select of the routing table where the service operation is active and them I look for actual transactions that have been previously published.  Now I do run the risk of missing an infrequent transaction that might be configured, but I have never actually encountered an upgrade related issue regarding asynchronous message in PeopleSoft.  So I am pretty happy with this approach.

More Common Table Expressions!

Maybe I am beating a dead horse here, but I use CTE's daily.  Today I was asked to look into self service address changes that were pending approval to provide a list of address changes that have not been processed.

First I created a SQL statement that allowed me to identify active employee's with pending address changes on the HR_ADDR_CHG_DAT.

This is what I came up with:

SELECT *
  FROM PS_HR_ADDR_CHG_DAT A
 WHERE ACTION_DT_SS = (
 SELECT MAX(B.ACTION_DT_SS)
  FROM PS_HR_ADDR_CHG_DAT B
 WHERE A.EMPLID = B.EMPLID
   AND A.ADDRESS_TYPE = B.ADDRESS_TYPE
   AND B.ACTION_DT_SS <= TO_DATE(SYSDATE))
   AND A.EFFSEQ = (
 SELECT MAX(Z.EFFSEQ)
  FROM PS_HR_ADDR_CHG_DAT Z
 WHERE A.EMPLID = Z.EMPLID
   AND A.ADDRESS_TYPE = Z.ADDRESS_TYPE
   AND A.ACTION_DT_SS = Z.ACTION_DT_SS)
   AND A.ADDRESS_TYPE = 'HOME'
   AND A.SS_STAT_INDICATOR = 'M'
   AND EXISTS(
 SELECT 'X'
  FROM PS_EMPLOYEES X
 WHERE A.EMPLID = X.EMPLID )

 Then I knew I wanted to compare the results of this SQL with current address data that can be found in the PS_ADDRESSES4_VW.  So rather then joining this record into the SQL above I used a CTE to create a dynamic view and joined the current address record to the CTE.

 WITH ADDR_CHG AS (
 SELECT *
  FROM PS_HR_ADDR_CHG_DAT A
 WHERE ACTION_DT_SS = (
 SELECT MAX(B.ACTION_DT_SS)
  FROM PS_HR_ADDR_CHG_DAT B
 WHERE A.EMPLID = B.EMPLID
   AND A.ADDRESS_TYPE = B.ADDRESS_TYPE
   AND B.ACTION_DT_SS <= TO_DATE(SYSDATE))
   AND A.EFFSEQ = (
 SELECT MAX(Z.EFFSEQ)
  FROM PS_HR_ADDR_CHG_DAT Z
 WHERE A.EMPLID = Z.EMPLID
   AND A.ADDRESS_TYPE = Z.ADDRESS_TYPE
   AND A.ACTION_DT_SS = Z.ACTION_DT_SS)
   AND A.ADDRESS_TYPE = 'HOME'
   AND A.SS_STAT_INDICATOR = 'M'
   AND EXISTS(
 SELECT 'X'
  FROM PS_ZZ_IDM_DATA X
 WHERE A.EMPLID = X.EMPLID2
   AND X.HR_STATUS IN ('A','L')))
 SELECT SS.EMPLID
 , SS.effseq
 , SS.ADDRESS_TYPE
 , SS.ADDRESS1 SS_ADDRESS1
 , CUR.ADDRESS1
 , SS.ADDRESS2 SS_ADDRESS2
 , CUR.ADDRESS2
 , SS.CITY SS_CITY
 , CUR.CITY
 , SS.POSTAL SS_POSTAL
 , CUR.POSTAL
 , SS.STATE SS_STATE
 , CUR.STATE
  FROM ADDR_CHG SS
  , PS_ADDRESSES4_VW CUR
 WHERE CUR.EMPLID = SS.EMPLID
   AND CUR.ADDRESS_TYPE = 'HOME'
   AND (UPPER(SS.ADDRESS1) <> UPPER(CUR.ADDRESS1)
    OR UPPER(SS.ADDRESS2) <> UPPER(CUR.ADDRESS2)
    OR UPPER(SS.CITY) <> UPPER(CUR.CITY)
    OR UPPER(SS.STATE) <> UPPER(CUR.STATE)
    OR SS.POSTAL <> CUR.POSTAL)



 This simplifies the SQL in my opinion.



Friday, February 14, 2014

Using CTEs to simplyfy SQL

Common Table Expressions, CTE for short, is a powerful tool that I use in views, debugging, testing and simplyfying SQL.

CTE Structure


With CTE (EMPLID, DEPARTMENT) AS ( SELECT EMPLID, DEPTID FROM PS_CURRENT_JOB WHERE JOBCODE = 'SUPER')
SELECT * FROM CTE


The CTE above would return everyone from the current job record where the JOBCODE is 'SUPER'.  This not very useful example, but it does show the syntax.

So how can CTE's simply sql?  For instance, if you were writing a process that needs to derive an value to be used in the selection and in the criteria, if you did not use a CTE then your SQL could look like this below:

select 
A.Emplid,
A.benfit_plan,
M.MAX_ANNUAL_CONTRIB,
A.ANNUAL_PLEDGE
CASE A.CNTRL_BENEFIT_PLAN WHEN ' ' THEN 0 ELSE CASE A.ZZ_HSA_YEARS WHEN 1 THEN DECODE(L.COVRG_CD ,'1' , I.ZZ_HSA_CONTRIB1 , I.Zz_Hsa_Fam1)/2 ELSE DECODE(L.COVRG_CD ,'1' , I.ZZ_HSA_CONTRIB2 , I.Zz_Hsa_Fam2/2) END END as UMB_CONTRIB
FROM PS_ZZ_UMB_TBL A., PS_ZZ_HSA_LIMITS M, PS_ZZ_INSTALLATION I
WHERE A.EMPLID = M.EMPLID AND A.EMPL_RCD = M.EMPL_RCD
 AND M.MAX_ANNUAL_CONTRIB < ( CASE A.CNTRL_BENEFIT_PLAN WHEN ' ' THEN 0 ELSE CASE A.ZZ_HSA_YEARS WHEN 1 THEN DECODE(L.COVRG_CD ,'1' , I.ZZ_HSA_CONTRIB1 , I.Zz_Hsa_Fam1)/2 ELSE DECODE(L.COVRG_CD ,'1' , I.ZZ_HSA_CONTRIB2 , I.Zz_Hsa_Fam2/2) END END + A.ANNUAL_PLEDGE)


The problem with this sql is that you have two copies of the same case statement.   Here is how you can use a CTE to only manage one case statement.



With CTE (EMPLID, BENEFIT_PLAN, MAX_ANNUAL_CONTRIB, ANNUAL_PLEDGE UMB_CONTRIB) AS ( select 
A.Emplid,
A.benfit_plan,
M.MAX_ANNUAL_CONTRIB,
A.ANNUAL_PLEDGE
CASE A.CNTRL_BENEFIT_PLAN WHEN ' ' THEN 0 ELSE CASE A.ZZ_HSA_YEARS WHEN 1 THEN DECODE(L.COVRG_CD ,'1' , I.ZZ_HSA_CONTRIB1 , I.Zz_Hsa_Fam1)/2 ELSE DECODE(L.COVRG_CD ,'1' , I.ZZ_HSA_CONTRIB2 , I.Zz_Hsa_Fam2/2) END END as UMB_CONTRIB
FROM PS_ZZ_UMB_TBL A., PS_ZZ_HSA_LIMITS M, PS_ZZ_INSTALLATION I
WHERE A.EMPLID = M.EMPLID AND A.EMPL_RCD = M.EMPL_RCD)
SELECT EMPLID, BENEFIT_PLAN, UMB_CONTRIB FROM CTE WHERE  MAX_ANNUAL_CONTRIB < ( ANNUAL_PLEDGE + UMB_CONTRIB)

Wednesday, January 22, 2014

Menu Name on Component Interfaces

When creating a component interface be sure to set your Menu Name property to the Component your are trying to update.  For instance we built a component interface for an onboarding feed that would update new employee's federal tax withholding and their marital status, however we discovered that is was also erronously updating the unemployment jurisdiction as well.  When we traced the issue we disovered that there was PeopleCode evaluating the menu name and setting a work field called UI_JURIS_SUM, this field was not getting set via the CI.  See Below:  


>>>>> Begin STATE_TAX_DATA.UI_JURISDICTION.FieldFormula level 2 row 1
1: If %Menu = MenuName."MAINTAIN_PAYROLL_DATA_(USF)" Or
Fetch Field: MENUNAME.MAINTAIN_PAYROLL_DATA_(USF) Value=MAINTAIN_PAYROLL_DATA_(USF)
Fetch Field: MENUNAME.MAINTAIN_PAYROLL_DATA_US Value=MAINTAIN_PAYROLL_DATA_US
Fetch Field: MENUNAME.MAINTAIN_PAYROLL_INTERFACE Value=MAINTAIN_PAYROLL_INTERFACE
658      11.09.15     0.000019   <<< end       Nest=01  


This made me look to the Component Interface Properties and then set the Menu Name to the Menu name of the Component I am trying to update.


Below is the PeopleCode from above, after I set the Menu Name Property on the CI.

>>>>> Begin STATE_TAX_DATA.UI_JURISDICTION.FieldFormula level 2 row 0
1: If %Menu = MenuName."MAINTAIN_PAYROLL_DATA_(USF)" Or
Fetch Field: MENUNAME.MAINTAIN_PAYROLL_DATA_(USF) Value=MAINTAIN_PAYROLL_DATA_(USF)
Fetch Field: MENUNAME.MAINTAIN_PAYROLL_DATA_US Value=MAINTAIN_PAYROLL_DATA_US
If All(STATE_TAX_DATA.STATE) Then
3:       If STATE_TAX_DATA.UI_JURISDICTION = "Y" Then
         Fetch Field: STATE_TAX_DATA.UI_JURISDICTION Value=Y
4:          DERIVED_PAY.UI_JURIS_SUM = DERIVED_PAY.UI_JURIS_SUM + 1;
            Fetch Field: DERIVED_PAY.UI_JURIS_SUM Value=0
            Store Field: DERIVED_PAY.UI_JURIS_SUM Value=1
5:       End-If;
8: 
<<< end       Nest=01  STATE_TAX_DATA.UI_JURISDICTION.FieldFormula Dur=0.000417 CPU=0.000000 Cycles=20

So if you want your Componet Interface to behave more like you Component, then make sure you set your menu property!