/* * 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.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.mail.MessagingException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.module.ar.ArConstants; import org.kuali.kfs.module.ar.batch.LockboxLoadStep; import org.kuali.kfs.module.ar.batch.service.LockboxLoadService; import org.kuali.kfs.module.ar.businessobject.Lockbox; import org.kuali.kfs.module.ar.businessobject.LockboxDetail; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.batch.BatchInputFileType; import org.kuali.kfs.sys.batch.FlatFileInformation; import org.kuali.kfs.sys.batch.FlatFileTransactionInformation; import org.kuali.kfs.sys.batch.service.BatchInputFileService; import org.kuali.kfs.sys.exception.ParseException; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.core.api.mail.MailMessage; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.krad.exception.InvalidAddressException; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.MailService; import org.kuali.rice.krad.util.GlobalVariables; import org.springframework.transaction.annotation.Transactional; /** * * @see org.kuali.kfs.module.ar.batch.service.LockboxLoadService */ @Transactional public class LockboxLoadServiceImpl implements LockboxLoadService { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LockboxLoadServiceImpl.class); private BatchInputFileType batchInputFileType; private String reportsDirectory; private BatchInputFileService batchInputFileService; private BusinessObjectService businessObjectService; private DateTimeService dateTimeService; private MailService mailService; private ParameterService parameterService; @Override public boolean loadFile() { boolean result = true; //List<LockboxLoadFileResult> fileResults = new ArrayList<LockboxLoadFileResult>(); List<FlatFileInformation> flatFileInformationList = new ArrayList<FlatFileInformation>(); //LockboxLoadFileResult fileResult = null; FlatFileInformation flatFileInformation = null; List<String> fileNamesToLoad = getListOfFilesToProcess(); LOG.info("Found " + fileNamesToLoad.size() + " file(s) to process."); List<String> processedFiles = new ArrayList<String>(); for (String inputFileName : fileNamesToLoad) { LOG.info("Beginning processing of filename: " + inputFileName + "."); flatFileInformation = new FlatFileInformation(inputFileName); flatFileInformationList.add(flatFileInformation); if (loadFile(inputFileName, flatFileInformation)) { processedFiles.add(inputFileName); flatFileInformation.addFileInfoMessage("File successfully completed processing."); } else { flatFileInformation.addFileErrorMessage("File failed to process successfully."); } } // remove done files removeDoneFiles(processedFiles); // SendEmail sendLoadSummaryEmail(flatFileInformationList); return result ; } public boolean loadFile(String fileName,FlatFileInformation flatFileInformation) { boolean valid = true; // load up the file into a byte array byte[] fileByteContent = safelyLoadFileBytes(fileName); // parse the file against the configuration define in the spring file and load it into an object LOG.info("Attempting to parse the file "); Object parsedObject = null; try { parsedObject = batchInputFileService.parse(batchInputFileType, fileByteContent); } catch (ParseException e) { LOG.error("Error parsing batch file: " + e.getMessage()); flatFileInformation.addFileErrorMessage("Error parsing batch file: " + e.getMessage()); valid = false; // throw new ParseException(e.getMessage()); } // validate the parsed data if (parsedObject != null ) { valid = validate(parsedObject); copyAllMessage(parsedObject,flatFileInformation); if (valid) { loadLockbox(parsedObject); } } return valid ; } @Override public boolean validate(Object parsedFileContents) { // compare header with detail record boolean valid = true; List<Lockbox> lockboxList = (List<Lockbox>)parsedFileContents; for (Lockbox lockbox : lockboxList) { if (! compareDetailsWithHeader(lockbox)) { valid = false; break; } } return valid; } /** * No processing */ @Override public void process(String fileName, Object parsedFileContents) {} protected boolean compareDetailsWithHeader(Lockbox lockbox) { boolean isHeaderMatchedDetails = true; KualiDecimal headerTransBatchTotal = lockbox.getHeaderTransactionBatchTotal(); Integer headerTransBatchCount = lockbox.getHeaderTransactionBatchCount(); KualiDecimal detailInvPaidTotal = new KualiDecimal(0); Integer totalDetailRecords = 0; for (LockboxDetail detail : lockbox.getLockboxDetails() ) { detailInvPaidTotal = detailInvPaidTotal.add(detail.getInvoicePaidOrAppliedAmount()); totalDetailRecords++; } if (headerTransBatchTotal.compareTo(detailInvPaidTotal)== 0 && headerTransBatchCount.compareTo(totalDetailRecords) == 0) { String message = "Good Transfer for lockbox number " + lockbox.getLockboxNumber() + "." + " Transaction count : " + lockbox.getHeaderTransactionBatchCount() + " Transaction total amount : $ " + lockbox.getHeaderTransactionBatchTotal(); lockbox.getFlatFileTransactionInformation().addInfoMessage(message); GlobalVariables.getMessageMap().putInfo(KFSConstants.GLOBAL_ERRORS,KFSKeyConstants.ERROR_CUSTOM, message); } if (headerTransBatchTotal.compareTo(detailInvPaidTotal)!= 0) { String message = "Bad Transmmission for lock box number " + lockbox.getLockboxNumber() + "." + " Detail does not match header control values " + " Header total : $ " + lockbox.getHeaderTransactionBatchTotal() + " Detail total : $ " + detailInvPaidTotal; lockbox.getFlatFileTransactionInformation().addErrorMessage(message); GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS,KFSKeyConstants.ERROR_CUSTOM, message); isHeaderMatchedDetails = false; } if (headerTransBatchCount.compareTo(totalDetailRecords) != 0 ) { String message = "Bad Transmmission for lock box number " + lockbox.getLockboxNumber() + "." + " Detail does not match header control values " + " Header Count : " + lockbox.getHeaderTransactionBatchCount() + " Detail total : " + totalDetailRecords; lockbox.getFlatFileTransactionInformation().addErrorMessage(message); GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS,KFSKeyConstants.ERROR_CUSTOM, message); isHeaderMatchedDetails = false; } return isHeaderMatchedDetails; } /** * Send load lockbox file validation * * @param report */ public void sendLoadSummaryEmail(List<FlatFileInformation> flatFileInformationList) { for(FlatFileInformation information : flatFileInformationList) { sendEmail(information); } } public void sendEmail(FlatFileInformation flatFileInformation) { LOG.debug("sendEmail() starting"); MailMessage message = new MailMessage(); // String returnAddress = parameterService.getParameterValueAsString(KFSConstants.OptionalModuleNamespaces.ACCOUNTS_RECEIVABLE, ParameterConstants.BATCH_COMPONENT, "IU_FROM_EMAIL_ADDRESS"); // if(StringUtils.isEmpty(returnAddress)) { String returnAddress = mailService.getBatchMailingList(); // } message.setFromAddress(returnAddress); String subject = parameterService.getParameterValueAsString(LockboxLoadStep.class, ArConstants.Lockbox.SUMMARY_AND_ERROR_NOTIFICATION_EMAIL_SUBJECT); //KFSMI-11479: discovered that the backslashes don't work for Linux machines. Need to use File.separator instead for fileName to work. String fileName = StringUtils.substringAfterLast(flatFileInformation.getFileName(), "\\"); message.setSubject(subject + "[ " + fileName + " ]"); List<String> toAddressList = new ArrayList<String>( parameterService.getParameterValuesAsString(LockboxLoadStep.class, ArConstants.Lockbox.SUMMARY_AND_ERROR_NOTIFICATION_TO_EMAIL_ADDRESSES) ); message.getToAddresses().addAll(toAddressList); String body = composeLockboxLoadBody(flatFileInformation); message.setMessage(body); try { mailService.sendMessage(message); } catch (InvalidAddressException e) { LOG.error("sendErrorEmail() Invalid email address. Message not sent", e); } catch (MessagingException me) { throw new RuntimeException("Could not send mail", me); } } protected String composeLockboxLoadBody( FlatFileInformation flatFileInformation) { String contactText = parameterService.getParameterValueAsString(LockboxLoadStep.class, ArConstants.Lockbox.CONTACTS_TEXT); StringBuffer body = new StringBuffer(); body.append(contactText); body.append("\n"); for(Object object : flatFileInformation.getFlatFileIdentifierToTransactionInfomationMap().values()) { for (String[] message : ((FlatFileTransactionInformation)object).getMessages()) { body.append(message[1]); body.append("\n"); } } for(String[] resultMessage : flatFileInformation.getMessages()) { body.append(resultMessage[1]); body.append("\n"); } return body.toString(); } protected List<String> getListOfFilesToProcess() { // create a list of the files to process List<String> fileNamesToLoad = batchInputFileService.listInputFileNamesWithDoneFile(batchInputFileType); if (fileNamesToLoad == null) { LOG.error("BatchInputFileService.listInputFileNamesWithDoneFile(" + batchInputFileType.getFileTypeIdentifer() + ") returned NULL which should never happen."); throw new RuntimeException("BatchInputFileService.listInputFileNamesWithDoneFile(" + batchInputFileType.getFileTypeIdentifer() + ") returned NULL which should never happen."); } // filenames returned should never be blank/empty/null for (String inputFileName : fileNamesToLoad) { if (StringUtils.isBlank(inputFileName)) { LOG.error("One of the file names returned as ready to process [" + inputFileName + "] was blank. This should not happen, so throwing an error to investigate."); throw new RuntimeException("One of the file names returned as ready to process [" + inputFileName + "] was blank. This should not happen, so throwing an error to investigate."); } } return fileNamesToLoad; } /** * * Accepts a file name and returns a byte-array of the file name contents, if possible. * * Throws RuntimeExceptions if FileNotFound or IOExceptions occur. * * @param fileName String containing valid path & filename (relative or absolute) of file to load. * @return A Byte Array of the contents of the file. */ protected byte[] safelyLoadFileBytes(String fileName) { byte[] fileByteContent; try (InputStream fileContents = new FileInputStream(fileName)){ fileByteContent = IOUtils.toByteArray(fileContents); } catch (FileNotFoundException e1) { LOG.error("Batch file not found [" + fileName + "]. " + e1.getMessage()); throw new RuntimeException("Batch File not found [" + fileName + "]. " + e1.getMessage()); } catch (IOException e1) { LOG.error("IO Exception loading: [" + fileName + "]. " + e1.getMessage()); throw new RuntimeException("IO Exception loading: [" + fileName + "]. " + e1.getMessage()); } return fileByteContent; } protected void loadLockbox(Object parsedObject) { // create the lockbox object to load data List loadLockboxList = new ArrayList<Lockbox>(); List<Lockbox> lockboxList = (List<Lockbox>)parsedObject; int batchSequenceNumber = 1; for (Lockbox lockbox : lockboxList) { setLockboxToLoad(loadLockboxList, lockbox, batchSequenceNumber); batchSequenceNumber ++; } // save lockbox data in AR_LOCKBOX_T businessObjectService.save(loadLockboxList); } /** * Clears out associated .done files for the processed data files. */ protected void removeDoneFiles(List<String> dataFileNames) { for (String dataFileName : dataFileNames) { File doneFile = new File(StringUtils.substringBeforeLast(dataFileName, ".") + ".done"); if (doneFile.exists()) { doneFile.delete(); } } } protected void setLockboxToLoad(List loadLockboxList ,Lockbox lockbox, int batchSequenceNumber ) { for (LockboxDetail detail : lockbox.getLockboxDetails()) { Lockbox lockboxToLoad = new Lockbox(); lockboxToLoad.setLockboxNumber(lockbox.getLockboxNumber()); lockboxToLoad.setScannedInvoiceDate(lockbox.getScannedInvoiceDate()); lockboxToLoad.setCustomerNumber(detail.getCustomerNumber()); lockboxToLoad.setBatchSequenceNumber(batchSequenceNumber); lockboxToLoad.setProcessedInvoiceDate(dateTimeService.getCurrentSqlDate()); lockboxToLoad.setFinancialDocumentReferenceInvoiceNumber(detail.getFinancialDocumentReferenceInvoiceNumber()); lockboxToLoad.setBillingDate(detail.getBillingDate()); lockboxToLoad.setInvoiceTotalAmount(detail.getInvoiceTotalAmount()); lockboxToLoad.setInvoicePaidOrAppliedAmount(detail.getInvoicePaidOrAppliedAmount()); lockboxToLoad.setCustomerPaymentMediumCode(detail.getCustomerPaymentMediumCode()); loadLockboxList.add(lockboxToLoad); } } protected void copyAllMessage(Object parsedObject, FlatFileInformation flatFileInformation) { List<Lockbox> lockboxList = (List<Lockbox>)parsedObject; for (Lockbox lockbox : lockboxList) { FlatFileTransactionInformation information = lockbox.getFlatFileTransactionInformation(); flatFileInformation.getOrAddFlatFileData(lockbox.getLockboxNumber(), information); } } @Override public String getFileName(String principalName, Object parsedFileContents, String fileUserIdentifier) { // TODO Auto-generated method stub return null; } public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } public void setBatchInputFileType(BatchInputFileType batchInputFileType) { this.batchInputFileType = batchInputFileType; } public void setReportsDirectory(String reportsDirectory) { this.reportsDirectory = reportsDirectory; } public void setBatchInputFileService(BatchInputFileService batchInputFileService) { this.batchInputFileService = batchInputFileService; } public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } public void setMailService(MailService mailService) { this.mailService = mailService; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } }