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 }