/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.module.ar.batch.service.impl; import java.awt.Color; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.kuali.kfs.module.ar.ArConstants; import org.kuali.kfs.module.ar.ArPropertyConstants; import org.kuali.kfs.module.ar.batch.service.LockboxService; import org.kuali.kfs.module.ar.businessobject.AccountsReceivableDocumentHeader; import org.kuali.kfs.module.ar.businessobject.CashControlDetail; import org.kuali.kfs.module.ar.businessobject.Customer; import org.kuali.kfs.module.ar.businessobject.Lockbox; import org.kuali.kfs.module.ar.businessobject.SystemInformation; import org.kuali.kfs.module.ar.dataaccess.LockboxDao; import org.kuali.kfs.module.ar.document.CashControlDocument; import org.kuali.kfs.module.ar.document.CustomerInvoiceDocument; import org.kuali.kfs.module.ar.document.PaymentApplicationDocument; import org.kuali.kfs.module.ar.document.service.AccountsReceivableDocumentHeaderService; import org.kuali.kfs.module.ar.document.service.CashControlDocumentService; import org.kuali.kfs.module.ar.document.service.CustomerService; import org.kuali.kfs.module.ar.document.service.PaymentApplicationDocumentService; import org.kuali.kfs.module.ar.document.service.SystemInformationService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.service.NonTransactional; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.kew.api.KewApiServiceLocator; import org.kuali.rice.kew.api.doctype.DocumentType; import org.kuali.rice.kew.api.doctype.DocumentTypeService; import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kim.api.identity.principal.Principal; import org.kuali.rice.kim.api.services.KimApiServiceLocator; import org.kuali.rice.kns.service.DataDictionaryService; import org.kuali.rice.krad.UserSession; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.ObjectUtils; import org.springframework.transaction.annotation.Transactional; import com.lowagie.text.Chunk; import com.lowagie.text.DocumentException; import com.lowagie.text.Font; import com.lowagie.text.FontFactory; import com.lowagie.text.PageSize; import com.lowagie.text.Paragraph; import com.lowagie.text.pdf.PdfWriter; /** * * Lockbox Iterators are sorted by processedInvoiceDate and batchSequenceNumber. * Potentially there could be many batches on the same date. * For each set of records with the same processedInvoiceDate and batchSequenceNumber, * there will be one Cash-Control document. Each record within this set will create one Application document. * */ public class LockboxServiceImpl implements LockboxService { private static Logger LOG = org.apache.log4j.Logger.getLogger(LockboxServiceImpl.class);; private DocumentService documentService; private SystemInformationService systemInformationService; private AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService; private CashControlDocumentService cashControlDocumentService; private PaymentApplicationDocumentService payAppDocService; private DataDictionaryService dataDictionaryService; private DateTimeService dateTimeService; private BusinessObjectService boService; private CustomerService customerService; private DocumentTypeService documentTypeService; private LockboxDao lockboxDao; private String reportsDirectory; Lockbox ctrlLockbox; CashControlDocument cashControlDocument; boolean anyRecordsFound = false; @Override @NonTransactional public boolean processLockboxes() throws WorkflowException { ctrlLockbox = new Lockbox(); cashControlDocument = null; anyRecordsFound = false; try { // create the pdf doc com.lowagie.text.Document pdfdoc = getPdfDoc(); // this giant try/catch is to make sure that something gets written to the // report. please dont use it for specific exception handling, rather nest // new try/catch handlers inside this. try { Iterator<Lockbox> itr = getAllLockboxes().iterator(); while (itr.hasNext()) { processLockbox(itr.next(), pdfdoc); } // if we have a cashControlDocument here, then it needs to be routed, its the last one if (cashControlDocument != null) { LOG.info(" routing cash control document."); //documentService.routeDocument(cashControlDocument, "Routed by Lockbox Batch process.", null); cashControlDocument.getDocumentHeader().getWorkflowDocument().route("Routed by Lockbox Batch process."); DocumentType documentType = documentTypeService.getDocumentTypeByName(cashControlDocument.getFinancialDocumentTypeCode()); DocumentAttributeIndexingQueue queue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(documentType.getApplicationId()); queue.indexDocument(cashControlDocument.getDocumentNumber()); } // if no records were found, write something useful to the report if (!anyRecordsFound) { writeDetailLine(pdfdoc, "NO LOCKBOX RECORDS WERE FOUND"); } // this annoying all-encompassing try/catch is here to make sure that the report gets // written. without it, if anything goes wrong, the report will end up a zero-byte document. } catch (Exception e) { writeDetailLine(pdfdoc, "AN EXCEPTION OCCURRED:"); writeDetailLine(pdfdoc, ""); writeDetailLine(pdfdoc, e.getMessage()); writeDetailLine(pdfdoc, ""); writeExceptionStackTrace(pdfdoc, e); throw new RuntimeException("An exception occured while processing Lockboxes.", e); } finally { // spool the report if (pdfdoc != null) { pdfdoc.close(); } } } catch (IOException | DocumentException ex) { throw new RuntimeException("Could not open file for lockbox processing results report", ex); } return true; } @Transactional protected Collection<Lockbox> getAllLockboxes() { Collection<Lockbox> itr = lockboxDao.getAllLockboxes(); return itr; } @Override @Transactional public void processLockbox(Lockbox lockbox, com.lowagie.text.Document pdfdoc) { anyRecordsFound = true; LOG.info("LOCKBOX: '" + lockbox.getLockboxNumber() + "'"); // retrieve the processingOrg (system information) for this lockbox number SystemInformation sysInfo = systemInformationService.getByLockboxNumberForCurrentFiscalYear(lockbox.getLockboxNumber()); String initiator = sysInfo.getFinancialDocumentInitiatorIdentifier(); LOG.info(" using SystemInformation: '" + sysInfo.toString() + "'"); LOG.info(" using Financial Document Initiator ID: '" + initiator + "'"); // puke if the initiator stored in the systemInformation table is no good Principal principal = KimApiServiceLocator.getIdentityService().getPrincipal(initiator); if (principal == null) { LOG.warn(" could not find [" + initiator + "] when searching by PrincipalID, so trying to find as a PrincipalName."); principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(initiator); if (principal == null) { 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."); 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."); } else { LOG.info(" found [" + initiator + "] in the system as a PrincipalName."); } } else { LOG.info(" found [" + initiator + "] in the system as a PrincipalID."); } // masquerade as the person indicated in the systemInformation GlobalVariables.clear(); GlobalVariables.setUserSession(new UserSession(principal.getPrincipalName())); if (lockbox.compareTo(ctrlLockbox) != 0) { // If we made it in here, then we have hit a different batchSequenceNumber and processedInvoiceDate. // When this is the case, we create a new cashcontroldocument and start tacking subsequent lockboxes on // to the current cashcontroldocument as cashcontroldetails. LOG.info("New Lockbox batch"); // we're creating a new cashcontrol, so if we have an old one, we need to route it if (cashControlDocument != null) { LOG.info(" routing cash control document."); try { // documentService.routeDocument(cashControlDocument, "Routed by Lockbox Batch process.", null); cashControlDocument.getDocumentHeader().getWorkflowDocument().route("Routed by Lockbox Batch process."); //RICE20 replaced searchableAttributeProcessingService.indexDocument with DocumentAttributeIndexingQueue.indexDocument DocumentType documentType = documentTypeService.getDocumentTypeByName(cashControlDocument.getFinancialDocumentTypeCode()); DocumentAttributeIndexingQueue queue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(documentType.getApplicationId()); queue.indexDocument(cashControlDocument.getDocumentNumber()); } catch (Exception e) { LOG.error("A Exception was thrown while trying to route the CashControl document.", e); throw new RuntimeException("A Exception was thrown while trying to route the CashControl document.", e); } } // create a new CashControl document LOG.info("Creating new CashControl document for invoice: " + lockbox.getFinancialDocumentReferenceInvoiceNumber() + "."); try { cashControlDocument = (CashControlDocument)documentService.getNewDocument(KFSConstants.FinancialDocumentTypeCodes.CASH_CONTROL); } catch (Exception e) { LOG.error("A Exception was thrown while trying to initiate a new CashControl document.", e); throw new RuntimeException("A Exception was thrown while trying to initiate a new CashControl document.", e); } LOG.info(" CashControl documentNumber == '" + cashControlDocument.getDocumentNumber() + "'"); // write the batch group header to the report writeBatchGroupSectionTitle(pdfdoc, lockbox.getBatchSequenceNumber().toString(), lockbox.getProcessedInvoiceDate(), cashControlDocument.getDocumentNumber()); cashControlDocument.setCustomerPaymentMediumCode(lockbox.getCustomerPaymentMediumCode()); if(ObjectUtils.isNotNull(lockbox.getBankCode())) { String bankCode = lockbox.getBankCode(); cashControlDocument.setBankCode(bankCode); } cashControlDocument.getDocumentHeader().setDocumentDescription(ArConstants.LOCKBOX_DOCUMENT_DESCRIPTION + lockbox.getLockboxNumber()); // setup the AR header for this CashControl doc LOG.info(" creating AR header for customer: [" + lockbox.getCustomerNumber() + "] and ProcessingOrg: " + sysInfo.getProcessingChartOfAccountCode() + "-" + sysInfo.getProcessingOrganizationCode() + "."); AccountsReceivableDocumentHeader arDocHeader = new AccountsReceivableDocumentHeader(); arDocHeader.setProcessingChartOfAccountCode(sysInfo.getProcessingChartOfAccountCode()); arDocHeader.setProcessingOrganizationCode(sysInfo.getProcessingOrganizationCode()); arDocHeader.setDocumentNumber(cashControlDocument.getDocumentNumber()); if(ObjectUtils.isNotNull(lockbox.getCustomerNumber())) { Customer customer = customerService.getByPrimaryKey(lockbox.getCustomerNumber()); if (ObjectUtils.isNotNull(customer)) { arDocHeader.setCustomerNumber(lockbox.getCustomerNumber()); } } cashControlDocument.setAccountsReceivableDocumentHeader(arDocHeader); } // set our control lockbox as the current lockbox and create details. ctrlLockbox = lockbox; // write the lockbox detail line to the report writeLockboxRecordLine(pdfdoc, lockbox.getLockboxNumber(), lockbox.getCustomerNumber(), lockbox.getFinancialDocumentReferenceInvoiceNumber(), lockbox.getInvoicePaidOrAppliedAmount(), lockbox.getCustomerPaymentMediumCode(), lockbox.getBankCode()); // skip zero-dollar-amount lockboxes if (lockbox.getInvoicePaidOrAppliedAmount().isZero()) { LOG.warn(" lockbox has a zero dollar amount, so we're skipping it."); writeSummaryDetailLine(pdfdoc, "ZERO-DOLLAR LOCKBOX - NO FURTHER PROCESSING"); deleteProcessedLockboxEntry(lockbox); return; } if (lockbox.getInvoicePaidOrAppliedAmount().isLessThan(KualiDecimal.ZERO)) { LOG.warn(" lockbox has a negative dollar amount, so we're skipping it."); writeCashControlDetailLine(pdfdoc, lockbox.getInvoicePaidOrAppliedAmount(), "SKIPPED"); writeSummaryDetailLine(pdfdoc, "NEGATIVE-DOLLAR LOCKBOX - NO FURTHER PROCESSING - LOCKBOX ENTRY NOT DELETED"); return; } // create a new cashcontrol detail CashControlDetail detail = new CashControlDetail(); if(ObjectUtils.isNotNull(lockbox.getCustomerNumber())) { Customer customer = customerService.getByPrimaryKey(lockbox.getCustomerNumber()); if (ObjectUtils.isNotNull(customer)) { detail.setCustomerNumber(lockbox.getCustomerNumber()); } } detail.setFinancialDocumentLineAmount(lockbox.getInvoicePaidOrAppliedAmount()); detail.setCustomerPaymentDate(lockbox.getProcessedInvoiceDate()); detail.setCustomerPaymentDescription("Lockbox Remittance " +lockbox.getFinancialDocumentReferenceInvoiceNumber()); // add it to the document LOG.info(" creating detail for $" + lockbox.getInvoicePaidOrAppliedAmount() + " with invoiceDate: " + lockbox.getProcessedInvoiceDate()); try { cashControlDocumentService.addNewCashControlDetail(ArConstants.LOCKBOX_DOCUMENT_DESCRIPTION, cashControlDocument, detail); } catch (WorkflowException e) { LOG.error("A Exception was thrown while trying to create a new CashControl detail.", e); throw new RuntimeException("A Exception was thrown while trying to create a new CashControl detail.", e); } // retrieve the docNumber of the generated payapp String payAppDocNumber = detail.getReferenceFinancialDocumentNumber(); LOG.info(" new PayAppDoc was created: " + payAppDocNumber + "."); String invoiceNumber = lockbox.getFinancialDocumentReferenceInvoiceNumber(); LOG.info(" lockbox references invoice number [" + invoiceNumber + "]."); // if thats the case, dont even bother looking for an invoice, just save the CashControl if (StringUtils.isBlank(invoiceNumber)) { LOG.info(" invoice number is blank; cannot load an invoice."); detail.setCustomerPaymentDescription(ArConstants.LOCKBOX_REMITTANCE_FOR_INVALID_INVOICE_NUMBER +lockbox.getFinancialDocumentReferenceInvoiceNumber()); try { documentService.saveDocument(cashControlDocument); } catch (WorkflowException e) { LOG.error("A Exception was thrown while trying to save the CashControl document.", e); throw new RuntimeException("A Exception was thrown while trying to save the CashControl document.", e); } //KFSMI-6719 //routePayAppWithoutBusinessRules(payAppDocNumber, "CREATED & SAVED by Lockbox batch"); //write the detail and payapp lines to the report writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription()); writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), "CREATED & SAVED"); writeSummaryDetailLine(pdfdoc, "INVOICE NUMBER IS BLANK"); // delete the lockbox now we're done with it deleteProcessedLockboxEntry(lockbox); return; } // check to see if the invoice indicated exists, and if not, then save the CashControl and move on if (!documentService.documentExists(invoiceNumber)) { LOG.info(" invoice number [" + invoiceNumber + "] does not exist in system, so cannot load the original invoice."); detail.setCustomerPaymentDescription(ArConstants.LOCKBOX_REMITTANCE_FOR_INVALID_INVOICE_NUMBER +lockbox.getFinancialDocumentReferenceInvoiceNumber()); try { documentService.saveDocument(cashControlDocument); } catch (WorkflowException e) { LOG.error("A Exception was thrown while trying to save the CashControl document.", e); throw new RuntimeException("A Exception was thrown while trying to save the CashControl document.", e); } // KFSCNTRB-1241 //routePayAppWithoutBusinessRules(payAppDocNumber, "CREATED & SAVED by Lockbox batch"); writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription()); writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), "CREATED & SAVED"); writeSummaryDetailLine(pdfdoc, "INVOICE DOESNT EXIST"); // delete the lockbox now we're done with it deleteProcessedLockboxEntry(lockbox); return; } // load up the specified invoice from the lockbox LOG.info(" loading invoice number [" + invoiceNumber + "]."); CustomerInvoiceDocument customerInvoiceDocument; try { customerInvoiceDocument = (CustomerInvoiceDocument)documentService.getByDocumentHeaderId(invoiceNumber); } catch (WorkflowException e) { LOG.error("A Exception was thrown while trying to load invoice #" + invoiceNumber + ".", e); throw new RuntimeException("A Exception was thrown while trying to load invoice #" + invoiceNumber + ".", e); } // if the invoice is already closed, then just save the CashControl and move on writeInvoiceDetailLine(pdfdoc, invoiceNumber, customerInvoiceDocument.isOpenInvoiceIndicator(), customerInvoiceDocument.getCustomer().getCustomerNumber(), customerInvoiceDocument.getOpenAmount()); if (!customerInvoiceDocument.isOpenInvoiceIndicator()) { LOG.info(" invoice is already closed, so saving CashControl doc and moving on."); detail.setCustomerPaymentDescription(ArConstants.LOCKBOX_REMITTANCE_FOR_CLOSED_INVOICE_NUMBER +lockbox.getFinancialDocumentReferenceInvoiceNumber()); try { documentService.saveDocument(cashControlDocument); } catch (WorkflowException e) { LOG.error("A Exception was thrown while trying to save the CashControl document.", e); throw new RuntimeException("A Exception was thrown while trying to save the CashControl document.", e); } // KFSCNTRB-1241 //routePayAppWithoutBusinessRules(payAppDocNumber, "CREATED & SAVED by Lockbox batch"); writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription()); writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), "CREATED & SAVED"); writeSummaryDetailLine(pdfdoc, "INVOICE ALREADY CLOSED"); deleteProcessedLockboxEntry(lockbox); return; } PaymentApplicationDocument payAppDoc; try { payAppDoc = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(payAppDocNumber); } catch (WorkflowException e) { LOG.error("A Exception was thrown while trying to load PayApp #" + payAppDocNumber + ".", e); throw new RuntimeException("A Exception was thrown while trying to load PayApp #" + payAppDocNumber + ".", e); } boolean autoApprove = canAutoApprove(customerInvoiceDocument, lockbox, payAppDoc); String annotation = "CREATED & SAVED"; // if the lockbox amount matches the invoice amount, then create, save and approve a PayApp, and then // mark the invoice if (autoApprove){ LOG.info(" lockbox amount matches invoice total document amount [" + customerInvoiceDocument.getTotalDollarAmount() + "]."); annotation = "CREATED, SAVED, and BLANKET APPROVED"; // load up the PayApp document that was created LOG.info(" loading the generated PayApp [" + payAppDocNumber + "], so we can route or approve it."); // create paidapplieds on the PayApp doc for all the Invoice details LOG.info(" attempting to create paidApplieds on the PayAppDoc for every detail on the invoice."); payAppDoc = payAppDocService.createInvoicePaidAppliedsForEntireInvoiceDocument(customerInvoiceDocument, payAppDoc); LOG.info(" PayAppDoc has TotalApplied of " + payAppDoc.getTotalApplied() + " for a Control Balance of " + payAppDoc.getTotalFromControl() + "."); // Save and approve the payapp doc LOG.info(" attempting to blanketApprove the PayApp Doc."); try { documentService.blanketApproveDocument(payAppDoc, "Automatically approved by Lockbox batch job.", null); } catch (WorkflowException e) { LOG.error("A Exception was thrown while trying to blanketApprove PayAppDoc #" + payAppDoc.getDocumentNumber() + ".", e); throw new RuntimeException("A Exception was thrown while trying to blanketApprove PayAppDoc #" + payAppDoc.getDocumentNumber() + ".", e); } // write the report details writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription()); writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), annotation); writeSummaryDetailLine(pdfdoc, "LOCKBOX AMOUNT MATCHES INVOICE OPEN AMOUNT"); } else { LOG.info(" lockbox amount does NOT match invoice total document amount [" + customerInvoiceDocument.getTotalDollarAmount() + "]."); // KFSCNTRB-1241 //routePayAppWithoutBusinessRules(payAppDocNumber, "CREATED & SAVED by Lockbox batch"); // write the report details writeCashControlDetailLine(pdfdoc, detail.getFinancialDocumentLineAmount(), detail.getCustomerPaymentDescription()); writePayAppLine(pdfdoc, detail.getReferenceFinancialDocumentNumber(), annotation); if (lockbox.getInvoicePaidOrAppliedAmount().isLessThan(customerInvoiceDocument.getOpenAmount())) { writeSummaryDetailLine(pdfdoc, "LOCKBOX UNDERPAID INVOICE"); } else { writeSummaryDetailLine(pdfdoc, "LOCKBOX OVERPAID INVOICE"); } } // save the cashcontrol, which saves any changes to the details detail.setCustomerPaymentDescription(ArConstants.LOCKBOX_REMITTANCE_FOR_INVOICE_NUMBER +lockbox.getFinancialDocumentReferenceInvoiceNumber()); LOG.info(" saving cash control document."); try { documentService.saveDocument(cashControlDocument); } catch (WorkflowException e) { LOG.error("A Exception was thrown while trying to save the CashControl document.", e); throw new RuntimeException("A Exception was thrown while trying to save the CashControl document.", e); } // delete the lockbox now we're done with it deleteProcessedLockboxEntry(lockbox); } protected boolean canAutoApprove(CustomerInvoiceDocument invoice, Lockbox lockbox, PaymentApplicationDocument payAppDoc) { boolean retVal = invoice.getOpenAmount().equals(lockbox.getInvoicePaidOrAppliedAmount()); retVal &= ObjectUtils.isNotNull(payAppDoc.getCashControlDetail().getCustomerNumber()); return retVal; } protected void routePayAppWithoutBusinessRules(String payAppDocNumber, String annotation) { // load up the PayApp document that was created LOG.info(" loading the generated PayApp [" + payAppDocNumber + "], so we can route or approve it."); PaymentApplicationDocument payAppDoc; try { payAppDoc = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(payAppDocNumber); } catch (WorkflowException e) { LOG.error("A Exception was thrown while trying to load PayApp #" + payAppDocNumber + ".", e); throw new RuntimeException("A Exception was thrown while trying to load PayApp #" + payAppDocNumber + ".", e); } // route without business rules LOG.info(" attempting to route without business rules the PayApp Doc."); payAppDoc.getDocumentHeader().getWorkflowDocument().route(annotation); DocumentType documentType = documentTypeService.getDocumentTypeByName(payAppDoc.getFinancialDocumentTypeCode()); DocumentAttributeIndexingQueue queue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(documentType.getApplicationId()); queue.indexDocument(payAppDoc.getDocumentNumber()); } protected void deleteProcessedLockboxEntry(Lockbox lockboxEntry) { Map<String,Object> pkMap = new HashMap<String,Object>(); pkMap.put(ArPropertyConstants.INVOICE_SEQUENCE_NUMBER, lockboxEntry.getInvoiceSequenceNumber()); boService.deleteMatching(Lockbox.class, pkMap); } protected com.lowagie.text.Document getPdfDoc() throws IOException, DocumentException { String reportDropFolder = reportsDirectory + "/" + ArConstants.Lockbox.LOCKBOX_REPORT_SUBFOLDER + "/"; String fileName = ArConstants.Lockbox.BATCH_REPORT_BASENAME + "_" + new SimpleDateFormat("yyyyMMdd_HHmmssSSS").format(dateTimeService.getCurrentDate()) + ".pdf"; // setup the writer File reportFile = new File(reportDropFolder + fileName); FileOutputStream fileOutStream; fileOutStream = new FileOutputStream(reportFile); BufferedOutputStream buffOutStream = new BufferedOutputStream(fileOutStream); com.lowagie.text.Document pdfdoc = new com.lowagie.text.Document(PageSize.LETTER, 54, 54, 72, 72); PdfWriter.getInstance(pdfdoc, buffOutStream); pdfdoc.open(); return pdfdoc; } protected String rightPad(String valToPad, int sizeToPadTo) { return rightPad(valToPad, sizeToPadTo, " "); } protected String rightPad(String valToPad, int sizeToPadTo, String padChar) { if (StringUtils.isBlank(valToPad)) { return StringUtils.repeat(padChar, sizeToPadTo); } if (valToPad.length() >= sizeToPadTo) { return valToPad; } return valToPad + StringUtils.repeat(padChar, sizeToPadTo - valToPad.length()); } protected void writeBatchGroupSectionTitle(com.lowagie.text.Document pdfDoc, String batchSeqNbr, java.sql.Date procInvDt, String cashControlDocNumber) { Font font = FontFactory.getFont(FontFactory.COURIER, 10, Font.BOLD); String lineText = "CASHCTL " + rightPad(cashControlDocNumber, 12) + " " + "BATCH GROUP: " + rightPad(batchSeqNbr, 5) + " " + rightPad((procInvDt == null ? "NONE" : procInvDt.toString()), 35); Paragraph paragraph = new Paragraph(); paragraph.setAlignment(com.lowagie.text.Element.ALIGN_LEFT); Chunk chunk = new Chunk(lineText, font); chunk.setBackground(Color.LIGHT_GRAY, 5, 5, 5, 5); paragraph.add(chunk); // blank line paragraph.add(new Chunk("", font)); try { pdfDoc.add(paragraph); } catch (DocumentException e) { LOG.error("iText DocumentException thrown when trying to write content.", e); throw new RuntimeException("iText DocumentException thrown when trying to write content.", e); } } protected void writeLockboxRecordLine(com.lowagie.text.Document pdfDoc, String lockboxNumber, String customerNumber, String invoiceNumber, KualiDecimal invoiceTotalAmount, String paymentMediumCode, String bankCode) { writeDetailLine(pdfDoc, StringUtils.repeat("-", 100)); StringBuilder sb = new StringBuilder(); sb.append(" "); // 3: 1 - 3 sb.append("LOCKBOX: " + rightPad(lockboxNumber, 10) + " "); // 20: 4 - 23 sb.append("CUST: " + rightPad(customerNumber, 9) + " "); // 15: 24 - 38 sb.append("INV: " + rightPad(invoiceNumber, 10) + " "); // 16: 39 - 55 sb.append(StringUtils.repeat(" ", 28)); // 28: 56 - 83 sb.append("AMT: " + rightPad(invoiceTotalAmount.toString(), 11) + " "); // 17: 84 - 100 writeDetailLine(pdfDoc, sb.toString()); } protected void writeInvoiceDetailLine(com.lowagie.text.Document pdfDoc, String invoiceNumber, boolean open, String customerNumber, KualiDecimal openAmount) { StringBuilder sb = new StringBuilder(); sb.append(" "); // 3: 1 - 3 sb.append("INVOICE: " + rightPad(invoiceNumber, 10) + " "); // 20: 4 - 23 sb.append("CUST: " + rightPad(customerNumber, 9) + " "); // 15: 24 - 38 if (open) { sb.append(rightPad("OPEN", 16) + " "); // 16: 39 - 55 } else { sb.append(rightPad("CLOSED", 16) + " "); // 16: 39 - 55 } sb.append(StringUtils.repeat(" ", 22)); // 28: 56 - 83 sb.append("OPEN AMT: " + rightPad(openAmount.toString(), 11) + " "); // 17: 84 - 100 writeDetailLine(pdfDoc, sb.toString()); } protected void writeCashControlDetailLine(com.lowagie.text.Document pdfDoc, KualiDecimal amount, String description) { StringBuilder sb = new StringBuilder(); sb.append(" "); // 3: 1 - 3 sb.append("CASHCTL DTL: " + rightPad(description, 66) + " "); // 80: 4 - 83 sb.append("AMT: " + rightPad(amount.toString(), 11) + " "); // 17: 84 - 100 writeDetailLine(pdfDoc, sb.toString()); } protected void writeSummaryDetailLine(com.lowagie.text.Document pdfDoc, String summary) { writeDetailLine(pdfDoc, " " + summary); } protected void writePayAppLine(com.lowagie.text.Document pdfDoc, String payAppDocNbr, String description) { StringBuilder sb = new StringBuilder(); sb.append(" "); // 3: 1 - 3 sb.append("PAYAPP DOC NBR: " + rightPad(payAppDocNbr, 12) + " "); // 29: 4 - 32 sb.append("ACTION: " + description); // 40: 33 - 72 writeDetailLine(pdfDoc, sb.toString()); } protected void writeExceptionStackTrace(com.lowagie.text.Document pdfDoc, Exception e) { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); PrintWriter printWriter = new PrintWriter(outStream, true); e.printStackTrace(printWriter); printWriter.flush(); writeDetailLine(pdfDoc, outStream.toString()); } protected void writeDetailLine(com.lowagie.text.Document pdfDoc, String detailLineText) { if (ObjectUtils.isNotNull(detailLineText)) { Font font = FontFactory.getFont(FontFactory.COURIER, 8, Font.NORMAL); Paragraph paragraph = new Paragraph(); paragraph.setAlignment(com.lowagie.text.Element.ALIGN_LEFT); paragraph.add(new Chunk(detailLineText, font)); try { pdfDoc.add(paragraph); } catch (DocumentException e) { LOG.error("iText DocumentException thrown when trying to write content.", e); throw new RuntimeException("iText DocumentException thrown when trying to write content.", e); } } } @Override @Transactional public Long getMaxLockboxSequenceNumber() { return lockboxDao.getMaxLockboxSequenceNumber(); } @NonTransactional public LockboxDao getLockboxDao() { return lockboxDao; } @NonTransactional public void setLockboxDao(LockboxDao lockboxDao) { this.lockboxDao = lockboxDao; } @NonTransactional public SystemInformationService getSystemInformationService() { return systemInformationService; } @NonTransactional public void setSystemInformationService(SystemInformationService systemInformationService) { this.systemInformationService = systemInformationService; } @NonTransactional public AccountsReceivableDocumentHeaderService getAccountsReceivableDocumentHeaderService() { return accountsReceivableDocumentHeaderService; } @NonTransactional public void setAccountsReceivableDocumentHeaderService(AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService) { this.accountsReceivableDocumentHeaderService = accountsReceivableDocumentHeaderService; } @NonTransactional public void setPaymentApplicationDocumentService(PaymentApplicationDocumentService paymentApplicationDocumentService) { this.payAppDocService = paymentApplicationDocumentService; } /** * Gets the documentService attribute. * @return Returns the documentService. */ @NonTransactional public DocumentService getDocumentService() { return documentService; } /** * Sets the documentService attribute value. * @param documentService The documentService to set. */ @NonTransactional public void setDocumentService(DocumentService documentService) { this.documentService = documentService; } /** * Sets the dataDictionaryService attribute value. * @param dataDictionaryService The dataDictionaryService to set. */ @NonTransactional public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { this.dataDictionaryService = dataDictionaryService; } @NonTransactional public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } @NonTransactional public CashControlDocumentService getCashControlDocumentService() { return cashControlDocumentService; } @NonTransactional public void setCashControlDocumentService(CashControlDocumentService cashControlDocumentService) { this.cashControlDocumentService = cashControlDocumentService; } @NonTransactional public void setReportsDirectory(String reportsDirectory) { this.reportsDirectory = reportsDirectory; } @NonTransactional public void setBoService(BusinessObjectService boService) { this.boService = boService; } /** * Gets the customerService attribute. * * @return Returns the customerService */ @NonTransactional public CustomerService getCustomerService() { return customerService; } /** * Sets the customerService attribute. * * @param customerService The customerService to set. */ @NonTransactional public void setCustomerService(CustomerService customerService) { this.customerService = customerService; } @NonTransactional public void setDocumentTypeService(DocumentTypeService documentTypeService) { this.documentTypeService = documentTypeService; } }