001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.ar.batch.service.impl;
017    
018    import java.awt.Color;
019    import java.io.BufferedOutputStream;
020    import java.io.ByteArrayOutputStream;
021    import java.io.File;
022    import java.io.FileOutputStream;
023    import java.io.IOException;
024    import java.io.PrintWriter;
025    import java.text.SimpleDateFormat;
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.Map;
029    
030    import org.apache.commons.lang.StringUtils;
031    import org.apache.log4j.Logger;
032    import org.kuali.kfs.module.ar.ArConstants;
033    import org.kuali.kfs.module.ar.batch.service.LockboxService;
034    import org.kuali.kfs.module.ar.businessobject.AccountsReceivableDocumentHeader;
035    import org.kuali.kfs.module.ar.businessobject.CashControlDetail;
036    import org.kuali.kfs.module.ar.businessobject.Lockbox;
037    import org.kuali.kfs.module.ar.businessobject.SystemInformation;
038    import org.kuali.kfs.module.ar.dataaccess.LockboxDao;
039    import org.kuali.kfs.module.ar.document.CashControlDocument;
040    import org.kuali.kfs.module.ar.document.CustomerInvoiceDocument;
041    import org.kuali.kfs.module.ar.document.PaymentApplicationDocument;
042    import org.kuali.kfs.module.ar.document.service.AccountsReceivableDocumentHeaderService;
043    import org.kuali.kfs.module.ar.document.service.CashControlDocumentService;
044    import org.kuali.kfs.module.ar.document.service.PaymentApplicationDocumentService;
045    import org.kuali.kfs.module.ar.document.service.SystemInformationService;
046    import org.kuali.kfs.sys.KFSConstants;
047    import org.kuali.kfs.sys.context.SpringContext;
048    import org.kuali.rice.kew.docsearch.service.SearchableAttributeProcessingService;
049    import org.kuali.rice.kew.exception.WorkflowException;
050    import org.kuali.rice.kim.bo.Person;
051    import org.kuali.rice.kim.service.PersonService;
052    import org.kuali.rice.kns.UserSession;
053    import org.kuali.rice.kns.service.BusinessObjectService;
054    import org.kuali.rice.kns.service.DataDictionaryService;
055    import org.kuali.rice.kns.service.DateTimeService;
056    import org.kuali.rice.kns.service.DocumentService;
057    import org.kuali.rice.kns.util.GlobalVariables;
058    import org.kuali.rice.kns.util.KualiDecimal;
059    import org.kuali.rice.kns.util.ObjectUtils;
060    import org.springframework.transaction.annotation.Transactional;
061    
062    import com.lowagie.text.Chunk;
063    import com.lowagie.text.DocumentException;
064    import com.lowagie.text.Font;
065    import com.lowagie.text.FontFactory;
066    import com.lowagie.text.PageSize;
067    import com.lowagie.text.Paragraph;
068    import com.lowagie.text.pdf.PdfWriter;
069    
070    /**
071     *  
072     * Lockbox Iterators are sorted by processedInvoiceDate and batchSequenceNumber. 
073     * Potentially there could be many batches on the same date. 
074     * For each set of records with the same processedInvoiceDate and batchSequenceNumber, 
075     * there will be one Cash-Control document. Each record within this set will create one Application document.
076     * 
077     */
078    
079    @Transactional
080    public class LockboxServiceImpl implements LockboxService {
081        private static Logger LOG = org.apache.log4j.Logger.getLogger(LockboxServiceImpl.class);;
082    
083        private PersonService<Person> personService;
084        private DocumentService documentService;
085        private SystemInformationService systemInformationService;
086        private AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService;
087        private CashControlDocumentService cashControlDocumentService;
088        private PaymentApplicationDocumentService payAppDocService;
089        private DataDictionaryService dataDictionaryService;
090        private DateTimeService dateTimeService;
091        private BusinessObjectService boService;
092        
093        private LockboxDao lockboxDao;
094        private String reportsDirectory;
095        
096        public boolean processLockbox() throws WorkflowException {
097    
098            //  create the pdf doc
099            com.lowagie.text.Document pdfdoc = getPdfDoc();
100            
101            //  this giant try/catch is to make sure that something gets written to the 
102            // report.  please dont use it for specific exception handling, rather nest 
103            // new try/catch handlers inside this.
104            try {
105                
106            Iterator<Lockbox> itr = lockboxDao.getAllLockboxes();
107            Lockbox ctrlLockbox = new Lockbox();
108            CashControlDocument cashControlDocument = null;
109            boolean anyRecordsFound = false;
110            while (itr.hasNext()) {
111                anyRecordsFound = true;
112                Lockbox lockbox = (Lockbox)itr.next();
113                LOG.info("LOCKBOX: '" + lockbox.getLockboxNumber() + "'");
114    
115                //  retrieve the processingOrg (system information) for this lockbox number
116                SystemInformation sysInfo = systemInformationService.getByLockboxNumberForCurrentFiscalYear(lockbox.getLockboxNumber());
117                String initiator = sysInfo.getFinancialDocumentInitiatorIdentifier();
118                LOG.info("   using SystemInformation: '" + sysInfo.toString() + "'");
119                LOG.info("   using Financial Document Initiator ID: '" + initiator + "'");
120                
121                //  puke if the initiator stored in the systemInformation table is no good
122                Person person = getPersonService().getPerson(initiator);
123                if (person == null) {
124                    LOG.warn("   could not find [" + initiator + "] when searching by PrincipalID, so trying to find as a PrincipalName.");
125                    person = getPersonService().getPersonByPrincipalName(initiator);
126                    if (person == null) {
127                        LOG.error("Financial Document Initiator ID [" + initiator + "] specified in SystemInformation [" + sysInfo.toString() + "] for Lockbox Number " + lockbox.getLockboxNumber() + " is not present in the system as either a PrincipalID or a PrincipalName.");
128                        throw new RuntimeException("Financial Document Initiator ID [" + initiator + "] specified in SystemInformation [" + sysInfo.toString() + "] for Lockbox Number " + lockbox.getLockboxNumber() + " is not present in the system as either a PrincipalID or a PrincipalName.");
129                    }
130                    else {
131                        LOG.info("   found [" + initiator + "] in the system as a PrincipalName.");
132                    }
133                }
134                else {
135                    LOG.info("   found [" + initiator + "] in the system as a PrincipalID.");
136                }
137                
138                //  masquerade as the person indicated in the systemInformation
139                GlobalVariables.clear();
140                GlobalVariables.setUserSession(new UserSession(person.getPrincipalName()));
141    
142                if (lockbox.compareTo(ctrlLockbox) != 0) {
143                    // If we made it in here, then we have hit a different batchSequenceNumber and processedInvoiceDate.
144                    // When this is the case, we create a new cashcontroldocument and start tacking subsequent lockboxes on 
145                    // to the current cashcontroldocument as cashcontroldetails.
146                    LOG.info("New Lockbox batch");
147    
148                    //  we're creating a new cashcontrol, so if we have an old one, we need to route it
149                    if (cashControlDocument != null) {
150                        LOG.info("   routing cash control document.");
151                        try {
152                            documentService.routeDocument(cashControlDocument, "Routed by Lockbox Batch process.", null);
153                        }
154                        catch (Exception e) {
155                            LOG.error("A Exception was thrown while trying to route the CashControl document.", e);
156                            throw new RuntimeException("A Exception was thrown while trying to route the CashControl document.", e);
157                        }
158                    }
159    
160                    //  create a new CashControl document
161                    LOG.info("Creating new CashControl document for invoice: " + lockbox.getFinancialDocumentReferenceInvoiceNumber() + ".");
162                    try {
163                        cashControlDocument = (CashControlDocument)documentService.getNewDocument(KFSConstants.FinancialDocumentTypeCodes.CASH_CONTROL);
164                    }
165                    catch (Exception e) {
166                        LOG.error("A Exception was thrown while trying to initiate a new CashControl document.", e);
167                        throw new RuntimeException("A Exception was thrown while trying to initiate a new CashControl document.", e);
168                    }
169                    LOG.info("   CashControl documentNumber == '" + cashControlDocument.getDocumentNumber() + "'");
170                    
171                    //  write the batch group header to the report
172                    writeBatchGroupSectionTitle(pdfdoc, lockbox.getBatchSequenceNumber().toString(), lockbox.getProcessedInvoiceDate(), 
173                            cashControlDocument.getDocumentNumber());
174                    
175                    cashControlDocument.setCustomerPaymentMediumCode(lockbox.getCustomerPaymentMediumCode());
176                    if(ObjectUtils.isNotNull(lockbox.getBankCode())) {
177                        String bankCode = lockbox.getBankCode();
178                        cashControlDocument.setBankCode(bankCode);
179                    } 
180                    cashControlDocument.getDocumentHeader().setDocumentDescription(ArConstants.LOCKBOX_DOCUMENT_DESCRIPTION + lockbox.getLockboxNumber());
181    
182                    //  setup the AR header for this CashControl doc
183                    LOG.info("   creating AR header for customer: [" + lockbox.getCustomerNumber() + "] and ProcessingOrg: " + sysInfo.getProcessingChartOfAccountCode() + "-" + sysInfo.getProcessingOrganizationCode() + ".");
184                    AccountsReceivableDocumentHeader arDocHeader;
185                    try {
186                        arDocHeader = accountsReceivableDocumentHeaderService.getNewAccountsReceivableDocumentHeader(
187                                sysInfo.getProcessingChartOfAccountCode(), sysInfo.getProcessingOrganizationCode());
188                    }
189                    catch (Exception e) {
190                        LOG.error("An Exception was thrown while trying to create a new AccountsReceivableDocumentHeader for the current user: '" + person.getPrincipalName() + "'.", e);
191                        throw new RuntimeException("An Exception was thrown while trying to create a new AccountsReceivableDocumentHeader for the current user: '" + person.getPrincipalName() + "'.", e);
192                    }
193                    arDocHeader.setDocumentNumber(cashControlDocument.getDocumentNumber());
194                    arDocHeader.setCustomerNumber(lockbox.getCustomerNumber());
195                    cashControlDocument.setAccountsReceivableDocumentHeader(arDocHeader);
196                } 
197                // set our control lockbox as the current lockbox and create details.
198                ctrlLockbox = lockbox;
199                
200                //  write the lockbox detail line to the report
201                writeLockboxRecordLine(pdfdoc, lockbox.getLockboxNumber(), lockbox.getCustomerNumber(), lockbox.getFinancialDocumentReferenceInvoiceNumber(), 
202                        lockbox.getInvoicePaidOrAppliedAmount(), lockbox.getCustomerPaymentMediumCode(), lockbox.getBankCode());
203    
204                //  skip zero-dollar-amount lockboxes
205                if (lockbox.getInvoicePaidOrAppliedAmount().isZero()) {
206                    LOG.warn("   lockbox has a zero dollar amount, so we're skipping it.");
207                    writeSummaryDetailLine(pdfdoc, "ZERO-DOLLAR LOCKBOX - NO FURTHER PROCESSING");
208                    deleteProcessedLockboxEntry(lockbox);
209                    continue;
210                }
211                if (lockbox.getInvoicePaidOrAppliedAmount().isLessThan(KualiDecimal.ZERO)) {
212                    LOG.warn("   lockbox has a negative dollar amount, so we're skipping it.");
213                    writeCashControlDetailLine(pdfdoc, lockbox.getInvoicePaidOrAppliedAmount(), "SKIPPED");
214                    writeSummaryDetailLine(pdfdoc, "NEGATIVE-DOLLAR LOCKBOX - NO FURTHER PROCESSING - LOCKBOX ENTRY NOT DELETED");
215                    continue;
216                }
217                
218                //  create a new cashcontrol detail
219                CashControlDetail detail = new CashControlDetail();
220                detail.setCustomerNumber(lockbox.getCustomerNumber());
221                detail.setFinancialDocumentLineAmount(lockbox.getInvoicePaidOrAppliedAmount());
222                detail.setCustomerPaymentDate(lockbox.getProcessedInvoiceDate());
223                detail.setCustomerPaymentDescription("Lockbox Remittance  " +lockbox.getFinancialDocumentReferenceInvoiceNumber());
224    
225                //  add it to the document
226                LOG.info("   creating detail for $" + lockbox.getInvoicePaidOrAppliedAmount() + " with invoiceDate: " + lockbox.getProcessedInvoiceDate());
227                try {
228                    cashControlDocumentService.addNewCashControlDetail(ArConstants.LOCKBOX_DOCUMENT_DESCRIPTION, cashControlDocument, detail);
229                }
230                catch (Exception e) {
231                    LOG.error("A Exception was thrown while trying to create a new CashControl detail.", e);
232                    throw new RuntimeException("A Exception was thrown while trying to create a new CashControl detail.", e);
233                }
234    
235                //  retrieve the docNumber of the generated payapp
236                String payAppDocNumber = detail.getReferenceFinancialDocumentNumber();
237                LOG.info("   new PayAppDoc was created: " + payAppDocNumber + ".");
238                
239                String invoiceNumber = lockbox.getFinancialDocumentReferenceInvoiceNumber();
240                LOG.info("   lockbox references invoice number [" + invoiceNumber + "].");
241                
242                //  before release 3, during dev, sometimes invoice numbers we got from the functional 
243                // testing dataset were old FIS style, and not compatible with KFS
244                boolean invoiceNumberNotParsable = false;
245                if (StringUtils.isBlank(invoiceNumber)) {
246                    invoiceNumberNotParsable = true;
247                }
248                else {
249                    try {
250                        Integer.parseInt(invoiceNumber);
251                    } catch (Exception e) {
252                        invoiceNumberNotParsable = true;
253                    }
254                }
255                
256                //  if thats the case, dont even bother looking for an invoice, just save the CashControl
257                if (invoiceNumberNotParsable) {
258                    LOG.info("   invoice number [" + invoiceNumber + "] isnt in expected KFS format, so cannot load the original invoice.");
259                    detail.setCustomerPaymentDescription(ArConstants.LOCKBOX_REMITTANCE_FOR_INVALID_INVOICE_NUMBER +lockbox.getFinancialDocumentReferenceInvoiceNumber());
260                    try {
261                        documentService.saveDocument(cashControlDocument);
262                    }
263                    catch (Exception e) {
264                        LOG.error("A Exception was thrown while trying to save the CashControl document.", e);
265                        throw new RuntimeException("A Exception was thrown while trying to save the CashControl document.", e);
266                    }
267                    
268                    //  write the detail and payapp lines to the report
269                    routePayAppWithoutBusinessRules(payAppDocNumber, "CREATED & SAVED by Lockbox batch");
270                    writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription());
271                    writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), "CREATED & SAVED");
272                    writeSummaryDetailLine(pdfdoc, "INVOICE NUMBER NOT PARSEABLE");
273                    //  delete the lockbox now we're done with it
274                    deleteProcessedLockboxEntry(lockbox);
275                    continue;
276                }
277                
278                //  check to see if the invoice indicated exists, and if not, then save the CashControl and move on
279                if (!documentService.documentExists(invoiceNumber)) {
280                    LOG.info("   invoice number [" + invoiceNumber + "] does not exist in system, so cannot load the original invoice.");
281                    detail.setCustomerPaymentDescription(ArConstants.LOCKBOX_REMITTANCE_FOR_INVALID_INVOICE_NUMBER +lockbox.getFinancialDocumentReferenceInvoiceNumber());
282                    try {
283                        documentService.saveDocument(cashControlDocument);
284                    }
285                    catch (Exception e) {
286                        LOG.error("A Exception was thrown while trying to save the CashControl document.", e);
287                        throw new RuntimeException("A Exception was thrown while trying to save the CashControl document.", e);
288                    }
289                    routePayAppWithoutBusinessRules(payAppDocNumber, "CREATED & SAVED by Lockbox batch");
290                    writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription());
291                    writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), "CREATED & SAVED");
292                    writeSummaryDetailLine(pdfdoc, "INVOICE DOESNT EXIST");
293                    //  delete the lockbox now we're done with it
294                    deleteProcessedLockboxEntry(lockbox);
295                    continue;
296                }
297    
298                //  load up the specified invoice from the lockbox
299                LOG.info("   loading invoice number [" + invoiceNumber + "].");
300                CustomerInvoiceDocument customerInvoiceDocument;
301                try {
302                    customerInvoiceDocument = (CustomerInvoiceDocument)documentService.getByDocumentHeaderId(invoiceNumber);
303                }
304                catch (Exception e) {
305                    LOG.error("A Exception was thrown while trying to load invoice #" + invoiceNumber + ".", e);
306                    throw new RuntimeException("A Exception was thrown while trying to load invoice #" + invoiceNumber + ".", e);
307                }
308                
309                //  if the invoice is already closed, then just save the CashControl and move on
310                writeInvoiceDetailLine(pdfdoc, invoiceNumber, customerInvoiceDocument.isOpenInvoiceIndicator(), 
311                        customerInvoiceDocument.getCustomer().getCustomerNumber(), customerInvoiceDocument.getOpenAmount());
312                if (!customerInvoiceDocument.isOpenInvoiceIndicator()) {
313                    LOG.info("   invoice is already closed, so saving CashControl doc and moving on.");
314                    detail.setCustomerPaymentDescription(ArConstants.LOCKBOX_REMITTANCE_FOR_CLOSED_INVOICE_NUMBER +lockbox.getFinancialDocumentReferenceInvoiceNumber());
315                    try {
316                        documentService.saveDocument(cashControlDocument);
317                    }
318                    catch (Exception e) {
319                        LOG.error("A Exception was thrown while trying to save the CashControl document.", e);
320                        throw new RuntimeException("A Exception was thrown while trying to save the CashControl document.", e);
321                    }
322                    routePayAppWithoutBusinessRules(payAppDocNumber, "CREATED & SAVED by Lockbox batch");
323                    writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription());
324                    writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), "CREATED & SAVED");
325                    writeSummaryDetailLine(pdfdoc, "INVOICE ALREADY CLOSED");
326                    deleteProcessedLockboxEntry(lockbox);
327                    continue;
328                }
329                
330                boolean autoApprove = customerInvoiceDocument.getOpenAmount().equals(lockbox.getInvoicePaidOrAppliedAmount());
331                String annotation = "CREATED & SAVED";
332                
333                //  if the lockbox amount matches the invoice amount, then create, save and approve a PayApp, and then 
334                // mark the invoice
335                if (autoApprove){
336                    LOG.info("   lockbox amount matches invoice total document amount [" + customerInvoiceDocument.getTotalDollarAmount() + "].");
337                    annotation = "CREATED, SAVED, and BLANKET APPROVED";
338                    
339                    //  load up the PayApp document that was created
340                    LOG.info("   loading the generated PayApp [" + payAppDocNumber + "], so we can route or approve it.");
341                    PaymentApplicationDocument payAppDoc;
342                    try {
343                        payAppDoc = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(payAppDocNumber);
344                    }
345                    catch (Exception e) {
346                        LOG.error("A Exception was thrown while trying to load PayApp #" + payAppDocNumber + ".", e);
347                        throw new RuntimeException("A Exception was thrown while trying to load PayApp #" + payAppDocNumber + ".", e);
348                    }
349    
350                    //  create paidapplieds on the PayApp doc for all the Invoice details
351                    LOG.info("   attempting to create paidApplieds on the PayAppDoc for every detail on the invoice.");
352                    payAppDoc = payAppDocService.createInvoicePaidAppliedsForEntireInvoiceDocument(customerInvoiceDocument, payAppDoc);
353                    LOG.info("   PayAppDoc has TotalApplied of " + payAppDoc.getTotalApplied() + " for a Control Balance of " + payAppDoc.getTotalFromControl() + ".");
354                    
355                    //  Save and approve the payapp doc
356                    LOG.info("   attempting to blanketApprove the PayApp Doc.");
357                    try {
358                        
359                        documentService.blanketApproveDocument(payAppDoc, "Automatically approved by Lockbox batch job.", null);
360                    }
361                    catch (Exception e) {
362                        LOG.error("A Exception was thrown while trying to blanketApprove PayAppDoc #" + payAppDoc.getDocumentNumber() + ".", e);
363                        throw new RuntimeException("A Exception was thrown while trying to blanketApprove PayAppDoc #" + payAppDoc.getDocumentNumber() + ".", e);
364                    }
365    
366                    //  write the report details 
367                    writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription());
368                    writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), annotation);
369                    writeSummaryDetailLine(pdfdoc, "LOCKBOX AMOUNT MATCHES INVOICE OPEN AMOUNT");
370                }
371                else {
372                    LOG.info("   lockbox amount does NOT match invoice total document amount [" + customerInvoiceDocument.getTotalDollarAmount() + "].");
373                    routePayAppWithoutBusinessRules(payAppDocNumber, "CREATED & SAVED by Lockbox batch");
374    
375                    //  write the report details 
376                    writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription());
377                    writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), annotation);
378                    if (lockbox.getInvoicePaidOrAppliedAmount().isLessThan(customerInvoiceDocument.getOpenAmount())) {
379                        writeSummaryDetailLine(pdfdoc, "LOCKBOX UNDERPAID INVOICE");
380                    }
381                    else {
382                        writeSummaryDetailLine(pdfdoc, "LOCKBOX OVERPAID INVOICE");
383                    }
384                }
385                
386                //  save the cashcontrol, which saves any changes to the details
387                detail.setCustomerPaymentDescription(ArConstants.LOCKBOX_REMITTANCE_FOR_INVOICE_NUMBER +lockbox.getFinancialDocumentReferenceInvoiceNumber());
388                LOG.info("   saving cash control document.");
389                try {
390                    documentService.saveDocument(cashControlDocument);
391                }
392                catch (Exception e) {
393                    LOG.error("A Exception was thrown while trying to save the CashControl document.", e);
394                    throw new RuntimeException("A Exception was thrown while trying to save the CashControl document.", e);
395                }
396                
397                //  delete the lockbox now we're done with it
398                deleteProcessedLockboxEntry(lockbox);
399            }
400            
401            //  if we have a cashControlDocument here, then it needs to be routed, its the last one
402            if (cashControlDocument != null) {
403                LOG.info("   routing cash control document.");
404                try {
405                    documentService.routeDocument(cashControlDocument, "Routed by Lockbox Batch process.", null);
406                }
407                catch (Exception e) {
408                    LOG.error("A Exception was thrown while trying to route the CashControl document.", e);
409                    throw new RuntimeException("A Exception was thrown while trying to route the CashControl document.", e);
410                }
411            }
412    
413            //  if no records were found, write something useful to the report
414            if (!anyRecordsFound) {
415                writeDetailLine(pdfdoc, "NO LOCKBOX RECORDS WERE FOUND");
416            }
417            
418            //  this annoying all-encompassing try/catch is here to make sure that the report gets 
419            // written.  without it, if anything goes wrong, the report will end up a zero-byte document.
420            }
421            catch (Exception e) {
422                writeDetailLine(pdfdoc, "AN EXCEPTION OCCURRED:");
423                writeDetailLine(pdfdoc, "");
424                writeDetailLine(pdfdoc, e.getMessage());
425                writeDetailLine(pdfdoc, "");
426                writeExceptionStackTrace(pdfdoc, e);
427            }
428            
429            //  spool the report
430            pdfdoc.close();
431            
432            return true;
433    
434        }
435    
436        protected void routePayAppWithoutBusinessRules(String payAppDocNumber, String annotation) {
437    
438            //  load up the PayApp document that was created
439            LOG.info("   loading the generated PayApp [" + payAppDocNumber + "], so we can route or approve it.");
440            PaymentApplicationDocument payAppDoc;
441            try {
442                payAppDoc = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(payAppDocNumber);
443            }
444            catch (Exception e) {
445                LOG.error("A Exception was thrown while trying to load PayApp #" + payAppDocNumber + ".", e);
446                throw new RuntimeException("A Exception was thrown while trying to load PayApp #" + payAppDocNumber + ".", e);
447            }
448    
449            //  route without business rules 
450            LOG.info("   attempting to route without business rules the PayApp Doc.");
451            try {
452               payAppDoc.getDocumentHeader().getWorkflowDocument().routeDocument(annotation);
453                final SearchableAttributeProcessingService searchableAttributeProcessingService = SpringContext.getBean(SearchableAttributeProcessingService.class);
454                searchableAttributeProcessingService.indexDocument(new Long(payAppDoc.getDocumentNumber()));
455            }
456            catch (Exception e) {
457                LOG.error("A Exception was thrown while trying to route (without business rules) PayAppDoc #" + payAppDoc.getDocumentNumber() + ".", e);
458                throw new RuntimeException("A Exception was thrown while trying to route (without business rules) PayAppDoc #" + payAppDoc.getDocumentNumber() + ".", e);
459            }
460        }
461        
462        protected void deleteProcessedLockboxEntry(Lockbox lockboxEntry) {
463            Map<String,Object> pkMap = new HashMap<String,Object>();
464            pkMap.put("invoiceSequenceNumber", lockboxEntry.getInvoiceSequenceNumber());
465            boService.deleteMatching(Lockbox.class, pkMap);
466        }
467        
468        protected com.lowagie.text.Document getPdfDoc() {
469            
470            String reportDropFolder = reportsDirectory + "/" + ArConstants.Lockbox.LOCKBOX_REPORT_SUBFOLDER + "/";
471            String fileName = ArConstants.Lockbox.BATCH_REPORT_BASENAME + "_" +  
472                new SimpleDateFormat("yyyyMMdd_HHmmssSSS").format(dateTimeService.getCurrentDate()) + ".pdf";
473           
474            //  setup the writer
475            File reportFile = new File(reportDropFolder + fileName);
476            FileOutputStream fileOutStream;
477            try {
478                fileOutStream = new FileOutputStream(reportFile);
479            }
480            catch (IOException e) {
481                LOG.error("IOException thrown when trying to open the FileOutputStream.", e);
482                throw new RuntimeException("IOException thrown when trying to open the FileOutputStream.", e);
483            }
484            BufferedOutputStream buffOutStream = new BufferedOutputStream(fileOutStream);
485            
486            com.lowagie.text.Document pdfdoc = new com.lowagie.text.Document(PageSize.LETTER, 54, 54, 72, 72);
487            try {
488                PdfWriter.getInstance(pdfdoc, buffOutStream);
489            }
490            catch (DocumentException e) {
491                LOG.error("iText DocumentException thrown when trying to start a new instance of the PdfWriter.", e);
492                throw new RuntimeException("iText DocumentException thrown when trying to start a new instance of the PdfWriter.", e);
493            }
494            
495            pdfdoc.open();
496            
497            return pdfdoc;
498        }
499    
500        protected String rightPad(String valToPad, int sizeToPadTo) {
501            return rightPad(valToPad, sizeToPadTo, " ");
502        }
503        
504        protected String rightPad(String valToPad, int sizeToPadTo, String padChar) {
505            if (StringUtils.isBlank(valToPad)) {
506                return StringUtils.repeat(padChar, sizeToPadTo);
507            }
508            if (valToPad.length() >= sizeToPadTo) {
509                return valToPad;
510            }
511            return valToPad + StringUtils.repeat(padChar, sizeToPadTo - valToPad.length());
512        }
513        
514        protected void writeBatchGroupSectionTitle(com.lowagie.text.Document pdfDoc, String batchSeqNbr, java.sql.Date procInvDt, String cashControlDocNumber) {
515            Font font = FontFactory.getFont(FontFactory.COURIER, 10, Font.BOLD);
516            
517            String lineText = "CASHCTL " + rightPad(cashControlDocNumber, 12) + " " + 
518                                "BATCH GROUP: " + rightPad(batchSeqNbr, 5) + " " + 
519                                rightPad((procInvDt == null ? "NONE" : procInvDt.toString()), 35);
520            
521            Paragraph paragraph = new Paragraph();
522            paragraph.setAlignment(com.lowagie.text.Element.ALIGN_LEFT);
523            Chunk chunk = new Chunk(lineText, font);
524            chunk.setBackground(Color.LIGHT_GRAY, 5, 5, 5, 5);
525            paragraph.add(chunk);
526            
527            //  blank line
528            paragraph.add(new Chunk("", font));
529            
530            try {
531                pdfDoc.add(paragraph);
532            }
533            catch (DocumentException e) {
534                LOG.error("iText DocumentException thrown when trying to write content.", e);
535                throw new RuntimeException("iText DocumentException thrown when trying to write content.", e);
536            }
537        }
538        
539        protected void writeLockboxRecordLine(com.lowagie.text.Document pdfDoc, String lockboxNumber, String customerNumber, String invoiceNumber, 
540                KualiDecimal invoiceTotalAmount, String paymentMediumCode, String bankCode) {
541            
542            writeDetailLine(pdfDoc, StringUtils.repeat("-", 100));
543            
544            StringBuilder sb = new StringBuilder();
545            sb.append("   ");                                                        // 3:   1 - 3
546            sb.append("LOCKBOX: " + rightPad(lockboxNumber, 10) + " ");              // 20:  4 - 23    
547            sb.append("CUST: " + rightPad(customerNumber, 9) + " ");                 // 15:  24 - 38 
548            sb.append("INV: " + rightPad(invoiceNumber, 10) + " ");                  // 16:  39 - 55 
549            sb.append(StringUtils.repeat(" ", 28));                                  // 28:  56 - 83 
550            sb.append("AMT: " + rightPad(invoiceTotalAmount.toString(), 11) + " ");  // 17:  84 - 100
551            
552            writeDetailLine(pdfDoc, sb.toString());
553        }
554        
555        protected void writeInvoiceDetailLine(com.lowagie.text.Document pdfDoc, String invoiceNumber, boolean open, String customerNumber, KualiDecimal openAmount) {
556            
557            StringBuilder sb = new StringBuilder();
558            sb.append("   ");                                                        // 3:   1 - 3
559            sb.append("INVOICE: " + rightPad(invoiceNumber, 10) + " ");              // 20:  4 - 23    
560            sb.append("CUST: " + rightPad(customerNumber, 9) + " ");                 // 15:  24 - 38 
561            if (open) {
562                sb.append(rightPad("OPEN", 16) + " ");                               // 16:  39 - 55 
563            }
564            else {
565                sb.append(rightPad("CLOSED", 16) + " ");                             // 16:  39 - 55 
566            }
567            sb.append(StringUtils.repeat(" ", 22));                                  // 28:  56 - 83 
568            sb.append("OPEN AMT: " + rightPad(openAmount.toString(), 11) + " ");  // 17:  84 - 100
569            
570            writeDetailLine(pdfDoc, sb.toString());
571        }
572        
573        protected void writeCashControlDetailLine(com.lowagie.text.Document pdfDoc, KualiDecimal amount, String description) {
574            
575            StringBuilder sb = new StringBuilder();
576            sb.append("   ");                                                        // 3:   1 - 3
577            sb.append("CASHCTL DTL: " + rightPad(description, 66) + " ");            // 80:  4 - 83    
578            sb.append("AMT: " + rightPad(amount.toString(), 11) + " ");              // 17:  84 - 100
579            
580            writeDetailLine(pdfDoc, sb.toString());
581        }
582        
583        protected void writeSummaryDetailLine(com.lowagie.text.Document pdfDoc, String summary) {
584            writeDetailLine(pdfDoc, "   " + summary);
585        }
586        
587        protected void writePayAppLine(com.lowagie.text.Document pdfDoc, String payAppDocNbr, String description) {
588            
589            StringBuilder sb = new StringBuilder();
590            sb.append("   ");                                                    // 3:   1 - 3
591            sb.append("PAYAPP DOC NBR: " + rightPad(payAppDocNbr, 12) + " ");    // 29:  4 - 32    
592            sb.append("ACTION: " + description);                                 // 40:  33 - 72
593            
594            writeDetailLine(pdfDoc, sb.toString());
595        }
596        
597        protected void writeExceptionStackTrace(com.lowagie.text.Document pdfDoc, Exception e) {
598            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
599            PrintWriter printWriter = new PrintWriter(outStream, true);
600            
601            e.printStackTrace(printWriter);
602            printWriter.flush();
603            writeDetailLine(pdfDoc, outStream.toString());
604        }
605        
606        protected void writeDetailLine(com.lowagie.text.Document pdfDoc, String detailLineText) {
607            Font font = FontFactory.getFont(FontFactory.COURIER, 8, Font.NORMAL);
608            
609            Paragraph paragraph = new Paragraph();
610            paragraph.setAlignment(com.lowagie.text.Element.ALIGN_LEFT);
611            paragraph.add(new Chunk(detailLineText, font));
612    
613            try {
614                pdfDoc.add(paragraph);
615            }
616            catch (DocumentException e) {
617                LOG.error("iText DocumentException thrown when trying to write content.", e);
618                throw new RuntimeException("iText DocumentException thrown when trying to write content.", e);
619            }
620        }
621        
622        public Long getMaxLockboxSequenceNumber() {
623            return lockboxDao.getMaxLockboxSequenceNumber();
624        }
625        
626        public LockboxDao getLockboxDao() {
627            return lockboxDao;
628        }
629    
630        public void setLockboxDao(LockboxDao lockboxDao) {
631            this.lockboxDao = lockboxDao;
632        }
633    
634        public SystemInformationService getSystemInformationService() {
635            return systemInformationService;
636        }
637    
638        public void setSystemInformationService(SystemInformationService systemInformationService) {
639            this.systemInformationService = systemInformationService;
640        }
641    
642        public AccountsReceivableDocumentHeaderService getAccountsReceivableDocumentHeaderService() {
643            return accountsReceivableDocumentHeaderService;
644        }
645    
646        public void setAccountsReceivableDocumentHeaderService(AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService) {
647            this.accountsReceivableDocumentHeaderService = accountsReceivableDocumentHeaderService;
648        }
649    
650        public void setPaymentApplicationDocumentService(PaymentApplicationDocumentService paymentApplicationDocumentService) {
651            this.payAppDocService = paymentApplicationDocumentService;
652        }
653    
654        /**
655         * @return Returns the personService.
656         */
657        protected PersonService<Person> getPersonService() {
658            if(personService==null)
659                personService = SpringContext.getBean(PersonService.class);
660            return personService;
661        }
662    
663        /**
664         * Gets the documentService attribute. 
665         * @return Returns the documentService.
666         */
667        public DocumentService getDocumentService() {
668            return documentService;
669        }
670    
671        /**
672         * Sets the documentService attribute value.
673         * @param documentService The documentService to set.
674         */
675        public void setDocumentService(DocumentService documentService) {
676            this.documentService = documentService;
677        }
678    
679        /**
680         * Sets the dataDictionaryService attribute value.
681         * @param dataDictionaryService The dataDictionaryService to set.
682         */
683        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
684            this.dataDictionaryService = dataDictionaryService;
685        }
686    
687        public void setDateTimeService(DateTimeService dateTimeService) {
688            this.dateTimeService = dateTimeService;
689        }
690    
691        public CashControlDocumentService getCashControlDocumentService() {
692            return cashControlDocumentService;
693        }
694    
695        public void setCashControlDocumentService(CashControlDocumentService cashControlDocumentService) {
696            this.cashControlDocumentService = cashControlDocumentService;
697        }
698    
699        public void setReportsDirectory(String reportsDirectory) {
700            this.reportsDirectory = reportsDirectory;
701        }
702    
703        public void setBoService(BusinessObjectService boService) {
704            this.boService = boService;
705        }
706    
707    }