/* * 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.document.service.impl; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.integration.cg.ContractsAndGrantsBillingAward; import org.kuali.kfs.module.ar.ArConstants; import org.kuali.kfs.module.ar.ArKeyConstants; import org.kuali.kfs.module.ar.businessobject.CollectionActivityType; import org.kuali.kfs.module.ar.businessobject.CollectionEvent; import org.kuali.kfs.module.ar.businessobject.CustomerAddress; import org.kuali.kfs.module.ar.businessobject.DunningLetterTemplate; import org.kuali.kfs.module.ar.businessobject.GenerateDunningLettersLookupResult; import org.kuali.kfs.module.ar.businessobject.InvoiceAddressDetail; import org.kuali.kfs.module.ar.document.ContractsGrantsInvoiceDocument; import org.kuali.kfs.module.ar.document.dataaccess.ContractsGrantsInvoiceDocumentDao; import org.kuali.kfs.module.ar.document.service.ContractsGrantsInvoiceDocumentService; import org.kuali.kfs.module.ar.document.service.DunningLetterService; import org.kuali.kfs.module.ar.service.ContractsGrantsBillingUtilityService; import org.kuali.kfs.sys.FinancialSystemModuleConfiguration; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.PdfFormFillerUtil; import org.kuali.kfs.sys.businessobject.ChartOrgHolder; import org.kuali.kfs.sys.service.FinancialSystemUserService; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.krad.bo.ModuleConfiguration; import org.kuali.rice.krad.bo.Note; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.KualiModuleService; import org.kuali.rice.krad.service.NoteService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.ObjectUtils; import org.springframework.transaction.annotation.Transactional; import com.lowagie.text.DocumentException; import com.lowagie.text.pdf.PdfCopyFields; import com.lowagie.text.pdf.PdfReader; /** * Implementation class for DunningLetterDistributionService. */ @Transactional public class DunningLetterServiceImpl implements DunningLetterService { protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DunningLetterServiceImpl.class); protected BusinessObjectService businessObjectService; protected ContractsGrantsInvoiceDocumentDao contractsGrantsInvoiceDocumentDao; protected ContractsGrantsInvoiceDocumentService contractsGrantsInvoiceDocumentService; protected ContractsGrantsBillingUtilityService contractsGrantsBillingUtilityService; protected DateTimeService dateTimeService; protected FinancialSystemUserService financialSystemUserService; protected KualiModuleService kualiModuleService; protected NoteService noteService; private ParameterService parameterService; /** * This method generates the actual pdf file with related invoices to the template to print. * * @param dunningLetterTemplate * @param dunningLetterDistributionLookupResult * @return */ protected byte[] createDunningLetters(DunningLetterTemplate dunningLetterTemplate, GenerateDunningLettersLookupResult dunningLetterDistributionLookupResult) { List<ContractsGrantsInvoiceDocument> selectedInvoices = new ArrayList<ContractsGrantsInvoiceDocument>(); byte[] reportStream = null; byte[] finalReportStream = null; int lastEventCode; if (ObjectUtils.isNotNull(dunningLetterTemplate) && dunningLetterTemplate.isActive() && ObjectUtils.isNotNull(dunningLetterTemplate.getFilename())) { // To get list of invoices per award per dunning letter template for (ContractsGrantsInvoiceDocument cgInvoice : dunningLetterDistributionLookupResult.getInvoices()) { if (StringUtils.equals(cgInvoice.getInvoiceGeneralDetail().getDunningLetterTemplateAssigned(), dunningLetterTemplate.getDunningLetterTemplateCode())) { selectedInvoices.add(cgInvoice); // 1. Now we know that the invoice is going to have its dunning letter processed. So we assume the letter is // sent and set the event for it. CollectionEvent event = new CollectionEvent(); event.setInvoiceNumber(cgInvoice.getDocumentNumber()); event.setCollectionEventCode(cgInvoice.getNextCollectionEventCode()); String activityCode = parameterService.getParameterValueAsString(CollectionActivityType.class, ArConstants.DUNNING_LETTER_GENERATION_CODE); if (StringUtils.isNotBlank(activityCode)) { event.setActivityCode(activityCode); event.setActivityDate(new java.sql.Date(new Date().getTime())); event.setActivityText(ArConstants.DunningLetters.DUNNING_LETTER_SENT_TXT); final Timestamp now = dateTimeService.getCurrentTimestamp(); event.setPostedDate(now); if (GlobalVariables.getUserSession() != null && GlobalVariables.getUserSession().getPerson() != null) { Person authorUniversal = GlobalVariables.getUserSession().getPerson(); event.setUserPrincipalId(authorUniversal.getPrincipalId()); event.setUser(authorUniversal); } businessObjectService.save(event); cgInvoice.getCollectionEvents().add(event); } // 2. To set the Last sent date of the dunning letter. cgInvoice.getInvoiceGeneralDetail().setDunningLetterTemplateSentDate(new java.sql.Date(new Date().getTime())); businessObjectService.save(cgInvoice); } } // to generate dunning letter from templates. ModuleConfiguration systemConfiguration = kualiModuleService.getModuleServiceByNamespaceCode(KFSConstants.OptionalModuleNamespaces.ACCOUNTS_RECEIVABLE).getModuleConfiguration(); String templateFolderPath = ((FinancialSystemModuleConfiguration) systemConfiguration).getTemplateFileDirectories().get(KFSConstants.TEMPLATES_DIRECTORY_KEY); String templateFilePath = templateFolderPath + File.separator + dunningLetterTemplate.getFilename(); File templateFile = new File(templateFilePath); File outputDirectory = null; String outputFileName; try { // Step2. add parameters to the dunning letter outputFileName = dunningLetterDistributionLookupResult.getProposalNumber() + getDateTimeService().toDateStringForFilename(getDateTimeService().getCurrentDate()) + ArConstants.TemplateUploadSystem.EXTENSION; Map<String, String> replacementList = getTemplateParameterList(selectedInvoices); CustomerAddress address; Map<String, Object> primaryKeys = new HashMap<String, Object>(); primaryKeys.put(KFSPropertyConstants.CUSTOMER_NUMBER, dunningLetterDistributionLookupResult.getCustomerNumber()); primaryKeys.put("customerAddressTypeCode", "P"); address = businessObjectService.findByPrimaryKey(CustomerAddress.class, primaryKeys); replacementList.put("agency.fullAddressInline", contractsGrantsBillingUtilityService.buildFullAddress(address)); replacementList.put("agency.fullName", address.getCustomer().getCustomerName()); replacementList.put("agency.contactName", address.getCustomer().getCustomerContactName()); if(CollectionUtils.isNotEmpty(selectedInvoices)){ reportStream = PdfFormFillerUtil.populateTemplate(templateFile, replacementList); // Step3. attach each dunning letter to invoice pdfs. finalReportStream = generateListOfInvoicesPdfToPrint(selectedInvoices, reportStream); } } catch (DocumentException | IOException ex) { // This means that the invoice pdfs were not generated properly. So get only the Dunning letters created. LOG.error("An exception occurred while retrieving invoice pdfs." + ex.getMessage()); finalReportStream = reportStream; } } else { GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, ArKeyConstants.ERROR_FILE_UPLOAD_NO_PDF_FILE_SELECTED_FOR_SAVE, "test"); } return finalReportStream; } /** * Loops through the collection of lookup results, creating pdfs for each and appending the bytes of the pdfs onto the returned "finalReport" * @see org.kuali.kfs.module.ar.document.service.DunningLetterDistributionService#createDunningLettersForAllResults(org.kuali.kfs.module.ar.businessobject.DunningLetterTemplate, java.util.Collection) */ @Override public byte[] createDunningLettersForAllResults(Collection<GenerateDunningLettersLookupResult> results) throws DocumentException, IOException { ByteArrayOutputStream zos = null; PdfCopyFields reportCopy = null; byte[] finalReport = null; try { zos = new ByteArrayOutputStream(); reportCopy = new PdfCopyFields(zos); reportCopy.open(); List<DunningLetterTemplate> dunningLetterTemplates = (List<DunningLetterTemplate>) getBusinessObjectService().findAll(DunningLetterTemplate.class); for (DunningLetterTemplate dunningLetterTemplate : dunningLetterTemplates) { for (GenerateDunningLettersLookupResult generateDunningLettersLookupResult : results) { final byte[] report = createDunningLetters(dunningLetterTemplate, generateDunningLettersLookupResult); if (ObjectUtils.isNotNull(report)) { reportCopy.addDocument(new PdfReader(report)); } } } reportCopy.close(); finalReport = zos.toByteArray(); } finally { if (zos != null) { zos.close(); } } return finalReport; } /** * This method generated the template parameter list to populate the pdf invoices that are attached to the Document. * * @return */ protected Map<String, String> getTemplateParameterList(List<ContractsGrantsInvoiceDocument> invoices) { Map<String, String> parameterMap = new HashMap<String, String>(); if (CollectionUtils.isNotEmpty(invoices)){ ContractsAndGrantsBillingAward award = invoices.get(0).getInvoiceGeneralDetail().getAward(); Map primaryKeys = new HashMap<String, Object>(); contractsGrantsBillingUtilityService.putValueOrEmptyString(parameterMap, "award.proposalNumber", org.apache.commons.lang.ObjectUtils.toString(award.getProposalNumber())); contractsGrantsBillingUtilityService.putValueOrEmptyString(parameterMap, "currentDate", getDateTimeService().toDateTimeString(getDateTimeService().getCurrentDate())); if (CollectionUtils.isNotEmpty(invoices)) { for (int i = 0; i < invoices.size(); i++) { contractsGrantsBillingUtilityService.putValueOrEmptyString(parameterMap, "invoice[" + i + "].documentNumber", invoices.get(i).getDocumentNumber()); contractsGrantsBillingUtilityService.putValueOrEmptyString(parameterMap, "invoice[" + i + "].billingDate", getDateTimeService().toDateString(invoices.get(i).getBillingDate())); contractsGrantsBillingUtilityService.putValueOrEmptyString(parameterMap, "invoice[" + i + "].totalAmount", contractsGrantsBillingUtilityService.formatForCurrency(invoices.get(i).getTotalDollarAmount())); contractsGrantsBillingUtilityService.putValueOrEmptyString(parameterMap, "invoice[" + i + "].customerName", invoices.get(i).getCustomerName()); contractsGrantsBillingUtilityService.putValueOrEmptyString(parameterMap, "invoice[" + i + "].customerNumber", invoices.get(i).getAccountsReceivableDocumentHeader().getCustomerNumber()); } } if (ObjectUtils.isNotNull(award)) { contractsGrantsBillingUtilityService.putValueOrEmptyString(parameterMap, "award.awardProjectTitle", award.getAwardProjectTitle()); } } return parameterMap; } /** * This method generates the actual pdf files to print. * * @param mapping * @param form * @param list * @return */ @Override public boolean createZipOfPDFs(byte[] report, ByteArrayOutputStream baos) throws IOException { ZipOutputStream zos = new ZipOutputStream(baos); int bytesRead; byte[] buffer = new byte[1024]; CRC32 crc = new CRC32(); if (ObjectUtils.isNotNull(report)) { BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(report)); crc.reset(); while ((bytesRead = bis.read(buffer)) != -1) { crc.update(buffer, 0, bytesRead); } bis.close(); // Reset to beginning of input stream bis = new BufferedInputStream(new ByteArrayInputStream(report)); ZipEntry entry = new ZipEntry("DunningLetters&Invoices-" + getDateTimeService().toDateStringForFilename(getDateTimeService().getCurrentDate()) + ".pdf"); entry.setMethod(ZipEntry.STORED); entry.setCompressedSize(report.length); entry.setSize(report.length); entry.setCrc(crc.getValue()); zos.putNextEntry(entry); while ((bytesRead = bis.read(buffer)) != -1) { zos.write(buffer, 0, bytesRead); } bis.close(); } zos.close(); return true; } /** * @see org.kuali.kfs.module.ar.report.service.ContractsGrantsInvoiceReportService#generateListOfInvoicesPdfToPrint(java.util.Collection) */ public byte[] generateListOfInvoicesPdfToPrint(Collection<ContractsGrantsInvoiceDocument> list, byte[] report) throws DocumentException, IOException { Date runDate = new Date(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); generateCombinedPdfForInvoices(list, report, baos); return baos.toByteArray(); } /** * Generates the pdf file for printing the invoices. * * @param list * @param outputStream * @throws DocumentException * @throws IOException */ protected void generateCombinedPdfForInvoices(Collection<ContractsGrantsInvoiceDocument> list, byte[] report, OutputStream outputStream) throws DocumentException, IOException { PdfCopyFields copy = new PdfCopyFields(outputStream); copy.open(); copy.addDocument(new PdfReader(report)); for (ContractsGrantsInvoiceDocument invoice : list) { for (InvoiceAddressDetail invoiceAddressDetail : invoice.getInvoiceAddressDetails()) { Note note = noteService.getNoteByNoteId(invoiceAddressDetail.getNoteId()); if (ObjectUtils.isNotNull(note) && note.getAttachment().getAttachmentFileSize() > 0) { copy.addDocument(new PdfReader(note.getAttachment().getAttachmentContents())); } } } copy.close(); } /** * * @see org.kuali.kfs.module.ar.document.service.DunningLetterDistributionService#isValidOrganizationForTemplate(org.kuali.kfs.module.ar.businessobject.DunningLetterTemplate, org.kuali.rice.kim.api.identity.Person) */ @Override public boolean isValidOrganizationForTemplate(DunningLetterTemplate template, Person user) { final ChartOrgHolder userChartOrg = getFinancialSystemUserService().getPrimaryOrganization(user, ArConstants.AR_NAMESPACE_CODE); if (!StringUtils.isBlank(template.getBillByChartOfAccountCode()) && !StringUtils.isBlank(template.getBilledByOrganizationCode())) { return StringUtils.equals(template.getBillByChartOfAccountCode(), userChartOrg.getChartOfAccountsCode()) && StringUtils.equals(template.getBilledByOrganizationCode(), userChartOrg.getOrganizationCode()); } return false; } /** * @see org.kuali.kfs.module.ar.document.service.DunningLetterDistributionService#getPopulatedDunningLetterDistributionLookupResults(java.util.Collection) */ @Override public Collection<GenerateDunningLettersLookupResult> getPopulatedGenerateDunningLettersLookupResults(Collection<ContractsGrantsInvoiceDocument> invoices) { Collection<GenerateDunningLettersLookupResult> populatedGenerateDunningLettersLookupResults = new ArrayList<GenerateDunningLettersLookupResult>(); if (CollectionUtils.isEmpty(invoices)) { return populatedGenerateDunningLettersLookupResults; } Iterator iter = getContractsGrantsInvoiceDocumentService().getInvoicesByAward(invoices).entrySet().iterator(); GenerateDunningLettersLookupResult generateDunningLettersLookupResult = null; while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); List<ContractsGrantsInvoiceDocument> list = (List<ContractsGrantsInvoiceDocument>) entry.getValue(); if (CollectionUtils.isNotEmpty(list)){ // Get data from first award for agency data ContractsGrantsInvoiceDocument document = list.get(0); ContractsAndGrantsBillingAward award = document.getInvoiceGeneralDetail().getAward(); if (ObjectUtils.isNotNull(award) && !award.isStopWorkIndicator()) { generateDunningLettersLookupResult = new GenerateDunningLettersLookupResult(); generateDunningLettersLookupResult.setProposalNumber(award.getProposalNumber()); generateDunningLettersLookupResult.setInvoiceDocumentNumber(document.getDocumentNumber()); generateDunningLettersLookupResult.setAgencyNumber(award.getAgencyNumber()); generateDunningLettersLookupResult.setCustomerNumber(document.getAccountsReceivableDocumentHeader().getCustomerNumber()); generateDunningLettersLookupResult.setAwardTotal(award.getAwardTotalAmount()); generateDunningLettersLookupResult.setCampaignID(award.getDunningCampaign()); if (CollectionUtils.isNotEmpty(document.getAccountDetails())) { generateDunningLettersLookupResult.setAccountNumber(document.getAccountDetails().get(0).getAccountNumber()); } generateDunningLettersLookupResult.setInvoices(list); populatedGenerateDunningLettersLookupResults.add(generateDunningLettersLookupResult); } } } return populatedGenerateDunningLettersLookupResults; } /** * Maps the given ContractsGrantsInvoiceDocuments by their agency number * @param invoices the invoices to Map to agency number * @return the Map of the invoices */ protected Map<Long, List<ContractsGrantsInvoiceDocument>> getInvoicesByAward(Collection<ContractsGrantsInvoiceDocument> invoices) { Map<Long, List<ContractsGrantsInvoiceDocument>> invoicesByAward = new HashMap<Long, List<ContractsGrantsInvoiceDocument>>(); for (ContractsGrantsInvoiceDocument invoice : invoices) { Long proposalNumber = invoice.getInvoiceGeneralDetail().getProposalNumber(); if (invoicesByAward.containsKey(proposalNumber)) { invoicesByAward.get(proposalNumber).add(invoice); } else { List<ContractsGrantsInvoiceDocument> invoicesByProposalNumber = new ArrayList<ContractsGrantsInvoiceDocument>(); invoicesByProposalNumber.add(invoice); invoicesByAward.put(proposalNumber, invoicesByProposalNumber); } } return invoicesByAward; } /** * Gets the businessObjectService attribute. * * @return Returns the businessObjectService. */ public BusinessObjectService getBusinessObjectService() { return businessObjectService; } /** * Sets the businessObjectService attribute value. * * @param businessObjectService The businessObjectService to set. */ public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } /** * Gets the contractsGrantsInvoiceDocumentDao attribute. * * @return Returns the contractsGrantsInvoiceDocumentDao. */ public ContractsGrantsInvoiceDocumentDao getContractsGrantsInvoiceDocumentDao() { return contractsGrantsInvoiceDocumentDao; } /** * Sets the contractsGrantsInvoiceDocumentDao attribute value. * * @param contractsGrantsInvoiceDocumentDao The contractsGrantsInvoiceDocumentDao to set. */ public void setContractsGrantsInvoiceDocumentDao(ContractsGrantsInvoiceDocumentDao contractsGrantsInvoiceDocumentDao) { this.contractsGrantsInvoiceDocumentDao = contractsGrantsInvoiceDocumentDao; } public DateTimeService getDateTimeService() { return dateTimeService; } public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } /** * Sets the kualiModuleService attribute value. * * @param kualiModuleService The kualiModuleService to set. */ public void setKualiModuleService(KualiModuleService kualiModuleService) { this.kualiModuleService = kualiModuleService; } public NoteService getNoteService() { return noteService; } public void setNoteService(NoteService noteService) { this.noteService = noteService; } public FinancialSystemUserService getFinancialSystemUserService() { return financialSystemUserService; } public void setFinancialSystemUserService(FinancialSystemUserService financialSystemUserService) { this.financialSystemUserService = financialSystemUserService; } public ContractsGrantsBillingUtilityService getContractsGrantsBillingUtilityService() { return contractsGrantsBillingUtilityService; } public void setContractsGrantsBillingUtilityService(ContractsGrantsBillingUtilityService contractsGrantsBillingUtilityService) { this.contractsGrantsBillingUtilityService = contractsGrantsBillingUtilityService; } public ContractsGrantsInvoiceDocumentService getContractsGrantsInvoiceDocumentService() { return contractsGrantsInvoiceDocumentService; } public void setContractsGrantsInvoiceDocumentService(ContractsGrantsInvoiceDocumentService contractsGrantsInvoiceDocumentService) { this.contractsGrantsInvoiceDocumentService = contractsGrantsInvoiceDocumentService; } public ParameterService getParameterService() { return parameterService; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } }