Saturday, February 27, 2016

Dynamic Position Title from Jobcode vs Position Data

Dynamic Position Title from Jobcode vs Position Data

On a several different occasion, I was asked how we dynamically derive a position title from the position data table when the position number exists in the job data instead of from jobcode table.

So, I wrote this SQL to return emplID, empl record, and dynamic position title (derived from jobcode vs position data).

You can also make this SQL as record view and you can call it using peoplecode to return a dynamic position title you desire.

For this example, I am querying emplID: KU0010.

Note: replace {less than sign} with <
For some reasons, it keeps translating and displaying it to < (If anyone has suggestion to fix the dispay, please let me know.  I just don't have time to research it right now)

Additionally, I am running this in Oracle database, so sysdate will work just fine.  If you are using MSSQL database, you know what to do.
SELECT job.emplid 
 , job.empl_rcd 
 , CASE pos.descr WHEN '' THEN jobcd.descr ELSE pos.descr END 
  FROM ps_job job LEFT JOIN ( 
 SELECT J1.setid 
 , J1.jobcode 
 , J1.Descr 
  FROM ps_jobcode_tbl J1 
 WHERE J1.effdt = ( 
 SELECT MAX(effdt) 
  FROM ps_jobcode_tbl 
 WHERE setid = J1.setid 
   AND jobcode = J1.jobcode 
   AND effdt {less than sign}= SYSDATE)) jobcd ON jobcd.setid = job.setid_jobcode 
   AND jobcd.jobcode = job.jobcode LEFT JOIN ( 
 SELECT P1.position_nbr 
 , P1.descr 
  FROM ps_position_data P1 
 WHERE P1.effdt = ( 
 SELECT MAX(effdt) 
  FROM ps_position_data 
 WHERE position_nbr = P1.position_nbr 
   AND effdt {less than sign}= SYSDATE)) pos ON pos.position_nbr = job.position_nbr 
 WHERE job.effdt = ( 
 SELECT MAX(effdt) 
  FROM ps_job 
 WHERE emplid = job.emplid 
   AND empl_rcd = job.empl_rcd 
   AND effdt {less than sign}= SYSDATE) 
   AND job.effseq = ( 
 SELECT MAX(effseq) 
  FROM ps_job 
 WHERE emplid = job.emplid 
   AND empl_rcd = job.empl_rcd 
   AND effdt = job.effdt)
 AND job.emplid like 'KU0010';

Peoplesoft Component Error - You Are Not Authorized

Component Error "You Are Not Authorized..."

This morning I was helping my colleague troubleshooting this common peoplesoft component error "You are not authorized to access this component. (40,20)"

It's not fun, HUH!...

Have you checked?
1. Component in the menu
2. Access to component in the permission list
3. permission list in the role
4. the role assigned to the user profile
5. clear browser cache, close the browser, reopen browser
6. clear and reboot app server cache

Ya..Ya..all checked, but why it still errors out??? I know your frustration, and don't bang your head against the table yet.

This is what I found today that I want to add it into the list:

7. non-existing permission list assigned to the role.

What!! How could it be possible?

The only way that non-existing permission list in the role is by doing a project migration from database to database. You may think the permission list in the project, but apparently not.

If you were like me, I do not add roles, permission lists, menu in my project. Unless you are the sole developer for the system, I would encourage to stay away from it.

Good Luck!

Code Tip:
Here is a SQL to check if there is non-existed permission list which is still attached to role:
Select R.rolename
, RC.classidFrom psroleuser R
      , psroleclass RCWhere RC.Rolename = R.Rolename
    And not exists
          (Select 'X' from psclassdefn C
           Where C.classid = RC.classid)
    And R.roleuser = '[--OPRID--]'

Script to update the Tablespace for all the records in the Project

Script to update the Tablespace for all the records in the Project. when we import Project in the client instance, we can use this SQL to change the tablespace for all the records at time, rather changing it one by one.

Update PSRECTBLSPC
SET DDLSPACENAME = {NEW TABLESPACE}
WHERE DDLSPACENAME = {OLD TABLESPACE};
and RECNAME IN (Select A.OBJECTVALUE1 from PSPROJECTITEM A, PSRECDEFN B
               Where A.PROJECTNAME = {PROJECTNAME} and A.OBJECTTYPE = 0 and A.OBJECTID1 = 1 and A.OBJECTVALUE1 = B.RECNAME and B.RECTYPE =0)

Peoplesoft Security - Rowlevel

HCM - RowLevel security directly driven by the - "RowLevelSecurity" Permission list.
User Profile needs to have Rowlevel Security Permission assigned to get the security.

We can have Department based - RowLevelSecurity as well as based on the custom Security Set Properties.(It can be based on COMPANY, BUSINESS UNIT etc.).

1) Security by Department 2) Security by Permission List.


Rowlevel security is enforced in the components by user Security Views as Search Records, where OPRID is a non-search key field. PeopleSoft

Rowlevel Security in the PSQuery is achieved by Search - Query Security Record - which is an property of the Record. Query Security Record (which a security view) is added to query automatically.

Since the Campus has different security model, using HCM Personal data records in PSQuery for Campus Solutions will be a problem.

Campus solution Module doesn't use the "RowLevelSecurity" Permission list in the User Profile.

Campus solution has 2 options -

a) SCAR security based on the data element linked to student is accessed. Like INSTITUTION, CAREER, PROGRAM, PLAN.
Each User ID needs to be configured in the SCAR security for each of the data elements like CAREER, PROGRAM, PLAN etc.

b) Person Mask security, even if user have access to different data elements in the SACR for the configuration purpose, we can restrict the access to student data by using this configuration.
PERSON_SRCH - Delivered search view is used to control the security and part of many search records of the components in Campus solution.

In this case, User "Primary Permission" list needs to be configured in the Person Mask component and Person Mask process needs to be executed so that User can get the access to view Student information in the campus solution.

Set up SACR > Security > Secure Student Administration > Permission List > Demographic Data Access
Set up SACR > Security > Secure Student Administration > Process > Demographic Data Access

Since the HR Person records has Row level security attached for the PSQuery and Campus doesn't use the Rowlevel security model similar to HR. We cannot use the base Personal records in Campus to create the PSquery reports (unless security is replicated).

Core Person Model -- Record PS Query View for CS
ACCOMPLISHMENTS -- SCC_ACCOMP_QVW
ACCOM_DIAGNOSIS -- SCC_ACCOM_D_QVW
ACCOM_JOB_TASK -- SCC_ACCOM_T_QVW
ACCOM_OPTION -- SCC_ACCOM_O_QVW
ACCOM_REQUEST -- SCC_ACCOM_R_QVW
ADDRESSES -- ADDRESS_NPC_VW
AUDIOMETRIC_TST -- SCC_AUDIO_T_QVW
CITIZENSHIP -- SCC_CITIZEN_QVW
CITIZEN_PSSPRT -- SCC_CITZN_P_QVW
DISABILITY -- SCC_DISABLE_QVW
DIVERSITY -- SCC_DIVERS_QVW
DIVERS_ETHNIC -- SCC_DIV_ETH_QVW
DRIVERS_LIC -- SCC_DRIVERS_QVW
EMAIL_ADDRESSES -- SCC_EMAIL_QVW
EMERGENCY_CNTCT -- SCC_EMERG_C_QVW
EMERGENCY_PHONE -- SCC_EMERG_P_QVW
EYE_EXAM -- SCC_EYE_EXA_QVW
NAMES -- SCC_NAMES_QVW
PERSON -- PERSON_NPC_VW
PERS_DATA_EFFDT -- SCC_PER_EFF_QVW
PERS_DATA_CAN -- SCC_PDE_CAN_QVW
PERS_DATA_USA -- SCC_PDE_USA_QVW
PERS_NID -- SCC_PERS_NI_QVW
PERSON_PHONE -- SCC_PERS_PH_QVW
PHYSICAL_EXAM -- SCC_PHYS_EX_QVW
PUBLICATIONS -- SCC_PUBLICA_QVW
RESPIRATORY_EXM -- SCC_RESP_EX_QVW
VISA_PMT_DATA -- SCC_VISA_P_QVW
VISA_PMT_SUPPRT -- SCC_VISA_S_QVW 


Query Views to be used in place of core Person Model Tables with PeopleSoft
Query for Campus Solutions reporting needs.

Sending EMAILS in PeopleSoft

ending an email is very simple and easy concept, but there are so many different things that we need to keep in mind from best practices to new additional methods that can be useful in different purpose. I am trying to cover all the different aspects of sending emails, this will be Bible for sending emails in the PeopleSoft.

Best Practice
1)  Anything that works is not correct or best method to follow while writing the PeopleCode. First thing comes to the mind - SendMail function to send the email, it is an deprecated method and should not be used. Application Class MCFOutboundEmail needs to be used and it is the fully SMTP Protocol compliant to RFC 821/822. It uses Javamail functionality using this package.

Where to Configure
MCFOutBoundEmail() function uses the SMTP section of the Application Server's psappsrv.cfg file, and Process Scheduler's SMTP section of the psprcs.cfg file, just as the SendMail() function does. No special configuration is required for the MCFOutboundEmail() function to work.

Sample Code send Email
import PT_MCF_MAIL:*;
import PT_MCF_MAIL:MCFOutboundEmail;
import PT_MCF_MAIL:MCFEmail;
Local PT_MCF_MAIL:MCFOutboundEmail &email = create PT_MCF_MAIL:MCFOutboundEmail();
&email.Recipients = "FINQAGenUser10@ap6023fems.us.oracle.com";
&email.From = "";
&email.BCC = "";
&email.Subject = "MCF Outbound Email test";
&email.ReplyTo = "";
&email.Text = "Email Body - This is a Test of the MCF Outbound Email";
&res = &email.Send();

Best Performance for sending Bulk email  
 if you need to send several emails where content is specific to each email is different. Following code will help if you need to send email notification on the component peoplecode, as it make one SMTP connection to send all the emails and performance will be better.
import PT_MCF_MAIL:MCFOutboundEmail;
import PT_MCF_MAIL:MCFBodyPart;
import PT_MCF_MAIL:MCFMultipart;
import PT_MCF_MAIL:MCFMailUtil;
import PT_MCF_MAIL:SMTPSession;
Local integer &noOfMails = 20;
Local array of PT_MCF_MAIL:MCFOutboundEmail &mails;
Local PT_MCF_MAIL:MCFOutboundEmail &email;
Local integer &i;
Local PT_MCF_MAIL:SMTPSession &commonSession = create PT_MCF_MAIL:SMTPSession();
&commonSession.Server = "ap6023fems.us.oracle.com";
&commonSession.Port = 25;
&mails = CreateArray(&email);
For &i = 1 To &noOfMails
&email = &commonSession.CreateOutboundEmail();
&email.From = "sender@sender.com";
&email.Recipients = "receiver@receiver.com";
&email.BCC = "sender@sender.com";
&email.Subject = "Mail: " | NumberToString("2.0", &i) | " Hi there";
&email.Text = "Mail No: " | NumberToString("2.0", &i) | "One of multiple mails from from java";
&email.ContentType = "text/html";
&mails &i = &email;
End-For;

Local PT_MCF_MAIL:MCFMailUtil &util = create PT_MCF_MAIL:MCFMailUtil();
Local array of integer &allRes = &commonSession.SendAll(&mails);


Debugging 
In case if mails are not working for you and you are not able to figure out the issue.
You should look into smtp logs for more details and it will tell you exactly what is happening.
Before using the SMTP logs , you need to enable it.
For enabling SMTP logs for App Server you need to edit the app server configuration file :psappsrv.cfg.
Open the psappsrv.cfg file  and find “SMTP”.Look for SMTPTrace variable.
It will be SMTPTrace=0 initially.

Set the value of this variable SMTPTrace=1 and save the file.
This change does not require a Application Server boot and changes will be reflected immediately.
If you are using mail classes from Process Scheduler, you may need to set their trace also.
The psprcs.cfg file needs to be modified for this.
Open the file and look for SMTP and change the settings as done in the App server configuration file.
It will generate Trace file SMTP.LOG in LOGS Folder under
$PS_HOMEappservdomain_namelogsSMTP.LOG  for Appserver and $PS_HOMEappservprcsdomain_namelogsSMTP.LOG for Batchserver.


Creating an Email from Rich Text Editor Output
Using the ParseRichTextHTML - you can send the Rich Text with messages. Follwoing link has the complete details of the sample code.

Validating Email Address
ValidateAddress(addresslist) - Use the ValidateAddress method to validate a comma- or semicolon-separated list of email addresses. This method checks the syntax of the email address. It does not verify the domain name or the validity of the user name.
import PT_MCF_MAIL:*;
Local PT_MCF_MAIL:MCFMailUtil
&emailutil = create PT_MCF_MAIL:MCFMailUtil();
Local boolean &result = &emailutil.ValidateAddress(&email.Recipients);
Local array of string &bAddresses = &emailutil.badaddresses;
If (&result = False) Then
If (&bAddresses <> Null) Then
&i = 0;
While &bAddresses.Next(&i)
Warning ("Bad email address in input = " | &bAddresses [&i]);
End-While;
End-If;
End-If;

Creating an HTML Email with Images
Sending the HTML content and sending the Client Image may be requried. It looks good to send them in the email, Rather than the boring static emails which doesn't have much of details in it.
Following link has the details of the code to send the same.


2)  Notification Framework Another option is not explored very well by developers to send emails.  It can be easy way sending the email for many scenarios, where code can be simple and  provide configurable options.

The Notifications Framework uses the generic templates of the PeopleTools Workflow Technology, and the reader is urged to review the information in PeopleTools: Workflow Technology, “Using Notification Templates,” for a more comprehensive understanding.

 Entity Registry to generalize all notifications into a single structure. The architecture is modeled on a pluggable channel-based approach. Each notification type is supported by a dedicated channel that supports the idiosyncrasies of the particular notification type.

After creating Templates

PeopleTools, then select  Workflow, then selectNotifications, then selectGeneric Templates 

Notification Setup page (select Set Up SACR, then select System Administration, then select Utilities, then select Notifications, then select Notification Setup)

Notification Consumer Setup page (select Set Up SACR, then select System Administration, then select Utilities, then select Notifications, then select Notification Consumer Setup).

Write an AppClass that extends AbstractNotification class. The Consumer ID must be passed to the Super Class constructor
Implement the abstract method createNtfContext().
Implement the abstract method populateNtfContext(). This method populates the context create above with the necessary values 
Invoke the send(), remind(), or resend() methods.
Override the OnSuccess() and OnError() for custom behavior

Consultants can refer to the Delegated Access Application Class SCC_DA:NOTIFY as a reference for how to trigger the Notifications Framework as a consumer.

Enhanced advice printing for 9.1 Payroll

Enhanced advice printing for 9.1 Payroll

HRMS 9.1 offers some great new features, including enhanced paycheck self service for employees with direct access to their "printer friendly" advice.  Oracle did a nice job with this feature by storing the employee advice data as XML in the system, and then providing an XML publisher report to provide presentation to the employees.  There are a couple drawbacks to how Oracle has implemented this functionality however. 

Advice generation remains as an SQR process (ddp003.sqr).  Oracle has added the XML generation routines to this file for the self service addition (same deal in pay003.sqr for paychecks).  Unfortunately, they continue to use the SQR print routines for any batch advices/checks printed for employees by your payroll department.   This leaves your organization in the predicament of having to customize two different printing mechanisms if you need to modify anything on your advices layouts.

Secondly, these two options (centralized print, or self-service print) don't provide much flexibility if you want to have your departments print advice batches themselves instead of picking up print batches from central payroll.

The solution to both of these issues:  Make use of the XML advice data and XML publisher report for both operations with a bolt-on component. 

What this component will do:
- Allow payroll staff to create a single PDF file with multiple employee advices in it from the XML advices already stored in the database for employee self-service.  (Using the same data and report for both operations)
- Allow payroll to filter and select just the employees they want to print advices for.
- Allow payroll to sort the advices into the single PDF file by simply sorting the grid before they push the button.
- Uses row level security, so you can decentralize batch printing into the departments so they don't have to travel (or mail) printed advices to the rest of the organization.

This is what I did:

1:  Create a custom component and page.  (XX_ADV_BATCHPRINT)
2:  Create a view of the advice self service record for all employees.  (Sample SQL and pics below)
3:  Retrieve the XML data for the report just like the Employee self service does, but parse multiple employees into a single file so you have a single PDF with multiple advices in it.
4:  Use the new rowset MapBufRowToUserSortRow property in tools 8.5 and above to access user sort in advice generation.
5:  Although for actual paycheck printing (hopefully very minimal) you still have to deal with the SQR print routines, at least for advices, you now only have to deal with the XML publisher report for customizations.


Sample SQL for the grid record:

SELECT A.PRD_END_DT
  , J.DEPTID , PN.NAME_PSFORMAT, A.EMPLID, J.EMPL_RCD, J.JOBCODE, PD.LC_DEPTID_FINANCE
  , A.PYMT_DT, A.NET_PAY, isnull(DD.SUPPR_DDP_ADVICE  ,'N'), A.URL, A.ATTACHSYSFILENAME
  , A.PY_PSLP_FILEURLID, A.PY_SSP_BURSTRPT_ID, A.BURST_TEMPLATE_ID, A.PY_PSLP_SOURCEFILE
 FROM PS_PY_SS_PSLP_GDE A JOIN PS_JOB J ON (J.EMPLID = A.EMPLID
   AND J.EFFDT = (
 SELECT MAX(J1.EFFDT) FROM PS_JOB J1 WHERE J1.EMPLID = J.EMPLID  AND J1.EMPL_RCD = J.EMPL_RCD  AND J1.EFFDT <= getdate())  AND J.EFFSEQ = (
 SELECT MAX(J2.EFFSEQ)
  FROM PS_JOB J2
 WHERE J2.EMPLID = J.EMPLID
   AND J2.EMPL_RCD = J.EMPL_RCD
   AND J2.EFFDT = J.EFFDT
   AND J2.EFFSEQ >= 0)
   AND ((J.JOB_INDICATOR = 'P'  AND J.EMPL_STATUS IN ('A','L','P','S'))  OR J.EMPL_STATUS IN ('A','L','P','S')) ) JOIN PS_PERSON_NAME PN ON (PN.EMPLID = A.EMPLID
   AND PN.NAME_TYPE = 'PRI') LEFT OUTER JOIN PS_LCTC_PRFL LP ON (LP.EMPLID = A.EMPLID) LEFT OUTER JOIN PS_LCTC_PRFL_DATA PD ON (PD.LC_TMCD_USERID = LP.LC_TMCD_USERID
   AND PD.LC_TMCD_USER_RCD = J.EMPL_RCD
   AND PD.EFFDT = (
 SELECT MAX(PD1.EFFDT)
  FROM PS_LCTC_PRFL_DATA PD1
 WHERE PD1.LC_TMCD_USERID = PD.LC_TMCD_USERID
   AND PD1.LC_TMCD_USER_RCD = PD.LC_TMCD_USER_RCD)) LEFT OUTER JOIN PS_DIRECT_DEPOSIT DD ON (DD.EMPLID = A.EMPLID
   AND DD.EFFDT = (
 SELECT MAX(DD1.EFFDT)
  FROM PS_DIRECT_DEPOSIT DD1
 WHERE DD1.EMPLID = DD.EMPLID
   AND DD1.EFFDT <= getdate()))
 WHERE A.URL = 'record://PY_SSP_XML_DATA'
   AND A.PY_SS_PSLP_PRV_STA = 'ORIG'
   AND A.PY_SSP_BURSTRPT_ID = 'SSPUSADV'

Peoplecode for the print routine:

/*Button code to open a batch of advices in a single PDF*/
import PSXP_RPTDEFNMANAGER:*;

Declare Function Get_Field_Label PeopleCode FUNCLIB_BAS.FIELDNAME FieldFormula;
Declare Function DeleteLocalFile PeopleCode PSXPFUNCLIB.FUNCLIB FieldFormula;
Declare Function GetDirSeparator PeopleCode PSXPFUNCLIB.FUNCLIB FieldFormula;
Declare Function GetFileNameFromPath PeopleCode PSXPFUNCLIB.FUNCLIB FieldFormula;
Declare Function GetDirectoryFromPath PeopleCode PSXPFUNCLIB.FUNCLIB FieldFormula;
Declare Function GetFileExtension PeopleCode PSXPFUNCLIB.FUNCLIB FieldFormula;


Local Rowset &AdvBatRSL1;
Local PSXP_RPTDEFNMANAGER:ReportDefn &oRptDefn;
Local PSXP_RPTDEFNMANAGER:Utility &oUtil;

Local string &sDirSep, &sOutputDir, &sOutputFile, &sDataDir, &RptOutputDir, &xmlDir, &xmlFile, &xmlFileTemp, &URL;
Local number &Rtn, &Count, &OutDestFormat, &CurUS_Num, &RowCount;
Local File &File1, &File2;
Local boolean &AdvFoundToRun, &UserSorted, &ProcessRow;
Local string &ReportName, &TemplateId, &LanguageCd;
Local date &AsOfDate;

&AdvFoundToRun = False;
&UserSorted = False;
&ProcessRow = False;

&AdvBatRSL1 = GetLevel0()(1).GetRowset(Scroll.XX_ADV_BATCHVW);
&RowCount = &AdvBatRSL1.ActiveRowCount;
For &i = 1 To &AdvBatRSL1.ActiveRowCount
   If &AdvBatRSL1(&i).Selected And
         All(&AdvBatRSL1(&i).XX_ADV_BATCHVW.EMPLID.Value) Then
      &AdvFoundToRun = True;
      &TemplateId = &AdvBatRSL1(&i).XX_ADV_BATCHVW.BURST_TEMPLATE_ID.Value;
      &ReportName = &AdvBatRSL1(&i).XX_ADV_BATCHVW.PY_SSP_BURSTRPT_ID.Value;
      &AsOfDate = &AdvBatRSL1(&i).XX_ADV_BATCHVW.PAY_END_DT.Value;
      Break;
   End-If;
End-For;
If Not &AdvFoundToRun Then
   Error ("No advices were found to generate!");
End-If;

&LanguageCd = %Language_User;
&OutDestFormat = 2;

If &AdvBatRSL1.IsUserSorted(%Page | ".XX_ADV_BATCHVW_GRD") Then
   &UserSorted = True;
End-If;

&Count = 0;
&CurUS_Num = 0;
While &CurUS_Num < &RowCount
  
   For &i = 1 To &AdvBatRSL1.ActiveRowCount
      If &UserSorted Then
         &US_Index = &AdvBatRSL1.MapBufRowToUserSortRow(%Page | ".XX_ADV_BATCHVW_GRD", &i);
         If &US_Index = &CurUS_Num + 1 Then
            &ProcessRow = True;
            &CurUS_Num = &US_Index;
         Else
            &ProcessRow = False;
         End-If;
      Else
         &ProcessRow = True;
         &CurUS_Num = &CurUS_Num + 1;
      End-If;
      If &AdvBatRSL1(&i).Selected And
            &ProcessRow Then
         &Count = &Count + 1;
         If &Count = 1 Then /*First file*/
            /* detect system directory separator */
            &sDirSep = GetDirSeparator();
           
            /* output directory */
            &xmlDir = GetEnv("PS_SERVDIR") | &sDirSep | "files" | &sDirSep | "XMLP" | &sDirSep | UuidGen();
            CreateDirectory(&xmlDir, %FilePath_Absolute);
           
            &xmlFile = &xmlDir | &sDirSep | "AdviceBatch.xml";
            /* start logging */
            WriteToLog(%ApplicationLogFence_Level1, "*** XMLP XX Batch Advice PDF: " | String(%Datetime) | " ***");
            WriteToLog(%ApplicationLogFence_Level1, "XML File      = " | &xmlFile);
           
            &File1 = GetFile(&xmlFile, "N", %FilePath_Absolute);
         End-If;
         &URL = &AdvBatRSL1(&i).XX_ADV_BATCHVW.URL.Value;
         &xmlFileTemp = &xmlDir | &sDirSep | &AdvBatRSL1(&i).XX_ADV_BATCHVW.ATTACHSYSFILENAME.Value;
         REM WinMessage(&xmlFileTemp, 0);
         /* Download XML data from attachment table*/
         &Rtn = GetAttachment(&URL, &AdvBatRSL1(&i).XX_ADV_BATCHVW.ATTACHSYSFILENAME.Value, &xmlFileTemp);
         If &Rtn <> %Attachment_Success Then
            /*MessageBox(0, "", 158, 653, "Error retrieving file from database");*/
            Error MsgGet(235, 5, "Error downloading file from database");
         End-If;
         REM WinMessage("success", 0);
         &File2 = GetFile(&xmlFileTemp, "R", %FilePath_Absolute);
         If &File2.IsOpen Then
            While &File2.ReadLine(&FileString)
               /*Strip off the opening and closing advice tags (except for first advice, keep opening tag */
               If (&Count = 1 And
                     RTrim(&FileString) <> "</US_ADVICE>") Or
                     (&Count > 1 And
                        (RTrim(&FileString) <> "<?xml version=""1.0"" encoding=""Windows-1252""?>" And
                           RTrim(&FileString) <> "<US_ADVICE>" And
                           RTrim(&FileString) <> "</US_ADVICE>")) Then
                  &File1.WriteLine(&FileString);
               End-If;
            End-While;
         End-If;
         &File2.Close();
         REM     &File2.Delete();
         try
            /* cleanup */
            DeleteLocalFile(&xmlFileTemp, %FilePath_Absolute);
           
         catch Exception &Dummy2
            WriteToLog(%ApplicationLogFence_Error, &Err2.ToString());
         end-try;
        
      End-If;
     
     
   End-For;
End-While;
&File1.WriteLine("</US_ADVICE>"); /*Add closing tag to XML file */
&File1.Close();
try
   /* get the report defn object */
   &oRptDefn = create PSXP_RPTDEFNMANAGER:ReportDefn(&ReportName);
   &oRptDefn.Get();
  
   /* &oRptDefn.OutDestination = &xmlDir; */
   &oRptDefn.SetRuntimeDataXMLFile(&xmlFile);
   &oRptDefn.ProcessReport(&TemplateId, &LanguageCd, &AsOfDate, &oRptDefn.GetOutDestFormatString(&OutDestFormat));
   /* &sFileExt = GetFileExtension(&sOutputFormat); */
   CommitWork();
  
   /* display the output */
   &oRptDefn.DisplayOutput();
  
catch Exception &Err
   WriteToLog(%ApplicationLogFence_Error, &Err.ToString());
end-try;

try
   /* cleanup */
   DeleteLocalFile(&xmlFile, %FilePath_Absolute);
   RemoveDirectory(&xmlDir, %FilePath_Absolute + %Remove_Subtree);
  
catch Exception &Dummy
   WriteToLog(%ApplicationLogFence_Error, &Err.ToString());
end-try;


Hopefully this will be of some use to others.  Our organization is less than 2000 people, with much less then that actually still receiving a printed advice.  I don't know how performant this same process would work for massive batches, but it works great for a few hundred into a single PDF.

Cloud computing Architecture: